@cjhyy/code-shell-core 0.5.0-rc.2 → 0.6.0-rc.2
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 +3 -0
- package/dist/agent/agent-definition-registry.js +3 -1
- package/dist/agent/agent-definition.d.ts +15 -0
- package/dist/agent/agent-definition.js +27 -3
- package/dist/arena/context/context-tools.js +47 -4
- package/dist/arena/ledger.js +9 -1
- package/dist/arena/strategies/utils.d.ts +1 -4
- package/dist/arena/strategies/utils.js +7 -84
- package/dist/automation/cron-expr.d.ts +10 -0
- package/dist/automation/cron-expr.js +23 -5
- package/dist/automation/runner.d.ts +21 -4
- package/dist/automation/runner.js +48 -12
- package/dist/automation/scheduler.d.ts +38 -0
- package/dist/automation/scheduler.js +89 -12
- package/dist/automation/store.js +5 -2
- package/dist/capability-control/disabled-lists.d.ts +25 -0
- package/dist/capability-control/disabled-lists.js +57 -0
- package/dist/capability-control/overlay.d.ts +15 -0
- package/dist/capability-control/overlay.js +27 -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 +15 -1
- package/dist/cli/agent-server-stdio.js +115 -17
- package/dist/cli/agent-server-tcp.js +40 -26
- package/dist/context/compaction.d.ts +56 -0
- package/dist/context/compaction.js +186 -0
- package/dist/context/manager.js +25 -4
- 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/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/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 +267 -138
- package/dist/engine/engine.js +957 -191
- package/dist/engine/friendly-error.d.ts +18 -0
- package/dist/engine/friendly-error.js +63 -0
- package/dist/engine/goal.d.ts +145 -0
- package/dist/engine/goal.js +144 -0
- package/dist/engine/image-policy.d.ts +13 -0
- package/dist/engine/image-policy.js +24 -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/resolve-llm-config.d.ts +16 -0
- package/dist/engine/resolve-llm-config.js +44 -0
- package/dist/engine/runtime.js +2 -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-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 +67 -3
- package/dist/engine/turn-loop.js +292 -41
- 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/utils.js +11 -6
- package/dist/git/worktree.d.ts +48 -0
- package/dist/git/worktree.js +86 -10
- package/dist/hooks/goal-stop-hook.d.ts +28 -0
- package/dist/hooks/goal-stop-hook.js +186 -9
- package/dist/hooks/registry.d.ts +8 -0
- package/dist/hooks/registry.js +19 -0
- package/dist/hooks/shell-runner.js +10 -21
- package/dist/index.d.ts +49 -6
- package/dist/index.js +60 -4
- package/dist/llm/capabilities/param-specs.d.ts +14 -0
- package/dist/llm/capabilities/param-specs.js +62 -0
- package/dist/llm/capabilities/rules.js +4 -0
- package/dist/llm/capabilities/types.d.ts +10 -0
- package/dist/llm/client-base.d.ts +28 -1
- package/dist/llm/client-base.js +119 -13
- package/dist/llm/model-cache.js +4 -2
- package/dist/llm/model-pool.d.ts +14 -0
- package/dist/llm/model-pool.js +13 -0
- package/dist/llm/provider-auth.d.ts +41 -0
- package/dist/llm/provider-auth.js +76 -0
- package/dist/llm/provider-catalog.d.ts +4 -0
- package/dist/llm/providers/anthropic.js +64 -14
- package/dist/llm/providers/openai.d.ts +36 -0
- package/dist/llm/providers/openai.js +257 -51
- package/dist/llm/reasoning-setting.d.ts +3 -3
- package/dist/llm/reasoning-setting.js +9 -1
- package/dist/llm/stream-watchdog.js +5 -1
- package/dist/llm/token-counter.js +9 -2
- package/dist/llm/types.d.ts +4 -0
- package/dist/logging/sanitize-messages.js +22 -0
- package/dist/lsp/manager.d.ts +1 -1
- package/dist/lsp/manager.js +40 -10
- 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 +61 -326
- package/dist/plugins/gitOps.d.ts +19 -0
- package/dist/plugins/gitOps.js +73 -4
- package/dist/plugins/installer/checkUpdate.d.ts +16 -0
- package/dist/plugins/installer/checkUpdate.js +32 -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 +5 -2
- package/dist/plugins/installer/codex/convertMcp.js +45 -5
- package/dist/plugins/installer/install.js +25 -1
- package/dist/plugins/installer/installFromArchive.d.ts +43 -0
- package/dist/plugins/installer/installFromArchive.js +134 -0
- package/dist/plugins/installer/installFromSource.js +8 -2
- package/dist/plugins/installer/loadPluginAgents.js +6 -2
- package/dist/plugins/installer/loadPluginMcp.d.ts +9 -2
- package/dist/plugins/installer/loadPluginMcp.js +35 -2
- package/dist/plugins/installer/pruneDisabled.d.ts +24 -0
- package/dist/plugins/installer/pruneDisabled.js +73 -0
- package/dist/plugins/installer/types.d.ts +5 -2
- package/dist/plugins/installer/types.js +1 -0
- package/dist/plugins/installer/uninstall.js +4 -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 +14 -0
- package/dist/plugins/installer/update.js +53 -21
- package/dist/plugins/loadPluginHooks.d.ts +47 -1
- package/dist/plugins/loadPluginHooks.js +73 -1
- package/dist/plugins/marketplaceManager.d.ts +7 -0
- package/dist/plugins/marketplaceManager.js +20 -0
- package/dist/plugins/pluginCommandHook.js +4 -18
- package/dist/plugins/pluginContent.d.ts +30 -0
- package/dist/plugins/pluginContent.js +83 -0
- package/dist/plugins/pluginInstaller.d.ts +13 -0
- package/dist/plugins/pluginInstaller.js +42 -8
- package/dist/plugins/schemas.js +1 -0
- package/dist/plugins/types.d.ts +6 -0
- package/dist/preset/index.d.ts +7 -1
- package/dist/preset/index.js +96 -6
- package/dist/product/types.d.ts +1 -1
- package/dist/prompt/composer.d.ts +27 -0
- package/dist/prompt/composer.js +67 -21
- 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/protocol/chat-session-manager.d.ts +8 -2
- package/dist/protocol/chat-session-manager.js +36 -0
- package/dist/protocol/chat-session.d.ts +65 -0
- package/dist/protocol/chat-session.js +94 -2
- package/dist/protocol/client.d.ts +27 -0
- package/dist/protocol/client.js +38 -0
- package/dist/protocol/server.d.ts +112 -0
- package/dist/protocol/server.js +586 -16
- package/dist/protocol/transport.js +3 -2
- package/dist/protocol/types.d.ts +46 -0
- package/dist/protocol/types.js +16 -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/review/review-prompt.d.ts +28 -0
- package/dist/review/review-prompt.js +81 -0
- package/dist/run/FileRunStore.js +8 -3
- package/dist/run/RunApprovalBackend.js +25 -5
- package/dist/run/RunManager.d.ts +12 -0
- package/dist/run/RunManager.js +35 -0
- package/dist/run/factory.d.ts +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/diagnostics.d.ts +1 -2
- package/dist/services/diagnostics.js +12 -7
- 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 -50
- package/dist/services/session-memory.js +24 -12
- package/dist/session/file-history.d.ts +124 -1
- package/dist/session/file-history.js +222 -6
- package/dist/session/memory.d.ts +116 -2
- package/dist/session/memory.js +250 -28
- package/dist/session/session-manager.d.ts +40 -0
- package/dist/session/session-manager.js +113 -3
- 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 +9 -2
- package/dist/settings/disk-defaults.js +11 -2
- package/dist/settings/feature-flags.d.ts +64 -0
- package/dist/settings/feature-flags.js +61 -0
- package/dist/settings/manager.d.ts +68 -1
- package/dist/settings/manager.js +266 -29
- package/dist/settings/migrate-config.d.ts +45 -0
- package/dist/settings/migrate-config.js +125 -0
- package/dist/settings/schema-export.d.ts +25 -0
- package/dist/settings/schema-export.js +38 -0
- package/dist/settings/schema.d.ts +1186 -771
- package/dist/settings/schema.js +268 -97
- 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/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 +12 -0
- package/dist/tool-system/builtin/agent-registry.js +8 -0
- package/dist/tool-system/builtin/agent.d.ts +21 -1
- package/dist/tool-system/builtin/agent.js +489 -42
- 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 +0 -15
- 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/config.js +7 -0
- package/dist/tool-system/builtin/cron.d.ts +7 -0
- package/dist/tool-system/builtin/cron.js +55 -3
- 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.js +20 -16
- 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 +35 -0
- package/dist/tool-system/builtin/generate-image.js +278 -50
- 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/glob.js +0 -7
- package/dist/tool-system/builtin/grep.js +6 -8
- 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 +7 -1
- package/dist/tool-system/builtin/index.js +249 -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.js +0 -7
- package/dist/tool-system/builtin/powershell.js +4 -1
- package/dist/tool-system/builtin/read.js +10 -10
- 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 +3 -2
- package/dist/tool-system/builtin/tool-search.js +25 -7
- 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/web-fetch.js +12 -2
- package/dist/tool-system/builtin/web-search.js +21 -7
- 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.js +0 -6
- package/dist/tool-system/context.d.ts +154 -6
- package/dist/tool-system/executor.d.ts +3 -1
- package/dist/tool-system/executor.js +172 -88
- package/dist/tool-system/investigation-guard.js +1 -1
- package/dist/tool-system/mcp-manager.d.ts +42 -4
- package/dist/tool-system/mcp-manager.js +186 -20
- package/dist/tool-system/path-policy.d.ts +5 -0
- package/dist/tool-system/path-policy.js +307 -8
- package/dist/tool-system/permission.d.ts +36 -1
- package/dist/tool-system/permission.js +281 -69
- package/dist/tool-system/plan-mode-allowlist.d.ts +13 -2
- package/dist/tool-system/plan-mode-allowlist.js +24 -2
- package/dist/tool-system/registry.d.ts +1 -0
- package/dist/tool-system/registry.js +16 -2
- 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/types.d.ts +217 -6
- 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/json.d.ts +12 -0
- package/dist/utils/json.js +92 -0
- package/dist/utils/theme.d.ts +1 -1
- package/dist/utils/theme.js +1 -1
- package/dist/utils/toolDisplay.js +0 -1
- package/package.json +13 -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
|
@@ -14,8 +14,33 @@ import { logger } from "../../logging/logger.js";
|
|
|
14
14
|
import { countTokens } from "../token-counter.js";
|
|
15
15
|
import { capabilitiesFor } from "../capabilities/index.js";
|
|
16
16
|
import { clampMaxTokens } from "../clamp-max-tokens.js";
|
|
17
|
+
import { resolveApiKey, resolveHeaders } from "../provider-auth.js";
|
|
17
18
|
import { stripVisionFromHistory } from "../strip-vision.js";
|
|
18
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
|
+
}
|
|
19
44
|
/**
|
|
20
45
|
* Consume an async iterable of stream chunks with an idle watchdog.
|
|
21
46
|
* Returns the accumulated text — either from onChunk return values, or
|
|
@@ -28,7 +53,12 @@ import { STREAM_WATCHDOG_CONFIG, StreamIdleTimeoutError, } from "../stream-watch
|
|
|
28
53
|
* the function rejects with StreamIdleTimeoutError.
|
|
29
54
|
*/
|
|
30
55
|
export async function runStreamWithWatchdog(stream, opts = {}) {
|
|
31
|
-
|
|
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);
|
|
32
62
|
const idleTimeoutMs = opts.idleTimeoutMs ?? STREAM_WATCHDOG_CONFIG.idleTimeoutMs;
|
|
33
63
|
let text = "";
|
|
34
64
|
// Fast path: watchdog disabled AND caller did not override → no overhead.
|
|
@@ -137,9 +167,15 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
137
167
|
}
|
|
138
168
|
get client() {
|
|
139
169
|
if (!this._client) {
|
|
170
|
+
const headers = resolveHeaders(this.config.httpHeaders);
|
|
140
171
|
this._client = new OpenAI({
|
|
141
|
-
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),
|
|
142
177
|
...(this.config.baseUrl ? { baseURL: this.config.baseUrl } : {}),
|
|
178
|
+
...(Object.keys(headers).length > 0 ? { defaultHeaders: headers } : {}),
|
|
143
179
|
timeout: this.timeout,
|
|
144
180
|
});
|
|
145
181
|
}
|
|
@@ -160,8 +196,27 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
160
196
|
}
|
|
161
197
|
return this._capability;
|
|
162
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
|
+
}
|
|
163
215
|
async createMessage(options) {
|
|
164
|
-
return this.withRetry(async () => {
|
|
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.
|
|
165
220
|
// Per-call reasoning wins; otherwise fall back to provider default
|
|
166
221
|
// (settings.providers[].reasoning, threaded through LLMConfig).
|
|
167
222
|
const reasoning = options.reasoning ?? this.config.reasoning;
|
|
@@ -177,8 +232,8 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
177
232
|
});
|
|
178
233
|
try {
|
|
179
234
|
const response = options.stream && options.onChunk
|
|
180
|
-
? await this.streamMessage(options, messages, tools, reasoning)
|
|
181
|
-
: await this.nonStreamMessage(options, messages, tools, reasoning);
|
|
235
|
+
? await this.streamMessage(options, messages, tools, reasoning, requestSignal)
|
|
236
|
+
: await this.nonStreamMessage(options, messages, tools, reasoning, requestSignal);
|
|
182
237
|
span.end({
|
|
183
238
|
stopReason: response.stopReason,
|
|
184
239
|
promptTokens: response.usage?.promptTokens,
|
|
@@ -192,7 +247,7 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
192
247
|
span.fail(err);
|
|
193
248
|
throw err;
|
|
194
249
|
}
|
|
195
|
-
});
|
|
250
|
+
}, { signal: options.signal });
|
|
196
251
|
}
|
|
197
252
|
/**
|
|
198
253
|
* Build the request body honoring the model's capability descriptor.
|
|
@@ -216,11 +271,23 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
216
271
|
: useCompletion
|
|
217
272
|
? { max_completion_tokens: maxTokens }
|
|
218
273
|
: { max_tokens: maxTokens };
|
|
219
|
-
// Sampling params — only include if the model accepts them.
|
|
220
|
-
|
|
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 = {};
|
|
221
284
|
if (!cap.rejectedParams.has("temperature")) {
|
|
222
|
-
|
|
223
|
-
|
|
285
|
+
if (options.temperature !== undefined) {
|
|
286
|
+
samplingOverride.temperature = options.temperature;
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
samplingDefault.temperature = this.temperature;
|
|
290
|
+
}
|
|
224
291
|
}
|
|
225
292
|
// Reasoning shape — translate the user's ReasoningSetting to the wire
|
|
226
293
|
// shape. Different vendors, different fields, never combine. We read the
|
|
@@ -228,6 +295,15 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
228
295
|
// when the setting says "thinking on" but carries no explicit effort
|
|
229
296
|
// ({mode:"on"}).
|
|
230
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);
|
|
231
307
|
if (reasoning && reasoning.mode !== "off") {
|
|
232
308
|
switch (cap.reasoning.kind) {
|
|
233
309
|
case "deepseek-thinking":
|
|
@@ -240,10 +316,11 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
240
316
|
// Mistral magistral, Groq reasoning models — `reasoning_effort`.
|
|
241
317
|
// Send the user's real level; {mode:"on"} (no level) → "medium".
|
|
242
318
|
//
|
|
243
|
-
// Skip
|
|
244
|
-
//
|
|
245
|
-
//
|
|
246
|
-
|
|
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) {
|
|
247
324
|
reasoningBody.reasoning_effort =
|
|
248
325
|
reasoning.mode === "effort" ? reasoning.effort : "medium";
|
|
249
326
|
}
|
|
@@ -269,8 +346,9 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
269
346
|
break;
|
|
270
347
|
case "openai-effort":
|
|
271
348
|
// The capability's `disabledEffort` (defaults "minimal"; xAI "low",
|
|
272
|
-
// Mistral "none"). Skip if the endpoint already rejected the field
|
|
273
|
-
|
|
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) {
|
|
274
352
|
reasoningBody.reasoning_effort = cap.reasoning.disabledEffort ?? "minimal";
|
|
275
353
|
}
|
|
276
354
|
break;
|
|
@@ -281,19 +359,58 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
281
359
|
break;
|
|
282
360
|
}
|
|
283
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);
|
|
284
400
|
return {
|
|
285
401
|
model: this.model,
|
|
286
402
|
messages,
|
|
287
403
|
...tokenLimit,
|
|
288
|
-
...
|
|
289
|
-
|
|
404
|
+
...paramBody,
|
|
405
|
+
// service_tier (TODO 7.2): passed through verbatim when configured.
|
|
406
|
+
...(this.config.serviceTier ? { service_tier: this.config.serviceTier } : {}),
|
|
290
407
|
...(tools ? { tools } : {}),
|
|
291
408
|
...(stream ? { stream: true, stream_options: { include_usage: true } } : {}),
|
|
292
409
|
};
|
|
293
410
|
}
|
|
294
|
-
async nonStreamMessage(options, messages, tools, reasoning) {
|
|
411
|
+
async nonStreamMessage(options, messages, tools, reasoning, requestSignal) {
|
|
295
412
|
try {
|
|
296
|
-
const response = await this.client.chat.completions.create(this.buildRequestBody(options, messages, tools, reasoning, false), { signal: options.signal });
|
|
413
|
+
const response = await this.client.chat.completions.create(this.buildRequestBody(options, messages, tools, reasoning, false), { signal: requestSignal ?? options.signal });
|
|
297
414
|
const choice = response.choices[0];
|
|
298
415
|
if (!choice)
|
|
299
416
|
throw new LLMError("No response from OpenAI", "openai");
|
|
@@ -301,6 +418,9 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
301
418
|
promptTokens: response.usage?.prompt_tokens ?? 0,
|
|
302
419
|
completionTokens: response.usage?.completion_tokens ?? 0,
|
|
303
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),
|
|
304
424
|
};
|
|
305
425
|
this.recordUsage(usage, options);
|
|
306
426
|
return this.processChoice(choice, usage);
|
|
@@ -310,9 +430,10 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
310
430
|
throw err;
|
|
311
431
|
}
|
|
312
432
|
}
|
|
313
|
-
async streamMessage(options, messages, tools, reasoning) {
|
|
433
|
+
async streamMessage(options, messages, tools, reasoning, requestSignal) {
|
|
434
|
+
const sdkSignal = requestSignal ?? options.signal;
|
|
314
435
|
try {
|
|
315
|
-
const stream = await this.client.chat.completions.create(this.buildRequestBody(options, messages, tools, reasoning, true), { signal:
|
|
436
|
+
const stream = await this.client.chat.completions.create(this.buildRequestBody(options, messages, tools, reasoning, true), { signal: sdkSignal });
|
|
316
437
|
let text = "";
|
|
317
438
|
let reasoningContent = "";
|
|
318
439
|
const toolCallsMap = new Map();
|
|
@@ -368,11 +489,18 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
368
489
|
for (const tc of delta.tool_calls) {
|
|
369
490
|
const idx = tc.index;
|
|
370
491
|
if (!toolCallsMap.has(idx)) {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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
|
+
}
|
|
376
504
|
}
|
|
377
505
|
const existing = toolCallsMap.get(idx);
|
|
378
506
|
if (tc.id)
|
|
@@ -405,26 +533,42 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
405
533
|
return delta.content ?? "";
|
|
406
534
|
};
|
|
407
535
|
await runStreamWithWatchdog(stream, {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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,
|
|
411
540
|
requestId,
|
|
412
541
|
onChunk: handleChunk,
|
|
413
|
-
signal:
|
|
542
|
+
signal: sdkSignal,
|
|
414
543
|
});
|
|
415
544
|
const toolCalls = [];
|
|
416
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
|
+
}
|
|
417
556
|
let args = {};
|
|
418
557
|
try {
|
|
419
558
|
args = JSON.parse(tc.args || "{}");
|
|
420
559
|
}
|
|
421
|
-
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
|
+
}
|
|
422
565
|
toolCalls.push({ id: tc.id, toolName: tc.name, args });
|
|
423
566
|
}
|
|
424
567
|
const usage = {
|
|
425
568
|
promptTokens: streamUsage?.prompt_tokens ?? 0,
|
|
426
569
|
completionTokens: streamUsage?.completion_tokens ?? 0,
|
|
427
570
|
totalTokens: streamUsage?.total_tokens ?? 0,
|
|
571
|
+
...cachedTokensOf(streamUsage),
|
|
428
572
|
};
|
|
429
573
|
this.recordUsage(usage, options);
|
|
430
574
|
return {
|
|
@@ -449,7 +593,11 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
449
593
|
try {
|
|
450
594
|
args = JSON.parse(tc.function.arguments || "{}");
|
|
451
595
|
}
|
|
452
|
-
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
|
+
}
|
|
453
601
|
toolCalls.push({
|
|
454
602
|
id: tc.id,
|
|
455
603
|
toolName: tc.function.name,
|
|
@@ -674,20 +822,56 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
674
822
|
}
|
|
675
823
|
}
|
|
676
824
|
}
|
|
825
|
+
if (this.isOpenRouterAnthropic) {
|
|
826
|
+
this.applyAnthropicCacheBreakpoints(result);
|
|
827
|
+
}
|
|
677
828
|
return result;
|
|
678
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
|
+
}
|
|
679
874
|
convertTools(tools) {
|
|
680
|
-
// DEBUG: OpenAI rejects tool names that don't match ^[a-zA-Z0-9_-]+$.
|
|
681
|
-
// Dump the full list with index + source so we can pin which tool
|
|
682
|
-
// (e.g. tools[33]) carries the illegal name. Remove once diagnosed.
|
|
683
|
-
const NAME_RE = /^[a-zA-Z0-9_-]+$/;
|
|
684
|
-
logger.info("openai.convertTools.names", {
|
|
685
|
-
count: tools.length,
|
|
686
|
-
names: tools.map((t, i) => `[${i}] ${t.name}${NAME_RE.test(t.name) ? "" : " <-- ILLEGAL"} (src=${t.source ?? "?"})`),
|
|
687
|
-
illegal: tools
|
|
688
|
-
.map((t, i) => ({ i, name: t.name, source: t.source }))
|
|
689
|
-
.filter((x) => !NAME_RE.test(x.name)),
|
|
690
|
-
});
|
|
691
875
|
return tools.map((t) => ({
|
|
692
876
|
type: "function",
|
|
693
877
|
function: {
|
|
@@ -773,6 +957,27 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
773
957
|
throw err;
|
|
774
958
|
}
|
|
775
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
|
+
}
|
|
776
981
|
/**
|
|
777
982
|
* Pull a non-empty reasoning payload out of a provider message.
|
|
778
983
|
*
|
|
@@ -785,14 +990,15 @@ function extractReasoningContent(msg) {
|
|
|
785
990
|
return typeof candidate === "string" && candidate.length > 0 ? candidate : undefined;
|
|
786
991
|
}
|
|
787
992
|
/**
|
|
788
|
-
* Map our internal image-
|
|
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).
|
|
789
1000
|
*
|
|
790
|
-
*
|
|
791
|
-
* `original` value through settings/config to match the Codex
|
|
792
|
-
* concept (preserve client-side dimensions, most expensive), but on
|
|
793
|
-
* the wire it has to collapse to `high` — OpenAI's server scales
|
|
794
|
-
* 2048+ images down regardless, so this is the closest faithful
|
|
795
|
-
* mapping.
|
|
1001
|
+
* Legacy "original" (from older settings) is treated as high.
|
|
796
1002
|
*
|
|
797
1003
|
* Returns undefined when the caller didn't set a detail at all, so
|
|
798
1004
|
* the OpenAI client uses its own default ("auto", equivalent to
|
|
@@ -27,13 +27,13 @@ export declare const ReasoningSettingSchema: z.ZodDiscriminatedUnion<"mode", [z.
|
|
|
27
27
|
mode: "on";
|
|
28
28
|
}>, z.ZodObject<{
|
|
29
29
|
mode: z.ZodLiteral<"effort">;
|
|
30
|
-
effort: z.
|
|
30
|
+
effort: z.ZodString;
|
|
31
31
|
}, "strip", z.ZodTypeAny, {
|
|
32
32
|
mode: "effort";
|
|
33
|
-
effort:
|
|
33
|
+
effort: string;
|
|
34
34
|
}, {
|
|
35
35
|
mode: "effort";
|
|
36
|
-
effort:
|
|
36
|
+
effort: string;
|
|
37
37
|
}>, z.ZodObject<{
|
|
38
38
|
mode: z.ZodLiteral<"budget">;
|
|
39
39
|
budgetTokens: z.ZodNumber;
|
|
@@ -11,11 +11,19 @@
|
|
|
11
11
|
* lingering caller/config still works (mapped to on/off).
|
|
12
12
|
*/
|
|
13
13
|
import { z } from "zod";
|
|
14
|
+
// Recommended effort levels — used to render UI dropdowns and as capability
|
|
15
|
+
// hints. NOT a hard schema constraint: effort is a free-form, catalog-driven
|
|
16
|
+
// param (a model's real ladder is declared by its catalog ParamSpec, and models
|
|
17
|
+
// gain/rename levels over time — e.g. gpt-5.5 added "xhigh", some models accept
|
|
18
|
+
// "max"). The settings schema validates the *shape* (mode/field names), not the
|
|
19
|
+
// *value*; pinning effort to a closed enum here made one unknown level
|
|
20
|
+
// (a connection's paramValues.reasoning flowing through the legacy models[]
|
|
21
|
+
// bridge) throw in validateSettings and take the whole app down on boot.
|
|
14
22
|
export const REASONING_EFFORTS = ["minimal", "low", "medium", "high", "xhigh"];
|
|
15
23
|
export const ReasoningSettingSchema = z.discriminatedUnion("mode", [
|
|
16
24
|
z.object({ mode: z.literal("off") }),
|
|
17
25
|
z.object({ mode: z.literal("on") }),
|
|
18
|
-
z.object({ mode: z.literal("effort"), effort: z.
|
|
26
|
+
z.object({ mode: z.literal("effort"), effort: z.string().min(1) }),
|
|
19
27
|
z.object({ mode: z.literal("budget"), budgetTokens: z.number().int().positive() }),
|
|
20
28
|
]);
|
|
21
29
|
/** Coerce legacy "enabled"/"disabled" or an object into a ReasoningSetting. */
|
|
@@ -64,7 +64,11 @@ export function createStreamWatchdog(opts) {
|
|
|
64
64
|
}
|
|
65
65
|
/** Environment-driven defaults. Read once at import time is fine — these never change inside a process. */
|
|
66
66
|
export const STREAM_WATCHDOG_CONFIG = {
|
|
67
|
-
|
|
67
|
+
// ON by default — a streamed response that connects then stalls mid-flight
|
|
68
|
+
// would otherwise hang until the SDK's (unreliable-for-half-dead-sockets)
|
|
69
|
+
// timeout, which we observed take 15–33 min in practice. Opt OUT with
|
|
70
|
+
// CODESHELL_ENABLE_STREAM_WATCHDOG=0 (rollback knob).
|
|
71
|
+
enabled: process.env.CODESHELL_ENABLE_STREAM_WATCHDOG !== "0",
|
|
68
72
|
idleTimeoutMs: parseInt(process.env.CODESHELL_STREAM_IDLE_TIMEOUT_MS || "", 10) || 90_000,
|
|
69
73
|
retries: parseInt(process.env.CODESHELL_STREAM_WATCHDOG_RETRIES || "", 10) || 2,
|
|
70
74
|
};
|
|
@@ -18,9 +18,16 @@ async function getEncoder() {
|
|
|
18
18
|
}
|
|
19
19
|
let encoderSync = null;
|
|
20
20
|
// Kick off the import so synchronous callers can use it on the next tick.
|
|
21
|
-
// First call still falls back to a length estimate.
|
|
22
|
-
|
|
21
|
+
// First call still falls back to a length estimate. The .catch keeps a failed
|
|
22
|
+
// import (corrupt node_modules / packaging error) from becoming an unhandled
|
|
23
|
+
// rejection — the chars/4 fallback below IS the intended degradation, so a
|
|
24
|
+
// load failure should stay silent, not surface as a process-level warning/crash.
|
|
25
|
+
void getEncoder()
|
|
26
|
+
.then((fn) => {
|
|
23
27
|
encoderSync = fn;
|
|
28
|
+
})
|
|
29
|
+
.catch(() => {
|
|
30
|
+
/* encoder unavailable — count() falls back to the length estimate */
|
|
24
31
|
});
|
|
25
32
|
/**
|
|
26
33
|
* Count tokens in `text`. Synchronous. If the encoder isn't loaded yet,
|
package/dist/llm/types.d.ts
CHANGED
|
@@ -32,5 +32,9 @@ export interface LLMUsageTracker {
|
|
|
32
32
|
totalPromptTokens: number;
|
|
33
33
|
totalCompletionTokens: number;
|
|
34
34
|
totalTokens: number;
|
|
35
|
+
/** Sum of cache-read tokens across responses (0 when no provider reported any). */
|
|
36
|
+
totalCacheReadTokens: number;
|
|
37
|
+
/** Sum of cache-creation tokens across responses. */
|
|
38
|
+
totalCacheCreationTokens: number;
|
|
35
39
|
requestCount: number;
|
|
36
40
|
}
|
|
@@ -139,6 +139,23 @@ export function sanitizeMessages(messages) {
|
|
|
139
139
|
// These walkers redact secrets *in place on a deep clone* so the caller
|
|
140
140
|
// can still log "useful shape, scrubbed values".
|
|
141
141
|
const SECRET_KEY_RE = /(^|[._-])(api[_-]?key|authorization|x[_-]api[_-]key|bearer[_-]?token|access[_-]?token|refresh[_-]?token|session[_-]?token|token|secret|password|client[_-]?secret|cookie)($|[._-])/i;
|
|
142
|
+
// Header containers whose VALUES are all header values — any of them can carry
|
|
143
|
+
// a bespoke auth token under a non-secret-looking key name (e.g.
|
|
144
|
+
// `x-custom-auth`), which SECRET_KEY_RE alone would miss. Treat the whole
|
|
145
|
+
// container as sensitive: every non-empty value inside is redacted.
|
|
146
|
+
const HEADERS_CONTAINER_RE = /^(httpHeaders|headers|defaultHeaders|envHeaders)$/;
|
|
147
|
+
/** Redact all non-empty string/scalar values of a headers container object. */
|
|
148
|
+
function redactHeaderValues(headers) {
|
|
149
|
+
const out = {};
|
|
150
|
+
for (const [hName, hVal] of Object.entries(headers)) {
|
|
151
|
+
// Preserve present-vs-absent for empty/null, redact everything else.
|
|
152
|
+
if (hVal === null || hVal === undefined || hVal === "")
|
|
153
|
+
out[hName] = hVal;
|
|
154
|
+
else
|
|
155
|
+
out[hName] = REDACTED;
|
|
156
|
+
}
|
|
157
|
+
return out;
|
|
158
|
+
}
|
|
142
159
|
const BEARER_RE = /\bBearer\s+[A-Za-z0-9._\-+/=~]{8,}/g;
|
|
143
160
|
// URL query parameters that look like credentials. Conservative — only
|
|
144
161
|
// trigger on a small named set so a normal `?id=…&q=…` URL passes through.
|
|
@@ -211,6 +228,11 @@ export function redactSecrets(value, depth = 0) {
|
|
|
211
228
|
else
|
|
212
229
|
out[k] = REDACTED;
|
|
213
230
|
}
|
|
231
|
+
else if (HEADERS_CONTAINER_RE.test(k) && v && typeof v === "object" && !Array.isArray(v)) {
|
|
232
|
+
// Blanket-redact header values regardless of the inner header name, so a
|
|
233
|
+
// custom `x-custom-auth` doesn't leak in cleartext.
|
|
234
|
+
out[k] = redactHeaderValues(v);
|
|
235
|
+
}
|
|
214
236
|
else {
|
|
215
237
|
out[k] = redactSecrets(v, depth + 1);
|
|
216
238
|
}
|
package/dist/lsp/manager.d.ts
CHANGED
|
@@ -33,8 +33,8 @@ export declare class LSPServerManager {
|
|
|
33
33
|
* Shutdown all servers.
|
|
34
34
|
*/
|
|
35
35
|
shutdownAll(): Promise<void>;
|
|
36
|
-
private isCommandAvailable;
|
|
37
36
|
}
|
|
37
|
+
export declare function isCommandAvailable(command: string, env?: NodeJS.ProcessEnv): boolean;
|
|
38
38
|
export declare function initializeLSPManager(cwd: string): LSPServerManager;
|
|
39
39
|
export declare function getLSPManager(): LSPServerManager | undefined;
|
|
40
40
|
export {};
|