@cjhyy/code-shell-core 0.5.0-rc.1 → 0.6.0-rc.1
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/README.md +13 -10
- package/dist/agent/agent-definition-registry.d.ts +21 -3
- package/dist/agent/agent-definition-registry.js +49 -18
- package/dist/agent/agent-definition.d.ts +32 -0
- package/dist/agent/agent-definition.js +49 -4
- package/dist/arena/arena.js +11 -12
- package/dist/arena/context/context-tools.js +47 -6
- package/dist/arena/context/within-root.d.ts +7 -0
- package/dist/arena/context/within-root.js +15 -0
- package/dist/arena/iterate/phases/argue.js +8 -2
- package/dist/arena/iterate/phases/revise.js +1 -1
- package/dist/arena/iterate/phases/tournament.js +3 -3
- package/dist/arena/ledger.js +9 -1
- package/dist/arena/phases/adjudication.js +1 -4
- package/dist/arena/phases/build-consensus.js +1 -4
- package/dist/arena/phases/cross-review.js +2 -8
- package/dist/arena/phases/debate-rounds.js +1 -4
- package/dist/arena/phases/participant-research.js +1 -4
- package/dist/arena/phases/planning-detail-expansion.js +8 -10
- package/dist/arena/planner.js +0 -1
- package/dist/arena/providers/docs.d.ts +1 -0
- package/dist/arena/providers/docs.js +5 -2
- package/dist/arena/providers/repo.d.ts +1 -0
- package/dist/arena/providers/repo.js +5 -2
- package/dist/arena/strategies/utils.d.ts +1 -4
- package/dist/arena/strategies/utils.js +7 -51
- package/dist/arena/transitions.d.ts +7 -2
- package/dist/arena/transitions.js +9 -4
- package/dist/arena/types.d.ts +8 -2
- package/dist/automation/cron-expr.d.ts +41 -0
- package/dist/automation/cron-expr.js +169 -0
- package/dist/automation/index.d.ts +41 -0
- package/dist/automation/index.js +39 -0
- package/dist/automation/runner.d.ts +84 -0
- package/dist/automation/runner.js +92 -0
- package/dist/automation/scheduler.d.ts +194 -0
- package/dist/automation/scheduler.js +623 -0
- package/dist/automation/store.d.ts +39 -0
- package/dist/automation/store.js +122 -0
- package/dist/automation/write-policy.d.ts +34 -0
- package/dist/automation/write-policy.js +113 -0
- package/dist/automation/write-run.d.ts +51 -0
- package/dist/automation/write-run.js +38 -0
- package/dist/capability-control/disabled-lists.d.ts +25 -0
- package/dist/capability-control/disabled-lists.js +57 -0
- package/dist/capability-control/index.d.ts +5 -0
- package/dist/capability-control/index.js +3 -0
- package/dist/capability-control/overlay.d.ts +64 -0
- package/dist/capability-control/overlay.js +116 -0
- package/dist/capability-control/project.d.ts +65 -0
- package/dist/capability-control/project.js +147 -0
- package/dist/capability-control/service.d.ts +70 -0
- package/dist/capability-control/service.js +147 -0
- package/dist/capability-control/types.d.ts +62 -0
- package/dist/capability-control/types.js +17 -0
- package/dist/cc-orchestrator/agent-adapter.d.ts +50 -0
- package/dist/cc-orchestrator/agent-adapter.js +133 -0
- package/dist/cc-orchestrator/cc-capability.d.ts +19 -0
- package/dist/cc-orchestrator/cc-capability.js +53 -0
- package/dist/cc-orchestrator/codex-session-discovery.d.ts +24 -0
- package/dist/cc-orchestrator/codex-session-discovery.js +191 -0
- package/dist/cc-orchestrator/codex-session-history.d.ts +25 -0
- package/dist/cc-orchestrator/codex-session-history.js +187 -0
- package/dist/cc-orchestrator/external-agent-changes.d.ts +19 -0
- package/dist/cc-orchestrator/external-agent-changes.js +214 -0
- package/dist/cc-orchestrator/external-agent-driver.d.ts +18 -0
- package/dist/cc-orchestrator/external-agent-driver.js +69 -0
- package/dist/cc-orchestrator/index.d.ts +8 -0
- package/dist/cc-orchestrator/index.js +8 -0
- package/dist/cc-orchestrator/relevance-judge.d.ts +15 -0
- package/dist/cc-orchestrator/relevance-judge.js +29 -0
- package/dist/cc-orchestrator/session-discovery.d.ts +46 -0
- package/dist/cc-orchestrator/session-discovery.js +125 -0
- package/dist/cc-orchestrator/session-history.d.ts +19 -0
- package/dist/cc-orchestrator/session-history.js +67 -0
- package/dist/cli/agent-server-stdio.d.ts +26 -1
- package/dist/cli/agent-server-stdio.js +221 -28
- package/dist/cli/agent-server-tcp.d.ts +20 -0
- package/dist/cli/agent-server-tcp.js +134 -0
- package/dist/cli/graceful-shutdown.d.ts +27 -0
- package/dist/cli/graceful-shutdown.js +30 -0
- package/dist/context/compaction.d.ts +56 -0
- package/dist/context/compaction.js +209 -15
- package/dist/context/manager.d.ts +0 -20
- package/dist/context/manager.js +25 -41
- package/dist/cost-tracker.js +5 -61
- package/dist/credentials/cipher.d.ts +49 -0
- package/dist/credentials/cipher.js +45 -0
- package/dist/credentials/cookie-jar.d.ts +24 -0
- package/dist/credentials/cookie-jar.js +40 -0
- package/dist/credentials/index.d.ts +7 -0
- package/dist/credentials/index.js +5 -0
- package/dist/credentials/inject-credential-tool.d.ts +20 -0
- package/dist/credentials/inject-credential-tool.js +130 -0
- package/dist/credentials/store.d.ts +72 -0
- package/dist/credentials/store.js +184 -0
- package/dist/credentials/types.d.ts +56 -0
- package/dist/credentials/use-credential-tool.d.ts +29 -0
- package/dist/credentials/use-credential-tool.js +205 -0
- package/dist/credentials/use-gate.d.ts +56 -0
- package/dist/credentials/use-gate.js +52 -0
- package/dist/cron/cron-runtime.d.ts +2 -0
- package/dist/cron/cron-runtime.js +2 -0
- package/dist/cron/cron-store.d.ts +2 -0
- package/dist/cron/cron-store.js +2 -0
- package/dist/cron/scheduler.d.ts +5 -32
- package/dist/cron/scheduler.js +5 -113
- package/dist/data/model-metadata.d.ts +77 -0
- package/dist/data/model-metadata.js +56 -0
- package/dist/data/model-metadata.json +216 -0
- package/dist/data/openrouter-models.d.ts +18 -7
- package/dist/data/openrouter-models.js +35 -8
- package/dist/data/openrouter-sync.js +1 -1
- package/dist/engine/aux-key.d.ts +10 -0
- package/dist/engine/aux-key.js +11 -0
- package/dist/engine/dynamic-tool-defs.d.ts +19 -0
- package/dist/engine/dynamic-tool-defs.js +36 -0
- package/dist/engine/engine.d.ts +431 -108
- package/dist/engine/engine.js +1630 -279
- package/dist/engine/friendly-error.d.ts +18 -0
- package/dist/engine/friendly-error.js +63 -0
- package/dist/engine/goal.d.ts +187 -0
- package/dist/engine/goal.js +196 -0
- package/dist/engine/image-compression.d.ts +81 -0
- package/dist/engine/image-compression.js +186 -0
- package/dist/engine/image-policy.d.ts +165 -0
- package/dist/engine/image-policy.js +189 -0
- package/dist/engine/model-connections-pool.d.ts +17 -0
- package/dist/engine/model-connections-pool.js +66 -0
- package/dist/engine/model-facade.d.ts +10 -0
- package/dist/engine/model-facade.js +25 -0
- package/dist/engine/patch-orphaned-tools.js +3 -0
- package/dist/engine/reactive-threshold.d.ts +13 -0
- package/dist/engine/reactive-threshold.js +17 -0
- package/dist/engine/resolve-llm-config.d.ts +16 -0
- package/dist/engine/resolve-llm-config.js +44 -0
- package/dist/engine/runtime.js +9 -1
- package/dist/engine/sandbox-cache-key.d.ts +10 -0
- package/dist/engine/sandbox-cache-key.js +9 -0
- package/dist/engine/sandbox-config.d.ts +31 -0
- package/dist/engine/sandbox-config.js +38 -0
- package/dist/engine/session-title.d.ts +13 -0
- package/dist/engine/session-title.js +34 -0
- package/dist/engine/session-usage.d.ts +19 -0
- package/dist/engine/session-usage.js +25 -0
- package/dist/engine/steer-queue.d.ts +32 -0
- package/dist/engine/steer-queue.js +26 -0
- package/dist/engine/streaming-tool-queue.d.ts +12 -0
- package/dist/engine/streaming-tool-queue.js +50 -10
- package/dist/engine/turn-loop.d.ts +94 -9
- package/dist/engine/turn-loop.js +775 -342
- package/dist/engine/types.d.ts +175 -0
- package/dist/engine/types.js +13 -0
- package/dist/external-agents/config.d.ts +2 -0
- package/dist/external-agents/config.js +15 -0
- package/dist/external-agents/types.d.ts +31 -0
- package/dist/external-agents/types.js +1 -0
- package/dist/git/parse-log.d.ts +13 -0
- package/dist/git/parse-log.js +21 -0
- package/dist/git/utils.d.ts +8 -6
- package/dist/git/utils.js +62 -26
- package/dist/git/worktree.d.ts +48 -0
- package/dist/git/worktree.js +111 -20
- package/dist/hooks/events.d.ts +23 -1
- package/dist/hooks/goal-stop-hook.d.ts +58 -0
- package/dist/hooks/goal-stop-hook.js +279 -0
- package/dist/hooks/hook-output.d.ts +33 -0
- package/dist/hooks/hook-output.js +79 -0
- package/dist/hooks/registry.d.ts +15 -0
- package/dist/hooks/registry.js +57 -1
- package/dist/hooks/shell-runner.d.ts +2 -0
- package/dist/hooks/shell-runner.js +48 -18
- package/dist/index.d.ts +79 -11
- package/dist/index.js +89 -6
- package/dist/llm/capabilities/param-specs.d.ts +14 -0
- package/dist/llm/capabilities/param-specs.js +62 -0
- package/dist/llm/capabilities/reasoning-control.d.ts +24 -0
- package/dist/llm/capabilities/reasoning-control.js +38 -0
- package/dist/llm/capabilities/rules.js +37 -3
- package/dist/llm/capabilities/types.d.ts +32 -2
- package/dist/llm/clamp-max-tokens.d.ts +11 -0
- package/dist/llm/clamp-max-tokens.js +17 -0
- package/dist/llm/client-base.d.ts +69 -5
- package/dist/llm/client-base.js +185 -25
- package/dist/llm/client-factory.d.ts +3 -3
- package/dist/llm/client-factory.js +2 -2
- package/dist/llm/model-cache.js +4 -2
- package/dist/llm/model-pool.d.ts +29 -11
- package/dist/llm/model-pool.js +36 -20
- package/dist/llm/provider-auth.d.ts +41 -0
- package/dist/llm/provider-auth.js +76 -0
- package/dist/llm/provider-catalog.d.ts +6 -2
- package/dist/llm/providers/anthropic.d.ts +32 -2
- package/dist/llm/providers/anthropic.js +207 -18
- package/dist/llm/providers/openai.d.ts +47 -2
- package/dist/llm/providers/openai.js +478 -71
- package/dist/llm/reasoning-setting.d.ts +51 -0
- package/dist/llm/reasoning-setting.js +40 -0
- package/dist/llm/stop-reason.d.ts +13 -0
- package/dist/llm/stop-reason.js +21 -0
- package/dist/llm/stream-watchdog.js +5 -1
- package/dist/llm/strip-vision.d.ts +32 -0
- package/dist/llm/strip-vision.js +94 -0
- package/dist/llm/token-counter.js +9 -2
- package/dist/llm/types.d.ts +9 -6
- package/dist/logging/logger.js +18 -4
- package/dist/logging/sanitize-messages.d.ts +10 -0
- package/dist/logging/sanitize-messages.js +112 -0
- package/dist/lsp/client.js +28 -8
- package/dist/lsp/manager.d.ts +1 -1
- package/dist/lsp/manager.js +42 -11
- package/dist/lsp/root-path.d.ts +9 -0
- package/dist/lsp/root-path.js +12 -0
- package/dist/model-catalog/builtin.d.ts +12 -0
- package/dist/model-catalog/builtin.js +407 -0
- package/dist/model-catalog/gen-connections.d.ts +20 -0
- package/dist/model-catalog/gen-connections.js +28 -0
- package/dist/model-catalog/index.d.ts +41 -0
- package/dist/model-catalog/index.js +90 -0
- package/dist/model-catalog/params.d.ts +20 -0
- package/dist/model-catalog/params.js +45 -0
- package/dist/model-catalog/resolve.d.ts +48 -0
- package/dist/model-catalog/resolve.js +33 -0
- package/dist/model-catalog/save-entry.d.ts +32 -0
- package/dist/model-catalog/save-entry.js +104 -0
- package/dist/model-catalog/types.d.ts +561 -0
- package/dist/model-catalog/types.js +93 -0
- package/dist/model-catalog/upsert.d.ts +9 -0
- package/dist/model-catalog/upsert.js +8 -0
- package/dist/onboarding.d.ts +12 -82
- package/dist/onboarding.js +75 -333
- package/dist/plugins/gitOps.d.ts +53 -0
- package/dist/plugins/gitOps.js +130 -5
- package/dist/plugins/installer/checkUpdate.d.ts +16 -0
- package/dist/plugins/installer/checkUpdate.js +32 -0
- package/dist/plugins/installer/codex/convertAgents.d.ts +7 -0
- package/dist/plugins/installer/codex/convertAgents.js +55 -0
- package/dist/plugins/installer/codex/convertCommands.d.ts +19 -0
- package/dist/plugins/installer/codex/convertCommands.js +46 -0
- package/dist/plugins/installer/codex/convertMcp.d.ts +11 -0
- package/dist/plugins/installer/codex/convertMcp.js +85 -0
- package/dist/plugins/installer/codex/convertSkills.d.ts +12 -0
- package/dist/plugins/installer/codex/convertSkills.js +33 -0
- package/dist/plugins/installer/detectFormat.d.ts +2 -0
- package/dist/plugins/installer/detectFormat.js +6 -0
- package/dist/plugins/installer/install.d.ts +7 -0
- package/dist/plugins/installer/install.js +123 -0
- package/dist/plugins/installer/installFromArchive.d.ts +43 -0
- package/dist/plugins/installer/installFromArchive.js +134 -0
- package/dist/plugins/installer/installFromSource.d.ts +13 -0
- package/dist/plugins/installer/installFromSource.js +51 -0
- package/dist/plugins/installer/list.d.ts +9 -0
- package/dist/plugins/installer/list.js +30 -0
- package/dist/plugins/installer/loadPluginAgents.d.ts +3 -0
- package/dist/plugins/installer/loadPluginAgents.js +27 -0
- package/dist/plugins/installer/loadPluginMcp.d.ts +14 -0
- package/dist/plugins/installer/loadPluginMcp.js +107 -0
- package/dist/plugins/installer/parseSource.d.ts +20 -0
- package/dist/plugins/installer/parseSource.js +74 -0
- package/dist/plugins/installer/paths.d.ts +5 -0
- package/dist/plugins/installer/paths.js +25 -0
- package/dist/plugins/installer/pruneDisabled.d.ts +24 -0
- package/dist/plugins/installer/pruneDisabled.js +73 -0
- package/dist/plugins/installer/types.d.ts +52 -0
- package/dist/plugins/installer/types.js +28 -0
- package/dist/plugins/installer/uninstall.d.ts +2 -0
- package/dist/plugins/installer/uninstall.js +18 -0
- package/dist/plugins/installer/unzip.d.ts +14 -0
- package/dist/plugins/installer/unzip.js +82 -0
- package/dist/plugins/installer/update.d.ts +28 -0
- package/dist/plugins/installer/update.js +97 -0
- package/dist/plugins/loadPluginHooks.d.ts +54 -1
- package/dist/plugins/loadPluginHooks.js +83 -1
- package/dist/plugins/marketplaceManager.d.ts +15 -1
- package/dist/plugins/marketplaceManager.js +58 -4
- package/dist/plugins/pluginCommandHook.js +34 -14
- package/dist/plugins/pluginContent.d.ts +30 -0
- package/dist/plugins/pluginContent.js +83 -0
- package/dist/plugins/pluginInstaller.d.ts +34 -0
- package/dist/plugins/pluginInstaller.js +168 -35
- package/dist/plugins/schemas.js +41 -6
- package/dist/plugins/types.d.ts +10 -0
- package/dist/preset/index.d.ts +7 -1
- package/dist/preset/index.js +112 -6
- package/dist/product/types.d.ts +1 -1
- package/dist/prompt/composer.d.ts +31 -0
- package/dist/prompt/composer.js +95 -25
- package/dist/prompt/instruction-scanner.js +5 -3
- package/dist/prompt/section-loader.js +1 -0
- package/dist/prompt/sections/base.md +1 -0
- package/dist/prompt/sections/browser.md +10 -0
- package/dist/prompt/sections/coding.md +4 -0
- package/dist/prompt/sections/orchestration.md +8 -0
- package/dist/protocol/chat-session-manager.d.ts +15 -2
- package/dist/protocol/chat-session-manager.js +46 -0
- package/dist/protocol/chat-session.d.ts +82 -0
- package/dist/protocol/chat-session.js +121 -1
- package/dist/protocol/client.d.ts +27 -0
- package/dist/protocol/client.js +38 -0
- package/dist/protocol/redact.d.ts +50 -0
- package/dist/protocol/redact.js +71 -0
- package/dist/protocol/server.d.ts +153 -2
- package/dist/protocol/server.js +807 -39
- package/dist/protocol/tcp-transport.d.ts +45 -0
- package/dist/protocol/tcp-transport.js +74 -0
- package/dist/protocol/transport.js +3 -2
- package/dist/protocol/types.d.ts +69 -0
- package/dist/protocol/types.js +22 -0
- package/dist/quota/credentials.d.ts +3 -0
- package/dist/quota/credentials.js +80 -0
- package/dist/quota/index.d.ts +36 -0
- package/dist/quota/index.js +155 -0
- package/dist/quota/types.d.ts +48 -0
- package/dist/quota/types.js +13 -0
- package/dist/remote/bridge.d.ts +11 -2
- package/dist/remote/bridge.js +61 -33
- package/dist/review/review-prompt.d.ts +28 -0
- package/dist/review/review-prompt.js +81 -0
- package/dist/run/ArtifactTracker.js +5 -4
- package/dist/run/EngineRunner.d.ts +27 -1
- package/dist/run/EngineRunner.js +46 -8
- package/dist/run/FileRunStore.js +40 -11
- package/dist/run/RunApprovalBackend.js +25 -5
- package/dist/run/RunLock.d.ts +28 -2
- package/dist/run/RunLock.js +49 -7
- package/dist/run/RunManager.d.ts +12 -0
- package/dist/run/RunManager.js +86 -3
- package/dist/run/RunQueue.d.ts +1 -0
- package/dist/run/RunQueue.js +8 -2
- package/dist/run/factory.d.ts +8 -1
- package/dist/run/factory.js +1 -0
- package/dist/run/index.d.ts +2 -2
- package/dist/run/index.js +1 -1
- package/dist/run/redirect-target.d.ts +7 -0
- package/dist/run/redirect-target.js +13 -0
- package/dist/run/types.js +1 -1
- package/dist/runtime/background-shell.d.ts +139 -0
- package/dist/runtime/background-shell.js +495 -0
- package/dist/runtime/output-clean.d.ts +24 -0
- package/dist/runtime/output-clean.js +41 -0
- package/dist/runtime/ring-file.d.ts +64 -0
- package/dist/runtime/ring-file.js +174 -0
- package/dist/runtime/safe-spawn.js +15 -34
- package/dist/runtime/spawn-common.d.ts +148 -0
- package/dist/runtime/spawn-common.js +303 -0
- package/dist/runtime/truncate-output.d.ts +22 -0
- package/dist/runtime/truncate-output.js +49 -0
- package/dist/runtime/utf8-cut.d.ts +11 -0
- package/dist/runtime/utf8-cut.js +23 -0
- package/dist/services/auto-dream.d.ts +4 -0
- package/dist/services/auto-dream.js +26 -26
- package/dist/services/browser-open.d.ts +13 -0
- package/dist/services/browser-open.js +16 -0
- package/dist/services/diagnostics.d.ts +1 -2
- package/dist/services/diagnostics.js +12 -7
- package/dist/services/dream-consolidation.d.ts +57 -0
- package/dist/services/dream-consolidation.js +151 -0
- package/dist/services/extract-memories.d.ts +14 -1
- package/dist/services/extract-memories.js +45 -6
- package/dist/services/memory-orchestrator.d.ts +21 -0
- package/dist/services/memory-orchestrator.js +125 -32
- package/dist/services/notifier.d.ts +8 -0
- package/dist/services/notifier.js +39 -25
- package/dist/services/oauth.js +5 -7
- package/dist/services/session-memory-sort.d.ts +8 -0
- package/dist/services/session-memory-sort.js +9 -0
- package/dist/services/session-memory.js +30 -19
- package/dist/session/file-history.d.ts +124 -1
- package/dist/session/file-history.js +232 -9
- package/dist/session/memory.d.ts +116 -2
- package/dist/session/memory.js +250 -28
- package/dist/session/session-manager.d.ts +63 -1
- package/dist/session/session-manager.js +186 -5
- package/dist/session/simple-diff.d.ts +23 -0
- package/dist/session/simple-diff.js +84 -0
- package/dist/session/transcript.d.ts +25 -1
- package/dist/session/transcript.js +35 -2
- package/dist/session/undo-target.d.ts +67 -0
- package/dist/session/undo-target.js +144 -0
- package/dist/settings/disk-defaults.d.ts +42 -0
- package/dist/settings/disk-defaults.js +33 -0
- package/dist/settings/feature-flags.d.ts +64 -0
- package/dist/settings/feature-flags.js +61 -0
- package/dist/settings/manager.d.ts +101 -1
- package/dist/settings/manager.js +345 -15
- package/dist/settings/migrate-config.d.ts +45 -0
- package/dist/settings/migrate-config.js +125 -0
- package/dist/settings/personalization.d.ts +19 -0
- package/dist/settings/personalization.js +7 -0
- package/dist/settings/schema-export.d.ts +25 -0
- package/dist/settings/schema-export.js +38 -0
- package/dist/settings/schema.d.ts +1827 -396
- package/dist/settings/schema.js +361 -81
- package/dist/skills/scanner.d.ts +9 -0
- package/dist/skills/scanner.js +30 -2
- package/dist/stt/resolve-transcribe.d.ts +31 -0
- package/dist/stt/resolve-transcribe.js +108 -0
- package/dist/stt/transcribe.d.ts +51 -0
- package/dist/stt/transcribe.js +65 -0
- package/dist/tool-system/browser-bridge.d.ts +226 -0
- package/dist/tool-system/browser-bridge.js +163 -0
- package/dist/tool-system/builtin/add-marketplace.d.ts +12 -0
- package/dist/tool-system/builtin/add-marketplace.js +76 -0
- package/dist/tool-system/builtin/agent-heartbeat.d.ts +49 -0
- package/dist/tool-system/builtin/agent-heartbeat.js +89 -0
- package/dist/tool-system/builtin/agent-notifications.d.ts +12 -3
- package/dist/tool-system/builtin/agent-notifications.js +9 -3
- package/dist/tool-system/builtin/agent-output-file.d.ts +38 -0
- package/dist/tool-system/builtin/agent-output-file.js +72 -0
- package/dist/tool-system/builtin/agent-registry.d.ts +23 -0
- package/dist/tool-system/builtin/agent-registry.js +16 -4
- package/dist/tool-system/builtin/agent.d.ts +50 -11
- package/dist/tool-system/builtin/agent.js +576 -73
- package/dist/tool-system/builtin/apply-patch/applier.js +66 -8
- package/dist/tool-system/builtin/apply-patch/backup-targets.d.ts +10 -0
- package/dist/tool-system/builtin/apply-patch/backup-targets.js +30 -0
- package/dist/tool-system/builtin/apply-patch/index.js +7 -3
- package/dist/tool-system/builtin/arena.js +6 -7
- package/dist/tool-system/builtin/background-jobs.d.ts +76 -0
- package/dist/tool-system/builtin/background-jobs.js +124 -0
- package/dist/tool-system/builtin/background-shell-tools.d.ts +20 -0
- package/dist/tool-system/builtin/background-shell-tools.js +108 -0
- package/dist/tool-system/builtin/background-work.d.ts +67 -0
- package/dist/tool-system/builtin/background-work.js +86 -0
- package/dist/tool-system/builtin/bash-output-style.d.ts +32 -0
- package/dist/tool-system/builtin/bash-output-style.js +40 -0
- package/dist/tool-system/builtin/bash.d.ts +5 -2
- package/dist/tool-system/builtin/bash.js +98 -62
- package/dist/tool-system/builtin/browser-tools.d.ts +33 -0
- package/dist/tool-system/builtin/browser-tools.js +312 -0
- package/dist/tool-system/builtin/cancel-goal.d.ts +31 -0
- package/dist/tool-system/builtin/cancel-goal.js +64 -0
- package/dist/tool-system/builtin/check-quota.d.ts +15 -0
- package/dist/tool-system/builtin/check-quota.js +34 -0
- package/dist/tool-system/builtin/complete-goal.d.ts +25 -0
- package/dist/tool-system/builtin/complete-goal.js +45 -0
- package/dist/tool-system/builtin/config.js +7 -0
- package/dist/tool-system/builtin/cron.d.ts +7 -0
- package/dist/tool-system/builtin/cron.js +105 -9
- package/dist/tool-system/builtin/drive-claude-code.d.ts +30 -0
- package/dist/tool-system/builtin/drive-claude-code.js +157 -0
- package/dist/tool-system/builtin/edit-model-catalog.d.ts +3 -0
- package/dist/tool-system/builtin/edit-model-catalog.js +94 -0
- package/dist/tool-system/builtin/edit.d.ts +2 -1
- package/dist/tool-system/builtin/edit.js +21 -12
- package/dist/tool-system/builtin/eol.d.ts +29 -0
- package/dist/tool-system/builtin/eol.js +37 -0
- package/dist/tool-system/builtin/file-cache.d.ts +6 -0
- package/dist/tool-system/builtin/file-cache.js +8 -0
- package/dist/tool-system/builtin/generate-image.d.ts +60 -0
- package/dist/tool-system/builtin/generate-image.js +374 -0
- package/dist/tool-system/builtin/generate-video.d.ts +55 -0
- package/dist/tool-system/builtin/generate-video.js +361 -0
- package/dist/tool-system/builtin/grep.js +6 -2
- package/dist/tool-system/builtin/image-providers.d.ts +86 -0
- package/dist/tool-system/builtin/image-providers.js +190 -0
- package/dist/tool-system/builtin/image-uploader.d.ts +33 -0
- package/dist/tool-system/builtin/image-uploader.js +74 -0
- package/dist/tool-system/builtin/index.d.ts +24 -1
- package/dist/tool-system/builtin/index.js +310 -25
- package/dist/tool-system/builtin/mcp-tools.js +23 -3
- package/dist/tool-system/builtin/memory.js +45 -7
- package/dist/tool-system/builtin/notebook-edit.d.ts +2 -1
- package/dist/tool-system/builtin/notebook-edit.js +1 -1
- package/dist/tool-system/builtin/powershell.js +4 -1
- package/dist/tool-system/builtin/read.d.ts +2 -1
- package/dist/tool-system/builtin/read.js +11 -3
- package/dist/tool-system/builtin/repl.js +4 -1
- package/dist/tool-system/builtin/skill.js +9 -0
- package/dist/tool-system/builtin/sleep.js +12 -5
- package/dist/tool-system/builtin/task.js +4 -1
- package/dist/tool-system/builtin/tool-search.js +25 -7
- package/dist/tool-system/builtin/update-automation-memory.d.ts +24 -0
- package/dist/tool-system/builtin/update-automation-memory.js +60 -0
- package/dist/tool-system/builtin/video-providers.d.ts +154 -0
- package/dist/tool-system/builtin/video-providers.js +235 -0
- package/dist/tool-system/builtin/view-image.d.ts +18 -0
- package/dist/tool-system/builtin/view-image.js +96 -0
- package/dist/tool-system/builtin/web-fetch.js +12 -2
- package/dist/tool-system/builtin/web-search.d.ts +4 -1
- package/dist/tool-system/builtin/web-search.js +57 -10
- package/dist/tool-system/builtin/worktree.d.ts +2 -1
- package/dist/tool-system/builtin/worktree.js +31 -4
- package/dist/tool-system/builtin/write.d.ts +2 -1
- package/dist/tool-system/builtin/write.js +1 -1
- package/dist/tool-system/context.d.ts +166 -6
- package/dist/tool-system/executor.d.ts +3 -2
- package/dist/tool-system/executor.js +197 -119
- package/dist/tool-system/investigation-guard.d.ts +4 -1
- package/dist/tool-system/investigation-guard.js +20 -4
- package/dist/tool-system/mcp-manager.d.ts +78 -16
- package/dist/tool-system/mcp-manager.js +357 -28
- package/dist/tool-system/path-policy.d.ts +99 -0
- package/dist/tool-system/path-policy.js +578 -0
- package/dist/tool-system/permission.d.ts +36 -1
- package/dist/tool-system/permission.js +322 -117
- package/dist/tool-system/plan-mode-allowlist.d.ts +32 -0
- package/dist/tool-system/plan-mode-allowlist.js +65 -0
- package/dist/tool-system/registry.d.ts +1 -0
- package/dist/tool-system/registry.js +26 -1
- package/dist/tool-system/sandbox/index.d.ts +8 -0
- package/dist/tool-system/sandbox/index.js +7 -2
- package/dist/tool-system/validate-tool-metadata.d.ts +36 -0
- package/dist/tool-system/validate-tool-metadata.js +63 -0
- package/dist/tool-system/validation.d.ts +10 -0
- package/dist/tool-system/validation.js +16 -3
- package/dist/types.d.ts +301 -15
- package/dist/updater.js +20 -9
- package/dist/utils/envUtils.d.ts +0 -9
- package/dist/utils/envUtils.js +3 -28
- package/dist/utils/exec.d.ts +40 -0
- package/dist/utils/exec.js +144 -0
- package/dist/utils/format.js +4 -2
- package/dist/utils/json.d.ts +12 -0
- package/dist/utils/json.js +92 -0
- package/dist/utils/lockfile.js +9 -2
- package/dist/utils/theme.d.ts +7 -1
- package/dist/utils/theme.js +5 -3
- package/dist/utils/toolDisplay.js +0 -1
- package/package.json +14 -7
- package/dist/agent/coordinator.d.ts +0 -49
- package/dist/agent/coordinator.js +0 -77
- package/dist/settings/manager.test.js +0 -73
- package/dist/tool-system/builtin/remote-trigger.d.ts +0 -6
- package/dist/tool-system/builtin/remote-trigger.js +0 -54
- package/dist/tool-system/builtin/send-message.d.ts +0 -6
- package/dist/tool-system/builtin/send-message.js +0 -47
- /package/dist/{settings/manager.test.d.ts → credentials/types.js} +0 -0
|
@@ -13,7 +13,34 @@ import { ContextLimitError, LLMError, LLMRateLimitError } from "../../exceptions
|
|
|
13
13
|
import { logger } from "../../logging/logger.js";
|
|
14
14
|
import { countTokens } from "../token-counter.js";
|
|
15
15
|
import { capabilitiesFor } from "../capabilities/index.js";
|
|
16
|
+
import { clampMaxTokens } from "../clamp-max-tokens.js";
|
|
17
|
+
import { resolveApiKey, resolveHeaders } from "../provider-auth.js";
|
|
18
|
+
import { stripVisionFromHistory } from "../strip-vision.js";
|
|
16
19
|
import { STREAM_WATCHDOG_CONFIG, StreamIdleTimeoutError, } from "../stream-watchdog.js";
|
|
20
|
+
/**
|
|
21
|
+
* Extract prompt-cache counts from an OpenAI-compatible usage object.
|
|
22
|
+
*
|
|
23
|
+
* - Cache HITS live under `usage.prompt_tokens_details.cached_tokens` (NOT a
|
|
24
|
+
* top-level field). Both OpenAI and OpenRouter report this.
|
|
25
|
+
* - Cache WRITES (first-time prefix ingestion) are reported by OpenRouter as
|
|
26
|
+
* `prompt_tokens_details.cache_write_tokens` (verified live 2026-07-02).
|
|
27
|
+
* OpenAI's automatic caching has no separate write charge and omits it. We
|
|
28
|
+
* map it to `cacheCreationTokens` so the UI can show "writing cache" on the
|
|
29
|
+
* first turn, not just hits on later turns.
|
|
30
|
+
*
|
|
31
|
+
* Returns a spreadable partial so callers omit each key entirely when the API
|
|
32
|
+
* reports no value — keeping the field `undefined` rather than a misleading 0.
|
|
33
|
+
* See docs/todo/prompt-cache-optimization.md.
|
|
34
|
+
*/
|
|
35
|
+
function cachedTokensOf(usage) {
|
|
36
|
+
const details = usage?.prompt_tokens_details;
|
|
37
|
+
const out = {};
|
|
38
|
+
if (typeof details?.cached_tokens === "number")
|
|
39
|
+
out.cacheReadTokens = details.cached_tokens;
|
|
40
|
+
if (typeof details?.cache_write_tokens === "number")
|
|
41
|
+
out.cacheCreationTokens = details.cache_write_tokens;
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
17
44
|
/**
|
|
18
45
|
* Consume an async iterable of stream chunks with an idle watchdog.
|
|
19
46
|
* Returns the accumulated text — either from onChunk return values, or
|
|
@@ -26,12 +53,24 @@ import { STREAM_WATCHDOG_CONFIG, StreamIdleTimeoutError, } from "../stream-watch
|
|
|
26
53
|
* the function rejects with StreamIdleTimeoutError.
|
|
27
54
|
*/
|
|
28
55
|
export async function runStreamWithWatchdog(stream, opts = {}) {
|
|
29
|
-
|
|
56
|
+
// An explicit idleTimeoutMs always activates the watchdog. Otherwise follow
|
|
57
|
+
// disableWatchdog (per-call override) if set, else the env default.
|
|
58
|
+
const watchdogActive = opts.idleTimeoutMs !== undefined ||
|
|
59
|
+
(opts.disableWatchdog === undefined
|
|
60
|
+
? STREAM_WATCHDOG_CONFIG.enabled
|
|
61
|
+
: !opts.disableWatchdog);
|
|
30
62
|
const idleTimeoutMs = opts.idleTimeoutMs ?? STREAM_WATCHDOG_CONFIG.idleTimeoutMs;
|
|
31
63
|
let text = "";
|
|
32
64
|
// Fast path: watchdog disabled AND caller did not override → no overhead.
|
|
33
65
|
if (!watchdogActive) {
|
|
34
66
|
for await (const chunk of stream) {
|
|
67
|
+
// Stop consuming the moment the turn is aborted — do NOT forward more
|
|
68
|
+
// chunks to onChunk (which emits text_delta to the UI). The SDK may still
|
|
69
|
+
// be draining buffered chunks after abort(); this prevents them leaking
|
|
70
|
+
// post-Stop. `break` from a for-await calls the iterator's return() for us,
|
|
71
|
+
// letting the SDK tear the stream down.
|
|
72
|
+
if (opts.signal?.aborted)
|
|
73
|
+
break;
|
|
35
74
|
if (opts.onChunk) {
|
|
36
75
|
text += opts.onChunk(chunk) ?? "";
|
|
37
76
|
}
|
|
@@ -46,6 +85,11 @@ export async function runStreamWithWatchdog(stream, opts = {}) {
|
|
|
46
85
|
const iterator = stream[Symbol.asyncIterator]();
|
|
47
86
|
try {
|
|
48
87
|
while (true) {
|
|
88
|
+
// Abort short-circuit: stop before awaiting/forwarding the next chunk so
|
|
89
|
+
// buffered post-abort deltas never reach onChunk. The finally below calls
|
|
90
|
+
// iterator.return() to tear the SDK stream down.
|
|
91
|
+
if (opts.signal?.aborted)
|
|
92
|
+
break;
|
|
49
93
|
const nextPromise = iterator.next();
|
|
50
94
|
// Build a timeout promise that rejects if no chunk arrives in time.
|
|
51
95
|
let timeoutHandle = null;
|
|
@@ -54,14 +98,34 @@ export async function runStreamWithWatchdog(stream, opts = {}) {
|
|
|
54
98
|
reject(new StreamIdleTimeoutError(idleTimeoutMs, opts.requestId));
|
|
55
99
|
}, idleTimeoutMs);
|
|
56
100
|
});
|
|
101
|
+
// Abort promise: resolve as soon as the signal fires so a cancel mid-chunk
|
|
102
|
+
// (while awaiting the next delta) breaks out immediately instead of
|
|
103
|
+
// waiting for the next chunk or the idle deadline.
|
|
104
|
+
const abortCleanups = [];
|
|
105
|
+
const abortPromise = new Promise((resolve) => {
|
|
106
|
+
const sig = opts.signal;
|
|
107
|
+
if (!sig)
|
|
108
|
+
return; // never resolves → no effect on the race
|
|
109
|
+
if (sig.aborted) {
|
|
110
|
+
resolve({ aborted: true });
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const onAbort = () => resolve({ aborted: true });
|
|
114
|
+
sig.addEventListener("abort", onAbort, { once: true });
|
|
115
|
+
abortCleanups.push(() => sig.removeEventListener("abort", onAbort));
|
|
116
|
+
});
|
|
57
117
|
let result;
|
|
58
118
|
try {
|
|
59
|
-
result = await Promise.race([nextPromise, timeoutPromise]);
|
|
119
|
+
result = await Promise.race([nextPromise, timeoutPromise, abortPromise]);
|
|
60
120
|
}
|
|
61
121
|
finally {
|
|
62
122
|
if (timeoutHandle !== null)
|
|
63
123
|
clearTimeout(timeoutHandle);
|
|
124
|
+
for (const c of abortCleanups)
|
|
125
|
+
c();
|
|
64
126
|
}
|
|
127
|
+
if ("aborted" in result)
|
|
128
|
+
break;
|
|
65
129
|
if (result.done)
|
|
66
130
|
break;
|
|
67
131
|
const chunk = result.value;
|
|
@@ -88,17 +152,30 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
88
152
|
// client. Cheaper and more reliable than re-deriving from the model id when
|
|
89
153
|
// a new variant ships before our regex knows about it.
|
|
90
154
|
_forceMaxCompletionTokens = false;
|
|
91
|
-
|
|
92
|
-
|
|
155
|
+
// Sticky override: some gpt-5.x variants reject `reasoning_effort` when it's
|
|
156
|
+
// combined with `tools` on /v1/chat/completions ("Please use /v1/responses
|
|
157
|
+
// instead"). Once we see that 400, drop `reasoning_effort` for the lifetime
|
|
158
|
+
// of the client so tool-calling turns (e.g. the dream consolidation loop)
|
|
159
|
+
// succeed. Omitting the field just means "model default reasoning", which is
|
|
160
|
+
// fine for our background/aux calls.
|
|
161
|
+
_dropReasoningEffort = false;
|
|
162
|
+
constructor(config, defaults) {
|
|
163
|
+
super(config, defaults);
|
|
93
164
|
}
|
|
94
165
|
initClient() {
|
|
95
166
|
// Lazy init — client created on first use
|
|
96
167
|
}
|
|
97
168
|
get client() {
|
|
98
169
|
if (!this._client) {
|
|
170
|
+
const headers = resolveHeaders(this.config.httpHeaders);
|
|
99
171
|
this._client = new OpenAI({
|
|
100
|
-
apiKey:
|
|
172
|
+
// apiKey: explicit > authCommand stdout > OPENAI_API_KEY (TODO 7.2).
|
|
173
|
+
// OpenAI's SDK requires a non-empty string; fall back to a placeholder
|
|
174
|
+
// when a custom provider authenticates purely via httpHeaders.
|
|
175
|
+
apiKey: resolveApiKey(this.config, process.env.OPENAI_API_KEY) ??
|
|
176
|
+
(Object.keys(headers).length > 0 ? "x-headers-auth" : undefined),
|
|
101
177
|
...(this.config.baseUrl ? { baseURL: this.config.baseUrl } : {}),
|
|
178
|
+
...(Object.keys(headers).length > 0 ? { defaultHeaders: headers } : {}),
|
|
102
179
|
timeout: this.timeout,
|
|
103
180
|
});
|
|
104
181
|
}
|
|
@@ -119,12 +196,31 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
119
196
|
}
|
|
120
197
|
return this._capability;
|
|
121
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* True when this client routes an Anthropic-family model through OpenRouter's
|
|
201
|
+
* OpenAI-compatible endpoint. Anthropic caching is EXPLICIT — nothing is
|
|
202
|
+
* cached unless the request carries `cache_control` breakpoints (verified live
|
|
203
|
+
* 2026-07-02: plain requests to anthropic/claude-opus-4.7-fast via OpenRouter
|
|
204
|
+
* report cached_tokens 0 on every repeat; a single system-block breakpoint
|
|
205
|
+
* turns the whole stable prefix — tools + system — into a cache hit, ~89%
|
|
206
|
+
* cheaper on the follow-up). OpenAI and other OpenRouter models cache
|
|
207
|
+
* automatically, so they must NOT get breakpoints. The slug arrives resolved
|
|
208
|
+
* (e.g. "anthropic/claude-opus-4.7-fast") or as the router alias
|
|
209
|
+
* ("~anthropic/claude-opus-latest") — both start with an optional "~" then
|
|
210
|
+
* "anthropic/".
|
|
211
|
+
*/
|
|
212
|
+
get isOpenRouterAnthropic() {
|
|
213
|
+
return this.config.providerKind === "openrouter" && /^~?anthropic\//.test(this.model);
|
|
214
|
+
}
|
|
122
215
|
async createMessage(options) {
|
|
123
|
-
return this.withRetry(async () => {
|
|
124
|
-
//
|
|
125
|
-
// (
|
|
126
|
-
|
|
127
|
-
|
|
216
|
+
return this.withRetry(async (requestSignal) => {
|
|
217
|
+
// requestSignal = caller's cancel signal composed with a per-request
|
|
218
|
+
// hard deadline (withRetry). Hand it to the SDK so a wedged socket is
|
|
219
|
+
// torn down instead of hanging for tens of minutes.
|
|
220
|
+
// Per-call reasoning wins; otherwise fall back to provider default
|
|
221
|
+
// (settings.providers[].reasoning, threaded through LLMConfig).
|
|
222
|
+
const reasoning = options.reasoning ?? this.config.reasoning;
|
|
223
|
+
const messages = this.buildMessages(options.systemPrompt, options.messages, reasoning);
|
|
128
224
|
const tools = options.tools?.length ? this.convertTools(options.tools) : undefined;
|
|
129
225
|
const span = logger.span("llm.request", {
|
|
130
226
|
cat: "llm",
|
|
@@ -136,8 +232,8 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
136
232
|
});
|
|
137
233
|
try {
|
|
138
234
|
const response = options.stream && options.onChunk
|
|
139
|
-
? await this.streamMessage(options, messages, tools,
|
|
140
|
-
: await this.nonStreamMessage(options, messages, tools,
|
|
235
|
+
? await this.streamMessage(options, messages, tools, reasoning, requestSignal)
|
|
236
|
+
: await this.nonStreamMessage(options, messages, tools, reasoning, requestSignal);
|
|
141
237
|
span.end({
|
|
142
238
|
stopReason: response.stopReason,
|
|
143
239
|
promptTokens: response.usage?.promptTokens,
|
|
@@ -151,56 +247,88 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
151
247
|
span.fail(err);
|
|
152
248
|
throw err;
|
|
153
249
|
}
|
|
154
|
-
});
|
|
250
|
+
}, { signal: options.signal });
|
|
155
251
|
}
|
|
156
252
|
/**
|
|
157
253
|
* Build the request body honoring the model's capability descriptor.
|
|
158
254
|
* Centralized so both streaming and non-streaming paths agree on the
|
|
159
255
|
* exact shape.
|
|
160
256
|
*/
|
|
161
|
-
buildRequestBody(options, messages, tools,
|
|
257
|
+
buildRequestBody(options, messages, tools, reasoning, stream) {
|
|
162
258
|
const cap = this.capability;
|
|
163
|
-
|
|
259
|
+
// Clamp to the model's known output ceiling so a stale catalog value
|
|
260
|
+
// (e.g. 384000 inherited after a hot model switch) can't 400 a
|
|
261
|
+
// smaller-cap model. No known cap → send the value as-is.
|
|
262
|
+
const maxTokens = clampMaxTokens(options.maxTokens ?? this.maxTokens, cap.maxOutputTokens);
|
|
164
263
|
// Token-limit field — capability picks `max_tokens` vs `max_completion_tokens`.
|
|
165
264
|
// Sticky fallback (set by handleApiError on a 400) overrides the rule for
|
|
166
|
-
// ids the regex hasn't learned about yet.
|
|
265
|
+
// ids the regex hasn't learned about yet. When neither a requested value nor
|
|
266
|
+
// a known cap exists, omit the field entirely and let the endpoint apply its
|
|
267
|
+
// own ceiling (rather than inventing 8192 and truncating long outputs).
|
|
167
268
|
const useCompletion = this._forceMaxCompletionTokens || cap.tokenLimitField === "max_completion_tokens";
|
|
168
|
-
const tokenLimit =
|
|
169
|
-
? {
|
|
170
|
-
:
|
|
171
|
-
|
|
172
|
-
|
|
269
|
+
const tokenLimit = maxTokens === undefined
|
|
270
|
+
? {}
|
|
271
|
+
: useCompletion
|
|
272
|
+
? { max_completion_tokens: maxTokens }
|
|
273
|
+
: { max_tokens: maxTokens };
|
|
274
|
+
// Sampling params — only include if the model accepts them. Split by
|
|
275
|
+
// precedence so the request body composes correctly against catalog
|
|
276
|
+
// extraBody (see the merge below):
|
|
277
|
+
// - samplingDefault: the connection's DEFAULT temperature (this.temperature).
|
|
278
|
+
// It's the BASE — a user's catalog `temperature` in extraBody should
|
|
279
|
+
// override the bare default.
|
|
280
|
+
// - samplingOverride: a PER-REQUEST temperature (options.temperature). It
|
|
281
|
+
// wins over both the default and the catalog value.
|
|
282
|
+
const samplingDefault = {};
|
|
283
|
+
const samplingOverride = {};
|
|
173
284
|
if (!cap.rejectedParams.has("temperature")) {
|
|
174
|
-
|
|
175
|
-
|
|
285
|
+
if (options.temperature !== undefined) {
|
|
286
|
+
samplingOverride.temperature = options.temperature;
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
samplingDefault.temperature = this.temperature;
|
|
290
|
+
}
|
|
176
291
|
}
|
|
177
|
-
// Reasoning shape —
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
|
|
181
|
-
|
|
292
|
+
// Reasoning shape — translate the user's ReasoningSetting to the wire
|
|
293
|
+
// shape. Different vendors, different fields, never combine. We read the
|
|
294
|
+
// real picked level (no "medium" hardcode) — only fall back to "medium"
|
|
295
|
+
// when the setting says "thinking on" but carries no explicit effort
|
|
296
|
+
// ({mode:"on"}).
|
|
297
|
+
const reasoningBody = {};
|
|
298
|
+
// gpt-5.5+ 400s when `reasoning_effort` rides alongside `tools` on
|
|
299
|
+
// /v1/chat/completions. If the capability flags that combo AND this request
|
|
300
|
+
// carries tools, suppress the field up-front (same effect as the reactive
|
|
301
|
+
// _dropReasoningEffort sticky, but BEFORE the first failed request — so we
|
|
302
|
+
// never burn a 400 + 1s-backoff retry on every tool-using turn).
|
|
303
|
+
const suppressEffort = this._dropReasoningEffort ||
|
|
304
|
+
(cap.reasoning.kind === "openai-effort" &&
|
|
305
|
+
cap.reasoning.noEffortWithTools === true &&
|
|
306
|
+
!!tools?.length);
|
|
307
|
+
if (reasoning && reasoning.mode !== "off") {
|
|
182
308
|
switch (cap.reasoning.kind) {
|
|
183
309
|
case "deepseek-thinking":
|
|
184
310
|
// DeepSeek V4, Z.AI GLM-4.5+ — top-level {thinking: {type}}.
|
|
185
|
-
|
|
311
|
+
// Binary: any non-off means thinking on (effort irrelevant).
|
|
312
|
+
reasoningBody.thinking = { type: "enabled" };
|
|
186
313
|
break;
|
|
187
314
|
case "openai-effort":
|
|
188
315
|
// OpenAI o-series, gpt-5+, Gemini OpenAI-compat, xAI grok-4.3,
|
|
189
316
|
// Mistral magistral, Groq reasoning models — `reasoning_effort`.
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
317
|
+
// Send the user's real level; {mode:"on"} (no level) → "medium".
|
|
318
|
+
//
|
|
319
|
+
// Skip when the endpoint has told us (reactive _dropReasoningEffort)
|
|
320
|
+
// OR the capability tells us up-front (noEffortWithTools) that
|
|
321
|
+
// `reasoning_effort` is incompatible with `tools` here — sending it
|
|
322
|
+
// would just trigger the "use /v1/responses" 400.
|
|
323
|
+
if (!suppressEffort) {
|
|
324
|
+
reasoningBody.reasoning_effort =
|
|
325
|
+
reasoning.mode === "effort" ? reasoning.effort : "medium";
|
|
326
|
+
}
|
|
197
327
|
break;
|
|
198
328
|
case "openrouter-reasoning":
|
|
199
|
-
// OpenRouter normalized shape — {reasoning: {effort
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
? { effort: "minimal", exclude: true }
|
|
203
|
-
: { effort: "medium" };
|
|
329
|
+
// OpenRouter normalized shape — {reasoning: {effort}}.
|
|
330
|
+
reasoningBody.reasoning =
|
|
331
|
+
reasoning.mode === "effort" ? { effort: reasoning.effort } : { effort: "medium" };
|
|
204
332
|
break;
|
|
205
333
|
case "anthropic-budget":
|
|
206
334
|
case "anthropic-adaptive":
|
|
@@ -210,19 +338,79 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
210
338
|
break;
|
|
211
339
|
}
|
|
212
340
|
}
|
|
341
|
+
else if (reasoning && reasoning.mode === "off") {
|
|
342
|
+
// Explicit OFF — each shape's "don't think" wire form.
|
|
343
|
+
switch (cap.reasoning.kind) {
|
|
344
|
+
case "deepseek-thinking":
|
|
345
|
+
reasoningBody.thinking = { type: "disabled" };
|
|
346
|
+
break;
|
|
347
|
+
case "openai-effort":
|
|
348
|
+
// The capability's `disabledEffort` (defaults "minimal"; xAI "low",
|
|
349
|
+
// Mistral "none"). Skip if the endpoint already rejected the field
|
|
350
|
+
// OR the capability says it's incompatible with tools here.
|
|
351
|
+
if (!suppressEffort) {
|
|
352
|
+
reasoningBody.reasoning_effort = cap.reasoning.disabledEffort ?? "minimal";
|
|
353
|
+
}
|
|
354
|
+
break;
|
|
355
|
+
case "openrouter-reasoning":
|
|
356
|
+
reasoningBody.reasoning = { effort: "minimal", exclude: true };
|
|
357
|
+
break;
|
|
358
|
+
default:
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
// reasoning_summary (TODO 7.2): when the model uses the object-form
|
|
363
|
+
// reasoning shape (OpenRouter normalized / Responses-style), attach the
|
|
364
|
+
// requested summary level to that object. For the bare `reasoning_effort`
|
|
365
|
+
// shape there's no summary field on chat-completions, so we skip it rather
|
|
366
|
+
// than send an unknown top-level param.
|
|
367
|
+
if (this.config.reasoningSummary && reasoningBody.reasoning &&
|
|
368
|
+
typeof reasoningBody.reasoning === "object") {
|
|
369
|
+
reasoningBody.reasoning.summary =
|
|
370
|
+
this.config.reasoningSummary;
|
|
371
|
+
}
|
|
372
|
+
// Catalog-driven passthrough params (temperature/top_p/thinking etc, already
|
|
373
|
+
// wire-mapped from the connection's paramValues by applyParams). Filter each
|
|
374
|
+
// key by rejectedParams so we never send a field the model rejects (same
|
|
375
|
+
// contract as `sampling` above — e.g. temperature to gpt-5).
|
|
376
|
+
const extra = {};
|
|
377
|
+
const rejected = cap.rejectedParams;
|
|
378
|
+
for (const [k, v] of Object.entries(this.config.extraBody ?? {})) {
|
|
379
|
+
if (rejected.has(k))
|
|
380
|
+
continue;
|
|
381
|
+
extra[k] = v;
|
|
382
|
+
}
|
|
383
|
+
// Compose the param fragments with explicit precedence + deep merge:
|
|
384
|
+
// connection default sampling (base)
|
|
385
|
+
// ← static catalog extraBody
|
|
386
|
+
// ← per-request sampling override
|
|
387
|
+
// ← reasoning translation (wins)
|
|
388
|
+
// - A catalog `temperature` in extraBody overrides the bare connection
|
|
389
|
+
// default, but a PER-REQUEST temperature (options.temperature) overrides
|
|
390
|
+
// both — a plain `...sampling, ...extra` got this backwards. (review #4)
|
|
391
|
+
// - extra and reasoningBody can both carry nested objects under the SAME
|
|
392
|
+
// top-level key (a catalog param wired to `thinking.type`/`reasoning.effort`
|
|
393
|
+
// vs the reasoning translation's `thinking`/`reasoning`). A shallow spread
|
|
394
|
+
// would wholesale-replace one nested object; deepMergeInto merges them. (review #5)
|
|
395
|
+
const paramBody = {};
|
|
396
|
+
deepMergeInto(paramBody, samplingDefault);
|
|
397
|
+
deepMergeInto(paramBody, extra);
|
|
398
|
+
deepMergeInto(paramBody, samplingOverride);
|
|
399
|
+
deepMergeInto(paramBody, reasoningBody);
|
|
213
400
|
return {
|
|
214
401
|
model: this.model,
|
|
215
402
|
messages,
|
|
216
403
|
...tokenLimit,
|
|
217
|
-
...
|
|
218
|
-
|
|
404
|
+
...paramBody,
|
|
405
|
+
// service_tier (TODO 7.2): passed through verbatim when configured.
|
|
406
|
+
...(this.config.serviceTier ? { service_tier: this.config.serviceTier } : {}),
|
|
219
407
|
...(tools ? { tools } : {}),
|
|
220
408
|
...(stream ? { stream: true, stream_options: { include_usage: true } } : {}),
|
|
221
409
|
};
|
|
222
410
|
}
|
|
223
|
-
async nonStreamMessage(options, messages, tools,
|
|
411
|
+
async nonStreamMessage(options, messages, tools, reasoning, requestSignal) {
|
|
224
412
|
try {
|
|
225
|
-
const response = await this.client.chat.completions.create(this.buildRequestBody(options, messages, tools,
|
|
413
|
+
const response = await this.client.chat.completions.create(this.buildRequestBody(options, messages, tools, reasoning, false), { signal: requestSignal ?? options.signal });
|
|
226
414
|
const choice = response.choices[0];
|
|
227
415
|
if (!choice)
|
|
228
416
|
throw new LLMError("No response from OpenAI", "openai");
|
|
@@ -230,6 +418,9 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
230
418
|
promptTokens: response.usage?.prompt_tokens ?? 0,
|
|
231
419
|
completionTokens: response.usage?.completion_tokens ?? 0,
|
|
232
420
|
totalTokens: response.usage?.total_tokens ?? 0,
|
|
421
|
+
// OpenAI caching is automatic; hits land here, NOT in a top-level
|
|
422
|
+
// cacheReadTokens field. Reading the wrong field made hit-rate invisible.
|
|
423
|
+
...cachedTokensOf(response.usage),
|
|
233
424
|
};
|
|
234
425
|
this.recordUsage(usage, options);
|
|
235
426
|
return this.processChoice(choice, usage);
|
|
@@ -239,13 +430,19 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
239
430
|
throw err;
|
|
240
431
|
}
|
|
241
432
|
}
|
|
242
|
-
async streamMessage(options, messages, tools,
|
|
433
|
+
async streamMessage(options, messages, tools, reasoning, requestSignal) {
|
|
434
|
+
const sdkSignal = requestSignal ?? options.signal;
|
|
243
435
|
try {
|
|
244
|
-
const stream = await this.client.chat.completions.create(this.buildRequestBody(options, messages, tools,
|
|
436
|
+
const stream = await this.client.chat.completions.create(this.buildRequestBody(options, messages, tools, reasoning, true), { signal: sdkSignal });
|
|
245
437
|
let text = "";
|
|
246
438
|
let reasoningContent = "";
|
|
247
439
|
const toolCallsMap = new Map();
|
|
248
440
|
let streamUsage;
|
|
441
|
+
// Last finish_reason seen on the stream. Without this we returned a
|
|
442
|
+
// hardcoded "stop", so an output-cap cutoff (finish_reason "length")
|
|
443
|
+
// was indistinguishable from a clean finish and the turn loop never
|
|
444
|
+
// ran its max-output continuation. Capture it and return it verbatim.
|
|
445
|
+
let finishReason;
|
|
249
446
|
// TTFT — first chunk that actually carried text. Tool-call-only chunks
|
|
250
447
|
// earlier in the stream don't count: the user-visible "text starts now"
|
|
251
448
|
// moment is what we want to compare across providers.
|
|
@@ -257,6 +454,11 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
257
454
|
if (chunk.usage) {
|
|
258
455
|
streamUsage = chunk.usage;
|
|
259
456
|
}
|
|
457
|
+
// Capture finish_reason BEFORE the no-delta early return below: the
|
|
458
|
+
// final chunk frequently carries finish_reason with an empty delta.
|
|
459
|
+
const chunkFinish = chunk.choices?.[0]?.finish_reason;
|
|
460
|
+
if (chunkFinish)
|
|
461
|
+
finishReason = chunkFinish;
|
|
260
462
|
const delta = chunk.choices[0]?.delta;
|
|
261
463
|
if (!delta)
|
|
262
464
|
return "";
|
|
@@ -287,11 +489,18 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
287
489
|
for (const tc of delta.tool_calls) {
|
|
288
490
|
const idx = tc.index;
|
|
289
491
|
if (!toolCallsMap.has(idx)) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
492
|
+
const id = tc.id ?? "";
|
|
493
|
+
const name = tc.function?.name ?? "";
|
|
494
|
+
toolCallsMap.set(idx, { id, name, args: "" });
|
|
495
|
+
// Only announce once we actually have an id+name; some providers
|
|
496
|
+
// stream them across deltas. Emitting undefined here violates the
|
|
497
|
+
// ToolCall contract and breaks downstream consumers.
|
|
498
|
+
if (id && name) {
|
|
499
|
+
options.onChunk?.({
|
|
500
|
+
type: "tool_use_start",
|
|
501
|
+
toolCall: { id, toolName: name },
|
|
502
|
+
});
|
|
503
|
+
}
|
|
295
504
|
}
|
|
296
505
|
const existing = toolCallsMap.get(idx);
|
|
297
506
|
if (tc.id)
|
|
@@ -324,32 +533,49 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
324
533
|
return delta.content ?? "";
|
|
325
534
|
};
|
|
326
535
|
await runStreamWithWatchdog(stream, {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
536
|
+
// Idle watchdog is now ON by default (see STREAM_WATCHDOG_CONFIG) — it
|
|
537
|
+
// catches a stream that connects then stalls mid-generation; the
|
|
538
|
+
// per-request deadline (sdkSignal) catches connect/first-byte hangs.
|
|
539
|
+
idleTimeoutMs: STREAM_WATCHDOG_CONFIG.idleTimeoutMs,
|
|
330
540
|
requestId,
|
|
331
541
|
onChunk: handleChunk,
|
|
542
|
+
signal: sdkSignal,
|
|
332
543
|
});
|
|
333
544
|
const toolCalls = [];
|
|
334
545
|
for (const [, tc] of toolCallsMap) {
|
|
546
|
+
// Drop incomplete tool calls: an empty id or name (the fallbacks set
|
|
547
|
+
// when a delta never delivered them) would become a malformed call
|
|
548
|
+
// that breaks transcript keying and tool dispatch downstream.
|
|
549
|
+
if (!tc.id || !tc.name) {
|
|
550
|
+
logger.warn("openai.incomplete_tool_call_dropped", {
|
|
551
|
+
id: tc.id || "(empty)",
|
|
552
|
+
name: tc.name || "(empty)",
|
|
553
|
+
});
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
335
556
|
let args = {};
|
|
336
557
|
try {
|
|
337
558
|
args = JSON.parse(tc.args || "{}");
|
|
338
559
|
}
|
|
339
|
-
catch {
|
|
560
|
+
catch {
|
|
561
|
+
/* intentional: model emitted malformed tool-call JSON — fall back to
|
|
562
|
+
empty args rather than crashing the stream; the tool layer reports
|
|
563
|
+
the resulting validation error back to the model. */
|
|
564
|
+
}
|
|
340
565
|
toolCalls.push({ id: tc.id, toolName: tc.name, args });
|
|
341
566
|
}
|
|
342
567
|
const usage = {
|
|
343
568
|
promptTokens: streamUsage?.prompt_tokens ?? 0,
|
|
344
569
|
completionTokens: streamUsage?.completion_tokens ?? 0,
|
|
345
570
|
totalTokens: streamUsage?.total_tokens ?? 0,
|
|
571
|
+
...cachedTokensOf(streamUsage),
|
|
346
572
|
};
|
|
347
573
|
this.recordUsage(usage, options);
|
|
348
574
|
return {
|
|
349
575
|
text,
|
|
350
576
|
toolCalls,
|
|
351
577
|
usage,
|
|
352
|
-
stopReason: "stop",
|
|
578
|
+
stopReason: finishReason ?? "stop",
|
|
353
579
|
...(reasoningContent ? { reasoningContent } : {}),
|
|
354
580
|
};
|
|
355
581
|
}
|
|
@@ -367,7 +593,11 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
367
593
|
try {
|
|
368
594
|
args = JSON.parse(tc.function.arguments || "{}");
|
|
369
595
|
}
|
|
370
|
-
catch {
|
|
596
|
+
catch {
|
|
597
|
+
/* intentional: model emitted malformed tool-call JSON — fall back to
|
|
598
|
+
empty args rather than crashing; the tool layer reports the
|
|
599
|
+
resulting validation error back to the model. */
|
|
600
|
+
}
|
|
371
601
|
toolCalls.push({
|
|
372
602
|
id: tc.id,
|
|
373
603
|
toolName: tc.function.name,
|
|
@@ -384,8 +614,14 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
384
614
|
...(reasoningContent ? { reasoningContent } : {}),
|
|
385
615
|
};
|
|
386
616
|
}
|
|
387
|
-
buildMessages(systemPrompt, messages,
|
|
617
|
+
buildMessages(systemPrompt, messages, reasoning) {
|
|
388
618
|
const result = [{ role: "system", content: systemPrompt }];
|
|
619
|
+
// Drop historical image blocks when the active model can't accept vision.
|
|
620
|
+
// Engine.run only gates *new* attachments; an image left in history from
|
|
621
|
+
// when a vision model was active otherwise re-serializes into `image_url`
|
|
622
|
+
// below and 400s ("unknown variant `image_url`") after a model switch.
|
|
623
|
+
// Identity-preserving on the common path (vision models / no images).
|
|
624
|
+
messages = stripVisionFromHistory(messages, this.capability.supportsVision);
|
|
389
625
|
// Reasoning-content echo-back contract — driven by capability:
|
|
390
626
|
// "when-tools" : backfill an empty placeholder if the prior assistant
|
|
391
627
|
// turn doesn't carry one (DeepSeek V4 + tools 400s
|
|
@@ -398,7 +634,7 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
398
634
|
const cap = this.capability;
|
|
399
635
|
const hasTools = messages.some((m) => Array.isArray(m.content) &&
|
|
400
636
|
m.content.some((b) => b.type === "tool_use" || b.type === "tool_result"));
|
|
401
|
-
const needsReasoningBackfill =
|
|
637
|
+
const needsReasoningBackfill = reasoning?.mode !== "off" &&
|
|
402
638
|
cap.echoReasoning === "when-tools" &&
|
|
403
639
|
hasTools;
|
|
404
640
|
const stripReasoning = cap.echoReasoning === "never";
|
|
@@ -492,10 +728,44 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
492
728
|
const toolResults = [];
|
|
493
729
|
for (const block of msg.content) {
|
|
494
730
|
if (block.type === "tool_result" && block.tool_use_id) {
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
731
|
+
if (typeof block.content === "string") {
|
|
732
|
+
toolResults.push({
|
|
733
|
+
tool_use_id: block.tool_use_id,
|
|
734
|
+
content: block.content,
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
else if (Array.isArray(block.content)) {
|
|
738
|
+
// view_image returns an image inside tool_result.content. OpenAI's
|
|
739
|
+
// role:"tool" message can't carry an image, so split it: text stays
|
|
740
|
+
// in the tool message, image blocks are hoisted into imageParts and
|
|
741
|
+
// get emitted as their own user image_url message below.
|
|
742
|
+
const texts = [];
|
|
743
|
+
for (const inner of block.content) {
|
|
744
|
+
if (inner.type === "text" && inner.text) {
|
|
745
|
+
texts.push(inner.text);
|
|
746
|
+
}
|
|
747
|
+
else if (inner.type === "image" && inner.source) {
|
|
748
|
+
const wireDetail = mapImageDetailToOpenAI(this.imageDetail);
|
|
749
|
+
imageParts.push({
|
|
750
|
+
type: "image_url",
|
|
751
|
+
image_url: {
|
|
752
|
+
url: `data:${inner.source.media_type};base64,${inner.source.data}`,
|
|
753
|
+
...(wireDetail ? { detail: wireDetail } : {}),
|
|
754
|
+
},
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
toolResults.push({
|
|
759
|
+
tool_use_id: block.tool_use_id,
|
|
760
|
+
content: texts.length > 0 ? texts.join("\n") : "[image returned to user message]",
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
else {
|
|
764
|
+
toolResults.push({
|
|
765
|
+
tool_use_id: block.tool_use_id,
|
|
766
|
+
content: "",
|
|
767
|
+
});
|
|
768
|
+
}
|
|
499
769
|
}
|
|
500
770
|
else if (block.type === "text" && block.text) {
|
|
501
771
|
textParts.push(block.text);
|
|
@@ -503,13 +773,25 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
503
773
|
else if (block.type === "image" && block.source) {
|
|
504
774
|
// OpenAI-compat image_url: every supported provider (OpenAI,
|
|
505
775
|
// OpenRouter, OpenAI-compatible proxies for Gemini/xAI/etc)
|
|
506
|
-
// accepts a base64 data URL as the URL.
|
|
507
|
-
//
|
|
508
|
-
//
|
|
776
|
+
// accepts a base64 data URL as the URL. Non-vision models never
|
|
777
|
+
// reach here — stripVisionFromHistory() (top of buildMessages)
|
|
778
|
+
// has already swapped their image blocks for text placeholders,
|
|
779
|
+
// and Engine.run rejects *new* attachments to non-vision models.
|
|
780
|
+
//
|
|
781
|
+
// The `detail` hint is honored by OpenAI; OpenAI-compat
|
|
782
|
+
// proxies (OpenRouter for non-OpenAI models, etc.) tolerate
|
|
783
|
+
// the field even when their backend ignores it, so it's
|
|
784
|
+
// safe to always set when settings.images.detail is on.
|
|
785
|
+
// OpenAI's wire only accepts "low" / "high" / "auto"; map
|
|
786
|
+
// our internal "original" (a Codex-style high-fidelity
|
|
787
|
+
// marker) to "high" since OpenAI server-side scales 2048+
|
|
788
|
+
// images anyway.
|
|
789
|
+
const wireDetail = mapImageDetailToOpenAI(this.imageDetail);
|
|
509
790
|
imageParts.push({
|
|
510
791
|
type: "image_url",
|
|
511
792
|
image_url: {
|
|
512
793
|
url: `data:${block.source.media_type};base64,${block.source.data}`,
|
|
794
|
+
...(wireDetail ? { detail: wireDetail } : {}),
|
|
513
795
|
},
|
|
514
796
|
});
|
|
515
797
|
}
|
|
@@ -540,8 +822,55 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
540
822
|
}
|
|
541
823
|
}
|
|
542
824
|
}
|
|
825
|
+
if (this.isOpenRouterAnthropic) {
|
|
826
|
+
this.applyAnthropicCacheBreakpoints(result);
|
|
827
|
+
}
|
|
543
828
|
return result;
|
|
544
829
|
}
|
|
830
|
+
/**
|
|
831
|
+
* In-place: add prompt-cache breakpoints for Anthropic-over-OpenRouter.
|
|
832
|
+
* Mirrors the native anthropic provider (≤4 breakpoints):
|
|
833
|
+
* 1. System block — the stable prefix. Anthropic sees tools BEFORE the
|
|
834
|
+
* system prompt, so one marker on the system block caches tools too
|
|
835
|
+
* (verified live: system-only marker cached 3511/3952 prompt tokens
|
|
836
|
+
* including tool defs).
|
|
837
|
+
* 2. Last message — one rolling breakpoint so the growing conversation
|
|
838
|
+
* history becomes a cached prefix. Not scrolled: as history grows the
|
|
839
|
+
* "last message" naturally advances and its tail is the next write.
|
|
840
|
+
* A string `content` is lifted to a single-element `[{type:"text",...}]`
|
|
841
|
+
* array so it can carry `cache_control`; OpenRouter accepts this OpenAI
|
|
842
|
+
* multimodal wire form for text.
|
|
843
|
+
*/
|
|
844
|
+
applyAnthropicCacheBreakpoints(messages) {
|
|
845
|
+
const mark = (m) => {
|
|
846
|
+
if (!m)
|
|
847
|
+
return;
|
|
848
|
+
// Lift a plain-string content to a text-block array so it can carry the
|
|
849
|
+
// marker. Non-text content (tool messages, image arrays) already uses an
|
|
850
|
+
// array of parts — mark the last part instead.
|
|
851
|
+
if (typeof m.content === "string") {
|
|
852
|
+
m.content = [
|
|
853
|
+
{ type: "text", text: m.content, cache_control: { type: "ephemeral" } },
|
|
854
|
+
];
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
if (Array.isArray(m.content) && m.content.length > 0) {
|
|
858
|
+
// cache_control is an Anthropic-via-OpenRouter extension field, not in
|
|
859
|
+
// the OpenAI content-part union — attach through `unknown`.
|
|
860
|
+
const last = m.content[m.content.length - 1];
|
|
861
|
+
last.cache_control = { type: "ephemeral" };
|
|
862
|
+
}
|
|
863
|
+
};
|
|
864
|
+
// 1. System block (always index 0 — buildMessages seeds it first).
|
|
865
|
+
const sys = messages[0];
|
|
866
|
+
if (sys && sys.role === "system")
|
|
867
|
+
mark(sys);
|
|
868
|
+
// 2. Rolling history breakpoint on the very last message. Skip if it IS the
|
|
869
|
+
// system message (no conversation yet) — one breakpoint already covers it.
|
|
870
|
+
const last = messages[messages.length - 1];
|
|
871
|
+
if (last && last !== sys)
|
|
872
|
+
mark(last);
|
|
873
|
+
}
|
|
545
874
|
convertTools(tools) {
|
|
546
875
|
return tools.map((t) => ({
|
|
547
876
|
type: "function",
|
|
@@ -553,22 +882,57 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
553
882
|
}));
|
|
554
883
|
}
|
|
555
884
|
handleApiError(err) {
|
|
885
|
+
// User pressed ESC / Stop — the SDK throws APIUserAbortError when
|
|
886
|
+
// the request's AbortSignal fires mid-flight. Rethrow it unchanged
|
|
887
|
+
// so callers up the chain (turn-loop → server.ts) can recognise it
|
|
888
|
+
// as a cancellation rather than a real API failure. Wrapping it
|
|
889
|
+
// into "OpenAI API error: Request was aborted" was surfacing a
|
|
890
|
+
// scary toast for what is, from the user's perspective, "I clicked
|
|
891
|
+
// Stop and it worked."
|
|
892
|
+
if (err instanceof OpenAI.APIUserAbortError) {
|
|
893
|
+
throw err;
|
|
894
|
+
}
|
|
556
895
|
if (err instanceof OpenAI.APIError) {
|
|
557
896
|
if (err.status === 429) {
|
|
558
897
|
throw new LLMRateLimitError("openai");
|
|
559
898
|
}
|
|
560
899
|
const msg = (err.message ?? "").toLowerCase();
|
|
900
|
+
// Some 400s are deterministically self-correctable: we flip a sticky
|
|
901
|
+
// flag that changes the NEXT request body. For those we rethrow a
|
|
902
|
+
// STATUS-LESS LLMError so withRetry's isClientError() check doesn't bail
|
|
903
|
+
// (4xx is normally non-retryable) and the immediate retry goes out with
|
|
904
|
+
// the corrected body — fixing the call that triggered it, not just the
|
|
905
|
+
// next one.
|
|
906
|
+
let selfCorrected = false;
|
|
561
907
|
// o-series / gpt-5+ reject `max_tokens` and demand
|
|
562
908
|
// `max_completion_tokens`. The id-based regex catches the common
|
|
563
909
|
// cases; this is the belt-and-suspenders path for ids that ship
|
|
564
910
|
// before the regex knows about them (e.g. new `gpt-5.x` variants
|
|
565
|
-
// routed via OpenAI-compatible proxies).
|
|
566
|
-
// next request — including `withRetry`'s next attempt — sends the
|
|
567
|
-
// right field, instead of looping on the same 400.
|
|
911
|
+
// routed via OpenAI-compatible proxies).
|
|
568
912
|
if (err.status === 400 &&
|
|
569
913
|
msg.includes("max_tokens") &&
|
|
570
|
-
msg.includes("max_completion_tokens")
|
|
914
|
+
msg.includes("max_completion_tokens") &&
|
|
915
|
+
!this._forceMaxCompletionTokens) {
|
|
571
916
|
this._forceMaxCompletionTokens = true;
|
|
917
|
+
selfCorrected = true;
|
|
918
|
+
}
|
|
919
|
+
// gpt-5.x: "Function tools with reasoning_effort are not supported for
|
|
920
|
+
// <model> in /v1/chat/completions. Please use /v1/responses instead."
|
|
921
|
+
// Drop reasoning_effort for the lifetime of the client so the retry —
|
|
922
|
+
// and every later tool-calling turn — goes through. We can't switch to
|
|
923
|
+
// /v1/responses here, but tool calls work on /v1/chat/completions as long
|
|
924
|
+
// as reasoning_effort is absent.
|
|
925
|
+
if (err.status === 400 &&
|
|
926
|
+
msg.includes("reasoning_effort") &&
|
|
927
|
+
(msg.includes("tools") || msg.includes("/v1/responses")) &&
|
|
928
|
+
!this._dropReasoningEffort) {
|
|
929
|
+
this._dropReasoningEffort = true;
|
|
930
|
+
selfCorrected = true;
|
|
931
|
+
}
|
|
932
|
+
if (selfCorrected) {
|
|
933
|
+
// No status in details → withRetry treats it as retryable and reissues
|
|
934
|
+
// with the now-corrected request body.
|
|
935
|
+
throw new LLMError(`OpenAI API error (auto-correcting): ${err.message}`, "openai");
|
|
572
936
|
}
|
|
573
937
|
if (msg.includes("context_length_exceeded") ||
|
|
574
938
|
msg.includes("maximum context length") ||
|
|
@@ -593,6 +957,27 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
593
957
|
throw err;
|
|
594
958
|
}
|
|
595
959
|
}
|
|
960
|
+
/**
|
|
961
|
+
* Merge `src` into `dst` in place, recursing into plain-object values so a
|
|
962
|
+
* nested field (e.g. `thinking.type`) from one source doesn't wholesale-replace
|
|
963
|
+
* the nested object from another. Arrays and non-objects overwrite. `src` wins
|
|
964
|
+
* on leaf collisions. Used to compose the request body's param fragments
|
|
965
|
+
* (static extraBody base ← per-request sampling ← reasoning translation) so
|
|
966
|
+
* shared nested keys like `thinking`/`reasoning` merge instead of clobber.
|
|
967
|
+
*/
|
|
968
|
+
function deepMergeInto(dst, src) {
|
|
969
|
+
for (const [k, v] of Object.entries(src)) {
|
|
970
|
+
const cur = dst[k];
|
|
971
|
+
if (v && typeof v === "object" && !Array.isArray(v) &&
|
|
972
|
+
cur && typeof cur === "object" && !Array.isArray(cur)) {
|
|
973
|
+
deepMergeInto(cur, v);
|
|
974
|
+
}
|
|
975
|
+
else {
|
|
976
|
+
dst[k] = v;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
return dst;
|
|
980
|
+
}
|
|
596
981
|
/**
|
|
597
982
|
* Pull a non-empty reasoning payload out of a provider message.
|
|
598
983
|
*
|
|
@@ -604,3 +989,25 @@ function extractReasoningContent(msg) {
|
|
|
604
989
|
const candidate = msg.reasoning_content ?? msg.reasoning;
|
|
605
990
|
return typeof candidate === "string" && candidate.length > 0 ? candidate : undefined;
|
|
606
991
|
}
|
|
992
|
+
/**
|
|
993
|
+
* Map our internal image-clarity enum to the OpenAI wire enum.
|
|
994
|
+
*
|
|
995
|
+
* OpenAI accepts only `low` / `high` / `auto`. Our provider-agnostic
|
|
996
|
+
* levels are low / standard / high — the real token saving happens in
|
|
997
|
+
* the renderer downscale (long-edge cap) before send, so on the wire
|
|
998
|
+
* we only need the coarse low/high distinction: low→low, while both
|
|
999
|
+
* standard and high collapse to high (OpenAI's full-tile rendering).
|
|
1000
|
+
*
|
|
1001
|
+
* Legacy "original" (from older settings) is treated as high.
|
|
1002
|
+
*
|
|
1003
|
+
* Returns undefined when the caller didn't set a detail at all, so
|
|
1004
|
+
* the OpenAI client uses its own default ("auto", equivalent to
|
|
1005
|
+
* "high" today).
|
|
1006
|
+
*/
|
|
1007
|
+
function mapImageDetailToOpenAI(detail) {
|
|
1008
|
+
if (!detail)
|
|
1009
|
+
return undefined;
|
|
1010
|
+
if (detail === "low")
|
|
1011
|
+
return "low";
|
|
1012
|
+
return "high";
|
|
1013
|
+
}
|