@nodetool-ai/runtime 0.7.0-rc.22 → 0.8.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 +29 -0
- package/dist/.nodetool-build-stamp +1 -0
- package/dist/agent-memory.d.ts +118 -0
- package/dist/agent-memory.d.ts.map +1 -0
- package/dist/agent-memory.js +186 -0
- package/dist/agent-memory.js.map +1 -0
- package/dist/async-local-storage.d.ts +20 -0
- package/dist/async-local-storage.d.ts.map +1 -0
- package/dist/async-local-storage.js +28 -0
- package/dist/async-local-storage.js.map +1 -0
- package/dist/blender-executor.d.ts +93 -0
- package/dist/blender-executor.d.ts.map +1 -0
- package/dist/blender-executor.js +294 -0
- package/dist/blender-executor.js.map +1 -0
- package/dist/comfy-executor.d.ts +23 -1
- package/dist/comfy-executor.d.ts.map +1 -1
- package/dist/comfy-executor.js +234 -55
- package/dist/comfy-executor.js.map +1 -1
- package/dist/context-packer.d.ts +1 -1
- package/dist/context-packer.d.ts.map +1 -1
- package/dist/context-packer.js +23 -25
- package/dist/context-packer.js.map +1 -1
- package/dist/context.d.ts +954 -127
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +2424 -513
- package/dist/context.js.map +1 -1
- package/dist/cost-reconciler.d.ts +32 -0
- package/dist/cost-reconciler.d.ts.map +1 -0
- package/dist/cost-reconciler.js +21 -0
- package/dist/cost-reconciler.js.map +1 -0
- package/dist/entities.d.ts +33 -0
- package/dist/entities.d.ts.map +1 -0
- package/dist/entities.js +43 -0
- package/dist/entities.js.map +1 -0
- package/dist/external-media-fetch.d.ts +29 -0
- package/dist/external-media-fetch.d.ts.map +1 -0
- package/dist/external-media-fetch.js +38 -0
- package/dist/external-media-fetch.js.map +1 -0
- package/dist/generation-receipt.d.ts +86 -0
- package/dist/generation-receipt.d.ts.map +1 -0
- package/dist/generation-receipt.js +137 -0
- package/dist/generation-receipt.js.map +1 -0
- package/dist/generation-registry.d.ts +67 -0
- package/dist/generation-registry.d.ts.map +1 -0
- package/dist/generation-registry.js +126 -0
- package/dist/generation-registry.js.map +1 -0
- package/dist/google/client.d.ts +196 -0
- package/dist/google/client.d.ts.map +1 -0
- package/dist/google/client.js +396 -0
- package/dist/google/client.js.map +1 -0
- package/dist/google/index.d.ts +3 -0
- package/dist/google/index.d.ts.map +1 -0
- package/dist/google/index.js +3 -0
- package/dist/google/index.js.map +1 -0
- package/dist/google/token.d.ts +15 -0
- package/dist/google/token.d.ts.map +1 -0
- package/dist/google/token.js +24 -0
- package/dist/google/token.js.map +1 -0
- package/dist/host-binaries.d.ts +106 -0
- package/dist/host-binaries.d.ts.map +1 -0
- package/dist/host-binaries.js +344 -0
- package/dist/host-binaries.js.map +1 -0
- package/dist/image-codec.d.ts +74 -0
- package/dist/image-codec.d.ts.map +1 -0
- package/dist/image-codec.js +273 -0
- package/dist/image-codec.js.map +1 -0
- package/dist/index.d.ts +56 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +43 -4
- package/dist/index.js.map +1 -1
- package/dist/invocation-account.d.ts +43 -0
- package/dist/invocation-account.d.ts.map +1 -0
- package/dist/invocation-account.js +60 -0
- package/dist/invocation-account.js.map +1 -0
- package/dist/media-ref-bytes.d.ts +19 -0
- package/dist/media-ref-bytes.d.ts.map +1 -0
- package/dist/media-ref-bytes.js +181 -0
- package/dist/media-ref-bytes.js.map +1 -0
- package/dist/node-executor.d.ts +113 -2
- package/dist/node-executor.d.ts.map +1 -1
- package/dist/permission-gate.d.ts +152 -0
- package/dist/permission-gate.d.ts.map +1 -0
- package/dist/permission-gate.js +87 -0
- package/dist/permission-gate.js.map +1 -0
- package/dist/prefixed-storage-adapter.d.ts +33 -0
- package/dist/prefixed-storage-adapter.d.ts.map +1 -0
- package/dist/prefixed-storage-adapter.js +70 -0
- package/dist/prefixed-storage-adapter.js.map +1 -0
- package/dist/prompt-asset-refs.d.ts +222 -0
- package/dist/prompt-asset-refs.d.ts.map +1 -0
- package/dist/prompt-asset-refs.js +751 -0
- package/dist/prompt-asset-refs.js.map +1 -0
- package/dist/provider-cache.d.ts +3 -0
- package/dist/provider-cache.d.ts.map +1 -0
- package/dist/provider-cache.js +8 -0
- package/dist/provider-cache.js.map +1 -0
- package/dist/providers/aki-provider.d.ts +33 -10
- package/dist/providers/aki-provider.d.ts.map +1 -1
- package/dist/providers/aki-provider.js +84 -77
- package/dist/providers/aki-provider.js.map +1 -1
- package/dist/providers/alibaba-provider.d.ts +39 -0
- package/dist/providers/alibaba-provider.d.ts.map +1 -0
- package/dist/providers/alibaba-provider.js +86 -0
- package/dist/providers/alibaba-provider.js.map +1 -0
- package/dist/providers/anthropic-provider.d.ts +52 -3
- package/dist/providers/anthropic-provider.d.ts.map +1 -1
- package/dist/providers/anthropic-provider.js +1020 -338
- package/dist/providers/anthropic-provider.js.map +1 -1
- package/dist/providers/atlascloud-provider.d.ts +101 -0
- package/dist/providers/atlascloud-provider.d.ts.map +1 -0
- package/dist/providers/atlascloud-provider.js +1009 -0
- package/dist/providers/atlascloud-provider.js.map +1 -0
- package/dist/providers/atlascloud-transport.d.ts +100 -0
- package/dist/providers/atlascloud-transport.d.ts.map +1 -0
- package/dist/providers/atlascloud-transport.js +364 -0
- package/dist/providers/atlascloud-transport.js.map +1 -0
- package/dist/providers/atlascloud-webhook-registry.d.ts +35 -0
- package/dist/providers/atlascloud-webhook-registry.d.ts.map +1 -0
- package/dist/providers/atlascloud-webhook-registry.js +104 -0
- package/dist/providers/atlascloud-webhook-registry.js.map +1 -0
- package/dist/providers/audio-mime.d.ts +11 -0
- package/dist/providers/audio-mime.d.ts.map +1 -0
- package/dist/providers/audio-mime.js +46 -0
- package/dist/providers/audio-mime.js.map +1 -0
- package/dist/providers/base-provider.d.ts +481 -24
- package/dist/providers/base-provider.d.ts.map +1 -1
- package/dist/providers/base-provider.js +1259 -42
- package/dist/providers/base-provider.js.map +1 -1
- package/dist/providers/cassette-provider.d.ts +217 -0
- package/dist/providers/cassette-provider.d.ts.map +1 -0
- package/dist/providers/cassette-provider.js +502 -0
- package/dist/providers/cassette-provider.js.map +1 -0
- package/dist/providers/cerebras-provider.d.ts +6 -12
- package/dist/providers/cerebras-provider.d.ts.map +1 -1
- package/dist/providers/cerebras-provider.js +8 -33
- package/dist/providers/cerebras-provider.js.map +1 -1
- package/dist/providers/claude-agent-provider.d.ts +131 -68
- package/dist/providers/claude-agent-provider.d.ts.map +1 -1
- package/dist/providers/claude-agent-provider.js +1095 -515
- package/dist/providers/claude-agent-provider.js.map +1 -1
- package/dist/providers/codex-provider.d.ts +82 -0
- package/dist/providers/codex-provider.d.ts.map +1 -0
- package/dist/providers/codex-provider.js +492 -0
- package/dist/providers/codex-provider.js.map +1 -0
- package/dist/providers/cohere-provider.d.ts +5 -2
- package/dist/providers/cohere-provider.d.ts.map +1 -1
- package/dist/providers/cohere-provider.js +9 -2
- package/dist/providers/cohere-provider.js.map +1 -1
- package/dist/providers/comfy-cloud-provider.d.ts +30 -0
- package/dist/providers/comfy-cloud-provider.d.ts.map +1 -0
- package/dist/providers/comfy-cloud-provider.js +42 -0
- package/dist/providers/comfy-cloud-provider.js.map +1 -0
- package/dist/providers/content-filter.d.ts +46 -0
- package/dist/providers/content-filter.d.ts.map +1 -0
- package/dist/providers/content-filter.js +92 -0
- package/dist/providers/content-filter.js.map +1 -0
- package/dist/providers/context-exceeded.d.ts +37 -0
- package/dist/providers/context-exceeded.d.ts.map +1 -0
- package/dist/providers/context-exceeded.js +114 -0
- package/dist/providers/context-exceeded.js.map +1 -0
- package/dist/providers/context-window.d.ts +44 -0
- package/dist/providers/context-window.d.ts.map +1 -0
- package/dist/providers/context-window.js +145 -0
- package/dist/providers/context-window.js.map +1 -0
- package/dist/providers/contract/index.d.ts +4 -0
- package/dist/providers/contract/index.d.ts.map +1 -0
- package/dist/providers/contract/index.js +4 -0
- package/dist/providers/contract/index.js.map +1 -0
- package/dist/providers/contract/probe-manifest.d.ts +75 -0
- package/dist/providers/contract/probe-manifest.d.ts.map +1 -0
- package/dist/providers/contract/probe-manifest.js +379 -0
- package/dist/providers/contract/probe-manifest.js.map +1 -0
- package/dist/providers/contract/probe-runner.d.ts +57 -0
- package/dist/providers/contract/probe-runner.d.ts.map +1 -0
- package/dist/providers/contract/probe-runner.js +180 -0
- package/dist/providers/contract/probe-runner.js.map +1 -0
- package/dist/providers/contract/redact.d.ts +29 -0
- package/dist/providers/contract/redact.d.ts.map +1 -0
- package/dist/providers/contract/redact.js +113 -0
- package/dist/providers/contract/redact.js.map +1 -0
- package/dist/providers/core-tools.d.ts +74 -0
- package/dist/providers/core-tools.d.ts.map +1 -0
- package/dist/providers/core-tools.js +127 -0
- package/dist/providers/core-tools.js.map +1 -0
- package/dist/providers/cost-calculator.d.ts +69 -50
- package/dist/providers/cost-calculator.d.ts.map +1 -1
- package/dist/providers/cost-calculator.js +207 -397
- package/dist/providers/cost-calculator.js.map +1 -1
- package/dist/providers/credential-check.d.ts +45 -0
- package/dist/providers/credential-check.d.ts.map +1 -0
- package/dist/providers/credential-check.js +159 -0
- package/dist/providers/credential-check.js.map +1 -0
- package/dist/providers/custom-model-kinds.d.ts +33 -0
- package/dist/providers/custom-model-kinds.d.ts.map +1 -0
- package/dist/providers/custom-model-kinds.js +96 -0
- package/dist/providers/custom-model-kinds.js.map +1 -0
- package/dist/providers/custom-openai-provider.d.ts +81 -0
- package/dist/providers/custom-openai-provider.d.ts.map +1 -0
- package/dist/providers/custom-openai-provider.js +182 -0
- package/dist/providers/custom-openai-provider.js.map +1 -0
- package/dist/providers/custom-provider-registry.d.ts +19 -0
- package/dist/providers/custom-provider-registry.d.ts.map +1 -0
- package/dist/providers/custom-provider-registry.js +52 -0
- package/dist/providers/custom-provider-registry.js.map +1 -0
- package/dist/providers/deepseek-provider.d.ts +8 -14
- package/dist/providers/deepseek-provider.d.ts.map +1 -1
- package/dist/providers/deepseek-provider.js +6 -35
- package/dist/providers/deepseek-provider.js.map +1 -1
- package/dist/providers/defaults.d.ts +12 -0
- package/dist/providers/defaults.d.ts.map +1 -0
- package/dist/providers/defaults.js +12 -0
- package/dist/providers/defaults.js.map +1 -0
- package/dist/providers/elevenlabs-provider.d.ts +49 -0
- package/dist/providers/elevenlabs-provider.d.ts.map +1 -0
- package/dist/providers/elevenlabs-provider.js +142 -0
- package/dist/providers/elevenlabs-provider.js.map +1 -0
- package/dist/providers/entity-references.d.ts +31 -0
- package/dist/providers/entity-references.d.ts.map +1 -0
- package/dist/providers/entity-references.js +113 -0
- package/dist/providers/entity-references.js.map +1 -0
- package/dist/providers/evolink-provider.d.ts +38 -0
- package/dist/providers/evolink-provider.d.ts.map +1 -0
- package/dist/providers/evolink-provider.js +304 -0
- package/dist/providers/evolink-provider.js.map +1 -0
- package/dist/providers/fake-provider.d.ts +72 -1
- package/dist/providers/fake-provider.d.ts.map +1 -1
- package/dist/providers/fake-provider.js +203 -1
- package/dist/providers/fake-provider.js.map +1 -1
- package/dist/providers/fal-generations.d.ts +106 -0
- package/dist/providers/fal-generations.d.ts.map +1 -0
- package/dist/providers/fal-generations.js +286 -0
- package/dist/providers/fal-generations.js.map +1 -0
- package/dist/providers/fal-provider.d.ts +171 -7
- package/dist/providers/fal-provider.d.ts.map +1 -1
- package/dist/providers/fal-provider.js +1369 -64
- package/dist/providers/fal-provider.js.map +1 -1
- package/dist/providers/fal-queue.d.ts +47 -0
- package/dist/providers/fal-queue.d.ts.map +1 -0
- package/dist/providers/fal-queue.js +231 -0
- package/dist/providers/fal-queue.js.map +1 -0
- package/dist/providers/gemini-provider.d.ts +148 -8
- package/dist/providers/gemini-provider.d.ts.map +1 -1
- package/dist/providers/gemini-provider.js +1500 -316
- package/dist/providers/gemini-provider.js.map +1 -1
- package/dist/providers/gmi-provider.d.ts +21 -0
- package/dist/providers/gmi-provider.d.ts.map +1 -0
- package/dist/providers/gmi-provider.js +34 -0
- package/dist/providers/gmi-provider.js.map +1 -0
- package/dist/providers/gpt-image-size.d.ts +3 -0
- package/dist/providers/gpt-image-size.d.ts.map +1 -0
- package/dist/providers/gpt-image-size.js +37 -0
- package/dist/providers/gpt-image-size.js.map +1 -0
- package/dist/providers/groq-provider.d.ts +9 -12
- package/dist/providers/groq-provider.d.ts.map +1 -1
- package/dist/providers/groq-provider.js +19 -33
- package/dist/providers/groq-provider.js.map +1 -1
- package/dist/providers/groq-request.d.ts +38 -0
- package/dist/providers/groq-request.d.ts.map +1 -0
- package/dist/providers/groq-request.js +138 -0
- package/dist/providers/groq-request.js.map +1 -0
- package/dist/providers/higgsfield-provider.d.ts +30 -0
- package/dist/providers/higgsfield-provider.d.ts.map +1 -0
- package/dist/providers/higgsfield-provider.js +131 -0
- package/dist/providers/higgsfield-provider.js.map +1 -0
- package/dist/providers/higgsfield-transport.d.ts +106 -0
- package/dist/providers/higgsfield-transport.d.ts.map +1 -0
- package/dist/providers/higgsfield-transport.js +182 -0
- package/dist/providers/higgsfield-transport.js.map +1 -0
- package/dist/providers/http-transport.d.ts +74 -0
- package/dist/providers/http-transport.d.ts.map +1 -0
- package/dist/providers/http-transport.js +152 -0
- package/dist/providers/http-transport.js.map +1 -0
- package/dist/providers/huggingface-provider.d.ts +71 -3
- package/dist/providers/huggingface-provider.d.ts.map +1 -1
- package/dist/providers/huggingface-provider.js +230 -34
- package/dist/providers/huggingface-provider.js.map +1 -1
- package/dist/providers/image-mime.d.ts +39 -0
- package/dist/providers/image-mime.d.ts.map +1 -0
- package/dist/providers/image-mime.js +111 -0
- package/dist/providers/image-mime.js.map +1 -0
- package/dist/providers/image-ref.d.ts +21 -0
- package/dist/providers/image-ref.d.ts.map +1 -0
- package/dist/providers/image-ref.js +44 -0
- package/dist/providers/image-ref.js.map +1 -0
- package/dist/providers/index.d.ts +84 -8
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +239 -56
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/jina-provider.d.ts +10 -2
- package/dist/providers/jina-provider.d.ts.map +1 -1
- package/dist/providers/jina-provider.js +16 -7
- package/dist/providers/jina-provider.js.map +1 -1
- package/dist/providers/kie-provider.d.ts +170 -9
- package/dist/providers/kie-provider.d.ts.map +1 -1
- package/dist/providers/kie-provider.js +1176 -228
- package/dist/providers/kie-provider.js.map +1 -1
- package/dist/providers/kie-webhook-registry.d.ts +28 -0
- package/dist/providers/kie-webhook-registry.d.ts.map +1 -0
- package/dist/providers/kie-webhook-registry.js +91 -0
- package/dist/providers/kie-webhook-registry.js.map +1 -0
- package/dist/providers/llama-provider.d.ts +21 -37
- package/dist/providers/llama-provider.d.ts.map +1 -1
- package/dist/providers/llama-provider.js +43 -354
- package/dist/providers/llama-provider.js.map +1 -1
- package/dist/providers/lmstudio-provider.d.ts +4 -7
- package/dist/providers/lmstudio-provider.d.ts.map +1 -1
- package/dist/providers/lmstudio-provider.js +24 -15
- package/dist/providers/lmstudio-provider.js.map +1 -1
- package/dist/providers/manifest-models.d.ts +341 -1
- package/dist/providers/manifest-models.d.ts.map +1 -1
- package/dist/providers/manifest-models.js +1106 -22
- package/dist/providers/manifest-models.js.map +1 -1
- package/dist/providers/media-mime.d.ts +14 -0
- package/dist/providers/media-mime.d.ts.map +1 -0
- package/dist/providers/media-mime.js +35 -0
- package/dist/providers/media-mime.js.map +1 -0
- package/dist/providers/meshy-provider.d.ts +14 -1
- package/dist/providers/meshy-provider.d.ts.map +1 -1
- package/dist/providers/meshy-provider.js +76 -46
- package/dist/providers/meshy-provider.js.map +1 -1
- package/dist/providers/meta-provider.d.ts +22 -0
- package/dist/providers/meta-provider.d.ts.map +1 -0
- package/dist/providers/meta-provider.js +54 -0
- package/dist/providers/meta-provider.js.map +1 -0
- package/dist/providers/minimax-provider.d.ts +25 -18
- package/dist/providers/minimax-provider.d.ts.map +1 -1
- package/dist/providers/minimax-provider.js +211 -72
- package/dist/providers/minimax-provider.js.map +1 -1
- package/dist/providers/mistral-provider.d.ts +6 -12
- package/dist/providers/mistral-provider.d.ts.map +1 -1
- package/dist/providers/mistral-provider.js +10 -34
- package/dist/providers/mistral-provider.js.map +1 -1
- package/dist/providers/moonshot-provider.d.ts +11 -15
- package/dist/providers/moonshot-provider.d.ts.map +1 -1
- package/dist/providers/moonshot-provider.js +33 -23
- package/dist/providers/moonshot-provider.js.map +1 -1
- package/dist/providers/node-llama-cpp-provider.d.ts +98 -0
- package/dist/providers/node-llama-cpp-provider.d.ts.map +1 -0
- package/dist/providers/node-llama-cpp-provider.js +479 -0
- package/dist/providers/node-llama-cpp-provider.js.map +1 -0
- package/dist/providers/nodetool-provider.d.ts +49 -0
- package/dist/providers/nodetool-provider.d.ts.map +1 -0
- package/dist/providers/nodetool-provider.js +224 -0
- package/dist/providers/nodetool-provider.js.map +1 -0
- package/dist/providers/oauth/browser-launcher.d.ts +23 -0
- package/dist/providers/oauth/browser-launcher.d.ts.map +1 -0
- package/dist/providers/oauth/browser-launcher.js +45 -0
- package/dist/providers/oauth/browser-launcher.js.map +1 -0
- package/dist/providers/oauth/claude-code-credentials.d.ts +74 -0
- package/dist/providers/oauth/claude-code-credentials.d.ts.map +1 -0
- package/dist/providers/oauth/claude-code-credentials.js +133 -0
- package/dist/providers/oauth/claude-code-credentials.js.map +1 -0
- package/dist/providers/oauth/claude-code-login.d.ts +124 -0
- package/dist/providers/oauth/claude-code-login.d.ts.map +1 -0
- package/dist/providers/oauth/claude-code-login.js +230 -0
- package/dist/providers/oauth/claude-code-login.js.map +1 -0
- package/dist/providers/oauth/claude-code-oauth-client.d.ts +111 -0
- package/dist/providers/oauth/claude-code-oauth-client.d.ts.map +1 -0
- package/dist/providers/oauth/claude-code-oauth-client.js +207 -0
- package/dist/providers/oauth/claude-code-oauth-client.js.map +1 -0
- package/dist/providers/oauth/errors.d.ts +54 -0
- package/dist/providers/oauth/errors.d.ts.map +1 -0
- package/dist/providers/oauth/errors.js +79 -0
- package/dist/providers/oauth/errors.js.map +1 -0
- package/dist/providers/oauth/index.d.ts +105 -0
- package/dist/providers/oauth/index.d.ts.map +1 -0
- package/dist/providers/oauth/index.js +167 -0
- package/dist/providers/oauth/index.js.map +1 -0
- package/dist/providers/oauth/jwt-claims.d.ts +23 -0
- package/dist/providers/oauth/jwt-claims.d.ts.map +1 -0
- package/dist/providers/oauth/jwt-claims.js +67 -0
- package/dist/providers/oauth/jwt-claims.js.map +1 -0
- package/dist/providers/oauth/local-callback-server.d.ts +60 -0
- package/dist/providers/oauth/local-callback-server.d.ts.map +1 -0
- package/dist/providers/oauth/local-callback-server.js +148 -0
- package/dist/providers/oauth/local-callback-server.js.map +1 -0
- package/dist/providers/oauth/oauth-client.d.ts +55 -0
- package/dist/providers/oauth/oauth-client.d.ts.map +1 -0
- package/dist/providers/oauth/oauth-client.js +161 -0
- package/dist/providers/oauth/oauth-client.js.map +1 -0
- package/dist/providers/oauth/openai-oauth-provider.d.ts +129 -0
- package/dist/providers/oauth/openai-oauth-provider.d.ts.map +1 -0
- package/dist/providers/oauth/openai-oauth-provider.js +265 -0
- package/dist/providers/oauth/openai-oauth-provider.js.map +1 -0
- package/dist/providers/oauth/pkce-helper.d.ts +43 -0
- package/dist/providers/oauth/pkce-helper.d.ts.map +1 -0
- package/dist/providers/oauth/pkce-helper.js +57 -0
- package/dist/providers/oauth/pkce-helper.js.map +1 -0
- package/dist/providers/oauth/redaction.d.ts +22 -0
- package/dist/providers/oauth/redaction.d.ts.map +1 -0
- package/dist/providers/oauth/redaction.js +59 -0
- package/dist/providers/oauth/redaction.js.map +1 -0
- package/dist/providers/oauth/secure-credential-store.d.ts +67 -0
- package/dist/providers/oauth/secure-credential-store.d.ts.map +1 -0
- package/dist/providers/oauth/secure-credential-store.js +73 -0
- package/dist/providers/oauth/secure-credential-store.js.map +1 -0
- package/dist/providers/oauth/token-store.d.ts +53 -0
- package/dist/providers/oauth/token-store.d.ts.map +1 -0
- package/dist/providers/oauth/token-store.js +78 -0
- package/dist/providers/oauth/token-store.js.map +1 -0
- package/dist/providers/oauth/types.d.ts +64 -0
- package/dist/providers/oauth/types.d.ts.map +1 -0
- package/dist/providers/oauth/types.js +12 -0
- package/dist/providers/oauth/types.js.map +1 -0
- package/dist/providers/object-tracking.d.ts +47 -0
- package/dist/providers/object-tracking.d.ts.map +1 -0
- package/dist/providers/object-tracking.js +46 -0
- package/dist/providers/object-tracking.js.map +1 -0
- package/dist/providers/offline-model-index.d.ts +20 -0
- package/dist/providers/offline-model-index.d.ts.map +1 -0
- package/dist/providers/offline-model-index.js +112 -0
- package/dist/providers/offline-model-index.js.map +1 -0
- package/dist/providers/ollama-provider.d.ts +28 -1
- package/dist/providers/ollama-provider.d.ts.map +1 -1
- package/dist/providers/ollama-provider.js +193 -43
- package/dist/providers/ollama-provider.js.map +1 -1
- package/dist/providers/openai-compat/client.d.ts +57 -0
- package/dist/providers/openai-compat/client.d.ts.map +1 -0
- package/dist/providers/openai-compat/client.js +157 -0
- package/dist/providers/openai-compat/client.js.map +1 -0
- package/dist/providers/openai-compat/decode.d.ts +36 -0
- package/dist/providers/openai-compat/decode.d.ts.map +1 -0
- package/dist/providers/openai-compat/decode.js +55 -0
- package/dist/providers/openai-compat/decode.js.map +1 -0
- package/dist/providers/openai-compat/errors.d.ts +54 -0
- package/dist/providers/openai-compat/errors.d.ts.map +1 -0
- package/dist/providers/openai-compat/errors.js +144 -0
- package/dist/providers/openai-compat/errors.js.map +1 -0
- package/dist/providers/openai-compat/index.d.ts +8 -0
- package/dist/providers/openai-compat/index.d.ts.map +1 -0
- package/dist/providers/openai-compat/index.js +5 -0
- package/dist/providers/openai-compat/index.js.map +1 -0
- package/dist/providers/openai-compat/sse.d.ts +14 -0
- package/dist/providers/openai-compat/sse.d.ts.map +1 -0
- package/dist/providers/openai-compat/sse.js +69 -0
- package/dist/providers/openai-compat/sse.js.map +1 -0
- package/dist/providers/openai-compat/types.d.ts +75 -0
- package/dist/providers/openai-compat/types.d.ts.map +1 -0
- package/dist/providers/openai-compat/types.js +9 -0
- package/dist/providers/openai-compat/types.js.map +1 -0
- package/dist/providers/openai-compat-provider.d.ts +90 -0
- package/dist/providers/openai-compat-provider.d.ts.map +1 -0
- package/dist/providers/openai-compat-provider.js +282 -0
- package/dist/providers/openai-compat-provider.js.map +1 -0
- package/dist/providers/openai-provider.d.ts +89 -26
- package/dist/providers/openai-provider.d.ts.map +1 -1
- package/dist/providers/openai-provider.js +990 -105
- package/dist/providers/openai-provider.js.map +1 -1
- package/dist/providers/openrouter-provider.d.ts +6 -12
- package/dist/providers/openrouter-provider.d.ts.map +1 -1
- package/dist/providers/openrouter-provider.js +24 -53
- package/dist/providers/openrouter-provider.js.map +1 -1
- package/dist/providers/provider-call-failure.d.ts +39 -0
- package/dist/providers/provider-call-failure.d.ts.map +1 -0
- package/dist/providers/provider-call-failure.js +132 -0
- package/dist/providers/provider-call-failure.js.map +1 -0
- package/dist/providers/provider-error.d.ts +59 -0
- package/dist/providers/provider-error.d.ts.map +1 -0
- package/dist/providers/provider-error.js +165 -0
- package/dist/providers/provider-error.js.map +1 -0
- package/dist/providers/provider-generations.d.ts +99 -0
- package/dist/providers/provider-generations.d.ts.map +1 -0
- package/dist/providers/provider-generations.js +71 -0
- package/dist/providers/provider-generations.js.map +1 -0
- package/dist/providers/provider-queue.d.ts +62 -0
- package/dist/providers/provider-queue.d.ts.map +1 -0
- package/dist/providers/provider-queue.js +28 -0
- package/dist/providers/provider-queue.js.map +1 -0
- package/dist/providers/provider-registry.d.ts +33 -14
- package/dist/providers/provider-registry.d.ts.map +1 -1
- package/dist/providers/provider-registry.js +85 -68
- package/dist/providers/provider-registry.js.map +1 -1
- package/dist/providers/provider-request-log.d.ts +53 -0
- package/dist/providers/provider-request-log.d.ts.map +1 -0
- package/dist/providers/provider-request-log.js +157 -0
- package/dist/providers/provider-request-log.js.map +1 -0
- package/dist/providers/provider-session.d.ts +3 -0
- package/dist/providers/provider-session.d.ts.map +1 -0
- package/dist/providers/provider-session.js +10 -0
- package/dist/providers/provider-session.js.map +1 -0
- package/dist/providers/provider-transport.d.ts +11 -0
- package/dist/providers/provider-transport.d.ts.map +1 -0
- package/dist/providers/provider-transport.js +11 -0
- package/dist/providers/provider-transport.js.map +1 -0
- package/dist/providers/python-provider.d.ts +20 -12
- package/dist/providers/python-provider.d.ts.map +1 -1
- package/dist/providers/python-provider.js +185 -25
- package/dist/providers/python-provider.js.map +1 -1
- package/dist/providers/replicate-provider.d.ts +89 -7
- package/dist/providers/replicate-provider.d.ts.map +1 -1
- package/dist/providers/replicate-provider.js +801 -65
- package/dist/providers/replicate-provider.js.map +1 -1
- package/dist/providers/replicate-retry.d.ts +30 -0
- package/dist/providers/replicate-retry.d.ts.map +1 -0
- package/dist/providers/replicate-retry.js +97 -0
- package/dist/providers/replicate-retry.js.map +1 -0
- package/dist/providers/responses-api.d.ts +27 -0
- package/dist/providers/responses-api.d.ts.map +1 -0
- package/dist/providers/responses-api.js +297 -0
- package/dist/providers/responses-api.js.map +1 -0
- package/dist/providers/reve-provider.d.ts +45 -0
- package/dist/providers/reve-provider.d.ts.map +1 -0
- package/dist/providers/reve-provider.js +159 -0
- package/dist/providers/reve-provider.js.map +1 -0
- package/dist/providers/rodin-provider.d.ts +23 -1
- package/dist/providers/rodin-provider.d.ts.map +1 -1
- package/dist/providers/rodin-provider.js +89 -51
- package/dist/providers/rodin-provider.js.map +1 -1
- package/dist/providers/safe-url.d.ts +70 -0
- package/dist/providers/safe-url.d.ts.map +1 -0
- package/dist/providers/safe-url.js +277 -0
- package/dist/providers/safe-url.js.map +1 -0
- package/dist/providers/scripted-provider.d.ts +9 -0
- package/dist/providers/scripted-provider.d.ts.map +1 -1
- package/dist/providers/scripted-provider.js +86 -10
- package/dist/providers/scripted-provider.js.map +1 -1
- package/dist/providers/structured-output.d.ts +42 -0
- package/dist/providers/structured-output.d.ts.map +1 -0
- package/dist/providers/structured-output.js +83 -0
- package/dist/providers/structured-output.js.map +1 -0
- package/dist/providers/together-provider.d.ts +19 -16
- package/dist/providers/together-provider.d.ts.map +1 -1
- package/dist/providers/together-provider.js +103 -225
- package/dist/providers/together-provider.js.map +1 -1
- package/dist/providers/topaz-provider.d.ts +44 -0
- package/dist/providers/topaz-provider.d.ts.map +1 -0
- package/dist/providers/topaz-provider.js +239 -0
- package/dist/providers/topaz-provider.js.map +1 -0
- package/dist/providers/types.d.ts +603 -4
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/types.js +81 -1
- package/dist/providers/types.js.map +1 -1
- package/dist/providers/useapi-provider.d.ts +27 -0
- package/dist/providers/useapi-provider.d.ts.map +1 -0
- package/dist/providers/useapi-provider.js +266 -0
- package/dist/providers/useapi-provider.js.map +1 -0
- package/dist/providers/video-duration.d.ts +6 -0
- package/dist/providers/video-duration.d.ts.map +1 -0
- package/dist/providers/video-duration.js +63 -0
- package/dist/providers/video-duration.js.map +1 -0
- package/dist/providers/video-frame-fallback.d.ts +39 -0
- package/dist/providers/video-frame-fallback.d.ts.map +1 -0
- package/dist/providers/video-frame-fallback.js +155 -0
- package/dist/providers/video-frame-fallback.js.map +1 -0
- package/dist/providers/video-frames.d.ts +61 -0
- package/dist/providers/video-frames.d.ts.map +1 -0
- package/dist/providers/video-frames.js +208 -0
- package/dist/providers/video-frames.js.map +1 -0
- package/dist/providers/video-mime.d.ts +14 -0
- package/dist/providers/video-mime.d.ts.map +1 -0
- package/dist/providers/video-mime.js +44 -0
- package/dist/providers/video-mime.js.map +1 -0
- package/dist/providers/video-to-audio.d.ts +34 -0
- package/dist/providers/video-to-audio.d.ts.map +1 -0
- package/dist/providers/video-to-audio.js +62 -0
- package/dist/providers/video-to-audio.js.map +1 -0
- package/dist/providers/vllm-provider.d.ts +22 -8
- package/dist/providers/vllm-provider.d.ts.map +1 -1
- package/dist/providers/vllm-provider.js +48 -23
- package/dist/providers/vllm-provider.js.map +1 -1
- package/dist/providers/voyage-provider.d.ts +10 -2
- package/dist/providers/voyage-provider.d.ts.map +1 -1
- package/dist/providers/voyage-provider.js +16 -7
- package/dist/providers/voyage-provider.js.map +1 -1
- package/dist/providers/xai-provider.d.ts +54 -13
- package/dist/providers/xai-provider.d.ts.map +1 -1
- package/dist/providers/xai-provider.js +299 -22
- package/dist/providers/xai-provider.js.map +1 -1
- package/dist/python-bridge-base.d.ts +358 -0
- package/dist/python-bridge-base.d.ts.map +1 -0
- package/dist/python-bridge-base.js +1557 -0
- package/dist/python-bridge-base.js.map +1 -0
- package/dist/python-bridge-codec.d.ts +28 -0
- package/dist/python-bridge-codec.d.ts.map +1 -0
- package/dist/python-bridge-codec.js +39 -0
- package/dist/python-bridge-codec.js.map +1 -0
- package/dist/python-bridge-factory.d.ts +52 -0
- package/dist/python-bridge-factory.d.ts.map +1 -0
- package/dist/python-bridge-factory.js +69 -0
- package/dist/python-bridge-factory.js.map +1 -0
- package/dist/python-bridge-framing.d.ts +68 -0
- package/dist/python-bridge-framing.d.ts.map +1 -0
- package/dist/python-bridge-framing.js +91 -0
- package/dist/python-bridge-framing.js.map +1 -0
- package/dist/python-bridge-types.d.ts +545 -2
- package/dist/python-bridge-types.d.ts.map +1 -1
- package/dist/python-bridge-types.js +49 -1
- package/dist/python-bridge-types.js.map +1 -1
- package/dist/python-graph-resolver.d.ts +57 -0
- package/dist/python-graph-resolver.d.ts.map +1 -0
- package/dist/python-graph-resolver.js +79 -0
- package/dist/python-graph-resolver.js.map +1 -0
- package/dist/python-node-executor.d.ts +36 -4
- package/dist/python-node-executor.d.ts.map +1 -1
- package/dist/python-node-executor.js +177 -86
- package/dist/python-node-executor.js.map +1 -1
- package/dist/python-stdio-bridge.d.ts +18 -37
- package/dist/python-stdio-bridge.d.ts.map +1 -1
- package/dist/python-stdio-bridge.js +148 -475
- package/dist/python-stdio-bridge.js.map +1 -1
- package/dist/python-websocket-bridge.d.ts +140 -0
- package/dist/python-websocket-bridge.d.ts.map +1 -0
- package/dist/python-websocket-bridge.js +561 -0
- package/dist/python-websocket-bridge.js.map +1 -0
- package/dist/python-worker-stderr.d.ts +4 -0
- package/dist/python-worker-stderr.d.ts.map +1 -0
- package/dist/python-worker-stderr.js +24 -0
- package/dist/python-worker-stderr.js.map +1 -0
- package/dist/recommended-models.d.ts +2 -2
- package/dist/recommended-models.d.ts.map +1 -1
- package/dist/recommended-models.js +24 -4
- package/dist/recommended-models.js.map +1 -1
- package/dist/recoverable-node-error.d.ts +31 -0
- package/dist/recoverable-node-error.d.ts.map +1 -0
- package/dist/recoverable-node-error.js +37 -0
- package/dist/recoverable-node-error.js.map +1 -0
- package/dist/redact-params.d.ts +13 -0
- package/dist/redact-params.d.ts.map +1 -0
- package/dist/redact-params.js +57 -0
- package/dist/redact-params.js.map +1 -0
- package/dist/sandbox-module-catalog.d.ts +43 -0
- package/dist/sandbox-module-catalog.d.ts.map +1 -0
- package/dist/sandbox-module-catalog.js +25 -0
- package/dist/sandbox-module-catalog.js.map +1 -0
- package/dist/storage-workspace.d.ts +59 -0
- package/dist/storage-workspace.d.ts.map +1 -0
- package/dist/storage-workspace.js +426 -0
- package/dist/storage-workspace.js.map +1 -0
- package/dist/swappable-python-bridge.d.ts +83 -0
- package/dist/swappable-python-bridge.d.ts.map +1 -0
- package/dist/swappable-python-bridge.js +246 -0
- package/dist/swappable-python-bridge.js.map +1 -0
- package/dist/telemetry.d.ts +7 -0
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +23 -0
- package/dist/telemetry.js.map +1 -1
- package/dist/testing.d.ts +81 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +130 -0
- package/dist/testing.js.map +1 -0
- package/dist/token-counter.d.ts +30 -0
- package/dist/token-counter.d.ts.map +1 -0
- package/dist/token-counter.js +156 -0
- package/dist/token-counter.js.map +1 -0
- package/dist/trace-exporters.d.ts +10 -0
- package/dist/trace-exporters.d.ts.map +1 -1
- package/dist/trace-exporters.js +59 -20
- package/dist/trace-exporters.js.map +1 -1
- package/dist/tracing-helpers.d.ts +45 -4
- package/dist/tracing-helpers.d.ts.map +1 -1
- package/dist/tracing-helpers.js +62 -19
- package/dist/tracing-helpers.js.map +1 -1
- package/dist/turn-budget.d.ts +278 -0
- package/dist/turn-budget.d.ts.map +1 -0
- package/dist/turn-budget.js +365 -0
- package/dist/turn-budget.js.map +1 -0
- package/dist/variable-channel.d.ts +48 -0
- package/dist/variable-channel.d.ts.map +1 -0
- package/dist/variable-channel.js +110 -0
- package/dist/variable-channel.js.map +1 -0
- package/dist/workspace.d.ts +119 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +112 -0
- package/dist/workspace.js.map +1 -0
- package/package.json +107 -19
|
@@ -1,601 +1,1181 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Claude
|
|
2
|
+
* ClaudeAgentProvider — Claude reached through the official
|
|
3
|
+
* `@anthropic-ai/claude-agent-sdk` instead of an API key.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
5
|
+
* Unlike {@link AnthropicProvider}, this provider sends no `ANTHROPIC_API_KEY`:
|
|
6
|
+
* the SDK drives the bundled `claude` binary (shipped as an optional platform
|
|
7
|
+
* dependency, so there is nothing to install on PATH) and lets it authenticate
|
|
8
|
+
* with the machine's logged-in Claude subscription (the credentials stored
|
|
9
|
+
* under `~/.claude`). The SDK streams `SDKMessage`s, which we translate into the
|
|
10
|
+
* cross-provider {@link ProviderStreamItem} stream.
|
|
6
11
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
12
|
+
* `@anthropic-ai/claude-agent-sdk` is a *soft* (optional peer) dependency: it is
|
|
13
|
+
* not installed by default and must be added with the package manager before
|
|
14
|
+
* this provider can run. It is loaded lazily via {@link loadSdk}, so a missing
|
|
15
|
+
* package surfaces as a clear install hint only when the provider is actually
|
|
16
|
+
* used — the rest of the runtime, and the browser worker bundle, never need it.
|
|
11
17
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
18
|
+
* It is a *pure LLM* provider: the agentic tool loop is collapsed to a single
|
|
19
|
+
* turn with every built-in tool disabled (`allowedTools: []`), filesystem
|
|
20
|
+
* settings/skills disabled (`settingSources: []`), permission prompts neutered
|
|
21
|
+
* (`permissionMode: "dontAsk"`), and the default agent system prompt fully
|
|
22
|
+
* replaced (`systemPrompt`), so the model behaves like a plain chat completion —
|
|
23
|
+
* text in, text (and thinking) out.
|
|
14
24
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
25
|
+
* Session continuity: when a `threadId` is supplied the provider routes the
|
|
26
|
+
* thread through a single upstream session and, on subsequent turns, resumes it
|
|
27
|
+
* (`options.resume`) and sends only the new user delta. The continuation token
|
|
28
|
+
* ({@link ProviderSession}) is surfaced as a {@link ProviderSessionUpdate} and
|
|
29
|
+
* persisted by the chat layer onto the assistant message; the in-memory map
|
|
30
|
+
* below is only a within-process cache (the DB column is authoritative).
|
|
19
31
|
*/
|
|
20
|
-
import
|
|
21
|
-
import
|
|
32
|
+
import * as fs from "node:fs/promises";
|
|
33
|
+
import * as os from "node:os";
|
|
34
|
+
import * as path from "node:path";
|
|
35
|
+
import { createLogger, importOptionalModule } from "@nodetool-ai/config";
|
|
22
36
|
import { z } from "zod";
|
|
23
|
-
|
|
24
|
-
|
|
37
|
+
import { BaseProvider, budgetStopItem, resolveTurnBudget } from "./base-provider.js";
|
|
38
|
+
import { sdkNativeReplacements } from "./core-tools.js";
|
|
39
|
+
import { isProviderMessageEvent, isProviderSessionUpdate, isProviderStop } from "./types.js";
|
|
40
|
+
import { hashSystemPrompt } from "./provider-session.js";
|
|
41
|
+
import { PROVIDER_IDS, isFiniteNumber, isNonEmptyString, isObjectLike, isString } from "@nodetool-ai/protocol";
|
|
42
|
+
const log = createLogger("nodetool.runtime.providers.claude-agent");
|
|
43
|
+
/** Replacement system prompt used when the caller supplies none. */
|
|
44
|
+
const DEFAULT_SYSTEM_PROMPT = "You are a helpful assistant.";
|
|
25
45
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
46
|
+
* The Claude Agent SDK's built-in tools. Passed as `disallowedTools` on the
|
|
47
|
+
* tool-free path so a "pure LLM" completion cannot execute host tools (Bash,
|
|
48
|
+
* file, web) under bypassPermissions.
|
|
29
49
|
*/
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
50
|
+
const SDK_BUILTIN_TOOLS = [
|
|
51
|
+
"Bash",
|
|
52
|
+
"BashOutput",
|
|
53
|
+
"KillShell",
|
|
54
|
+
"Read",
|
|
55
|
+
"Write",
|
|
56
|
+
"Edit",
|
|
57
|
+
"MultiEdit",
|
|
58
|
+
"NotebookEdit",
|
|
59
|
+
"Glob",
|
|
60
|
+
"Grep",
|
|
61
|
+
"WebFetch",
|
|
62
|
+
"WebSearch",
|
|
63
|
+
"Task",
|
|
64
|
+
"TodoWrite"
|
|
65
|
+
];
|
|
38
66
|
/**
|
|
39
|
-
*
|
|
40
|
-
* a
|
|
67
|
+
* Env vars a nested Claude subscription session leaks into its children. The
|
|
68
|
+
* SDK spawns the bundled CLI as a subprocess; left in place these would make it
|
|
69
|
+
* believe it is itself nested. We strip them via `options.env` rather than any
|
|
70
|
+
* raw `child_process` plumbing.
|
|
41
71
|
*/
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const lower = original.toLowerCase();
|
|
47
|
-
// SDK package itself not installed (optional dependency missing).
|
|
48
|
-
if (lower.includes("cannot find module") &&
|
|
49
|
-
lower.includes("claude-agent-sdk")) {
|
|
50
|
-
return new ClaudeAgentError("sdk_not_installed", "The @anthropic-ai/claude-agent-sdk package is not installed. " +
|
|
51
|
-
"The Claude Agent provider requires this optional dependency — install it with " +
|
|
52
|
-
"`npm install @anthropic-ai/claude-agent-sdk`, then restart NodeTool. " +
|
|
53
|
-
`Original error: ${original}`, error);
|
|
54
|
-
}
|
|
55
|
-
// Claude Code CLI binary not on PATH.
|
|
56
|
-
if (lower.includes("enoent") ||
|
|
57
|
-
lower.includes("claude: not found") ||
|
|
58
|
-
lower.includes("command not found: claude") ||
|
|
59
|
-
(lower.includes("spawn") && lower.includes("claude"))) {
|
|
60
|
-
return new ClaudeAgentError("cli_not_found", "Claude Code CLI not found on PATH. The Claude Agent provider spawns the " +
|
|
61
|
-
"`claude` command — install it from https://docs.anthropic.com/claude-code/install " +
|
|
62
|
-
"(or via `npm install -g @anthropic-ai/claude-code`), then run `claude login` to " +
|
|
63
|
-
`authenticate. Original error: ${original}`, error);
|
|
64
|
-
}
|
|
65
|
-
// Running as root — SDK refuses --dangerously-skip-permissions.
|
|
66
|
-
if (lower.includes("dangerously-skip-permissions") ||
|
|
67
|
-
(lower.includes("root") && lower.includes("refus")) ||
|
|
68
|
-
(lower.includes("uid") && lower.includes("0"))) {
|
|
69
|
-
return new ClaudeAgentError("running_as_root", "The Claude Agent SDK refuses to run as root (uid=0) because it passes " +
|
|
70
|
-
"--dangerously-skip-permissions to the Claude CLI. Run NodeTool as a non-root " +
|
|
71
|
-
"user, or switch to the `anthropic` provider which uses ANTHROPIC_API_KEY " +
|
|
72
|
-
`directly. Original error: ${original}`, error);
|
|
73
|
-
}
|
|
74
|
-
// Abort.
|
|
75
|
-
if (lower.includes("abort") ||
|
|
76
|
-
error instanceof Error && error.name === "AbortError") {
|
|
77
|
-
return new ClaudeAgentError("aborted", "Claude Agent request was aborted.", error);
|
|
78
|
-
}
|
|
79
|
-
// Authentication failure.
|
|
80
|
-
if (lower.includes("unauthorized") ||
|
|
81
|
-
lower.includes("401") ||
|
|
82
|
-
lower.includes("403") ||
|
|
83
|
-
lower.includes("forbidden") ||
|
|
84
|
-
lower.includes("not logged in") ||
|
|
85
|
-
lower.includes("not authenticated") ||
|
|
86
|
-
lower.includes("invalid api key") ||
|
|
87
|
-
lower.includes("authentication") ||
|
|
88
|
-
lower.includes("oauth")) {
|
|
89
|
-
return new ClaudeAgentError("auth", "Claude Agent authentication failed. Run `claude login` in a terminal to " +
|
|
90
|
-
"sign in with your Claude subscription, or set ANTHROPIC_API_KEY. " +
|
|
91
|
-
`Original error: ${original}`, error);
|
|
92
|
-
}
|
|
93
|
-
// Rate limit.
|
|
94
|
-
if (lower.includes("429") ||
|
|
95
|
-
lower.includes("rate limit") ||
|
|
96
|
-
lower.includes("too many requests") ||
|
|
97
|
-
lower.includes("quota")) {
|
|
98
|
-
return new ClaudeAgentError("rate_limit", "Claude Agent rate limit reached. Wait a moment and retry, or check your " +
|
|
99
|
-
`Claude subscription usage limits. Original error: ${original}`, error);
|
|
100
|
-
}
|
|
101
|
-
// Context length.
|
|
102
|
-
if (lower.includes("context length") ||
|
|
103
|
-
lower.includes("context window") ||
|
|
104
|
-
lower.includes("token limit") ||
|
|
105
|
-
lower.includes("maximum context") ||
|
|
106
|
-
lower.includes("too long") ||
|
|
107
|
-
lower.includes("prompt is too long")) {
|
|
108
|
-
return new ClaudeAgentError("context_length", "Claude Agent request exceeded the model's context window. Shorten the " +
|
|
109
|
-
"conversation (clear older messages) or switch to a model with a larger " +
|
|
110
|
-
`context window. Original error: ${original}`, error);
|
|
111
|
-
}
|
|
112
|
-
// Invalid / unknown model.
|
|
113
|
-
if (lower.includes("model") &&
|
|
114
|
-
(lower.includes("invalid") ||
|
|
115
|
-
lower.includes("not found") ||
|
|
116
|
-
lower.includes("unknown") ||
|
|
117
|
-
lower.includes("does not exist"))) {
|
|
118
|
-
return new ClaudeAgentError("invalid_model", "Claude Agent could not use the requested model. Check that the model ID " +
|
|
119
|
-
"is valid and available on your Claude subscription. " +
|
|
120
|
-
`Original error: ${original}`, error);
|
|
121
|
-
}
|
|
122
|
-
return new ClaudeAgentError("unknown", `Claude Agent provider error: ${original}`, error);
|
|
123
|
-
}
|
|
124
|
-
const CLAUDE_AGENT_MODELS = [
|
|
125
|
-
{
|
|
126
|
-
id: "claude-opus-4-7",
|
|
127
|
-
name: "Claude Opus 4.7",
|
|
128
|
-
provider: "claude_agent"
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
id: "claude-sonnet-4-6",
|
|
132
|
-
name: "Claude Sonnet 4.6",
|
|
133
|
-
provider: "claude_agent"
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
id: "claude-haiku-4-5",
|
|
137
|
-
name: "Claude Haiku 4.5",
|
|
138
|
-
provider: "claude_agent"
|
|
139
|
-
}
|
|
140
|
-
];
|
|
141
|
-
const DISALLOWED_TOOLS = [];
|
|
142
|
-
function extractText(content) {
|
|
143
|
-
if (typeof content === "string")
|
|
144
|
-
return content;
|
|
145
|
-
if (Array.isArray(content)) {
|
|
146
|
-
return content
|
|
147
|
-
.filter((c) => c.type === "text")
|
|
148
|
-
.map((c) => c.text)
|
|
149
|
-
.join("\n");
|
|
150
|
-
}
|
|
151
|
-
return String(content ?? "");
|
|
152
|
-
}
|
|
72
|
+
const NESTED_SESSION_ENV = /^(CLAUDECODE|CLAUDE_(CODE|SESSION|ENABLE|AFTER|AUTO)_[A-Za-z0-9_]*)$/;
|
|
73
|
+
/** MCP server name under which NodeTool's tools are exposed to the SDK. */
|
|
74
|
+
const TOOL_SERVER_NAME = "nodetool_tools";
|
|
75
|
+
const TOOL_PREFIX = `mcp__${TOOL_SERVER_NAME}__`;
|
|
153
76
|
/**
|
|
154
|
-
*
|
|
155
|
-
*
|
|
77
|
+
* The member expression the retired guest toolbelt was called through:
|
|
78
|
+
* `await tools.<name>({…})`. Models trained on it still emit it verbatim as a
|
|
79
|
+
* tool name, so it is stripped on the way in. Nothing produces it any more.
|
|
156
80
|
*/
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (enumValues.every((v) => typeof v === "string")) {
|
|
163
|
-
return z.enum(enumValues);
|
|
164
|
-
}
|
|
165
|
-
return z.any();
|
|
166
|
-
}
|
|
167
|
-
switch (type) {
|
|
168
|
-
case "string":
|
|
169
|
-
return z.string();
|
|
170
|
-
case "number":
|
|
171
|
-
case "integer":
|
|
172
|
-
return z.number();
|
|
173
|
-
case "boolean":
|
|
174
|
-
return z.boolean();
|
|
175
|
-
case "array": {
|
|
176
|
-
const items = prop.items;
|
|
177
|
-
if (items) {
|
|
178
|
-
return z.array(jsonSchemaPropertyToZod(items, z));
|
|
179
|
-
}
|
|
180
|
-
return z.array(z.any());
|
|
181
|
-
}
|
|
182
|
-
case "object": {
|
|
183
|
-
const properties = prop.properties;
|
|
184
|
-
if (properties) {
|
|
185
|
-
const shape = {};
|
|
186
|
-
const required = prop.required ?? [];
|
|
187
|
-
for (const [key, val] of Object.entries(properties)) {
|
|
188
|
-
let zodType = jsonSchemaPropertyToZod(val, z);
|
|
189
|
-
if (!required.includes(key)) {
|
|
190
|
-
zodType = zodType.optional();
|
|
191
|
-
}
|
|
192
|
-
shape[key] = zodType;
|
|
193
|
-
}
|
|
194
|
-
return z.object(shape);
|
|
195
|
-
}
|
|
196
|
-
return z.record(z.string(), z.any());
|
|
197
|
-
}
|
|
198
|
-
default:
|
|
199
|
-
return z.any();
|
|
200
|
-
}
|
|
201
|
-
}
|
|
81
|
+
const GUEST_TOOL_PREFIX = "tools.";
|
|
82
|
+
/** Default cap on internal agent turns when tools are in play. */
|
|
83
|
+
const DEFAULT_TOOL_TURNS = 16;
|
|
84
|
+
/** Name of the local plugin the user's DB skills are materialized into. */
|
|
85
|
+
const SKILLS_PLUGIN_NAME = "nodetool-user-skills";
|
|
202
86
|
/**
|
|
203
|
-
*
|
|
204
|
-
*
|
|
87
|
+
* Lazily import the optional `@anthropic-ai/claude-agent-sdk`. The SDK is a soft
|
|
88
|
+
* (peer) dependency — it is not bundled with the app and must be installed at
|
|
89
|
+
* runtime from the Package Manager (Software → Claude Agent SDK), which drops it
|
|
90
|
+
* into the user-managed optional-node `node_modules`. {@link importOptionalModule}
|
|
91
|
+
* hides the specifier from bundlers (so the browser/worker bundle never pulls in
|
|
92
|
+
* the Node-only SDK) and falls back to resolving it from that optional root via
|
|
93
|
+
* `NODETOOL_OPTIONAL_NODE_MODULES`, so a plain ESM `import` (which ignores
|
|
94
|
+
* NODE_PATH) still finds a Package-Manager install. A missing package surfaces
|
|
95
|
+
* as a clear, actionable error at call time.
|
|
205
96
|
*/
|
|
206
|
-
function
|
|
207
|
-
|
|
208
|
-
return
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
for (const [key, prop] of Object.entries(properties)) {
|
|
215
|
-
let zodType = jsonSchemaPropertyToZod(prop, z);
|
|
216
|
-
if (!required.includes(key)) {
|
|
217
|
-
zodType = zodType.optional();
|
|
218
|
-
}
|
|
219
|
-
if (typeof prop.description === "string") {
|
|
220
|
-
zodType = zodType.describe(prop.description);
|
|
221
|
-
}
|
|
222
|
-
shape[key] = zodType;
|
|
97
|
+
async function loadSdk() {
|
|
98
|
+
try {
|
|
99
|
+
return await importOptionalModule("@anthropic-ai/claude-agent-sdk");
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
throw new Error("The Claude Agent provider requires the optional " +
|
|
103
|
+
"'@anthropic-ai/claude-agent-sdk' package, which is not installed. " +
|
|
104
|
+
"Install it from the Package Manager (Software → Claude Agent SDK).", { cause: err });
|
|
223
105
|
}
|
|
224
|
-
return shape;
|
|
225
106
|
}
|
|
226
107
|
export class ClaudeAgentProvider extends BaseProvider {
|
|
108
|
+
/** The SDK runs with bypassPermissions, so its built-in `WebSearch` is live. */
|
|
109
|
+
get supportsNativeWebSearch() {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
227
112
|
static requiredSecrets() {
|
|
113
|
+
// Auth lives in the SDK's own credential store (~/.claude), not in an env
|
|
114
|
+
// secret — there is nothing for the registry to resolve.
|
|
228
115
|
return [];
|
|
229
116
|
}
|
|
117
|
+
injectedQueryFn;
|
|
118
|
+
injectedCreateMcpServerFn;
|
|
230
119
|
/**
|
|
231
|
-
*
|
|
232
|
-
*
|
|
120
|
+
* Within-process cache of the active session per thread. The persisted
|
|
121
|
+
* `provider_session` column is the source of truth; this only spares a DB
|
|
122
|
+
* round-trip for back-to-back turns in the same process.
|
|
233
123
|
*/
|
|
234
|
-
|
|
235
|
-
constructor(_secrets = {}) {
|
|
236
|
-
super(
|
|
124
|
+
sessions = new Map();
|
|
125
|
+
constructor(_secrets = {}, options = {}) {
|
|
126
|
+
super(PROVIDER_IDS.CLAUDE_AGENT_SDK);
|
|
127
|
+
this.injectedQueryFn = options.queryFn ?? null;
|
|
128
|
+
this.injectedCreateMcpServerFn = options.createMcpServerFn ?? null;
|
|
129
|
+
}
|
|
130
|
+
/** The subscription token is the SDK's business — never hand it to a sandbox. */
|
|
131
|
+
getContainerEnv() {
|
|
132
|
+
return {};
|
|
237
133
|
}
|
|
238
|
-
|
|
134
|
+
/**
|
|
135
|
+
* Tools run inside the SDK's own agent loop ({@link generateLoop}): NodeTool's
|
|
136
|
+
* tools are exposed as an in-process MCP server whose handlers call back into
|
|
137
|
+
* the harness's tool executor. The single-turn {@link generateMessages} stays
|
|
138
|
+
* tool-free.
|
|
139
|
+
*/
|
|
140
|
+
async hasToolSupport() {
|
|
239
141
|
return true;
|
|
240
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* Stable model aliases the SDK resolves to the latest dated model. Aliases
|
|
145
|
+
* avoid pinning a version that ages out; the concrete model id the SDK
|
|
146
|
+
* selects is captured from the stream for accurate cost attribution.
|
|
147
|
+
*/
|
|
241
148
|
async getAvailableLanguageModels() {
|
|
242
|
-
|
|
149
|
+
const provider = PROVIDER_IDS.CLAUDE_AGENT_SDK;
|
|
150
|
+
return [
|
|
151
|
+
{ id: "fable", name: "Claude Fable (subscription)", provider },
|
|
152
|
+
{ id: "opus", name: "Claude Opus (subscription)", provider },
|
|
153
|
+
{ id: "sonnet", name: "Claude Sonnet (subscription)", provider },
|
|
154
|
+
{ id: "haiku", name: "Claude Haiku (subscription)", provider }
|
|
155
|
+
];
|
|
156
|
+
}
|
|
157
|
+
/** Resolve the SDK `query`, lazily importing it off the Node-only package. */
|
|
158
|
+
async loadQuery() {
|
|
159
|
+
if (this.injectedQueryFn)
|
|
160
|
+
return this.injectedQueryFn;
|
|
161
|
+
const mod = await loadSdk();
|
|
162
|
+
return mod.query;
|
|
163
|
+
}
|
|
164
|
+
/** Resolve the SDK `createSdkMcpServer`, lazily importing the Node-only pkg. */
|
|
165
|
+
async loadCreateMcpServer() {
|
|
166
|
+
if (this.injectedCreateMcpServerFn)
|
|
167
|
+
return this.injectedCreateMcpServerFn;
|
|
168
|
+
const mod = await loadSdk();
|
|
169
|
+
// SAFETY: the SDK's factory is generic over each tool's Zod shape; this
|
|
170
|
+
// provider always hands it the default `AnyZodRawShape` definitions.
|
|
171
|
+
return mod.createSdkMcpServer;
|
|
243
172
|
}
|
|
244
173
|
/**
|
|
245
|
-
*
|
|
174
|
+
* Run the SDK's own agent loop. Tool-free turns behave exactly like
|
|
175
|
+
* {@link generateMessages}; with `tools` we expose them as an in-process MCP
|
|
176
|
+
* server whose handlers call `executeTool`, let the SDK loop (`maxTurns > 1`),
|
|
177
|
+
* and translate its `tool_use`/`tool_result` stream into ToolCall items and
|
|
178
|
+
* persistable {@link ProviderMessageEvent}s.
|
|
246
179
|
*/
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
180
|
+
async *generateLoop(args) {
|
|
181
|
+
// The SDK owns the loop, so honoring the budget is this override's job:
|
|
182
|
+
// reserve before the first turn, and again after every assistant turn that
|
|
183
|
+
// requested tools — that is the point where the SDK will make another call.
|
|
184
|
+
// A refusal aborts the session instead of letting it spend past the cap.
|
|
185
|
+
const { runBudget, turnBudget } = resolveTurnBudget(args.turnBudget);
|
|
186
|
+
if (runBudget?.deadline.expired()) {
|
|
187
|
+
yield budgetStopItem(runBudget);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
// Every turn this session makes holds its own reservation, and the whole
|
|
191
|
+
// set is settled in the `finally` below — the SDK reports one number for
|
|
192
|
+
// the session, not one per turn.
|
|
193
|
+
const reservations = [];
|
|
194
|
+
if (turnBudget) {
|
|
195
|
+
const first = this._admitTurn(turnBudget, args.model, args.messages);
|
|
196
|
+
if (first === null) {
|
|
197
|
+
yield budgetStopItem(runBudget);
|
|
198
|
+
return;
|
|
253
199
|
}
|
|
200
|
+
reservations.push(first);
|
|
201
|
+
}
|
|
202
|
+
// Drop every NodeTool tool the SDK ships a built-in for. Those built-ins
|
|
203
|
+
// are live under bypassPermissions, so keeping the MCP copy would give one
|
|
204
|
+
// capability two surfaces and make the model pick between them.
|
|
205
|
+
const offered = args.tools ?? [];
|
|
206
|
+
const replaced = sdkNativeReplacements(offered.map((t) => t.name), args.workspaceDir);
|
|
207
|
+
const tools = offered.filter((t) => !replaced.has(t.name));
|
|
208
|
+
if (replaced.size > 0) {
|
|
209
|
+
log.debug("Using SDK built-ins in place of NodeTool tools", {
|
|
210
|
+
replaced: [...replaced]
|
|
211
|
+
});
|
|
254
212
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
213
|
+
const executeTool = args.executeTool;
|
|
214
|
+
// A tool dispatches either through its own `execute` or the harness
|
|
215
|
+
// `executeTool`; build the MCP server when at least one route exists.
|
|
216
|
+
const hasToolExecute = tools.some((t) => t.execute);
|
|
217
|
+
// The SDK loop is stopped by aborting this controller — a terminal tool
|
|
218
|
+
// fires it after running. Bridge the caller's signal into it too, and pass
|
|
219
|
+
// its signal (overriding args.signal) down so runTurn cancels on either.
|
|
220
|
+
const abortController = new AbortController();
|
|
221
|
+
const onExternalAbort = () => abortController.abort();
|
|
222
|
+
if (args.signal) {
|
|
223
|
+
if (args.signal.aborted)
|
|
224
|
+
abortController.abort();
|
|
225
|
+
else
|
|
226
|
+
args.signal.addEventListener("abort", onExternalAbort, { once: true });
|
|
227
|
+
}
|
|
228
|
+
let mcp = null;
|
|
229
|
+
if (tools.length > 0 && (executeTool || hasToolExecute)) {
|
|
230
|
+
const createServer = await this.loadCreateMcpServer();
|
|
231
|
+
// MCP tool results carry typed content blocks, so image-bearing results
|
|
232
|
+
// (view_image) are returned as real image blocks the SDK hands to Claude —
|
|
233
|
+
// no flattening to text on the agent-SDK path.
|
|
234
|
+
const defs = tools.map((t) => toolDefinition(t, async (name, toolArgs) => {
|
|
235
|
+
// The SDK runs parallel calls of one tool in the same millisecond,
|
|
236
|
+
// so a clock alone would hand them one id.
|
|
237
|
+
const toolCallId = `call_${name}_${crypto.randomUUID()}`;
|
|
238
|
+
const result = t.execute
|
|
239
|
+
? await t.execute(toolArgs, toolCallId)
|
|
240
|
+
: executeTool
|
|
241
|
+
? await executeTool({
|
|
242
|
+
id: toolCallId,
|
|
243
|
+
name,
|
|
244
|
+
args: toolArgs
|
|
245
|
+
})
|
|
246
|
+
: `Tool "${name}" is not available`;
|
|
247
|
+
// A terminal tool ends the SDK loop after its result is delivered.
|
|
248
|
+
if (t.terminal)
|
|
249
|
+
abortController.abort();
|
|
250
|
+
return result;
|
|
251
|
+
}, args.resolveMedia));
|
|
252
|
+
const server = createServer({
|
|
253
|
+
name: TOOL_SERVER_NAME,
|
|
254
|
+
version: "1.0.0",
|
|
255
|
+
tools: defs
|
|
256
|
+
});
|
|
257
|
+
mcp = {
|
|
258
|
+
mcpServers: { [TOOL_SERVER_NAME]: server },
|
|
259
|
+
allowedTools: tools.map((t) => `${TOOL_PREFIX}${t.name}`)
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
// Materialize the user's skills into a throwaway local plugin the SDK's
|
|
263
|
+
// native skill loader discovers. Best-effort: a disk failure drops skills
|
|
264
|
+
// for the turn, it does not sink it. Cleaned up in the `finally` below.
|
|
265
|
+
let skillsPlugin = null;
|
|
266
|
+
if ((args.skills?.length ?? 0) > 0) {
|
|
267
|
+
try {
|
|
268
|
+
skillsPlugin = await materializeSkillsPlugin(args.skills ?? []);
|
|
269
|
+
}
|
|
270
|
+
catch (err) {
|
|
271
|
+
log.warn("Failed to materialize user skills for the SDK", {
|
|
272
|
+
error: err instanceof Error ? err.message : String(err)
|
|
273
|
+
});
|
|
260
274
|
}
|
|
261
275
|
}
|
|
262
|
-
|
|
263
|
-
|
|
276
|
+
const costBefore = turnBudget ? this.getTotalCost() : 0;
|
|
277
|
+
// Set when the budget refuses a further turn: the SDK session is aborted
|
|
278
|
+
// and the stop is yielded once the stream has drained, so it stays the
|
|
279
|
+
// loop's final item.
|
|
280
|
+
let budgetStop = null;
|
|
281
|
+
try {
|
|
282
|
+
const stream = this.runWithSession({ ...args, signal: abortController.signal }, {
|
|
283
|
+
emitMessages: true,
|
|
284
|
+
// Turns are for tool rounds, and a replaced tool still costs one —
|
|
285
|
+
// key this on what the caller offered, not on what survived into MCP.
|
|
286
|
+
maxTurns: offered.length > 0 ? (args.maxIterations ?? DEFAULT_TOOL_TURNS) : 1,
|
|
287
|
+
mcp,
|
|
288
|
+
toolsOffered: offered.length > 0,
|
|
289
|
+
cwd: args.workspaceDir,
|
|
290
|
+
skillsPlugin
|
|
291
|
+
});
|
|
292
|
+
// The SDK grows the conversation internally, so reserving against the
|
|
293
|
+
// opening prompt every time would under-count every later turn — badly,
|
|
294
|
+
// on a tool-heavy loop. The messages it emits are the same ones it is
|
|
295
|
+
// accumulating, so the transcript is rebuilt here to reserve against.
|
|
296
|
+
const transcript = [...args.messages];
|
|
297
|
+
for await (const item of stream) {
|
|
298
|
+
yield item;
|
|
299
|
+
if (!turnBudget)
|
|
300
|
+
continue;
|
|
301
|
+
if (!isProviderMessageEvent(item))
|
|
302
|
+
continue;
|
|
303
|
+
transcript.push(item.message);
|
|
304
|
+
const isToolCallingTurn = item.message.role === "assistant" &&
|
|
305
|
+
(item.message.toolCalls?.length ?? 0) > 0;
|
|
306
|
+
if (!isToolCallingTurn)
|
|
307
|
+
continue;
|
|
308
|
+
const next = this._admitTurn(turnBudget, args.model, transcript);
|
|
309
|
+
if (next === null) {
|
|
310
|
+
budgetStop = budgetStopItem(runBudget);
|
|
311
|
+
abortController.abort();
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
reservations.push(next);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
finally {
|
|
318
|
+
// The SDK reports usage once, on the terminal `result` message, so a
|
|
319
|
+
// per-turn actual doesn't exist here. Reservations accumulate untouched
|
|
320
|
+
// for the session's duration — conservative by construction — and the
|
|
321
|
+
// real number lands when the session ends.
|
|
322
|
+
//
|
|
323
|
+
// Except when it doesn't: an aborted session never emits `result`, so
|
|
324
|
+
// nothing is recorded even though its turns ran and were billed. That is
|
|
325
|
+
// the *normal* ending here — a terminal tool (`finish_step`) aborts the
|
|
326
|
+
// query — so booking zero would mean the cap never counted a single
|
|
327
|
+
// successful decision on this provider. A zero delta is therefore read
|
|
328
|
+
// as "unknown", and the reserved worst case is charged instead.
|
|
329
|
+
if (turnBudget) {
|
|
330
|
+
const observed = this.getTotalCost() - costBefore;
|
|
331
|
+
// One number covers the whole session, so it is booked against the
|
|
332
|
+
// first reservation and the rest settle at zero; with no number at
|
|
333
|
+
// all, each reservation is charged its own worst case.
|
|
334
|
+
let remaining = observed > 0 ? observed : null;
|
|
335
|
+
for (const reservation of reservations) {
|
|
336
|
+
turnBudget.commit(reservation, remaining);
|
|
337
|
+
if (remaining !== null)
|
|
338
|
+
remaining = 0;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (args.signal)
|
|
342
|
+
args.signal.removeEventListener("abort", onExternalAbort);
|
|
343
|
+
// Remove the throwaway skills plugin. The SDK's subprocess has finished
|
|
344
|
+
// reading it by now (the stream is drained), so best-effort cleanup is
|
|
345
|
+
// safe; a leftover temp dir is not worth failing the turn over.
|
|
346
|
+
if (skillsPlugin) {
|
|
347
|
+
await fs
|
|
348
|
+
.rm(skillsPlugin.dir, { recursive: true, force: true })
|
|
349
|
+
.catch(() => { });
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (budgetStop) {
|
|
353
|
+
yield budgetStop;
|
|
264
354
|
}
|
|
265
|
-
return { systemPrompt, prompt };
|
|
266
|
-
}
|
|
267
|
-
/** Look up an existing Claude session for the given threadId. */
|
|
268
|
-
getSessionId(threadId) {
|
|
269
|
-
if (!threadId)
|
|
270
|
-
return undefined;
|
|
271
|
-
return this._sessions.get(threadId);
|
|
272
|
-
}
|
|
273
|
-
/** Store the Claude session ID for a threadId. */
|
|
274
|
-
setSessionId(threadId, sessionId) {
|
|
275
|
-
this._sessions.set(threadId, sessionId);
|
|
276
355
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
356
|
+
async *generateMessages(args) {
|
|
357
|
+
// Single-turn, tool-free primitive. The agent loop lives in generateLoop.
|
|
358
|
+
const resultTool = args.tools?.length === 1 &&
|
|
359
|
+
args.toolChoice === args.tools[0].name
|
|
360
|
+
? args.tools[0]
|
|
361
|
+
: undefined;
|
|
362
|
+
const resultSchema = resultTool?.inputSchema;
|
|
363
|
+
const config = {
|
|
364
|
+
emitMessages: false,
|
|
365
|
+
maxTurns: args.maxTurns ?? 1,
|
|
366
|
+
mcp: null,
|
|
367
|
+
toolsOffered: false
|
|
368
|
+
};
|
|
369
|
+
if (resultTool && resultSchema) {
|
|
370
|
+
config.outputFormat = { type: "json_schema", schema: resultSchema };
|
|
371
|
+
config.resultToolName = resultTool.name;
|
|
372
|
+
}
|
|
373
|
+
yield* this.runWithSession(args, config);
|
|
280
374
|
}
|
|
281
375
|
/**
|
|
282
|
-
*
|
|
283
|
-
*
|
|
376
|
+
* Shared session machinery for {@link generateMessages} and
|
|
377
|
+
* {@link generateLoop}: decide resume vs fresh (best-effort, with a
|
|
378
|
+
* full-history priming fallback), then drive one SDK turn via {@link runTurn}.
|
|
379
|
+
* `config` selects single-turn streaming vs the tool/message-emitting loop.
|
|
284
380
|
*/
|
|
285
|
-
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
return sdk.tool(t.name, t.description ?? "", zodShape, async (args) => {
|
|
289
|
-
// Track the tool call for yielding to the caller
|
|
290
|
-
const toolCall = {
|
|
291
|
-
id: `call_${Date.now()}_${toolCallTracker.length}`,
|
|
292
|
-
name: t.name,
|
|
293
|
-
args
|
|
294
|
-
};
|
|
295
|
-
toolCallTracker.push(toolCall);
|
|
296
|
-
log.debug("MCP tool called", { name: t.name, args });
|
|
297
|
-
try {
|
|
298
|
-
const result = await onToolCall(t.name, args);
|
|
299
|
-
return { content: [{ type: "text", text: result }] };
|
|
300
|
-
}
|
|
301
|
-
catch (e) {
|
|
302
|
-
const errorMsg = `Tool execution error: ${e}`;
|
|
303
|
-
log.error("MCP tool error", { name: t.name, error: errorMsg });
|
|
304
|
-
return {
|
|
305
|
-
content: [{ type: "text", text: errorMsg }],
|
|
306
|
-
isError: true
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
});
|
|
310
|
-
});
|
|
311
|
-
return sdk.createSdkMcpServer({
|
|
312
|
-
name: MCP_SERVER_NAME,
|
|
313
|
-
version: "1.0.0",
|
|
314
|
-
tools: mcpTools
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
async *generateMessages(args) {
|
|
318
|
-
const toolCount = args.tools?.length ?? 0;
|
|
319
|
-
const hasOnToolCall = !!args.onToolCall;
|
|
320
|
-
const hasTools = toolCount > 0 && hasOnToolCall;
|
|
321
|
-
const { systemPrompt, prompt } = this.extractPrompt(args.messages);
|
|
381
|
+
async *runWithSession(args, config) {
|
|
382
|
+
const systemPrompt = extractSystemPrompt(args.messages);
|
|
383
|
+
const systemHash = hashSystemPrompt(systemPrompt);
|
|
322
384
|
const threadId = args.threadId ?? null;
|
|
323
|
-
|
|
324
|
-
//
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
//
|
|
339
|
-
//
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
385
|
+
// Source of truth is the token threaded in from the persisted assistant
|
|
386
|
+
// message; the in-memory cache is a fallback for same-process turns.
|
|
387
|
+
const prior = args.providerSession ??
|
|
388
|
+
(threadId ? (this.sessions.get(threadId) ?? null) : null);
|
|
389
|
+
const canResume = prior != null &&
|
|
390
|
+
prior.providerId === this.provider &&
|
|
391
|
+
prior.model === args.model &&
|
|
392
|
+
(prior.systemHash == null || prior.systemHash === systemHash) &&
|
|
393
|
+
args.messages.length > prior.checkpoint;
|
|
394
|
+
// Cache the session in-process only when the caller doesn't thread a token
|
|
395
|
+
// in (the CLI path). When it does (the websocket path), the DB is
|
|
396
|
+
// authoritative and the emitted checkpoint may be relative to a trimmed
|
|
397
|
+
// view, so caching it would corrupt a later cache-based resume.
|
|
398
|
+
const cacheSession = args.providerSession == null;
|
|
399
|
+
// RESUME is best-effort: a session file may have been pruned/expired. If the
|
|
400
|
+
// resume query fails *before* any content reached the consumer, fall back to
|
|
401
|
+
// a fresh session; if it fails mid-stream we surface the error instead.
|
|
402
|
+
const emitted = { content: false };
|
|
403
|
+
if (canResume && prior) {
|
|
404
|
+
const delta = buildResumeDelta(args.messages, prior.checkpoint);
|
|
405
|
+
try {
|
|
406
|
+
yield* this.runTurn(args, {
|
|
407
|
+
prompt: delta,
|
|
408
|
+
resume: prior.token,
|
|
409
|
+
systemPrompt,
|
|
410
|
+
systemHash,
|
|
411
|
+
threadId,
|
|
412
|
+
emitted,
|
|
413
|
+
cacheSession,
|
|
414
|
+
config
|
|
415
|
+
});
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
catch (err) {
|
|
419
|
+
if (emitted.content)
|
|
420
|
+
throw err;
|
|
421
|
+
log.warn("Claude session resume failed; starting fresh", {
|
|
422
|
+
error: err instanceof Error ? err.message : String(err)
|
|
423
|
+
});
|
|
424
|
+
}
|
|
360
425
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
426
|
+
// Fresh / fallback. When the caller handed us only the delta (a trimmed
|
|
427
|
+
// `messages` view) plus a loader, prime from the full conversation so prior
|
|
428
|
+
// context isn't lost — the session may have expired or the system prompt
|
|
429
|
+
// changed. Otherwise `messages` already holds the full history.
|
|
430
|
+
const freshMessages = args.loadFullHistory
|
|
431
|
+
? await args.loadFullHistory()
|
|
432
|
+
: args.messages;
|
|
433
|
+
yield* this.runTurn(args, {
|
|
434
|
+
prompt: buildFreshPrompt(freshMessages),
|
|
435
|
+
resume: undefined,
|
|
436
|
+
systemPrompt,
|
|
437
|
+
systemHash,
|
|
364
438
|
threadId,
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
439
|
+
emitted,
|
|
440
|
+
cacheSession,
|
|
441
|
+
config
|
|
368
442
|
});
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
key.startsWith("CLAUDE_CODE_") ||
|
|
378
|
-
key.startsWith("CLAUDE_SESSION_") ||
|
|
379
|
-
key.startsWith("CLAUDE_ENABLE_") ||
|
|
380
|
-
key.startsWith("CLAUDE_AFTER_") ||
|
|
381
|
-
key.startsWith("CLAUDE_AUTO_")) {
|
|
382
|
-
delete cleanEnv[key];
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
// Bridge AbortSignal → AbortController for the SDK
|
|
386
|
-
let abortController;
|
|
443
|
+
}
|
|
444
|
+
/** Drive one SDK `query` turn and translate its messages into stream items. */
|
|
445
|
+
async *runTurn(args, plan) {
|
|
446
|
+
const queryFn = await this.loadQuery();
|
|
447
|
+
// Cancellation: the SDK stops the underlying query when its AbortController
|
|
448
|
+
// fires. Bridge the caller's signal onto a fresh controller per turn.
|
|
449
|
+
const abortController = new AbortController();
|
|
450
|
+
const onAbort = () => abortController.abort();
|
|
387
451
|
if (args.signal) {
|
|
388
|
-
|
|
389
|
-
if (args.signal.aborted) {
|
|
452
|
+
if (args.signal.aborted)
|
|
390
453
|
abortController.abort();
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
args.signal.addEventListener("abort", () => abortController.abort(), { once: true });
|
|
394
|
-
}
|
|
454
|
+
else
|
|
455
|
+
args.signal.addEventListener("abort", onAbort, { once: true });
|
|
395
456
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
457
|
+
const options = {
|
|
458
|
+
systemPrompt: plan.systemPrompt,
|
|
459
|
+
// Pass aliases ("sonnet"/"opus"/"haiku") straight through to the SDK.
|
|
460
|
+
model: args.model || undefined,
|
|
461
|
+
// 1 for a single turn; higher when tools may drive multiple rounds.
|
|
462
|
+
maxTurns: plan.config.maxTurns,
|
|
463
|
+
// Auto-approve NodeTool's MCP tools (this is the no-prompt allowlist, not
|
|
464
|
+
// an availability restriction). Empty (pure LLM) when tool-free.
|
|
465
|
+
allowedTools: plan.config.mcp?.allowedTools ?? [],
|
|
466
|
+
// Do NOT load repo .claude / CLAUDE.md / skills.
|
|
467
|
+
settingSources: [],
|
|
468
|
+
// Run without asking for permissions: never prompt AND never deny. This
|
|
469
|
+
// lets the SDK agent use its built-in tools (WebSearch/WebFetch/Bash/…)
|
|
470
|
+
// alongside NodeTool's MCP tools. bypassPermissions requires the explicit
|
|
471
|
+
// safety flag below.
|
|
472
|
+
permissionMode: "bypassPermissions",
|
|
473
|
+
allowDangerouslySkipPermissions: true,
|
|
474
|
+
// On the tool-free path (the documented "pure LLM" primitive used by
|
|
475
|
+
// generateMessage/generateMessages), explicitly disable the SDK's
|
|
476
|
+
// built-in tools. Under bypassPermissions they are otherwise live and
|
|
477
|
+
// auto-approved, so a prompt-injected "call Bash …" in untrusted text
|
|
478
|
+
// being summarized/classified would execute on the host. allowedTools is
|
|
479
|
+
// only a no-prompt approval list, not an availability restriction.
|
|
480
|
+
//
|
|
481
|
+
// The test is `toolsOffered`, not `mcp`: a caller whose every tool was
|
|
482
|
+
// replaced by a built-in offered tools and has no MCP server, and
|
|
483
|
+
// disabling the built-ins there would leave it with nothing.
|
|
484
|
+
//
|
|
485
|
+
// `ToolSearch` is disabled on BOTH paths. It searches tools the SDK has
|
|
486
|
+
// deferred, and NodeTool defers none — it registers its handful of tools
|
|
487
|
+
// in-process — so the search always comes back empty. A model that fell
|
|
488
|
+
// back to it after a mis-named tool call got nothing and stalled the turn.
|
|
489
|
+
disallowedTools: plan.config.toolsOffered
|
|
490
|
+
? ["ToolSearch"]
|
|
491
|
+
: [...SDK_BUILTIN_TOOLS, "ToolSearch"],
|
|
492
|
+
includePartialMessages: true,
|
|
493
|
+
// Setting env REPLACES the child env, so spread process.env minus the
|
|
494
|
+
// nested-session leakage. Preserves PATH/HOME/ANTHROPIC_BASE_URL/proxies.
|
|
495
|
+
env: buildChildEnv(),
|
|
496
|
+
abortController
|
|
497
|
+
};
|
|
498
|
+
if (plan.config.outputFormat) {
|
|
499
|
+
options.outputFormat = plan.config.outputFormat;
|
|
500
|
+
}
|
|
501
|
+
// Anchor the path-scoped built-ins to the run's workspace, which is where
|
|
502
|
+
// the NodeTool tools they replace were contained.
|
|
503
|
+
if (plan.config.cwd) {
|
|
504
|
+
options.cwd = plan.config.cwd;
|
|
505
|
+
}
|
|
506
|
+
if (plan.config.mcp) {
|
|
507
|
+
options.mcpServers = plan.config.mcp.mcpServers;
|
|
508
|
+
}
|
|
509
|
+
// Enable the SDK's native skill loop over the user's DB skills, which were
|
|
510
|
+
// materialized into an isolated local plugin. This rides `plugins`, NOT
|
|
511
|
+
// `settingSources` (kept `[]`), so no `~/.claude` / `.claude` settings or
|
|
512
|
+
// `CLAUDE.md` are read; `skipMcpDiscovery` blocks any `.mcp.json` the dir
|
|
513
|
+
// might carry. `skills` names the exact user skills to enable, so any
|
|
514
|
+
// bundled skill is hidden from the listing and the `Skill` tool.
|
|
515
|
+
if (plan.config.skillsPlugin) {
|
|
516
|
+
options.plugins = [
|
|
517
|
+
{
|
|
518
|
+
type: "local",
|
|
519
|
+
path: plan.config.skillsPlugin.dir,
|
|
520
|
+
skipMcpDiscovery: true
|
|
420
521
|
}
|
|
421
|
-
|
|
522
|
+
];
|
|
523
|
+
options.skills = plan.config.skillsPlugin.names;
|
|
422
524
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
log.error("Claude Agent sdk.query() failed", {
|
|
426
|
-
kind: classified.kind,
|
|
427
|
-
error: classified.message,
|
|
428
|
-
model: args.model
|
|
429
|
-
});
|
|
430
|
-
throw classified;
|
|
525
|
+
if (plan.resume) {
|
|
526
|
+
options.resume = plan.resume;
|
|
431
527
|
}
|
|
432
|
-
//
|
|
433
|
-
//
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
528
|
+
// Log the exact wire body (sans the non-serializable AbortController, MCP
|
|
529
|
+
// server instances, and the full env, which can carry secrets) so the
|
|
530
|
+
// request-log UI shows what was sent without leaking the environment.
|
|
531
|
+
this.recordRequestPayload({
|
|
532
|
+
prompt: plan.prompt,
|
|
533
|
+
resume: plan.resume ?? null,
|
|
534
|
+
options: {
|
|
535
|
+
...options,
|
|
536
|
+
abortController: undefined,
|
|
537
|
+
env: undefined,
|
|
538
|
+
mcpServers: plan.config.mcp
|
|
539
|
+
? Object.keys(plan.config.mcp.mcpServers ?? {})
|
|
540
|
+
: undefined
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
log.debug("Claude Agent SDK request", {
|
|
544
|
+
model: args.model,
|
|
545
|
+
resume: Boolean(plan.resume)
|
|
546
|
+
});
|
|
547
|
+
let resolvedModel = args.model;
|
|
548
|
+
// When partial deltas stream, we render from them and skip the final
|
|
549
|
+
// assistant message to avoid duplication; if a build omits partials we fall
|
|
550
|
+
// back to the final message's content blocks.
|
|
551
|
+
let streamedFromPartials = false;
|
|
552
|
+
// The SDK splits one API assistant turn into one frame per content block —
|
|
553
|
+
// thinking, then text, then each tool_use. Emitting a message per frame
|
|
554
|
+
// would hand the consumer a text-only assistant message in the middle of a
|
|
555
|
+
// tool round, which every chat surface reads as "the turn ended" (the chat
|
|
556
|
+
// UI flips the composer back to Send and hides Stop), and would write an
|
|
557
|
+
// empty row for a thinking-only frame. Buffer the blocks and emit one
|
|
558
|
+
// message per assistant turn, text and tool calls together, the way every
|
|
559
|
+
// other provider does.
|
|
560
|
+
let pending = null;
|
|
561
|
+
const flushPending = () => {
|
|
562
|
+
const buffered = pending;
|
|
563
|
+
pending = null;
|
|
564
|
+
if (!buffered)
|
|
565
|
+
return null;
|
|
566
|
+
if (!buffered.text && buffered.toolCalls.length === 0)
|
|
567
|
+
return null;
|
|
568
|
+
return {
|
|
569
|
+
type: "message",
|
|
570
|
+
message: {
|
|
571
|
+
role: "assistant",
|
|
572
|
+
content: buffered.text || null,
|
|
573
|
+
toolCalls: buffered.toolCalls.length ? buffered.toolCalls : null
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
};
|
|
439
577
|
try {
|
|
440
|
-
for await (const msg of
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
578
|
+
for await (const msg of queryFn({ prompt: plan.prompt, options })) {
|
|
579
|
+
if (msg.type === "system" && msg.subtype === "init") {
|
|
580
|
+
// Capture the session and surface it immediately so a streaming
|
|
581
|
+
// consumer can persist it onto the assistant message it creates.
|
|
582
|
+
if (isNonEmptyString(msg.model))
|
|
583
|
+
resolvedModel = msg.model;
|
|
584
|
+
if (plan.threadId) {
|
|
585
|
+
const session = {
|
|
586
|
+
providerId: this.provider,
|
|
587
|
+
model: args.model,
|
|
588
|
+
token: msg.session_id,
|
|
589
|
+
checkpoint: args.messages.length,
|
|
590
|
+
systemHash: plan.systemHash
|
|
591
|
+
};
|
|
592
|
+
if (plan.cacheSession)
|
|
593
|
+
this.sessions.set(plan.threadId, session);
|
|
594
|
+
yield { type: "session", session };
|
|
595
|
+
}
|
|
453
596
|
continue;
|
|
454
597
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
598
|
+
if (msg.type === "stream_event") {
|
|
599
|
+
const captured = capturedModelFromPartial(msg);
|
|
600
|
+
if (captured)
|
|
601
|
+
resolvedModel = captured;
|
|
602
|
+
const delta = partialDelta(msg);
|
|
603
|
+
if (delta?.text != null) {
|
|
604
|
+
streamedFromPartials = true;
|
|
605
|
+
plan.emitted.content = true;
|
|
606
|
+
yield { type: "chunk", content: delta.text, done: false };
|
|
607
|
+
}
|
|
608
|
+
else if (delta?.thinking != null) {
|
|
609
|
+
streamedFromPartials = true;
|
|
610
|
+
plan.emitted.content = true;
|
|
611
|
+
yield {
|
|
612
|
+
type: "chunk",
|
|
613
|
+
content: delta.thinking,
|
|
614
|
+
done: false,
|
|
615
|
+
thinking: true
|
|
616
|
+
};
|
|
460
617
|
}
|
|
461
|
-
|
|
462
|
-
streamedTextLength = 0;
|
|
618
|
+
continue;
|
|
463
619
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
.
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
620
|
+
if (msg.type === "assistant") {
|
|
621
|
+
const m = msg.message;
|
|
622
|
+
if (m && isNonEmptyString(m.model))
|
|
623
|
+
resolvedModel = m.model;
|
|
624
|
+
// Fallback only: no partials arrived, so render text/thinking from the
|
|
625
|
+
// final content blocks — kept strictly separate, never merged.
|
|
626
|
+
if (!streamedFromPartials) {
|
|
627
|
+
for (const block of finalBlocks(msg)) {
|
|
628
|
+
plan.emitted.content = true;
|
|
629
|
+
yield block.thinking
|
|
630
|
+
? {
|
|
631
|
+
type: "chunk",
|
|
632
|
+
content: block.content,
|
|
633
|
+
done: false,
|
|
634
|
+
thinking: true
|
|
635
|
+
}
|
|
636
|
+
: { type: "chunk", content: block.content, done: false };
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
// The loop needs each assistant turn (text + any tool calls) as a
|
|
640
|
+
// persistable message, and a ToolCall item per call for live display.
|
|
641
|
+
// The ToolCall items go out immediately; the message waits for the
|
|
642
|
+
// rest of the turn's blocks (see `pending`).
|
|
643
|
+
if (plan.config.emitMessages) {
|
|
644
|
+
const { text, toolCalls } = assistantParts(msg);
|
|
645
|
+
for (const tc of toolCalls)
|
|
646
|
+
yield tc;
|
|
647
|
+
const parentToolUseId = msg.parent_tool_use_id ?? null;
|
|
648
|
+
if (pending && pending.parentToolUseId !== parentToolUseId) {
|
|
649
|
+
// A subagent's turn and the main agent's are different turns.
|
|
650
|
+
const flushed = flushPending();
|
|
651
|
+
if (flushed)
|
|
652
|
+
yield flushed;
|
|
478
653
|
}
|
|
654
|
+
pending ??= { parentToolUseId, text: "", toolCalls: [] };
|
|
655
|
+
pending.text += text;
|
|
656
|
+
pending.toolCalls.push(...toolCalls);
|
|
479
657
|
}
|
|
480
658
|
continue;
|
|
481
659
|
}
|
|
482
|
-
//
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
if (
|
|
487
|
-
const
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
660
|
+
// Tool results come back as a user message; surface them as tool
|
|
661
|
+
// messages so the harness can persist them. The assistant turn that
|
|
662
|
+
// asked for them is complete by now, so it goes out first.
|
|
663
|
+
if (msg.type === "user") {
|
|
664
|
+
if (plan.config.emitMessages) {
|
|
665
|
+
const flushed = flushPending();
|
|
666
|
+
if (flushed)
|
|
667
|
+
yield flushed;
|
|
668
|
+
for (const tr of toolResultsFromUser(msg)) {
|
|
669
|
+
yield {
|
|
670
|
+
type: "message",
|
|
671
|
+
message: {
|
|
672
|
+
role: "tool",
|
|
673
|
+
toolCallId: tr.toolCallId,
|
|
674
|
+
content: tr.content
|
|
675
|
+
}
|
|
676
|
+
};
|
|
496
677
|
}
|
|
497
678
|
}
|
|
498
|
-
// Reset for next turn (if multi-turn agentic query)
|
|
499
|
-
streamedTextLength = 0;
|
|
500
679
|
continue;
|
|
501
680
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
: `Claude Agent returned an error result (${subtype ?? "unknown"}): ${detail}`;
|
|
522
|
-
log.error("Claude Agent result error", {
|
|
523
|
-
subtype,
|
|
524
|
-
is_error: isError,
|
|
525
|
-
detail
|
|
526
|
-
});
|
|
527
|
-
throw new ClaudeAgentError(kind, message);
|
|
681
|
+
if (msg.type === "result") {
|
|
682
|
+
// Bill every terminal result, not just the successful ones: an
|
|
683
|
+
// errored/max-turns run still consumed (and was charged for) tokens.
|
|
684
|
+
this.trackResultUsage(msg, resolvedModel);
|
|
685
|
+
if (msg.subtype === "success") {
|
|
686
|
+
const flushed = flushPending();
|
|
687
|
+
if (flushed)
|
|
688
|
+
yield flushed;
|
|
689
|
+
const structured = msg.structured_output;
|
|
690
|
+
if (plan.config.resultToolName &&
|
|
691
|
+
isObjectLike(structured) &&
|
|
692
|
+
!Array.isArray(structured)) {
|
|
693
|
+
yield {
|
|
694
|
+
id: `result_${msg.uuid}`,
|
|
695
|
+
name: plan.config.resultToolName,
|
|
696
|
+
args: structured
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
yield { type: "chunk", content: "", done: true };
|
|
528
700
|
}
|
|
529
|
-
|
|
530
|
-
|
|
701
|
+
else {
|
|
702
|
+
throw resultError(msg);
|
|
531
703
|
}
|
|
704
|
+
continue;
|
|
532
705
|
}
|
|
533
706
|
}
|
|
707
|
+
// A stream that ended without a terminal `result` still owes its last
|
|
708
|
+
// assistant turn.
|
|
709
|
+
const trailing = flushPending();
|
|
710
|
+
if (trailing)
|
|
711
|
+
yield trailing;
|
|
534
712
|
}
|
|
535
713
|
catch (err) {
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
});
|
|
542
|
-
throw classified;
|
|
714
|
+
// The query can reject (auth failure, missing session, spawn error). Never
|
|
715
|
+
// collapse to a generic string — surface the real message.
|
|
716
|
+
if (args.signal?.aborted)
|
|
717
|
+
return;
|
|
718
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
543
719
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
720
|
+
finally {
|
|
721
|
+
// Terminate the SDK query on every exit path. On normal completion the
|
|
722
|
+
// query has already finished and this is a no-op; on an early `break`
|
|
723
|
+
// (consumer cancelled) or a throw it is the only thing that stops the
|
|
724
|
+
// subprocess, which otherwise runs its whole agentic loop to completion.
|
|
725
|
+
abortController.abort();
|
|
726
|
+
if (args.signal)
|
|
727
|
+
args.signal.removeEventListener("abort", onAbort);
|
|
547
728
|
}
|
|
548
|
-
|
|
549
|
-
|
|
729
|
+
}
|
|
730
|
+
/** Record token usage from a terminal `result` message (success or error). */
|
|
731
|
+
trackResultUsage(msg, model) {
|
|
732
|
+
const usage = msg.usage;
|
|
733
|
+
if (!usage)
|
|
734
|
+
return;
|
|
735
|
+
const input = num(usage.input_tokens);
|
|
736
|
+
const cacheRead = num(usage.cache_read_input_tokens);
|
|
737
|
+
const cacheWrite = num(usage.cache_creation_input_tokens);
|
|
738
|
+
// genai-prices expects the full prompt total (uncached + cache read/write),
|
|
739
|
+
// matching AnthropicProvider's accounting.
|
|
740
|
+
this.trackUsage(model, {
|
|
741
|
+
inputTokens: input + cacheRead + cacheWrite,
|
|
742
|
+
outputTokens: num(usage.output_tokens),
|
|
743
|
+
cachedTokens: cacheRead,
|
|
744
|
+
cacheWriteTokens: cacheWrite
|
|
745
|
+
});
|
|
550
746
|
}
|
|
551
747
|
async generateMessage(args) {
|
|
552
|
-
|
|
748
|
+
let content = "";
|
|
553
749
|
const toolCalls = [];
|
|
554
750
|
for await (const item of this.generateMessages(args)) {
|
|
555
|
-
if (
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
if ("id" in item && "name" in item && "args" in item) {
|
|
751
|
+
if (isProviderSessionUpdate(item) ||
|
|
752
|
+
isProviderMessageEvent(item) ||
|
|
753
|
+
isProviderStop(item))
|
|
754
|
+
continue;
|
|
755
|
+
if ("args" in item) {
|
|
561
756
|
toolCalls.push(item);
|
|
562
757
|
}
|
|
758
|
+
else if (!item.thinking && isString(item.content)) {
|
|
759
|
+
content += item.content;
|
|
760
|
+
}
|
|
563
761
|
}
|
|
564
762
|
return {
|
|
565
763
|
role: "assistant",
|
|
566
|
-
content:
|
|
567
|
-
|
|
764
|
+
content: content || null,
|
|
765
|
+
toolCalls: toolCalls.length ? toolCalls : null
|
|
568
766
|
};
|
|
569
767
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
768
|
+
}
|
|
769
|
+
function num(value) {
|
|
770
|
+
return isFiniteNumber(value) ? value : 0;
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* `CLAUDE_CODE_OAUTH_TOKEN` matches {@link NESTED_SESSION_ENV} but is the CLI's
|
|
774
|
+
* credential, not session leakage: on a headless host (CI) with no interactive
|
|
775
|
+
* `~/.claude` login it is the *only* way the child authenticates. Keep it.
|
|
776
|
+
*/
|
|
777
|
+
const CHILD_ENV_ALLOWLIST = /^CLAUDE_CODE_OAUTH_TOKEN$/;
|
|
778
|
+
/**
|
|
779
|
+
* A copy of the current environment with nested-session leakage stripped, for
|
|
780
|
+
* the SDK's `options.env`. `ANTHROPIC_BASE_URL` and the `HTTP(S)_PROXY` vars are
|
|
781
|
+
* preserved (they are not matched by {@link NESTED_SESSION_ENV}) so API routing
|
|
782
|
+
* keeps working; `CLAUDE_CODE_OAUTH_TOKEN` is explicitly allowlisted so
|
|
783
|
+
* token-based auth survives on headless hosts.
|
|
784
|
+
*/
|
|
785
|
+
function buildChildEnv() {
|
|
786
|
+
const env = {};
|
|
787
|
+
const source = typeof process !== "undefined" ? process.env : {};
|
|
788
|
+
for (const [key, value] of Object.entries(source)) {
|
|
789
|
+
if (value === undefined)
|
|
790
|
+
continue;
|
|
791
|
+
if (NESTED_SESSION_ENV.test(key) && !CHILD_ENV_ALLOWLIST.test(key))
|
|
792
|
+
continue;
|
|
793
|
+
env[key] = value;
|
|
794
|
+
}
|
|
795
|
+
return env;
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* A DB skill name that is safe to use as a filesystem directory. The DB
|
|
799
|
+
* validates names (lowercase `a-z0-9-`), but path safety is enforced here too:
|
|
800
|
+
* a name with a separator or `.`-segment could otherwise escape the plugin
|
|
801
|
+
* dir. Defense in depth — an unsafe name is skipped, not written.
|
|
802
|
+
*/
|
|
803
|
+
function isFilesystemSafeSkillName(name) {
|
|
804
|
+
return /^[a-z0-9][a-z0-9-]{0,63}$/.test(name);
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Write the user's DB skills to a throwaway local plugin the SDK's native skill
|
|
808
|
+
* loader can discover, and return `{ dir, names }` (or null when nothing was
|
|
809
|
+
* written). The layout is the Claude Code plugin format:
|
|
810
|
+
*
|
|
811
|
+
* <dir>/.claude-plugin/plugin.json
|
|
812
|
+
* <dir>/skills/<name>/SKILL.md (name/description frontmatter + body)
|
|
813
|
+
*
|
|
814
|
+
* This is deliberately separate from `settingSources`: a `plugins` entry loads
|
|
815
|
+
* skills without enabling `~/.claude` / `.claude` settings or `CLAUDE.md`, and
|
|
816
|
+
* `skipMcpDiscovery` blocks any `.mcp.json` the dir might contain. The caller
|
|
817
|
+
* owns cleanup (see {@link ClaudeAgentProvider.generateLoop}'s `finally`).
|
|
818
|
+
*/
|
|
819
|
+
async function materializeSkillsPlugin(skills) {
|
|
820
|
+
const usable = skills.filter((s) => isFilesystemSafeSkillName(s.name) && s.content.trim().length > 0);
|
|
821
|
+
if (usable.length === 0)
|
|
822
|
+
return null;
|
|
823
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "nodetool-skills-"));
|
|
824
|
+
await fs.mkdir(path.join(dir, ".claude-plugin"), { recursive: true });
|
|
825
|
+
await fs.writeFile(path.join(dir, ".claude-plugin", "plugin.json"), JSON.stringify({
|
|
826
|
+
name: SKILLS_PLUGIN_NAME,
|
|
827
|
+
version: "1.0.0",
|
|
828
|
+
description: "User-defined skills from NodeTool."
|
|
829
|
+
}), "utf8");
|
|
830
|
+
const names = [];
|
|
831
|
+
for (const skill of usable) {
|
|
832
|
+
const skillDir = path.join(dir, "skills", skill.name);
|
|
833
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
834
|
+
// JSON-stringify the description: a JSON string is a valid YAML double-
|
|
835
|
+
// quoted scalar, so colons / quotes / newlines can't break the frontmatter.
|
|
836
|
+
const md = [
|
|
837
|
+
"---",
|
|
838
|
+
`name: ${skill.name}`,
|
|
839
|
+
`description: ${JSON.stringify(skill.description)}`,
|
|
840
|
+
"---",
|
|
841
|
+
"",
|
|
842
|
+
skill.content.trim(),
|
|
843
|
+
""
|
|
844
|
+
].join("\n");
|
|
845
|
+
await fs.writeFile(path.join(skillDir, "SKILL.md"), md, "utf8");
|
|
846
|
+
names.push(skill.name);
|
|
847
|
+
}
|
|
848
|
+
return { dir, names };
|
|
849
|
+
}
|
|
850
|
+
/** Pull a text/thinking delta out of a partial `stream_event`, if any. */
|
|
851
|
+
function partialDelta(msg) {
|
|
852
|
+
const event = msg.event;
|
|
853
|
+
if (event.type !== "content_block_delta")
|
|
854
|
+
return null;
|
|
855
|
+
const delta = event.delta;
|
|
856
|
+
if (delta.type === "text_delta")
|
|
857
|
+
return { text: delta.text };
|
|
858
|
+
if (delta.type === "thinking_delta")
|
|
859
|
+
return { thinking: delta.thinking };
|
|
860
|
+
return null;
|
|
861
|
+
}
|
|
862
|
+
/** The concrete dated model id, captured from a partial `message_start`. */
|
|
863
|
+
function capturedModelFromPartial(msg) {
|
|
864
|
+
const event = msg.event;
|
|
865
|
+
if (event.type !== "message_start")
|
|
866
|
+
return null;
|
|
867
|
+
const model = event.message?.model;
|
|
868
|
+
return isNonEmptyString(model) && model !== "<synthetic>" ? model : null;
|
|
869
|
+
}
|
|
870
|
+
/** Text/thinking blocks of a final assistant message, kept separate. */
|
|
871
|
+
function finalBlocks(msg) {
|
|
872
|
+
const content = msg.message?.content;
|
|
873
|
+
if (!Array.isArray(content))
|
|
874
|
+
return [];
|
|
875
|
+
const out = [];
|
|
876
|
+
for (const block of content) {
|
|
877
|
+
if (block.type === "text" && isString(block.text)) {
|
|
878
|
+
out.push({ content: block.text, thinking: false });
|
|
879
|
+
}
|
|
880
|
+
else if (block.type === "thinking" && isString(block.thinking)) {
|
|
881
|
+
out.push({ content: block.thinking, thinking: true });
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
return out;
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* Recover the plain tool name from what the model emitted.
|
|
888
|
+
*
|
|
889
|
+
* Two prefixes get stripped, in order:
|
|
890
|
+
* - `mcp__<server>__`, which the SDK adds to in-process MCP tool names.
|
|
891
|
+
* - `tools.`, the retired guest toolbelt's call form, which models still
|
|
892
|
+
* turn into a top-level tool name. Without this the call reaches no tool
|
|
893
|
+
* at all.
|
|
894
|
+
*/
|
|
895
|
+
function stripToolPrefix(name) {
|
|
896
|
+
let n = isString(name) ? name : "";
|
|
897
|
+
if (n.startsWith(TOOL_PREFIX))
|
|
898
|
+
n = n.slice(TOOL_PREFIX.length);
|
|
899
|
+
if (n.startsWith(GUEST_TOOL_PREFIX))
|
|
900
|
+
n = n.slice(GUEST_TOOL_PREFIX.length);
|
|
901
|
+
return n;
|
|
902
|
+
}
|
|
903
|
+
/** Split a final assistant message into its text and its tool calls. */
|
|
904
|
+
function assistantParts(msg) {
|
|
905
|
+
const content = msg.message?.content;
|
|
906
|
+
let text = "";
|
|
907
|
+
const toolCalls = [];
|
|
908
|
+
if (Array.isArray(content)) {
|
|
909
|
+
for (const raw of content) {
|
|
910
|
+
const b = raw;
|
|
911
|
+
if (b.type === "text" && isString(b.text)) {
|
|
912
|
+
text += b.text;
|
|
913
|
+
}
|
|
914
|
+
else if (b.type === "tool_use") {
|
|
915
|
+
toolCalls.push({
|
|
916
|
+
id: isString(b.id) ? b.id : `call_${toolCalls.length}`,
|
|
917
|
+
name: stripToolPrefix(b.name),
|
|
918
|
+
args: isObjectLike(b.input)
|
|
919
|
+
? b.input
|
|
920
|
+
: {}
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
return { text, toolCalls };
|
|
926
|
+
}
|
|
927
|
+
/** Extract `tool_result` blocks from a user message the SDK emitted. */
|
|
928
|
+
function toolResultsFromUser(msg) {
|
|
929
|
+
const content = msg.message?.content;
|
|
930
|
+
const out = [];
|
|
931
|
+
if (Array.isArray(content)) {
|
|
932
|
+
for (const raw of content) {
|
|
933
|
+
const b = raw;
|
|
934
|
+
if (b.type === "tool_result" && isString(b.tool_use_id)) {
|
|
935
|
+
out.push({
|
|
936
|
+
toolCallId: b.tool_use_id,
|
|
937
|
+
content: toolResultText(b.content)
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
return out;
|
|
943
|
+
}
|
|
944
|
+
/** Flatten an MCP tool-result content payload to text. */
|
|
945
|
+
function toolResultText(content) {
|
|
946
|
+
if (isString(content))
|
|
947
|
+
return content;
|
|
948
|
+
if (Array.isArray(content)) {
|
|
949
|
+
return content
|
|
950
|
+
.map((c) => {
|
|
951
|
+
const cc = c;
|
|
952
|
+
return cc.type === "text" && isString(cc.text) ? cc.text : "";
|
|
953
|
+
})
|
|
954
|
+
.join("");
|
|
955
|
+
}
|
|
956
|
+
return "";
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Build an in-process MCP tool definition that bridges a NodeTool
|
|
960
|
+
* {@link ProviderTool} to the harness's tool executor. The JSON-Schema input is
|
|
961
|
+
* shimmed to the Zod raw shape the SDK expects.
|
|
962
|
+
*/
|
|
963
|
+
function toolDefinition(tool, run, resolveMedia) {
|
|
964
|
+
return {
|
|
965
|
+
name: tool.name,
|
|
966
|
+
description: tool.description ?? "",
|
|
967
|
+
inputSchema: jsonSchemaToZodShape(tool.inputSchema),
|
|
968
|
+
handler: async (toolArgs) => {
|
|
969
|
+
const result = await run(tool.name, toolArgs ?? {});
|
|
970
|
+
const resolved = Array.isArray(result) &&
|
|
971
|
+
result.some((part) => part.type === "image_url") &&
|
|
972
|
+
resolveMedia
|
|
973
|
+
? await resolveMedia([{ role: "user", content: result }])
|
|
974
|
+
: null;
|
|
975
|
+
const content = resolved?.[0]?.content;
|
|
976
|
+
return {
|
|
977
|
+
content: toolResultToMcpContent(Array.isArray(content) ? content : result)
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Decode a NodeTool image content part into an MCP image block (raw base64 +
|
|
984
|
+
* mimeType). Returns null for a remote URL we cannot inline — MCP image content
|
|
985
|
+
* is base64-only, so the caller reports that the image could not be shown.
|
|
986
|
+
*/
|
|
987
|
+
function toMcpImageBlock(image) {
|
|
988
|
+
let base64;
|
|
989
|
+
let mimeType = image.mimeType;
|
|
990
|
+
const fromDataUri = (uri) => {
|
|
991
|
+
const match = /^data:([^;,]+)?(;base64)?,([\s\S]*)$/.exec(uri);
|
|
992
|
+
if (!match)
|
|
993
|
+
return;
|
|
994
|
+
mimeType = mimeType ?? match[1] ?? "image/png";
|
|
995
|
+
base64 = match[2]
|
|
996
|
+
? (match[3] ?? "")
|
|
997
|
+
: Buffer.from(decodeURIComponent(match[3] ?? "")).toString("base64");
|
|
998
|
+
};
|
|
999
|
+
if (isString(image.data)) {
|
|
1000
|
+
if (image.data.startsWith("data:"))
|
|
1001
|
+
fromDataUri(image.data);
|
|
1002
|
+
else
|
|
1003
|
+
base64 = image.data;
|
|
1004
|
+
}
|
|
1005
|
+
else if (image.data instanceof Uint8Array) {
|
|
1006
|
+
base64 = Buffer.from(image.data).toString("base64");
|
|
1007
|
+
}
|
|
1008
|
+
if (!base64 && isString(image.uri) && image.uri.startsWith("data:")) {
|
|
1009
|
+
fromDataUri(image.uri);
|
|
1010
|
+
}
|
|
1011
|
+
if (!base64)
|
|
1012
|
+
return null;
|
|
1013
|
+
return { type: "image", data: base64, mimeType: mimeType ?? "image/png" };
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Convert a tool result into MCP content blocks. Text passes through; image
|
|
1017
|
+
* parts become MCP image blocks (the SDK forwards them to Claude as real
|
|
1018
|
+
* images). An image that can't be inlined becomes a failure note.
|
|
1019
|
+
*/
|
|
1020
|
+
export function toolResultToMcpContent(result) {
|
|
1021
|
+
if (isString(result)) {
|
|
1022
|
+
return [{ type: "text", text: result }];
|
|
1023
|
+
}
|
|
1024
|
+
const blocks = [];
|
|
1025
|
+
for (const part of result) {
|
|
1026
|
+
if (part.type === "text") {
|
|
1027
|
+
blocks.push({ type: "text", text: part.text });
|
|
1028
|
+
}
|
|
1029
|
+
else if (part.type === "image_url") {
|
|
1030
|
+
const img = toMcpImageBlock(part.image);
|
|
1031
|
+
if (img)
|
|
1032
|
+
blocks.push(img);
|
|
1033
|
+
else {
|
|
1034
|
+
blocks.push({
|
|
1035
|
+
type: "text",
|
|
1036
|
+
text: isString(part.image.uri)
|
|
1037
|
+
? `[image could not be shown: ${part.image.uri}]`
|
|
1038
|
+
: "[image could not be shown]"
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
if (blocks.length === 0) {
|
|
1044
|
+
blocks.push({ type: "text", text: "[no content]" });
|
|
1045
|
+
}
|
|
1046
|
+
return blocks;
|
|
1047
|
+
}
|
|
1048
|
+
/** Convert a JSON-Schema object's properties to a Zod raw shape. */
|
|
1049
|
+
function jsonSchemaToZodShape(schema) {
|
|
1050
|
+
const props = schema?.properties ?? {};
|
|
1051
|
+
const required = new Set(schema?.required ?? []);
|
|
1052
|
+
const shape = {};
|
|
1053
|
+
for (const [key, raw] of Object.entries(props)) {
|
|
1054
|
+
let zt = jsonPropToZod(raw);
|
|
1055
|
+
if (!required.has(key))
|
|
1056
|
+
zt = zt.optional();
|
|
1057
|
+
shape[key] = zt;
|
|
1058
|
+
}
|
|
1059
|
+
return shape;
|
|
1060
|
+
}
|
|
1061
|
+
/** Convert one JSON-Schema property to a Zod type (the subset NodeTool uses). */
|
|
1062
|
+
function jsonPropToZod(prop) {
|
|
1063
|
+
const desc = isString(prop?.description) ? prop.description : undefined;
|
|
1064
|
+
let zt;
|
|
1065
|
+
switch (prop?.type) {
|
|
1066
|
+
case "string":
|
|
1067
|
+
zt = z.string();
|
|
1068
|
+
break;
|
|
1069
|
+
case "number":
|
|
1070
|
+
case "integer":
|
|
1071
|
+
zt = z.number();
|
|
1072
|
+
break;
|
|
1073
|
+
case "boolean":
|
|
1074
|
+
zt = z.boolean();
|
|
1075
|
+
break;
|
|
1076
|
+
case "array":
|
|
1077
|
+
zt = z.array(prop.items
|
|
1078
|
+
? jsonPropToZod(prop.items)
|
|
1079
|
+
: z.unknown());
|
|
1080
|
+
break;
|
|
1081
|
+
case "object": {
|
|
1082
|
+
const shape = jsonSchemaToZodShape(prop);
|
|
1083
|
+
// Free-form objects (no declared sub-properties — e.g. add_node's
|
|
1084
|
+
// `node_properties`) must keep arbitrary keys. `z.object({})` strips
|
|
1085
|
+
// every key, silently dropping all node configuration on the SDK tool
|
|
1086
|
+
// bridge; passthrough preserves the nested values.
|
|
1087
|
+
zt =
|
|
1088
|
+
Object.keys(shape).length > 0
|
|
1089
|
+
? z.object(shape)
|
|
1090
|
+
: z.object({}).passthrough();
|
|
1091
|
+
break;
|
|
578
1092
|
}
|
|
1093
|
+
default:
|
|
1094
|
+
zt = z.unknown();
|
|
579
1095
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
1096
|
+
return desc ? zt.describe(desc) : zt;
|
|
1097
|
+
}
|
|
1098
|
+
/** Build a descriptive Error from a non-success `result` message. */
|
|
1099
|
+
function resultError(msg) {
|
|
1100
|
+
const parts = [`Claude Agent SDK query failed (${msg.subtype})`];
|
|
1101
|
+
if ("errors" in msg && Array.isArray(msg.errors) && msg.errors.length) {
|
|
1102
|
+
parts.push(msg.errors.join("; "));
|
|
1103
|
+
}
|
|
1104
|
+
else if ("result" in msg && isNonEmptyString(msg.result)) {
|
|
1105
|
+
parts.push(msg.result);
|
|
1106
|
+
}
|
|
1107
|
+
const denials = (msg).permission_denials;
|
|
1108
|
+
if (Array.isArray(denials) && denials.length) {
|
|
1109
|
+
parts.push(`permission denied: ${denials.map((d) => d.tool_name ?? "?").join(", ")}`);
|
|
1110
|
+
}
|
|
1111
|
+
return new Error(parts.join(": "));
|
|
1112
|
+
}
|
|
1113
|
+
/** Flatten a message's content to plain text (text blocks only). */
|
|
1114
|
+
function textOf(content) {
|
|
1115
|
+
if (content == null)
|
|
1116
|
+
return "";
|
|
1117
|
+
if (isString(content))
|
|
1118
|
+
return content;
|
|
1119
|
+
return content
|
|
1120
|
+
.filter((c) => c.type === "text")
|
|
1121
|
+
.map((c) => c.text)
|
|
1122
|
+
.join("");
|
|
1123
|
+
}
|
|
1124
|
+
/** Join all system messages into the replacement system prompt. */
|
|
1125
|
+
function extractSystemPrompt(messages) {
|
|
1126
|
+
const system = messages
|
|
1127
|
+
.filter((m) => m.role === "system")
|
|
1128
|
+
.map((m) => textOf(m.content))
|
|
1129
|
+
.filter(Boolean)
|
|
1130
|
+
.join("\n\n");
|
|
1131
|
+
return system || DEFAULT_SYSTEM_PROMPT;
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* The new turn(s) to send when resuming: only the user messages added since the
|
|
1135
|
+
* session's checkpoint. Assistant messages in the slice were produced by the
|
|
1136
|
+
* resumed session itself, so they are already known to it and must not be
|
|
1137
|
+
* replayed (re-feeding them would present the model its own prior answer as
|
|
1138
|
+
* user input).
|
|
1139
|
+
*/
|
|
1140
|
+
function buildResumeDelta(messages, checkpoint) {
|
|
1141
|
+
return messages
|
|
1142
|
+
.slice(checkpoint)
|
|
1143
|
+
.filter((m) => m.role === "user")
|
|
1144
|
+
.map((m) => textOf(m.content))
|
|
1145
|
+
.filter(Boolean)
|
|
1146
|
+
.join("\n\n");
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* The prompt for a fresh session. A lone user turn is sent verbatim. When there
|
|
1150
|
+
* is pre-existing FOREIGN history (a cold thread, a model switch, or an
|
|
1151
|
+
* edited/branched conversation) we prime context ONCE with a single delimited
|
|
1152
|
+
* user message instead of rebuilding a `Human:/Assistant:` transcript — the SDK
|
|
1153
|
+
* cannot import external assistant turns, so this is deliberate context priming,
|
|
1154
|
+
* not a faithful reconstruction. Only final assistant TEXT is included
|
|
1155
|
+
* (thinking is stripped by {@link textOf}).
|
|
1156
|
+
*/
|
|
1157
|
+
function buildFreshPrompt(messages) {
|
|
1158
|
+
const convo = messages.filter((m) => m.role !== "system");
|
|
1159
|
+
if (convo.length === 0)
|
|
1160
|
+
return "";
|
|
1161
|
+
if (convo.length === 1 && convo[0].role === "user") {
|
|
1162
|
+
return textOf(convo[0].content);
|
|
599
1163
|
}
|
|
1164
|
+
const last = convo[convo.length - 1];
|
|
1165
|
+
const newTurn = last.role === "user" ? textOf(last.content) : "";
|
|
1166
|
+
const prior = last.role === "user" ? convo.slice(0, -1) : convo;
|
|
1167
|
+
const transcript = prior
|
|
1168
|
+
.map((m) => {
|
|
1169
|
+
const text = textOf(m.content);
|
|
1170
|
+
if (!text)
|
|
1171
|
+
return "";
|
|
1172
|
+
return `${m.role === "assistant" ? "Assistant" : "User"}: ${text}`;
|
|
1173
|
+
})
|
|
1174
|
+
.filter(Boolean)
|
|
1175
|
+
.join("\n\n");
|
|
1176
|
+
const primed = transcript
|
|
1177
|
+
? `<conversation_so_far>\n${transcript}\n</conversation_so_far>`
|
|
1178
|
+
: "";
|
|
1179
|
+
return [primed, newTurn].filter(Boolean).join("\n\n");
|
|
600
1180
|
}
|
|
601
1181
|
//# sourceMappingURL=claude-agent-provider.js.map
|