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