@qduc/term2 0.32.2 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +30 -14
- package/dist/agent.js.map +1 -1
- package/dist/build-info.json +2 -2
- package/dist/completion-verification-admin.d.ts +2 -0
- package/dist/completion-verification-admin.d.ts.map +1 -0
- package/dist/completion-verification-admin.js +37 -0
- package/dist/completion-verification-admin.js.map +1 -0
- package/dist/components/input/MenuSurface.d.ts.map +1 -1
- package/dist/components/input/MenuSurface.js +12 -3
- package/dist/components/input/MenuSurface.js.map +1 -1
- package/dist/components/input/ModelMenuSession.d.ts.map +1 -1
- package/dist/components/input/ModelMenuSession.js +15 -1
- package/dist/components/input/ModelMenuSession.js.map +1 -1
- package/dist/components/input/ProviderMenuSession.d.ts.map +1 -1
- package/dist/components/input/ProviderMenuSession.js +2 -2
- package/dist/components/input/ProviderMenuSession.js.map +1 -1
- package/dist/components/input/SettingsMenuSession.d.ts.map +1 -1
- package/dist/components/input/SettingsMenuSession.js +119 -82
- package/dist/components/input/SettingsMenuSession.js.map +1 -1
- package/dist/components/input/SettingsValueMenuSession.d.ts.map +1 -1
- package/dist/components/input/SettingsValueMenuSession.js +18 -3
- package/dist/components/input/SettingsValueMenuSession.js.map +1 -1
- package/dist/components/input/determine-active-menu.d.ts.map +1 -1
- package/dist/components/input/determine-active-menu.js +5 -1
- package/dist/components/input/determine-active-menu.js.map +1 -1
- package/dist/components/input/settings-intent-host.d.ts.map +1 -1
- package/dist/components/input/settings-intent-host.js +60 -4
- package/dist/components/input/settings-intent-host.js.map +1 -1
- package/dist/components/menu/ModelSelectionMenu.d.ts +4 -0
- package/dist/components/menu/ModelSelectionMenu.d.ts.map +1 -1
- package/dist/components/menu/ModelSelectionMenu.js +15 -16
- package/dist/components/menu/ModelSelectionMenu.js.map +1 -1
- package/dist/components/menu/ProviderSelectionMenu.d.ts +1 -0
- package/dist/components/menu/ProviderSelectionMenu.d.ts.map +1 -1
- package/dist/components/menu/ProviderSelectionMenu.js +21 -6
- package/dist/components/menu/ProviderSelectionMenu.js.map +1 -1
- package/dist/components/menu/SettingsSelectionMenu.d.ts +3 -0
- package/dist/components/menu/SettingsSelectionMenu.d.ts.map +1 -1
- package/dist/components/menu/SettingsSelectionMenu.js +78 -47
- package/dist/components/menu/SettingsSelectionMenu.js.map +1 -1
- package/dist/components/menu/SettingsValueSelectionMenu.d.ts +2 -0
- package/dist/components/menu/SettingsValueSelectionMenu.d.ts.map +1 -1
- package/dist/components/menu/SettingsValueSelectionMenu.js +17 -12
- package/dist/components/menu/SettingsValueSelectionMenu.js.map +1 -1
- package/dist/components/menu/settings-value-formatter.d.ts +8 -3
- package/dist/components/menu/settings-value-formatter.d.ts.map +1 -1
- package/dist/components/menu/settings-value-formatter.js +55 -32
- package/dist/components/menu/settings-value-formatter.js.map +1 -1
- package/dist/components/message/command-message-helpers.d.ts +5 -5
- package/dist/contracts/run-termination.d.ts +3 -1
- package/dist/contracts/run-termination.d.ts.map +1 -1
- package/dist/hooks/settings-completion-config.d.ts +28 -12
- package/dist/hooks/settings-completion-config.d.ts.map +1 -1
- package/dist/hooks/settings-completion-config.js +73 -76
- package/dist/hooks/settings-completion-config.js.map +1 -1
- package/dist/hooks/settings-completion-logic.d.ts +10 -1
- package/dist/hooks/settings-completion-logic.d.ts.map +1 -1
- package/dist/hooks/settings-completion-logic.js +80 -49
- package/dist/hooks/settings-completion-logic.js.map +1 -1
- package/dist/hooks/use-provider-selection.d.ts +6 -0
- package/dist/hooks/use-provider-selection.d.ts.map +1 -1
- package/dist/hooks/use-provider-selection.js +69 -10
- package/dist/hooks/use-provider-selection.js.map +1 -1
- package/dist/hooks/use-settings-completion.d.ts +14 -0
- package/dist/hooks/use-settings-completion.d.ts.map +1 -1
- package/dist/hooks/use-settings-completion.js +54 -6
- package/dist/hooks/use-settings-completion.js.map +1 -1
- package/dist/hooks/use-settings-value-completion.d.ts.map +1 -1
- package/dist/hooks/use-settings-value-completion.js +15 -4
- package/dist/hooks/use-settings-value-completion.js.map +1 -1
- package/dist/lib/agent-chat-service.d.ts.map +1 -1
- package/dist/lib/agent-chat-service.js +2 -6
- package/dist/lib/agent-chat-service.js.map +1 -1
- package/dist/lib/agent-client.d.ts +2 -1
- package/dist/lib/agent-client.d.ts.map +1 -1
- package/dist/lib/agent-client.js +99 -85
- package/dist/lib/agent-client.js.map +1 -1
- package/dist/lib/agent-factory.d.ts.map +1 -1
- package/dist/lib/agent-factory.js +17 -19
- package/dist/lib/agent-factory.js.map +1 -1
- package/dist/non-interactive.d.ts.map +1 -1
- package/dist/non-interactive.js +4 -0
- package/dist/non-interactive.js.map +1 -1
- package/dist/prompts/tool-surface-guidance.js +1 -1
- package/dist/prompts/tool-surface-guidance.js.map +1 -1
- package/dist/prompts/tool-surface-guidance.ts +1 -1
- package/dist/providers/ai-sdk-anthropic.provider.d.ts.map +1 -1
- package/dist/providers/ai-sdk-anthropic.provider.js +16 -2
- package/dist/providers/ai-sdk-anthropic.provider.js.map +1 -1
- package/dist/providers/ai-sdk-google.provider.d.ts.map +1 -1
- package/dist/providers/ai-sdk-google.provider.js +16 -2
- package/dist/providers/ai-sdk-google.provider.js.map +1 -1
- package/dist/providers/ai-sdk-openrouter.provider.d.ts.map +1 -1
- package/dist/providers/ai-sdk-openrouter.provider.js +16 -1
- package/dist/providers/ai-sdk-openrouter.provider.js.map +1 -1
- package/dist/providers/ai-sdk-provider-settings.d.ts +2 -0
- package/dist/providers/ai-sdk-provider-settings.d.ts.map +1 -1
- package/dist/providers/ai-sdk-provider-settings.js +5 -0
- package/dist/providers/ai-sdk-provider-settings.js.map +1 -1
- package/dist/providers/ai-sdk-streamed-model.d.ts +3 -3
- package/dist/providers/ai-sdk-streamed-model.d.ts.map +1 -1
- package/dist/providers/ai-sdk-streamed-model.js +5 -69
- package/dist/providers/ai-sdk-streamed-model.js.map +1 -1
- package/dist/providers/anthropic-chat-caching.d.ts +5 -0
- package/dist/providers/anthropic-chat-caching.d.ts.map +1 -0
- package/dist/providers/{common/openai-compatible-messages.js → anthropic-chat-caching.js} +19 -1
- package/dist/providers/anthropic-chat-caching.js.map +1 -0
- package/dist/providers/codex.provider.js +2 -0
- package/dist/providers/codex.provider.js.map +1 -1
- package/dist/providers/custom-provider-runtime.d.ts +18 -0
- package/dist/providers/custom-provider-runtime.d.ts.map +1 -0
- package/dist/providers/custom-provider-runtime.js +26 -0
- package/dist/providers/custom-provider-runtime.js.map +1 -0
- package/dist/providers/grok.provider.d.ts +13 -3
- package/dist/providers/grok.provider.d.ts.map +1 -1
- package/dist/providers/grok.provider.js +82 -6
- package/dist/providers/grok.provider.js.map +1 -1
- package/dist/providers/llama-cpp.provider.d.ts +3 -0
- package/dist/providers/llama-cpp.provider.d.ts.map +1 -1
- package/dist/providers/llama-cpp.provider.js +17 -0
- package/dist/providers/llama-cpp.provider.js.map +1 -1
- package/dist/providers/model-catalog/catalog.generated.d.ts +347 -351
- package/dist/providers/model-catalog/catalog.generated.d.ts.map +1 -1
- package/dist/providers/model-catalog/catalog.generated.js +362 -357
- package/dist/providers/model-catalog/catalog.generated.js.map +1 -1
- package/dist/providers/neuralwatt.provider.d.ts +6 -0
- package/dist/providers/neuralwatt.provider.d.ts.map +1 -0
- package/dist/providers/neuralwatt.provider.js +8 -0
- package/dist/providers/neuralwatt.provider.js.map +1 -0
- package/dist/providers/openai-compatible-lazy.d.ts.map +1 -1
- package/dist/providers/openai-compatible-lazy.js +2 -0
- package/dist/providers/openai-compatible-lazy.js.map +1 -1
- package/dist/providers/openai-compatible-middleware.d.ts +4 -9
- package/dist/providers/openai-compatible-middleware.d.ts.map +1 -1
- package/dist/providers/openai-compatible-middleware.js +25 -48
- package/dist/providers/openai-compatible-middleware.js.map +1 -1
- package/dist/providers/openai-compatible-models.d.ts +9 -1
- package/dist/providers/openai-compatible-models.d.ts.map +1 -1
- package/dist/providers/openai-compatible-models.js +10 -2
- package/dist/providers/openai-compatible-models.js.map +1 -1
- package/dist/providers/openai-compatible-response-normalizer.d.ts +1 -1
- package/dist/providers/openai-compatible-response-normalizer.d.ts.map +1 -1
- package/dist/providers/openai-compatible-response-normalizer.js +6 -6
- package/dist/providers/openai-compatible-response-normalizer.js.map +1 -1
- package/dist/providers/openai-compatible.provider.d.ts +2 -32
- package/dist/providers/openai-compatible.provider.d.ts.map +1 -1
- package/dist/providers/openai-compatible.provider.js +27 -144
- package/dist/providers/openai-compatible.provider.js.map +1 -1
- package/dist/providers/openai.provider.d.ts.map +1 -1
- package/dist/providers/openai.provider.js +1 -0
- package/dist/providers/openai.provider.js.map +1 -1
- package/dist/providers/opencode-middleware.d.ts +9 -0
- package/dist/providers/opencode-middleware.d.ts.map +1 -0
- package/dist/providers/opencode-middleware.js +15 -0
- package/dist/providers/opencode-middleware.js.map +1 -0
- package/dist/providers/opencode-model-provider.d.ts +19 -0
- package/dist/providers/opencode-model-provider.d.ts.map +1 -0
- package/dist/providers/opencode-model-provider.js +128 -0
- package/dist/providers/opencode-model-provider.js.map +1 -0
- package/dist/providers/opencode.provider.d.ts +2 -0
- package/dist/providers/opencode.provider.d.ts.map +1 -1
- package/dist/providers/opencode.provider.js +4 -0
- package/dist/providers/opencode.provider.js.map +1 -1
- package/dist/providers/openrouter-decision-models.d.ts +7 -0
- package/dist/providers/openrouter-decision-models.d.ts.map +1 -0
- package/dist/providers/openrouter-decision-models.js +31 -0
- package/dist/providers/openrouter-decision-models.js.map +1 -0
- package/dist/providers/openrouter.provider.js +2 -1
- package/dist/providers/openrouter.provider.js.map +1 -1
- package/dist/providers/orcarouter-models.d.ts +12 -0
- package/dist/providers/orcarouter-models.d.ts.map +1 -0
- package/dist/providers/orcarouter-models.js +38 -0
- package/dist/providers/orcarouter-models.js.map +1 -0
- package/dist/providers/provider-presets.d.ts +37 -0
- package/dist/providers/provider-presets.d.ts.map +1 -0
- package/dist/providers/provider-presets.js +33 -0
- package/dist/providers/provider-presets.js.map +1 -0
- package/dist/providers/provider-presets.json +201 -0
- package/dist/providers/provider-service.d.ts +3 -2
- package/dist/providers/provider-service.d.ts.map +1 -1
- package/dist/providers/provider-service.js +4 -20
- package/dist/providers/provider-service.js.map +1 -1
- package/dist/providers/registry.d.ts +7 -0
- package/dist/providers/registry.d.ts.map +1 -1
- package/dist/providers/registry.js.map +1 -1
- package/dist/services/agent-runtime/application-run-loop.d.ts +25 -2
- package/dist/services/agent-runtime/application-run-loop.d.ts.map +1 -1
- package/dist/services/agent-runtime/application-run-loop.js +927 -758
- package/dist/services/agent-runtime/application-run-loop.js.map +1 -1
- package/dist/services/agent-runtime/tool-call-execution.d.ts +1 -1
- package/dist/services/agent-runtime/tool-call-execution.d.ts.map +1 -1
- package/dist/services/agent-runtime/tool-call-execution.js +6 -3
- package/dist/services/agent-runtime/tool-call-execution.js.map +1 -1
- package/dist/services/approval/approval-flow-coordinator.d.ts +4 -2
- package/dist/services/approval/approval-flow-coordinator.d.ts.map +1 -1
- package/dist/services/approval/approval-flow-coordinator.js +2 -1
- package/dist/services/approval/approval-flow-coordinator.js.map +1 -1
- package/dist/services/approval/approval-state.d.ts +0 -13
- package/dist/services/approval/approval-state.d.ts.map +1 -1
- package/dist/services/approval/approval-state.js +0 -5
- package/dist/services/approval/approval-state.js.map +1 -1
- package/dist/services/approval/background-subagent-approval-controller.d.ts.map +1 -1
- package/dist/services/approval/background-subagent-approval-controller.js +0 -2
- package/dist/services/approval/background-subagent-approval-controller.js.map +1 -1
- package/dist/services/approval/nested-approval-owner.d.ts.map +1 -1
- package/dist/services/approval/nested-approval-owner.js +5 -1
- package/dist/services/approval/nested-approval-owner.js.map +1 -1
- package/dist/services/approval/shell-auto-approval-resolver.d.ts +2 -2
- package/dist/services/approval/shell-auto-approval-resolver.d.ts.map +1 -1
- package/dist/services/approval/shell-auto-approval-resolver.js +2 -2
- package/dist/services/approval/shell-auto-approval-resolver.js.map +1 -1
- package/dist/services/approval/tool-approval-batch-coordinator.d.ts +2 -2
- package/dist/services/approval/tool-approval-batch-coordinator.d.ts.map +1 -1
- package/dist/services/approval/tool-approval-batch-coordinator.js +4 -4
- package/dist/services/approval/tool-approval-batch-coordinator.js.map +1 -1
- package/dist/services/assembly/feature-selection.d.ts +48 -0
- package/dist/services/assembly/feature-selection.d.ts.map +1 -0
- package/dist/services/assembly/feature-selection.js +12 -0
- package/dist/services/assembly/feature-selection.js.map +1 -0
- package/dist/services/completion-verification/contracts.d.ts +151 -0
- package/dist/services/completion-verification/contracts.d.ts.map +1 -0
- package/dist/services/completion-verification/contracts.js +84 -0
- package/dist/services/completion-verification/contracts.js.map +1 -0
- package/dist/services/completion-verification/gate.d.ts +9 -0
- package/dist/services/completion-verification/gate.d.ts.map +1 -0
- package/dist/services/completion-verification/gate.js +60 -0
- package/dist/services/completion-verification/gate.js.map +1 -0
- package/dist/services/completion-verification/goal-stop-policy.d.ts +10 -0
- package/dist/services/completion-verification/goal-stop-policy.d.ts.map +1 -0
- package/dist/services/completion-verification/goal-stop-policy.js +53 -0
- package/dist/services/completion-verification/goal-stop-policy.js.map +1 -0
- package/dist/services/completion-verification/orchestrator.d.ts +26 -0
- package/dist/services/completion-verification/orchestrator.d.ts.map +1 -0
- package/dist/services/completion-verification/orchestrator.js +275 -0
- package/dist/services/completion-verification/orchestrator.js.map +1 -0
- package/dist/services/completion-verification/root-boundary.d.ts +21 -0
- package/dist/services/completion-verification/root-boundary.d.ts.map +1 -0
- package/dist/services/completion-verification/root-boundary.js +127 -0
- package/dist/services/completion-verification/root-boundary.js.map +1 -0
- package/dist/services/completion-verification/store.d.ts +13 -0
- package/dist/services/completion-verification/store.d.ts.map +1 -0
- package/dist/services/completion-verification/store.js +69 -0
- package/dist/services/completion-verification/store.js.map +1 -0
- package/dist/services/completion-verification/verifier.d.ts +11 -0
- package/dist/services/completion-verification/verifier.d.ts.map +1 -0
- package/dist/services/completion-verification/verifier.js +173 -0
- package/dist/services/completion-verification/verifier.js.map +1 -0
- package/dist/services/completion-verification/workspace.d.ts +17 -0
- package/dist/services/completion-verification/workspace.d.ts.map +1 -0
- package/dist/services/completion-verification/workspace.js +183 -0
- package/dist/services/completion-verification/workspace.js.map +1 -0
- package/dist/services/conversation/conversation-adapter.d.ts +17 -47
- package/dist/services/conversation/conversation-adapter.d.ts.map +1 -1
- package/dist/services/conversation/conversation-adapter.js +114 -88
- package/dist/services/conversation/conversation-adapter.js.map +1 -1
- package/dist/services/conversation/conversation-orchestrator.d.ts.map +1 -1
- package/dist/services/conversation/conversation-orchestrator.js +48 -121
- package/dist/services/conversation/conversation-orchestrator.js.map +1 -1
- package/dist/services/conversation/conversation-record-stream.d.ts +21 -0
- package/dist/services/conversation/conversation-record-stream.d.ts.map +1 -0
- package/dist/services/conversation/conversation-record-stream.js +186 -0
- package/dist/services/conversation/conversation-record-stream.js.map +1 -0
- package/dist/services/conversation/conversation-record.d.ts +160 -0
- package/dist/services/conversation/conversation-record.d.ts.map +1 -0
- package/dist/services/conversation/conversation-record.js +371 -0
- package/dist/services/conversation/conversation-record.js.map +1 -0
- package/dist/services/conversation/conversation-result-builder.d.ts +0 -4
- package/dist/services/conversation/conversation-result-builder.d.ts.map +1 -1
- package/dist/services/conversation/conversation-result-builder.js +0 -10
- package/dist/services/conversation/conversation-result-builder.js.map +1 -1
- package/dist/services/conversation/conversation-service.d.ts +2 -1
- package/dist/services/conversation/conversation-service.d.ts.map +1 -1
- package/dist/services/conversation/conversation-service.js +3 -0
- package/dist/services/conversation/conversation-service.js.map +1 -1
- package/dist/services/conversation/durable-goal-stop-check.js +1 -1
- package/dist/services/conversation/durable-goal-stop-check.js.map +1 -1
- package/dist/services/conversation/durable-goal.d.ts.map +1 -1
- package/dist/services/conversation/durable-goal.js +1 -1
- package/dist/services/conversation/durable-goal.js.map +1 -1
- package/dist/services/conversation-agent-client.d.ts +17 -7
- package/dist/services/conversation-agent-client.d.ts.map +1 -1
- package/dist/services/logging/conversation-logger.d.ts +1 -7
- package/dist/services/logging/conversation-logger.d.ts.map +1 -1
- package/dist/services/logging/conversation-logger.js +0 -18
- package/dist/services/logging/conversation-logger.js.map +1 -1
- package/dist/services/model-service.d.ts +2 -6
- package/dist/services/model-service.d.ts.map +1 -1
- package/dist/services/model-service.js +8 -21
- package/dist/services/model-service.js.map +1 -1
- package/dist/services/models/decision-model-listing.d.ts +2 -12
- package/dist/services/models/decision-model-listing.d.ts.map +1 -1
- package/dist/services/models/decision-model-listing.js +2 -29
- package/dist/services/models/decision-model-listing.js.map +1 -1
- package/dist/services/provider-continuity.d.ts +26 -0
- package/dist/services/provider-continuity.d.ts.map +1 -1
- package/dist/services/provider-continuity.js +47 -0
- package/dist/services/provider-continuity.js.map +1 -1
- package/dist/services/retry/recovery-executor.d.ts +3 -4
- package/dist/services/retry/recovery-executor.d.ts.map +1 -1
- package/dist/services/retry/recovery-executor.js +13 -75
- package/dist/services/retry/recovery-executor.js.map +1 -1
- package/dist/services/retry/retry-contracts.d.ts +2 -10
- package/dist/services/retry/retry-contracts.d.ts.map +1 -1
- package/dist/services/session/continuation-plan-applier.d.ts +5 -9
- package/dist/services/session/continuation-plan-applier.d.ts.map +1 -1
- package/dist/services/session/continuation-plan-applier.js +9 -43
- package/dist/services/session/continuation-plan-applier.js.map +1 -1
- package/dist/services/session/continuation-recovery-handler.d.ts +4 -6
- package/dist/services/session/continuation-recovery-handler.d.ts.map +1 -1
- package/dist/services/session/continuation-recovery-handler.js +3 -15
- package/dist/services/session/continuation-recovery-handler.js.map +1 -1
- package/dist/services/session/initial-input-preparer.d.ts +2 -2
- package/dist/services/session/initial-input-preparer.d.ts.map +1 -1
- package/dist/services/session/initial-input-preparer.js +3 -3
- package/dist/services/session/initial-input-preparer.js.map +1 -1
- package/dist/services/session/initial-turn-recovery-handler.d.ts +2 -4
- package/dist/services/session/initial-turn-recovery-handler.d.ts.map +1 -1
- package/dist/services/session/initial-turn-recovery-handler.js +4 -5
- package/dist/services/session/initial-turn-recovery-handler.js.map +1 -1
- package/dist/services/session/live-run.d.ts +9 -3
- package/dist/services/session/live-run.d.ts.map +1 -1
- package/dist/services/session/live-run.js +19 -4
- package/dist/services/session/live-run.js.map +1 -1
- package/dist/services/session/session-composition.d.ts +8 -12
- package/dist/services/session/session-composition.d.ts.map +1 -1
- package/dist/services/session/session-composition.js +41 -87
- package/dist/services/session/session-composition.js.map +1 -1
- package/dist/services/session/session-input-planner.d.ts +2 -2
- package/dist/services/session/session-input-planner.d.ts.map +1 -1
- package/dist/services/session/session-input-planner.js +6 -13
- package/dist/services/session/session-input-planner.js.map +1 -1
- package/dist/services/session/session-lifecycle.d.ts +73 -23
- package/dist/services/session/session-lifecycle.d.ts.map +1 -1
- package/dist/services/session/session-lifecycle.js +127 -45
- package/dist/services/session/session-lifecycle.js.map +1 -1
- package/dist/services/session/session-stream-processor.d.ts +4 -30
- package/dist/services/session/session-stream-processor.d.ts.map +1 -1
- package/dist/services/session/session-stream-processor.js +11 -300
- package/dist/services/session/session-stream-processor.js.map +1 -1
- package/dist/services/session/session-tool-tracker.d.ts +9 -144
- package/dist/services/session/session-tool-tracker.d.ts.map +1 -1
- package/dist/services/session/session-tool-tracker.js +17 -251
- package/dist/services/session/session-tool-tracker.js.map +1 -1
- package/dist/services/session/{turn-workflow.d.ts → session-turn-adapter.d.ts} +10 -43
- package/dist/services/session/session-turn-adapter.d.ts.map +1 -0
- package/dist/services/session/session-turn-adapter.js +734 -0
- package/dist/services/session/session-turn-adapter.js.map +1 -0
- package/dist/services/session/turn-attempt-factory.d.ts +2 -11
- package/dist/services/session/turn-attempt-factory.d.ts.map +1 -1
- package/dist/services/session/turn-attempt-factory.js +4 -12
- package/dist/services/session/turn-attempt-factory.js.map +1 -1
- package/dist/services/session/turn-attempt.d.ts +0 -3
- package/dist/services/session/turn-attempt.d.ts.map +1 -1
- package/dist/services/session/turn-attempt.js +0 -5
- package/dist/services/session/turn-attempt.js.map +1 -1
- package/dist/services/session/turn-coordinator.d.ts +3 -3
- package/dist/services/session/turn-coordinator.d.ts.map +1 -1
- package/dist/services/session/turn-coordinator.js +31 -26
- package/dist/services/session/turn-coordinator.js.map +1 -1
- package/dist/services/session/turn-projection-state.d.ts +45 -0
- package/dist/services/session/turn-projection-state.d.ts.map +1 -0
- package/dist/services/session/turn-projection-state.js +61 -0
- package/dist/services/session/turn-projection-state.js.map +1 -0
- package/dist/services/settings/settings-env.d.ts +5 -0
- package/dist/services/settings/settings-env.d.ts.map +1 -1
- package/dist/services/settings/settings-env.js +22 -0
- package/dist/services/settings/settings-env.js.map +1 -1
- package/dist/services/settings/settings-schema.d.ts +8 -0
- package/dist/services/settings/settings-schema.d.ts.map +1 -1
- package/dist/services/settings/settings-schema.js +17 -0
- package/dist/services/settings/settings-schema.js.map +1 -1
- package/dist/services/settings/settings-service.d.ts +6 -0
- package/dist/services/settings/settings-service.d.ts.map +1 -1
- package/dist/services/settings/settings-service.js +21 -9
- package/dist/services/settings/settings-service.js.map +1 -1
- package/dist/services/settings/settings-sources.d.ts +2 -0
- package/dist/services/settings/settings-sources.d.ts.map +1 -1
- package/dist/services/settings/settings-sources.js +2 -0
- package/dist/services/settings/settings-sources.js.map +1 -1
- package/dist/services/settings/settings-ui-metadata.d.ts +21 -0
- package/dist/services/settings/settings-ui-metadata.d.ts.map +1 -1
- package/dist/services/settings/settings-ui-metadata.js +201 -23
- package/dist/services/settings/settings-ui-metadata.js.map +1 -1
- package/dist/services/settings/test-helpers/settings-consumer-inventory.d.ts +1 -0
- package/dist/services/settings/test-helpers/settings-consumer-inventory.d.ts.map +1 -1
- package/dist/services/settings/test-helpers/settings-consumer-inventory.js +4 -0
- package/dist/services/settings/test-helpers/settings-consumer-inventory.js.map +1 -1
- package/dist/services/subagents/tool-policy.d.ts +5 -5
- package/dist/services/subagents/tool-policy.d.ts.map +1 -1
- package/dist/services/subagents/tool-policy.js +15 -9
- package/dist/services/subagents/tool-policy.js.map +1 -1
- package/dist/tools/agent/goal-check.js +2 -2
- package/dist/tools/agent/goal-check.js.map +1 -1
- package/dist/tools/agent/list-models.d.ts +71 -0
- package/dist/tools/agent/list-models.d.ts.map +1 -0
- package/dist/tools/agent/list-models.js +175 -0
- package/dist/tools/agent/list-models.js.map +1 -0
- package/dist/tools/system/run-code/run-code-runtime.d.ts.map +1 -1
- package/dist/tools/system/run-code/run-code-runtime.js +90 -19
- package/dist/tools/system/run-code/run-code-runtime.js.map +1 -1
- package/dist/tools/system/shell.d.ts +2 -2
- package/dist/tools/system/shell.d.ts.map +1 -1
- package/dist/tools/system/shell.js +1 -0
- package/dist/tools/system/shell.js.map +1 -1
- package/dist/tools/tool-names.d.ts +1 -1
- package/dist/tools/tool-names.d.ts.map +1 -1
- package/dist/utils/ai/token-usage.js +9 -8
- package/dist/utils/ai/token-usage.js.map +1 -1
- package/dist/utils/value-suggestions.d.ts.map +1 -1
- package/dist/utils/value-suggestions.js +14 -11
- package/dist/utils/value-suggestions.js.map +1 -1
- package/package.json +2 -2
- package/dist/providers/anthropic-middleware.d.ts +0 -25
- package/dist/providers/anthropic-middleware.d.ts.map +0 -1
- package/dist/providers/anthropic-middleware.js +0 -32
- package/dist/providers/anthropic-middleware.js.map +0 -1
- package/dist/providers/common/openai-compatible-messages.d.ts +0 -2
- package/dist/providers/common/openai-compatible-messages.d.ts.map +0 -1
- package/dist/providers/common/openai-compatible-messages.js.map +0 -1
- package/dist/services/session/continuation-call-id-resolver.d.ts +0 -29
- package/dist/services/session/continuation-call-id-resolver.d.ts.map +0 -1
- package/dist/services/session/continuation-call-id-resolver.js +0 -69
- package/dist/services/session/continuation-call-id-resolver.js.map +0 -1
- package/dist/services/session/continuation-state.d.ts +0 -69
- package/dist/services/session/continuation-state.d.ts.map +0 -1
- package/dist/services/session/continuation-state.js +0 -78
- package/dist/services/session/continuation-state.js.map +0 -1
- package/dist/services/session/session-manager.d.ts +0 -87
- package/dist/services/session/session-manager.d.ts.map +0 -1
- package/dist/services/session/session-manager.js +0 -146
- package/dist/services/session/session-manager.js.map +0 -1
- package/dist/services/session/turn-workflow.d.ts.map +0 -1
- package/dist/services/session/turn-workflow.js +0 -1055
- package/dist/services/session/turn-workflow.js.map +0 -1
|
@@ -46,11 +46,16 @@ export class MaxTurnsExceededError extends Error {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
class EventQueue {
|
|
49
|
+
acceptBeforePublish;
|
|
50
|
+
constructor(acceptBeforePublish) {
|
|
51
|
+
this.acceptBeforePublish = acceptBeforePublish;
|
|
52
|
+
}
|
|
49
53
|
#items = [];
|
|
50
54
|
#waiters = [];
|
|
51
55
|
#closed = false;
|
|
52
56
|
#failure;
|
|
53
57
|
push(item) {
|
|
58
|
+
this.acceptBeforePublish?.(item);
|
|
54
59
|
const waiter = this.#waiters.shift();
|
|
55
60
|
if (waiter)
|
|
56
61
|
waiter.resolve({ done: false, value: item });
|
|
@@ -98,6 +103,7 @@ export class ApplicationRunLoop {
|
|
|
98
103
|
#contextCompactionSessionState;
|
|
99
104
|
#turnInputMailbox;
|
|
100
105
|
#activeAbortController = null;
|
|
106
|
+
#activeTurnController = null;
|
|
101
107
|
#segmentGeneration = 0;
|
|
102
108
|
/** The run whose budget an out-of-band grant applies to, held across a pause. */
|
|
103
109
|
#activeRunBudgetState;
|
|
@@ -143,6 +149,7 @@ export class ApplicationRunLoop {
|
|
|
143
149
|
this.#turnInputMailbox.closeTurn();
|
|
144
150
|
}
|
|
145
151
|
abort() {
|
|
152
|
+
this.#activeTurnController?.abort();
|
|
146
153
|
this.abortSegment();
|
|
147
154
|
// An aborted turn will not resume, so nothing may keep waiting on it.
|
|
148
155
|
this.#turnInputMailbox.abortTurn();
|
|
@@ -192,7 +199,6 @@ export class ApplicationRunLoop {
|
|
|
192
199
|
#createToolCallExecution() {
|
|
193
200
|
return new ToolCallExecution({
|
|
194
201
|
toolLifecycle: this.#deps.toolLifecycle,
|
|
195
|
-
getOnToolDispatch: this.#deps.getOnToolDispatch,
|
|
196
202
|
resolveMaxParallelToolCalls: this.#deps.resolveMaxParallelToolCalls,
|
|
197
203
|
isMaxTurnsExceeded: (error) => error instanceof MaxTurnsExceededError,
|
|
198
204
|
logDiagnostic: this.#deps.logDiagnostic,
|
|
@@ -256,12 +262,9 @@ export class ApplicationRunLoop {
|
|
|
256
262
|
* the tool results of the round that was in flight when they typed.
|
|
257
263
|
*/
|
|
258
264
|
startStream(agent, input, options = {}) {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
// With one, this call may equally be the same turn restarting after a
|
|
263
|
-
// retry, which the loop cannot tell apart — so `openTurn`/`closeTurn` own
|
|
264
|
-
// the decision instead.
|
|
265
|
+
return this.#run(this.#createState(agent, input, options), options);
|
|
266
|
+
}
|
|
267
|
+
#createState(agent, input, options) {
|
|
265
268
|
this.#turnInputMailbox.startStream();
|
|
266
269
|
const state = {
|
|
267
270
|
agent,
|
|
@@ -293,6 +296,7 @@ export class ApplicationRunLoop {
|
|
|
293
296
|
approvals: options.approvals ?? new ApprovalLedger(),
|
|
294
297
|
turnCount: 0,
|
|
295
298
|
maxTurns: options.maxTurns,
|
|
299
|
+
completionRepairDeadline: options.completionRepairDeadline,
|
|
296
300
|
...(options.runBudget
|
|
297
301
|
? {
|
|
298
302
|
runBudget: new RunBudget(options.runBudget),
|
|
@@ -329,24 +333,31 @@ export class ApplicationRunLoop {
|
|
|
329
333
|
state.approvalDecisionInterruption = interruption;
|
|
330
334
|
state.approvalMessage = approvalOptions?.message;
|
|
331
335
|
};
|
|
332
|
-
return
|
|
336
|
+
return state;
|
|
337
|
+
}
|
|
338
|
+
/** Root turns use the same loop; session operations never dispatch a run. */
|
|
339
|
+
async runTurn(session) {
|
|
340
|
+
const controller = new AbortController();
|
|
341
|
+
this.#activeTurnController = controller;
|
|
342
|
+
try {
|
|
343
|
+
await this.#runTurn(undefined, session, controller.signal);
|
|
344
|
+
}
|
|
345
|
+
finally {
|
|
346
|
+
if (this.#activeTurnController === controller)
|
|
347
|
+
this.#activeTurnController = null;
|
|
348
|
+
}
|
|
333
349
|
}
|
|
334
350
|
continueRunStream(handle, options = {}) {
|
|
335
351
|
const state = unwrapContinuationHandle(handle);
|
|
336
352
|
if (!state || typeof state !== 'object' || !('agent' in state) || !('input' in state)) {
|
|
337
353
|
throw new Error('Invalid application continuation state');
|
|
338
354
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
if (!state.toolExecution)
|
|
346
|
-
state.toolExecution = this.#createToolCallExecution();
|
|
347
|
-
// Handles created before cost accounting existed have no record list.
|
|
348
|
-
if (!state.costRecords)
|
|
349
|
-
state.costRecords = [];
|
|
355
|
+
if (!state.live?.resume)
|
|
356
|
+
throw new Error('Application turn is not waiting for continuation');
|
|
357
|
+
this.#refreshContinuation(state, options);
|
|
358
|
+
return this.#run(state, options);
|
|
359
|
+
}
|
|
360
|
+
#refreshContinuation(state, options) {
|
|
350
361
|
if (!state.runBudget && options.runBudget) {
|
|
351
362
|
state.runBudget = new RunBudget(options.runBudget);
|
|
352
363
|
}
|
|
@@ -382,13 +393,7 @@ export class ApplicationRunLoop {
|
|
|
382
393
|
state.turnCount = 0;
|
|
383
394
|
if (state.maxTurns === undefined)
|
|
384
395
|
state.maxTurns = options.maxTurns;
|
|
385
|
-
|
|
386
|
-
state.supportsConversationChaining = options.supportsConversationChaining === true;
|
|
387
|
-
}
|
|
388
|
-
else {
|
|
389
|
-
// AgentClient supplies the current provider capability on every resume.
|
|
390
|
-
state.supportsConversationChaining = options.supportsConversationChaining === true;
|
|
391
|
-
}
|
|
396
|
+
state.supportsConversationChaining = options.supportsConversationChaining === true;
|
|
392
397
|
// A continuation receives a fresh immutable snapshot from the session;
|
|
393
398
|
// refresh the preparation closure when supplied, while preserving the
|
|
394
399
|
// root closure for callers that do not provide one again.
|
|
@@ -409,12 +414,12 @@ export class ApplicationRunLoop {
|
|
|
409
414
|
state.turnId = options.turnId;
|
|
410
415
|
if (options.hookScope !== undefined)
|
|
411
416
|
state.hookScope = options.hookScope;
|
|
412
|
-
return this.#run(state, options);
|
|
413
417
|
}
|
|
414
|
-
#
|
|
415
|
-
const queue = new EventQueue();
|
|
418
|
+
#createView(state, options, acceptEvent) {
|
|
419
|
+
const queue = new EventQueue(acceptEvent);
|
|
416
420
|
const segmentGeneration = ++this.#segmentGeneration;
|
|
417
|
-
|
|
421
|
+
if (!state.live)
|
|
422
|
+
this.abortSegment();
|
|
418
423
|
const controller = new AbortController();
|
|
419
424
|
this.#activeAbortController = controller;
|
|
420
425
|
this.#turnInputMailbox.startSegment();
|
|
@@ -465,46 +470,137 @@ export class ApplicationRunLoop {
|
|
|
465
470
|
return state.costRecords ?? [];
|
|
466
471
|
},
|
|
467
472
|
});
|
|
468
|
-
let
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
+
let resolveSegment;
|
|
474
|
+
let rejectSegment;
|
|
475
|
+
stream.completed = new Promise((resolve, reject) => {
|
|
476
|
+
resolveSegment = resolve;
|
|
477
|
+
rejectSegment = reject;
|
|
478
|
+
});
|
|
479
|
+
// The iterator may report a failure before the caller observes completed.
|
|
480
|
+
void stream.completed.catch(() => undefined);
|
|
481
|
+
const current = {
|
|
482
|
+
state,
|
|
483
|
+
stream,
|
|
484
|
+
queue,
|
|
485
|
+
options: effectiveOptions,
|
|
486
|
+
toolContext,
|
|
487
|
+
requestAbortController: controller,
|
|
488
|
+
generation: segmentGeneration,
|
|
489
|
+
resolveSegment,
|
|
490
|
+
rejectSegment,
|
|
491
|
+
};
|
|
492
|
+
this.#activeRunBudgetState = state.runBudget ? state : undefined;
|
|
493
|
+
return current;
|
|
494
|
+
}
|
|
495
|
+
#run(state, options) {
|
|
496
|
+
const current = this.#createView(state, options);
|
|
497
|
+
if (state.live) {
|
|
498
|
+
const resume = state.live.resume;
|
|
499
|
+
Object.assign(state.live, current, { resume: undefined });
|
|
500
|
+
resume();
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
state.live = current;
|
|
504
|
+
void this.#runTurn(current).then((value) => this.#settleSegment(current, value), (error) => this.#settleSegment(current, undefined, error));
|
|
505
|
+
}
|
|
506
|
+
return current.stream;
|
|
507
|
+
}
|
|
508
|
+
#observe(context) {
|
|
509
|
+
context.session.beginSegment(context.stream);
|
|
510
|
+
context.observation = context.session.consume(context.stream);
|
|
511
|
+
void context.observation.catch(() => undefined);
|
|
512
|
+
}
|
|
513
|
+
async #completeTurn(context) {
|
|
514
|
+
const value = finish(context.stream, context.state, context.queue);
|
|
515
|
+
if (context.session) {
|
|
516
|
+
this.#settleSegment(context, value);
|
|
517
|
+
await context.observation;
|
|
518
|
+
await context.session.completed(context.stream);
|
|
519
|
+
}
|
|
520
|
+
return value;
|
|
521
|
+
}
|
|
522
|
+
/** Complete an event view; a pause leaves the owning async task alive. */
|
|
523
|
+
#settleSegment(context, value, error, waiting = false) {
|
|
524
|
+
const { state, stream, queue, generation } = context;
|
|
525
|
+
if (error === undefined)
|
|
526
|
+
context.session?.acceptSegment(stream);
|
|
527
|
+
state.runBudget?.pause();
|
|
528
|
+
const cancelled = error instanceof Error && error.name === 'AbortError';
|
|
529
|
+
const paused = (waiting || context.resume !== undefined) && !cancelled && error === undefined;
|
|
530
|
+
if (error !== undefined) {
|
|
531
|
+
stream.cancelled = cancelled;
|
|
473
532
|
queue.close(error);
|
|
474
|
-
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
533
|
+
context.rejectSegment(error);
|
|
534
|
+
}
|
|
535
|
+
else {
|
|
536
|
+
context.resolveSegment(value);
|
|
537
|
+
}
|
|
538
|
+
if (generation !== this.#segmentGeneration)
|
|
539
|
+
return;
|
|
540
|
+
if (!paused && this.#activeAbortController === context.requestAbortController)
|
|
541
|
+
this.#activeAbortController = null;
|
|
542
|
+
this.#turnInputMailbox.settleSegment({
|
|
543
|
+
paused,
|
|
544
|
+
reason: {
|
|
545
|
+
pendingApprovals: state.toolExecution.pendingApprovals.length,
|
|
546
|
+
pendingBudgetInteraction: state.pendingRunBudgetInteraction !== undefined,
|
|
547
|
+
cancelled,
|
|
548
|
+
error: error instanceof Error ? error.name : error ? String(error) : undefined,
|
|
549
|
+
turnCount: state.turnCount,
|
|
550
|
+
},
|
|
551
|
+
});
|
|
552
|
+
if (!paused && this.#activeRunBudgetState === state)
|
|
553
|
+
this.#activeRunBudgetState = undefined;
|
|
554
|
+
}
|
|
555
|
+
/** Publish a paused view and suspend here until its owner reconnects or cancels. */
|
|
556
|
+
async #waitForDecision(context) {
|
|
557
|
+
if (context.session) {
|
|
558
|
+
this.#settleSegment(context, finish(context.stream, context.state, context.queue), undefined, true);
|
|
559
|
+
await context.observation;
|
|
560
|
+
const options = await context.session.interrupted(context.stream);
|
|
561
|
+
this.#assertLive(context);
|
|
562
|
+
this.#refreshContinuation(context.state, options);
|
|
563
|
+
Object.assign(context, this.#createView(context.state, { ...options, signal: context.turnSignal }, (event) => context.session.acceptEvent(event)));
|
|
564
|
+
this.#observe(context);
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
const { state, stream, queue, options } = context;
|
|
568
|
+
await new Promise((resolve, reject) => {
|
|
569
|
+
const signal = options.signal;
|
|
570
|
+
const abort = () => {
|
|
571
|
+
context.resume = undefined;
|
|
572
|
+
reject(Object.assign(new Error('Operation aborted'), { name: 'AbortError' }));
|
|
573
|
+
};
|
|
574
|
+
context.resume = () => {
|
|
575
|
+
signal?.removeEventListener('abort', abort);
|
|
576
|
+
resolve();
|
|
577
|
+
};
|
|
578
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
579
|
+
if (signal?.aborted) {
|
|
580
|
+
abort();
|
|
502
581
|
return;
|
|
503
|
-
|
|
504
|
-
|
|
582
|
+
}
|
|
583
|
+
this.#settleSegment(context, finish(stream, state, queue));
|
|
505
584
|
});
|
|
506
|
-
|
|
507
|
-
|
|
585
|
+
}
|
|
586
|
+
/** A budget decision resumes this exact boundary without recounting it. */
|
|
587
|
+
async #waitForBudget(context) {
|
|
588
|
+
const { state } = context;
|
|
589
|
+
while (state.pendingRunBudgetInteraction) {
|
|
590
|
+
if (state.runBudgetInteractionDecision === 'stop') {
|
|
591
|
+
state.pendingRunBudgetInteraction = undefined;
|
|
592
|
+
state.runBudgetInteractionDecision = undefined;
|
|
593
|
+
return true;
|
|
594
|
+
}
|
|
595
|
+
if (state.runBudgetInteractionDecision === 'continue') {
|
|
596
|
+
state.pendingRunBudgetInteraction = undefined;
|
|
597
|
+
state.runBudgetInteractionDecision = undefined;
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
context.stream.interruptions = [state.pendingRunBudgetInteraction];
|
|
601
|
+
await this.#waitForDecision(context);
|
|
602
|
+
}
|
|
603
|
+
return false;
|
|
508
604
|
}
|
|
509
605
|
/** Route boundary advice through the same admission lane as mode notices. */
|
|
510
606
|
#queuePendingSystemNotice(text) {
|
|
@@ -512,746 +608,825 @@ export class ApplicationRunLoop {
|
|
|
512
608
|
}
|
|
513
609
|
#admitPendingSteers(state, stream, queue) {
|
|
514
610
|
this.#turnInputMailbox.admitAtRequestBoundary((item) => {
|
|
611
|
+
const text = typeof item.content === 'string'
|
|
612
|
+
? item.content
|
|
613
|
+
: Array.isArray(item.content)
|
|
614
|
+
? item.content.map((part) => (typeof part?.text === 'string' ? part.text : '')).join('')
|
|
615
|
+
: '';
|
|
616
|
+
if (item.role === 'user' && !text.startsWith('[Mode Notice]'))
|
|
617
|
+
state.completionRepairDeadline = undefined;
|
|
515
618
|
state.history.push(item);
|
|
516
619
|
state.input.push(...normalizeApplicationInput([item]));
|
|
517
620
|
outputPush(stream, queue, { type: 'item', item });
|
|
518
621
|
}, state.turnCount);
|
|
519
622
|
}
|
|
520
|
-
|
|
623
|
+
/** The single model → tools → model cycle, shared by root and nested turns. */
|
|
624
|
+
async #runTurn(context, session, turnSignal) {
|
|
521
625
|
while (true) {
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
state
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
626
|
+
try {
|
|
627
|
+
turnSignal?.throwIfAborted();
|
|
628
|
+
if (!context) {
|
|
629
|
+
const start = await session.prepare(turnSignal);
|
|
630
|
+
turnSignal?.throwIfAborted();
|
|
631
|
+
if (!start)
|
|
632
|
+
return;
|
|
633
|
+
const options = { ...start.options, signal: turnSignal };
|
|
634
|
+
const state = this.#createState(start.agent, start.input, options);
|
|
635
|
+
this.#refreshContinuation(state, options);
|
|
636
|
+
context = this.#createView(state, options, (event) => session.acceptEvent(event));
|
|
637
|
+
context.session = session;
|
|
638
|
+
context.turnSignal = turnSignal;
|
|
639
|
+
state.live = context;
|
|
640
|
+
this.#observe(context);
|
|
536
641
|
}
|
|
642
|
+
this.#assertLive(context);
|
|
643
|
+
// New explicit user authority supersedes a harness repair continuation before its deadline is applied.
|
|
644
|
+
this.#admitPendingSteers(context.state, context.stream, context.queue);
|
|
645
|
+
if (await this.#withinRepairDeadline(context, () => this.#prepareRequest(context)))
|
|
646
|
+
return await this.#completeTurn(context);
|
|
647
|
+
const response = await this.#withinRepairDeadline(context, () => this.#askModel(context));
|
|
648
|
+
this.#assertLive(context);
|
|
649
|
+
const calls = this.#recordResponse(context, response);
|
|
650
|
+
if (await this.#withinRepairDeadline(context, () => this.#executeTools(context, response, calls)))
|
|
651
|
+
return await this.#completeTurn(context);
|
|
652
|
+
if (calls.length === 0 && (await this.#finishTurn(context)))
|
|
653
|
+
return await this.#completeTurn(context);
|
|
537
654
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
state.approvalMessage = undefined;
|
|
544
|
-
await state.toolExecution.settle(this.#toolCallContext(state, stream, queue, toolContext));
|
|
545
|
-
stream.interruptions = state.toolExecution.pendingApprovals.map((item) => item.interruption);
|
|
546
|
-
if (state.toolExecution.pendingApprovals.length > 0) {
|
|
547
|
-
return finish(stream, state, queue);
|
|
655
|
+
catch (error) {
|
|
656
|
+
if (context?.completionRepairExpired) {
|
|
657
|
+
context.state.terminalCause = 'completion_verification_blocked';
|
|
658
|
+
this.#appendStopNotice(context, 'Completion verification blocked: the repair task deadline expired. The durable goal remains active.');
|
|
659
|
+
return await this.#completeTurn(context);
|
|
548
660
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
return finish(stream, state, queue);
|
|
555
|
-
}
|
|
556
|
-
// A budget interaction can park a response after its tool calls are
|
|
557
|
-
// recorded but before any body runs. Resume those retained calls only
|
|
558
|
-
// after processing an ordinary approval decision for that same plan.
|
|
559
|
-
if (state.toolExecution.hasPlan) {
|
|
560
|
-
await state.toolExecution.settle(this.#toolCallContext(state, stream, queue, toolContext));
|
|
561
|
-
if (state.toolExecution.pendingApprovals.length) {
|
|
562
|
-
stream.interruptions = state.toolExecution.pendingApprovals.map((item) => item.interruption);
|
|
563
|
-
return finish(stream, state, queue);
|
|
661
|
+
if (!session)
|
|
662
|
+
throw error;
|
|
663
|
+
if (context) {
|
|
664
|
+
this.#settleSegment(context, undefined, error);
|
|
665
|
+
await context.observation?.catch(() => undefined);
|
|
564
666
|
}
|
|
667
|
+
turnSignal?.throwIfAborted();
|
|
668
|
+
if (!(await session.recover(error)))
|
|
669
|
+
return;
|
|
670
|
+
context = undefined;
|
|
565
671
|
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
async #withinRepairDeadline(context, operation) {
|
|
675
|
+
const deadline = context.state.completionRepairDeadline;
|
|
676
|
+
if (deadline === undefined)
|
|
677
|
+
return operation();
|
|
678
|
+
let timer;
|
|
679
|
+
try {
|
|
680
|
+
return await Promise.race([
|
|
681
|
+
new Promise((_resolve, reject) => {
|
|
682
|
+
const expire = () => {
|
|
683
|
+
if (context.state.completionRepairDeadline !== deadline)
|
|
684
|
+
return;
|
|
685
|
+
context.completionRepairExpired = true;
|
|
686
|
+
context.requestAbortController.abort();
|
|
687
|
+
reject(new Error('Completion repair deadline exceeded'));
|
|
688
|
+
};
|
|
689
|
+
if (Date.now() >= deadline)
|
|
690
|
+
expire();
|
|
691
|
+
else
|
|
692
|
+
timer = setTimeout(expire, deadline - Date.now());
|
|
693
|
+
}),
|
|
694
|
+
Promise.resolve().then(() => {
|
|
695
|
+
if (context.completionRepairExpired)
|
|
696
|
+
throw new Error('Completion repair deadline exceeded');
|
|
697
|
+
return operation();
|
|
698
|
+
}),
|
|
699
|
+
]);
|
|
700
|
+
}
|
|
701
|
+
finally {
|
|
702
|
+
if (timer)
|
|
703
|
+
clearTimeout(timer);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
#appendStopNotice(context, notice) {
|
|
707
|
+
const text = `\n\n${notice}`;
|
|
708
|
+
context.stream.finalOutput = (context.stream.finalOutput ?? '') + text;
|
|
709
|
+
context.queue.push({ type: 'text_delta', text });
|
|
710
|
+
}
|
|
711
|
+
#assertLive(context) {
|
|
712
|
+
if (context.options.signal?.aborted)
|
|
713
|
+
throw Object.assign(new Error('Operation aborted'), { name: 'AbortError' });
|
|
714
|
+
}
|
|
715
|
+
/** Admit input and prepare context once for the next logical model round. */
|
|
716
|
+
async #prepareRequest(context) {
|
|
717
|
+
let { state, stream, queue, options } = context;
|
|
718
|
+
// The request boundary: every tool result of the previous round is in
|
|
719
|
+
// history, and the next request has not been built. A user message
|
|
720
|
+
// admitted here reaches the model in sequence, mid-turn.
|
|
721
|
+
this.#admitPendingSteers(state, stream, queue);
|
|
722
|
+
this.#evaluateRunBudget(state, stream, queue);
|
|
723
|
+
if (await this.#waitForBudget(context))
|
|
724
|
+
return true;
|
|
725
|
+
({ state, stream, queue, options } = context);
|
|
726
|
+
const compactionHistoryResult = state.usesDeltaHistory
|
|
727
|
+
? state.compactionHistory
|
|
728
|
+
? mergeChainedCompactionHistory(state.compactionHistory, state.history)
|
|
729
|
+
: { kind: 'skipped', reason: 'missing_snapshot' }
|
|
730
|
+
: { kind: 'ready', history: state.history };
|
|
731
|
+
const boundaryDecision = options.onRequestBoundary?.(compactionHistoryResult.kind === 'ready' ? compactionHistoryResult.history : state.history, (text) => this.#queuePendingSystemNotice(text), {
|
|
732
|
+
...(state.lastCompletedInputTokens !== undefined
|
|
733
|
+
? { lastCompletedInputTokens: state.lastCompletedInputTokens }
|
|
734
|
+
: {}),
|
|
735
|
+
...(compactionHistoryResult.kind === 'skipped' ? { compactionSkipReason: compactionHistoryResult.reason } : {}),
|
|
736
|
+
});
|
|
737
|
+
const compactionHistory = compactionHistoryResult.kind === 'ready' ? compactionHistoryResult.history : undefined;
|
|
738
|
+
if (options.boundaryCompaction && compactionHistory && !boundaryDecision?.deferCompaction) {
|
|
739
|
+
const compactionStartedAt = Date.now();
|
|
740
|
+
let costsRecordedLive = false;
|
|
741
|
+
const compaction = await options.boundaryCompaction.compact({
|
|
742
|
+
history: compactionHistory,
|
|
743
|
+
instructions: state.agent.resolveInstructionsForRequest?.() ?? state.agent.instructions,
|
|
744
|
+
tools: toModelTools(state.agent.tools),
|
|
745
|
+
recordCostRecords: (records) => {
|
|
746
|
+
if (records.length) {
|
|
747
|
+
costsRecordedLive = true;
|
|
612
748
|
state.costRecords ??= [];
|
|
613
|
-
state.costRecords.push(...
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
state
|
|
617
|
-
state.lastCompletedInputTokens = undefined;
|
|
618
|
-
state.compactedDuringRun = true;
|
|
619
|
-
state.history.splice(0, state.history.length, ...compaction.history);
|
|
620
|
-
state.input.splice(0, state.input.length, ...normalizeApplicationInput(compaction.modelInput));
|
|
621
|
-
state.responseId = undefined;
|
|
622
|
-
state.responseProviderId = undefined;
|
|
623
|
-
state.usesDeltaHistory = false;
|
|
624
|
-
state.automaticCompactionsThisRun = (state.automaticCompactionsThisRun ?? 0) + 1;
|
|
625
|
-
outputPush(stream, queue, {
|
|
626
|
-
type: 'context_compaction_completed',
|
|
627
|
-
provider: state.currentProviderId ?? 'unknown',
|
|
628
|
-
strategy: 'local',
|
|
629
|
-
durationMs: Math.max(0, Date.now() - compactionStartedAt),
|
|
630
|
-
});
|
|
631
|
-
}
|
|
632
|
-
else if (compaction.kind === 'failed') {
|
|
633
|
-
outputPush(stream, queue, {
|
|
634
|
-
type: 'context_compaction_failed',
|
|
635
|
-
provider: compaction.provider,
|
|
636
|
-
strategy: 'local',
|
|
637
|
-
durationMs: Math.max(0, Date.now() - compactionStartedAt),
|
|
638
|
-
});
|
|
639
|
-
if (compaction.error)
|
|
640
|
-
throw compaction.error;
|
|
641
|
-
}
|
|
642
|
-
else if (compaction.notice && compaction.notice !== state.compactionNotice) {
|
|
643
|
-
state.compactionNotice = compaction.notice;
|
|
644
|
-
this.#queuePendingSystemNotice(compaction.notice);
|
|
749
|
+
state.costRecords.push(...records);
|
|
750
|
+
for (const record of records)
|
|
751
|
+
queue.push({ type: 'cost_update', record });
|
|
752
|
+
this.#evaluateRunBudget(state, stream, queue);
|
|
645
753
|
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
state.turnCount += 1;
|
|
657
|
-
this.#evaluateRunBudget(state, stream, queue);
|
|
658
|
-
if (state.pendingRunBudgetInteraction) {
|
|
659
|
-
state.requestBoundaryReady = true;
|
|
660
|
-
return this.#pauseForRunBudgetInteraction(state, stream, queue);
|
|
661
|
-
}
|
|
662
|
-
// Retain the legacy error only for callers that have not adopted staged
|
|
663
|
-
// budgets. A configured run budget turns the same count into critical
|
|
664
|
-
// evidence and lets the decision surface choose what happens next.
|
|
665
|
-
if (!state.runBudget && state.maxTurns !== undefined && state.turnCount > state.maxTurns) {
|
|
666
|
-
throw new MaxTurnsExceededError(state.maxTurns);
|
|
667
|
-
}
|
|
754
|
+
return !state.pendingRunBudgetInteraction && !state.criticalWrapUpPending;
|
|
755
|
+
},
|
|
756
|
+
automaticCompactionsThisRun: state.automaticCompactionsThisRun ?? 0,
|
|
757
|
+
lastCompletedInputTokens: state.lastCompletedInputTokens,
|
|
758
|
+
signal: options.signal,
|
|
759
|
+
onStarted: (provider) => outputPush(stream, queue, { type: 'context_compaction_started', provider, strategy: 'local' }),
|
|
760
|
+
});
|
|
761
|
+
if (compaction.kind !== 'unchanged' && !costsRecordedLive && compaction.costRecords?.length) {
|
|
762
|
+
state.costRecords ??= [];
|
|
763
|
+
state.costRecords.push(...compaction.costRecords);
|
|
668
764
|
}
|
|
669
|
-
|
|
670
|
-
state.
|
|
765
|
+
if (compaction.kind === 'compacted') {
|
|
766
|
+
state.inputUsageInvalidatedByCompaction = true;
|
|
767
|
+
state.lastCompletedInputTokens = undefined;
|
|
768
|
+
state.compactedDuringRun = true;
|
|
769
|
+
state.history.splice(0, state.history.length, ...compaction.history);
|
|
770
|
+
state.input.splice(0, state.input.length, ...normalizeApplicationInput(compaction.modelInput));
|
|
771
|
+
state.responseId = undefined;
|
|
772
|
+
state.responseProviderId = undefined;
|
|
773
|
+
state.usesDeltaHistory = false;
|
|
774
|
+
state.automaticCompactionsThisRun = (state.automaticCompactionsThisRun ?? 0) + 1;
|
|
775
|
+
outputPush(stream, queue, {
|
|
776
|
+
type: 'context_compaction_completed',
|
|
777
|
+
provider: state.currentProviderId ?? 'unknown',
|
|
778
|
+
strategy: 'local',
|
|
779
|
+
durationMs: Math.max(0, Date.now() - compactionStartedAt),
|
|
780
|
+
});
|
|
671
781
|
}
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
let activeRequestId = '';
|
|
679
|
-
let activeRequest;
|
|
680
|
-
let pendingNativeReasoning;
|
|
681
|
-
let deferredNativeReasoning = [];
|
|
682
|
-
let criticalWrapUp = false;
|
|
683
|
-
while (true) {
|
|
684
|
-
streamedToolCalls = [];
|
|
685
|
-
completion = undefined;
|
|
686
|
-
sawToolCall = false;
|
|
687
|
-
pendingNativeReasoning = undefined;
|
|
688
|
-
deferredNativeReasoning = [];
|
|
689
|
-
// Stable process-unique request id, allocated immediately before dispatch
|
|
690
|
-
// so a cost record can be settled exactly once (success or failure).
|
|
691
|
-
activeRequestId = this.#nextRequestId();
|
|
692
|
-
const requestId = activeRequestId;
|
|
693
|
-
const outputLengthBefore = stream.output.length;
|
|
694
|
-
const newItemsLengthBefore = stream.newItems.length;
|
|
695
|
-
const inputLengthBefore = state.input.length;
|
|
696
|
-
const historyLengthBefore = state.history.length;
|
|
697
|
-
const criticalWrapUpDispatchedBefore = state.criticalWrapUpDispatched;
|
|
698
|
-
let provisionalTextCharacters = 0;
|
|
699
|
-
let provisionalReasoningCharacters = 0;
|
|
700
|
-
let provisionalTextDeltas = 0;
|
|
701
|
-
let provisionalReasoningDeltas = 0;
|
|
702
|
-
let lastTextPartId;
|
|
703
|
-
criticalWrapUp = state.criticalWrapUpPending === true && state.criticalWrapUpDispatched !== true;
|
|
704
|
-
if (criticalWrapUp)
|
|
705
|
-
state.criticalWrapUpDispatched = true;
|
|
706
|
-
const disableChaining = state.disableChainingForAttempt === true;
|
|
707
|
-
if (disableChaining) {
|
|
708
|
-
state.disableChainingForAttempt = false;
|
|
709
|
-
state.responseId = undefined;
|
|
710
|
-
state.responseProviderId = undefined;
|
|
711
|
-
}
|
|
712
|
-
// A per-request signal lets a recoverable guard trip cancel only this
|
|
713
|
-
// provider request; the segment signal still cancels everything.
|
|
714
|
-
const requestSignal = new AbortController();
|
|
715
|
-
const abortRequest = () => requestSignal.abort();
|
|
716
|
-
if (options.signal?.aborted)
|
|
717
|
-
requestSignal.abort();
|
|
718
|
-
else
|
|
719
|
-
options.signal?.addEventListener('abort', abortRequest, { once: true });
|
|
720
|
-
const requestInstructions = state.agent.resolveInstructionsForRequest?.() ?? state.agent.instructions;
|
|
721
|
-
// Same synchronous step as the instructions above, so the snapshot is
|
|
722
|
-
// exactly the state the model is shown in this request.
|
|
723
|
-
state.requestSnapshot = state.agent.resolveRequestSnapshot?.();
|
|
724
|
-
const requestCatalog = getCatalogModel(state.currentProviderId ?? 'openai', state.agent.model);
|
|
725
|
-
const effectiveOutput = resolveRequestOutput(requestCatalog, state.agent.modelSettings?.maxTokens);
|
|
726
|
-
const request = {
|
|
727
|
-
instructions: criticalWrapUp
|
|
728
|
-
? `${requestInstructions}\n\nBudget containment is terminal. Do not call tools. In this one final response, summarize what you completed, the evidence you have, and what remains.`
|
|
729
|
-
: requestInstructions,
|
|
730
|
-
...(state.supportsConversationChaining &&
|
|
731
|
-
!disableChaining &&
|
|
732
|
-
state.responseId &&
|
|
733
|
-
state.responseProviderId !== undefined &&
|
|
734
|
-
state.responseProviderId === state.currentProviderId
|
|
735
|
-
? { previousResponseId: state.responseId }
|
|
736
|
-
: {}),
|
|
737
|
-
...(disableChaining ? { disableChaining: true } : {}),
|
|
738
|
-
input: projectModelRequestHistory(state.input),
|
|
739
|
-
tools: toModelTools(criticalWrapUp ? [] : state.agent.tools),
|
|
740
|
-
applicationTools: criticalWrapUp ? [] : state.agent.tools,
|
|
741
|
-
...(state.agent.modelSettings?.temperature !== undefined
|
|
742
|
-
? { temperature: state.agent.modelSettings.temperature }
|
|
743
|
-
: {}),
|
|
744
|
-
...(state.agent.modelSettings?.reasoning ? { reasoning: state.agent.modelSettings.reasoning } : {}),
|
|
745
|
-
...(effectiveOutput !== undefined ? { maxTokens: effectiveOutput } : {}),
|
|
746
|
-
...(state.agent.outputType !== undefined ? { outputType: state.agent.outputType } : {}),
|
|
747
|
-
...(state.agent.modelSettings?.codex ? { codex: state.agent.modelSettings.codex } : {}),
|
|
748
|
-
...(state.agent.modelSettings?.providerData
|
|
749
|
-
? {
|
|
750
|
-
providerOptions: this.#contextCompactionSessionState.disabled
|
|
751
|
-
? Object.fromEntries(Object.entries(state.agent.modelSettings.providerData).filter(([key]) => key !== 'contextCompaction'))
|
|
752
|
-
: state.agent.modelSettings.providerData,
|
|
753
|
-
}
|
|
754
|
-
: {}),
|
|
755
|
-
signal: requestSignal.signal,
|
|
756
|
-
...(state.recoveryBudget ? { recoveryBudget: state.recoveryBudget } : {}),
|
|
757
|
-
};
|
|
758
|
-
activeRequest = request;
|
|
759
|
-
const generationGuard = new GenerationGuard({
|
|
760
|
-
maxOutputCharacters: state.agent.modelSettings?.maxStreamOutputChars,
|
|
761
|
-
maxTextCharacters: state.agent.modelSettings?.maxStreamOutputChars,
|
|
762
|
-
maxReasoningCharacters: state.agent.modelSettings?.maxStreamOutputChars,
|
|
763
|
-
maxToolArgumentCharacters: state.agent.modelSettings?.maxStreamOutputChars,
|
|
764
|
-
maxCumulativeToolArgumentCharacters: state.agent.modelSettings?.maxStreamOutputChars,
|
|
765
|
-
requestDeadlineMs: state.agent.modelSettings?.maxModelRequestDurationMs,
|
|
766
|
-
maxStreamIdleMs: state.agent.modelSettings?.maxModelStreamIdleMs,
|
|
767
|
-
...options.generationGuard,
|
|
782
|
+
else if (compaction.kind === 'failed') {
|
|
783
|
+
outputPush(stream, queue, {
|
|
784
|
+
type: 'context_compaction_failed',
|
|
785
|
+
provider: compaction.provider,
|
|
786
|
+
strategy: 'local',
|
|
787
|
+
durationMs: Math.max(0, Date.now() - compactionStartedAt),
|
|
768
788
|
});
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
789
|
+
if (compaction.error)
|
|
790
|
+
throw compaction.error;
|
|
791
|
+
}
|
|
792
|
+
else if (compaction.notice && compaction.notice !== state.compactionNotice) {
|
|
793
|
+
state.compactionNotice = compaction.notice;
|
|
794
|
+
this.#queuePendingSystemNotice(compaction.notice);
|
|
795
|
+
}
|
|
796
|
+
// Summarization is an asynchronous part of this same request
|
|
797
|
+
// boundary. Admit anything that arrived while it was in flight after
|
|
798
|
+
// applying a replacement, so the steer cannot be overwritten by the
|
|
799
|
+
// checkpoint or miss a terminal model request.
|
|
800
|
+
this.#admitPendingSteers(state, stream, queue);
|
|
801
|
+
this.#evaluateRunBudget(state, stream, queue);
|
|
802
|
+
if (await this.#waitForBudget(context))
|
|
803
|
+
return true;
|
|
804
|
+
({ state, stream, queue, options } = context);
|
|
805
|
+
}
|
|
806
|
+
this.#admitPendingSteers(state, stream, queue);
|
|
807
|
+
state.turnCount += 1;
|
|
808
|
+
this.#evaluateRunBudget(state, stream, queue);
|
|
809
|
+
if (await this.#waitForBudget(context))
|
|
810
|
+
return true;
|
|
811
|
+
({ state, stream, queue, options } = context);
|
|
812
|
+
// Retain the legacy error only for callers that have not adopted staged
|
|
813
|
+
// budgets. A configured run budget turns the same count into critical
|
|
814
|
+
// evidence and lets the decision surface choose what happens next.
|
|
815
|
+
if (!state.runBudget && state.maxTurns !== undefined && state.turnCount > state.maxTurns) {
|
|
816
|
+
throw new MaxTurnsExceededError(state.maxTurns);
|
|
817
|
+
}
|
|
818
|
+
return false;
|
|
819
|
+
}
|
|
820
|
+
/** Own streaming and safe request attempts; never dispatch tools here. */
|
|
821
|
+
async #askModel(context) {
|
|
822
|
+
const { state, stream, queue, options, requestAbortController } = context;
|
|
823
|
+
const model = await this.#deps.resolveModel(state.agent.model);
|
|
824
|
+
const maxRetries = state.agent.modelSettings?.retry?.maxRetries ?? 0;
|
|
825
|
+
let attempt = 0;
|
|
826
|
+
let streamedToolCalls = [];
|
|
827
|
+
let completion;
|
|
828
|
+
let sawToolCall = false;
|
|
829
|
+
let activeRequestId = '';
|
|
830
|
+
let activeRequest;
|
|
831
|
+
let pendingNativeReasoning;
|
|
832
|
+
let deferredNativeReasoning = [];
|
|
833
|
+
let criticalWrapUp = false;
|
|
834
|
+
while (true) {
|
|
835
|
+
streamedToolCalls = [];
|
|
836
|
+
completion = undefined;
|
|
837
|
+
sawToolCall = false;
|
|
838
|
+
pendingNativeReasoning = undefined;
|
|
839
|
+
deferredNativeReasoning = [];
|
|
840
|
+
// Stable process-unique request id, allocated immediately before dispatch
|
|
841
|
+
// so a cost record can be settled exactly once (success or failure).
|
|
842
|
+
activeRequestId = this.#nextRequestId();
|
|
843
|
+
const requestId = activeRequestId;
|
|
844
|
+
const outputLengthBefore = stream.output.length;
|
|
845
|
+
const newItemsLengthBefore = stream.newItems.length;
|
|
846
|
+
const inputLengthBefore = state.input.length;
|
|
847
|
+
const historyLengthBefore = state.history.length;
|
|
848
|
+
const criticalWrapUpDispatchedBefore = state.criticalWrapUpDispatched;
|
|
849
|
+
let provisionalTextCharacters = 0;
|
|
850
|
+
let provisionalReasoningCharacters = 0;
|
|
851
|
+
let provisionalTextDeltas = 0;
|
|
852
|
+
let provisionalReasoningDeltas = 0;
|
|
853
|
+
let lastTextPartId;
|
|
854
|
+
criticalWrapUp = state.criticalWrapUpPending === true && state.criticalWrapUpDispatched !== true;
|
|
855
|
+
if (criticalWrapUp)
|
|
856
|
+
state.criticalWrapUpDispatched = true;
|
|
857
|
+
const disableChaining = state.disableChainingForAttempt === true;
|
|
858
|
+
if (disableChaining) {
|
|
859
|
+
state.disableChainingForAttempt = false;
|
|
860
|
+
state.responseId = undefined;
|
|
861
|
+
state.responseProviderId = undefined;
|
|
862
|
+
}
|
|
863
|
+
// A per-request signal lets a recoverable guard trip cancel only this
|
|
864
|
+
// provider request; the segment signal still cancels everything.
|
|
865
|
+
const requestSignal = new AbortController();
|
|
866
|
+
const abortRequest = () => requestSignal.abort();
|
|
867
|
+
if (options.signal?.aborted)
|
|
868
|
+
requestSignal.abort();
|
|
869
|
+
else
|
|
870
|
+
options.signal?.addEventListener('abort', abortRequest, { once: true });
|
|
871
|
+
const requestInstructions = state.agent.resolveInstructionsForRequest?.() ?? state.agent.instructions;
|
|
872
|
+
// Same synchronous step as the instructions above, so the snapshot is
|
|
873
|
+
// exactly the state the model is shown in this request.
|
|
874
|
+
state.requestSnapshot = state.agent.resolveRequestSnapshot?.();
|
|
875
|
+
const requestCatalog = getCatalogModel(state.currentProviderId ?? 'openai', state.agent.model);
|
|
876
|
+
const effectiveOutput = resolveRequestOutput(requestCatalog, state.agent.modelSettings?.maxTokens);
|
|
877
|
+
const request = {
|
|
878
|
+
instructions: criticalWrapUp
|
|
879
|
+
? `${requestInstructions}\n\nBudget containment is terminal. Do not call tools. In this one final response, summarize what you completed, the evidence you have, and what remains.`
|
|
880
|
+
: requestInstructions,
|
|
881
|
+
...(state.supportsConversationChaining &&
|
|
882
|
+
!disableChaining &&
|
|
883
|
+
state.responseId &&
|
|
884
|
+
state.responseProviderId !== undefined &&
|
|
885
|
+
state.responseProviderId === state.currentProviderId
|
|
886
|
+
? { previousResponseId: state.responseId }
|
|
887
|
+
: {}),
|
|
888
|
+
...(disableChaining ? { disableChaining: true } : {}),
|
|
889
|
+
input: projectModelRequestHistory(state.input),
|
|
890
|
+
tools: toModelTools(criticalWrapUp ? [] : state.agent.tools),
|
|
891
|
+
applicationTools: criticalWrapUp ? [] : state.agent.tools,
|
|
892
|
+
...(state.agent.modelSettings?.temperature !== undefined
|
|
893
|
+
? { temperature: state.agent.modelSettings.temperature }
|
|
894
|
+
: {}),
|
|
895
|
+
...(state.agent.modelSettings?.reasoning ? { reasoning: state.agent.modelSettings.reasoning } : {}),
|
|
896
|
+
...(effectiveOutput !== undefined ? { maxTokens: effectiveOutput } : {}),
|
|
897
|
+
...(state.agent.outputType !== undefined ? { outputType: state.agent.outputType } : {}),
|
|
898
|
+
...(state.agent.modelSettings?.codex ? { codex: state.agent.modelSettings.codex } : {}),
|
|
899
|
+
...(state.agent.modelSettings?.providerData
|
|
900
|
+
? {
|
|
901
|
+
providerOptions: this.#contextCompactionSessionState.disabled
|
|
902
|
+
? Object.fromEntries(Object.entries(state.agent.modelSettings.providerData).filter(([key]) => key !== 'contextCompaction'))
|
|
903
|
+
: state.agent.modelSettings.providerData,
|
|
856
904
|
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
: state.lastCompletedInputTokens,
|
|
883
|
-
chainedContextKnown: !needsChainedSnapshot || fullContext?.kind === 'ready',
|
|
884
|
-
});
|
|
885
|
-
await consume();
|
|
886
|
-
};
|
|
905
|
+
: {}),
|
|
906
|
+
signal: requestSignal.signal,
|
|
907
|
+
...(state.recoveryBudget ? { recoveryBudget: state.recoveryBudget } : {}),
|
|
908
|
+
};
|
|
909
|
+
activeRequest = request;
|
|
910
|
+
const generationGuard = new GenerationGuard({
|
|
911
|
+
maxOutputCharacters: state.agent.modelSettings?.maxStreamOutputChars,
|
|
912
|
+
maxTextCharacters: state.agent.modelSettings?.maxStreamOutputChars,
|
|
913
|
+
maxReasoningCharacters: state.agent.modelSettings?.maxStreamOutputChars,
|
|
914
|
+
maxToolArgumentCharacters: state.agent.modelSettings?.maxStreamOutputChars,
|
|
915
|
+
maxCumulativeToolArgumentCharacters: state.agent.modelSettings?.maxStreamOutputChars,
|
|
916
|
+
requestDeadlineMs: state.agent.modelSettings?.maxModelRequestDurationMs,
|
|
917
|
+
maxStreamIdleMs: state.agent.modelSettings?.maxModelStreamIdleMs,
|
|
918
|
+
...options.generationGuard,
|
|
919
|
+
});
|
|
920
|
+
const consume = async () => {
|
|
921
|
+
const deadline = new GenerationStreamDeadlines({ totalMs: generationGuard.requestDeadlineMs, idleMs: generationGuard.maxStreamIdleMs }, () => requestAbortController.abort(), () => generationGuard.progress);
|
|
922
|
+
const toolArgumentRunaway = isLunaChainedRequest(state, request)
|
|
923
|
+
? new ToolArgumentRunawayGuard({
|
|
924
|
+
...DEFAULT_TOOL_ARGUMENT_RUNAWAY_SIGNATURE,
|
|
925
|
+
timeoutMs: generationGuard.toolArgumentRunawayMs,
|
|
926
|
+
}, () => requestAbortController.abort())
|
|
927
|
+
: undefined;
|
|
928
|
+
let iterator;
|
|
929
|
+
let iteratorFinished = false;
|
|
887
930
|
try {
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
await
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
if (error instanceof RequestInputLimitError) {
|
|
896
|
-
this.#deps.logDiagnostic?.('Request input admission refused', {
|
|
897
|
-
code: error.code,
|
|
898
|
-
action: error.action,
|
|
899
|
-
reason: error.reason,
|
|
900
|
-
guardClass: 'admission',
|
|
901
|
-
setting: 'agent.maxRequestInputTokens',
|
|
902
|
-
executionScope: state.hookScope ?? 'root',
|
|
903
|
-
recovery: 'retained_work',
|
|
904
|
-
limit: error.limit,
|
|
905
|
-
estimatedTokens: error.estimatedTokens,
|
|
906
|
-
observedTokens: error.observedTokens,
|
|
907
|
-
capacity: error.capacity,
|
|
908
|
-
outputReserve: error.outputReserve,
|
|
909
|
-
estimationReserve: error.estimationReserve,
|
|
910
|
-
});
|
|
911
|
-
throw error;
|
|
912
|
-
}
|
|
913
|
-
if (isProviderContextOverflow(error)) {
|
|
914
|
-
this.#observeTerminalFailure(error, {
|
|
915
|
-
requestId: activeRequestId,
|
|
916
|
-
provider: state.currentProviderId,
|
|
917
|
-
model: state.agent.model,
|
|
918
|
-
tier: resolveServiceTier(request),
|
|
919
|
-
});
|
|
920
|
-
throw new ProviderContextOverflowError(error);
|
|
921
|
-
}
|
|
922
|
-
const recoverOversizedToolArgument = isOversizedToolArgumentTrip(error) &&
|
|
923
|
-
(state.oversizedToolArgumentRecoveries ?? 0) < MAX_OVERSIZED_TOOL_ARGUMENT_RECOVERIES &&
|
|
924
|
-
!options.signal?.aborted;
|
|
925
|
-
if (recoverOversizedToolArgument)
|
|
926
|
-
requestSignal.abort();
|
|
927
|
-
else if (error instanceof GenerationGuardError)
|
|
928
|
-
requestAbortController.abort();
|
|
929
|
-
const decision = classifyInLoopModelRetry(error, attempt, maxRetries, Math.random, {
|
|
930
|
-
previousResponseId: activeRequest?.previousResponseId,
|
|
931
|
-
});
|
|
932
|
-
const rollbackProvisionalAttempt = () => {
|
|
933
|
-
stream.output.splice(outputLengthBefore);
|
|
934
|
-
if (stream.newItems !== stream.output) {
|
|
935
|
-
stream.newItems.splice(newItemsLengthBefore);
|
|
931
|
+
iterator = model.stream(request)[Symbol.asyncIterator]();
|
|
932
|
+
while (true) {
|
|
933
|
+
const nextOperation = iterator.next();
|
|
934
|
+
const next = await deadline.wait(toolArgumentRunaway ? toolArgumentRunaway.wait(nextOperation) : nextOperation);
|
|
935
|
+
if (next.done) {
|
|
936
|
+
iteratorFinished = true;
|
|
937
|
+
return;
|
|
936
938
|
}
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
939
|
+
// Any streamed event is transport activity: re-arm the inactivity
|
|
940
|
+
// window so long legitimate reasoning is never mistaken for a stall.
|
|
941
|
+
deadline.recordActivity();
|
|
942
|
+
const event = next.value;
|
|
943
|
+
if (event.type === 'completion') {
|
|
944
|
+
toolArgumentRunaway?.observeToolCallCompleted();
|
|
945
|
+
generationGuard.observeCompletion(event.output);
|
|
946
|
+
completion = event;
|
|
947
|
+
continue;
|
|
945
948
|
}
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
949
|
+
if (event.type === 'text_delta') {
|
|
950
|
+
const startsNewPart = event.partId !== undefined &&
|
|
951
|
+
lastTextPartId !== undefined &&
|
|
952
|
+
event.partId !== lastTextPartId &&
|
|
953
|
+
event.text.length > 0;
|
|
954
|
+
if (event.partId !== undefined && event.text.length > 0)
|
|
955
|
+
lastTextPartId = event.partId;
|
|
956
|
+
const text = startsNewPart ? `${ASSISTANT_TEXT_PART_SEPARATOR}${event.text}` : event.text;
|
|
957
|
+
toolArgumentRunaway?.observeText();
|
|
958
|
+
generationGuard.observeText(text);
|
|
959
|
+
provisionalTextCharacters += text.length;
|
|
960
|
+
provisionalTextDeltas++;
|
|
961
|
+
outputPush(stream, queue, { type: 'text_delta', text });
|
|
962
|
+
continue;
|
|
963
|
+
}
|
|
964
|
+
if (event.type === 'codex_rate_limits') {
|
|
965
|
+
outputPush(stream, queue, { type: 'codex_rate_limits', rateLimits: event.rateLimits });
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
968
|
+
if (event.type === 'reasoning_delta') {
|
|
969
|
+
const accepted = generationGuard.observeReasoning(event.text);
|
|
970
|
+
if (!accepted)
|
|
971
|
+
continue;
|
|
972
|
+
const forwarded = accepted === event.text ? event : { ...event, text: accepted };
|
|
973
|
+
pendingNativeReasoning = appendNativeReasoning(pendingNativeReasoning, forwarded);
|
|
974
|
+
provisionalReasoningCharacters += accepted.length;
|
|
975
|
+
provisionalReasoningDeltas++;
|
|
976
|
+
outputPush(stream, queue, { type: 'reasoning_delta', text: accepted });
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
if (event.type === 'tool_call_streaming_delta') {
|
|
980
|
+
toolArgumentRunaway?.observeToolArgumentProgress(event.argumentCharCount);
|
|
981
|
+
generationGuard.observeToolArgumentProgress(event.argumentCharCount);
|
|
982
|
+
outputPush(stream, queue, event);
|
|
983
|
+
continue;
|
|
984
|
+
}
|
|
985
|
+
if (event.type === 'context_compaction_started' || event.type === 'context_compaction_completed') {
|
|
986
|
+
outputPush(stream, queue, event);
|
|
987
|
+
continue;
|
|
988
|
+
}
|
|
989
|
+
if (event.type === 'tool_call') {
|
|
990
|
+
toolArgumentRunaway?.observeToolCallCompleted();
|
|
991
|
+
generationGuard.observeToolCall(event.arguments);
|
|
992
|
+
// The authoritative completion may introduce a native
|
|
993
|
+
// replacement boundary before this call's reasoning.
|
|
994
|
+
if (pendingNativeReasoning)
|
|
995
|
+
deferredNativeReasoning.push(pendingNativeReasoning);
|
|
996
|
+
pendingNativeReasoning = undefined;
|
|
997
|
+
sawToolCall = true;
|
|
998
|
+
streamedToolCalls.push(event);
|
|
974
999
|
}
|
|
975
|
-
this.#deps.logDiagnostic?.('Retrying model request in run loop', {
|
|
976
|
-
attempt,
|
|
977
|
-
maxRetries,
|
|
978
|
-
kind: decision.kind,
|
|
979
|
-
delayMs: decision.delayMs,
|
|
980
|
-
error: describeError(error),
|
|
981
|
-
});
|
|
982
|
-
await (this.#deps.waitBeforeModelRetry ?? sleepWithAbort)(decision.delayMs, options.signal);
|
|
983
|
-
continue;
|
|
984
|
-
}
|
|
985
|
-
// Session recovery owns full-history rebuild for a chained delta.
|
|
986
|
-
// Retract this cycle's provisional tokens so a later chain_recovery
|
|
987
|
-
// does not append a second answer after a half-sentence.
|
|
988
|
-
if (!decision.retryable && decision.reason === 'chained_delta_not_self_contained') {
|
|
989
|
-
rollbackProvisionalAttempt();
|
|
990
1000
|
}
|
|
991
|
-
// Dispatch began, but no terminal completion was accepted. Record an
|
|
992
|
-
// unpriced marker so the summary stays honest (partial) rather than
|
|
993
|
-
// appearing exact. Observational only: the error still propagates.
|
|
994
|
-
const cancelled = error instanceof Error && error.name === 'AbortError';
|
|
995
|
-
const record = this.#appendCostRecord(state, {
|
|
996
|
-
requestId,
|
|
997
|
-
provider: state.currentProviderId,
|
|
998
|
-
model: state.agent.model,
|
|
999
|
-
tier: resolveServiceTier(request),
|
|
1000
|
-
outcome: cancelled ? 'cancelled' : 'failed',
|
|
1001
|
-
});
|
|
1002
|
-
// Only the live queue sees cost records: `output`/`newItems` feed
|
|
1003
|
-
// persistence and replay, and a cost event must never become a
|
|
1004
|
-
// restored history item.
|
|
1005
|
-
queue.push({ type: 'cost_update', record });
|
|
1006
|
-
this.#evaluateRunBudget(state, stream, queue);
|
|
1007
|
-
this.#observeTerminalFailure(error, {
|
|
1008
|
-
requestId,
|
|
1009
|
-
provider: state.currentProviderId,
|
|
1010
|
-
model: state.agent.model,
|
|
1011
|
-
tier: resolveServiceTier(request),
|
|
1012
|
-
});
|
|
1013
|
-
throw error;
|
|
1014
1001
|
}
|
|
1015
1002
|
finally {
|
|
1016
|
-
|
|
1003
|
+
deadline.dispose();
|
|
1004
|
+
toolArgumentRunaway?.dispose();
|
|
1005
|
+
if (iterator && !iteratorFinished)
|
|
1006
|
+
void iterator.return?.().catch(() => undefined);
|
|
1017
1007
|
}
|
|
1008
|
+
};
|
|
1009
|
+
const dispatch = async () => {
|
|
1010
|
+
state.requestPreparation?.prepare(request);
|
|
1011
|
+
// A response ID can be established after a self-contained request.
|
|
1012
|
+
// It does not make this run's full history a delta needing a snapshot.
|
|
1013
|
+
const needsChainedSnapshot = Boolean(request.previousResponseId && state.usesDeltaHistory);
|
|
1014
|
+
const fullContext = needsChainedSnapshot && state.compactionHistory
|
|
1015
|
+
? mergeChainedCompactionHistory(state.compactionHistory, state.history)
|
|
1016
|
+
: undefined;
|
|
1017
|
+
const contextCatalog = getCatalogModel(state.currentProviderId ?? 'openai', state.agent.model);
|
|
1018
|
+
enforceRequestInputLimit({
|
|
1019
|
+
contextWindow: contextCatalog?.contextWindow,
|
|
1020
|
+
maxOutputTokens: request.maxTokens,
|
|
1021
|
+
limit: state.requestInputLimit ? state.requestInputLimit() : state.agent.modelSettings?.maxRequestInputTokens,
|
|
1022
|
+
history: fullContext?.kind === 'ready'
|
|
1023
|
+
? fullContext.history
|
|
1024
|
+
: needsChainedSnapshot
|
|
1025
|
+
? [...(state.compactionHistory ?? []), ...request.input]
|
|
1026
|
+
: request.input,
|
|
1027
|
+
instructions: request.instructions ?? '',
|
|
1028
|
+
tools: request.tools,
|
|
1029
|
+
lastCompletedInputTokens: state.inputUsageInvalidatedByCompaction
|
|
1030
|
+
? undefined
|
|
1031
|
+
: state.lastCompletedInputTokens,
|
|
1032
|
+
chainedContextKnown: !needsChainedSnapshot || fullContext?.kind === 'ready',
|
|
1033
|
+
});
|
|
1034
|
+
await consume();
|
|
1035
|
+
};
|
|
1036
|
+
try {
|
|
1037
|
+
if (state.requestPreparation)
|
|
1038
|
+
await state.requestPreparation.run(dispatch);
|
|
1039
|
+
else
|
|
1040
|
+
await dispatch();
|
|
1041
|
+
break;
|
|
1018
1042
|
}
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1043
|
+
catch (error) {
|
|
1044
|
+
if (error instanceof RequestInputLimitError) {
|
|
1045
|
+
this.#deps.logDiagnostic?.('Request input admission refused', {
|
|
1046
|
+
code: error.code,
|
|
1047
|
+
action: error.action,
|
|
1048
|
+
reason: error.reason,
|
|
1049
|
+
guardClass: 'admission',
|
|
1050
|
+
setting: 'agent.maxRequestInputTokens',
|
|
1051
|
+
executionScope: state.hookScope ?? 'root',
|
|
1052
|
+
recovery: 'retained_work',
|
|
1053
|
+
limit: error.limit,
|
|
1054
|
+
estimatedTokens: error.estimatedTokens,
|
|
1055
|
+
observedTokens: error.observedTokens,
|
|
1056
|
+
capacity: error.capacity,
|
|
1057
|
+
outputReserve: error.outputReserve,
|
|
1058
|
+
estimationReserve: error.estimationReserve,
|
|
1059
|
+
});
|
|
1060
|
+
throw error;
|
|
1061
|
+
}
|
|
1062
|
+
if (isProviderContextOverflow(error)) {
|
|
1027
1063
|
this.#observeTerminalFailure(error, {
|
|
1028
1064
|
requestId: activeRequestId,
|
|
1029
1065
|
provider: state.currentProviderId,
|
|
1030
1066
|
model: state.agent.model,
|
|
1031
|
-
tier:
|
|
1067
|
+
tier: resolveServiceTier(request),
|
|
1032
1068
|
});
|
|
1033
|
-
throw error;
|
|
1034
|
-
}
|
|
1035
|
-
if (criticalWrapUp) {
|
|
1036
|
-
return finish(stream, state, queue);
|
|
1069
|
+
throw new ProviderContextOverflowError(error);
|
|
1037
1070
|
}
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
// activity. Later failures must not inherit an old episode's deadline.
|
|
1053
|
-
state.recoveryBudget?.noteModelResponseCompleted();
|
|
1054
|
-
// Commit the authoritative terminal state before handling terminal-only
|
|
1055
|
-
// tool calls. Approval may pause immediately after this point, and the
|
|
1056
|
-
// continuation must still carry the response that produced the calls.
|
|
1057
|
-
// Response IDs are provider-chain state, not generic run metadata. Keep
|
|
1058
|
-
// them out of continuation handles for providers that do not support
|
|
1059
|
-
// chaining, while still exposing the completed provider response on the
|
|
1060
|
-
// current stream for diagnostics.
|
|
1061
|
-
if (state.supportsConversationChaining && state.currentProviderId !== undefined) {
|
|
1062
|
-
state.responseId = finalCompletion.responseId;
|
|
1063
|
-
state.responseProviderId = state.currentProviderId;
|
|
1064
|
-
}
|
|
1065
|
-
else {
|
|
1066
|
-
state.responseId = undefined;
|
|
1067
|
-
state.responseProviderId = undefined;
|
|
1068
|
-
}
|
|
1069
|
-
let normalizedCompletionUsage;
|
|
1070
|
-
const upstreamProvider = extractAiSdkUpstreamProvider(finalCompletion.providerMetadata);
|
|
1071
|
-
if (finalCompletion.usage !== undefined) {
|
|
1072
|
-
normalizedCompletionUsage = normalizeModelUsage(finalCompletion.usage);
|
|
1073
|
-
if (upstreamProvider) {
|
|
1074
|
-
if (normalizedCompletionUsage) {
|
|
1075
|
-
normalizedCompletionUsage.upstream_provider = upstreamProvider;
|
|
1071
|
+
const recoverOversizedToolArgument = isOversizedToolArgumentTrip(error) &&
|
|
1072
|
+
(state.oversizedToolArgumentRecoveries ?? 0) < MAX_OVERSIZED_TOOL_ARGUMENT_RECOVERIES &&
|
|
1073
|
+
!options.signal?.aborted;
|
|
1074
|
+
if (recoverOversizedToolArgument)
|
|
1075
|
+
requestSignal.abort();
|
|
1076
|
+
else if (error instanceof GenerationGuardError)
|
|
1077
|
+
requestAbortController.abort();
|
|
1078
|
+
const decision = classifyInLoopModelRetry(error, attempt, maxRetries, Math.random, {
|
|
1079
|
+
previousResponseId: activeRequest?.previousResponseId,
|
|
1080
|
+
});
|
|
1081
|
+
const rollbackProvisionalAttempt = () => {
|
|
1082
|
+
stream.output.splice(outputLengthBefore);
|
|
1083
|
+
if (stream.newItems !== stream.output) {
|
|
1084
|
+
stream.newItems.splice(newItemsLengthBefore);
|
|
1076
1085
|
}
|
|
1077
|
-
|
|
1078
|
-
|
|
1086
|
+
if (provisionalTextDeltas > 0 || provisionalReasoningDeltas > 0) {
|
|
1087
|
+
queue.push({
|
|
1088
|
+
type: 'model_attempt_rollback',
|
|
1089
|
+
textCharacters: provisionalTextCharacters,
|
|
1090
|
+
reasoningCharacters: provisionalReasoningCharacters,
|
|
1091
|
+
textDeltas: provisionalTextDeltas,
|
|
1092
|
+
reasoningDeltas: provisionalReasoningDeltas,
|
|
1093
|
+
});
|
|
1079
1094
|
}
|
|
1095
|
+
};
|
|
1096
|
+
if (recoverOversizedToolArgument) {
|
|
1097
|
+
// No tool ran: a call is dispatched only after its arguments
|
|
1098
|
+
// complete. Discard the partial response and ask for smaller calls.
|
|
1099
|
+
state.oversizedToolArgumentRecoveries = (state.oversizedToolArgumentRecoveries ?? 0) + 1;
|
|
1100
|
+
state.input.splice(inputLengthBefore);
|
|
1101
|
+
state.history.splice(historyLengthBefore);
|
|
1102
|
+
state.criticalWrapUpDispatched = criticalWrapUpDispatchedBefore;
|
|
1103
|
+
rollbackProvisionalAttempt();
|
|
1104
|
+
this.#deps.logDiagnostic?.('Recovering oversized tool argument in run loop', {
|
|
1105
|
+
code: error.code,
|
|
1106
|
+
recovery: state.oversizedToolArgumentRecoveries,
|
|
1107
|
+
maxRecoveries: MAX_OVERSIZED_TOOL_ARGUMENT_RECOVERIES,
|
|
1108
|
+
});
|
|
1109
|
+
this.#queuePendingSystemNotice(oversizedToolArgumentNotice(error.message));
|
|
1110
|
+
this.#admitPendingSteers(state, stream, queue);
|
|
1111
|
+
continue;
|
|
1080
1112
|
}
|
|
1081
|
-
if (
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1113
|
+
if (decision.retryable && !options.signal?.aborted) {
|
|
1114
|
+
attempt++;
|
|
1115
|
+
state.input.splice(inputLengthBefore);
|
|
1116
|
+
state.history.splice(historyLengthBefore);
|
|
1117
|
+
state.criticalWrapUpDispatched = criticalWrapUpDispatchedBefore;
|
|
1118
|
+
rollbackProvisionalAttempt();
|
|
1119
|
+
if (decision.kind === 'chain_recovery') {
|
|
1120
|
+
state.disableChainingForAttempt = true;
|
|
1121
|
+
state.responseId = undefined;
|
|
1122
|
+
state.responseProviderId = undefined;
|
|
1085
1123
|
}
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
? { cachedInputTokens: accumulated.cache_read_tokens }
|
|
1096
|
-
: {}),
|
|
1097
|
-
...(accumulated.cache_creation_tokens !== undefined
|
|
1098
|
-
? { cacheWriteTokens: accumulated.cache_creation_tokens }
|
|
1099
|
-
: {}),
|
|
1100
|
-
};
|
|
1124
|
+
this.#deps.logDiagnostic?.('Retrying model request in run loop', {
|
|
1125
|
+
attempt,
|
|
1126
|
+
maxRetries,
|
|
1127
|
+
kind: decision.kind,
|
|
1128
|
+
delayMs: decision.delayMs,
|
|
1129
|
+
error: describeError(error),
|
|
1130
|
+
});
|
|
1131
|
+
await (this.#deps.waitBeforeModelRetry ?? sleepWithAbort)(decision.delayMs, options.signal);
|
|
1132
|
+
continue;
|
|
1101
1133
|
}
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1134
|
+
// Session recovery owns full-history rebuild for a chained delta.
|
|
1135
|
+
// Retract this cycle's provisional tokens so a later chain_recovery
|
|
1136
|
+
// does not append a second answer after a half-sentence.
|
|
1137
|
+
if (!decision.retryable && decision.reason === 'chained_delta_not_self_contained') {
|
|
1138
|
+
rollbackProvisionalAttempt();
|
|
1106
1139
|
}
|
|
1140
|
+
// Dispatch began, but no terminal completion was accepted. Record an
|
|
1141
|
+
// unpriced marker so the summary stays honest (partial) rather than
|
|
1142
|
+
// appearing exact. Observational only: the error still propagates.
|
|
1143
|
+
const cancelled = error instanceof Error && error.name === 'AbortError';
|
|
1144
|
+
const record = this.#appendCostRecord(state, {
|
|
1145
|
+
requestId,
|
|
1146
|
+
provider: state.currentProviderId,
|
|
1147
|
+
model: state.agent.model,
|
|
1148
|
+
tier: resolveServiceTier(request),
|
|
1149
|
+
outcome: cancelled ? 'cancelled' : 'failed',
|
|
1150
|
+
});
|
|
1151
|
+
// Only the live queue sees cost records: `output`/`newItems` feed
|
|
1152
|
+
// persistence and replay, and a cost event must never become a
|
|
1153
|
+
// restored history item.
|
|
1154
|
+
queue.push({ type: 'cost_update', record });
|
|
1155
|
+
this.#evaluateRunBudget(state, stream, queue);
|
|
1156
|
+
this.#observeTerminalFailure(error, {
|
|
1157
|
+
requestId,
|
|
1158
|
+
provider: state.currentProviderId,
|
|
1159
|
+
model: state.agent.model,
|
|
1160
|
+
tier: resolveServiceTier(request),
|
|
1161
|
+
});
|
|
1162
|
+
throw error;
|
|
1107
1163
|
}
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
}
|
|
1111
|
-
else {
|
|
1112
|
-
normalizedCompletionUsage = undefined;
|
|
1164
|
+
finally {
|
|
1165
|
+
options.signal?.removeEventListener('abort', abortRequest);
|
|
1113
1166
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
// likewise be terminal-only, so associate it before replaying calls.
|
|
1142
|
-
const toolCalls = [...streamedToolCalls];
|
|
1143
|
-
const hasTerminalReasoning = finalCompletion.output.some((item) => item.type === 'reasoning' && appendNativeReasoning(undefined, item) !== undefined);
|
|
1144
|
-
if (hasTerminalReasoning) {
|
|
1145
|
-
// Terminal output supplies native ordering and authoritative metadata.
|
|
1146
|
-
// Streamed fragments have already been displayed, but are not history.
|
|
1147
|
-
pendingNativeReasoning = undefined;
|
|
1167
|
+
}
|
|
1168
|
+
return {
|
|
1169
|
+
streamedToolCalls,
|
|
1170
|
+
completion,
|
|
1171
|
+
sawToolCall,
|
|
1172
|
+
activeRequestId,
|
|
1173
|
+
activeRequest,
|
|
1174
|
+
pendingNativeReasoning,
|
|
1175
|
+
deferredNativeReasoning,
|
|
1176
|
+
criticalWrapUp,
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
/** Accept response facts before any tool from that response can execute. */
|
|
1180
|
+
#recordResponse(context, response) {
|
|
1181
|
+
const { state, stream, queue } = context;
|
|
1182
|
+
const { streamedToolCalls, completion, sawToolCall, activeRequestId, activeRequest, deferredNativeReasoning, criticalWrapUp, } = response;
|
|
1183
|
+
let { pendingNativeReasoning } = response;
|
|
1184
|
+
if (!completion) {
|
|
1185
|
+
if (streamedToolCalls.length === 0) {
|
|
1186
|
+
const error = new Error('Application model turn ended without completion');
|
|
1187
|
+
this.#observeTerminalFailure(error, {
|
|
1188
|
+
requestId: activeRequestId,
|
|
1189
|
+
provider: state.currentProviderId,
|
|
1190
|
+
model: state.agent.model,
|
|
1191
|
+
tier: activeRequest ? resolveServiceTier(activeRequest) : 'standard',
|
|
1192
|
+
});
|
|
1193
|
+
throw error;
|
|
1148
1194
|
}
|
|
1149
|
-
|
|
1195
|
+
if (!criticalWrapUp) {
|
|
1150
1196
|
for (const reasoning of deferredNativeReasoning)
|
|
1151
1197
|
commitPendingNativeReasoning(state, stream, queue, reasoning);
|
|
1198
|
+
commitPendingNativeReasoning(state, stream, queue, pendingNativeReasoning);
|
|
1152
1199
|
}
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1200
|
+
return criticalWrapUp ? [] : streamedToolCalls;
|
|
1201
|
+
}
|
|
1202
|
+
const finalCompletion = completion;
|
|
1203
|
+
// A terminal response ends recovery, unlike partial tokens or tool
|
|
1204
|
+
// activity. Later failures must not inherit an old episode's deadline.
|
|
1205
|
+
state.recoveryBudget?.noteModelResponseCompleted();
|
|
1206
|
+
// Commit the authoritative terminal state before handling terminal-only
|
|
1207
|
+
// tool calls. Approval may pause immediately after this point, and the
|
|
1208
|
+
// continuation must still carry the response that produced the calls.
|
|
1209
|
+
// Response IDs are provider-chain state, not generic run metadata. Keep
|
|
1210
|
+
// them out of continuation handles for providers that do not support
|
|
1211
|
+
// chaining, while still exposing the completed provider response on the
|
|
1212
|
+
// current stream for diagnostics.
|
|
1213
|
+
if (state.supportsConversationChaining && state.currentProviderId !== undefined) {
|
|
1214
|
+
state.responseId = finalCompletion.responseId;
|
|
1215
|
+
state.responseProviderId = state.currentProviderId;
|
|
1216
|
+
}
|
|
1217
|
+
else {
|
|
1218
|
+
state.responseId = undefined;
|
|
1219
|
+
state.responseProviderId = undefined;
|
|
1220
|
+
}
|
|
1221
|
+
let normalizedCompletionUsage;
|
|
1222
|
+
const upstreamProvider = extractAiSdkUpstreamProvider(finalCompletion.providerMetadata);
|
|
1223
|
+
if (finalCompletion.usage !== undefined) {
|
|
1224
|
+
normalizedCompletionUsage = normalizeModelUsage(finalCompletion.usage);
|
|
1225
|
+
if (upstreamProvider) {
|
|
1226
|
+
if (normalizedCompletionUsage) {
|
|
1227
|
+
normalizedCompletionUsage.upstream_provider = upstreamProvider;
|
|
1166
1228
|
}
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
pendingNativeReasoning = commitPendingNativeReasoning(state, stream, queue, pendingNativeReasoning);
|
|
1170
|
-
const historyItem = {
|
|
1171
|
-
...item.item,
|
|
1172
|
-
providerOpaque: {
|
|
1173
|
-
provider: item.provider,
|
|
1174
|
-
...(item.sourceProvider ? { sourceProvider: item.sourceProvider } : {}),
|
|
1175
|
-
},
|
|
1176
|
-
};
|
|
1177
|
-
state.history.push(historyItem);
|
|
1178
|
-
state.input.push(item);
|
|
1179
|
-
if (isOpenAICompaction(item)) {
|
|
1180
|
-
// Completion usage describes the request before native replacement.
|
|
1181
|
-
// Retain billed usage/costs; invalidate only the next admission hint.
|
|
1182
|
-
state.lastCompletedInputTokens = undefined;
|
|
1183
|
-
state.inputUsageInvalidatedByCompaction = true;
|
|
1229
|
+
else {
|
|
1230
|
+
normalizedCompletionUsage = { upstream_provider: upstreamProvider };
|
|
1184
1231
|
}
|
|
1185
|
-
outputPush(stream, queue, { type: 'item', item });
|
|
1186
1232
|
}
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
content: [{ type: 'output_text', text: assistantText }],
|
|
1233
|
+
if (normalizedCompletionUsage) {
|
|
1234
|
+
if (normalizedCompletionUsage.prompt_tokens !== undefined) {
|
|
1235
|
+
state.lastCompletedInputTokens = normalizedCompletionUsage.prompt_tokens;
|
|
1236
|
+
state.inputUsageInvalidatedByCompaction = false;
|
|
1237
|
+
}
|
|
1238
|
+
const accumulated = addTokenUsage(normalizeModelUsage(state.usage), normalizedCompletionUsage);
|
|
1239
|
+
state.usage = {
|
|
1240
|
+
...(accumulated.prompt_tokens !== undefined ? { inputTokens: accumulated.prompt_tokens } : {}),
|
|
1241
|
+
...(accumulated.completion_tokens !== undefined ? { outputTokens: accumulated.completion_tokens } : {}),
|
|
1242
|
+
// `total_tokens` is the billable normalized total, including cache
|
|
1243
|
+
// creation tokens. Keep it alongside the application aliases so the
|
|
1244
|
+
// authoritative run accumulator never reconstructs a smaller total.
|
|
1245
|
+
...(accumulated.total_tokens !== undefined ? { totalTokens: accumulated.total_tokens } : {}),
|
|
1246
|
+
...(accumulated.cache_read_tokens !== undefined ? { cachedInputTokens: accumulated.cache_read_tokens } : {}),
|
|
1247
|
+
...(accumulated.cache_creation_tokens !== undefined
|
|
1248
|
+
? { cacheWriteTokens: accumulated.cache_creation_tokens }
|
|
1249
|
+
: {}),
|
|
1205
1250
|
};
|
|
1206
|
-
state.history.push(item);
|
|
1207
|
-
outputPush(stream, queue, { type: 'item', item });
|
|
1208
|
-
state.input.push({ type: 'message', role: 'assistant', content: [{ type: 'text', text: assistantText }] });
|
|
1209
1251
|
}
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
// the text stranded after its own tool result, where the assistant
|
|
1215
|
-
// message merger glued it onto the *next* turn's tool call and every
|
|
1216
|
-
// request ended with a bare assistant message. Models read that as a
|
|
1217
|
-
// turn truncated mid-sentence and restart instead of progressing.
|
|
1218
|
-
if (!state.criticalWrapUpPending && toolCalls.length > 0) {
|
|
1219
|
-
await this.#dispatchToolCalls(state, stream, queue, toolCalls, toolContext);
|
|
1252
|
+
else {
|
|
1253
|
+
// Providers may carry a future/opaque usage shape. Preserve the old
|
|
1254
|
+
// pass-through behavior when normalization cannot recognize it.
|
|
1255
|
+
state.usage = finalCompletion.usage;
|
|
1220
1256
|
}
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1257
|
+
}
|
|
1258
|
+
else if (upstreamProvider) {
|
|
1259
|
+
normalizedCompletionUsage = { upstream_provider: upstreamProvider };
|
|
1260
|
+
}
|
|
1261
|
+
else {
|
|
1262
|
+
normalizedCompletionUsage = undefined;
|
|
1263
|
+
}
|
|
1264
|
+
// Attribute cost while the dispatched request's identity is still known.
|
|
1265
|
+
// Emit it immediately so the UI can show this request's cost before the
|
|
1266
|
+
// run ends; the terminal result re-delivers the cumulative record list
|
|
1267
|
+
// and the session accumulator ignores the duplicates by request id.
|
|
1268
|
+
// Only the live queue sees the event: `output`/`newItems` feed
|
|
1269
|
+
// persistence and replay, and a cost event must never become a restored
|
|
1270
|
+
// history item.
|
|
1271
|
+
const record = this.#appendCostRecord(state, {
|
|
1272
|
+
requestId: activeRequestId,
|
|
1273
|
+
provider: state.currentProviderId,
|
|
1274
|
+
model: state.agent.model,
|
|
1275
|
+
tier: activeRequest ? resolveServiceTier(activeRequest) : 'standard',
|
|
1276
|
+
outcome: 'completed',
|
|
1277
|
+
usage: normalizedCompletionUsage,
|
|
1278
|
+
providerUsd: finalCompletion.costUsd,
|
|
1279
|
+
});
|
|
1280
|
+
queue.push({ type: 'cost_update', record });
|
|
1281
|
+
// Cost and usage arrive in the same completion metadata, so surface both
|
|
1282
|
+
// live. The footer is a per-request indicator: emit this request's own
|
|
1283
|
+
// usage, never the run accumulator in `state.usage`.
|
|
1284
|
+
if (normalizedCompletionUsage)
|
|
1285
|
+
queue.push({ type: 'usage_update', usage: normalizedCompletionUsage });
|
|
1286
|
+
this.#evaluateRunBudget(state, stream, queue);
|
|
1287
|
+
stream.lastResponseId = finalCompletion.responseId;
|
|
1288
|
+
stream.rawResponses?.push(finalCompletion);
|
|
1289
|
+
// Some provider adapters report function calls only in the terminal
|
|
1290
|
+
// completion rather than as separate stream events. Their reasoning may
|
|
1291
|
+
// likewise be terminal-only, so associate it before replaying calls.
|
|
1292
|
+
const toolCalls = [...streamedToolCalls];
|
|
1293
|
+
const hasTerminalReasoning = finalCompletion.output.some((item) => item.type === 'reasoning' && appendNativeReasoning(undefined, item) !== undefined);
|
|
1294
|
+
if (hasTerminalReasoning) {
|
|
1295
|
+
// Terminal output supplies native ordering and authoritative metadata.
|
|
1296
|
+
// Streamed fragments have already been displayed, but are not history.
|
|
1297
|
+
pendingNativeReasoning = undefined;
|
|
1298
|
+
}
|
|
1299
|
+
else {
|
|
1300
|
+
for (const reasoning of deferredNativeReasoning)
|
|
1301
|
+
commitPendingNativeReasoning(state, stream, queue, reasoning);
|
|
1302
|
+
}
|
|
1303
|
+
for (const item of finalCompletion.output) {
|
|
1304
|
+
if (item.type === 'reasoning') {
|
|
1305
|
+
// Each terminal native item is independently replayable; encrypted
|
|
1306
|
+
// reasoning cannot be concatenated or overwritten by its sibling.
|
|
1307
|
+
pendingNativeReasoning = commitPendingNativeReasoning(state, stream, queue, pendingNativeReasoning);
|
|
1308
|
+
pendingNativeReasoning = appendNativeReasoning(undefined, item);
|
|
1309
|
+
continue;
|
|
1228
1310
|
}
|
|
1311
|
+
if (item.type === 'tool_call') {
|
|
1312
|
+
pendingNativeReasoning = commitPendingNativeReasoning(state, stream, queue, pendingNativeReasoning);
|
|
1313
|
+
if (!sawToolCall)
|
|
1314
|
+
toolCalls.push(item);
|
|
1315
|
+
continue;
|
|
1316
|
+
}
|
|
1317
|
+
if (item.type !== 'provider_opaque')
|
|
1318
|
+
continue;
|
|
1319
|
+
pendingNativeReasoning = commitPendingNativeReasoning(state, stream, queue, pendingNativeReasoning);
|
|
1320
|
+
const historyItem = {
|
|
1321
|
+
...item.item,
|
|
1322
|
+
providerOpaque: {
|
|
1323
|
+
provider: item.provider,
|
|
1324
|
+
...(item.sourceProvider ? { sourceProvider: item.sourceProvider } : {}),
|
|
1325
|
+
},
|
|
1326
|
+
};
|
|
1327
|
+
state.history.push(historyItem);
|
|
1328
|
+
state.input.push(item);
|
|
1329
|
+
if (isOpenAICompaction(item)) {
|
|
1330
|
+
// Completion usage describes the request before native replacement.
|
|
1331
|
+
// Retain billed usage/costs; invalidate only the next admission hint.
|
|
1332
|
+
state.lastCompletedInputTokens = undefined;
|
|
1333
|
+
state.inputUsageInvalidatedByCompaction = true;
|
|
1334
|
+
}
|
|
1335
|
+
outputPush(stream, queue, { type: 'item', item });
|
|
1336
|
+
}
|
|
1337
|
+
pendingNativeReasoning = commitPendingNativeReasoning(state, stream, queue, pendingNativeReasoning);
|
|
1338
|
+
const assistantText = finalCompletion.output
|
|
1339
|
+
.filter((item) => item.type === 'message')
|
|
1340
|
+
.flatMap((item) => item.content)
|
|
1341
|
+
// Whitespace-only output (e.g. a background-notification acknowledgment)
|
|
1342
|
+
// must not enter the canonical history: the truthy check used to let
|
|
1343
|
+
// "\n\n" through, accumulating junk in model context.
|
|
1344
|
+
.map((part) => part.text)
|
|
1345
|
+
.filter((text) => text.length > 0)
|
|
1346
|
+
.join(ASSISTANT_TEXT_PART_SEPARATOR)
|
|
1347
|
+
.trim();
|
|
1348
|
+
if (assistantText) {
|
|
1349
|
+
stream.finalOutput = assistantText;
|
|
1350
|
+
const item = {
|
|
1351
|
+
type: 'message',
|
|
1352
|
+
role: 'assistant',
|
|
1353
|
+
content: [{ type: 'output_text', text: assistantText }],
|
|
1354
|
+
};
|
|
1355
|
+
state.history.push(item);
|
|
1356
|
+
outputPush(stream, queue, { type: 'item', item });
|
|
1357
|
+
state.input.push({ type: 'message', role: 'assistant', content: [{ type: 'text', text: assistantText }] });
|
|
1358
|
+
}
|
|
1359
|
+
return toolCalls;
|
|
1360
|
+
}
|
|
1361
|
+
/** Execute and settle one tool plan; approvals suspend this operation in place. */
|
|
1362
|
+
async #executeTools(context, response, calls) {
|
|
1363
|
+
let { state, stream, queue, options, toolContext } = context;
|
|
1364
|
+
if (response.criticalWrapUp)
|
|
1365
|
+
return true;
|
|
1366
|
+
if (!state.criticalWrapUpPending && calls.length > 0) {
|
|
1367
|
+
await this.#dispatchToolCalls(state, stream, queue, calls, toolContext);
|
|
1368
|
+
}
|
|
1369
|
+
if (state.toolExecution.terminateAfterExecution)
|
|
1370
|
+
return true;
|
|
1371
|
+
if (calls.length === 0)
|
|
1372
|
+
return false;
|
|
1373
|
+
while (state.pendingRunBudgetInteraction || state.toolExecution.pendingApprovals.length > 0) {
|
|
1374
|
+
if (await this.#waitForBudget(context))
|
|
1375
|
+
return true;
|
|
1376
|
+
({ state, stream, queue, options, toolContext } = context);
|
|
1377
|
+
let answeredApproval = false;
|
|
1229
1378
|
if (state.toolExecution.pendingApprovals.length > 0) {
|
|
1230
1379
|
stream.interruptions = state.toolExecution.pendingApprovals.map((item) => item.interruption);
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
if (stopDecision) {
|
|
1241
|
-
this.#deps.logDiagnostic?.('Normal stop policy decided', { action: stopDecision.action, turnCount: state.turnCount, ...stopDecision.diagnostics }, { severity: 'debug', eventType: 'run_loop.normal_stop_policy' });
|
|
1380
|
+
await this.#waitForDecision(context);
|
|
1381
|
+
({ state, stream, queue, options, toolContext } = context);
|
|
1382
|
+
if (state.approvalDecision) {
|
|
1383
|
+
state.toolExecution.resolveApproval({ callId: state.approvalDecisionCallId, interruption: state.approvalDecisionInterruption }, state.approvalDecision, state.approvalMessage, state.approvals);
|
|
1384
|
+
state.approvalDecision = undefined;
|
|
1385
|
+
state.approvalDecisionCallId = undefined;
|
|
1386
|
+
state.approvalDecisionInterruption = undefined;
|
|
1387
|
+
state.approvalMessage = undefined;
|
|
1388
|
+
answeredApproval = true;
|
|
1242
1389
|
}
|
|
1243
|
-
if (stopDecision?.action === 'continue') {
|
|
1244
|
-
this.#queuePendingSystemNotice(stopDecision.reminder);
|
|
1245
|
-
continue;
|
|
1246
|
-
}
|
|
1247
|
-
if (stopDecision?.terminalCause)
|
|
1248
|
-
state.terminalCause = stopDecision.terminalCause;
|
|
1249
|
-
// Evidence discovered when a response has already completed needs no
|
|
1250
|
-
// human boundary: there is no later request or tool to block.
|
|
1251
|
-
state.pendingRunBudgetInteraction = undefined;
|
|
1252
|
-
return finish(stream, state, queue);
|
|
1253
1390
|
}
|
|
1391
|
+
this.#assertLive(context);
|
|
1392
|
+
if (state.toolExecution.hasPlan) {
|
|
1393
|
+
await state.toolExecution.settle(this.#toolCallContext(state, stream, queue, toolContext));
|
|
1394
|
+
}
|
|
1395
|
+
stream.interruptions = state.toolExecution.pendingApprovals.map((item) => item.interruption);
|
|
1396
|
+
if (answeredApproval && options.stopAfterApprovalResolution && stream.interruptions.length === 0)
|
|
1397
|
+
return true;
|
|
1398
|
+
if (state.toolExecution.terminateAfterExecution)
|
|
1399
|
+
return true;
|
|
1400
|
+
}
|
|
1401
|
+
return false;
|
|
1402
|
+
}
|
|
1403
|
+
/** Normal no-tool completion; only an explicit stop-policy continuation repeats. */
|
|
1404
|
+
async #finishTurn(context) {
|
|
1405
|
+
const { state, options } = context;
|
|
1406
|
+
// The normal stop seam. A stop policy may keep the same run going; the
|
|
1407
|
+
// reminder is admitted at the next request boundary, where pending
|
|
1408
|
+
// budget evidence pauses first and turn accounting applies as usual.
|
|
1409
|
+
const stopDecision = options.onNormalStop && !options.signal?.aborted && !state.criticalWrapUpPending
|
|
1410
|
+
? await options.onNormalStop(state.history, state.agent, context.requestAbortController.signal)
|
|
1411
|
+
: undefined;
|
|
1412
|
+
if (stopDecision) {
|
|
1413
|
+
this.#deps.logDiagnostic?.('Normal stop policy decided', { action: stopDecision.action, turnCount: state.turnCount, ...stopDecision.diagnostics }, { severity: 'debug', eventType: 'run_loop.normal_stop_policy' });
|
|
1414
|
+
}
|
|
1415
|
+
if (stopDecision?.action === 'continue') {
|
|
1416
|
+
if (stopDecision.repairDeadline !== undefined)
|
|
1417
|
+
state.completionRepairDeadline = stopDecision.repairDeadline;
|
|
1418
|
+
this.#queuePendingSystemNotice(stopDecision.reminder);
|
|
1419
|
+
return false;
|
|
1254
1420
|
}
|
|
1421
|
+
if (stopDecision?.terminalCause)
|
|
1422
|
+
state.terminalCause = stopDecision.terminalCause;
|
|
1423
|
+
if (stopDecision?.action === 'stop' && stopDecision.notice) {
|
|
1424
|
+
this.#appendStopNotice(context, stopDecision.notice);
|
|
1425
|
+
}
|
|
1426
|
+
// Evidence discovered when a response has already completed needs no
|
|
1427
|
+
// human boundary: there is no later request or tool to block.
|
|
1428
|
+
state.pendingRunBudgetInteraction = undefined;
|
|
1429
|
+
return true;
|
|
1255
1430
|
}
|
|
1256
1431
|
async #dispatchToolCalls(state, stream, queue, events, toolContext) {
|
|
1257
1432
|
const context = this.#toolCallContext(state, stream, queue, toolContext);
|
|
@@ -1288,6 +1463,7 @@ export class ApplicationRunLoop {
|
|
|
1288
1463
|
});
|
|
1289
1464
|
outputPush(stream, queue, { type: 'item', item: callItem });
|
|
1290
1465
|
},
|
|
1466
|
+
onExecutionStarted: (callId) => state.live?.session?.acceptToolDispatch(callId),
|
|
1291
1467
|
onDispatch: (entry) => {
|
|
1292
1468
|
outputPush(stream, queue, {
|
|
1293
1469
|
type: 'tool_call_dispatched',
|
|
@@ -1388,13 +1564,6 @@ export class ApplicationRunLoop {
|
|
|
1388
1564
|
#requiresHumanBudgetDecision(event) {
|
|
1389
1565
|
return event.type === 'tool_stall' || (event.type === 'budget_stage' && event.stage !== 'soft');
|
|
1390
1566
|
}
|
|
1391
|
-
#pauseForRunBudgetInteraction(state, stream, queue) {
|
|
1392
|
-
const interaction = state.pendingRunBudgetInteraction;
|
|
1393
|
-
if (!interaction)
|
|
1394
|
-
return finish(stream, state, queue);
|
|
1395
|
-
stream.interruptions = [interaction];
|
|
1396
|
-
return finish(stream, state, queue);
|
|
1397
|
-
}
|
|
1398
1567
|
}
|
|
1399
1568
|
function isLunaChainedRequest(state, request) {
|
|
1400
1569
|
return (state.currentProviderId === 'codex' &&
|