@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,7 +1,38 @@
|
|
|
1
1
|
import OpenAI, { toFile } from "openai";
|
|
2
|
-
import { createLogger } from "@nodetool-ai/config";
|
|
3
|
-
import { BaseProvider } from "./base-provider.js";
|
|
2
|
+
import { createLogger, importHidden } from "@nodetool-ai/config";
|
|
3
|
+
import { BaseProvider, budgetStopItem, resolveTurnBudget, splitToolResultImages } from "./base-provider.js";
|
|
4
|
+
import { openAIContextExceeded } from "./context-exceeded.js";
|
|
5
|
+
import { hashSystemPrompt } from "./provider-session.js";
|
|
6
|
+
import { sniffAudioMime } from "./audio-mime.js";
|
|
7
|
+
/** Extension for each mime `sniffAudioMime` can return; anything else is mp3. */
|
|
8
|
+
const AUDIO_MIME_EXT = {
|
|
9
|
+
"audio/wav": "wav",
|
|
10
|
+
"audio/ogg": "ogg",
|
|
11
|
+
"audio/flac": "flac",
|
|
12
|
+
"audio/mpeg": "mp3"
|
|
13
|
+
};
|
|
14
|
+
import { extractResponsesImages, extractResponsesText, extractResponsesToolCalls, messagesToResponsesInput, responseToolChoice, responseTools, responseUsage, streamResponsesEvents } from "./responses-api.js";
|
|
15
|
+
import { PROVIDER_IDS, isCallable, isNumber, isRecord, isString } from "@nodetool-ai/protocol";
|
|
16
|
+
import { isProviderMessageEvent, isProviderSessionUpdate, IMAGE_GENERATION_TOOL_NAME, PROVIDER_STOP_ABORTED, WEB_SEARCH_TOOL_NAME } from "./types.js";
|
|
17
|
+
async function loadSharp() {
|
|
18
|
+
const mod = await importHidden("sharp");
|
|
19
|
+
if (!mod)
|
|
20
|
+
throw new Error("sharp requires Node");
|
|
21
|
+
return "default" in mod ? mod.default : mod;
|
|
22
|
+
}
|
|
4
23
|
const log = createLogger("nodetool.runtime.providers.openai");
|
|
24
|
+
import { snapToGptImage2Size } from "./gpt-image-size.js";
|
|
25
|
+
function openAIImageUsage(response, quality) {
|
|
26
|
+
return {
|
|
27
|
+
imageCount: response.data?.length ?? 1,
|
|
28
|
+
imageQuality: quality ?? undefined,
|
|
29
|
+
inputTokens: response.usage?.input_tokens,
|
|
30
|
+
outputTokens: response.usage?.output_tokens,
|
|
31
|
+
inputTextTokens: response.usage?.input_tokens_details?.text_tokens,
|
|
32
|
+
inputImageTokens: response.usage?.input_tokens_details?.image_tokens,
|
|
33
|
+
outputImageTokens: response.usage?.output_tokens_details?.image_tokens
|
|
34
|
+
};
|
|
35
|
+
}
|
|
5
36
|
function asUint8Array(data) {
|
|
6
37
|
if (data instanceof Uint8Array) {
|
|
7
38
|
return data;
|
|
@@ -9,7 +40,7 @@ function asUint8Array(data) {
|
|
|
9
40
|
if (Array.isArray(data) && data.every((v) => Number.isInteger(v))) {
|
|
10
41
|
return new Uint8Array(data);
|
|
11
42
|
}
|
|
12
|
-
if (
|
|
43
|
+
if (isString(data)) {
|
|
13
44
|
return Uint8Array.from(Buffer.from(data, "base64"));
|
|
14
45
|
}
|
|
15
46
|
if (data instanceof ArrayBuffer) {
|
|
@@ -18,8 +49,8 @@ function asUint8Array(data) {
|
|
|
18
49
|
throw new Error("Unsupported binary payload format");
|
|
19
50
|
}
|
|
20
51
|
function toInt16Samples(bytes) {
|
|
21
|
-
const
|
|
22
|
-
return new Int16Array(
|
|
52
|
+
const copy = Uint8Array.from(bytes);
|
|
53
|
+
return new Int16Array(copy.buffer);
|
|
23
54
|
}
|
|
24
55
|
function parseDataUri(uri) {
|
|
25
56
|
const idx = uri.indexOf(",");
|
|
@@ -43,9 +74,15 @@ function makeDataUri(mime, data) {
|
|
|
43
74
|
return `data:${mime};base64,${b64}`;
|
|
44
75
|
}
|
|
45
76
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
77
|
+
* OpenAI's Chat Completions `input_audio` block accepts only `"wav"` or `"mp3"`
|
|
78
|
+
* as its `format` — a bare token, not a mime type. Resolve it from the declared
|
|
79
|
+
* mime when present, otherwise sniff the leading magic bytes. Anything that
|
|
80
|
+
* isn't WAV is sent as `"mp3"` (the only other format OpenAI decodes).
|
|
48
81
|
*/
|
|
82
|
+
function openAiAudioFormat(mime, bytes) {
|
|
83
|
+
const resolved = mime && mime !== "application/octet-stream" ? mime : sniffAudioMime(bytes);
|
|
84
|
+
return /wave?$/i.test(resolved) ? "wav" : "mp3";
|
|
85
|
+
}
|
|
49
86
|
function defaultSerializer(_key, value) {
|
|
50
87
|
if (value === null || value === undefined)
|
|
51
88
|
return value;
|
|
@@ -59,11 +96,118 @@ function defaultSerializer(_key, value) {
|
|
|
59
96
|
return [...value];
|
|
60
97
|
if (typeof value === "object" &&
|
|
61
98
|
"toJSON" in value &&
|
|
62
|
-
|
|
99
|
+
isCallable(value.toJSON)) {
|
|
100
|
+
// SAFETY: `toJSON` is the JSON.stringify protocol hook — it exists on the
|
|
101
|
+
// value and, by that contract, produces a JSON-representable result.
|
|
63
102
|
return value.toJSON();
|
|
64
103
|
}
|
|
65
104
|
return value;
|
|
66
105
|
}
|
|
106
|
+
const RESPONSE_WEB_SEARCH_TOOL = {
|
|
107
|
+
type: "web_search"
|
|
108
|
+
};
|
|
109
|
+
const RESPONSE_IMAGE_GENERATION_TOOL = {
|
|
110
|
+
type: "image_generation"
|
|
111
|
+
};
|
|
112
|
+
const RESPONSE_HOSTED_TOOL_CHOICES = {
|
|
113
|
+
[WEB_SEARCH_TOOL_NAME]: "web_search",
|
|
114
|
+
[IMAGE_GENERATION_TOOL_NAME]: "image_generation"
|
|
115
|
+
};
|
|
116
|
+
const SORA_VIDEO_DURATIONS = [4, 8, 12, 16, 20];
|
|
117
|
+
export const OPENAI_FALLBACK_MODELS = [
|
|
118
|
+
"gpt-6-astra",
|
|
119
|
+
"gpt-5.6",
|
|
120
|
+
"gpt-5.6-sol",
|
|
121
|
+
"gpt-5.6-terra",
|
|
122
|
+
"gpt-5.6-luna",
|
|
123
|
+
"gpt-5.5",
|
|
124
|
+
"gpt-5.5-pro",
|
|
125
|
+
"gpt-5.4",
|
|
126
|
+
"gpt-5.4-pro",
|
|
127
|
+
"gpt-5.4-mini",
|
|
128
|
+
"gpt-5.4-nano",
|
|
129
|
+
"gpt-5",
|
|
130
|
+
"gpt-5-mini",
|
|
131
|
+
"gpt-5-nano"
|
|
132
|
+
].map((id) => ({
|
|
133
|
+
id,
|
|
134
|
+
name: id,
|
|
135
|
+
provider: PROVIDER_IDS.OPENAI
|
|
136
|
+
}));
|
|
137
|
+
/**
|
|
138
|
+
* OpenAI language models we support: the gpt-5 family, served through the
|
|
139
|
+
* Responses API. Everything before gpt-5 (gpt-4o, gpt-4.1, the o-series, …) is
|
|
140
|
+
* retired. OpenAI-compatible subclasses set their own provider id and never
|
|
141
|
+
* reach this predicate, so they keep their own model support on Chat Completions.
|
|
142
|
+
*/
|
|
143
|
+
function isOpenAIResponsesModel(model) {
|
|
144
|
+
const id = model.toLowerCase();
|
|
145
|
+
// Audio / realtime / transcription variants stay on Chat Completions — the
|
|
146
|
+
// Responses API doesn't serve the `modalities: ["text","audio"]` path.
|
|
147
|
+
if (/audio|realtime|transcribe/.test(id))
|
|
148
|
+
return false;
|
|
149
|
+
return /^gpt-5(?:[.-]|$)/.test(id);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Decode a `GET /v1/models` body into the language models it announces.
|
|
153
|
+
*
|
|
154
|
+
* Split out of {@link OpenAIProvider.getAvailableLanguageModels} so the live
|
|
155
|
+
* contract probe and its raw-response fixtures parse a real production
|
|
156
|
+
* response the same way the provider does. Strict on the envelope: a body
|
|
157
|
+
* without a `data` array throws, and the caller decides what to fall back to.
|
|
158
|
+
*/
|
|
159
|
+
export function decodeOpenAIModelList(payload, options) {
|
|
160
|
+
if (!isRecord(payload)) {
|
|
161
|
+
throw new Error("OpenAI model list is not an object");
|
|
162
|
+
}
|
|
163
|
+
const rows = payload.data;
|
|
164
|
+
if (!Array.isArray(rows)) {
|
|
165
|
+
throw new Error("OpenAI model list has no `data` array");
|
|
166
|
+
}
|
|
167
|
+
return rows
|
|
168
|
+
.filter(isRecord)
|
|
169
|
+
.filter((row) => isString(row.id) && row.id.length > 0)
|
|
170
|
+
.filter((row) => !options.onlyResponsesModels || isOpenAIResponsesModel(row.id))
|
|
171
|
+
.map((row) => ({
|
|
172
|
+
id: row.id,
|
|
173
|
+
name: row.id,
|
|
174
|
+
provider: options.provider
|
|
175
|
+
}));
|
|
176
|
+
}
|
|
177
|
+
function responsesSystemPrompt(messages) {
|
|
178
|
+
return messages
|
|
179
|
+
.filter((m) => m.role === "system")
|
|
180
|
+
.map((m) => (isString(m.content) ? m.content : ""))
|
|
181
|
+
.filter(Boolean)
|
|
182
|
+
.join("\n\n");
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* A native image_generation result surfaced by the stream parser as an image
|
|
186
|
+
* chunk. Returns the content block to attach to the assistant message, or
|
|
187
|
+
* null for any other stream item. The multi-megabyte base64 blob must be
|
|
188
|
+
* absorbed here, never yielded onward as a chunk.
|
|
189
|
+
*/
|
|
190
|
+
function imageContentFromChunk(item) {
|
|
191
|
+
if (!isRecord(item) ||
|
|
192
|
+
item.type !== "chunk" ||
|
|
193
|
+
item.content_type !== "image" ||
|
|
194
|
+
!isString(item.content)) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
const metadata = isRecord(item.content_metadata) ? item.content_metadata : {};
|
|
198
|
+
const mimeType = isString(metadata.mimeType)
|
|
199
|
+
? metadata.mimeType
|
|
200
|
+
: "image/png";
|
|
201
|
+
return { type: "image_url", image: { data: item.content, mimeType } };
|
|
202
|
+
}
|
|
203
|
+
/** Text block (when non-empty) followed by generated-image blocks. */
|
|
204
|
+
function assistantContentWithImages(text, images) {
|
|
205
|
+
const blocks = [];
|
|
206
|
+
if (text)
|
|
207
|
+
blocks.push({ type: "text", text });
|
|
208
|
+
blocks.push(...images);
|
|
209
|
+
return blocks;
|
|
210
|
+
}
|
|
67
211
|
export class OpenAIProvider extends BaseProvider {
|
|
68
212
|
static requiredSecrets() {
|
|
69
213
|
return ["OPENAI_API_KEY"];
|
|
@@ -73,7 +217,7 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
73
217
|
_clientFactory;
|
|
74
218
|
_fetch;
|
|
75
219
|
constructor(secrets, options = {}) {
|
|
76
|
-
super("openai");
|
|
220
|
+
super(options.providerId ?? "openai");
|
|
77
221
|
const apiKey = secrets.OPENAI_API_KEY;
|
|
78
222
|
if (!apiKey) {
|
|
79
223
|
throw new Error("OPENAI_API_KEY is required");
|
|
@@ -93,46 +237,136 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
93
237
|
}
|
|
94
238
|
return this._client;
|
|
95
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* True when this instance should serve `model` through the Responses API.
|
|
242
|
+
* Gated on the real `openai` provider so OpenAI-compatible subclasses (which
|
|
243
|
+
* pass their own provider id) stay on Chat Completions.
|
|
244
|
+
*/
|
|
245
|
+
usesResponsesApi(model) {
|
|
246
|
+
return (this.provider === PROVIDER_IDS.OPENAI && isOpenAIResponsesModel(model));
|
|
247
|
+
}
|
|
248
|
+
get supportsNativeWebSearch() {
|
|
249
|
+
return this.provider === PROVIDER_IDS.OPENAI;
|
|
250
|
+
}
|
|
251
|
+
get supportsNativeImageGeneration() {
|
|
252
|
+
return this.provider === PROVIDER_IDS.OPENAI;
|
|
253
|
+
}
|
|
96
254
|
async hasToolSupport(model) {
|
|
255
|
+
if (this.usesResponsesApi(model))
|
|
256
|
+
return true;
|
|
97
257
|
return !(model.startsWith("o1") || model.startsWith("o3"));
|
|
98
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* Whether this instance actually serves OpenAI's own model catalog.
|
|
261
|
+
*
|
|
262
|
+
* The media and embedding lists below are hardcoded OpenAI models tagged
|
|
263
|
+
* `provider: "openai"`. Subclasses inherit OpenAI's *chat dialect*, not its
|
|
264
|
+
* lineup — a Groq or vLLM instance offering `gpt-image-2` or `whisper-1` is
|
|
265
|
+
* always wrong, and picking one sends the request to a host that has no such
|
|
266
|
+
* endpoint. Subclasses that do serve media (Together, MiniMax, GMI, xAI, …)
|
|
267
|
+
* override these methods with their own catalog; everyone else correctly
|
|
268
|
+
* reports none.
|
|
269
|
+
*/
|
|
270
|
+
servesOpenAICatalog() {
|
|
271
|
+
return this.provider === PROVIDER_IDS.OPENAI;
|
|
272
|
+
}
|
|
99
273
|
async getAvailableLanguageModels() {
|
|
274
|
+
// The `openai` provider only serves the supported (gpt-5+) models; retire
|
|
275
|
+
// everything older. Subclasses (their own provider id) keep their full list.
|
|
276
|
+
const isOpenAI = this.provider === PROVIDER_IDS.OPENAI;
|
|
100
277
|
const response = await this._fetch("https://api.openai.com/v1/models", {
|
|
101
278
|
headers: {
|
|
102
279
|
Authorization: `Bearer ${this.apiKey}`
|
|
103
280
|
}
|
|
104
281
|
});
|
|
105
282
|
if (!response.ok) {
|
|
106
|
-
return [];
|
|
283
|
+
return isOpenAI ? OPENAI_FALLBACK_MODELS : [];
|
|
107
284
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
285
|
+
let models;
|
|
286
|
+
try {
|
|
287
|
+
models = decodeOpenAIModelList(await response.json(), {
|
|
288
|
+
provider: this.provider,
|
|
289
|
+
onlyResponsesModels: isOpenAI
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
// A body OpenAI's own decoder rejects is the same situation as an
|
|
294
|
+
// unreachable listing: fall back rather than fail model discovery.
|
|
295
|
+
return isOpenAI ? OPENAI_FALLBACK_MODELS : [];
|
|
296
|
+
}
|
|
297
|
+
if (models.length === 0 && isOpenAI) {
|
|
298
|
+
return OPENAI_FALLBACK_MODELS;
|
|
299
|
+
}
|
|
300
|
+
return models;
|
|
117
301
|
}
|
|
118
302
|
async getAvailableTTSModels() {
|
|
303
|
+
if (!this.servesOpenAICatalog())
|
|
304
|
+
return [];
|
|
305
|
+
const legacyVoices = [
|
|
306
|
+
"alloy",
|
|
307
|
+
"ash",
|
|
308
|
+
"coral",
|
|
309
|
+
"echo",
|
|
310
|
+
"fable",
|
|
311
|
+
"onyx",
|
|
312
|
+
"nova",
|
|
313
|
+
"sage",
|
|
314
|
+
"shimmer"
|
|
315
|
+
];
|
|
119
316
|
return [
|
|
317
|
+
{
|
|
318
|
+
id: "gpt-4o-mini-tts",
|
|
319
|
+
name: "GPT-4o Mini TTS (Deprecated)",
|
|
320
|
+
provider: "openai",
|
|
321
|
+
voices: [
|
|
322
|
+
"alloy",
|
|
323
|
+
"ash",
|
|
324
|
+
"ballad",
|
|
325
|
+
"coral",
|
|
326
|
+
"echo",
|
|
327
|
+
"fable",
|
|
328
|
+
"nova",
|
|
329
|
+
"onyx",
|
|
330
|
+
"sage",
|
|
331
|
+
"shimmer",
|
|
332
|
+
"verse",
|
|
333
|
+
"marin",
|
|
334
|
+
"cedar"
|
|
335
|
+
]
|
|
336
|
+
},
|
|
120
337
|
{
|
|
121
338
|
id: "tts-1",
|
|
122
339
|
name: "TTS 1",
|
|
123
340
|
provider: "openai",
|
|
124
|
-
voices:
|
|
341
|
+
voices: legacyVoices
|
|
125
342
|
},
|
|
126
343
|
{
|
|
127
344
|
id: "tts-1-hd",
|
|
128
345
|
name: "TTS 1 HD",
|
|
129
346
|
provider: "openai",
|
|
130
|
-
voices:
|
|
347
|
+
voices: legacyVoices
|
|
131
348
|
}
|
|
132
349
|
];
|
|
133
350
|
}
|
|
134
351
|
async getAvailableASRModels() {
|
|
352
|
+
if (!this.servesOpenAICatalog())
|
|
353
|
+
return [];
|
|
135
354
|
return [
|
|
355
|
+
{
|
|
356
|
+
id: "gpt-4o-transcribe",
|
|
357
|
+
name: "GPT-4o Transcribe",
|
|
358
|
+
provider: "openai"
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
id: "gpt-4o-mini-transcribe",
|
|
362
|
+
name: "GPT-4o Mini Transcribe",
|
|
363
|
+
provider: "openai"
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
id: "gpt-4o-transcribe-diarize",
|
|
367
|
+
name: "GPT-4o Transcribe Diarize",
|
|
368
|
+
provider: "openai"
|
|
369
|
+
},
|
|
136
370
|
{
|
|
137
371
|
id: "whisper-1",
|
|
138
372
|
name: "Whisper",
|
|
@@ -141,23 +375,45 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
141
375
|
];
|
|
142
376
|
}
|
|
143
377
|
async getAvailableVideoModels() {
|
|
378
|
+
if (!this.servesOpenAICatalog())
|
|
379
|
+
return [];
|
|
144
380
|
return [
|
|
145
381
|
{
|
|
146
382
|
id: "sora-2",
|
|
147
383
|
name: "Sora 2",
|
|
148
384
|
provider: "openai",
|
|
149
|
-
supportedTasks: ["text_to_video"]
|
|
385
|
+
supportedTasks: ["text_to_video", "image_to_video"],
|
|
386
|
+
durations: [...SORA_VIDEO_DURATIONS],
|
|
387
|
+
resolutions: ["720p"],
|
|
388
|
+
aspectRatios: ["16:9", "9:16"]
|
|
150
389
|
},
|
|
151
390
|
{
|
|
152
391
|
id: "sora-2-pro",
|
|
153
392
|
name: "Sora 2 Pro",
|
|
154
393
|
provider: "openai",
|
|
155
|
-
supportedTasks: ["text_to_video"]
|
|
394
|
+
supportedTasks: ["text_to_video", "image_to_video"],
|
|
395
|
+
durations: [...SORA_VIDEO_DURATIONS],
|
|
396
|
+
resolutions: ["720p", "1024p", "1080p"],
|
|
397
|
+
aspectRatios: ["16:9", "9:16"]
|
|
156
398
|
}
|
|
157
399
|
];
|
|
158
400
|
}
|
|
159
401
|
async getAvailableImageModels() {
|
|
402
|
+
if (!this.servesOpenAICatalog())
|
|
403
|
+
return [];
|
|
160
404
|
return [
|
|
405
|
+
{
|
|
406
|
+
id: "gpt-image-2.5-flare",
|
|
407
|
+
name: "GPT Image 2.5 Flare",
|
|
408
|
+
provider: "openai",
|
|
409
|
+
supportedTasks: ["text_to_image", "image_to_image"]
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
id: "gpt-image-2.5-sunburst",
|
|
413
|
+
name: "GPT Image 2.5 Sunburst",
|
|
414
|
+
provider: "openai",
|
|
415
|
+
supportedTasks: ["text_to_image", "image_to_image"]
|
|
416
|
+
},
|
|
161
417
|
{
|
|
162
418
|
id: "gpt-image-2",
|
|
163
419
|
name: "GPT Image 2",
|
|
@@ -185,6 +441,8 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
185
441
|
];
|
|
186
442
|
}
|
|
187
443
|
async getAvailableEmbeddingModels() {
|
|
444
|
+
if (!this.servesOpenAICatalog())
|
|
445
|
+
return [];
|
|
188
446
|
return [
|
|
189
447
|
{
|
|
190
448
|
id: "text-embedding-3-small",
|
|
@@ -206,10 +464,27 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
206
464
|
}
|
|
207
465
|
];
|
|
208
466
|
}
|
|
209
|
-
|
|
467
|
+
/**
|
|
468
|
+
* gpt-image-2 accepts arbitrary sizes within limits: both edges a multiple of
|
|
469
|
+
* 16, long edge <= 3840, aspect ratio <= 3:1, and total area between 0.64MP
|
|
470
|
+
* and 8.3MP. Requested sizes rarely land on that grid (16:9 at 1K is
|
|
471
|
+
* 1820x1024), so snap to the nearest valid size preserving the aspect ratio
|
|
472
|
+
* rather than dropping `size` and letting the API default to a square.
|
|
473
|
+
*/
|
|
474
|
+
static snapToGptImage2Size(width, height) {
|
|
475
|
+
const size = snapToGptImage2Size(width, height);
|
|
476
|
+
return size ? `${size[0]}x${size[1]}` : null;
|
|
477
|
+
}
|
|
478
|
+
resolveImageSize(width, height, model) {
|
|
210
479
|
if (!width || !height) {
|
|
211
480
|
return null;
|
|
212
481
|
}
|
|
482
|
+
const usesFlexibleDimensions = model === "gpt-image-2" ||
|
|
483
|
+
model?.startsWith("gpt-image-2-") === true ||
|
|
484
|
+
model?.startsWith("gpt-image-2.") === true;
|
|
485
|
+
if (usesFlexibleDimensions) {
|
|
486
|
+
return OpenAIProvider.snapToGptImage2Size(width, height);
|
|
487
|
+
}
|
|
213
488
|
const supported = [
|
|
214
489
|
[1024, 1024],
|
|
215
490
|
[1024, 1536],
|
|
@@ -229,16 +504,24 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
229
504
|
: bestSoFar);
|
|
230
505
|
return `${best[0]}x${best[1]}`;
|
|
231
506
|
}
|
|
232
|
-
static resolveVideoSize(aspectRatio, resolution) {
|
|
507
|
+
static resolveVideoSize(aspectRatio, resolution, model) {
|
|
233
508
|
if (!resolution) {
|
|
234
509
|
return null;
|
|
235
510
|
}
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
[
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
511
|
+
const isStandardSora2 = model?.startsWith("sora-2") === true && !model.includes("pro");
|
|
512
|
+
const supported = isStandardSora2
|
|
513
|
+
? [
|
|
514
|
+
[1280, 720],
|
|
515
|
+
[720, 1280]
|
|
516
|
+
]
|
|
517
|
+
: [
|
|
518
|
+
[1280, 720],
|
|
519
|
+
[1792, 1024],
|
|
520
|
+
[1920, 1080],
|
|
521
|
+
[720, 1280],
|
|
522
|
+
[1024, 1792],
|
|
523
|
+
[1080, 1920]
|
|
524
|
+
];
|
|
242
525
|
const aspect = (aspectRatio ?? "16:9").replaceAll(" ", "");
|
|
243
526
|
const resolutionKey = resolution.toLowerCase();
|
|
244
527
|
const presets = {
|
|
@@ -284,16 +567,18 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
284
567
|
return closest(Math.max(2, Math.round(targetW)), Math.max(2, Math.round(targetH)));
|
|
285
568
|
}
|
|
286
569
|
static secondsFromParams(params) {
|
|
570
|
+
const durationSeconds = params.durationSeconds;
|
|
571
|
+
if (durationSeconds != null &&
|
|
572
|
+
Number.isFinite(durationSeconds) &&
|
|
573
|
+
durationSeconds > 0) {
|
|
574
|
+
return SORA_VIDEO_DURATIONS.reduce((duration, candidate) => candidate <= durationSeconds ? candidate : duration, SORA_VIDEO_DURATIONS[0]);
|
|
575
|
+
}
|
|
287
576
|
const numFrames = params.numFrames;
|
|
288
|
-
if (!numFrames || numFrames <= 0) {
|
|
577
|
+
if (!numFrames || !Number.isFinite(numFrames) || numFrames <= 0) {
|
|
289
578
|
return null;
|
|
290
579
|
}
|
|
291
580
|
const estimated = Math.ceil(numFrames / 24);
|
|
292
|
-
|
|
293
|
-
return 4;
|
|
294
|
-
if (estimated < 12)
|
|
295
|
-
return 8;
|
|
296
|
-
return 12;
|
|
581
|
+
return SORA_VIDEO_DURATIONS.reduce((duration, candidate) => (candidate <= estimated ? candidate : duration), SORA_VIDEO_DURATIONS[0]);
|
|
297
582
|
}
|
|
298
583
|
static snapToValidVideoDimensions(width, height) {
|
|
299
584
|
const supported = [
|
|
@@ -369,17 +654,38 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
369
654
|
}
|
|
370
655
|
if (content.type === "audio") {
|
|
371
656
|
const c = content;
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
657
|
+
let bytes;
|
|
658
|
+
let mime = c.audio.mimeType;
|
|
659
|
+
if (c.audio.uri) {
|
|
660
|
+
const parsed = parseDataUri(await this.uriToBase64(c.audio.uri));
|
|
661
|
+
bytes = parsed.data;
|
|
662
|
+
mime = mime ?? parsed.mime;
|
|
663
|
+
}
|
|
664
|
+
else {
|
|
665
|
+
bytes = asUint8Array(c.audio.data ?? new Uint8Array());
|
|
666
|
+
}
|
|
375
667
|
return {
|
|
376
668
|
type: "input_audio",
|
|
377
669
|
input_audio: {
|
|
378
|
-
format:
|
|
379
|
-
data: base64
|
|
670
|
+
format: openAiAudioFormat(mime, bytes),
|
|
671
|
+
data: Buffer.from(bytes).toString("base64")
|
|
380
672
|
}
|
|
381
673
|
};
|
|
382
674
|
}
|
|
675
|
+
if (content.type === "video") {
|
|
676
|
+
// Reached only with the fallback switched off: BaseProvider otherwise
|
|
677
|
+
// samples the clip into frames before conversion whenever
|
|
678
|
+
// `supportsVideoInput` is false, and a missing ffmpeg fails there with
|
|
679
|
+
// its own message. Named for the configured provider, not the wire
|
|
680
|
+
// format: every
|
|
681
|
+
// OpenAI-compatible re-host lands here, and reporting "openai" sent a
|
|
682
|
+
// diagnosis after the wrong provider.
|
|
683
|
+
throw new Error(`video input is not supported by ${this.provider} — its chat API is ` +
|
|
684
|
+
`OpenAI-compatible, which has no video content part. Frame sampling ` +
|
|
685
|
+
`is off (NODETOOL_VIDEO_FRAME_FALLBACK=0); unset it to read the clip ` +
|
|
686
|
+
`as stills, or use a provider whose chat models read video natively ` +
|
|
687
|
+
`(gemini).`);
|
|
688
|
+
}
|
|
383
689
|
const c = content;
|
|
384
690
|
const imageUrl = c.image.uri
|
|
385
691
|
? await this.uriToBase64(c.image.uri)
|
|
@@ -395,7 +701,7 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
395
701
|
throw new Error("Tool message requires toolCallId");
|
|
396
702
|
}
|
|
397
703
|
let content = "";
|
|
398
|
-
if (
|
|
704
|
+
if (isString(message.content)) {
|
|
399
705
|
content = message.content;
|
|
400
706
|
}
|
|
401
707
|
else if (message.content != null) {
|
|
@@ -410,7 +716,7 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
410
716
|
if (message.role === "system") {
|
|
411
717
|
return {
|
|
412
718
|
role: "system",
|
|
413
|
-
content:
|
|
719
|
+
content: isString(message.content) ? message.content : ""
|
|
414
720
|
};
|
|
415
721
|
}
|
|
416
722
|
if (message.role === "assistant") {
|
|
@@ -422,24 +728,30 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
422
728
|
arguments: JSON.stringify(tc.args, defaultSerializer)
|
|
423
729
|
}
|
|
424
730
|
}));
|
|
425
|
-
if (
|
|
426
|
-
|
|
731
|
+
if (isString(message.content) || message.content == null) {
|
|
732
|
+
const assistant = {
|
|
427
733
|
role: "assistant",
|
|
428
|
-
content: message.content
|
|
429
|
-
...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {})
|
|
734
|
+
content: message.content
|
|
430
735
|
};
|
|
736
|
+
if (toolCalls.length > 0) {
|
|
737
|
+
assistant.tool_calls = toolCalls;
|
|
738
|
+
}
|
|
739
|
+
return assistant;
|
|
431
740
|
}
|
|
432
741
|
const parts = await Promise.all(message.content.map((part) => this.messageContentToOpenAIContentPart(part)));
|
|
433
|
-
|
|
742
|
+
const assistant = {
|
|
434
743
|
role: "assistant",
|
|
435
|
-
content: parts
|
|
436
|
-
...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {})
|
|
744
|
+
content: parts
|
|
437
745
|
};
|
|
746
|
+
if (toolCalls.length > 0) {
|
|
747
|
+
assistant.tool_calls = toolCalls;
|
|
748
|
+
}
|
|
749
|
+
return assistant;
|
|
438
750
|
}
|
|
439
751
|
if (message.role !== "user") {
|
|
440
752
|
throw new Error(`Unsupported role: ${message.role}`);
|
|
441
753
|
}
|
|
442
|
-
if (
|
|
754
|
+
if (isString(message.content)) {
|
|
443
755
|
return {
|
|
444
756
|
role: "user",
|
|
445
757
|
content: message.content
|
|
@@ -468,19 +780,31 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
468
780
|
});
|
|
469
781
|
}
|
|
470
782
|
convertSystemToUserForOModels(messages, model) {
|
|
471
|
-
|
|
783
|
+
// Match ONLY genuine OpenAI reasoning models (o1/o3/o4), not any id that
|
|
784
|
+
// merely starts with "o". Compat providers use namespaced ids like
|
|
785
|
+
// "openai/gpt-4o" or "openai/gpt-oss-120b" that begin with "o" but fully
|
|
786
|
+
// support the system role — mangling their system prompt corrupts every
|
|
787
|
+
// request. Strip a leading "vendor/" namespace before matching.
|
|
788
|
+
const bareModel = model.includes("/")
|
|
789
|
+
? model.slice(model.lastIndexOf("/") + 1)
|
|
790
|
+
: model;
|
|
791
|
+
if (!/^o[134](-|$)/.test(bareModel)) {
|
|
472
792
|
return messages;
|
|
473
793
|
}
|
|
474
794
|
return messages.map((msg) => msg.role === "system"
|
|
475
795
|
? {
|
|
476
796
|
...msg,
|
|
477
797
|
role: "user",
|
|
478
|
-
content: `Instructions: ${
|
|
798
|
+
content: `Instructions: ${isString(msg.content) ? msg.content : ""}`
|
|
479
799
|
}
|
|
480
800
|
: msg);
|
|
481
801
|
}
|
|
482
802
|
async *generateMessages(args) {
|
|
483
|
-
|
|
803
|
+
if (this.usesResponsesApi(args.model)) {
|
|
804
|
+
yield* this.generateMessagesResponses(args);
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
const { model, tools = [], maxTokens = 16384, temperature, topP, presencePenalty, frequencyPenalty, audio, toolChoice } = args;
|
|
484
808
|
const messages = this.convertSystemToUserForOModels(args.messages, model);
|
|
485
809
|
const openaiMessages = await Promise.all(messages.map((m) => this.convertMessage(m)));
|
|
486
810
|
const request = {
|
|
@@ -505,9 +829,19 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
505
829
|
}
|
|
506
830
|
if (hasTools) {
|
|
507
831
|
request.tools = this.formatTools(tools);
|
|
832
|
+
if (toolChoice) {
|
|
833
|
+
request.tool_choice =
|
|
834
|
+
toolChoice === "any"
|
|
835
|
+
? "required"
|
|
836
|
+
: { type: "function", function: { name: toolChoice } };
|
|
837
|
+
}
|
|
508
838
|
}
|
|
509
839
|
log.debug("OpenAI request", { model });
|
|
510
|
-
|
|
840
|
+
this.recordRequestPayload(request);
|
|
841
|
+
// SAFETY: the request is assembled field by field as a dictionary, which
|
|
842
|
+
// the SDK's closed param interface does not overlap; `stream: true` on it
|
|
843
|
+
// selects the streaming overload.
|
|
844
|
+
const stream = (await this.getClient().chat.completions.create(request, { signal: args.signal }));
|
|
511
845
|
const deltaToolCalls = new Map();
|
|
512
846
|
try {
|
|
513
847
|
for await (const chunk of stream) {
|
|
@@ -555,22 +889,34 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
555
889
|
};
|
|
556
890
|
yield item;
|
|
557
891
|
}
|
|
558
|
-
if (choice.finish_reason
|
|
892
|
+
if (choice.finish_reason && deltaToolCalls.size > 0) {
|
|
559
893
|
for (const call of deltaToolCalls.values()) {
|
|
560
894
|
const toolCall = this.buildToolCall(call.id, call.name, call.arguments);
|
|
561
895
|
yield toolCall;
|
|
562
896
|
}
|
|
563
897
|
deltaToolCalls.clear();
|
|
564
898
|
}
|
|
899
|
+
// Always emit a terminal `done: true` chunk when the completion
|
|
900
|
+
// finishes, regardless of reason. "stop" already emits one above; for
|
|
901
|
+
// every other terminal reason (tool_calls, length, content_filter)
|
|
902
|
+
// emit one here so consumers get a consistent end-of-stream marker
|
|
903
|
+
// (matching the Anthropic and Gemini providers).
|
|
904
|
+
if (choice.finish_reason && choice.finish_reason !== "stop") {
|
|
905
|
+
const doneChunk = { type: "chunk", content: "", done: true };
|
|
906
|
+
yield doneChunk;
|
|
907
|
+
}
|
|
565
908
|
}
|
|
566
909
|
}
|
|
567
910
|
finally {
|
|
568
|
-
if (
|
|
911
|
+
if (isCallable(stream.close)) {
|
|
569
912
|
await stream.close();
|
|
570
913
|
}
|
|
571
914
|
}
|
|
572
915
|
}
|
|
573
916
|
async generateMessage(args) {
|
|
917
|
+
if (this.usesResponsesApi(args.model)) {
|
|
918
|
+
return this.generateMessageResponses(args);
|
|
919
|
+
}
|
|
574
920
|
const { model, tools = [], toolChoice, maxTokens = 16384, temperature, topP, presencePenalty, frequencyPenalty } = args;
|
|
575
921
|
const messages = this.convertSystemToUserForOModels(args.messages, model);
|
|
576
922
|
const openaiMessages = await Promise.all(messages.map((m) => this.convertMessage(m)));
|
|
@@ -599,7 +945,10 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
599
945
|
}
|
|
600
946
|
}
|
|
601
947
|
log.debug("OpenAI request", { model });
|
|
602
|
-
|
|
948
|
+
this.recordRequestPayload(request);
|
|
949
|
+
// SAFETY: as above — a dictionary request against the SDK's closed
|
|
950
|
+
// non-streaming param interface.
|
|
951
|
+
const completion = await this.getClient().chat.completions.create(request, { signal: args.signal });
|
|
603
952
|
const choice = completion.choices?.[0];
|
|
604
953
|
if (!choice) {
|
|
605
954
|
throw new Error("OpenAI returned no choices");
|
|
@@ -624,6 +973,401 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
624
973
|
toolCalls
|
|
625
974
|
};
|
|
626
975
|
}
|
|
976
|
+
// ---------------------------------------------------------------------------
|
|
977
|
+
// Responses API path (used for OpenAI models matched by isOpenAIResponsesModel)
|
|
978
|
+
// ---------------------------------------------------------------------------
|
|
979
|
+
/** Tool shapes for the Responses API: native web_search / image_generation. */
|
|
980
|
+
formatResponsesTools(tools) {
|
|
981
|
+
const formatted = [];
|
|
982
|
+
for (const tool of tools) {
|
|
983
|
+
if (tool.name === WEB_SEARCH_TOOL_NAME) {
|
|
984
|
+
formatted.push(RESPONSE_WEB_SEARCH_TOOL);
|
|
985
|
+
continue;
|
|
986
|
+
}
|
|
987
|
+
if (tool.name === IMAGE_GENERATION_TOOL_NAME) {
|
|
988
|
+
formatted.push(RESPONSE_IMAGE_GENERATION_TOOL);
|
|
989
|
+
continue;
|
|
990
|
+
}
|
|
991
|
+
const [functionTool] = responseTools([tool]);
|
|
992
|
+
if (functionTool)
|
|
993
|
+
formatted.push(functionTool);
|
|
994
|
+
}
|
|
995
|
+
return formatted;
|
|
996
|
+
}
|
|
997
|
+
async generateMessageResponses(args) {
|
|
998
|
+
const request = await this.buildResponsesRequest(args, {
|
|
999
|
+
input: await messagesToResponsesInput(args.messages, (uri) => this.resolveUri(uri)),
|
|
1000
|
+
stream: false,
|
|
1001
|
+
store: false
|
|
1002
|
+
});
|
|
1003
|
+
const client = this.getClient();
|
|
1004
|
+
this.recordRequestPayload(request);
|
|
1005
|
+
// SAFETY: the SDK's `create` is overloaded over a closed param interface,
|
|
1006
|
+
// while this request is assembled field by field; `stream: false` is set
|
|
1007
|
+
// on it, so the SDK resolves a response object, not an async iterable.
|
|
1008
|
+
const response = (await client.responses.create.call(client.responses, request, {
|
|
1009
|
+
signal: args.signal
|
|
1010
|
+
}));
|
|
1011
|
+
this.trackUsage(args.model, responseUsage(response));
|
|
1012
|
+
const outputText = isString(response.output_text)
|
|
1013
|
+
? response.output_text
|
|
1014
|
+
: extractResponsesText(response.output);
|
|
1015
|
+
const toolCalls = extractResponsesToolCalls(response.output, this.buildToolCall.bind(this));
|
|
1016
|
+
const images = extractResponsesImages(response.output);
|
|
1017
|
+
const content = images.length > 0
|
|
1018
|
+
? assistantContentWithImages(outputText, images)
|
|
1019
|
+
: outputText || null;
|
|
1020
|
+
return {
|
|
1021
|
+
role: "assistant",
|
|
1022
|
+
content,
|
|
1023
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
async *generateMessagesResponses(args) {
|
|
1027
|
+
const input = await messagesToResponsesInput(args.messages, (uri) => this.resolveUri(uri));
|
|
1028
|
+
const request = await this.buildResponsesRequest(args, {
|
|
1029
|
+
input,
|
|
1030
|
+
stream: true,
|
|
1031
|
+
store: false
|
|
1032
|
+
});
|
|
1033
|
+
// Absorb native image results here too — this non-loop path must uphold
|
|
1034
|
+
// the same invariant as collectModelTurn: the base64 blob never leaks
|
|
1035
|
+
// outward as a chunk. The image rides a trailing assistant message event.
|
|
1036
|
+
const images = [];
|
|
1037
|
+
let assistantText = "";
|
|
1038
|
+
for await (const item of this.streamResponsesRequest(args, request)) {
|
|
1039
|
+
const image = imageContentFromChunk(item);
|
|
1040
|
+
if (image) {
|
|
1041
|
+
images.push(image);
|
|
1042
|
+
continue;
|
|
1043
|
+
}
|
|
1044
|
+
if (isRecord(item) &&
|
|
1045
|
+
item.type === "chunk" &&
|
|
1046
|
+
isString(item.content) &&
|
|
1047
|
+
!item.thinking) {
|
|
1048
|
+
assistantText += item.content;
|
|
1049
|
+
}
|
|
1050
|
+
yield item;
|
|
1051
|
+
}
|
|
1052
|
+
if (images.length > 0) {
|
|
1053
|
+
yield {
|
|
1054
|
+
type: "message",
|
|
1055
|
+
message: {
|
|
1056
|
+
role: "assistant",
|
|
1057
|
+
content: assistantContentWithImages(assistantText, images)
|
|
1058
|
+
}
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
async *generateLoop(args) {
|
|
1063
|
+
if (!this.usesResponsesApi(args.model)) {
|
|
1064
|
+
yield* super.generateLoop(args);
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
const maxIterations = args.maxIterations ?? 25;
|
|
1068
|
+
const { executeTool, maxIterations: _omitMaxIterations, sequentialTools, turnBudget: budgetArg, resolveMedia, ...turnArgs } = args;
|
|
1069
|
+
const { runBudget, turnBudget } = resolveTurnBudget(budgetArg);
|
|
1070
|
+
const systemHash = hashSystemPrompt(responsesSystemPrompt(args.messages));
|
|
1071
|
+
const prior = args.providerSession ?? null;
|
|
1072
|
+
const canResume = prior != null &&
|
|
1073
|
+
prior.providerId === this.provider &&
|
|
1074
|
+
prior.model === args.model &&
|
|
1075
|
+
(prior.systemHash == null || prior.systemHash === systemHash) &&
|
|
1076
|
+
args.messages.length > prior.checkpoint;
|
|
1077
|
+
if (canResume && prior) {
|
|
1078
|
+
const firstMessages = args.messages.slice(prior.checkpoint);
|
|
1079
|
+
const firstTurnState = this.createResponsesTurnState(prior.token);
|
|
1080
|
+
try {
|
|
1081
|
+
yield* this.runResponsesLoop({
|
|
1082
|
+
args: turnArgs,
|
|
1083
|
+
resolveMedia,
|
|
1084
|
+
executeTool,
|
|
1085
|
+
sequentialTools: sequentialTools === true,
|
|
1086
|
+
maxIterations,
|
|
1087
|
+
firstMessages,
|
|
1088
|
+
firstPreviousResponseId: prior.token,
|
|
1089
|
+
systemHash,
|
|
1090
|
+
firstTurnState,
|
|
1091
|
+
turnBudget,
|
|
1092
|
+
runBudget
|
|
1093
|
+
});
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
catch (err) {
|
|
1097
|
+
if (firstTurnState.emittedContent) {
|
|
1098
|
+
throw err;
|
|
1099
|
+
}
|
|
1100
|
+
log.warn("OpenAI Responses resume failed; starting fresh", {
|
|
1101
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
const freshMessages = args.loadFullHistory
|
|
1106
|
+
? await args.loadFullHistory()
|
|
1107
|
+
: args.messages;
|
|
1108
|
+
yield* this.runResponsesLoop({
|
|
1109
|
+
args: turnArgs,
|
|
1110
|
+
resolveMedia,
|
|
1111
|
+
executeTool,
|
|
1112
|
+
sequentialTools: sequentialTools === true,
|
|
1113
|
+
maxIterations,
|
|
1114
|
+
firstMessages: freshMessages,
|
|
1115
|
+
firstPreviousResponseId: null,
|
|
1116
|
+
systemHash,
|
|
1117
|
+
firstTurnState: this.createResponsesTurnState(null),
|
|
1118
|
+
turnBudget,
|
|
1119
|
+
runBudget
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
async buildResponsesRequest(args, config) {
|
|
1123
|
+
const request = {
|
|
1124
|
+
model: args.model,
|
|
1125
|
+
input: config.input,
|
|
1126
|
+
stream: config.stream,
|
|
1127
|
+
store: config.store
|
|
1128
|
+
};
|
|
1129
|
+
if (config.previousResponseId) {
|
|
1130
|
+
request.previous_response_id = config.previousResponseId;
|
|
1131
|
+
}
|
|
1132
|
+
if (args.maxTokens != null)
|
|
1133
|
+
request.max_output_tokens = args.maxTokens;
|
|
1134
|
+
if (args.temperature != null)
|
|
1135
|
+
request.temperature = args.temperature;
|
|
1136
|
+
if (args.topP != null)
|
|
1137
|
+
request.top_p = args.topP;
|
|
1138
|
+
const tools = this.formatResponsesTools(args.tools ?? []);
|
|
1139
|
+
if (tools.length > 0) {
|
|
1140
|
+
request.tools = tools;
|
|
1141
|
+
const hostedToolChoice = args.toolChoice
|
|
1142
|
+
? RESPONSE_HOSTED_TOOL_CHOICES[args.toolChoice]
|
|
1143
|
+
: undefined;
|
|
1144
|
+
request.tool_choice = hostedToolChoice
|
|
1145
|
+
? { type: hostedToolChoice }
|
|
1146
|
+
: (responseToolChoice(args.toolChoice) ?? "auto");
|
|
1147
|
+
}
|
|
1148
|
+
return request;
|
|
1149
|
+
}
|
|
1150
|
+
async *streamResponsesRequest(args, request, onResponseId) {
|
|
1151
|
+
const client = this.getClient();
|
|
1152
|
+
this.recordRequestPayload(request);
|
|
1153
|
+
// SAFETY: as in `generateMessageResponses` — an overloaded SDK method over
|
|
1154
|
+
// a closed param interface, given a request assembled field by field. The
|
|
1155
|
+
// caller set `stream: true`, so the SDK resolves an async iterable.
|
|
1156
|
+
const stream = (await client.responses.create.call(client.responses, request, {
|
|
1157
|
+
signal: args.signal
|
|
1158
|
+
}));
|
|
1159
|
+
yield* streamResponsesEvents(stream, {
|
|
1160
|
+
model: args.model,
|
|
1161
|
+
buildToolCall: this.buildToolCall.bind(this),
|
|
1162
|
+
onUsage: (model, usage) => this.trackUsage(model, usage),
|
|
1163
|
+
onResponseId
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
createResponsesTurnState(responseId) {
|
|
1167
|
+
return {
|
|
1168
|
+
assistantText: "",
|
|
1169
|
+
images: [],
|
|
1170
|
+
pending: [],
|
|
1171
|
+
responseId,
|
|
1172
|
+
emittedContent: false
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
async *runResponsesLoop(config) {
|
|
1176
|
+
const toolMap = new Map((config.args.tools ?? []).map((tool) => [tool.name, tool]));
|
|
1177
|
+
let previousResponseId = config.firstPreviousResponseId;
|
|
1178
|
+
const toInput = async (messages) => messagesToResponsesInput(config.resolveMedia ? await config.resolveMedia(messages) : messages, (uri) => this.resolveUri(uri));
|
|
1179
|
+
let input = await toInput(config.firstMessages);
|
|
1180
|
+
let state = config.firstTurnState;
|
|
1181
|
+
// Responses carries prior turns server-side behind `previousResponseId`,
|
|
1182
|
+
// so `input` holds only the delta. Reserving against the delta would price
|
|
1183
|
+
// a turn as if the conversation restarted; the transcript is tracked here
|
|
1184
|
+
// so the reservation grows with the turn it is admitting.
|
|
1185
|
+
const transcript = [...config.firstMessages];
|
|
1186
|
+
const runBudget = config.runBudget ?? null;
|
|
1187
|
+
for (let iteration = 0; iteration < config.maxIterations; iteration++) {
|
|
1188
|
+
if (config.args.signal?.aborted) {
|
|
1189
|
+
yield PROVIDER_STOP_ABORTED;
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
if (runBudget?.deadline.expired()) {
|
|
1193
|
+
yield budgetStopItem(runBudget);
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
let reservation = null;
|
|
1197
|
+
if (config.turnBudget) {
|
|
1198
|
+
reservation = this._admitTurn(config.turnBudget, config.args.model, transcript);
|
|
1199
|
+
if (reservation === null) {
|
|
1200
|
+
yield budgetStopItem(runBudget);
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
const costBeforeTurn = config.turnBudget ? this.getTotalCost() : 0;
|
|
1205
|
+
// The reservation covers exactly one turn, so it has to be reconciled
|
|
1206
|
+
// even when that turn throws. Left outstanding, a single provider error
|
|
1207
|
+
// would shrink the run's headroom for good — the budget is run-level and
|
|
1208
|
+
// outlives the decision that hit the error.
|
|
1209
|
+
try {
|
|
1210
|
+
const request = await this.buildResponsesRequest(config.args, {
|
|
1211
|
+
input,
|
|
1212
|
+
stream: true,
|
|
1213
|
+
store: true,
|
|
1214
|
+
previousResponseId
|
|
1215
|
+
});
|
|
1216
|
+
yield* this.collectResponsesTurn(config.args, request, config.systemHash, state);
|
|
1217
|
+
}
|
|
1218
|
+
finally {
|
|
1219
|
+
if (config.turnBudget && reservation) {
|
|
1220
|
+
config.turnBudget.commit(reservation, this.getTotalCost() - costBeforeTurn);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
previousResponseId = state.responseId;
|
|
1224
|
+
// Native image_generation results ride the assistant message as image
|
|
1225
|
+
// content and don't affect pending-tool semantics.
|
|
1226
|
+
const assistantContent = state.images.length > 0
|
|
1227
|
+
? assistantContentWithImages(state.assistantText, state.images)
|
|
1228
|
+
: state.assistantText || null;
|
|
1229
|
+
const assistantMsg = {
|
|
1230
|
+
role: "assistant",
|
|
1231
|
+
content: assistantContent,
|
|
1232
|
+
toolCalls: state.pending.length > 0 ? state.pending : null
|
|
1233
|
+
};
|
|
1234
|
+
yield { type: "message", message: assistantMsg };
|
|
1235
|
+
transcript.push(assistantMsg);
|
|
1236
|
+
if (state.pending.length === 0) {
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
if (!previousResponseId) {
|
|
1240
|
+
throw new Error("OpenAI Responses returned tool calls without a response id");
|
|
1241
|
+
}
|
|
1242
|
+
const toolMessages = [];
|
|
1243
|
+
const runTool = async (tc) => {
|
|
1244
|
+
// Per-tool error isolation: a thrown tool must still yield a
|
|
1245
|
+
// tool_result so a parallel Promise.all rejection can't discard sibling
|
|
1246
|
+
// results and leave a dangling tool_use (rejected by the API next turn).
|
|
1247
|
+
try {
|
|
1248
|
+
const tool = toolMap.get(tc.name);
|
|
1249
|
+
if (tool?.execute)
|
|
1250
|
+
return await tool.execute(tc.args ?? {}, tc.id);
|
|
1251
|
+
if (config.executeTool)
|
|
1252
|
+
return await config.executeTool(tc);
|
|
1253
|
+
return `Tool "${tc.name}" is not available`;
|
|
1254
|
+
}
|
|
1255
|
+
catch (err) {
|
|
1256
|
+
return `Error executing tool "${tc.name}": ${err instanceof Error ? err.message : String(err)}`;
|
|
1257
|
+
}
|
|
1258
|
+
};
|
|
1259
|
+
const emitToolResult = function* (tc, content) {
|
|
1260
|
+
const { toolContent, imageMessage } = splitToolResultImages(content);
|
|
1261
|
+
const toolMsg = {
|
|
1262
|
+
role: "tool",
|
|
1263
|
+
toolCallId: tc.id,
|
|
1264
|
+
content: toolContent
|
|
1265
|
+
};
|
|
1266
|
+
toolMessages.push(toolMsg);
|
|
1267
|
+
transcript.push(toolMsg);
|
|
1268
|
+
yield { type: "message", message: toolMsg };
|
|
1269
|
+
if (imageMessage) {
|
|
1270
|
+
toolMessages.push(imageMessage);
|
|
1271
|
+
// It rides the next turn's input, so it is part of what that turn
|
|
1272
|
+
// is billed for — and therefore part of what its reservation covers.
|
|
1273
|
+
transcript.push(imageMessage);
|
|
1274
|
+
}
|
|
1275
|
+
};
|
|
1276
|
+
let terminated = false;
|
|
1277
|
+
if (config.sequentialTools) {
|
|
1278
|
+
for (const tc of state.pending) {
|
|
1279
|
+
// Honor a mid-turn abort (mirrors base-provider generateLoop): a tool
|
|
1280
|
+
// like finish_step aborts the signal to stop the model running any
|
|
1281
|
+
// further tools THIS turn. Without this check the Responses loop would
|
|
1282
|
+
// keep executing pending tools after completion — duplicate
|
|
1283
|
+
// step_result/StepCompleted events, overwritten memory, and stray
|
|
1284
|
+
// side-effecting tools after the step is already done.
|
|
1285
|
+
if (config.args.signal?.aborted)
|
|
1286
|
+
break;
|
|
1287
|
+
const content = await runTool(tc);
|
|
1288
|
+
yield* emitToolResult(tc, content);
|
|
1289
|
+
const tool = toolMap.get(tc.name);
|
|
1290
|
+
if (tool?.terminal) {
|
|
1291
|
+
terminated = true;
|
|
1292
|
+
break;
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
else {
|
|
1297
|
+
const results = await Promise.all(state.pending.map(async (tc) => ({
|
|
1298
|
+
tc,
|
|
1299
|
+
content: await runTool(tc)
|
|
1300
|
+
})));
|
|
1301
|
+
for (const { tc, content } of results) {
|
|
1302
|
+
yield* emitToolResult(tc, content);
|
|
1303
|
+
const tool = toolMap.get(tc.name);
|
|
1304
|
+
if (tool?.terminal)
|
|
1305
|
+
terminated = true;
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
input = await toInput(toolMessages);
|
|
1309
|
+
if (terminated)
|
|
1310
|
+
return;
|
|
1311
|
+
state = this.createResponsesTurnState(previousResponseId);
|
|
1312
|
+
}
|
|
1313
|
+
yield { type: "chunk", content: "", done: true };
|
|
1314
|
+
yield {
|
|
1315
|
+
type: "stop",
|
|
1316
|
+
reason: "iterations",
|
|
1317
|
+
detail: `stopped after ${config.maxIterations} tool-calling round(s)`
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
async *collectResponsesTurn(args, request, systemHash, state) {
|
|
1321
|
+
const sessionUpdates = [];
|
|
1322
|
+
const enqueueSession = (responseId) => {
|
|
1323
|
+
if (state.responseId === responseId)
|
|
1324
|
+
return;
|
|
1325
|
+
state.responseId = responseId;
|
|
1326
|
+
const session = {
|
|
1327
|
+
providerId: this.provider,
|
|
1328
|
+
model: args.model,
|
|
1329
|
+
token: responseId,
|
|
1330
|
+
checkpoint: args.messages.length,
|
|
1331
|
+
systemHash
|
|
1332
|
+
};
|
|
1333
|
+
sessionUpdates.push({ type: "session", session });
|
|
1334
|
+
};
|
|
1335
|
+
const flushSessions = function* () {
|
|
1336
|
+
while (sessionUpdates.length > 0) {
|
|
1337
|
+
const update = sessionUpdates.shift();
|
|
1338
|
+
if (update)
|
|
1339
|
+
yield update;
|
|
1340
|
+
}
|
|
1341
|
+
};
|
|
1342
|
+
for await (const item of this.streamResponsesRequest(args, request, enqueueSession)) {
|
|
1343
|
+
yield* flushSessions();
|
|
1344
|
+
if (isProviderSessionUpdate(item) || isProviderMessageEvent(item)) {
|
|
1345
|
+
yield item;
|
|
1346
|
+
continue;
|
|
1347
|
+
}
|
|
1348
|
+
if ("id" in item && "name" in item && "args" in item) {
|
|
1349
|
+
state.pending.push(item);
|
|
1350
|
+
state.emittedContent = true;
|
|
1351
|
+
yield item;
|
|
1352
|
+
continue;
|
|
1353
|
+
}
|
|
1354
|
+
if (isRecord(item) && item.type === "chunk") {
|
|
1355
|
+
const image = imageContentFromChunk(item);
|
|
1356
|
+
if (image) {
|
|
1357
|
+
state.images.push(image);
|
|
1358
|
+
state.emittedContent = true;
|
|
1359
|
+
continue;
|
|
1360
|
+
}
|
|
1361
|
+
if (isString(item.content) && !item.thinking) {
|
|
1362
|
+
state.assistantText += item.content;
|
|
1363
|
+
if (item.content)
|
|
1364
|
+
state.emittedContent = true;
|
|
1365
|
+
}
|
|
1366
|
+
yield item;
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
yield* flushSessions();
|
|
1370
|
+
}
|
|
627
1371
|
async textToImage(params) {
|
|
628
1372
|
if (!params.prompt) {
|
|
629
1373
|
throw new Error("The input prompt cannot be empty.");
|
|
@@ -635,12 +1379,14 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
635
1379
|
model: params.model.id,
|
|
636
1380
|
prompt
|
|
637
1381
|
};
|
|
638
|
-
const size = this.resolveImageSize(params.width ?? undefined, params.height ?? undefined);
|
|
1382
|
+
const size = this.resolveImageSize(params.width ?? undefined, params.height ?? undefined, params.model.id);
|
|
639
1383
|
if (size)
|
|
640
1384
|
request.size = size;
|
|
641
1385
|
if (params.quality)
|
|
642
1386
|
request.quality = params.quality;
|
|
643
|
-
|
|
1387
|
+
// SAFETY: a dictionary request against the SDK's closed image params.
|
|
1388
|
+
const response = (await this.getClient().images.generate(request, { signal: params.signal }));
|
|
1389
|
+
this.trackUsage(params.model.id, openAIImageUsage(response, params.quality));
|
|
644
1390
|
const item = response.data?.[0];
|
|
645
1391
|
if (!item) {
|
|
646
1392
|
throw new Error("OpenAI image generation returned no image data.");
|
|
@@ -657,8 +1403,40 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
657
1403
|
}
|
|
658
1404
|
throw new Error("OpenAI image generation returned no image data.");
|
|
659
1405
|
}
|
|
660
|
-
|
|
661
|
-
|
|
1406
|
+
/**
|
|
1407
|
+
* Convert a white-region mask (R=G=B=255, alpha = selection) into the mask
|
|
1408
|
+
* OpenAI's edit endpoint expects, where fully transparent pixels mark the
|
|
1409
|
+
* region to regenerate. Inverts the alpha channel and keeps RGBA shape.
|
|
1410
|
+
*/
|
|
1411
|
+
async toOpenAiEditMask(mask) {
|
|
1412
|
+
const sharp = await loadSharp();
|
|
1413
|
+
const { data, info } = await sharp(Buffer.from(mask))
|
|
1414
|
+
.ensureAlpha()
|
|
1415
|
+
.raw()
|
|
1416
|
+
.toBuffer({ resolveWithObject: true });
|
|
1417
|
+
for (let i = 3; i < data.length; i += 4) {
|
|
1418
|
+
data[i] = 255 - data[i];
|
|
1419
|
+
}
|
|
1420
|
+
const png = await sharp(data, {
|
|
1421
|
+
raw: { width: info.width, height: info.height, channels: 4 }
|
|
1422
|
+
})
|
|
1423
|
+
.png()
|
|
1424
|
+
.toBuffer();
|
|
1425
|
+
return new Uint8Array(png);
|
|
1426
|
+
}
|
|
1427
|
+
async imageToImage(images, params) {
|
|
1428
|
+
return this.runImageEdit(images, params);
|
|
1429
|
+
}
|
|
1430
|
+
async inpaint(images, params) {
|
|
1431
|
+
return this.runImageEdit(images, params, params.mask);
|
|
1432
|
+
}
|
|
1433
|
+
/**
|
|
1434
|
+
* Shared OpenAI image-edit call for both image-to-image and inpaint. When
|
|
1435
|
+
* `mask` is supplied the masked region is regenerated.
|
|
1436
|
+
*/
|
|
1437
|
+
async runImageEdit(images, params, mask) {
|
|
1438
|
+
const sources = images.filter((b) => b && b.length > 0);
|
|
1439
|
+
if (sources.length === 0) {
|
|
662
1440
|
throw new Error("image must not be empty.");
|
|
663
1441
|
}
|
|
664
1442
|
if (!params.prompt) {
|
|
@@ -667,19 +1445,32 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
667
1445
|
const prompt = params.negativePrompt
|
|
668
1446
|
? `${params.prompt.trim()}\n\nDo not include: ${params.negativePrompt.trim()}`
|
|
669
1447
|
: params.prompt;
|
|
1448
|
+
// gpt-image-* accepts multiple reference images as an array; fall back to a
|
|
1449
|
+
// single file for one input so older edit models keep working.
|
|
1450
|
+
const imageFiles = await Promise.all(sources.map((b, i) => toFile(Buffer.from(b), `image_${i}.png`, { type: "image/png" })));
|
|
670
1451
|
const request = {
|
|
671
1452
|
model: params.model.id,
|
|
672
|
-
image:
|
|
673
|
-
type: "image/png"
|
|
674
|
-
}),
|
|
1453
|
+
image: imageFiles.length === 1 ? imageFiles[0] : imageFiles,
|
|
675
1454
|
prompt
|
|
676
1455
|
};
|
|
677
|
-
|
|
1456
|
+
// Inpainting: the edit endpoint marks the region to regenerate with the
|
|
1457
|
+
// mask's *transparent* pixels. Our masks use the opposite convention
|
|
1458
|
+
// (opaque/white alpha = edit region, matching FAL), so invert the alpha
|
|
1459
|
+
// channel before sending.
|
|
1460
|
+
if (mask && mask.length > 0) {
|
|
1461
|
+
const openAiMask = await this.toOpenAiEditMask(mask);
|
|
1462
|
+
request.mask = await toFile(Buffer.from(openAiMask), "mask.png", {
|
|
1463
|
+
type: "image/png"
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
const size = this.resolveImageSize(params.targetWidth ?? undefined, params.targetHeight ?? undefined, params.model.id);
|
|
678
1467
|
if (size)
|
|
679
1468
|
request.size = size;
|
|
680
1469
|
if (params.quality)
|
|
681
1470
|
request.quality = params.quality;
|
|
682
|
-
|
|
1471
|
+
// SAFETY: a dictionary request against the SDK's closed edit params.
|
|
1472
|
+
const response = (await this.getClient().images.edit(request, { signal: params.signal }));
|
|
1473
|
+
this.trackUsage(params.model.id, openAIImageUsage(response, params.quality));
|
|
683
1474
|
const item = response.data?.[0];
|
|
684
1475
|
if (!item) {
|
|
685
1476
|
throw new Error("OpenAI image editing returned no image data.");
|
|
@@ -704,29 +1495,58 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
704
1495
|
const speed = Math.max(0.25, Math.min(4.0, args.speed ?? 1.0));
|
|
705
1496
|
const speechApi = this.getClient().audio.speech;
|
|
706
1497
|
if (speechApi.with_streaming_response?.create) {
|
|
707
|
-
const
|
|
1498
|
+
const request = {
|
|
708
1499
|
model: args.model,
|
|
709
1500
|
input: args.text,
|
|
710
1501
|
voice,
|
|
711
1502
|
speed,
|
|
712
1503
|
response_format: "pcm"
|
|
713
|
-
}
|
|
1504
|
+
};
|
|
1505
|
+
if (args.model === "gpt-4o-mini-tts" && (args.language || args.instructions)) {
|
|
1506
|
+
request.instructions = [
|
|
1507
|
+
args.language ? `Speak in ${args.language}.` : undefined,
|
|
1508
|
+
args.instructions
|
|
1509
|
+
]
|
|
1510
|
+
.filter((value) => value !== undefined)
|
|
1511
|
+
.join(" ");
|
|
1512
|
+
}
|
|
1513
|
+
const response = await speechApi.with_streaming_response.create(request);
|
|
1514
|
+
// TTS is billed per input character, not per token.
|
|
1515
|
+
this.trackUsage(args.model, { inputCharacters: args.text.length });
|
|
1516
|
+
let carry;
|
|
714
1517
|
for await (const chunk of response.iterBytes(4096)) {
|
|
715
|
-
const
|
|
716
|
-
|
|
1518
|
+
const incoming = asUint8Array(chunk);
|
|
1519
|
+
const bytes = new Uint8Array(incoming.length + (carry == null ? 0 : 1));
|
|
1520
|
+
if (carry != null)
|
|
1521
|
+
bytes[0] = carry;
|
|
1522
|
+
bytes.set(incoming, carry == null ? 0 : 1);
|
|
1523
|
+
const completeLength = bytes.length - (bytes.length % 2);
|
|
1524
|
+
carry =
|
|
1525
|
+
completeLength < bytes.length ? bytes[bytes.length - 1] : undefined;
|
|
1526
|
+
if (completeLength > 0) {
|
|
1527
|
+
yield { samples: toInt16Samples(bytes.subarray(0, completeLength)) };
|
|
1528
|
+
}
|
|
717
1529
|
}
|
|
718
1530
|
return;
|
|
719
1531
|
}
|
|
720
|
-
const
|
|
1532
|
+
const request = {
|
|
721
1533
|
model: args.model,
|
|
722
1534
|
input: args.text,
|
|
723
1535
|
voice,
|
|
724
1536
|
speed,
|
|
725
1537
|
response_format: "pcm"
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
1538
|
+
};
|
|
1539
|
+
if (args.model === "gpt-4o-mini-tts" && (args.language || args.instructions)) {
|
|
1540
|
+
request.instructions = [
|
|
1541
|
+
args.language ? `Speak in ${args.language}.` : undefined,
|
|
1542
|
+
args.instructions
|
|
1543
|
+
]
|
|
1544
|
+
.filter((value) => value !== undefined)
|
|
1545
|
+
.join(" ");
|
|
1546
|
+
}
|
|
1547
|
+
const response = await speechApi.create(request);
|
|
1548
|
+
this.trackUsage(args.model, { inputCharacters: args.text.length });
|
|
1549
|
+
const bytes = asUint8Array(isCallable(response.arrayBuffer) ? await response.arrayBuffer() : response);
|
|
730
1550
|
yield { samples: toInt16Samples(bytes) };
|
|
731
1551
|
}
|
|
732
1552
|
/**
|
|
@@ -754,16 +1574,24 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
754
1574
|
const voice = args.voice ?? "alloy";
|
|
755
1575
|
const speed = Math.max(0.25, Math.min(4.0, args.speed ?? 1.0));
|
|
756
1576
|
const speechApi = this.getClient().audio.speech;
|
|
757
|
-
const
|
|
1577
|
+
const request = {
|
|
758
1578
|
model: args.model,
|
|
759
1579
|
input: args.text,
|
|
760
1580
|
voice,
|
|
761
1581
|
speed,
|
|
762
1582
|
response_format: fmt
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
1583
|
+
};
|
|
1584
|
+
if (args.model === "gpt-4o-mini-tts" && (args.language || args.instructions)) {
|
|
1585
|
+
request.instructions = [
|
|
1586
|
+
args.language ? `Speak in ${args.language}.` : undefined,
|
|
1587
|
+
args.instructions
|
|
1588
|
+
]
|
|
1589
|
+
.filter((value) => value !== undefined)
|
|
1590
|
+
.join(" ");
|
|
1591
|
+
}
|
|
1592
|
+
const response = await speechApi.create(request);
|
|
1593
|
+
this.trackUsage(args.model, { inputCharacters: args.text.length });
|
|
1594
|
+
const bytes = asUint8Array(isCallable(response.arrayBuffer) ? await response.arrayBuffer() : response);
|
|
767
1595
|
return { data: bytes, mimeType: formatToMime[fmt] };
|
|
768
1596
|
}
|
|
769
1597
|
async automaticSpeechRecognition(args) {
|
|
@@ -772,25 +1600,53 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
772
1600
|
}
|
|
773
1601
|
const temperature = Math.max(0, Math.min(1, args.temperature ?? 0));
|
|
774
1602
|
const audioPart = new Uint8Array(args.audio);
|
|
1603
|
+
// Label the upload with what the bytes actually are. Hardcoding audio/mpeg
|
|
1604
|
+
// made OpenAI reject anything that was not MP3 with "400 Audio file might
|
|
1605
|
+
// be corrupted or unsupported" — which is every clip coming through
|
|
1606
|
+
// `nodetool.video.ExtractAudio`, since that writes PCM WAV.
|
|
1607
|
+
const audioMime = sniffAudioMime(audioPart);
|
|
1608
|
+
const audioName = `audio.${AUDIO_MIME_EXT[audioMime] ?? "mp3"}`;
|
|
775
1609
|
const fileLike = typeof File !== "undefined"
|
|
776
|
-
? new File([audioPart],
|
|
777
|
-
: Object.assign(new Blob([audioPart], { type:
|
|
778
|
-
name:
|
|
1610
|
+
? new File([audioPart], audioName, { type: audioMime })
|
|
1611
|
+
: Object.assign(new Blob([audioPart], { type: audioMime }), {
|
|
1612
|
+
name: audioName
|
|
779
1613
|
});
|
|
1614
|
+
const isDiarizationModel = args.model.startsWith("gpt-4o-transcribe-diarize");
|
|
1615
|
+
if (args.word_timestamps && args.model !== "whisper-1") {
|
|
1616
|
+
throw new Error("Word timestamps are only supported by whisper-1");
|
|
1617
|
+
}
|
|
780
1618
|
const requestParams = {
|
|
781
1619
|
file: fileLike,
|
|
782
1620
|
model: args.model,
|
|
783
1621
|
language: args.language,
|
|
784
|
-
prompt: args.prompt,
|
|
785
1622
|
temperature
|
|
786
1623
|
};
|
|
1624
|
+
if (!isDiarizationModel && args.prompt) {
|
|
1625
|
+
requestParams.prompt = args.prompt;
|
|
1626
|
+
}
|
|
1627
|
+
if (isDiarizationModel) {
|
|
1628
|
+
requestParams.response_format = "diarized_json";
|
|
1629
|
+
requestParams.chunking_strategy = "auto";
|
|
1630
|
+
}
|
|
787
1631
|
if (args.word_timestamps) {
|
|
788
1632
|
requestParams.response_format = "verbose_json";
|
|
789
1633
|
requestParams.timestamp_granularities = ["word", "segment"];
|
|
790
1634
|
}
|
|
791
1635
|
const response = await this.getClient().audio.transcriptions.create(requestParams);
|
|
1636
|
+
// Whisper/transcribe is billed per second of audio. The duration is only
|
|
1637
|
+
// reported on the verbose/diarized formats (and as `usage.seconds` on the
|
|
1638
|
+
// newer transcribe models); skip accounting when it is absent rather than
|
|
1639
|
+
// guessing.
|
|
1640
|
+
const durationSeconds = isNumber(response.duration)
|
|
1641
|
+
? response.duration
|
|
1642
|
+
: isNumber(response.usage?.seconds)
|
|
1643
|
+
? response.usage.seconds
|
|
1644
|
+
: undefined;
|
|
1645
|
+
if (durationSeconds !== undefined) {
|
|
1646
|
+
this.trackUsage(args.model, { durationSeconds });
|
|
1647
|
+
}
|
|
792
1648
|
const text = String(response.text ?? "");
|
|
793
|
-
if (!args.word_timestamps) {
|
|
1649
|
+
if (!args.word_timestamps && !isDiarizationModel) {
|
|
794
1650
|
return { text };
|
|
795
1651
|
}
|
|
796
1652
|
const chunks = [];
|
|
@@ -820,19 +1676,24 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
820
1676
|
if (!params.prompt) {
|
|
821
1677
|
throw new Error("The input prompt cannot be empty.");
|
|
822
1678
|
}
|
|
823
|
-
const size = OpenAIProvider.resolveVideoSize(params.aspectRatio, params.resolution);
|
|
1679
|
+
const size = OpenAIProvider.resolveVideoSize(params.aspectRatio, params.resolution, params.model.id);
|
|
824
1680
|
const seconds = OpenAIProvider.secondsFromParams(params) ?? 4;
|
|
825
1681
|
const request = {
|
|
826
1682
|
model: params.model.id,
|
|
827
1683
|
prompt: params.prompt,
|
|
828
|
-
size: size ?? "
|
|
1684
|
+
size: size ?? "1280x720",
|
|
829
1685
|
seconds: String(seconds)
|
|
830
1686
|
};
|
|
831
|
-
|
|
1687
|
+
// SAFETY: a dictionary request against the SDK's closed video params.
|
|
1688
|
+
const video = await this.getClient().videos.create(request, { signal: params.signal });
|
|
832
1689
|
if (!video?.id) {
|
|
833
1690
|
throw new Error("OpenAI video create response did not contain a video id");
|
|
834
1691
|
}
|
|
835
|
-
const timeoutMs =
|
|
1692
|
+
const timeoutMs = params.timeoutSeconds != null &&
|
|
1693
|
+
Number.isFinite(params.timeoutSeconds) &&
|
|
1694
|
+
params.timeoutSeconds > 0
|
|
1695
|
+
? params.timeoutSeconds * 1000
|
|
1696
|
+
: 10 * 60 * 1000;
|
|
836
1697
|
const intervalMs = 2000;
|
|
837
1698
|
const start = Date.now();
|
|
838
1699
|
let latest = video;
|
|
@@ -840,8 +1701,17 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
840
1701
|
if (Date.now() - start > timeoutMs) {
|
|
841
1702
|
throw new Error("Video generation timed out");
|
|
842
1703
|
}
|
|
843
|
-
|
|
844
|
-
|
|
1704
|
+
const remainingMs = timeoutMs - (Date.now() - start);
|
|
1705
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(intervalMs, remainingMs)));
|
|
1706
|
+
if (Date.now() - start >= timeoutMs) {
|
|
1707
|
+
throw new Error("Video generation timed out");
|
|
1708
|
+
}
|
|
1709
|
+
if (params.signal?.aborted) {
|
|
1710
|
+
throw new Error("Video generation cancelled");
|
|
1711
|
+
}
|
|
1712
|
+
latest = await this.getClient().videos.retrieve(video.id, {
|
|
1713
|
+
signal: params.signal
|
|
1714
|
+
});
|
|
845
1715
|
}
|
|
846
1716
|
if (latest.status !== "completed") {
|
|
847
1717
|
throw new Error(String(latest.error ??
|
|
@@ -855,14 +1725,13 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
855
1725
|
throw new Error("The input image cannot be empty.");
|
|
856
1726
|
}
|
|
857
1727
|
const [width, height] = OpenAIProvider.extractImageDimensions(image);
|
|
858
|
-
const requestedSize = OpenAIProvider.resolveVideoSize(params.aspectRatio, params.resolution);
|
|
1728
|
+
const requestedSize = OpenAIProvider.resolveVideoSize(params.aspectRatio, params.resolution, params.model.id);
|
|
859
1729
|
const size = requestedSize ?? OpenAIProvider.snapToValidVideoDimensions(width, height);
|
|
860
1730
|
const seconds = OpenAIProvider.secondsFromParams(params) ?? 4;
|
|
861
1731
|
const [targetW, targetH] = size.split("x").map(Number);
|
|
862
|
-
const sharp = (await import("sharp")).default;
|
|
863
1732
|
const resized = width === targetW && height === targetH
|
|
864
1733
|
? image
|
|
865
|
-
: new Uint8Array(await
|
|
1734
|
+
: new Uint8Array(await (await loadSharp())(image)
|
|
866
1735
|
.resize(targetW, targetH, { fit: "cover", position: "centre" })
|
|
867
1736
|
.png()
|
|
868
1737
|
.toBuffer());
|
|
@@ -874,11 +1743,15 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
874
1743
|
}),
|
|
875
1744
|
size: size,
|
|
876
1745
|
seconds: String(seconds)
|
|
877
|
-
});
|
|
1746
|
+
}, { signal: params.signal });
|
|
878
1747
|
if (!video?.id) {
|
|
879
1748
|
throw new Error("OpenAI image-to-video create response did not contain a video id");
|
|
880
1749
|
}
|
|
881
|
-
const timeoutMs =
|
|
1750
|
+
const timeoutMs = params.timeoutSeconds != null &&
|
|
1751
|
+
Number.isFinite(params.timeoutSeconds) &&
|
|
1752
|
+
params.timeoutSeconds > 0
|
|
1753
|
+
? params.timeoutSeconds * 1000
|
|
1754
|
+
: 10 * 60 * 1000;
|
|
882
1755
|
const intervalMs = 2000;
|
|
883
1756
|
const start = Date.now();
|
|
884
1757
|
let latest = video;
|
|
@@ -886,8 +1759,17 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
886
1759
|
if (Date.now() - start > timeoutMs) {
|
|
887
1760
|
throw new Error("Image-to-video generation timed out");
|
|
888
1761
|
}
|
|
889
|
-
|
|
890
|
-
|
|
1762
|
+
const remainingMs = timeoutMs - (Date.now() - start);
|
|
1763
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(intervalMs, remainingMs)));
|
|
1764
|
+
if (Date.now() - start >= timeoutMs) {
|
|
1765
|
+
throw new Error("Image-to-video generation timed out");
|
|
1766
|
+
}
|
|
1767
|
+
if (params.signal?.aborted) {
|
|
1768
|
+
throw new Error("Image-to-video generation cancelled");
|
|
1769
|
+
}
|
|
1770
|
+
latest = await this.getClient().videos.retrieve(video.id, {
|
|
1771
|
+
signal: params.signal
|
|
1772
|
+
});
|
|
891
1773
|
}
|
|
892
1774
|
if (latest.status !== "completed") {
|
|
893
1775
|
throw new Error(String(latest.error ??
|
|
@@ -901,13 +1783,16 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
901
1783
|
if (input.length === 0 || input.every((v) => !v)) {
|
|
902
1784
|
throw new Error("text must not be empty");
|
|
903
1785
|
}
|
|
904
|
-
const
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
1786
|
+
const request = { model: args.model, input };
|
|
1787
|
+
if (args.dimensions) {
|
|
1788
|
+
request.dimensions = args.dimensions;
|
|
1789
|
+
}
|
|
1790
|
+
const response = await this.getClient().embeddings.create(request);
|
|
909
1791
|
return response.data.map((row) => row.embedding);
|
|
910
1792
|
}
|
|
1793
|
+
isContextExceededError(error) {
|
|
1794
|
+
return openAIContextExceeded(error) !== null;
|
|
1795
|
+
}
|
|
911
1796
|
isContextLengthError(error) {
|
|
912
1797
|
const msg = String(error).toLowerCase();
|
|
913
1798
|
return msg.includes("context length") || msg.includes("maximum context");
|