@nodetool-ai/runtime 0.7.0-rc.23 → 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,9 +1,77 @@
|
|
|
1
|
+
import { isProviderSessionUpdate, isProviderMessageEvent, isProviderStop, isProviderToolErrorResult, PROVIDER_STOP_ABORTED } from "./types.js";
|
|
2
|
+
import { ProviderGenerationsUnsupportedError } from "./provider-generations.js";
|
|
1
3
|
import { CostCalculator } from "./cost-calculator.js";
|
|
2
4
|
import { getTracer } from "../telemetry.js";
|
|
3
|
-
import { withUsageCapture, setLastUsage, consumeLastUsage, peekLastUsage, createUsageSlot } from "../tracing-helpers.js";
|
|
5
|
+
import { withUsageCapture, withModalityCapture, setLastUsage, setLastRequest, consumeLastUsage, peekLastUsage, peekLastRequest, createUsageSlot } from "../tracing-helpers.js";
|
|
6
|
+
import { isRunBudget } from "../turn-budget.js";
|
|
7
|
+
import { countTokens } from "../token-counter.js";
|
|
8
|
+
import { isCallable, isObjectLike, isRecord, isString } from "@nodetool-ai/protocol";
|
|
9
|
+
import { logProviderRequestFailure } from "./provider-request-log.js";
|
|
10
|
+
import { annotateProviderError, markContextExceeded } from "./provider-error.js";
|
|
11
|
+
import { buildProviderCallFailure } from "./provider-call-failure.js";
|
|
12
|
+
import { applyEntityReferences } from "./entity-references.js";
|
|
13
|
+
import { expandVideoContentAsFrames, videoFrameFallbackEnabled } from "./video-frame-fallback.js";
|
|
4
14
|
import { SpanStatusCode } from "@opentelemetry/api";
|
|
5
|
-
import { createLogger,
|
|
15
|
+
import { createLogger, getDefaultAssetsPath } from "@nodetool-ai/config";
|
|
6
16
|
const log = createLogger("nodetool.runtime.provider");
|
|
17
|
+
/**
|
|
18
|
+
* Split the `turnBudget` argument into the run-level bounds (when the caller
|
|
19
|
+
* passed a {@link RunBudget}) and the per-turn admission object every loop
|
|
20
|
+
* reserves against. One argument serves both, so callers holding only a bare
|
|
21
|
+
* {@link TurnBudget} — the supervisor — keep working unchanged.
|
|
22
|
+
*
|
|
23
|
+
* A free function rather than a method: `generateLoop` is called unbound on
|
|
24
|
+
* duck-typed test providers (`BaseProvider.prototype.generateLoop.call(mock)`),
|
|
25
|
+
* where `this` has no class methods.
|
|
26
|
+
*/
|
|
27
|
+
export function resolveTurnBudget(budget) {
|
|
28
|
+
if (isRunBudget(budget)) {
|
|
29
|
+
return { runBudget: budget, turnBudget: budget.turns };
|
|
30
|
+
}
|
|
31
|
+
return { runBudget: null, turnBudget: budget };
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The stop item for a refused turn. A {@link RunBudget} says which ceiling
|
|
35
|
+
* refused it; a bare {@link TurnBudget} only knows that one did.
|
|
36
|
+
*/
|
|
37
|
+
export function budgetStopItem(runBudget) {
|
|
38
|
+
const exhausted = runBudget?.exhausted ?? null;
|
|
39
|
+
return {
|
|
40
|
+
type: "stop",
|
|
41
|
+
reason: exhausted?.kind === "deadline" ? "deadline" : "budget",
|
|
42
|
+
detail: exhausted?.detail ?? "the turn budget refused the next model turn"
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Collapse a tool result to plain text. Used by string-only tool channels (the
|
|
47
|
+
* legacy inline callback, the Claude Agent SDK's MCP tools) when a tool returns
|
|
48
|
+
* {@link MessageContent} blocks they can't render — keep the text, drop images.
|
|
49
|
+
*/
|
|
50
|
+
export function toolResultToText(result) {
|
|
51
|
+
if (isString(result))
|
|
52
|
+
return result;
|
|
53
|
+
const text = result
|
|
54
|
+
.filter((c) => c.type === "text")
|
|
55
|
+
.map((c) => c.text)
|
|
56
|
+
.join("\n");
|
|
57
|
+
return text || "[image result]";
|
|
58
|
+
}
|
|
59
|
+
export function splitToolResultImages(result) {
|
|
60
|
+
if (isString(result)) {
|
|
61
|
+
return { toolContent: result, imageMessage: null };
|
|
62
|
+
}
|
|
63
|
+
const images = result.filter((c) => c.type === "image_url");
|
|
64
|
+
if (images.length === 0) {
|
|
65
|
+
return { toolContent: result, imageMessage: null };
|
|
66
|
+
}
|
|
67
|
+
const texts = result.filter((c) => c.type === "text");
|
|
68
|
+
const note = texts.map((c) => c.text).join("\n") ||
|
|
69
|
+
"Returned an image, shown in the next message.";
|
|
70
|
+
return {
|
|
71
|
+
toolContent: note,
|
|
72
|
+
imageMessage: { role: "user", content: [...texts, ...images] }
|
|
73
|
+
};
|
|
74
|
+
}
|
|
7
75
|
/**
|
|
8
76
|
* Derive a provider's capability set by checking which optional `getAvailable*`
|
|
9
77
|
* methods it overrides on its prototype. Every provider can always generate
|
|
@@ -27,10 +95,70 @@ export function providerCapabilities(instance) {
|
|
|
27
95
|
BaseProvider.prototype.getAvailableVideoModels) {
|
|
28
96
|
capabilities.push("text_to_video", "image_to_video");
|
|
29
97
|
}
|
|
98
|
+
if (instance.referenceToVideo !== BaseProvider.prototype.referenceToVideo) {
|
|
99
|
+
capabilities.push("reference_to_video");
|
|
100
|
+
}
|
|
101
|
+
// The remaining task types don't have their own model-discovery method —
|
|
102
|
+
// they reuse Image/VideoModel (advertised via each model's `supportedTasks`).
|
|
103
|
+
// Advertise the provider-level capability when the concrete class overrides
|
|
104
|
+
// the matching task method.
|
|
105
|
+
if (instance.inpaint !== BaseProvider.prototype.inpaint) {
|
|
106
|
+
capabilities.push("inpainting");
|
|
107
|
+
}
|
|
108
|
+
if (instance.outpaintImage !== BaseProvider.prototype.outpaintImage) {
|
|
109
|
+
capabilities.push("outpaint_image");
|
|
110
|
+
}
|
|
111
|
+
if (instance.upscaleImage !== BaseProvider.prototype.upscaleImage) {
|
|
112
|
+
capabilities.push("upscale_image");
|
|
113
|
+
}
|
|
114
|
+
if (instance.removeBackground !== BaseProvider.prototype.removeBackground) {
|
|
115
|
+
capabilities.push("remove_background");
|
|
116
|
+
}
|
|
117
|
+
if (instance.relightImage !== BaseProvider.prototype.relightImage) {
|
|
118
|
+
capabilities.push("relight_image");
|
|
119
|
+
}
|
|
120
|
+
if (instance.segmentImage !== BaseProvider.prototype.segmentImage) {
|
|
121
|
+
capabilities.push("segment_image");
|
|
122
|
+
}
|
|
123
|
+
if (instance.vectorizeImage !== BaseProvider.prototype.vectorizeImage) {
|
|
124
|
+
capabilities.push("vectorize_image");
|
|
125
|
+
}
|
|
126
|
+
if (instance.videoToVideo !== BaseProvider.prototype.videoToVideo) {
|
|
127
|
+
capabilities.push("video_to_video");
|
|
128
|
+
}
|
|
129
|
+
if (instance.videoToAudio !== BaseProvider.prototype.videoToAudio) {
|
|
130
|
+
capabilities.push("video_to_audio");
|
|
131
|
+
}
|
|
132
|
+
if (instance.trackObject !== BaseProvider.prototype.trackObject) {
|
|
133
|
+
capabilities.push("track_object");
|
|
134
|
+
}
|
|
135
|
+
if (instance.extendVideo !== BaseProvider.prototype.extendVideo) {
|
|
136
|
+
capabilities.push("extend_video");
|
|
137
|
+
}
|
|
138
|
+
if (instance.upscaleVideo !== BaseProvider.prototype.upscaleVideo) {
|
|
139
|
+
capabilities.push("upscale_video");
|
|
140
|
+
}
|
|
141
|
+
if (instance.interpolateVideo !== BaseProvider.prototype.interpolateVideo) {
|
|
142
|
+
capabilities.push("interpolate_video");
|
|
143
|
+
}
|
|
144
|
+
if (instance.outpaintVideo !== BaseProvider.prototype.outpaintVideo) {
|
|
145
|
+
capabilities.push("outpaint_video");
|
|
146
|
+
}
|
|
147
|
+
if (instance.lipSync !== BaseProvider.prototype.lipSync) {
|
|
148
|
+
capabilities.push("lip_sync");
|
|
149
|
+
}
|
|
30
150
|
if (instance.getAvailableTTSModels !==
|
|
31
151
|
BaseProvider.prototype.getAvailableTTSModels) {
|
|
32
152
|
capabilities.push("text_to_speech");
|
|
33
153
|
}
|
|
154
|
+
if (instance.getAvailableMusicModels !==
|
|
155
|
+
BaseProvider.prototype.getAvailableMusicModels) {
|
|
156
|
+
capabilities.push("text_to_music");
|
|
157
|
+
}
|
|
158
|
+
if (instance.getAvailableAudioToAudioModels !==
|
|
159
|
+
BaseProvider.prototype.getAvailableAudioToAudioModels) {
|
|
160
|
+
capabilities.push("audio_to_audio");
|
|
161
|
+
}
|
|
34
162
|
if (instance.getAvailableASRModels !==
|
|
35
163
|
BaseProvider.prototype.getAvailableASRModels) {
|
|
36
164
|
capabilities.push("automatic_speech_recognition");
|
|
@@ -45,12 +173,134 @@ export function providerCapabilities(instance) {
|
|
|
45
173
|
// individual `textTo3D` / `imageTo3D` calls will throw if not implemented.
|
|
46
174
|
capabilities.push("text_to_3d", "image_to_3d");
|
|
47
175
|
}
|
|
176
|
+
// The provider's own record of what it ran, exposed only by providers with
|
|
177
|
+
// a history API behind it (see provider-generations.ts).
|
|
178
|
+
if (instance.listGenerations !== BaseProvider.prototype.listGenerations) {
|
|
179
|
+
capabilities.push("list_generations");
|
|
180
|
+
}
|
|
181
|
+
if (instance.getGeneration !== BaseProvider.prototype.getGeneration) {
|
|
182
|
+
capabilities.push("get_generation");
|
|
183
|
+
}
|
|
48
184
|
return capabilities;
|
|
49
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* What one attached medium costs a vision/audio model, per block. These are
|
|
188
|
+
* modality constants, not measurements of the bytes: by the time a turn is
|
|
189
|
+
* estimated, `resolveMessageMediaUris` has inlined every `asset://` as a
|
|
190
|
+
* `data:` uri, and BPE-tokenizing that base64 scores a 50 KB image at ~49,000
|
|
191
|
+
* tokens against a real bill of ~1,600. A fixed figure per block is the right
|
|
192
|
+
* order of magnitude; the base64 length is off by two of them.
|
|
193
|
+
*
|
|
194
|
+
* - Image: 1,600 — the common vision-model charge for a full-size image
|
|
195
|
+
* (Gemini bills a large image in the 1,000-1,600 band, GPT-4o high-detail
|
|
196
|
+
* lands near 1,100). Take the top of the band, since a reservation is a
|
|
197
|
+
* worst case.
|
|
198
|
+
* - Audio: 2,000 — roughly a minute at the ~32 tokens/second Gemini charges
|
|
199
|
+
* for audio. Longer clips under-count, but audio in a chat turn is short.
|
|
200
|
+
* - Video: 4,000 — video is billed per sampled frame (~250-300 tokens/second
|
|
201
|
+
* at 1 fps), so this covers a short clip of ~15 seconds.
|
|
202
|
+
* - Document: 3,000 — a few pages of extracted PDF text.
|
|
203
|
+
*/
|
|
204
|
+
const IMAGE_CONTENT_TOKENS = 1_600;
|
|
205
|
+
const AUDIO_CONTENT_TOKENS = 2_000;
|
|
206
|
+
const VIDEO_CONTENT_TOKENS = 4_000;
|
|
207
|
+
const DOCUMENT_CONTENT_TOKENS = 3_000;
|
|
208
|
+
/** Any content block that is neither text nor a modality named above. */
|
|
209
|
+
const UNKNOWN_CONTENT_TOKENS = 1_600;
|
|
210
|
+
function estimateContentPartTokens(part) {
|
|
211
|
+
switch (part.type) {
|
|
212
|
+
case "text":
|
|
213
|
+
return countTokens(part.text);
|
|
214
|
+
case "image_url":
|
|
215
|
+
return IMAGE_CONTENT_TOKENS;
|
|
216
|
+
case "audio":
|
|
217
|
+
return AUDIO_CONTENT_TOKENS;
|
|
218
|
+
case "video":
|
|
219
|
+
return VIDEO_CONTENT_TOKENS;
|
|
220
|
+
case "document":
|
|
221
|
+
return DOCUMENT_CONTENT_TOKENS;
|
|
222
|
+
default: {
|
|
223
|
+
// A block of some shape this build does not narrow — a `thought` block
|
|
224
|
+
// from the protocol union, or a newer type. Count it when it carries
|
|
225
|
+
// text; otherwise charge the unknown-block figure rather than tokenize
|
|
226
|
+
// it, since it may hold a `data:` uri.
|
|
227
|
+
const text = part.text;
|
|
228
|
+
return isString(text) ? countTokens(text) : UNKNOWN_CONTENT_TOKENS;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Prompt tokens a turn will send, for reservation purposes. Deliberately a
|
|
234
|
+
* rough count: a reservation is a worst case, and a per-provider exact count
|
|
235
|
+
* would cost a tokenizer round-trip per turn to refine a number that only has
|
|
236
|
+
* to be an upper-ish bound.
|
|
237
|
+
*
|
|
238
|
+
* Text is tokenized. Media blocks get a fixed per-modality figure, so an
|
|
239
|
+
* inlined `data:` uri is never fed to the tokenizer.
|
|
240
|
+
*/
|
|
241
|
+
export function estimatePromptTokens(messages) {
|
|
242
|
+
let total = 0;
|
|
243
|
+
for (const message of messages) {
|
|
244
|
+
const content = message.content;
|
|
245
|
+
if (isString(content)) {
|
|
246
|
+
total += countTokens(content);
|
|
247
|
+
}
|
|
248
|
+
else if (Array.isArray(content)) {
|
|
249
|
+
for (const part of content) {
|
|
250
|
+
total += estimateContentPartTokens(part);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
else if (content != null) {
|
|
254
|
+
total += countTokens(JSON.stringify(content));
|
|
255
|
+
}
|
|
256
|
+
if (message.toolCalls?.length) {
|
|
257
|
+
total += countTokens(JSON.stringify(message.toolCalls));
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return total;
|
|
261
|
+
}
|
|
50
262
|
export class BaseProvider {
|
|
51
263
|
provider;
|
|
52
264
|
_cost = 0;
|
|
265
|
+
_usageTotals = {
|
|
266
|
+
inputTokens: 0,
|
|
267
|
+
outputTokens: 0,
|
|
268
|
+
cachedTokens: 0
|
|
269
|
+
};
|
|
270
|
+
_unpricedReason = null;
|
|
271
|
+
_lastCallTokens = null;
|
|
53
272
|
_emitMessage = null;
|
|
273
|
+
/**
|
|
274
|
+
* Whether this provider has a built-in, server-side web search it runs
|
|
275
|
+
* itself (e.g. Anthropic's `web_search_20250305` server tool, the Claude
|
|
276
|
+
* Agent SDK's `WebSearch`). When `true`, a tool named
|
|
277
|
+
* {@link WEB_SEARCH_TOOL_NAME} is fulfilled by the provider natively rather
|
|
278
|
+
* than the SerpAPI-backed `WebSearchTool`. Default `false` (use the fallback).
|
|
279
|
+
*/
|
|
280
|
+
get supportsNativeWebSearch() {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Whether this provider has a built-in, server-side image generator (e.g.
|
|
285
|
+
* the OpenAI Responses `image_generation` tool). When `true`, a tool named
|
|
286
|
+
* {@link IMAGE_GENERATION_TOOL_NAME} is rendered as the provider's native
|
|
287
|
+
* tool — its result arrives as image content on the assistant message, not
|
|
288
|
+
* as a function-call round-trip. Default `false` (use the fallback function
|
|
289
|
+
* tool).
|
|
290
|
+
*/
|
|
291
|
+
get supportsNativeImageGeneration() {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Whether this provider's chat API accepts a `video` content part. Only
|
|
296
|
+
* providers that read a whole clip natively answer `true` — an
|
|
297
|
+
* OpenAI-compatible endpoint re-hosting a video-capable model (AtlasCloud
|
|
298
|
+
* serving `google/gemini-2.5-flash`, say) does not, because the wire format
|
|
299
|
+
* has nowhere to put the bytes. Default `false`.
|
|
300
|
+
*/
|
|
301
|
+
get supportsVideoInput() {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
54
304
|
setMessageEmitter(fn) {
|
|
55
305
|
this._emitMessage = fn;
|
|
56
306
|
}
|
|
@@ -58,29 +308,300 @@ export class BaseProvider {
|
|
|
58
308
|
if (this._emitMessage)
|
|
59
309
|
this._emitMessage(msg);
|
|
60
310
|
}
|
|
311
|
+
/**
|
|
312
|
+
* Record a failed provider call: a server-side log with the exact request,
|
|
313
|
+
* and a `provider_call_failed` message so the surface showing the failure
|
|
314
|
+
* can report it without the user copying a status code out of a toast.
|
|
315
|
+
*
|
|
316
|
+
* One call, so the log and the report can never disagree about what failed.
|
|
317
|
+
* Public because the modality generator wrapper below is a free function.
|
|
318
|
+
* @internal
|
|
319
|
+
*/
|
|
320
|
+
recordCallFailure(input) {
|
|
321
|
+
logProviderRequestFailure({
|
|
322
|
+
provider: this.provider,
|
|
323
|
+
model: input.model ?? "unknown",
|
|
324
|
+
request: input.request,
|
|
325
|
+
nodetoolArgs: input.nodetoolArgs,
|
|
326
|
+
error: input.error
|
|
327
|
+
});
|
|
328
|
+
if (!this._emitMessage)
|
|
329
|
+
return;
|
|
330
|
+
const failure = buildProviderCallFailure({
|
|
331
|
+
...input,
|
|
332
|
+
provider: this.provider
|
|
333
|
+
});
|
|
334
|
+
if (failure)
|
|
335
|
+
this.emitMessage(failure);
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Record the exact payload this provider is about to send to the upstream
|
|
339
|
+
* API. Call this right before the network request in `generateMessage` /
|
|
340
|
+
* `generateMessages`. On failure, the traced wrappers log precisely what was
|
|
341
|
+
* sent (secrets redacted, large fields truncated). Safe to call even outside
|
|
342
|
+
* a traced call — it is a no-op when no capture slot is active.
|
|
343
|
+
*/
|
|
344
|
+
// HOLDOUT (anti-slop/no-unknown-parameters): see `setLastRequest` — the
|
|
345
|
+
// honest type is `object | null`, which `no-object-parameters` forbids.
|
|
346
|
+
recordRequestPayload(payload) {
|
|
347
|
+
setLastRequest(payload);
|
|
348
|
+
}
|
|
61
349
|
constructor(provider) {
|
|
62
350
|
this.provider = provider;
|
|
351
|
+
this.installModalityFailureLogging();
|
|
352
|
+
this.installVideoFrameFallback();
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Chat already records what it sent and logs it on failure through
|
|
356
|
+
* {@link generateMessageTraced} / {@link generateMessagesTraced}. The
|
|
357
|
+
* non-chat modalities (image/video/audio/3D/embedding) are called directly,
|
|
358
|
+
* so wrap every one a concrete provider actually overrides with the same
|
|
359
|
+
* central failure logging.
|
|
360
|
+
*
|
|
361
|
+
* We replace methods on the instance (not the prototype), and only the ones
|
|
362
|
+
* that differ from the base — so an unsupported modality keeps its base
|
|
363
|
+
* "does not support" throw and never logs a spurious failure. Providers may
|
|
364
|
+
* additionally call {@link recordRequestPayload} to log the exact wire body;
|
|
365
|
+
* otherwise the call arguments are logged (binary buffers reduced to size
|
|
366
|
+
* markers, secrets redacted).
|
|
367
|
+
*/
|
|
368
|
+
installModalityFailureLogging() {
|
|
369
|
+
for (const name of MODALITY_PROMISE_METHODS) {
|
|
370
|
+
const fn = this[name];
|
|
371
|
+
if (!isCallable(fn) || fn === BaseProvider.prototype[name]) {
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
// SAFETY: `name` comes from MODALITY_PROMISE_METHODS and the check above
|
|
375
|
+
// proved this instance overrides it, so `fn` is that modality method.
|
|
376
|
+
const original = fn;
|
|
377
|
+
Reflect.set(this, name, (...rawArgs) => withModalityCapture(async (alreadyActive) => {
|
|
378
|
+
// Central entity expansion: descriptors into the prompt, reference
|
|
379
|
+
// images onto the source list, before the concrete provider runs.
|
|
380
|
+
const args = applyEntityReferences(name, rawArgs);
|
|
381
|
+
const startedAt = Date.now();
|
|
382
|
+
try {
|
|
383
|
+
return await original.apply(this, args);
|
|
384
|
+
}
|
|
385
|
+
catch (err) {
|
|
386
|
+
annotateProviderError(err, {
|
|
387
|
+
provider: this.provider,
|
|
388
|
+
model: extractModelId(args)
|
|
389
|
+
});
|
|
390
|
+
if (!alreadyActive) {
|
|
391
|
+
const failureArgs = name === "referenceToVideo"
|
|
392
|
+
? [referenceVideoDiagnostics(args)]
|
|
393
|
+
: args;
|
|
394
|
+
this.recordCallFailure({
|
|
395
|
+
model: extractModelId(args),
|
|
396
|
+
operation: name,
|
|
397
|
+
request: name === "referenceToVideo" ? undefined : peekLastRequest(),
|
|
398
|
+
nodetoolArgs: failureArgs,
|
|
399
|
+
error: err,
|
|
400
|
+
startedAt
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
throw err;
|
|
404
|
+
}
|
|
405
|
+
}));
|
|
406
|
+
}
|
|
407
|
+
for (const name of MODALITY_GENERATOR_METHODS) {
|
|
408
|
+
const fn = this[name];
|
|
409
|
+
if (!isCallable(fn) || fn === BaseProvider.prototype[name]) {
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
const original = fn;
|
|
413
|
+
Reflect.set(this, name, (...args) => wrapModalityGenerator(this, original, args, name));
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Make a `video` content part readable by a provider whose chat API has no
|
|
418
|
+
* place to put one: sample the clip into stills and send those instead.
|
|
419
|
+
*
|
|
420
|
+
* Wrapping the instance's own methods (the pattern
|
|
421
|
+
* {@link installModalityFailureLogging} uses) is what makes it transparent.
|
|
422
|
+
* Not every caller goes through the traced wrappers — `packages/llm-nodes`
|
|
423
|
+
* and the jtbd optimizer call `generateMessages` straight — and a seam only
|
|
424
|
+
* some callers pass through is not a fallback, it is a coin flip.
|
|
425
|
+
*
|
|
426
|
+
* A provider that reads video natively is left alone, and so is one whose
|
|
427
|
+
* `generateMessage` delegates to its own `generateMessages`: by then the
|
|
428
|
+
* video parts are already frames, and the second pass is a no-op.
|
|
429
|
+
*/
|
|
430
|
+
installVideoFrameFallback() {
|
|
431
|
+
// An arrow, so the `async function*` below (which binds its own `this`)
|
|
432
|
+
// still reaches this instance.
|
|
433
|
+
const sample = (args) => this.sampleVideoAsFrames(args);
|
|
434
|
+
// `generateMessage` and `generateMessages` are abstract, so a subclass that
|
|
435
|
+
// implements only one leaves the other undefined at construction — wrap
|
|
436
|
+
// what is there, the way installModalityFailureLogging does.
|
|
437
|
+
const message = this.generateMessage;
|
|
438
|
+
if (isCallable(message)) {
|
|
439
|
+
const original = message.bind(this);
|
|
440
|
+
Reflect.set(this, "generateMessage", async (args) => original({ ...args, messages: await sample(args) }));
|
|
441
|
+
}
|
|
442
|
+
const messages = this.generateMessages;
|
|
443
|
+
if (isCallable(messages)) {
|
|
444
|
+
const original = messages.bind(this);
|
|
445
|
+
Reflect.set(this, "generateMessages", async function* (args) {
|
|
446
|
+
yield* original({
|
|
447
|
+
...args,
|
|
448
|
+
messages: await sample(args)
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* The messages to actually send: unchanged unless this provider cannot take
|
|
455
|
+
* video and the conversation carries some.
|
|
456
|
+
*/
|
|
457
|
+
async sampleVideoAsFrames(args) {
|
|
458
|
+
if (this.supportsVideoInput || !videoFrameFallbackEnabled()) {
|
|
459
|
+
return args.messages;
|
|
460
|
+
}
|
|
461
|
+
// Returns the same array when nothing carries video, which is every call
|
|
462
|
+
// but the rare one.
|
|
463
|
+
return await expandVideoContentAsFrames(args.messages, {
|
|
464
|
+
resolveUri: (uri) => this.resolveUri(uri),
|
|
465
|
+
signal: args.signal,
|
|
466
|
+
provider: this.provider
|
|
467
|
+
});
|
|
63
468
|
}
|
|
64
469
|
static requiredSecrets() {
|
|
65
470
|
return [];
|
|
66
471
|
}
|
|
472
|
+
/**
|
|
473
|
+
* Why this provider cannot serve a call right now, or `null` when it can.
|
|
474
|
+
*
|
|
475
|
+
* The credential check (`isProviderConfigured`) runs before a provider is
|
|
476
|
+
* ever constructed, so a provider that reaches this point has its keys. This
|
|
477
|
+
* answers what a key cannot: an optional SDK nobody installed, a local
|
|
478
|
+
* runtime the host lacks. `find_model` and `list_models` read it, so a model
|
|
479
|
+
* nothing here can run is not offered as the best match — the failure lands
|
|
480
|
+
* on the discovery call instead of on the paid generation call.
|
|
481
|
+
*/
|
|
482
|
+
async unavailableReason() {
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Explicit capability declaration. Returns `null` by default, in which case
|
|
487
|
+
* {@link getCapabilities} derives capabilities by reflecting on which
|
|
488
|
+
* optional methods the concrete class overrides. Providers may override this
|
|
489
|
+
* to declare their capabilities directly — an alternative to method
|
|
490
|
+
* reflection when methods are wrapped, bound, or composed via mixins.
|
|
491
|
+
*/
|
|
492
|
+
declaredCapabilities() {
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* The capabilities this provider exposes. Prefers an explicit declaration
|
|
497
|
+
* from {@link declaredCapabilities}; otherwise falls back to reflecting over
|
|
498
|
+
* overridden methods via {@link providerCapabilities}. Callers should use
|
|
499
|
+
* this instead of calling `providerCapabilities()` directly so explicit
|
|
500
|
+
* declarations are honored.
|
|
501
|
+
*/
|
|
502
|
+
getCapabilities() {
|
|
503
|
+
const declared = this.declaredCapabilities();
|
|
504
|
+
if (declared) {
|
|
505
|
+
// Message generation is always available; ensure it is present.
|
|
506
|
+
const set = new Set(declared);
|
|
507
|
+
set.add("generate_message");
|
|
508
|
+
set.add("generate_messages");
|
|
509
|
+
return [...set];
|
|
510
|
+
}
|
|
511
|
+
return providerCapabilities(this);
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* The provider's own record of the generations this account ran, newest
|
|
515
|
+
* first. Only providers with a request-history API override this; the base
|
|
516
|
+
* throws {@link ProviderGenerationsUnsupportedError} so a caller can tell
|
|
517
|
+
* "this provider cannot answer" from "this provider ran nothing".
|
|
518
|
+
*
|
|
519
|
+
* This is the provider's truth, not NodeTool's: it covers generations made
|
|
520
|
+
* outside this installation and carries the billed cost rather than the
|
|
521
|
+
* estimate the local row holds until reconciliation.
|
|
522
|
+
*/
|
|
523
|
+
async listGenerations(_query = {}) {
|
|
524
|
+
throw new ProviderGenerationsUnsupportedError(this.provider, "list");
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* One generation at the provider, by the provider's own request id — the id
|
|
528
|
+
* the local row keeps as `provider_request_id`. Returns null when the
|
|
529
|
+
* provider answers that it has no such request. Throws
|
|
530
|
+
* {@link ProviderGenerationsUnsupportedError} when the provider has no
|
|
531
|
+
* lookup API at all.
|
|
532
|
+
*/
|
|
533
|
+
async getGeneration(_requestId, _options = {}) {
|
|
534
|
+
throw new ProviderGenerationsUnsupportedError(this.provider, "get");
|
|
535
|
+
}
|
|
67
536
|
getContainerEnv() {
|
|
68
537
|
return {};
|
|
69
538
|
}
|
|
539
|
+
/**
|
|
540
|
+
* Release any resources held by this provider (HTTP connection pools, cached
|
|
541
|
+
* model metadata, child processes, etc.). Default is a no-op; providers that
|
|
542
|
+
* hold long-lived resources should override it. Safe to call more than once.
|
|
543
|
+
* Callers that own a provider instance should invoke this on shutdown.
|
|
544
|
+
*/
|
|
545
|
+
async close() { }
|
|
70
546
|
async hasToolSupport(_model) {
|
|
71
547
|
return true;
|
|
72
548
|
}
|
|
549
|
+
/**
|
|
550
|
+
* The input window, in tokens, this provider's own API reports for `model`,
|
|
551
|
+
* or null when it has no such endpoint. Callers go through
|
|
552
|
+
* `resolveContextWindow`, which falls back to models.dev.
|
|
553
|
+
*/
|
|
554
|
+
async getContextWindow(_model) {
|
|
555
|
+
return null;
|
|
556
|
+
}
|
|
73
557
|
trackUsage(model, usage) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
558
|
+
// Accounting must never break a generation that already ran (and was
|
|
559
|
+
// already billed by the provider): keep the token counts and record the
|
|
560
|
+
// call as *unpriced*. A zero here would be written to the ledger as a real
|
|
561
|
+
// zero, which reads as "this call was free" — the one thing it is not.
|
|
562
|
+
let cost;
|
|
563
|
+
try {
|
|
564
|
+
cost = CostCalculator.calculate(model, usage, this.provider);
|
|
565
|
+
}
|
|
566
|
+
catch (error) {
|
|
567
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
568
|
+
this._unpricedReason = `No unit price for ${this.provider}/${model}: ${reason}`;
|
|
569
|
+
log.warn("Cost calculation failed; recording usage as unpriced", {
|
|
570
|
+
model,
|
|
571
|
+
provider: this.provider,
|
|
572
|
+
error: reason
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
this._cost += cost ?? 0;
|
|
576
|
+
this._usageTotals.inputTokens += usage.inputTokens ?? 0;
|
|
577
|
+
this._usageTotals.outputTokens += usage.outputTokens ?? 0;
|
|
578
|
+
this._usageTotals.cachedTokens += usage.cachedTokens ?? 0;
|
|
579
|
+
if (usage.inputTokens !== undefined) {
|
|
580
|
+
this._lastCallTokens = usage.inputTokens + (usage.outputTokens ?? 0);
|
|
581
|
+
}
|
|
582
|
+
const tracked = {
|
|
77
583
|
inputTokens: usage.inputTokens ?? 0,
|
|
78
584
|
outputTokens: usage.outputTokens ?? 0,
|
|
79
585
|
cachedInputTokens: usage.cachedTokens,
|
|
80
|
-
|
|
81
|
-
}
|
|
586
|
+
cacheWriteTokens: usage.cacheWriteTokens
|
|
587
|
+
};
|
|
588
|
+
// Left unset rather than zero: `setLastUsage` charges the enclosing node
|
|
589
|
+
// invocation with it, and an invented zero is spend nobody can audit.
|
|
590
|
+
if (cost !== undefined) {
|
|
591
|
+
tracked.cost = cost;
|
|
592
|
+
}
|
|
593
|
+
setLastUsage(tracked);
|
|
82
594
|
log.debug("Cost tracked", { model, cost, total: this._cost });
|
|
83
|
-
return cost;
|
|
595
|
+
return cost ?? 0;
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Why this provider's cost total is missing spend, when a price lookup
|
|
599
|
+
* failed. Null when every tracked call was priced. A host writing a ledger
|
|
600
|
+
* row reads it to record the row as unpriced (`cost: null`) instead of
|
|
601
|
+
* booking an unpriced call as free.
|
|
602
|
+
*/
|
|
603
|
+
get unpricedReason() {
|
|
604
|
+
return this._unpricedReason;
|
|
84
605
|
}
|
|
85
606
|
/** Alias for getTotalCost() — mirrors Python's BaseProvider.cost property. */
|
|
86
607
|
get cost() {
|
|
@@ -89,6 +610,20 @@ export class BaseProvider {
|
|
|
89
610
|
getTotalCost() {
|
|
90
611
|
return this._cost;
|
|
91
612
|
}
|
|
613
|
+
/**
|
|
614
|
+
* Input (cache reads and writes included) plus output tokens of the most
|
|
615
|
+
* recent tracked call, as the provider reported them. The prompt of the next
|
|
616
|
+
* call on the same transcript is at least this large, and unlike a local
|
|
617
|
+
* estimate it counts the tool definitions and system prompt too. Null until
|
|
618
|
+
* a call reports usage.
|
|
619
|
+
*/
|
|
620
|
+
get lastCallTokens() {
|
|
621
|
+
return this._lastCallTokens;
|
|
622
|
+
}
|
|
623
|
+
/** Running token counts across every call tracked on this instance. */
|
|
624
|
+
get usageTotals() {
|
|
625
|
+
return { ...this._usageTotals };
|
|
626
|
+
}
|
|
92
627
|
/**
|
|
93
628
|
* Log a provider call for cost tracking.
|
|
94
629
|
* Base implementation logs to debug; subclasses or callers may persist to a Prediction model.
|
|
@@ -104,6 +639,9 @@ export class BaseProvider {
|
|
|
104
639
|
}
|
|
105
640
|
resetCost() {
|
|
106
641
|
this._cost = 0;
|
|
642
|
+
this._unpricedReason = null;
|
|
643
|
+
this._usageTotals = { inputTokens: 0, outputTokens: 0, cachedTokens: 0 };
|
|
644
|
+
this._lastCallTokens = null;
|
|
107
645
|
}
|
|
108
646
|
async getAvailableLanguageModels() {
|
|
109
647
|
return [];
|
|
@@ -120,6 +658,24 @@ export class BaseProvider {
|
|
|
120
658
|
async getAvailableASRModels() {
|
|
121
659
|
return [];
|
|
122
660
|
}
|
|
661
|
+
/**
|
|
662
|
+
* Music **generation** models exposed by this provider (e.g. MusicGen,
|
|
663
|
+
* Stable Audio, Suno, MiniMax Music). Override on providers that can
|
|
664
|
+
* synthesize music from a text prompt; the base returns none so the
|
|
665
|
+
* `text_to_music` capability is advertised only when overridden.
|
|
666
|
+
*/
|
|
667
|
+
async getAvailableMusicModels() {
|
|
668
|
+
return [];
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Audio **transform** models exposed by this provider (voice changers, stem
|
|
672
|
+
* separators, denoisers, audio super-resolution). Override on providers that
|
|
673
|
+
* can rewrite a recording; the base returns none so the `audio_to_audio`
|
|
674
|
+
* capability is advertised only when overridden.
|
|
675
|
+
*/
|
|
676
|
+
async getAvailableAudioToAudioModels() {
|
|
677
|
+
return [];
|
|
678
|
+
}
|
|
123
679
|
async getAvailableEmbeddingModels() {
|
|
124
680
|
return [];
|
|
125
681
|
}
|
|
@@ -142,7 +698,7 @@ export class BaseProvider {
|
|
|
142
698
|
this.applyLlmRequestAttributes(span, args, false);
|
|
143
699
|
try {
|
|
144
700
|
const result = await this.generateMessage(args);
|
|
145
|
-
const content =
|
|
701
|
+
const content = isString(result.content)
|
|
146
702
|
? result.content
|
|
147
703
|
: JSON.stringify(result.content);
|
|
148
704
|
span.setAttributes({
|
|
@@ -170,18 +726,45 @@ export class BaseProvider {
|
|
|
170
726
|
let result;
|
|
171
727
|
let error;
|
|
172
728
|
let usage = null;
|
|
729
|
+
let requestPayload = null;
|
|
730
|
+
const startedAt = Date.now();
|
|
173
731
|
try {
|
|
174
732
|
// withUsageCapture creates a per-call AsyncLocalStorage slot so
|
|
175
733
|
// trackUsage() in the provider hands its numbers back to us.
|
|
176
734
|
result = await withUsageCapture(async () => {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
735
|
+
try {
|
|
736
|
+
const r = await doCall();
|
|
737
|
+
usage = consumeLastUsage();
|
|
738
|
+
return r;
|
|
739
|
+
}
|
|
740
|
+
finally {
|
|
741
|
+
// Read the recorded wire payload while still inside the capture slot.
|
|
742
|
+
requestPayload = peekLastRequest();
|
|
743
|
+
}
|
|
180
744
|
});
|
|
181
745
|
return result;
|
|
182
746
|
}
|
|
183
747
|
catch (err) {
|
|
748
|
+
annotateProviderError(err, {
|
|
749
|
+
provider: this.provider,
|
|
750
|
+
model: args.model
|
|
751
|
+
});
|
|
752
|
+
if (this.isContextExceededError(err)) {
|
|
753
|
+
markContextExceeded(err, this.provider);
|
|
754
|
+
}
|
|
184
755
|
error = String(err);
|
|
756
|
+
this.recordCallFailure({
|
|
757
|
+
model: args.model,
|
|
758
|
+
operation: "generateMessage",
|
|
759
|
+
request: requestPayload,
|
|
760
|
+
nodetoolArgs: {
|
|
761
|
+
model: args.model,
|
|
762
|
+
messages: args.messages,
|
|
763
|
+
tools: args.tools
|
|
764
|
+
},
|
|
765
|
+
error: err,
|
|
766
|
+
startedAt
|
|
767
|
+
});
|
|
185
768
|
throw err;
|
|
186
769
|
}
|
|
187
770
|
finally {
|
|
@@ -245,12 +828,13 @@ export class BaseProvider {
|
|
|
245
828
|
let error;
|
|
246
829
|
// Per-call usage slot survives across the generator's yields by wrapping
|
|
247
830
|
// each `next()` call in `runInSlot`.
|
|
248
|
-
const { runInSlot, getUsage } = createUsageSlot();
|
|
831
|
+
const { runInSlot, getUsage, getRequest } = createUsageSlot();
|
|
249
832
|
const tracer = getTracer();
|
|
250
833
|
const source = tracer
|
|
251
834
|
? this._tracedStream(args, tracer)
|
|
252
835
|
: this.generateMessages(args);
|
|
253
836
|
let exhausted = false;
|
|
837
|
+
const startedAt = Date.now();
|
|
254
838
|
try {
|
|
255
839
|
while (true) {
|
|
256
840
|
const result = await runInSlot(() => source.next());
|
|
@@ -280,7 +864,26 @@ export class BaseProvider {
|
|
|
280
864
|
});
|
|
281
865
|
}
|
|
282
866
|
catch (err) {
|
|
867
|
+
annotateProviderError(err, {
|
|
868
|
+
provider: this.provider,
|
|
869
|
+
model: args.model
|
|
870
|
+
});
|
|
871
|
+
if (this.isContextExceededError(err)) {
|
|
872
|
+
markContextExceeded(err, this.provider);
|
|
873
|
+
}
|
|
283
874
|
error = String(err);
|
|
875
|
+
this.recordCallFailure({
|
|
876
|
+
model: args.model,
|
|
877
|
+
operation: "generateMessages",
|
|
878
|
+
request: getRequest(),
|
|
879
|
+
nodetoolArgs: {
|
|
880
|
+
model: args.model,
|
|
881
|
+
messages: args.messages,
|
|
882
|
+
tools: args.tools
|
|
883
|
+
},
|
|
884
|
+
error: err,
|
|
885
|
+
startedAt
|
|
886
|
+
});
|
|
284
887
|
throw err;
|
|
285
888
|
}
|
|
286
889
|
finally {
|
|
@@ -288,7 +891,11 @@ export class BaseProvider {
|
|
|
288
891
|
// called return()), give the underlying generator a chance to close
|
|
289
892
|
// so _tracedStream's finally runs and the LLM stream span ends.
|
|
290
893
|
if (!exhausted) {
|
|
291
|
-
await runInSlot(() => source.return?.(undefined) ??
|
|
894
|
+
await runInSlot(() => source.return?.(undefined) ??
|
|
895
|
+
Promise.resolve({
|
|
896
|
+
done: true,
|
|
897
|
+
value: undefined
|
|
898
|
+
})).catch(() => { });
|
|
292
899
|
}
|
|
293
900
|
const usage = getUsage();
|
|
294
901
|
this.emitMessage({
|
|
@@ -311,6 +918,309 @@ export class BaseProvider {
|
|
|
311
918
|
});
|
|
312
919
|
}
|
|
313
920
|
}
|
|
921
|
+
/**
|
|
922
|
+
* Run the agentic tool-calling loop and stream its events. This is the entry
|
|
923
|
+
* point an agent harness calls; the harness no longer implements the loop
|
|
924
|
+
* itself. The default implementation drives the standard external loop:
|
|
925
|
+
* stream one {@link generateMessages} turn, collect any tool calls, run them
|
|
926
|
+
* via the harness-supplied {@link executeTool}, append the results, and repeat
|
|
927
|
+
* until the model stops calling tools (or `maxIterations` is hit).
|
|
928
|
+
*
|
|
929
|
+
* It yields the same {@link Chunk}/{@link ToolCall}/{@link ProviderSessionUpdate}
|
|
930
|
+
* items a single turn does — for live display — plus a
|
|
931
|
+
* {@link ProviderMessageEvent} for every finalized message (assistant turns
|
|
932
|
+
* and tool results) so the harness can persist/collect them without
|
|
933
|
+
* reassembling them from chunks.
|
|
934
|
+
*
|
|
935
|
+
* Providers whose backend runs its own agent loop (e.g. the Claude Agent SDK)
|
|
936
|
+
* override this to drive that loop instead, while still emitting the same
|
|
937
|
+
* stream items. `generateMessages` remains the single-turn primitive.
|
|
938
|
+
*/
|
|
939
|
+
/**
|
|
940
|
+
* Ask a {@link TurnBudget} to admit the turn that is about to be made.
|
|
941
|
+
* Exposed to subclasses because a provider driving a backend's own agent
|
|
942
|
+
* loop has to make the same call at the same point.
|
|
943
|
+
*
|
|
944
|
+
* Answers the reservation to hand back to `commit` after the turn, or `null`
|
|
945
|
+
* when the budget refused. The handle belongs to this turn alone: another
|
|
946
|
+
* loop sharing the budget settles its own.
|
|
947
|
+
*/
|
|
948
|
+
_admitTurn(budget, model, messages) {
|
|
949
|
+
return budget.reserve({
|
|
950
|
+
model,
|
|
951
|
+
provider: this.provider,
|
|
952
|
+
inputTokens: estimatePromptTokens(messages)
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
async *generateLoop(args) {
|
|
956
|
+
const maxIterations = args.maxIterations ?? 25;
|
|
957
|
+
const { executeTool, maxIterations: _omitMax, sequentialTools, turnBudget: budgetArg, workspaceDir: _omitWorkspaceDir, skills: _omitSkills, resolveMedia, ...turnArgs } = args;
|
|
958
|
+
const { runBudget, turnBudget } = resolveTurnBudget(budgetArg);
|
|
959
|
+
const messages = [...args.messages];
|
|
960
|
+
// What the provider is actually sent: `messages` with every media
|
|
961
|
+
// reference dereferenced. Kept beside `messages` rather than replacing it,
|
|
962
|
+
// so the budget still reserves against the references (what the loop
|
|
963
|
+
// carries) and the tail is resolved once instead of once per round.
|
|
964
|
+
const sent = [];
|
|
965
|
+
let resolvedUpTo = 0;
|
|
966
|
+
const resolvedMessages = async () => {
|
|
967
|
+
if (!resolveMedia)
|
|
968
|
+
return messages;
|
|
969
|
+
if (resolvedUpTo < messages.length) {
|
|
970
|
+
sent.push(...(await resolveMedia(messages.slice(resolvedUpTo))));
|
|
971
|
+
resolvedUpTo = messages.length;
|
|
972
|
+
}
|
|
973
|
+
return sent;
|
|
974
|
+
};
|
|
975
|
+
// Tools may carry their own `execute` (provider-dispatched) and/or a
|
|
976
|
+
// `terminal` flag (end the loop after they run). Index by name so the
|
|
977
|
+
// tool-execution section can look up the matching ProviderTool per call.
|
|
978
|
+
const toolMap = new Map((args.tools ?? []).map((t) => [t.name, t]));
|
|
979
|
+
// Bridge to the legacy inline tool callback for any provider that executes
|
|
980
|
+
// tools mid-stream instead of yielding ToolCall items. Real providers yield
|
|
981
|
+
// ToolCall (handled below); this keeps the inline path working too. That
|
|
982
|
+
// channel is string-only, so image results collapse to their text.
|
|
983
|
+
let toolCallSeq = 0;
|
|
984
|
+
const onToolCall = executeTool
|
|
985
|
+
? async (name, toolArgs) => {
|
|
986
|
+
const result = await executeTool({
|
|
987
|
+
id: `call_${++toolCallSeq}`,
|
|
988
|
+
name,
|
|
989
|
+
args: toolArgs
|
|
990
|
+
});
|
|
991
|
+
if (isProviderToolErrorResult(result)) {
|
|
992
|
+
return toolResultToText(result.content);
|
|
993
|
+
}
|
|
994
|
+
return isString(result) ? result : toolResultToText(result);
|
|
995
|
+
}
|
|
996
|
+
: turnArgs.onToolCall;
|
|
997
|
+
for (let iteration = 0; iteration < maxIterations; iteration++) {
|
|
998
|
+
if (turnArgs.signal?.aborted) {
|
|
999
|
+
yield PROVIDER_STOP_ABORTED;
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
if (runBudget?.deadline.expired()) {
|
|
1003
|
+
yield budgetStopItem(runBudget);
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
let reservation = null;
|
|
1007
|
+
if (turnBudget) {
|
|
1008
|
+
reservation = this._admitTurn(turnBudget, args.model, messages);
|
|
1009
|
+
if (reservation === null) {
|
|
1010
|
+
yield budgetStopItem(runBudget);
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
const costBeforeTurn = turnBudget ? this.getTotalCost() : 0;
|
|
1015
|
+
let assistantText = "";
|
|
1016
|
+
let finalizedAssistant;
|
|
1017
|
+
const pending = [];
|
|
1018
|
+
// The round's `done: true` chunk, withheld until we know if it ends the
|
|
1019
|
+
// turn. Discarded when tools are pending — the next round supplies its own.
|
|
1020
|
+
let terminalChunk;
|
|
1021
|
+
// The reservation covers exactly one turn, so it is reconciled in a
|
|
1022
|
+
// `finally`: a turn that throws must release it. Left outstanding, a
|
|
1023
|
+
// single provider error would shrink the budget's headroom for good —
|
|
1024
|
+
// it is run-level and outlives the decision that hit the error.
|
|
1025
|
+
try {
|
|
1026
|
+
for await (const item of this.generateMessagesTraced({
|
|
1027
|
+
...turnArgs,
|
|
1028
|
+
messages: await resolvedMessages(),
|
|
1029
|
+
onToolCall
|
|
1030
|
+
})) {
|
|
1031
|
+
if (turnArgs.signal?.aborted)
|
|
1032
|
+
break;
|
|
1033
|
+
if (isProviderMessageEvent(item)) {
|
|
1034
|
+
if (item.message.role === "assistant") {
|
|
1035
|
+
finalizedAssistant = item.message;
|
|
1036
|
+
}
|
|
1037
|
+
yield item;
|
|
1038
|
+
continue;
|
|
1039
|
+
}
|
|
1040
|
+
if (isProviderSessionUpdate(item)) {
|
|
1041
|
+
yield item;
|
|
1042
|
+
continue;
|
|
1043
|
+
}
|
|
1044
|
+
if ("id" in item && "name" in item && "args" in item) {
|
|
1045
|
+
pending.push(item);
|
|
1046
|
+
yield item;
|
|
1047
|
+
continue;
|
|
1048
|
+
}
|
|
1049
|
+
if (isProviderStop(item)) {
|
|
1050
|
+
// Only this loop ends a loop. A single turn never yields a stop,
|
|
1051
|
+
// so one arriving from `generateMessages` is not the loop's stop
|
|
1052
|
+
// and must not be forwarded as if it were.
|
|
1053
|
+
continue;
|
|
1054
|
+
}
|
|
1055
|
+
const chunk = item;
|
|
1056
|
+
// Only text belongs in the assistant message. Audio/image chunks carry
|
|
1057
|
+
// base64 in the same `content` field; concatenating those produced
|
|
1058
|
+
// assistant messages full of binary garbage.
|
|
1059
|
+
const isTextChunk = chunk.content_type === undefined || chunk.content_type === "text";
|
|
1060
|
+
if (isString(chunk.content) &&
|
|
1061
|
+
!chunk.thinking &&
|
|
1062
|
+
isTextChunk) {
|
|
1063
|
+
assistantText += chunk.content;
|
|
1064
|
+
}
|
|
1065
|
+
// `done: true` closes the provider's *completion*, which is not the same
|
|
1066
|
+
// as the end of the turn — every provider emits one per tool-calling
|
|
1067
|
+
// round. Forwarding them made consumers (the chat composer's Stop
|
|
1068
|
+
// button) treat each round as the end of the turn. Hold the terminal
|
|
1069
|
+
// chunk until we know whether tools are pending; release it below only
|
|
1070
|
+
// when this round really is the last one.
|
|
1071
|
+
if (chunk.done === true) {
|
|
1072
|
+
terminalChunk = item;
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
yield item;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
finally {
|
|
1079
|
+
// Replace the reservation with what the turn really cost. A zero delta
|
|
1080
|
+
// here is a real zero, not an unknown: `trackUsage` fires on every
|
|
1081
|
+
// completed call, so nothing recorded means nothing was billed. (The
|
|
1082
|
+
// Claude Agent SDK path cannot say that, which is why it commits
|
|
1083
|
+
// `null` instead — see its override.)
|
|
1084
|
+
if (turnBudget && reservation) {
|
|
1085
|
+
turnBudget.commit(reservation, this.getTotalCost() - costBeforeTurn);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
if (pending.length === 0) {
|
|
1089
|
+
if (terminalChunk !== undefined) {
|
|
1090
|
+
yield terminalChunk;
|
|
1091
|
+
}
|
|
1092
|
+
if (finalizedAssistant) {
|
|
1093
|
+
messages.push(finalizedAssistant);
|
|
1094
|
+
}
|
|
1095
|
+
else {
|
|
1096
|
+
const assistantMsg = {
|
|
1097
|
+
role: "assistant",
|
|
1098
|
+
content: assistantText || null
|
|
1099
|
+
};
|
|
1100
|
+
messages.push(assistantMsg);
|
|
1101
|
+
yield { type: "message", message: assistantMsg };
|
|
1102
|
+
}
|
|
1103
|
+
// An abort mid-stream cut the turn short; the model did not end it.
|
|
1104
|
+
if (turnArgs.signal?.aborted) {
|
|
1105
|
+
yield PROVIDER_STOP_ABORTED;
|
|
1106
|
+
}
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
// The assistant turn requested tools — emit it (with the calls), run them,
|
|
1110
|
+
// then loop with the results appended.
|
|
1111
|
+
const assistantMsg = {
|
|
1112
|
+
role: "assistant",
|
|
1113
|
+
content: assistantText || null,
|
|
1114
|
+
toolCalls: pending
|
|
1115
|
+
};
|
|
1116
|
+
// Carry Gemini thought-signature parts forward so multi-turn function
|
|
1117
|
+
// calling keeps working — the loop owns the message now, so a tool call
|
|
1118
|
+
// that arrived with raw parts can't stash them on the message itself.
|
|
1119
|
+
const rawParts = pending.find((tc) => tc._rawGeminiParts)?._rawGeminiParts;
|
|
1120
|
+
if (rawParts) {
|
|
1121
|
+
assistantMsg._rawGeminiParts = rawParts;
|
|
1122
|
+
}
|
|
1123
|
+
const thinkingBlocks = pending.find((tc) => tc._anthropicThinkingBlocks)?._anthropicThinkingBlocks;
|
|
1124
|
+
if (thinkingBlocks) {
|
|
1125
|
+
assistantMsg._anthropicThinkingBlocks = thinkingBlocks;
|
|
1126
|
+
}
|
|
1127
|
+
messages.push(assistantMsg);
|
|
1128
|
+
yield { type: "message", message: assistantMsg };
|
|
1129
|
+
// Dispatch order: a tool's own `execute` (provider-driven) wins; else the
|
|
1130
|
+
// harness-supplied `executeTool` callback; else the tool is unavailable.
|
|
1131
|
+
const runTool = async (tc) => {
|
|
1132
|
+
const tool = toolMap.get(tc.name);
|
|
1133
|
+
try {
|
|
1134
|
+
if (tool?.execute)
|
|
1135
|
+
return await tool.execute(tc.args ?? {}, tc.id);
|
|
1136
|
+
if (executeTool)
|
|
1137
|
+
return await executeTool(tc);
|
|
1138
|
+
return `Tool "${tc.name}" is not available`;
|
|
1139
|
+
}
|
|
1140
|
+
catch (err) {
|
|
1141
|
+
// A throwing tool must not destroy the whole assistant turn: turn the
|
|
1142
|
+
// failure into an error result so the model still receives output for
|
|
1143
|
+
// every tool call (and sibling results are preserved). Without this,
|
|
1144
|
+
// Promise.all in the parallel path would reject before any result is
|
|
1145
|
+
// emitted, discarding the turn.
|
|
1146
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1147
|
+
return {
|
|
1148
|
+
content: `Error executing tool "${tc.name}": ${message}`,
|
|
1149
|
+
isError: true
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
1153
|
+
// A tool flagged `terminal` ends the loop once its turn's results are
|
|
1154
|
+
// emitted (e.g. a finish/submit tool).
|
|
1155
|
+
let terminated = false;
|
|
1156
|
+
const imageMessages = [];
|
|
1157
|
+
const emitToolResult = function* (tc, result) {
|
|
1158
|
+
let content;
|
|
1159
|
+
let isError = false;
|
|
1160
|
+
if (isProviderToolErrorResult(result)) {
|
|
1161
|
+
content = result.content;
|
|
1162
|
+
isError = true;
|
|
1163
|
+
}
|
|
1164
|
+
else {
|
|
1165
|
+
content = result;
|
|
1166
|
+
}
|
|
1167
|
+
const { toolContent, imageMessage } = splitToolResultImages(content);
|
|
1168
|
+
const toolMsg = {
|
|
1169
|
+
role: "tool",
|
|
1170
|
+
toolCallId: tc.id,
|
|
1171
|
+
content: toolContent
|
|
1172
|
+
};
|
|
1173
|
+
if (isError) {
|
|
1174
|
+
toolMsg.isError = true;
|
|
1175
|
+
}
|
|
1176
|
+
messages.push(toolMsg);
|
|
1177
|
+
yield { type: "message", message: toolMsg };
|
|
1178
|
+
if (imageMessage) {
|
|
1179
|
+
// The pixels ride a user message for the next model turn (most
|
|
1180
|
+
// providers can't render an image in a tool-result message). It is
|
|
1181
|
+
// in-flight only — pushed to the loop's messages but never yielded,
|
|
1182
|
+
// so it is never persisted or echoed, keeping saved history cheap.
|
|
1183
|
+
imageMessages.push(imageMessage);
|
|
1184
|
+
}
|
|
1185
|
+
};
|
|
1186
|
+
if (sequentialTools) {
|
|
1187
|
+
// One at a time so a tool can read the state a prior tool wrote, and so
|
|
1188
|
+
// a terminal tool can abort the loop mid-turn (skipping the rest).
|
|
1189
|
+
for (const tc of pending) {
|
|
1190
|
+
if (turnArgs.signal?.aborted)
|
|
1191
|
+
break;
|
|
1192
|
+
yield* emitToolResult(tc, await runTool(tc));
|
|
1193
|
+
if (toolMap.get(tc.name)?.terminal) {
|
|
1194
|
+
terminated = true;
|
|
1195
|
+
break;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
else {
|
|
1200
|
+
if (turnArgs.signal?.aborted) {
|
|
1201
|
+
yield PROVIDER_STOP_ABORTED;
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
const results = await Promise.all(pending.map(async (tc) => ({ tc, content: await runTool(tc) })));
|
|
1205
|
+
for (const { tc, content } of results) {
|
|
1206
|
+
yield* emitToolResult(tc, content);
|
|
1207
|
+
if (toolMap.get(tc.name)?.terminal)
|
|
1208
|
+
terminated = true;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
messages.push(...imageMessages);
|
|
1212
|
+
// A terminal tool ran this turn — stop after emitting its results. That
|
|
1213
|
+
// is the loop doing what it was asked, not a limit being hit, so it
|
|
1214
|
+
// carries no stop item.
|
|
1215
|
+
if (terminated)
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
yield {
|
|
1219
|
+
type: "stop",
|
|
1220
|
+
reason: "iterations",
|
|
1221
|
+
detail: `stopped after ${maxIterations} tool-calling round(s)`
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
314
1224
|
/** Internal: wrap generateMessages with OTel span */
|
|
315
1225
|
async *_tracedStream(args, tracer) {
|
|
316
1226
|
const span = tracer.startSpan(`llm.stream ${this.provider}/${args.model}`);
|
|
@@ -351,7 +1261,14 @@ export class BaseProvider {
|
|
|
351
1261
|
}
|
|
352
1262
|
return results;
|
|
353
1263
|
}
|
|
354
|
-
|
|
1264
|
+
/**
|
|
1265
|
+
* Transform one or more source images into a single output image.
|
|
1266
|
+
*
|
|
1267
|
+
* The first image is the primary subject; providers that accept multiple
|
|
1268
|
+
* reference images (e.g. multi-image editing / composition) use the rest,
|
|
1269
|
+
* while single-image providers operate on `images[0]`.
|
|
1270
|
+
*/
|
|
1271
|
+
async imageToImage(_images, _params) {
|
|
355
1272
|
throw new Error(`${this.provider} does not support imageToImage`);
|
|
356
1273
|
}
|
|
357
1274
|
/**
|
|
@@ -360,13 +1277,69 @@ export class BaseProvider {
|
|
|
360
1277
|
* Default implementation calls imageToImage() sequentially as a fallback.
|
|
361
1278
|
* Providers that support native batch generation should override this.
|
|
362
1279
|
*/
|
|
363
|
-
async imageToImages(
|
|
1280
|
+
async imageToImages(images, params, numImages) {
|
|
1281
|
+
const results = [];
|
|
1282
|
+
for (let i = 0; i < numImages; i++) {
|
|
1283
|
+
results.push(await this.imageToImage(images, params));
|
|
1284
|
+
}
|
|
1285
|
+
return results;
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Inpaint: regenerate the masked region of one or more source images
|
|
1289
|
+
* according to a prompt. Like {@link imageToImage} but mask-guided — the mask
|
|
1290
|
+
* is routed to whatever mask input the target endpoint declares.
|
|
1291
|
+
*
|
|
1292
|
+
* The first image is the primary subject; the mask in `params.mask` marks the
|
|
1293
|
+
* region to regenerate (white = regenerate).
|
|
1294
|
+
*/
|
|
1295
|
+
async inpaint(_images, _params) {
|
|
1296
|
+
throw new Error(`${this.provider} does not support inpaint`);
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Generate multiple inpaint results in one logical call.
|
|
1300
|
+
*
|
|
1301
|
+
* Default implementation calls inpaint() sequentially as a fallback.
|
|
1302
|
+
* Providers that support native batch generation should override this.
|
|
1303
|
+
*/
|
|
1304
|
+
async inpaintImages(images, params, numImages) {
|
|
364
1305
|
const results = [];
|
|
365
1306
|
for (let i = 0; i < numImages; i++) {
|
|
366
|
-
results.push(await this.
|
|
1307
|
+
results.push(await this.inpaint(images, params));
|
|
367
1308
|
}
|
|
368
1309
|
return results;
|
|
369
1310
|
}
|
|
1311
|
+
/**
|
|
1312
|
+
* Outpaint (uncrop): extend an image past its frame, filling the added area
|
|
1313
|
+
* with content that continues the scene. The first image is the subject;
|
|
1314
|
+
* providers that take reference images use the rest.
|
|
1315
|
+
*/
|
|
1316
|
+
async outpaintImage(_images, _params) {
|
|
1317
|
+
throw new Error(`${this.provider} does not support outpaintImage`);
|
|
1318
|
+
}
|
|
1319
|
+
/** Increase the resolution / detail of an image. */
|
|
1320
|
+
async upscaleImage(_image, _params) {
|
|
1321
|
+
throw new Error(`${this.provider} does not support upscaleImage`);
|
|
1322
|
+
}
|
|
1323
|
+
/** Remove the background from an image, returning an image with alpha. */
|
|
1324
|
+
async removeBackground(_image, _params) {
|
|
1325
|
+
throw new Error(`${this.provider} does not support removeBackground`);
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* Segment an image into object masks. Returns one entry per object the model
|
|
1329
|
+
* found, in the model's own order; an empty array means it found nothing,
|
|
1330
|
+
* which is an answer rather than a failure.
|
|
1331
|
+
*/
|
|
1332
|
+
async segmentImage(_image, _params) {
|
|
1333
|
+
throw new Error(`${this.provider} does not support segmentImage`);
|
|
1334
|
+
}
|
|
1335
|
+
/** Re-light a subject according to a prompt / background reference. */
|
|
1336
|
+
async relightImage(_image, _params) {
|
|
1337
|
+
throw new Error(`${this.provider} does not support relightImage`);
|
|
1338
|
+
}
|
|
1339
|
+
/** Convert a raster image into a vector (SVG) representation. */
|
|
1340
|
+
async vectorizeImage(_image, _params) {
|
|
1341
|
+
throw new Error(`${this.provider} does not support vectorizeImage`);
|
|
1342
|
+
}
|
|
370
1343
|
async *textToSpeech(_args) {
|
|
371
1344
|
yield* [];
|
|
372
1345
|
throw new Error(`${this.provider} does not support textToSpeech`);
|
|
@@ -383,15 +1356,87 @@ export class BaseProvider {
|
|
|
383
1356
|
async textToSpeechEncoded(_args) {
|
|
384
1357
|
return null;
|
|
385
1358
|
}
|
|
1359
|
+
/**
|
|
1360
|
+
* True when this provider streams raw PCM via {@link textToSpeech}. Providers
|
|
1361
|
+
* that only return encoded audio files (e.g. FAL/KIE) leave the base method in
|
|
1362
|
+
* place and are consumed via {@link textToSpeechEncoded} instead.
|
|
1363
|
+
*/
|
|
1364
|
+
supportsStreamingTextToSpeech() {
|
|
1365
|
+
return this.textToSpeech !== BaseProvider.prototype.textToSpeech;
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Generate music / instrumental audio from a text prompt. Music providers
|
|
1369
|
+
* return a fully-encoded audio file (mp3/wav/flac), so this resolves to an
|
|
1370
|
+
* {@link EncodedAudioResult} rather than streaming raw PCM. Providers that
|
|
1371
|
+
* expose music models via {@link getAvailableMusicModels} must override this.
|
|
1372
|
+
*/
|
|
1373
|
+
async textToMusic(_params) {
|
|
1374
|
+
throw new Error(`${this.provider} does not support textToMusic`);
|
|
1375
|
+
}
|
|
1376
|
+
/**
|
|
1377
|
+
* Optional video-conditioned sound task. Call through requestVideoToAudio
|
|
1378
|
+
* for input and capability validation before dispatch.
|
|
1379
|
+
*/
|
|
1380
|
+
async videoToAudio(_video, _params) {
|
|
1381
|
+
throw new Error(`${this.provider} does not support videoToAudio`);
|
|
1382
|
+
}
|
|
1383
|
+
/**
|
|
1384
|
+
* Rewrite a recording: convert the voice, isolate or denoise it, separate a
|
|
1385
|
+
* stem, raise its sample rate. The source bytes are passed positionally, the
|
|
1386
|
+
* direction rides in the params. Like {@link textToMusic} this resolves to an
|
|
1387
|
+
* {@link EncodedAudioResult} — every provider that offers these endpoints
|
|
1388
|
+
* hands back a file, not PCM.
|
|
1389
|
+
*/
|
|
1390
|
+
async audioToAudio(_audio, _params) {
|
|
1391
|
+
throw new Error(`${this.provider} does not support audioToAudio`);
|
|
1392
|
+
}
|
|
386
1393
|
async automaticSpeechRecognition(_args) {
|
|
387
1394
|
throw new Error(`${this.provider} does not support automaticSpeechRecognition`);
|
|
388
1395
|
}
|
|
389
1396
|
async textToVideo(_params) {
|
|
390
1397
|
throw new Error(`${this.provider} does not support textToVideo`);
|
|
391
1398
|
}
|
|
1399
|
+
/**
|
|
1400
|
+
* Animate a single source image into a video. Reference media uses
|
|
1401
|
+
* {@link referenceToVideo} so the input role remains explicit.
|
|
1402
|
+
*/
|
|
392
1403
|
async imageToVideo(_image, _params) {
|
|
393
1404
|
throw new Error(`${this.provider} does not support imageToVideo`);
|
|
394
1405
|
}
|
|
1406
|
+
async referenceToVideo(_inputs, _params) {
|
|
1407
|
+
throw new Error(`${this.provider} does not support referenceToVideo`);
|
|
1408
|
+
}
|
|
1409
|
+
/** Track a source-frame box and return samples in absolute source milliseconds. */
|
|
1410
|
+
async trackObject(_video, _params) {
|
|
1411
|
+
throw new Error(`${this.provider} does not support object tracking`);
|
|
1412
|
+
}
|
|
1413
|
+
/** Restyle / edit an existing video, guided by a prompt. */
|
|
1414
|
+
async videoToVideo(_video, _params) {
|
|
1415
|
+
throw new Error(`${this.provider} does not support videoToVideo`);
|
|
1416
|
+
}
|
|
1417
|
+
/** Add source time before or after an existing video window. */
|
|
1418
|
+
async extendVideo(_video, _params) {
|
|
1419
|
+
throw new Error(`${this.provider} does not support extendVideo`);
|
|
1420
|
+
}
|
|
1421
|
+
/** Increase the resolution / detail of a video. */
|
|
1422
|
+
async upscaleVideo(_video, _params) {
|
|
1423
|
+
throw new Error(`${this.provider} does not support upscaleVideo`);
|
|
1424
|
+
}
|
|
1425
|
+
/**
|
|
1426
|
+
* Synthesize intermediate frames so a clip plays at a higher frame rate (or
|
|
1427
|
+
* in slow motion at the same one).
|
|
1428
|
+
*/
|
|
1429
|
+
async interpolateVideo(_video, _params) {
|
|
1430
|
+
throw new Error(`${this.provider} does not support interpolateVideo`);
|
|
1431
|
+
}
|
|
1432
|
+
/** Extend a video past its frame, consistently across its frames. */
|
|
1433
|
+
async outpaintVideo(_video, _params) {
|
|
1434
|
+
throw new Error(`${this.provider} does not support outpaintVideo`);
|
|
1435
|
+
}
|
|
1436
|
+
/** Drive a face in a video/image to match the speech in an audio track. */
|
|
1437
|
+
async lipSync(_video, _params) {
|
|
1438
|
+
throw new Error(`${this.provider} does not support lipSync`);
|
|
1439
|
+
}
|
|
395
1440
|
/**
|
|
396
1441
|
* Generate a 3D asset from a text prompt. Returns the encoded asset bytes
|
|
397
1442
|
* (typically GLB). Providers that support 3D generation should override.
|
|
@@ -410,6 +1455,16 @@ export class BaseProvider {
|
|
|
410
1455
|
async generateEmbedding(_args) {
|
|
411
1456
|
throw new Error(`${this.provider} does not support generateEmbedding`);
|
|
412
1457
|
}
|
|
1458
|
+
/**
|
|
1459
|
+
* Whether `error` is this provider refusing the request because it exceeds
|
|
1460
|
+
* the model's context window. Default `false`: a provider whose signal has
|
|
1461
|
+
* not been established must not have one guessed for it, because a false
|
|
1462
|
+
* positive rewrites a transcript over an unrelated failure. Overridden by the
|
|
1463
|
+
* providers in `context-exceeded.ts`.
|
|
1464
|
+
*/
|
|
1465
|
+
isContextExceededError(_error) {
|
|
1466
|
+
return false;
|
|
1467
|
+
}
|
|
413
1468
|
isContextLengthError(error) {
|
|
414
1469
|
const msg = String(error).toLowerCase();
|
|
415
1470
|
return msg.includes("context length") || msg.includes("maximum context");
|
|
@@ -420,7 +1475,9 @@ export class BaseProvider {
|
|
|
420
1475
|
*/
|
|
421
1476
|
isRateLimitError(error) {
|
|
422
1477
|
const msg = error instanceof Error ? error.message : String(error);
|
|
423
|
-
|
|
1478
|
+
// Match 429 as a standalone token, not as digits inside a larger number
|
|
1479
|
+
// (e.g. "request 4290", a byte count, or a model id like "gpt-4-0429").
|
|
1480
|
+
return /\b429\b|rate.?limit|too many requests/i.test(msg);
|
|
424
1481
|
}
|
|
425
1482
|
/**
|
|
426
1483
|
* Check if an error is an authentication error (HTTP 401/403).
|
|
@@ -428,18 +1485,21 @@ export class BaseProvider {
|
|
|
428
1485
|
*/
|
|
429
1486
|
isAuthError(error) {
|
|
430
1487
|
const msg = error instanceof Error ? error.message : String(error);
|
|
431
|
-
|
|
1488
|
+
// Match 401/403 as standalone tokens, not as digits inside a larger number
|
|
1489
|
+
// (e.g. "model gpt-4-0403 not found") which would misclassify unrelated
|
|
1490
|
+
// errors as auth failures and skip a retry that should have happened.
|
|
1491
|
+
return /\b401\b|\b403\b|unauthorized|forbidden|invalid.*api.*key|authentication/i.test(msg);
|
|
432
1492
|
}
|
|
433
1493
|
parseToolCallArgs(raw) {
|
|
434
|
-
if (
|
|
1494
|
+
if (isRecord(raw)) {
|
|
435
1495
|
return raw;
|
|
436
1496
|
}
|
|
437
|
-
if (
|
|
1497
|
+
if (!isString(raw)) {
|
|
438
1498
|
return {};
|
|
439
1499
|
}
|
|
440
1500
|
try {
|
|
441
1501
|
const parsed = JSON.parse(raw);
|
|
442
|
-
if (
|
|
1502
|
+
if (isRecord(parsed)) {
|
|
443
1503
|
return parsed;
|
|
444
1504
|
}
|
|
445
1505
|
return {};
|
|
@@ -458,42 +1518,192 @@ export class BaseProvider {
|
|
|
458
1518
|
/**
|
|
459
1519
|
* Resolve a URI to a `data:` URI that providers can consume directly.
|
|
460
1520
|
*
|
|
461
|
-
* - `file://...` → read
|
|
1521
|
+
* - `file://...` → read an asset file, return `data:<mime>;base64,…`
|
|
462
1522
|
* - `/api/storage/<k>` → read from getDefaultAssetsPath()/<k> (legacy fallback)
|
|
463
1523
|
* - Everything else → returned unchanged (http/https/data: pass through)
|
|
464
1524
|
*/
|
|
465
1525
|
async resolveUri(uri) {
|
|
466
|
-
const {
|
|
1526
|
+
const { importNodeBuiltin } = await import("@nodetool-ai/config");
|
|
1527
|
+
const fsP = await importNodeBuiltin("node:fs/promises");
|
|
1528
|
+
if (!fsP) {
|
|
1529
|
+
throw new Error("resolveUri requires node:fs/promises (Node-only feature)");
|
|
1530
|
+
}
|
|
1531
|
+
const pathMod = await importNodeBuiltin("node:path");
|
|
1532
|
+
if (!pathMod) {
|
|
1533
|
+
throw new Error("resolveUri requires node:path (Node-only feature)");
|
|
1534
|
+
}
|
|
1535
|
+
const getAssetsRoot = () => fsP.realpath(getDefaultAssetsPath());
|
|
1536
|
+
const readAssetFile = async (filePath) => {
|
|
1537
|
+
const assetsRoot = await getAssetsRoot();
|
|
1538
|
+
const assertContained = (candidate) => {
|
|
1539
|
+
const relative = pathMod.relative(assetsRoot, candidate);
|
|
1540
|
+
if (relative.startsWith("..") || pathMod.isAbsolute(relative)) {
|
|
1541
|
+
throw new Error("Asset URI resolves outside the asset directory");
|
|
1542
|
+
}
|
|
1543
|
+
};
|
|
1544
|
+
const requestedPath = pathMod.resolve(filePath);
|
|
1545
|
+
assertContained(requestedPath);
|
|
1546
|
+
const realPath = await fsP.realpath(requestedPath);
|
|
1547
|
+
assertContained(realPath);
|
|
1548
|
+
return await fsP.readFile(realPath);
|
|
1549
|
+
};
|
|
467
1550
|
if (uri.startsWith("file://")) {
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
const bytes = await readFile(filePath);
|
|
472
|
-
const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
|
|
473
|
-
const mime = EXT_TO_MIME[ext] ?? "application/octet-stream";
|
|
474
|
-
return `data:${mime};base64,${bytes.toString("base64")}`;
|
|
475
|
-
}
|
|
476
|
-
catch {
|
|
477
|
-
return uri;
|
|
1551
|
+
const urlMod = await importNodeBuiltin("node:url");
|
|
1552
|
+
if (!urlMod) {
|
|
1553
|
+
throw new Error("resolveUri file:// requires node:url");
|
|
478
1554
|
}
|
|
1555
|
+
const filePath = urlMod.fileURLToPath(uri);
|
|
1556
|
+
const bytes = await readAssetFile(filePath);
|
|
1557
|
+
const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
|
|
1558
|
+
const mime = EXT_TO_MIME[ext] ?? "application/octet-stream";
|
|
1559
|
+
return `data:${mime};base64,${Buffer.from(bytes).toString("base64")}`;
|
|
479
1560
|
}
|
|
480
1561
|
// Legacy: old messages stored with browser-facing /api/storage/ path
|
|
481
1562
|
if (uri.startsWith("/api/storage/")) {
|
|
482
1563
|
const key = uri.slice("/api/storage/".length);
|
|
483
1564
|
try {
|
|
484
|
-
const
|
|
1565
|
+
const assetsRoot = await getAssetsRoot();
|
|
1566
|
+
const decodedKey = decodeURIComponent(key);
|
|
1567
|
+
const bytes = await readAssetFile(pathMod.resolve(assetsRoot, decodedKey.replace(/^\/+/, "")));
|
|
485
1568
|
const ext = key.split(".").pop()?.toLowerCase() ?? "";
|
|
486
1569
|
const mime = EXT_TO_MIME[ext] ?? "application/octet-stream";
|
|
487
|
-
return `data:${mime};base64,${bytes.toString("base64")}`;
|
|
1570
|
+
return `data:${mime};base64,${Buffer.from(bytes).toString("base64")}`;
|
|
488
1571
|
}
|
|
489
|
-
catch {
|
|
1572
|
+
catch (error) {
|
|
1573
|
+
if (error instanceof Error &&
|
|
1574
|
+
error.message === "Asset URI resolves outside the asset directory") {
|
|
1575
|
+
throw error;
|
|
1576
|
+
}
|
|
490
1577
|
// Remote deployment: file not local, fall back to absolute HTTP
|
|
491
1578
|
return `http://127.0.0.1:${process.env.PORT ?? 7777}${uri}`;
|
|
492
1579
|
}
|
|
493
1580
|
}
|
|
1581
|
+
// A reference scheme reaching a provider means the caller never
|
|
1582
|
+
// dereferenced it (see `ProcessingContext.resolveMessageMediaUris`). Say
|
|
1583
|
+
// that, instead of handing `asset://…` to `fetch` and reporting the SSRF
|
|
1584
|
+
// guard's "Refusing to fetch unsafe URL" — which names neither the asset
|
|
1585
|
+
// nor the real cause.
|
|
1586
|
+
if (uri.startsWith("asset://") || uri.startsWith("package://")) {
|
|
1587
|
+
throw new Error(`Unresolved asset reference reached the model provider: ${uri}. ` +
|
|
1588
|
+
`The asset bytes could not be loaded from storage.`);
|
|
1589
|
+
}
|
|
494
1590
|
return uri;
|
|
495
1591
|
}
|
|
496
1592
|
}
|
|
1593
|
+
function referenceVideoDiagnostics(args) {
|
|
1594
|
+
const inputs = args[0];
|
|
1595
|
+
const params = args[1];
|
|
1596
|
+
const media = isRecord(inputs) ? inputs : {};
|
|
1597
|
+
const listBytes = (value) => Array.isArray(value)
|
|
1598
|
+
? value.reduce((total, item) => total + (item instanceof Uint8Array ? item.byteLength : 0), 0)
|
|
1599
|
+
: 0;
|
|
1600
|
+
return {
|
|
1601
|
+
image_count: Array.isArray(media.images) ? media.images.length : 0,
|
|
1602
|
+
video_count: Array.isArray(media.videos) ? media.videos.length : 0,
|
|
1603
|
+
image_bytes: listBytes(media.images),
|
|
1604
|
+
video_bytes: listBytes(media.videos),
|
|
1605
|
+
model: extractModelId([params])
|
|
1606
|
+
};
|
|
1607
|
+
}
|
|
1608
|
+
const MODALITY_PROMISE_METHODS = [
|
|
1609
|
+
"textToImage",
|
|
1610
|
+
"textToImages",
|
|
1611
|
+
"imageToImage",
|
|
1612
|
+
"imageToImages",
|
|
1613
|
+
"inpaint",
|
|
1614
|
+
"inpaintImages",
|
|
1615
|
+
"outpaintImage",
|
|
1616
|
+
"upscaleImage",
|
|
1617
|
+
"removeBackground",
|
|
1618
|
+
"relightImage",
|
|
1619
|
+
"vectorizeImage",
|
|
1620
|
+
"segmentImage",
|
|
1621
|
+
"textToSpeechEncoded",
|
|
1622
|
+
"textToMusic",
|
|
1623
|
+
"videoToAudio",
|
|
1624
|
+
"automaticSpeechRecognition",
|
|
1625
|
+
"textToVideo",
|
|
1626
|
+
"imageToVideo",
|
|
1627
|
+
"referenceToVideo",
|
|
1628
|
+
"videoToVideo",
|
|
1629
|
+
"trackObject",
|
|
1630
|
+
"extendVideo",
|
|
1631
|
+
"upscaleVideo",
|
|
1632
|
+
"interpolateVideo",
|
|
1633
|
+
"outpaintVideo",
|
|
1634
|
+
"lipSync",
|
|
1635
|
+
"textTo3D",
|
|
1636
|
+
"imageTo3D",
|
|
1637
|
+
"generateEmbedding"
|
|
1638
|
+
];
|
|
1639
|
+
/** Async-generator non-chat modality methods wrapped with failure logging. */
|
|
1640
|
+
const MODALITY_GENERATOR_METHODS = ["textToSpeech"];
|
|
1641
|
+
/**
|
|
1642
|
+
* Best-effort model id for failure logging. Modality params carry the model
|
|
1643
|
+
* either as a plain string (ASR, embeddings) or as a model object with an `id`
|
|
1644
|
+
* (image/video tasks). Returns "unknown" when neither is present.
|
|
1645
|
+
*/
|
|
1646
|
+
function extractModelId(args) {
|
|
1647
|
+
for (const arg of args) {
|
|
1648
|
+
if (!isObjectLike(arg))
|
|
1649
|
+
continue;
|
|
1650
|
+
const model = arg.model;
|
|
1651
|
+
if (isString(model))
|
|
1652
|
+
return model;
|
|
1653
|
+
if (isObjectLike(model)) {
|
|
1654
|
+
const id = model.id;
|
|
1655
|
+
if (isString(id))
|
|
1656
|
+
return id;
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
return "unknown";
|
|
1660
|
+
}
|
|
1661
|
+
/**
|
|
1662
|
+
* Drive a provider's async-generator modality method (e.g. textToSpeech)
|
|
1663
|
+
* through a per-call slot — so `recordRequestPayload` is visible across yields —
|
|
1664
|
+
* and log centrally if it throws. The slot is wrapped around each `next()`
|
|
1665
|
+
* because AsyncLocalStorage does not persist across generator suspension.
|
|
1666
|
+
*/
|
|
1667
|
+
async function* wrapModalityGenerator(provider, original, args, operationName) {
|
|
1668
|
+
const { runInSlot, getRequest } = createUsageSlot();
|
|
1669
|
+
const startedAt = Date.now();
|
|
1670
|
+
const source = original.apply(provider, args);
|
|
1671
|
+
let exhausted = false;
|
|
1672
|
+
try {
|
|
1673
|
+
while (true) {
|
|
1674
|
+
const result = await runInSlot(() => source.next());
|
|
1675
|
+
if (result.done) {
|
|
1676
|
+
exhausted = true;
|
|
1677
|
+
return result.value;
|
|
1678
|
+
}
|
|
1679
|
+
yield result.value;
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
catch (err) {
|
|
1683
|
+
annotateProviderError(err, {
|
|
1684
|
+
provider: provider.provider,
|
|
1685
|
+
model: extractModelId(args)
|
|
1686
|
+
});
|
|
1687
|
+
provider.recordCallFailure({
|
|
1688
|
+
model: extractModelId(args),
|
|
1689
|
+
operation: operationName,
|
|
1690
|
+
request: getRequest(),
|
|
1691
|
+
nodetoolArgs: args,
|
|
1692
|
+
error: err,
|
|
1693
|
+
startedAt
|
|
1694
|
+
});
|
|
1695
|
+
throw err;
|
|
1696
|
+
}
|
|
1697
|
+
finally {
|
|
1698
|
+
if (!exhausted) {
|
|
1699
|
+
await runInSlot(() => source.return?.(undefined) ??
|
|
1700
|
+
Promise.resolve({
|
|
1701
|
+
done: true,
|
|
1702
|
+
value: undefined
|
|
1703
|
+
})).catch(() => { });
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
497
1707
|
/** Attach gen_ai usage attributes to a span (no-op if usage is null). */
|
|
498
1708
|
function applyUsageAttributes(span, usage) {
|
|
499
1709
|
if (!usage)
|
|
@@ -505,14 +1715,21 @@ function applyUsageAttributes(span, usage) {
|
|
|
505
1715
|
...(usage.cachedInputTokens !== undefined && {
|
|
506
1716
|
"gen_ai.usage.cached_input_tokens": usage.cachedInputTokens
|
|
507
1717
|
}),
|
|
508
|
-
...(usage.cost !== undefined && { "gen_ai.usage.
|
|
1718
|
+
...(usage.cost !== undefined && { "gen_ai.usage.cost_usd": usage.cost })
|
|
509
1719
|
});
|
|
510
1720
|
}
|
|
511
1721
|
const EXT_TO_MIME = {
|
|
512
|
-
jpg: "image/jpeg",
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
1722
|
+
jpg: "image/jpeg",
|
|
1723
|
+
jpeg: "image/jpeg",
|
|
1724
|
+
png: "image/png",
|
|
1725
|
+
gif: "image/gif",
|
|
1726
|
+
webp: "image/webp",
|
|
1727
|
+
svg: "image/svg+xml",
|
|
1728
|
+
mp4: "video/mp4",
|
|
1729
|
+
webm: "video/webm",
|
|
1730
|
+
mp3: "audio/mpeg",
|
|
1731
|
+
wav: "audio/wav",
|
|
1732
|
+
ogg: "audio/ogg",
|
|
516
1733
|
pdf: "application/pdf"
|
|
517
1734
|
};
|
|
518
1735
|
//# sourceMappingURL=base-provider.js.map
|