| ... | ... | @@ -203,6 +203,9 @@ export function codexProviderTurnTokenUsage( |
| 203 | 203 | } |
| 204 | 204 | const DEFAULT_CODEX_SETTINGS = Schema.decodeSync(CodexSettings)({}); |
| 205 | 205 | const CODEX_ASSISTANT_DELTA_FLUSH_INTERVAL_MS = 50; |
| 206 | // Each plan flush stores the whole plan three times (node, plan, turn item), so |
| 207 | // flush at the cadence clients already see for assistant text. |
| 208 | const CODEX_PLAN_DELTA_FLUSH_INTERVAL_MS = 400; |
| 206 | 209 | const CodexBackgroundTerminalTerminateResponse = Schema.Struct({ |
| 207 | 210 | terminated: Schema.Boolean, |
| 208 | 211 | }); |
| ... | ... | @@ -1864,8 +1867,6 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* ( |
| 1864 | 1867 | const providerRetries = yield* Ref.make( |
| 1865 | 1868 | new Map<ProviderTurnId, ActiveCodexProviderRetry>(), |
| 1866 | 1869 | ); |
| 1867 | | // Streamed plan text per plan item, dropped when the item completes. |
| 1868 | | const planDeltas = new Map<string, string>(); |
| 1869 | 1870 | const planIds = yield* Ref.make(new Map<string, OrchestrationV2PlanArtifact["id"]>()); |
| 1870 | 1871 | const pendingRuntimeRequests = yield* Ref.make( |
| 1871 | 1872 | new Map<string, PendingCodexRuntimeRequest>(), |
| ... | ... | @@ -3948,6 +3949,43 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* ( |
| 3948 | 3949 | return { node, plan, turnItem }; |
| 3949 | 3950 | }); |
| 3950 | 3951 | |
| 3952 | // Streamed plan text, flushed at most once per interval. A turn that ends |
| 3953 | // early leaves its partial plan streaming, as an unfinished plan item. |
| 3954 | const planDeltas = yield* makeProviderTextDeltaCoalescer({ |
| 3955 | flushIntervalMs: CODEX_PLAN_DELTA_FLUSH_INTERVAL_MS, |
| 3956 | emit: (update) => |
| 3957 | Effect.gen(function* () { |
| 3958 | const resolved = yield* resolveItemEventContext(update.turnId); |
| 3959 | if (resolved === undefined) return; |
| 3960 | const interrupted = (yield* Ref.get(terminalizedNonCompletedNativeTurns)).has( |
| 3961 | update.turnId, |
| 3962 | ); |
| 3963 | const artifacts = yield* buildProposedPlanArtifacts({ |
| 3964 | context: resolved.context, |
| 3965 | nativeItemId: update.itemId, |
| 3966 | // A finished proposal stays active until Implement consumes it. |
| 3967 | status: "active", |
| 3968 | markdown: update.text, |
| 3969 | completed: update.completed && !interrupted, |
| 3970 | }); |
| 3971 | yield* emitProviderEvent({ |
| 3972 | type: "node.updated", |
| 3973 | driver: CODEX_PROVIDER, |
| 3974 | node: artifacts.node, |
| 3975 | }); |
| 3976 | yield* emitProviderEvent({ |
| 3977 | type: "plan.updated", |
| 3978 | driver: CODEX_PROVIDER, |
| 3979 | plan: artifacts.plan, |
| 3980 | }); |
| 3981 | yield* emitProviderEvent({ |
| 3982 | type: "turn_item.updated", |
| 3983 | driver: CODEX_PROVIDER, |
| 3984 | turnItem: artifacts.turnItem, |
| 3985 | }); |
| 3986 | }).pipe(Effect.orDie), |
| 3987 | }); |
| 3988 | |
| 3951 | 3989 | const buildTodoListArtifacts = (input: { |
| 3952 | 3990 | readonly context: ActiveCodexTurnContext; |
| 3953 | 3991 | readonly nativeItemId: string; |
| ... | ... | @@ -4245,28 +4283,10 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* ( |
| 4245 | 4283 | return; |
| 4246 | 4284 | } |
| 4247 | 4285 | yield* completeProviderRetry(context, yield* DateTime.now); |
| 4248 | | const markdown = `${planDeltas.get(payload.itemId) ?? ""}${payload.delta}`; |
| 4249 | | planDeltas.set(payload.itemId, markdown); |
| 4250 | | const artifacts = yield* buildProposedPlanArtifacts({ |
| 4251 | | context, |
| 4252 | | nativeItemId: payload.itemId, |
| 4253 | | status: "active", |
| 4254 | | markdown, |
| 4255 | | }); |
| 4256 | | yield* emitProviderEvent({ |
| 4257 | | type: "node.updated", |
| 4258 | | driver: CODEX_PROVIDER, |
| 4259 | | node: artifacts.node, |
| 4260 | | }); |
| 4261 | | yield* emitProviderEvent({ |
| 4262 | | type: "plan.updated", |
| 4263 | | driver: CODEX_PROVIDER, |
| 4264 | | plan: artifacts.plan, |
| 4265 | | }); |
| 4266 | | yield* emitProviderEvent({ |
| 4267 | | type: "turn_item.updated", |
| 4268 | | driver: CODEX_PROVIDER, |
| 4269 | | turnItem: artifacts.turnItem, |
| 4286 | yield* planDeltas.append({ |
| 4287 | turnId: payload.turnId, |
| 4288 | itemId: payload.itemId, |
| 4289 | delta: payload.delta, |
| 4270 | 4290 | }); |
| 4271 | 4291 | }).pipe(Effect.orDie), |
| 4272 | 4292 | ); |
| ... | ... | @@ -4916,33 +4936,10 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* ( |
| 4916 | 4936 | } |
| 4917 | 4937 | |
| 4918 | 4938 | if (payload.item.type === "plan") { |
| 4919 | | const markdown = |
| 4920 | | payload.item.text.length > 0 |
| 4921 | | ? payload.item.text |
| 4922 | | : (planDeltas.get(payload.item.id) ?? ""); |
| 4923 | | planDeltas.delete(payload.item.id); |
| 4924 | | // A finished proposal stays active until Implement consumes it. |
| 4925 | | const artifacts = yield* buildProposedPlanArtifacts({ |
| 4926 | | context, |
| 4927 | | nativeItemId: payload.item.id, |
| 4928 | | status: "active", |
| 4929 | | markdown, |
| 4930 | | completed: true, |
| 4931 | | }); |
| 4932 | | yield* emitProviderEvent({ |
| 4933 | | type: "node.updated", |
| 4934 | | driver: CODEX_PROVIDER, |
| 4935 | | node: artifacts.node, |
| 4936 | | }); |
| 4937 | | yield* emitProviderEvent({ |
| 4938 | | type: "plan.updated", |
| 4939 | | driver: CODEX_PROVIDER, |
| 4940 | | plan: artifacts.plan, |
| 4941 | | }); |
| 4942 | | yield* emitProviderEvent({ |
| 4943 | | type: "turn_item.updated", |
| 4944 | | driver: CODEX_PROVIDER, |
| 4945 | | turnItem: artifacts.turnItem, |
| 4939 | yield* planDeltas.complete({ |
| 4940 | turnId: payload.turnId, |
| 4941 | itemId: payload.item.id, |
| 4942 | ...(payload.item.text.length > 0 ? { finalText: payload.item.text } : {}), |
| 4946 | 4943 | }); |
| 4947 | 4944 | return; |
| 4948 | 4945 | } |
| ... | ... | @@ -5780,6 +5777,7 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* ( |
| 5780 | 5777 | } |
| 5781 | 5778 | yield* agentMessageDeltas.flushTurn(input.nativeTurnId); |
| 5782 | 5779 | yield* reasoningDeltas.flushTurn(input.nativeTurnId); |
| 5780 | yield* planDeltas.flushTurn(input.nativeTurnId); |
| 5783 | 5781 | for (const [key, part] of reasoningParts) { |
| 5784 | 5782 | if (part.turnId === input.nativeTurnId) reasoningParts.delete(key); |
| 5785 | 5783 | } |