@nodetool-ai/runtime 0.7.0-rc.23 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -0
- package/dist/.nodetool-build-stamp +1 -0
- package/dist/agent-memory.d.ts +118 -0
- package/dist/agent-memory.d.ts.map +1 -0
- package/dist/agent-memory.js +186 -0
- package/dist/agent-memory.js.map +1 -0
- package/dist/async-local-storage.d.ts +20 -0
- package/dist/async-local-storage.d.ts.map +1 -0
- package/dist/async-local-storage.js +28 -0
- package/dist/async-local-storage.js.map +1 -0
- package/dist/blender-executor.d.ts +93 -0
- package/dist/blender-executor.d.ts.map +1 -0
- package/dist/blender-executor.js +294 -0
- package/dist/blender-executor.js.map +1 -0
- package/dist/comfy-executor.d.ts +23 -1
- package/dist/comfy-executor.d.ts.map +1 -1
- package/dist/comfy-executor.js +234 -55
- package/dist/comfy-executor.js.map +1 -1
- package/dist/context-packer.d.ts +1 -1
- package/dist/context-packer.d.ts.map +1 -1
- package/dist/context-packer.js +23 -25
- package/dist/context-packer.js.map +1 -1
- package/dist/context.d.ts +954 -127
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +2424 -513
- package/dist/context.js.map +1 -1
- package/dist/cost-reconciler.d.ts +32 -0
- package/dist/cost-reconciler.d.ts.map +1 -0
- package/dist/cost-reconciler.js +21 -0
- package/dist/cost-reconciler.js.map +1 -0
- package/dist/entities.d.ts +33 -0
- package/dist/entities.d.ts.map +1 -0
- package/dist/entities.js +43 -0
- package/dist/entities.js.map +1 -0
- package/dist/external-media-fetch.d.ts +29 -0
- package/dist/external-media-fetch.d.ts.map +1 -0
- package/dist/external-media-fetch.js +38 -0
- package/dist/external-media-fetch.js.map +1 -0
- package/dist/generation-receipt.d.ts +86 -0
- package/dist/generation-receipt.d.ts.map +1 -0
- package/dist/generation-receipt.js +137 -0
- package/dist/generation-receipt.js.map +1 -0
- package/dist/generation-registry.d.ts +67 -0
- package/dist/generation-registry.d.ts.map +1 -0
- package/dist/generation-registry.js +126 -0
- package/dist/generation-registry.js.map +1 -0
- package/dist/google/client.d.ts +196 -0
- package/dist/google/client.d.ts.map +1 -0
- package/dist/google/client.js +396 -0
- package/dist/google/client.js.map +1 -0
- package/dist/google/index.d.ts +3 -0
- package/dist/google/index.d.ts.map +1 -0
- package/dist/google/index.js +3 -0
- package/dist/google/index.js.map +1 -0
- package/dist/google/token.d.ts +15 -0
- package/dist/google/token.d.ts.map +1 -0
- package/dist/google/token.js +24 -0
- package/dist/google/token.js.map +1 -0
- package/dist/host-binaries.d.ts +106 -0
- package/dist/host-binaries.d.ts.map +1 -0
- package/dist/host-binaries.js +344 -0
- package/dist/host-binaries.js.map +1 -0
- package/dist/image-codec.d.ts +74 -0
- package/dist/image-codec.d.ts.map +1 -0
- package/dist/image-codec.js +273 -0
- package/dist/image-codec.js.map +1 -0
- package/dist/index.d.ts +56 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +43 -4
- package/dist/index.js.map +1 -1
- package/dist/invocation-account.d.ts +43 -0
- package/dist/invocation-account.d.ts.map +1 -0
- package/dist/invocation-account.js +60 -0
- package/dist/invocation-account.js.map +1 -0
- package/dist/media-ref-bytes.d.ts +19 -0
- package/dist/media-ref-bytes.d.ts.map +1 -0
- package/dist/media-ref-bytes.js +181 -0
- package/dist/media-ref-bytes.js.map +1 -0
- package/dist/node-executor.d.ts +113 -2
- package/dist/node-executor.d.ts.map +1 -1
- package/dist/permission-gate.d.ts +152 -0
- package/dist/permission-gate.d.ts.map +1 -0
- package/dist/permission-gate.js +87 -0
- package/dist/permission-gate.js.map +1 -0
- package/dist/prefixed-storage-adapter.d.ts +33 -0
- package/dist/prefixed-storage-adapter.d.ts.map +1 -0
- package/dist/prefixed-storage-adapter.js +70 -0
- package/dist/prefixed-storage-adapter.js.map +1 -0
- package/dist/prompt-asset-refs.d.ts +222 -0
- package/dist/prompt-asset-refs.d.ts.map +1 -0
- package/dist/prompt-asset-refs.js +751 -0
- package/dist/prompt-asset-refs.js.map +1 -0
- package/dist/provider-cache.d.ts +3 -0
- package/dist/provider-cache.d.ts.map +1 -0
- package/dist/provider-cache.js +8 -0
- package/dist/provider-cache.js.map +1 -0
- package/dist/providers/aki-provider.d.ts +33 -10
- package/dist/providers/aki-provider.d.ts.map +1 -1
- package/dist/providers/aki-provider.js +84 -77
- package/dist/providers/aki-provider.js.map +1 -1
- package/dist/providers/alibaba-provider.d.ts +39 -0
- package/dist/providers/alibaba-provider.d.ts.map +1 -0
- package/dist/providers/alibaba-provider.js +86 -0
- package/dist/providers/alibaba-provider.js.map +1 -0
- package/dist/providers/anthropic-provider.d.ts +52 -3
- package/dist/providers/anthropic-provider.d.ts.map +1 -1
- package/dist/providers/anthropic-provider.js +1020 -338
- package/dist/providers/anthropic-provider.js.map +1 -1
- package/dist/providers/atlascloud-provider.d.ts +101 -0
- package/dist/providers/atlascloud-provider.d.ts.map +1 -0
- package/dist/providers/atlascloud-provider.js +1009 -0
- package/dist/providers/atlascloud-provider.js.map +1 -0
- package/dist/providers/atlascloud-transport.d.ts +100 -0
- package/dist/providers/atlascloud-transport.d.ts.map +1 -0
- package/dist/providers/atlascloud-transport.js +364 -0
- package/dist/providers/atlascloud-transport.js.map +1 -0
- package/dist/providers/atlascloud-webhook-registry.d.ts +35 -0
- package/dist/providers/atlascloud-webhook-registry.d.ts.map +1 -0
- package/dist/providers/atlascloud-webhook-registry.js +104 -0
- package/dist/providers/atlascloud-webhook-registry.js.map +1 -0
- package/dist/providers/audio-mime.d.ts +11 -0
- package/dist/providers/audio-mime.d.ts.map +1 -0
- package/dist/providers/audio-mime.js +46 -0
- package/dist/providers/audio-mime.js.map +1 -0
- package/dist/providers/base-provider.d.ts +481 -24
- package/dist/providers/base-provider.d.ts.map +1 -1
- package/dist/providers/base-provider.js +1259 -42
- package/dist/providers/base-provider.js.map +1 -1
- package/dist/providers/cassette-provider.d.ts +217 -0
- package/dist/providers/cassette-provider.d.ts.map +1 -0
- package/dist/providers/cassette-provider.js +502 -0
- package/dist/providers/cassette-provider.js.map +1 -0
- package/dist/providers/cerebras-provider.d.ts +6 -12
- package/dist/providers/cerebras-provider.d.ts.map +1 -1
- package/dist/providers/cerebras-provider.js +8 -33
- package/dist/providers/cerebras-provider.js.map +1 -1
- package/dist/providers/claude-agent-provider.d.ts +131 -68
- package/dist/providers/claude-agent-provider.d.ts.map +1 -1
- package/dist/providers/claude-agent-provider.js +1095 -515
- package/dist/providers/claude-agent-provider.js.map +1 -1
- package/dist/providers/codex-provider.d.ts +82 -0
- package/dist/providers/codex-provider.d.ts.map +1 -0
- package/dist/providers/codex-provider.js +492 -0
- package/dist/providers/codex-provider.js.map +1 -0
- package/dist/providers/cohere-provider.d.ts +5 -2
- package/dist/providers/cohere-provider.d.ts.map +1 -1
- package/dist/providers/cohere-provider.js +9 -2
- package/dist/providers/cohere-provider.js.map +1 -1
- package/dist/providers/comfy-cloud-provider.d.ts +30 -0
- package/dist/providers/comfy-cloud-provider.d.ts.map +1 -0
- package/dist/providers/comfy-cloud-provider.js +42 -0
- package/dist/providers/comfy-cloud-provider.js.map +1 -0
- package/dist/providers/content-filter.d.ts +46 -0
- package/dist/providers/content-filter.d.ts.map +1 -0
- package/dist/providers/content-filter.js +92 -0
- package/dist/providers/content-filter.js.map +1 -0
- package/dist/providers/context-exceeded.d.ts +37 -0
- package/dist/providers/context-exceeded.d.ts.map +1 -0
- package/dist/providers/context-exceeded.js +114 -0
- package/dist/providers/context-exceeded.js.map +1 -0
- package/dist/providers/context-window.d.ts +44 -0
- package/dist/providers/context-window.d.ts.map +1 -0
- package/dist/providers/context-window.js +145 -0
- package/dist/providers/context-window.js.map +1 -0
- package/dist/providers/contract/index.d.ts +4 -0
- package/dist/providers/contract/index.d.ts.map +1 -0
- package/dist/providers/contract/index.js +4 -0
- package/dist/providers/contract/index.js.map +1 -0
- package/dist/providers/contract/probe-manifest.d.ts +75 -0
- package/dist/providers/contract/probe-manifest.d.ts.map +1 -0
- package/dist/providers/contract/probe-manifest.js +379 -0
- package/dist/providers/contract/probe-manifest.js.map +1 -0
- package/dist/providers/contract/probe-runner.d.ts +57 -0
- package/dist/providers/contract/probe-runner.d.ts.map +1 -0
- package/dist/providers/contract/probe-runner.js +180 -0
- package/dist/providers/contract/probe-runner.js.map +1 -0
- package/dist/providers/contract/redact.d.ts +29 -0
- package/dist/providers/contract/redact.d.ts.map +1 -0
- package/dist/providers/contract/redact.js +113 -0
- package/dist/providers/contract/redact.js.map +1 -0
- package/dist/providers/core-tools.d.ts +74 -0
- package/dist/providers/core-tools.d.ts.map +1 -0
- package/dist/providers/core-tools.js +127 -0
- package/dist/providers/core-tools.js.map +1 -0
- package/dist/providers/cost-calculator.d.ts +69 -50
- package/dist/providers/cost-calculator.d.ts.map +1 -1
- package/dist/providers/cost-calculator.js +207 -397
- package/dist/providers/cost-calculator.js.map +1 -1
- package/dist/providers/credential-check.d.ts +45 -0
- package/dist/providers/credential-check.d.ts.map +1 -0
- package/dist/providers/credential-check.js +159 -0
- package/dist/providers/credential-check.js.map +1 -0
- package/dist/providers/custom-model-kinds.d.ts +33 -0
- package/dist/providers/custom-model-kinds.d.ts.map +1 -0
- package/dist/providers/custom-model-kinds.js +96 -0
- package/dist/providers/custom-model-kinds.js.map +1 -0
- package/dist/providers/custom-openai-provider.d.ts +81 -0
- package/dist/providers/custom-openai-provider.d.ts.map +1 -0
- package/dist/providers/custom-openai-provider.js +182 -0
- package/dist/providers/custom-openai-provider.js.map +1 -0
- package/dist/providers/custom-provider-registry.d.ts +19 -0
- package/dist/providers/custom-provider-registry.d.ts.map +1 -0
- package/dist/providers/custom-provider-registry.js +52 -0
- package/dist/providers/custom-provider-registry.js.map +1 -0
- package/dist/providers/deepseek-provider.d.ts +8 -14
- package/dist/providers/deepseek-provider.d.ts.map +1 -1
- package/dist/providers/deepseek-provider.js +6 -35
- package/dist/providers/deepseek-provider.js.map +1 -1
- package/dist/providers/defaults.d.ts +12 -0
- package/dist/providers/defaults.d.ts.map +1 -0
- package/dist/providers/defaults.js +12 -0
- package/dist/providers/defaults.js.map +1 -0
- package/dist/providers/elevenlabs-provider.d.ts +49 -0
- package/dist/providers/elevenlabs-provider.d.ts.map +1 -0
- package/dist/providers/elevenlabs-provider.js +142 -0
- package/dist/providers/elevenlabs-provider.js.map +1 -0
- package/dist/providers/entity-references.d.ts +31 -0
- package/dist/providers/entity-references.d.ts.map +1 -0
- package/dist/providers/entity-references.js +113 -0
- package/dist/providers/entity-references.js.map +1 -0
- package/dist/providers/evolink-provider.d.ts +38 -0
- package/dist/providers/evolink-provider.d.ts.map +1 -0
- package/dist/providers/evolink-provider.js +304 -0
- package/dist/providers/evolink-provider.js.map +1 -0
- package/dist/providers/fake-provider.d.ts +72 -1
- package/dist/providers/fake-provider.d.ts.map +1 -1
- package/dist/providers/fake-provider.js +203 -1
- package/dist/providers/fake-provider.js.map +1 -1
- package/dist/providers/fal-generations.d.ts +106 -0
- package/dist/providers/fal-generations.d.ts.map +1 -0
- package/dist/providers/fal-generations.js +286 -0
- package/dist/providers/fal-generations.js.map +1 -0
- package/dist/providers/fal-provider.d.ts +171 -7
- package/dist/providers/fal-provider.d.ts.map +1 -1
- package/dist/providers/fal-provider.js +1369 -64
- package/dist/providers/fal-provider.js.map +1 -1
- package/dist/providers/fal-queue.d.ts +47 -0
- package/dist/providers/fal-queue.d.ts.map +1 -0
- package/dist/providers/fal-queue.js +231 -0
- package/dist/providers/fal-queue.js.map +1 -0
- package/dist/providers/gemini-provider.d.ts +148 -8
- package/dist/providers/gemini-provider.d.ts.map +1 -1
- package/dist/providers/gemini-provider.js +1500 -316
- package/dist/providers/gemini-provider.js.map +1 -1
- package/dist/providers/gmi-provider.d.ts +21 -0
- package/dist/providers/gmi-provider.d.ts.map +1 -0
- package/dist/providers/gmi-provider.js +34 -0
- package/dist/providers/gmi-provider.js.map +1 -0
- package/dist/providers/gpt-image-size.d.ts +3 -0
- package/dist/providers/gpt-image-size.d.ts.map +1 -0
- package/dist/providers/gpt-image-size.js +37 -0
- package/dist/providers/gpt-image-size.js.map +1 -0
- package/dist/providers/groq-provider.d.ts +9 -12
- package/dist/providers/groq-provider.d.ts.map +1 -1
- package/dist/providers/groq-provider.js +19 -33
- package/dist/providers/groq-provider.js.map +1 -1
- package/dist/providers/groq-request.d.ts +38 -0
- package/dist/providers/groq-request.d.ts.map +1 -0
- package/dist/providers/groq-request.js +138 -0
- package/dist/providers/groq-request.js.map +1 -0
- package/dist/providers/higgsfield-provider.d.ts +30 -0
- package/dist/providers/higgsfield-provider.d.ts.map +1 -0
- package/dist/providers/higgsfield-provider.js +131 -0
- package/dist/providers/higgsfield-provider.js.map +1 -0
- package/dist/providers/higgsfield-transport.d.ts +106 -0
- package/dist/providers/higgsfield-transport.d.ts.map +1 -0
- package/dist/providers/higgsfield-transport.js +182 -0
- package/dist/providers/higgsfield-transport.js.map +1 -0
- package/dist/providers/http-transport.d.ts +74 -0
- package/dist/providers/http-transport.d.ts.map +1 -0
- package/dist/providers/http-transport.js +152 -0
- package/dist/providers/http-transport.js.map +1 -0
- package/dist/providers/huggingface-provider.d.ts +71 -3
- package/dist/providers/huggingface-provider.d.ts.map +1 -1
- package/dist/providers/huggingface-provider.js +230 -34
- package/dist/providers/huggingface-provider.js.map +1 -1
- package/dist/providers/image-mime.d.ts +39 -0
- package/dist/providers/image-mime.d.ts.map +1 -0
- package/dist/providers/image-mime.js +111 -0
- package/dist/providers/image-mime.js.map +1 -0
- package/dist/providers/image-ref.d.ts +21 -0
- package/dist/providers/image-ref.d.ts.map +1 -0
- package/dist/providers/image-ref.js +44 -0
- package/dist/providers/image-ref.js.map +1 -0
- package/dist/providers/index.d.ts +84 -8
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +239 -56
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/jina-provider.d.ts +10 -2
- package/dist/providers/jina-provider.d.ts.map +1 -1
- package/dist/providers/jina-provider.js +16 -7
- package/dist/providers/jina-provider.js.map +1 -1
- package/dist/providers/kie-provider.d.ts +170 -9
- package/dist/providers/kie-provider.d.ts.map +1 -1
- package/dist/providers/kie-provider.js +1176 -228
- package/dist/providers/kie-provider.js.map +1 -1
- package/dist/providers/kie-webhook-registry.d.ts +28 -0
- package/dist/providers/kie-webhook-registry.d.ts.map +1 -0
- package/dist/providers/kie-webhook-registry.js +91 -0
- package/dist/providers/kie-webhook-registry.js.map +1 -0
- package/dist/providers/llama-provider.d.ts +21 -37
- package/dist/providers/llama-provider.d.ts.map +1 -1
- package/dist/providers/llama-provider.js +43 -354
- package/dist/providers/llama-provider.js.map +1 -1
- package/dist/providers/lmstudio-provider.d.ts +4 -7
- package/dist/providers/lmstudio-provider.d.ts.map +1 -1
- package/dist/providers/lmstudio-provider.js +24 -15
- package/dist/providers/lmstudio-provider.js.map +1 -1
- package/dist/providers/manifest-models.d.ts +341 -1
- package/dist/providers/manifest-models.d.ts.map +1 -1
- package/dist/providers/manifest-models.js +1106 -22
- package/dist/providers/manifest-models.js.map +1 -1
- package/dist/providers/media-mime.d.ts +14 -0
- package/dist/providers/media-mime.d.ts.map +1 -0
- package/dist/providers/media-mime.js +35 -0
- package/dist/providers/media-mime.js.map +1 -0
- package/dist/providers/meshy-provider.d.ts +14 -1
- package/dist/providers/meshy-provider.d.ts.map +1 -1
- package/dist/providers/meshy-provider.js +76 -46
- package/dist/providers/meshy-provider.js.map +1 -1
- package/dist/providers/meta-provider.d.ts +22 -0
- package/dist/providers/meta-provider.d.ts.map +1 -0
- package/dist/providers/meta-provider.js +54 -0
- package/dist/providers/meta-provider.js.map +1 -0
- package/dist/providers/minimax-provider.d.ts +25 -18
- package/dist/providers/minimax-provider.d.ts.map +1 -1
- package/dist/providers/minimax-provider.js +211 -72
- package/dist/providers/minimax-provider.js.map +1 -1
- package/dist/providers/mistral-provider.d.ts +6 -12
- package/dist/providers/mistral-provider.d.ts.map +1 -1
- package/dist/providers/mistral-provider.js +10 -34
- package/dist/providers/mistral-provider.js.map +1 -1
- package/dist/providers/moonshot-provider.d.ts +11 -15
- package/dist/providers/moonshot-provider.d.ts.map +1 -1
- package/dist/providers/moonshot-provider.js +33 -23
- package/dist/providers/moonshot-provider.js.map +1 -1
- package/dist/providers/node-llama-cpp-provider.d.ts +98 -0
- package/dist/providers/node-llama-cpp-provider.d.ts.map +1 -0
- package/dist/providers/node-llama-cpp-provider.js +479 -0
- package/dist/providers/node-llama-cpp-provider.js.map +1 -0
- package/dist/providers/nodetool-provider.d.ts +49 -0
- package/dist/providers/nodetool-provider.d.ts.map +1 -0
- package/dist/providers/nodetool-provider.js +224 -0
- package/dist/providers/nodetool-provider.js.map +1 -0
- package/dist/providers/oauth/browser-launcher.d.ts +23 -0
- package/dist/providers/oauth/browser-launcher.d.ts.map +1 -0
- package/dist/providers/oauth/browser-launcher.js +45 -0
- package/dist/providers/oauth/browser-launcher.js.map +1 -0
- package/dist/providers/oauth/claude-code-credentials.d.ts +74 -0
- package/dist/providers/oauth/claude-code-credentials.d.ts.map +1 -0
- package/dist/providers/oauth/claude-code-credentials.js +133 -0
- package/dist/providers/oauth/claude-code-credentials.js.map +1 -0
- package/dist/providers/oauth/claude-code-login.d.ts +124 -0
- package/dist/providers/oauth/claude-code-login.d.ts.map +1 -0
- package/dist/providers/oauth/claude-code-login.js +230 -0
- package/dist/providers/oauth/claude-code-login.js.map +1 -0
- package/dist/providers/oauth/claude-code-oauth-client.d.ts +111 -0
- package/dist/providers/oauth/claude-code-oauth-client.d.ts.map +1 -0
- package/dist/providers/oauth/claude-code-oauth-client.js +207 -0
- package/dist/providers/oauth/claude-code-oauth-client.js.map +1 -0
- package/dist/providers/oauth/errors.d.ts +54 -0
- package/dist/providers/oauth/errors.d.ts.map +1 -0
- package/dist/providers/oauth/errors.js +79 -0
- package/dist/providers/oauth/errors.js.map +1 -0
- package/dist/providers/oauth/index.d.ts +105 -0
- package/dist/providers/oauth/index.d.ts.map +1 -0
- package/dist/providers/oauth/index.js +167 -0
- package/dist/providers/oauth/index.js.map +1 -0
- package/dist/providers/oauth/jwt-claims.d.ts +23 -0
- package/dist/providers/oauth/jwt-claims.d.ts.map +1 -0
- package/dist/providers/oauth/jwt-claims.js +67 -0
- package/dist/providers/oauth/jwt-claims.js.map +1 -0
- package/dist/providers/oauth/local-callback-server.d.ts +60 -0
- package/dist/providers/oauth/local-callback-server.d.ts.map +1 -0
- package/dist/providers/oauth/local-callback-server.js +148 -0
- package/dist/providers/oauth/local-callback-server.js.map +1 -0
- package/dist/providers/oauth/oauth-client.d.ts +55 -0
- package/dist/providers/oauth/oauth-client.d.ts.map +1 -0
- package/dist/providers/oauth/oauth-client.js +161 -0
- package/dist/providers/oauth/oauth-client.js.map +1 -0
- package/dist/providers/oauth/openai-oauth-provider.d.ts +129 -0
- package/dist/providers/oauth/openai-oauth-provider.d.ts.map +1 -0
- package/dist/providers/oauth/openai-oauth-provider.js +265 -0
- package/dist/providers/oauth/openai-oauth-provider.js.map +1 -0
- package/dist/providers/oauth/pkce-helper.d.ts +43 -0
- package/dist/providers/oauth/pkce-helper.d.ts.map +1 -0
- package/dist/providers/oauth/pkce-helper.js +57 -0
- package/dist/providers/oauth/pkce-helper.js.map +1 -0
- package/dist/providers/oauth/redaction.d.ts +22 -0
- package/dist/providers/oauth/redaction.d.ts.map +1 -0
- package/dist/providers/oauth/redaction.js +59 -0
- package/dist/providers/oauth/redaction.js.map +1 -0
- package/dist/providers/oauth/secure-credential-store.d.ts +67 -0
- package/dist/providers/oauth/secure-credential-store.d.ts.map +1 -0
- package/dist/providers/oauth/secure-credential-store.js +73 -0
- package/dist/providers/oauth/secure-credential-store.js.map +1 -0
- package/dist/providers/oauth/token-store.d.ts +53 -0
- package/dist/providers/oauth/token-store.d.ts.map +1 -0
- package/dist/providers/oauth/token-store.js +78 -0
- package/dist/providers/oauth/token-store.js.map +1 -0
- package/dist/providers/oauth/types.d.ts +64 -0
- package/dist/providers/oauth/types.d.ts.map +1 -0
- package/dist/providers/oauth/types.js +12 -0
- package/dist/providers/oauth/types.js.map +1 -0
- package/dist/providers/object-tracking.d.ts +47 -0
- package/dist/providers/object-tracking.d.ts.map +1 -0
- package/dist/providers/object-tracking.js +46 -0
- package/dist/providers/object-tracking.js.map +1 -0
- package/dist/providers/offline-model-index.d.ts +20 -0
- package/dist/providers/offline-model-index.d.ts.map +1 -0
- package/dist/providers/offline-model-index.js +112 -0
- package/dist/providers/offline-model-index.js.map +1 -0
- package/dist/providers/ollama-provider.d.ts +28 -1
- package/dist/providers/ollama-provider.d.ts.map +1 -1
- package/dist/providers/ollama-provider.js +193 -43
- package/dist/providers/ollama-provider.js.map +1 -1
- package/dist/providers/openai-compat/client.d.ts +57 -0
- package/dist/providers/openai-compat/client.d.ts.map +1 -0
- package/dist/providers/openai-compat/client.js +157 -0
- package/dist/providers/openai-compat/client.js.map +1 -0
- package/dist/providers/openai-compat/decode.d.ts +36 -0
- package/dist/providers/openai-compat/decode.d.ts.map +1 -0
- package/dist/providers/openai-compat/decode.js +55 -0
- package/dist/providers/openai-compat/decode.js.map +1 -0
- package/dist/providers/openai-compat/errors.d.ts +54 -0
- package/dist/providers/openai-compat/errors.d.ts.map +1 -0
- package/dist/providers/openai-compat/errors.js +144 -0
- package/dist/providers/openai-compat/errors.js.map +1 -0
- package/dist/providers/openai-compat/index.d.ts +8 -0
- package/dist/providers/openai-compat/index.d.ts.map +1 -0
- package/dist/providers/openai-compat/index.js +5 -0
- package/dist/providers/openai-compat/index.js.map +1 -0
- package/dist/providers/openai-compat/sse.d.ts +14 -0
- package/dist/providers/openai-compat/sse.d.ts.map +1 -0
- package/dist/providers/openai-compat/sse.js +69 -0
- package/dist/providers/openai-compat/sse.js.map +1 -0
- package/dist/providers/openai-compat/types.d.ts +75 -0
- package/dist/providers/openai-compat/types.d.ts.map +1 -0
- package/dist/providers/openai-compat/types.js +9 -0
- package/dist/providers/openai-compat/types.js.map +1 -0
- package/dist/providers/openai-compat-provider.d.ts +90 -0
- package/dist/providers/openai-compat-provider.d.ts.map +1 -0
- package/dist/providers/openai-compat-provider.js +282 -0
- package/dist/providers/openai-compat-provider.js.map +1 -0
- package/dist/providers/openai-provider.d.ts +89 -26
- package/dist/providers/openai-provider.d.ts.map +1 -1
- package/dist/providers/openai-provider.js +990 -105
- package/dist/providers/openai-provider.js.map +1 -1
- package/dist/providers/openrouter-provider.d.ts +6 -12
- package/dist/providers/openrouter-provider.d.ts.map +1 -1
- package/dist/providers/openrouter-provider.js +24 -53
- package/dist/providers/openrouter-provider.js.map +1 -1
- package/dist/providers/provider-call-failure.d.ts +39 -0
- package/dist/providers/provider-call-failure.d.ts.map +1 -0
- package/dist/providers/provider-call-failure.js +132 -0
- package/dist/providers/provider-call-failure.js.map +1 -0
- package/dist/providers/provider-error.d.ts +59 -0
- package/dist/providers/provider-error.d.ts.map +1 -0
- package/dist/providers/provider-error.js +165 -0
- package/dist/providers/provider-error.js.map +1 -0
- package/dist/providers/provider-generations.d.ts +99 -0
- package/dist/providers/provider-generations.d.ts.map +1 -0
- package/dist/providers/provider-generations.js +71 -0
- package/dist/providers/provider-generations.js.map +1 -0
- package/dist/providers/provider-queue.d.ts +62 -0
- package/dist/providers/provider-queue.d.ts.map +1 -0
- package/dist/providers/provider-queue.js +28 -0
- package/dist/providers/provider-queue.js.map +1 -0
- package/dist/providers/provider-registry.d.ts +33 -14
- package/dist/providers/provider-registry.d.ts.map +1 -1
- package/dist/providers/provider-registry.js +85 -68
- package/dist/providers/provider-registry.js.map +1 -1
- package/dist/providers/provider-request-log.d.ts +53 -0
- package/dist/providers/provider-request-log.d.ts.map +1 -0
- package/dist/providers/provider-request-log.js +157 -0
- package/dist/providers/provider-request-log.js.map +1 -0
- package/dist/providers/provider-session.d.ts +3 -0
- package/dist/providers/provider-session.d.ts.map +1 -0
- package/dist/providers/provider-session.js +10 -0
- package/dist/providers/provider-session.js.map +1 -0
- package/dist/providers/provider-transport.d.ts +11 -0
- package/dist/providers/provider-transport.d.ts.map +1 -0
- package/dist/providers/provider-transport.js +11 -0
- package/dist/providers/provider-transport.js.map +1 -0
- package/dist/providers/python-provider.d.ts +20 -12
- package/dist/providers/python-provider.d.ts.map +1 -1
- package/dist/providers/python-provider.js +185 -25
- package/dist/providers/python-provider.js.map +1 -1
- package/dist/providers/replicate-provider.d.ts +89 -7
- package/dist/providers/replicate-provider.d.ts.map +1 -1
- package/dist/providers/replicate-provider.js +801 -65
- package/dist/providers/replicate-provider.js.map +1 -1
- package/dist/providers/replicate-retry.d.ts +30 -0
- package/dist/providers/replicate-retry.d.ts.map +1 -0
- package/dist/providers/replicate-retry.js +97 -0
- package/dist/providers/replicate-retry.js.map +1 -0
- package/dist/providers/responses-api.d.ts +27 -0
- package/dist/providers/responses-api.d.ts.map +1 -0
- package/dist/providers/responses-api.js +297 -0
- package/dist/providers/responses-api.js.map +1 -0
- package/dist/providers/reve-provider.d.ts +45 -0
- package/dist/providers/reve-provider.d.ts.map +1 -0
- package/dist/providers/reve-provider.js +159 -0
- package/dist/providers/reve-provider.js.map +1 -0
- package/dist/providers/rodin-provider.d.ts +23 -1
- package/dist/providers/rodin-provider.d.ts.map +1 -1
- package/dist/providers/rodin-provider.js +89 -51
- package/dist/providers/rodin-provider.js.map +1 -1
- package/dist/providers/safe-url.d.ts +70 -0
- package/dist/providers/safe-url.d.ts.map +1 -0
- package/dist/providers/safe-url.js +277 -0
- package/dist/providers/safe-url.js.map +1 -0
- package/dist/providers/scripted-provider.d.ts +9 -0
- package/dist/providers/scripted-provider.d.ts.map +1 -1
- package/dist/providers/scripted-provider.js +86 -10
- package/dist/providers/scripted-provider.js.map +1 -1
- package/dist/providers/structured-output.d.ts +42 -0
- package/dist/providers/structured-output.d.ts.map +1 -0
- package/dist/providers/structured-output.js +83 -0
- package/dist/providers/structured-output.js.map +1 -0
- package/dist/providers/together-provider.d.ts +19 -16
- package/dist/providers/together-provider.d.ts.map +1 -1
- package/dist/providers/together-provider.js +103 -225
- package/dist/providers/together-provider.js.map +1 -1
- package/dist/providers/topaz-provider.d.ts +44 -0
- package/dist/providers/topaz-provider.d.ts.map +1 -0
- package/dist/providers/topaz-provider.js +239 -0
- package/dist/providers/topaz-provider.js.map +1 -0
- package/dist/providers/types.d.ts +603 -4
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/types.js +81 -1
- package/dist/providers/types.js.map +1 -1
- package/dist/providers/useapi-provider.d.ts +27 -0
- package/dist/providers/useapi-provider.d.ts.map +1 -0
- package/dist/providers/useapi-provider.js +266 -0
- package/dist/providers/useapi-provider.js.map +1 -0
- package/dist/providers/video-duration.d.ts +6 -0
- package/dist/providers/video-duration.d.ts.map +1 -0
- package/dist/providers/video-duration.js +63 -0
- package/dist/providers/video-duration.js.map +1 -0
- package/dist/providers/video-frame-fallback.d.ts +39 -0
- package/dist/providers/video-frame-fallback.d.ts.map +1 -0
- package/dist/providers/video-frame-fallback.js +155 -0
- package/dist/providers/video-frame-fallback.js.map +1 -0
- package/dist/providers/video-frames.d.ts +61 -0
- package/dist/providers/video-frames.d.ts.map +1 -0
- package/dist/providers/video-frames.js +208 -0
- package/dist/providers/video-frames.js.map +1 -0
- package/dist/providers/video-mime.d.ts +14 -0
- package/dist/providers/video-mime.d.ts.map +1 -0
- package/dist/providers/video-mime.js +44 -0
- package/dist/providers/video-mime.js.map +1 -0
- package/dist/providers/video-to-audio.d.ts +34 -0
- package/dist/providers/video-to-audio.d.ts.map +1 -0
- package/dist/providers/video-to-audio.js +62 -0
- package/dist/providers/video-to-audio.js.map +1 -0
- package/dist/providers/vllm-provider.d.ts +22 -8
- package/dist/providers/vllm-provider.d.ts.map +1 -1
- package/dist/providers/vllm-provider.js +48 -23
- package/dist/providers/vllm-provider.js.map +1 -1
- package/dist/providers/voyage-provider.d.ts +10 -2
- package/dist/providers/voyage-provider.d.ts.map +1 -1
- package/dist/providers/voyage-provider.js +16 -7
- package/dist/providers/voyage-provider.js.map +1 -1
- package/dist/providers/xai-provider.d.ts +54 -13
- package/dist/providers/xai-provider.d.ts.map +1 -1
- package/dist/providers/xai-provider.js +299 -22
- package/dist/providers/xai-provider.js.map +1 -1
- package/dist/python-bridge-base.d.ts +358 -0
- package/dist/python-bridge-base.d.ts.map +1 -0
- package/dist/python-bridge-base.js +1557 -0
- package/dist/python-bridge-base.js.map +1 -0
- package/dist/python-bridge-codec.d.ts +28 -0
- package/dist/python-bridge-codec.d.ts.map +1 -0
- package/dist/python-bridge-codec.js +39 -0
- package/dist/python-bridge-codec.js.map +1 -0
- package/dist/python-bridge-factory.d.ts +52 -0
- package/dist/python-bridge-factory.d.ts.map +1 -0
- package/dist/python-bridge-factory.js +69 -0
- package/dist/python-bridge-factory.js.map +1 -0
- package/dist/python-bridge-framing.d.ts +68 -0
- package/dist/python-bridge-framing.d.ts.map +1 -0
- package/dist/python-bridge-framing.js +91 -0
- package/dist/python-bridge-framing.js.map +1 -0
- package/dist/python-bridge-types.d.ts +545 -2
- package/dist/python-bridge-types.d.ts.map +1 -1
- package/dist/python-bridge-types.js +49 -1
- package/dist/python-bridge-types.js.map +1 -1
- package/dist/python-graph-resolver.d.ts +57 -0
- package/dist/python-graph-resolver.d.ts.map +1 -0
- package/dist/python-graph-resolver.js +79 -0
- package/dist/python-graph-resolver.js.map +1 -0
- package/dist/python-node-executor.d.ts +36 -4
- package/dist/python-node-executor.d.ts.map +1 -1
- package/dist/python-node-executor.js +177 -86
- package/dist/python-node-executor.js.map +1 -1
- package/dist/python-stdio-bridge.d.ts +18 -37
- package/dist/python-stdio-bridge.d.ts.map +1 -1
- package/dist/python-stdio-bridge.js +148 -475
- package/dist/python-stdio-bridge.js.map +1 -1
- package/dist/python-websocket-bridge.d.ts +140 -0
- package/dist/python-websocket-bridge.d.ts.map +1 -0
- package/dist/python-websocket-bridge.js +561 -0
- package/dist/python-websocket-bridge.js.map +1 -0
- package/dist/python-worker-stderr.d.ts +4 -0
- package/dist/python-worker-stderr.d.ts.map +1 -0
- package/dist/python-worker-stderr.js +24 -0
- package/dist/python-worker-stderr.js.map +1 -0
- package/dist/recommended-models.d.ts +2 -2
- package/dist/recommended-models.d.ts.map +1 -1
- package/dist/recommended-models.js +24 -4
- package/dist/recommended-models.js.map +1 -1
- package/dist/recoverable-node-error.d.ts +31 -0
- package/dist/recoverable-node-error.d.ts.map +1 -0
- package/dist/recoverable-node-error.js +37 -0
- package/dist/recoverable-node-error.js.map +1 -0
- package/dist/redact-params.d.ts +13 -0
- package/dist/redact-params.d.ts.map +1 -0
- package/dist/redact-params.js +57 -0
- package/dist/redact-params.js.map +1 -0
- package/dist/sandbox-module-catalog.d.ts +43 -0
- package/dist/sandbox-module-catalog.d.ts.map +1 -0
- package/dist/sandbox-module-catalog.js +25 -0
- package/dist/sandbox-module-catalog.js.map +1 -0
- package/dist/storage-workspace.d.ts +59 -0
- package/dist/storage-workspace.d.ts.map +1 -0
- package/dist/storage-workspace.js +426 -0
- package/dist/storage-workspace.js.map +1 -0
- package/dist/swappable-python-bridge.d.ts +83 -0
- package/dist/swappable-python-bridge.d.ts.map +1 -0
- package/dist/swappable-python-bridge.js +246 -0
- package/dist/swappable-python-bridge.js.map +1 -0
- package/dist/telemetry.d.ts +7 -0
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +23 -0
- package/dist/telemetry.js.map +1 -1
- package/dist/testing.d.ts +81 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +130 -0
- package/dist/testing.js.map +1 -0
- package/dist/token-counter.d.ts +30 -0
- package/dist/token-counter.d.ts.map +1 -0
- package/dist/token-counter.js +156 -0
- package/dist/token-counter.js.map +1 -0
- package/dist/trace-exporters.d.ts +10 -0
- package/dist/trace-exporters.d.ts.map +1 -1
- package/dist/trace-exporters.js +59 -20
- package/dist/trace-exporters.js.map +1 -1
- package/dist/tracing-helpers.d.ts +45 -4
- package/dist/tracing-helpers.d.ts.map +1 -1
- package/dist/tracing-helpers.js +62 -19
- package/dist/tracing-helpers.js.map +1 -1
- package/dist/turn-budget.d.ts +278 -0
- package/dist/turn-budget.d.ts.map +1 -0
- package/dist/turn-budget.js +365 -0
- package/dist/turn-budget.js.map +1 -0
- package/dist/variable-channel.d.ts +48 -0
- package/dist/variable-channel.d.ts.map +1 -0
- package/dist/variable-channel.js +110 -0
- package/dist/variable-channel.js.map +1 -0
- package/dist/workspace.d.ts +119 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +112 -0
- package/dist/workspace.js.map +1 -0
- package/package.json +107 -19
|
@@ -7,22 +7,140 @@
|
|
|
7
7
|
* endpoints and reuse the existing OpenAI/Anthropic provider implementations
|
|
8
8
|
* where their APIs are wire-compatible.
|
|
9
9
|
*/
|
|
10
|
+
import { recordGenerationReceipt } from "../generation-receipt.js";
|
|
10
11
|
import Anthropic from "@anthropic-ai/sdk";
|
|
11
12
|
import OpenAI from "openai";
|
|
12
13
|
import { BaseProvider } from "./base-provider.js";
|
|
14
|
+
import { safeFetch } from "./safe-url.js";
|
|
15
|
+
import { isString } from "@nodetool-ai/protocol";
|
|
13
16
|
import { createLogger } from "@nodetool-ai/config";
|
|
14
|
-
import { loadVideoModels, loadImageModels } from "./manifest-models.js";
|
|
17
|
+
import { loadVideoModels, loadImageModels, loadMusicModels, loadTTSModels, getModelImageInputs, getModelMediaInputs, getModelReferenceInputs, getModelInputFields, getManifestNodeMeta, selectPrimaryImageInput, selectMaskImageInput } from "./manifest-models.js";
|
|
18
|
+
import { validateReferenceInputs } from "./manifest-models.js";
|
|
19
|
+
import { registerWebhookWait } from "./kie-webhook-registry.js";
|
|
20
|
+
import { sniffAudioMime } from "./audio-mime.js";
|
|
21
|
+
import { detectImageMime, extForImageMime } from "./image-mime.js";
|
|
22
|
+
import { sniffMediaMime } from "./media-mime.js";
|
|
15
23
|
import { OpenAIProvider } from "./openai-provider.js";
|
|
16
24
|
import { AnthropicProvider } from "./anthropic-provider.js";
|
|
25
|
+
import { extractResponsesText, extractResponsesToolCalls, messagesToResponsesInput, responseToolChoice, responseTools, responseUsage, streamResponsesEvents } from "./responses-api.js";
|
|
17
26
|
const log = createLogger("nodetool.runtime.providers.kie");
|
|
27
|
+
function combineSignals(signal, timeout) {
|
|
28
|
+
if (!signal)
|
|
29
|
+
return timeout;
|
|
30
|
+
if (!timeout)
|
|
31
|
+
return signal;
|
|
32
|
+
return AbortSignal.any([signal, timeout]);
|
|
33
|
+
}
|
|
18
34
|
const KIE_API_BASE = "https://api.kie.ai";
|
|
35
|
+
const KIE_UPLOAD_URL = "https://kieai.redpandaai.co/api/file-stream-upload";
|
|
36
|
+
// The Suno API always requires a callBackUrl. When KIE_WEBHOOK_URL is set we
|
|
37
|
+
// use it; otherwise a placeholder satisfies the requirement (we poll instead).
|
|
38
|
+
const KIE_SUNO_CALLBACK = "https://nodetool.ai/kie-callback";
|
|
39
|
+
/**
|
|
40
|
+
* When set, KIE tasks are submitted with a real callBackUrl and the provider
|
|
41
|
+
* waits for the webhook instead of polling. Set to the public base URL of this
|
|
42
|
+
* server (e.g. `https://myserver.example.com`).
|
|
43
|
+
*/
|
|
44
|
+
function getWebhookBaseUrl() {
|
|
45
|
+
const url = process.env.KIE_WEBHOOK_URL;
|
|
46
|
+
return url && url.trim() ? url.replace(/\/+$/, "") : undefined;
|
|
47
|
+
}
|
|
48
|
+
// Mirrors the "Chat Models" section of docs.kie.ai/llms.txt. Each entry's
|
|
49
|
+
// basePath is the model's documented endpoint prefix: the Claude models share
|
|
50
|
+
// /claude/v1/messages, the GPT, Grok, Kimi and DeepSeek models post to a
|
|
51
|
+
// /responses endpoint, and every Gemini (plus GPT 5.2) endpoint is OpenAI
|
|
52
|
+
// chat-completions with the model pinned by the path.
|
|
19
53
|
const KIE_CHAT_MODELS = [
|
|
54
|
+
{
|
|
55
|
+
id: "gpt-6-sol",
|
|
56
|
+
name: "GPT 6 Sol",
|
|
57
|
+
api: "responses",
|
|
58
|
+
basePath: "/codex/v1"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: "gpt-6-astra",
|
|
62
|
+
name: "GPT 6 Astra",
|
|
63
|
+
api: "responses",
|
|
64
|
+
basePath: "/codex/v1"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: "gpt-6-luna",
|
|
68
|
+
name: "GPT 6 Luna",
|
|
69
|
+
api: "responses",
|
|
70
|
+
basePath: "/codex/v1"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: "gpt-5-6-sol",
|
|
74
|
+
name: "GPT 5.6 Sol",
|
|
75
|
+
api: "responses",
|
|
76
|
+
basePath: "/codex/v1"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: "gpt-5-6-terra",
|
|
80
|
+
name: "GPT 5.6 Terra",
|
|
81
|
+
api: "responses",
|
|
82
|
+
basePath: "/codex/v1"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: "gpt-5-6-luna",
|
|
86
|
+
name: "GPT 5.6 Luna",
|
|
87
|
+
api: "responses",
|
|
88
|
+
basePath: "/codex/v1"
|
|
89
|
+
},
|
|
20
90
|
{
|
|
21
91
|
id: "gpt-5-5",
|
|
22
92
|
name: "GPT 5.5",
|
|
23
93
|
api: "responses",
|
|
24
94
|
basePath: "/codex/v1"
|
|
25
95
|
},
|
|
96
|
+
{
|
|
97
|
+
id: "gpt-5-4",
|
|
98
|
+
name: "GPT 5.4",
|
|
99
|
+
api: "responses",
|
|
100
|
+
basePath: "/codex/v1"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: "gpt-5.1-codex",
|
|
104
|
+
name: "GPT Codex",
|
|
105
|
+
api: "responses",
|
|
106
|
+
basePath: "/api/v1"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: "gpt-5-2",
|
|
110
|
+
name: "GPT 5.2",
|
|
111
|
+
api: "openai",
|
|
112
|
+
basePath: "/gpt-5-2/v1"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
id: "claude-opus-5",
|
|
116
|
+
name: "Claude Opus 5",
|
|
117
|
+
api: "anthropic",
|
|
118
|
+
basePath: "/claude"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: "claude-sonnet-5",
|
|
122
|
+
name: "Claude Sonnet 5",
|
|
123
|
+
api: "anthropic",
|
|
124
|
+
basePath: "/claude"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: "claude-fable-5",
|
|
128
|
+
name: "Claude Fable 5",
|
|
129
|
+
api: "anthropic",
|
|
130
|
+
basePath: "/claude"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: "claude-opus-4-8",
|
|
134
|
+
name: "Claude Opus 4.8",
|
|
135
|
+
api: "anthropic",
|
|
136
|
+
basePath: "/claude"
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: "claude-opus-4-7",
|
|
140
|
+
name: "Claude Opus 4.7",
|
|
141
|
+
api: "anthropic",
|
|
142
|
+
basePath: "/claude"
|
|
143
|
+
},
|
|
26
144
|
{
|
|
27
145
|
id: "claude-opus-4-6",
|
|
28
146
|
name: "Claude Opus 4.6",
|
|
@@ -35,23 +153,113 @@ const KIE_CHAT_MODELS = [
|
|
|
35
153
|
api: "anthropic",
|
|
36
154
|
basePath: "/claude"
|
|
37
155
|
},
|
|
156
|
+
{
|
|
157
|
+
id: "claude-opus-4-5",
|
|
158
|
+
name: "Claude Opus 4.5",
|
|
159
|
+
api: "anthropic",
|
|
160
|
+
basePath: "/claude"
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: "claude-sonnet-4-5",
|
|
164
|
+
name: "Claude Sonnet 4.5",
|
|
165
|
+
api: "anthropic",
|
|
166
|
+
basePath: "/claude"
|
|
167
|
+
},
|
|
38
168
|
{
|
|
39
169
|
id: "claude-haiku-4-5",
|
|
40
170
|
name: "Claude Haiku 4.5",
|
|
41
171
|
api: "anthropic",
|
|
42
172
|
basePath: "/claude"
|
|
43
173
|
},
|
|
174
|
+
{
|
|
175
|
+
id: "grok-4-7",
|
|
176
|
+
name: "Grok 4.7",
|
|
177
|
+
api: "responses",
|
|
178
|
+
basePath: "/grok/v1"
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
id: "grok-4-6",
|
|
182
|
+
name: "Grok 4.6",
|
|
183
|
+
api: "responses",
|
|
184
|
+
basePath: "/grok/v1"
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
id: "grok-4-5",
|
|
188
|
+
name: "Grok 4.5",
|
|
189
|
+
api: "responses",
|
|
190
|
+
basePath: "/grok/v1"
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
id: "grok-4-3",
|
|
194
|
+
name: "Grok 4.3",
|
|
195
|
+
api: "responses",
|
|
196
|
+
basePath: "/grok/v1"
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
id: "kimi-k3",
|
|
200
|
+
name: "Kimi K3",
|
|
201
|
+
api: "responses",
|
|
202
|
+
basePath: "/openai/v1"
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
id: "deepseek-v4-1-flash",
|
|
206
|
+
name: "DeepSeek V4.1 Flash",
|
|
207
|
+
api: "responses",
|
|
208
|
+
basePath: "/openai/v1"
|
|
209
|
+
},
|
|
44
210
|
{
|
|
45
211
|
id: "gemini-3.1-pro",
|
|
46
212
|
name: "Gemini 3.1 Pro",
|
|
47
213
|
api: "openai",
|
|
48
214
|
basePath: "/gemini-3.1-pro/v1"
|
|
49
215
|
},
|
|
216
|
+
{
|
|
217
|
+
id: "gemini-3-pro",
|
|
218
|
+
name: "Gemini 3 Pro",
|
|
219
|
+
api: "openai",
|
|
220
|
+
basePath: "/gemini-3-pro/v1"
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
id: "gemini-3-8-flash",
|
|
224
|
+
name: "Gemini 3.8 Flash",
|
|
225
|
+
api: "openai",
|
|
226
|
+
basePath: "/gemini-3-8-flash-openai/v1"
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
id: "gemini-3-7-flash",
|
|
230
|
+
name: "Gemini 3.7 Flash",
|
|
231
|
+
api: "openai",
|
|
232
|
+
basePath: "/gemini-3-7-flash-openai/v1"
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
id: "gemini-3-6-flash",
|
|
236
|
+
name: "Gemini 3.6 Flash",
|
|
237
|
+
api: "openai",
|
|
238
|
+
basePath: "/gemini-3-6-flash-openai/v1"
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
id: "gemini-3-5-flash",
|
|
242
|
+
name: "Gemini 3.5 Flash",
|
|
243
|
+
api: "openai",
|
|
244
|
+
basePath: "/gemini-3-5-flash-openai/v1"
|
|
245
|
+
},
|
|
50
246
|
{
|
|
51
247
|
id: "gemini-3-flash",
|
|
52
248
|
name: "Gemini 3 Flash",
|
|
53
249
|
api: "openai",
|
|
54
250
|
basePath: "/gemini-3-flash/v1"
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
id: "gemini-2.5-pro",
|
|
254
|
+
name: "Gemini 2.5 Pro",
|
|
255
|
+
api: "openai",
|
|
256
|
+
basePath: "/gemini-2.5-pro/v1"
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
id: "gemini-2.5-flash",
|
|
260
|
+
name: "Gemini 2.5 Flash",
|
|
261
|
+
api: "openai",
|
|
262
|
+
basePath: "/gemini-2.5-flash/v1"
|
|
55
263
|
}
|
|
56
264
|
];
|
|
57
265
|
function chatModel(model) {
|
|
@@ -63,73 +271,377 @@ function headers(apiKey) {
|
|
|
63
271
|
"Content-Type": "application/json"
|
|
64
272
|
};
|
|
65
273
|
}
|
|
66
|
-
|
|
274
|
+
/** Resolve the abort reason as an Error (mirrors the gemini provider). */
|
|
275
|
+
function abortError(signal) {
|
|
276
|
+
return signal?.reason instanceof Error ? signal.reason : new Error("Aborted");
|
|
277
|
+
}
|
|
278
|
+
/** Sleep that rejects promptly when `signal` aborts instead of running full. */
|
|
279
|
+
function sleep(ms, signal) {
|
|
280
|
+
return new Promise((resolve, reject) => {
|
|
281
|
+
if (signal?.aborted) {
|
|
282
|
+
reject(abortError(signal));
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const onAbort = () => {
|
|
286
|
+
clearTimeout(timer);
|
|
287
|
+
reject(abortError(signal));
|
|
288
|
+
};
|
|
289
|
+
const timer = setTimeout(() => {
|
|
290
|
+
signal?.removeEventListener("abort", onAbort);
|
|
291
|
+
resolve();
|
|
292
|
+
}, ms);
|
|
293
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* KIE reports API errors inside HTTP-200 bodies as `{code, msg}`. Map the known
|
|
298
|
+
* error codes to a message and throw; code 200 (and any unmapped code) passes
|
|
299
|
+
* through. Mirrors kie-base's `checkStatus` — without this a failed job returns
|
|
300
|
+
* a 200 that the poll loop reads as "not done yet" and runs to full timeout.
|
|
301
|
+
*/
|
|
302
|
+
/** The `code` values KIE returns inside an HTTP-200 error envelope. */
|
|
303
|
+
const KIE_ERROR_CODES = {
|
|
304
|
+
401: "Unauthorized",
|
|
305
|
+
402: "Insufficient Credits",
|
|
306
|
+
404: "Not Found",
|
|
307
|
+
422: "Validation Error",
|
|
308
|
+
429: "Rate Limited",
|
|
309
|
+
455: "Service Unavailable",
|
|
310
|
+
500: "Server Error",
|
|
311
|
+
501: "Generation Failed",
|
|
312
|
+
505: "Feature Disabled"
|
|
313
|
+
};
|
|
314
|
+
/**
|
|
315
|
+
* Classify a KIE response envelope. Returns the error KIE announced inside an
|
|
316
|
+
* otherwise-successful HTTP response, or null when the envelope is not an
|
|
317
|
+
* error. The live contract probe reads this to check that the error envelope
|
|
318
|
+
* still looks the way the provider expects.
|
|
319
|
+
*/
|
|
320
|
+
export function kieEnvelopeError(data) {
|
|
321
|
+
const code = Number(data.code);
|
|
322
|
+
const label = KIE_ERROR_CODES[code];
|
|
323
|
+
if (!label)
|
|
324
|
+
return null;
|
|
325
|
+
const message = isString(data.msg) ? data.msg : "";
|
|
326
|
+
return { code, label, message };
|
|
327
|
+
}
|
|
328
|
+
function checkStatus(data) {
|
|
329
|
+
const error = kieEnvelopeError(data);
|
|
330
|
+
if (error)
|
|
331
|
+
throw new Error(`${error.label}: ${JSON.stringify(data)}`);
|
|
332
|
+
}
|
|
333
|
+
/** Decode a `createTask` body into the task id it announces. */
|
|
334
|
+
export function decodeKieTaskSubmission(data) {
|
|
335
|
+
checkStatus(data);
|
|
336
|
+
const taskId = data.data?.taskId;
|
|
337
|
+
if (!isString(taskId) || taskId.length === 0) {
|
|
338
|
+
throw new Error(`No taskId in Kie response: ${JSON.stringify(data)}`);
|
|
339
|
+
}
|
|
340
|
+
return taskId;
|
|
341
|
+
}
|
|
342
|
+
/** Decode a `recordInfo` body into the job state the poller branches on. */
|
|
343
|
+
export function decodeKieRecordInfo(data) {
|
|
344
|
+
checkStatus(data);
|
|
345
|
+
const payload = data.data ?? {};
|
|
346
|
+
const state = payload.state;
|
|
347
|
+
if (!isString(state) || state.length === 0) {
|
|
348
|
+
throw new Error(`No state in Kie recordInfo: ${JSON.stringify(data)}`);
|
|
349
|
+
}
|
|
350
|
+
return { state, failMsg: isString(payload.failMsg) ? payload.failMsg : "" };
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Decode the result URLs out of a finished `recordInfo` body. KIE nests them
|
|
354
|
+
* as a JSON *string* under `data.resultJson`, so this is two decodes.
|
|
355
|
+
*/
|
|
356
|
+
export function decodeKieResultUrls(data) {
|
|
357
|
+
checkStatus(data);
|
|
358
|
+
const resultJsonStr = data.data?.resultJson;
|
|
359
|
+
if (!isString(resultJsonStr)) {
|
|
360
|
+
throw new Error("No resultJson in Kie response");
|
|
361
|
+
}
|
|
362
|
+
let resultData;
|
|
363
|
+
try {
|
|
364
|
+
resultData = JSON.parse(resultJsonStr);
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
throw new Error("Kie resultJson is not JSON");
|
|
368
|
+
}
|
|
369
|
+
const resultUrls = resultData.resultUrls;
|
|
370
|
+
if (!Array.isArray(resultUrls) || resultUrls.length === 0) {
|
|
371
|
+
throw new Error("No resultUrls in Kie resultJson");
|
|
372
|
+
}
|
|
373
|
+
return resultUrls.filter(isString);
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Parse a KIE JSON response defensively: a gateway 502 returns an HTML page, not
|
|
377
|
+
* JSON, so read the text and surface a "Kie <label> failed: <status> …" error
|
|
378
|
+
* with a body snippet rather than a bare SyntaxError. Applies the HTTP-200 error
|
|
379
|
+
* envelope check ({@link checkStatus}) on every parsed body.
|
|
380
|
+
*/
|
|
381
|
+
async function parseKieJson(res, label) {
|
|
382
|
+
const text = await res.text();
|
|
383
|
+
let data;
|
|
384
|
+
try {
|
|
385
|
+
data = JSON.parse(text);
|
|
386
|
+
}
|
|
387
|
+
catch {
|
|
388
|
+
throw new Error(`Kie ${label} failed: ${res.status} ${text.slice(0, 200)}`);
|
|
389
|
+
}
|
|
390
|
+
if (data.code !== undefined)
|
|
391
|
+
checkStatus(data);
|
|
392
|
+
return data;
|
|
393
|
+
}
|
|
394
|
+
/** Parse an "a:b" aspect ratio string into its numeric value, or undefined. */
|
|
395
|
+
function aspectToNumber(ratio) {
|
|
396
|
+
const m = /^(\d+(?:\.\d+)?)\s*:\s*(\d+(?:\.\d+)?)$/.exec(ratio.trim());
|
|
397
|
+
if (!m)
|
|
398
|
+
return undefined;
|
|
399
|
+
const w = Number(m[1]);
|
|
400
|
+
const h = Number(m[2]);
|
|
401
|
+
if (!Number.isFinite(w) || !Number.isFinite(h) || h === 0)
|
|
402
|
+
return undefined;
|
|
403
|
+
return w / h;
|
|
404
|
+
}
|
|
405
|
+
/** Choose the declared enum aspect ratio closest to a target numeric ratio. */
|
|
406
|
+
function nearestAspect(enumValues, target) {
|
|
407
|
+
let best;
|
|
408
|
+
for (const value of enumValues) {
|
|
409
|
+
const n = aspectToNumber(value);
|
|
410
|
+
if (n === undefined)
|
|
411
|
+
continue;
|
|
412
|
+
const diff = Math.abs(n - target);
|
|
413
|
+
if (!best || diff < best.diff)
|
|
414
|
+
best = { value, diff };
|
|
415
|
+
}
|
|
416
|
+
return best?.value ?? enumValues[0];
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Coerce a requested duration (seconds) to the model's declared `duration`
|
|
420
|
+
* field type: snap to the nearest allowed enum value (sent as the enum's own
|
|
421
|
+
* value type — the manifest enums are strings), round to an int, or pass a
|
|
422
|
+
* float through. Returns undefined when the model declares no duration field.
|
|
423
|
+
*/
|
|
424
|
+
function coerceDuration(field, seconds) {
|
|
425
|
+
if (!field)
|
|
426
|
+
return undefined;
|
|
427
|
+
if (field.type === "enum" && field.enumValues && field.enumValues.length > 0) {
|
|
428
|
+
let best;
|
|
429
|
+
for (const value of field.enumValues) {
|
|
430
|
+
const n = Number(value);
|
|
431
|
+
if (!Number.isFinite(n))
|
|
432
|
+
continue;
|
|
433
|
+
const diff = Math.abs(n - seconds);
|
|
434
|
+
if (!best || diff < best.diff)
|
|
435
|
+
best = { value, diff };
|
|
436
|
+
}
|
|
437
|
+
return best?.value ?? field.enumValues[0];
|
|
438
|
+
}
|
|
439
|
+
if (field.type === "float")
|
|
440
|
+
return seconds;
|
|
441
|
+
// int and any other numeric type: send a rounded integer.
|
|
442
|
+
return Math.round(seconds);
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* A field's default usable as an input value: the declared default when it's
|
|
446
|
+
* meaningful (not an empty string), otherwise the first enum option. KIE's
|
|
447
|
+
* required `model` field ships with an empty default, so the first declared
|
|
448
|
+
* version is used.
|
|
449
|
+
*/
|
|
450
|
+
/**
|
|
451
|
+
* HOLDOUT (anti-slop/no-unknown-returns): the value comes straight from the
|
|
452
|
+
* manifest's `default`, which `ModelInputField` declares `unknown`; naming the
|
|
453
|
+
* result means typing the manifest decoder in `manifest-models.ts`.
|
|
454
|
+
*/
|
|
455
|
+
function defaultForField(field) {
|
|
456
|
+
const d = field.default;
|
|
457
|
+
if (d !== undefined && !(isString(d) && d === ""))
|
|
458
|
+
return d;
|
|
459
|
+
if (field.enumValues && field.enumValues.length > 0)
|
|
460
|
+
return field.enumValues[0];
|
|
461
|
+
return undefined;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Upload raw image bytes to KIE's file store and return the hosted URL. Sniffs
|
|
465
|
+
* the container from the magic bytes so the upload carries the right mime and
|
|
466
|
+
* extension (KIE rejects a JPEG announced as PNG).
|
|
467
|
+
*/
|
|
468
|
+
async function uploadImageBytes(apiKey, bytes, signal) {
|
|
469
|
+
const mime = detectImageMime(bytes);
|
|
470
|
+
const ext = extForImageMime(mime) ?? "png";
|
|
471
|
+
const fileName = `upload-${Date.now()}.${ext}`;
|
|
472
|
+
const form = new FormData();
|
|
473
|
+
form.append("file", new Blob([new Uint8Array(bytes)], { type: mime }), fileName);
|
|
474
|
+
form.append("uploadPath", "images/user-uploads");
|
|
475
|
+
form.append("fileName", fileName);
|
|
476
|
+
const res = await fetch(KIE_UPLOAD_URL, {
|
|
477
|
+
method: "POST",
|
|
478
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
479
|
+
body: form,
|
|
480
|
+
signal
|
|
481
|
+
});
|
|
482
|
+
const data = await parseKieJson(res, "upload");
|
|
483
|
+
if (!res.ok || !data.success) {
|
|
484
|
+
throw new Error(`Kie upload failed: ${res.status} ${JSON.stringify(data)}`);
|
|
485
|
+
}
|
|
486
|
+
const downloadUrl = data.data
|
|
487
|
+
?.downloadUrl;
|
|
488
|
+
if (!downloadUrl) {
|
|
489
|
+
throw new Error(`No downloadUrl in Kie upload response`);
|
|
490
|
+
}
|
|
491
|
+
return downloadUrl;
|
|
492
|
+
}
|
|
493
|
+
async function uploadMediaBytes(apiKey, bytes, signal, kind) {
|
|
494
|
+
const mime = sniffMediaMime(bytes, kind === "image" ? "image/png" : "video/mp4");
|
|
495
|
+
const ext = kind === "image" ? (extForImageMime(mime) ?? "png") : "mp4";
|
|
496
|
+
const fileName = `upload-${Date.now()}.${ext}`;
|
|
497
|
+
const form = new FormData();
|
|
498
|
+
form.append("file", new Blob([new Uint8Array(bytes)], { type: mime }), fileName);
|
|
499
|
+
form.append("uploadPath", kind === "image" ? "images/user-uploads" : "videos/user-uploads");
|
|
500
|
+
form.append("fileName", fileName);
|
|
501
|
+
const res = await fetch(KIE_UPLOAD_URL, { method: "POST", headers: { Authorization: `Bearer ${apiKey}` }, body: form, signal });
|
|
502
|
+
const data = await parseKieJson(res, "upload");
|
|
503
|
+
if (!res.ok || !data.success)
|
|
504
|
+
throw new Error(`Kie upload failed: ${res.status} ${JSON.stringify(data)}`);
|
|
505
|
+
const downloadUrl = data.data?.downloadUrl;
|
|
506
|
+
if (typeof downloadUrl !== "string" || !downloadUrl)
|
|
507
|
+
throw new Error("No downloadUrl in Kie upload response");
|
|
508
|
+
return downloadUrl;
|
|
509
|
+
}
|
|
510
|
+
async function submitTask(apiKey, model, input, signal) {
|
|
67
511
|
const res = await fetch(`${KIE_API_BASE}/api/v1/jobs/createTask`, {
|
|
68
512
|
method: "POST",
|
|
69
513
|
headers: headers(apiKey),
|
|
70
|
-
body: JSON.stringify({ model, input })
|
|
514
|
+
body: JSON.stringify({ model, input }),
|
|
515
|
+
signal
|
|
71
516
|
});
|
|
72
|
-
const data =
|
|
517
|
+
const data = await parseKieJson(res, "submit");
|
|
73
518
|
if (!res.ok) {
|
|
74
519
|
throw new Error(`Kie submit failed: ${res.status} ${JSON.stringify(data)}`);
|
|
75
520
|
}
|
|
76
|
-
const taskId = data
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
521
|
+
const taskId = decodeKieTaskSubmission(data);
|
|
522
|
+
// KIE bills at submit, so the task id is the receipt even when the poll
|
|
523
|
+
// later fails or times out: the reconciler looks the charge up by it.
|
|
524
|
+
recordGenerationReceipt({ provider_request_id: taskId });
|
|
80
525
|
return taskId;
|
|
81
526
|
}
|
|
82
|
-
|
|
527
|
+
/**
|
|
528
|
+
* Submit a task with an optional webhook callback URL. When webhook mode is
|
|
529
|
+
* enabled, the callBackUrl is injected into the input so KIE calls us back
|
|
530
|
+
* when the task completes. The generic `/api/kie/webhook` endpoint extracts
|
|
531
|
+
* the taskId from the callback body.
|
|
532
|
+
*/
|
|
533
|
+
async function submitTaskWithWebhook(apiKey, model, input, signal) {
|
|
534
|
+
const webhookBase = getWebhookBaseUrl();
|
|
535
|
+
const finalInput = webhookBase
|
|
536
|
+
? { ...input, callBackUrl: `${webhookBase}/api/kie/webhook` }
|
|
537
|
+
: input;
|
|
538
|
+
return submitTask(apiKey, model, finalInput, signal);
|
|
539
|
+
}
|
|
540
|
+
async function pollUntilDone(apiKey, taskId, pollInterval = 4000, maxAttempts = 300, signal) {
|
|
83
541
|
const url = `${KIE_API_BASE}/api/v1/jobs/recordInfo?taskId=${taskId}`;
|
|
542
|
+
// KIE reports failures two ways that both look like "still running" to a naive
|
|
543
|
+
// loop: a persistent non-OK poll (429/5xx/revoked key) after createTask
|
|
544
|
+
// succeeded, and an HTTP-200 body carrying an error `code` (checkStatus). Both
|
|
545
|
+
// used to run the full maxAttempts (~20 min) before blaming a slow job. Fail
|
|
546
|
+
// fast: on a bounded run of consecutive HTTP errors, and immediately on a code
|
|
547
|
+
// envelope (via parseKieJson).
|
|
548
|
+
const MAX_CONSECUTIVE_ERRORS = 5;
|
|
549
|
+
let consecutiveErrors = 0;
|
|
84
550
|
for (let i = 0; i < maxAttempts; i++) {
|
|
85
|
-
const res = await fetch(url, { headers: headers(apiKey) });
|
|
86
|
-
|
|
87
|
-
|
|
551
|
+
const res = await fetch(url, { headers: headers(apiKey), signal });
|
|
552
|
+
if (!res.ok) {
|
|
553
|
+
consecutiveErrors += 1;
|
|
554
|
+
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
555
|
+
const body = await res.text().catch(() => "");
|
|
556
|
+
throw new Error(`Kie recordInfo failed ${consecutiveErrors}× (HTTP ${res.status}) for taskId ${taskId}: ${body.slice(0, 200)}`);
|
|
557
|
+
}
|
|
558
|
+
await sleep(pollInterval, signal);
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
consecutiveErrors = 0;
|
|
562
|
+
const { state, failMsg } = decodeKieRecordInfo(await parseKieJson(res, "recordInfo"));
|
|
88
563
|
if (state === "success")
|
|
89
564
|
return;
|
|
90
565
|
if (state === "failed" || state === "fail") {
|
|
91
|
-
|
|
92
|
-
throw new Error(`Kie task failed: ${msg}`);
|
|
566
|
+
throw new Error(`Kie task failed: ${failMsg || "Unknown error"} (taskId: ${taskId})`);
|
|
93
567
|
}
|
|
94
|
-
await
|
|
568
|
+
await sleep(pollInterval, signal);
|
|
95
569
|
}
|
|
96
|
-
|
|
570
|
+
const timeoutSeconds = (maxAttempts * pollInterval) / 1000;
|
|
571
|
+
throw new Error(`Kie task timed out after ${timeoutSeconds}s (taskId: ${taskId}). ` +
|
|
572
|
+
"The job may still complete on KIE — check recordInfo or the KIE dashboard.");
|
|
97
573
|
}
|
|
98
|
-
|
|
574
|
+
/**
|
|
575
|
+
* Wait for a KIE task to complete: use webhook if KIE_WEBHOOK_URL is set,
|
|
576
|
+
* otherwise fall back to polling.
|
|
577
|
+
*/
|
|
578
|
+
async function waitForCompletion(apiKey, taskId, pollInterval, maxAttempts, signal) {
|
|
579
|
+
if (getWebhookBaseUrl()) {
|
|
580
|
+
const timeoutMs = pollInterval * maxAttempts;
|
|
581
|
+
log.info("Waiting for KIE webhook callback", { taskId, timeoutMs });
|
|
582
|
+
await registerWebhookWait(taskId, timeoutMs, signal);
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
await pollUntilDone(apiKey, taskId, pollInterval, maxAttempts, signal);
|
|
586
|
+
}
|
|
587
|
+
async function downloadResultBytes(apiKey, taskId, signal) {
|
|
99
588
|
const url = `${KIE_API_BASE}/api/v1/jobs/recordInfo?taskId=${taskId}`;
|
|
100
|
-
const res = await fetch(url, { headers: headers(apiKey) });
|
|
589
|
+
const res = await fetch(url, { headers: headers(apiKey), signal });
|
|
101
590
|
if (!res.ok)
|
|
102
591
|
throw new Error(`Failed to get Kie result: ${res.status}`);
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
?.resultJson;
|
|
106
|
-
if (!resultJsonStr)
|
|
107
|
-
throw new Error("No resultJson in Kie response");
|
|
108
|
-
const resultData = JSON.parse(resultJsonStr);
|
|
109
|
-
const resultUrls = resultData.resultUrls;
|
|
110
|
-
if (!resultUrls?.length)
|
|
111
|
-
throw new Error("No resultUrls in Kie resultJson");
|
|
112
|
-
const dlRes = await fetch(resultUrls[0]);
|
|
592
|
+
const resultUrls = decodeKieResultUrls(await parseKieJson(res, "recordInfo"));
|
|
593
|
+
const dlRes = await safeFetch(resultUrls[0], { signal });
|
|
113
594
|
if (!dlRes.ok) {
|
|
114
595
|
throw new Error(`Failed to download from ${resultUrls[0]}`);
|
|
115
596
|
}
|
|
116
597
|
return new Uint8Array(await dlRes.arrayBuffer());
|
|
117
598
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
599
|
+
/**
|
|
600
|
+
* Submit a Suno music task to `<sunoEndpoint>` and return the task id. The Suno
|
|
601
|
+
* API is separate from the generic jobs API and always requires `callBackUrl`.
|
|
602
|
+
*/
|
|
603
|
+
async function submitSuno(apiKey, endpoint, input, signal) {
|
|
604
|
+
const callBackUrl = getWebhookBaseUrl()
|
|
605
|
+
? `${getWebhookBaseUrl()}/api/kie/webhook`
|
|
606
|
+
: KIE_SUNO_CALLBACK;
|
|
607
|
+
const body = { ...input, callBackUrl };
|
|
608
|
+
const res = await fetch(`${KIE_API_BASE}${endpoint}`, {
|
|
609
|
+
method: "POST",
|
|
610
|
+
headers: headers(apiKey),
|
|
611
|
+
body: JSON.stringify(body),
|
|
612
|
+
signal
|
|
613
|
+
});
|
|
614
|
+
const data = await parseKieJson(res, "submit");
|
|
615
|
+
if (!res.ok) {
|
|
616
|
+
throw new Error(`Kie submit failed: ${res.status} ${JSON.stringify(data)}`);
|
|
617
|
+
}
|
|
618
|
+
const taskId = decodeKieTaskSubmission(data);
|
|
619
|
+
recordGenerationReceipt({ provider_request_id: taskId });
|
|
620
|
+
return taskId;
|
|
127
621
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
|
|
622
|
+
/** Poll the Suno record-info endpoint until the task reaches a terminal state. */
|
|
623
|
+
async function pollSuno(apiKey, taskId, pollInterval, maxAttempts, signal) {
|
|
624
|
+
const url = `${KIE_API_BASE}/api/v1/generate/record-info?taskId=${taskId}`;
|
|
625
|
+
const failed = new Set([
|
|
626
|
+
"CREATE_TASK_FAILED",
|
|
627
|
+
"GENERATE_AUDIO_FAILED",
|
|
628
|
+
"CALLBACK_EXCEPTION",
|
|
629
|
+
"SENSITIVE_WORD_ERROR"
|
|
630
|
+
]);
|
|
631
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
632
|
+
const res = await fetch(url, { headers: headers(apiKey), signal });
|
|
633
|
+
const data = await parseKieJson(res, "record-info");
|
|
634
|
+
const status = data.data?.status;
|
|
635
|
+
if (status === "SUCCESS")
|
|
636
|
+
return data;
|
|
637
|
+
if (failed.has(status)) {
|
|
638
|
+
throw new Error(`Kie Suno task failed: ${status} (taskId: ${taskId})`);
|
|
639
|
+
}
|
|
640
|
+
await sleep(pollInterval, signal);
|
|
641
|
+
}
|
|
642
|
+
const timeoutSeconds = (maxAttempts * pollInterval) / 1000;
|
|
643
|
+
throw new Error(`Kie Suno task timed out after ${timeoutSeconds}s (taskId: ${taskId}). ` +
|
|
644
|
+
"The job may still complete on KIE — check record-info or the KIE dashboard.");
|
|
133
645
|
}
|
|
134
646
|
const KIE_MANIFEST_PKG = "@nodetool-ai/kie-nodes";
|
|
135
647
|
const KIE_MANIFEST_PATH = "kie-manifest.json";
|
|
@@ -152,24 +664,22 @@ export class KieProvider extends BaseProvider {
|
|
|
152
664
|
return this.apiKey;
|
|
153
665
|
}
|
|
154
666
|
makeOpenAIProvider(basePath) {
|
|
155
|
-
|
|
667
|
+
return new OpenAIProvider({ OPENAI_API_KEY: this.requireApiKey() }, {
|
|
668
|
+
providerId: "kie",
|
|
156
669
|
clientFactory: (apiKey) => new OpenAI({
|
|
157
670
|
apiKey,
|
|
158
671
|
baseURL: `${KIE_API_BASE}${basePath}`
|
|
159
672
|
})
|
|
160
673
|
});
|
|
161
|
-
provider.provider = "kie";
|
|
162
|
-
return provider;
|
|
163
674
|
}
|
|
164
675
|
makeAnthropicProvider(basePath) {
|
|
165
|
-
|
|
676
|
+
return new AnthropicProvider({ ANTHROPIC_API_KEY: this.requireApiKey() }, {
|
|
677
|
+
providerId: "kie",
|
|
166
678
|
clientFactory: (apiKey) => new Anthropic({
|
|
167
679
|
authToken: apiKey,
|
|
168
680
|
baseURL: `${KIE_API_BASE}${basePath}`
|
|
169
681
|
})
|
|
170
682
|
});
|
|
171
|
-
provider.provider = "kie";
|
|
172
|
-
return provider;
|
|
173
683
|
}
|
|
174
684
|
makeResponsesClient(basePath) {
|
|
175
685
|
return new OpenAI({
|
|
@@ -215,93 +725,11 @@ export class KieProvider extends BaseProvider {
|
|
|
215
725
|
}
|
|
216
726
|
yield* this.generateResponseMessages(args, model.basePath);
|
|
217
727
|
}
|
|
218
|
-
async responseInputContent(content) {
|
|
219
|
-
if (content.type === "text") {
|
|
220
|
-
return { type: "input_text", text: content.text };
|
|
221
|
-
}
|
|
222
|
-
if (content.type === "image_url") {
|
|
223
|
-
const image = content.image;
|
|
224
|
-
if (image.uri) {
|
|
225
|
-
const resolved = await this.resolveUri(image.uri);
|
|
226
|
-
return { type: "input_image", image_url: resolved };
|
|
227
|
-
}
|
|
228
|
-
if (image.data) {
|
|
229
|
-
return {
|
|
230
|
-
type: "input_image",
|
|
231
|
-
image_url: dataUri(image.mimeType ?? "image/jpeg", image.data)
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
return null;
|
|
236
|
-
}
|
|
237
|
-
async messagesToResponsesInput(messages) {
|
|
238
|
-
const input = [];
|
|
239
|
-
for (const message of messages) {
|
|
240
|
-
if (message.role === "tool") {
|
|
241
|
-
input.push({
|
|
242
|
-
type: "function_call_output",
|
|
243
|
-
call_id: message.toolCallId ?? "",
|
|
244
|
-
output: stringifyContent(message.content)
|
|
245
|
-
});
|
|
246
|
-
continue;
|
|
247
|
-
}
|
|
248
|
-
if (message.role === "assistant") {
|
|
249
|
-
const text = stringifyContent(message.content);
|
|
250
|
-
if (text) {
|
|
251
|
-
input.push({
|
|
252
|
-
type: "message",
|
|
253
|
-
role: "assistant",
|
|
254
|
-
content: [{ type: "output_text", text }]
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
for (const toolCall of message.toolCalls ?? []) {
|
|
258
|
-
input.push({
|
|
259
|
-
type: "function_call",
|
|
260
|
-
id: toolCall.id,
|
|
261
|
-
call_id: toolCall.id,
|
|
262
|
-
name: toolCall.name,
|
|
263
|
-
arguments: JSON.stringify(toolCall.args ?? {})
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
continue;
|
|
267
|
-
}
|
|
268
|
-
const content = [];
|
|
269
|
-
if (typeof message.content === "string") {
|
|
270
|
-
content.push({ type: "input_text", text: message.content });
|
|
271
|
-
}
|
|
272
|
-
else if (Array.isArray(message.content)) {
|
|
273
|
-
for (const part of message.content) {
|
|
274
|
-
const converted = await this.responseInputContent(part);
|
|
275
|
-
if (converted)
|
|
276
|
-
content.push(converted);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
if (content.length === 0)
|
|
280
|
-
continue;
|
|
281
|
-
input.push({ role: message.role, content });
|
|
282
|
-
}
|
|
283
|
-
return input;
|
|
284
|
-
}
|
|
285
|
-
responseTools(tools = []) {
|
|
286
|
-
return tools.map((tool) => ({
|
|
287
|
-
type: "function",
|
|
288
|
-
name: tool.name,
|
|
289
|
-
description: tool.description ?? "",
|
|
290
|
-
parameters: tool.inputSchema ?? { type: "object", properties: {} }
|
|
291
|
-
}));
|
|
292
|
-
}
|
|
293
|
-
responseToolChoice(toolChoice) {
|
|
294
|
-
if (!toolChoice)
|
|
295
|
-
return undefined;
|
|
296
|
-
if (toolChoice === "any")
|
|
297
|
-
return "auto";
|
|
298
|
-
return { type: "function", name: toolChoice };
|
|
299
|
-
}
|
|
300
728
|
async generateResponseMessage(args, basePath) {
|
|
301
729
|
const client = this.makeResponsesClient(basePath);
|
|
302
730
|
const request = {
|
|
303
731
|
model: args.model,
|
|
304
|
-
input: await
|
|
732
|
+
input: await messagesToResponsesInput(args.messages, (uri) => this.resolveUri(uri)),
|
|
305
733
|
stream: false
|
|
306
734
|
};
|
|
307
735
|
if (args.maxTokens != null)
|
|
@@ -310,19 +738,22 @@ export class KieProvider extends BaseProvider {
|
|
|
310
738
|
request.temperature = args.temperature;
|
|
311
739
|
if (args.topP != null)
|
|
312
740
|
request.top_p = args.topP;
|
|
313
|
-
const tools =
|
|
741
|
+
const tools = responseTools(args.tools);
|
|
314
742
|
if (tools.length > 0) {
|
|
315
743
|
request.tools = tools;
|
|
316
|
-
request.tool_choice =
|
|
744
|
+
request.tool_choice = responseToolChoice(args.toolChoice) ?? "auto";
|
|
317
745
|
}
|
|
746
|
+
this.recordRequestPayload(request);
|
|
747
|
+
// SAFETY: the SDK's `create` is overloaded over a closed param interface,
|
|
748
|
+
// while this request is assembled field by field.
|
|
318
749
|
const response = (await client.responses.create.call(client.responses, request, {
|
|
319
750
|
signal: args.signal
|
|
320
751
|
}));
|
|
321
|
-
this.
|
|
322
|
-
const outputText =
|
|
752
|
+
this.trackUsage(args.model, responseUsage(response));
|
|
753
|
+
const outputText = isString(response.output_text)
|
|
323
754
|
? response.output_text
|
|
324
|
-
:
|
|
325
|
-
const toolCalls =
|
|
755
|
+
: extractResponsesText(response.output);
|
|
756
|
+
const toolCalls = extractResponsesToolCalls(response.output, this.buildToolCall.bind(this));
|
|
326
757
|
return {
|
|
327
758
|
role: "assistant",
|
|
328
759
|
content: outputText || null,
|
|
@@ -333,7 +764,7 @@ export class KieProvider extends BaseProvider {
|
|
|
333
764
|
const client = this.makeResponsesClient(basePath);
|
|
334
765
|
const request = {
|
|
335
766
|
model: args.model,
|
|
336
|
-
input: await
|
|
767
|
+
input: await messagesToResponsesInput(args.messages, (uri) => this.resolveUri(uri)),
|
|
337
768
|
stream: true
|
|
338
769
|
};
|
|
339
770
|
if (args.maxTokens != null)
|
|
@@ -342,122 +773,639 @@ export class KieProvider extends BaseProvider {
|
|
|
342
773
|
request.temperature = args.temperature;
|
|
343
774
|
if (args.topP != null)
|
|
344
775
|
request.top_p = args.topP;
|
|
345
|
-
const tools =
|
|
776
|
+
const tools = responseTools(args.tools);
|
|
346
777
|
if (tools.length > 0) {
|
|
347
778
|
request.tools = tools;
|
|
348
|
-
request.tool_choice =
|
|
779
|
+
request.tool_choice = responseToolChoice(args.toolChoice) ?? "auto";
|
|
349
780
|
}
|
|
781
|
+
this.recordRequestPayload(request);
|
|
782
|
+
// SAFETY: as above, for the streaming overload.
|
|
350
783
|
const stream = (await client.responses.create.call(client.responses, request, { signal: args.signal }));
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
784
|
+
yield* streamResponsesEvents(stream, {
|
|
785
|
+
model: args.model,
|
|
786
|
+
buildToolCall: this.buildToolCall.bind(this),
|
|
787
|
+
onUsage: (model, usage) => this.trackUsage(model, usage)
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
async getAvailableVideoModels() {
|
|
791
|
+
return loadVideoModels(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, "kie");
|
|
792
|
+
}
|
|
793
|
+
async getAvailableImageModels() {
|
|
794
|
+
return loadImageModels(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, "kie");
|
|
795
|
+
}
|
|
796
|
+
async getAvailableTTSModels() {
|
|
797
|
+
return loadTTSModels(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, "kie");
|
|
798
|
+
}
|
|
799
|
+
async getAvailableMusicModels() {
|
|
800
|
+
return loadMusicModels(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, "kie");
|
|
801
|
+
}
|
|
802
|
+
/** Resolve poll interval and attempt count for a model's async task. */
|
|
803
|
+
pollConfig(modelId, timeoutSeconds) {
|
|
804
|
+
const meta = getManifestNodeMeta(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, modelId);
|
|
805
|
+
const pollInterval = meta?.pollInterval ?? 4000;
|
|
806
|
+
// A caller-supplied timeout wins: translate it into a bounded poll window.
|
|
807
|
+
if (timeoutSeconds != null && timeoutSeconds > 0) {
|
|
808
|
+
const maxAttempts = Math.max(1, Math.ceil((timeoutSeconds * 1000) / pollInterval));
|
|
809
|
+
return { pollInterval, maxAttempts };
|
|
810
|
+
}
|
|
811
|
+
return { pollInterval, maxAttempts: meta?.maxAttempts ?? 300 };
|
|
812
|
+
}
|
|
813
|
+
declaresField(fields, name) {
|
|
814
|
+
return fields.some((f) => f.name === name);
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Per-call timeout signal (mirrors the gemini provider): threaded into the
|
|
818
|
+
* poll-loop fetches and sleeps so an expired budget aborts the in-flight
|
|
819
|
+
* request promptly instead of leaking it until the loop exhausts.
|
|
820
|
+
*/
|
|
821
|
+
timeoutSignal(timeoutSeconds) {
|
|
822
|
+
return timeoutSeconds && timeoutSeconds > 0
|
|
823
|
+
? AbortSignal.timeout(timeoutSeconds * 1000)
|
|
824
|
+
: undefined;
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Generate music as an encoded audio file. The advertised KIE music models
|
|
828
|
+
* (generate-music, generate-sounds) run through the Suno API — a separate
|
|
829
|
+
* submit endpoint and record-info poll, not the generic jobs task API. A
|
|
830
|
+
* music model without Suno metadata falls back to the generic jobs path.
|
|
831
|
+
*/
|
|
832
|
+
async textToMusic(params) {
|
|
833
|
+
if (!params.prompt)
|
|
834
|
+
throw new Error("Prompt is required");
|
|
835
|
+
const apiKey = this.requireApiKey();
|
|
836
|
+
const modelId = params.model.id;
|
|
837
|
+
log.debug("Kie textToMusic", { model: modelId });
|
|
838
|
+
const signal = this.timeoutSignal(params.timeoutSeconds);
|
|
839
|
+
const meta = getManifestNodeMeta(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, modelId);
|
|
840
|
+
if (meta?.useSuno && meta.sunoEndpoint) {
|
|
841
|
+
const input = this.buildSunoInput(modelId, params);
|
|
842
|
+
const taskId = await submitSuno(apiKey, meta.sunoEndpoint, input, signal);
|
|
843
|
+
const { pollInterval, maxAttempts } = this.sunoPollConfig(meta.pollInterval, meta.maxAttempts, params.timeoutSeconds);
|
|
844
|
+
if (getWebhookBaseUrl()) {
|
|
845
|
+
const timeoutMs = pollInterval * maxAttempts;
|
|
846
|
+
log.info("Waiting for KIE Suno webhook callback", { taskId, timeoutMs });
|
|
847
|
+
await registerWebhookWait(taskId, timeoutMs, signal);
|
|
848
|
+
// After webhook fires, fetch the record to download audio
|
|
849
|
+
const url = `${KIE_API_BASE}/api/v1/generate/record-info?taskId=${taskId}`;
|
|
850
|
+
const res = await fetch(url, { headers: headers(apiKey), signal });
|
|
851
|
+
const record = await parseKieJson(res, "record-info");
|
|
852
|
+
const bytes = await this.downloadSunoAudio(record, signal);
|
|
853
|
+
return { data: bytes, mimeType: sniffAudioMime(bytes) };
|
|
362
854
|
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
855
|
+
const record = await pollSuno(apiKey, taskId, pollInterval, maxAttempts, signal);
|
|
856
|
+
const bytes = await this.downloadSunoAudio(record, signal);
|
|
857
|
+
return { data: bytes, mimeType: sniffAudioMime(bytes) };
|
|
858
|
+
}
|
|
859
|
+
// Fallback: a music model that doesn't route through Suno.
|
|
860
|
+
const input = { prompt: params.prompt };
|
|
861
|
+
if (params.lyrics)
|
|
862
|
+
input.lyrics = params.lyrics;
|
|
863
|
+
if (params.durationSeconds != null) {
|
|
864
|
+
input.duration = Math.round(params.durationSeconds);
|
|
865
|
+
}
|
|
866
|
+
const { pollInterval, maxAttempts } = this.pollConfig(modelId, params.timeoutSeconds);
|
|
867
|
+
const taskId = await submitTaskWithWebhook(apiKey, modelId, input, signal);
|
|
868
|
+
await waitForCompletion(apiKey, taskId, pollInterval, maxAttempts, signal);
|
|
869
|
+
const bytes = await downloadResultBytes(apiKey, taskId, signal);
|
|
870
|
+
return { data: bytes, mimeType: sniffAudioMime(bytes) };
|
|
871
|
+
}
|
|
872
|
+
/** Poll interval/attempts for a Suno task, honoring a per-call timeout. */
|
|
873
|
+
sunoPollConfig(metaPollInterval, metaMaxAttempts, timeoutSeconds) {
|
|
874
|
+
const pollInterval = metaPollInterval ?? 4000;
|
|
875
|
+
if (timeoutSeconds != null && timeoutSeconds > 0) {
|
|
876
|
+
return {
|
|
877
|
+
pollInterval,
|
|
878
|
+
maxAttempts: Math.max(1, Math.ceil((timeoutSeconds * 1000) / pollInterval))
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
return { pollInterval, maxAttempts: metaMaxAttempts ?? 120 };
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Build the Suno submit body from the model's manifest fields. `generate-music`
|
|
885
|
+
* distinguishes custom mode (lyrics supplied → prompt = lyrics, style = the
|
|
886
|
+
* description) from non-custom mode (KIE auto-generates lyrics from the
|
|
887
|
+
* prompt). Required fields with a usable default (e.g. `model`) are filled.
|
|
888
|
+
*/
|
|
889
|
+
buildSunoInput(modelId, params) {
|
|
890
|
+
const fields = getModelInputFields(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, modelId);
|
|
891
|
+
const has = (name) => this.declaresField(fields, name);
|
|
892
|
+
const input = {};
|
|
893
|
+
if (params.lyrics && has("customMode") && has("style")) {
|
|
894
|
+
input.customMode = true;
|
|
895
|
+
input.prompt = params.lyrics;
|
|
896
|
+
input.style = params.prompt;
|
|
897
|
+
if (has("instrumental"))
|
|
898
|
+
input.instrumental = false;
|
|
899
|
+
}
|
|
900
|
+
else {
|
|
901
|
+
if (has("customMode"))
|
|
902
|
+
input.customMode = false;
|
|
903
|
+
input.prompt = params.prompt;
|
|
904
|
+
}
|
|
905
|
+
for (const f of fields) {
|
|
906
|
+
if (f.required && input[f.name] === undefined) {
|
|
907
|
+
const d = defaultForField(f);
|
|
908
|
+
if (d !== undefined)
|
|
909
|
+
input[f.name] = d;
|
|
367
910
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
911
|
+
}
|
|
912
|
+
return input;
|
|
913
|
+
}
|
|
914
|
+
/** Download the first audio track from a Suno record-info response. */
|
|
915
|
+
async downloadSunoAudio(record, signal) {
|
|
916
|
+
const response = record.data?.response;
|
|
917
|
+
const sunoData = response?.sunoData;
|
|
918
|
+
const audioUrl = sunoData?.[0]?.audioUrl;
|
|
919
|
+
if (!audioUrl)
|
|
920
|
+
throw new Error("No audioUrl in Kie Suno response");
|
|
921
|
+
const dlRes = await safeFetch(audioUrl, { signal });
|
|
922
|
+
if (!dlRes.ok) {
|
|
923
|
+
throw new Error(`Failed to download from ${audioUrl}`);
|
|
924
|
+
}
|
|
925
|
+
return new Uint8Array(await dlRes.arrayBuffer());
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Generate speech as an encoded audio file. KIE runs TTS as an async job and
|
|
929
|
+
* returns a result URL (mp3); the unified TTS node consumes this encoded path
|
|
930
|
+
* rather than streaming PCM samples.
|
|
931
|
+
*/
|
|
932
|
+
async textToSpeechEncoded(args) {
|
|
933
|
+
if (!args.text)
|
|
934
|
+
throw new Error("text must not be empty");
|
|
935
|
+
const input = { text: args.text };
|
|
936
|
+
if (args.voice)
|
|
937
|
+
input.voice = args.voice;
|
|
938
|
+
if (args.speed != null)
|
|
939
|
+
input.speed = args.speed;
|
|
940
|
+
log.debug("Kie textToSpeech", { model: args.model });
|
|
941
|
+
const apiKey = this.requireApiKey();
|
|
942
|
+
const { pollInterval, maxAttempts } = this.pollConfig(args.model);
|
|
943
|
+
const taskId = await submitTaskWithWebhook(apiKey, args.model, input);
|
|
944
|
+
await waitForCompletion(apiKey, taskId, pollInterval, maxAttempts);
|
|
945
|
+
const bytes = await downloadResultBytes(apiKey, taskId);
|
|
946
|
+
return { data: bytes, mimeType: sniffAudioMime(bytes) };
|
|
947
|
+
}
|
|
948
|
+
async textToVideo(params) {
|
|
949
|
+
if (!params.prompt)
|
|
950
|
+
throw new Error("Prompt is required");
|
|
951
|
+
const modelId = params.model.id;
|
|
952
|
+
const fields = getModelInputFields(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, modelId);
|
|
953
|
+
const input = { prompt: params.prompt };
|
|
954
|
+
if (params.aspectRatio)
|
|
955
|
+
input.aspect_ratio = params.aspectRatio;
|
|
956
|
+
this.applyVideoDuration(input, fields, params);
|
|
957
|
+
this.applyRequiredDefaults(input, fields);
|
|
958
|
+
this.clampToDeclaredMax(input, fields);
|
|
959
|
+
log.debug("Kie textToVideo", { model: modelId });
|
|
960
|
+
const apiKey = this.requireApiKey();
|
|
961
|
+
const signal = this.timeoutSignal(params.timeoutSeconds);
|
|
962
|
+
const { pollInterval, maxAttempts } = this.pollConfig(modelId, params.timeoutSeconds);
|
|
963
|
+
const taskId = await submitTaskWithWebhook(apiKey, modelId, input, signal);
|
|
964
|
+
await waitForCompletion(apiKey, taskId, pollInterval, maxAttempts, signal);
|
|
965
|
+
return downloadResultBytes(apiKey, taskId, signal);
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Set the request `duration` from the caller's requested seconds (preferring
|
|
969
|
+
* `durationSeconds`, falling back to `numFrames`/24), coerced to the model's
|
|
970
|
+
* declared field type. A declared schema with no `duration` field sends
|
|
971
|
+
* nothing; an unknown model keeps the historical numeric behavior.
|
|
972
|
+
*/
|
|
973
|
+
/**
|
|
974
|
+
* Fill required fields the model declares but nothing else set, using the
|
|
975
|
+
* manifest's declared default.
|
|
976
|
+
*
|
|
977
|
+
* Kie rejects a task with `500 "This field is required"` when a declared
|
|
978
|
+
* required field is absent — it does not fall back to the documented default
|
|
979
|
+
* itself. `kling-2.6/image-to-video` requires `sound` (default false), which
|
|
980
|
+
* no param maps to, so an image-to-video task failed outright.
|
|
981
|
+
*
|
|
982
|
+
* Only fields the manifest marks required AND gives a default are filled, so
|
|
983
|
+
* this cannot invent a value the model never described.
|
|
984
|
+
*/
|
|
985
|
+
/**
|
|
986
|
+
* Trim string inputs to the length the manifest declares for them.
|
|
987
|
+
*
|
|
988
|
+
* Kie rejects an over-long field with `500 {"msg":"The text length cannot
|
|
989
|
+
* exceed the maximum limit"}` — no field name, no limit, no actual length.
|
|
990
|
+
* The manifest already carries the cap (Kling 2.6's `prompt` is capped at
|
|
991
|
+
* 1000), so the ceiling is known here and there is no reason to spend a
|
|
992
|
+
* round trip discovering it.
|
|
993
|
+
*
|
|
994
|
+
* Composed prompts are what hit this: `composeShotPrompt` concatenates a
|
|
995
|
+
* shot's action, camera, motion and the screenplay's style bible, and a
|
|
996
|
+
* verbose director clears 1000 characters without trying. Truncation is on a
|
|
997
|
+
* word boundary and always logged with both lengths — silently reshaping
|
|
998
|
+
* someone's prompt is worse than the 500 it replaces.
|
|
999
|
+
*/
|
|
1000
|
+
clampToDeclaredMax(input, fields) {
|
|
1001
|
+
for (const field of fields) {
|
|
1002
|
+
const max = field.max;
|
|
1003
|
+
if (max === undefined || max <= 0)
|
|
373
1004
|
continue;
|
|
374
|
-
|
|
375
|
-
if (
|
|
376
|
-
const response = event.response;
|
|
377
|
-
if (isRecord(response))
|
|
378
|
-
this.trackResponseUsage(args.model, response);
|
|
379
|
-
const chunk = { type: "chunk", content: "", done: true };
|
|
380
|
-
yield chunk;
|
|
1005
|
+
const value = input[field.name];
|
|
1006
|
+
if (!isString(value) || value.length <= max)
|
|
381
1007
|
continue;
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
1008
|
+
const cut = value.slice(0, max);
|
|
1009
|
+
const lastSpace = cut.lastIndexOf(" ");
|
|
1010
|
+
input[field.name] = (lastSpace > max * 0.6 ? cut.slice(0, lastSpace) : cut).trimEnd();
|
|
1011
|
+
log.warn("Kie input truncated to the manifest's declared maximum", {
|
|
1012
|
+
field: field.name,
|
|
1013
|
+
max,
|
|
1014
|
+
originalLength: value.length
|
|
1015
|
+
});
|
|
386
1016
|
}
|
|
387
1017
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
const parts = [];
|
|
392
|
-
for (const item of output) {
|
|
393
|
-
if (!isRecord(item) || item.type !== "message" || !Array.isArray(item.content)) {
|
|
1018
|
+
applyRequiredDefaults(input, fields) {
|
|
1019
|
+
for (const field of fields) {
|
|
1020
|
+
if (!field.required)
|
|
394
1021
|
continue;
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
}
|
|
1022
|
+
if (field.default === undefined)
|
|
1023
|
+
continue;
|
|
1024
|
+
if (input[field.name] !== undefined)
|
|
1025
|
+
continue;
|
|
1026
|
+
input[field.name] = field.default;
|
|
401
1027
|
}
|
|
402
|
-
return parts.join("");
|
|
403
1028
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
1029
|
+
/**
|
|
1030
|
+
* Refuse a call whose required text field is blank, naming the field and the
|
|
1031
|
+
* model.
|
|
1032
|
+
*
|
|
1033
|
+
* Kie answers one with `500 {"msg":"This field is required"}` — no field, no
|
|
1034
|
+
* model, and by then the rest of the graph has been paid for. Reaching it is
|
|
1035
|
+
* easy from a generic node: `nodetool.video.ImageToVideo` treats `prompt` as
|
|
1036
|
+
* optional because Veo 3.1 does, so pointing the same node at
|
|
1037
|
+
* `kling-2.6/image-to-video` sends a blank one. `applyRequiredDefaults` does
|
|
1038
|
+
* not save it — the manifest's declared default for such a field is `""`,
|
|
1039
|
+
* which is the value the endpoint rejects.
|
|
1040
|
+
*
|
|
1041
|
+
* Text only. A required media field arrives under its upload descriptor's API
|
|
1042
|
+
* name (`images` is sent as `image_urls`), so reading it off the input bag
|
|
1043
|
+
* under the manifest's name would refuse a call that is correctly formed.
|
|
1044
|
+
*/
|
|
1045
|
+
assertRequiredTextFields(input, fields, modelId) {
|
|
1046
|
+
for (const field of fields) {
|
|
1047
|
+
if (!field.required || field.type !== "str")
|
|
1048
|
+
continue;
|
|
1049
|
+
const value = input[field.name];
|
|
1050
|
+
if (isString(value) && value.trim().length > 0)
|
|
410
1051
|
continue;
|
|
411
|
-
|
|
1052
|
+
throw new Error(`Kie model "${modelId}" requires "${field.name}", which is empty. ` +
|
|
1053
|
+
`Set the node's "${field.name}" property (or wire it), or choose a ` +
|
|
1054
|
+
"model that does not require it.");
|
|
412
1055
|
}
|
|
413
|
-
return toolCalls;
|
|
414
1056
|
}
|
|
415
|
-
|
|
416
|
-
const
|
|
417
|
-
|
|
1057
|
+
applyVideoDuration(input, fields, params) {
|
|
1058
|
+
const seconds = params.durationSeconds != null
|
|
1059
|
+
? params.durationSeconds
|
|
1060
|
+
: params.numFrames != null
|
|
1061
|
+
? params.numFrames / 24
|
|
1062
|
+
: undefined;
|
|
1063
|
+
if (seconds == null)
|
|
418
1064
|
return;
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
return loadVideoModels(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, "kie");
|
|
431
|
-
}
|
|
432
|
-
async getAvailableImageModels() {
|
|
433
|
-
return loadImageModels(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, "kie");
|
|
1065
|
+
if (fields.length === 0) {
|
|
1066
|
+
// Unknown model: derive a plain integer as before (ceil for a frame count,
|
|
1067
|
+
// round for an explicit second count).
|
|
1068
|
+
input.duration =
|
|
1069
|
+
params.durationSeconds != null ? Math.round(seconds) : Math.ceil(seconds);
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
const durationField = fields.find((f) => f.name === "duration");
|
|
1073
|
+
if (!durationField)
|
|
1074
|
+
return;
|
|
1075
|
+
input.duration = coerceDuration(durationField, seconds);
|
|
434
1076
|
}
|
|
435
|
-
async
|
|
1077
|
+
async textToImage(params) {
|
|
436
1078
|
if (!params.prompt)
|
|
437
1079
|
throw new Error("Prompt is required");
|
|
1080
|
+
const modelId = params.model.id;
|
|
1081
|
+
const fields = getModelInputFields(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, modelId);
|
|
438
1082
|
const input = { prompt: params.prompt };
|
|
439
|
-
|
|
1083
|
+
// Schema-driven: only send fields the model declares. No KIE model declares
|
|
1084
|
+
// width/height, so those are never sent — an aspect ratio is derived instead.
|
|
1085
|
+
if (params.negativePrompt && this.declaresField(fields, "negative_prompt")) {
|
|
1086
|
+
input.negative_prompt = params.negativePrompt;
|
|
1087
|
+
}
|
|
1088
|
+
if (params.seed != null && this.declaresField(fields, "seed")) {
|
|
1089
|
+
input.seed = params.seed;
|
|
1090
|
+
}
|
|
1091
|
+
this.applyTextToImageAspect(input, fields, params);
|
|
1092
|
+
log.debug("Kie textToImage", { model: modelId });
|
|
1093
|
+
const apiKey = this.requireApiKey();
|
|
1094
|
+
const { pollInterval, maxAttempts } = this.pollConfig(modelId);
|
|
1095
|
+
this.applyRequiredDefaults(input, fields);
|
|
1096
|
+
this.clampToDeclaredMax(input, fields);
|
|
1097
|
+
const taskId = await submitTaskWithWebhook(apiKey, modelId, input);
|
|
1098
|
+
await waitForCompletion(apiKey, taskId, pollInterval, maxAttempts);
|
|
1099
|
+
return downloadResultBytes(apiKey, taskId);
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Set `aspect_ratio` when the model declares it: pass a caller-supplied ratio
|
|
1103
|
+
* through if it's a declared enum value (else snap to the nearest), or derive
|
|
1104
|
+
* the nearest declared ratio from width/height when no ratio was given.
|
|
1105
|
+
*/
|
|
1106
|
+
applyTextToImageAspect(input, fields, params) {
|
|
1107
|
+
const field = fields.find((f) => f.name === "aspect_ratio");
|
|
1108
|
+
if (!field)
|
|
1109
|
+
return;
|
|
1110
|
+
const enumValues = field.enumValues;
|
|
1111
|
+
if (params.aspectRatio) {
|
|
1112
|
+
if (enumValues && enumValues.length > 0) {
|
|
1113
|
+
input.aspect_ratio = enumValues.includes(params.aspectRatio)
|
|
1114
|
+
? params.aspectRatio
|
|
1115
|
+
: nearestAspect(enumValues, aspectToNumber(params.aspectRatio) ?? 1);
|
|
1116
|
+
}
|
|
1117
|
+
else {
|
|
1118
|
+
input.aspect_ratio = params.aspectRatio;
|
|
1119
|
+
}
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
if (params.width && params.height && enumValues && enumValues.length > 0) {
|
|
1123
|
+
input.aspect_ratio = nearestAspect(enumValues, params.width / params.height);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Set the request's `negative_prompt` / `aspect_ratio` / `seed` from the
|
|
1128
|
+
* caller only when the model declares them. Unknown models (empty schema)
|
|
1129
|
+
* keep the historical behavior: send whatever the caller supplied under the
|
|
1130
|
+
* conventional names.
|
|
1131
|
+
*/
|
|
1132
|
+
applyEditOptions(input, fields, params) {
|
|
1133
|
+
const declaredOrUnknown = (name) => fields.length === 0 || this.declaresField(fields, name);
|
|
1134
|
+
if (params.negativePrompt && declaredOrUnknown("negative_prompt")) {
|
|
1135
|
+
input.negative_prompt = params.negativePrompt;
|
|
1136
|
+
}
|
|
1137
|
+
if (params.aspectRatio && declaredOrUnknown("aspect_ratio")) {
|
|
440
1138
|
input.aspect_ratio = params.aspectRatio;
|
|
441
|
-
|
|
442
|
-
|
|
1139
|
+
}
|
|
1140
|
+
if (params.seed != null && declaredOrUnknown("seed")) {
|
|
1141
|
+
input.seed = params.seed;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
/**
|
|
1145
|
+
* Edit/transform one or more source images. Uses the model's schema to route
|
|
1146
|
+
* the uploaded image(s) to the correct input field — single-image models
|
|
1147
|
+
* (`image_url`) and multi-image edit/composition models (`image_urls` /
|
|
1148
|
+
* `image_input`) are both supported.
|
|
1149
|
+
*/
|
|
1150
|
+
async imageToImage(images, params) {
|
|
1151
|
+
const apiKey = this.requireApiKey();
|
|
1152
|
+
const imageUrls = await this.uploadImages(apiKey, images);
|
|
1153
|
+
if (imageUrls.length === 0) {
|
|
1154
|
+
throw new Error("The input image is empty.");
|
|
443
1155
|
}
|
|
444
1156
|
const modelId = params.model.id;
|
|
445
|
-
|
|
446
|
-
const
|
|
447
|
-
|
|
448
|
-
|
|
1157
|
+
const fields = getModelInputFields(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, modelId);
|
|
1158
|
+
const input = {
|
|
1159
|
+
prompt: params.prompt,
|
|
1160
|
+
...this.imageInput(modelId, imageUrls)
|
|
1161
|
+
};
|
|
1162
|
+
this.applyEditOptions(input, fields, params);
|
|
1163
|
+
log.debug("Kie imageToImage", { model: modelId, images: imageUrls.length });
|
|
1164
|
+
const { pollInterval, maxAttempts } = this.pollConfig(modelId);
|
|
1165
|
+
this.applyRequiredDefaults(input, fields);
|
|
1166
|
+
this.clampToDeclaredMax(input, fields);
|
|
1167
|
+
const taskId = await submitTaskWithWebhook(apiKey, modelId, input);
|
|
1168
|
+
await waitForCompletion(apiKey, taskId, pollInterval, maxAttempts);
|
|
1169
|
+
return downloadResultBytes(apiKey, taskId);
|
|
449
1170
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
1171
|
+
/**
|
|
1172
|
+
* Inpaint: regenerate the masked region of one or more source images. The
|
|
1173
|
+
* source image(s) route to the model's primary image field; the mask routes
|
|
1174
|
+
* to whatever mask field the model's schema declares (falling back to
|
|
1175
|
+
* `mask_url`).
|
|
1176
|
+
*/
|
|
1177
|
+
async inpaint(images, params) {
|
|
1178
|
+
const apiKey = this.requireApiKey();
|
|
1179
|
+
const imageUrls = await this.uploadImages(apiKey, images);
|
|
1180
|
+
if (imageUrls.length === 0) {
|
|
1181
|
+
throw new Error("The input image is empty.");
|
|
1182
|
+
}
|
|
456
1183
|
const modelId = params.model.id;
|
|
457
|
-
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
1184
|
+
const fields = getModelInputFields(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, modelId);
|
|
1185
|
+
const input = {
|
|
1186
|
+
prompt: params.prompt,
|
|
1187
|
+
...this.imageInput(modelId, imageUrls)
|
|
1188
|
+
};
|
|
1189
|
+
this.applyEditOptions(input, fields, params);
|
|
1190
|
+
if (params.mask && params.mask.length > 0) {
|
|
1191
|
+
const [maskUrl] = await this.uploadImages(apiKey, [params.mask]);
|
|
1192
|
+
if (maskUrl) {
|
|
1193
|
+
const maskField = selectMaskImageInput(getModelImageInputs(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, modelId));
|
|
1194
|
+
const fieldName = maskField?.apiName ?? "mask_url";
|
|
1195
|
+
input[fieldName] = maskField?.isList ? [maskUrl] : maskUrl;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
log.debug("Kie inpaint", { model: modelId, images: imageUrls.length });
|
|
1199
|
+
const { pollInterval, maxAttempts } = this.pollConfig(modelId);
|
|
1200
|
+
this.applyRequiredDefaults(input, fields);
|
|
1201
|
+
this.clampToDeclaredMax(input, fields);
|
|
1202
|
+
const taskId = await submitTaskWithWebhook(apiKey, modelId, input);
|
|
1203
|
+
await waitForCompletion(apiKey, taskId, pollInterval, maxAttempts);
|
|
1204
|
+
return downloadResultBytes(apiKey, taskId);
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Animate one or more source images into a video. The model schema decides
|
|
1208
|
+
* whether the frame goes to a single field (`image_url`) or a list.
|
|
1209
|
+
*/
|
|
1210
|
+
async imageToVideo(image, params) {
|
|
1211
|
+
const selected = loadVideoModels(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, "kie")
|
|
1212
|
+
.find((model) => model.id === params.model.id);
|
|
1213
|
+
if (selected && !selected.supportedTasks?.includes("image_to_video")) {
|
|
1214
|
+
throw new Error(`Kie model ${params.model.id} does not support image_to_video`);
|
|
1215
|
+
}
|
|
1216
|
+
const apiKey = this.requireApiKey();
|
|
1217
|
+
const signal = this.timeoutSignal(params.timeoutSeconds);
|
|
1218
|
+
const imageUrls = await this.uploadImages(apiKey, [image], signal);
|
|
1219
|
+
if (imageUrls.length === 0) {
|
|
1220
|
+
throw new Error("The input image is empty.");
|
|
1221
|
+
}
|
|
1222
|
+
const modelId = params.model.id;
|
|
1223
|
+
const fields = getModelInputFields(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, modelId);
|
|
1224
|
+
const input = this.imageInput(modelId, imageUrls);
|
|
1225
|
+
if (params.prompt)
|
|
1226
|
+
input.prompt = params.prompt;
|
|
1227
|
+
this.applyEditOptions(input, fields, params);
|
|
1228
|
+
this.applyVideoDuration(input, fields, params);
|
|
1229
|
+
this.applyRequiredDefaults(input, fields);
|
|
1230
|
+
this.clampToDeclaredMax(input, fields);
|
|
1231
|
+
this.assertRequiredTextFields(input, fields, modelId);
|
|
1232
|
+
log.debug("Kie imageToVideo", { model: modelId, images: imageUrls.length });
|
|
1233
|
+
const { pollInterval, maxAttempts } = this.pollConfig(modelId, params.timeoutSeconds);
|
|
1234
|
+
const taskId = await submitTaskWithWebhook(apiKey, modelId, input, signal);
|
|
1235
|
+
await waitForCompletion(apiKey, taskId, pollInterval, maxAttempts, signal);
|
|
1236
|
+
return downloadResultBytes(apiKey, taskId, signal);
|
|
1237
|
+
}
|
|
1238
|
+
async extendVideo(video, params) {
|
|
1239
|
+
if (video.length === 0) {
|
|
1240
|
+
throw new Error("The input video is empty.");
|
|
1241
|
+
}
|
|
1242
|
+
const modelId = params.model.id;
|
|
1243
|
+
const selected = loadVideoModels(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, "kie").find((model) => model.id === modelId);
|
|
1244
|
+
if (!selected?.supportedTasks?.includes("extend_video")) {
|
|
1245
|
+
throw new Error(`Kie model ${modelId} does not support extend_video`);
|
|
1246
|
+
}
|
|
1247
|
+
if (!selected.supportedTasks.includes(`extend_video_${params.mode}`)) {
|
|
1248
|
+
throw new Error(`Kie model ${modelId} does not support ${params.mode} extension`);
|
|
1249
|
+
}
|
|
1250
|
+
if (!Number.isFinite(params.durationSeconds) ||
|
|
1251
|
+
params.durationSeconds <= 0 ||
|
|
1252
|
+
(selected.durations?.length &&
|
|
1253
|
+
!selected.durations.includes(params.durationSeconds))) {
|
|
1254
|
+
throw new Error("Choose a supported extension duration.");
|
|
1255
|
+
}
|
|
1256
|
+
const fields = getModelInputFields(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, modelId);
|
|
1257
|
+
const durationField = fields.find((field) => field.name === "duration");
|
|
1258
|
+
if ((durationField?.min !== undefined &&
|
|
1259
|
+
params.durationSeconds < durationField.min) ||
|
|
1260
|
+
(durationField?.max !== undefined &&
|
|
1261
|
+
params.durationSeconds > durationField.max)) {
|
|
1262
|
+
throw new Error("Choose an extension duration within the model's supported range.");
|
|
1263
|
+
}
|
|
1264
|
+
const promptField = fields.find((field) => field.name === "prompt");
|
|
1265
|
+
if (promptField?.required && !params.prompt.trim()) {
|
|
1266
|
+
throw new Error(`Kie model ${modelId} requires a prompt`);
|
|
1267
|
+
}
|
|
1268
|
+
const videoField = getModelMediaInputs(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, modelId).find((field) => field.kind === "video");
|
|
1269
|
+
if (!videoField) {
|
|
1270
|
+
throw new Error(`Kie model ${modelId} does not accept a source video`);
|
|
1271
|
+
}
|
|
1272
|
+
const qualityField = fields.find((field) => field.name === "quality");
|
|
1273
|
+
const quality = qualityField?.enumValues?.includes("720p")
|
|
1274
|
+
? "720p"
|
|
1275
|
+
: qualityField?.enumValues?.[0];
|
|
1276
|
+
if (qualityField?.required && !quality) {
|
|
1277
|
+
throw new Error(`Kie model ${modelId} does not declare a usable quality`);
|
|
1278
|
+
}
|
|
1279
|
+
const apiKey = this.requireApiKey();
|
|
1280
|
+
const videoUrl = await uploadMediaBytes(apiKey, video, undefined, "video");
|
|
1281
|
+
const input = {
|
|
1282
|
+
[videoField.apiName]: videoField.isList ? [videoUrl] : videoUrl,
|
|
1283
|
+
prompt: params.prompt
|
|
1284
|
+
};
|
|
1285
|
+
this.applyVideoDuration(input, fields, params);
|
|
1286
|
+
if (quality) {
|
|
1287
|
+
input.quality = quality;
|
|
1288
|
+
}
|
|
1289
|
+
this.clampToDeclaredMax(input, fields);
|
|
1290
|
+
log.debug("Kie extendVideo", { model: modelId, mode: params.mode });
|
|
1291
|
+
const { pollInterval, maxAttempts } = this.pollConfig(modelId);
|
|
1292
|
+
const taskId = await submitTaskWithWebhook(apiKey, modelId, input);
|
|
1293
|
+
await waitForCompletion(apiKey, taskId, pollInterval, maxAttempts);
|
|
1294
|
+
return downloadResultBytes(apiKey, taskId);
|
|
1295
|
+
}
|
|
1296
|
+
/**
|
|
1297
|
+
* Upscale a video. The clip uploads to the model's declared video field and
|
|
1298
|
+
* the requested size is written to whichever of `upscale_factor` /
|
|
1299
|
+
* `resolution` the schema declares — Kie's two upscalers take one each.
|
|
1300
|
+
*/
|
|
1301
|
+
async upscaleVideo(video, params) {
|
|
1302
|
+
if (video.length === 0) {
|
|
1303
|
+
throw new Error("The input video is empty.");
|
|
1304
|
+
}
|
|
1305
|
+
const modelId = params.model.id;
|
|
1306
|
+
const supported = loadVideoModels(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, "kie")
|
|
1307
|
+
.find((model) => model.id === modelId)
|
|
1308
|
+
?.supportedTasks;
|
|
1309
|
+
if (supported && !supported.includes("upscale_video")) {
|
|
1310
|
+
throw new Error(`Kie model ${modelId} does not support upscale_video`);
|
|
1311
|
+
}
|
|
1312
|
+
const apiKey = this.requireApiKey();
|
|
1313
|
+
const signal = this.timeoutSignal(params.timeoutSeconds);
|
|
1314
|
+
const videoUrl = await uploadMediaBytes(apiKey, video, signal, "video");
|
|
1315
|
+
const fields = getModelInputFields(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, modelId);
|
|
1316
|
+
const videoField = getModelMediaInputs(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, modelId)
|
|
1317
|
+
.find((field) => field.kind === "video");
|
|
1318
|
+
const input = {
|
|
1319
|
+
[videoField?.apiName ?? "video_url"]: videoField?.isList
|
|
1320
|
+
? [videoUrl]
|
|
1321
|
+
: videoUrl
|
|
1322
|
+
};
|
|
1323
|
+
if (params.scale != null && this.declaresField(fields, "upscale_factor")) {
|
|
1324
|
+
input.upscale_factor = params.scale;
|
|
1325
|
+
}
|
|
1326
|
+
if (params.targetResolution &&
|
|
1327
|
+
this.declaresField(fields, "resolution")) {
|
|
1328
|
+
input.resolution = params.targetResolution;
|
|
1329
|
+
}
|
|
1330
|
+
this.applyEditOptions(input, fields, params);
|
|
1331
|
+
this.applyRequiredDefaults(input, fields);
|
|
1332
|
+
this.clampToDeclaredMax(input, fields);
|
|
1333
|
+
log.debug("Kie upscaleVideo", { model: modelId });
|
|
1334
|
+
const { pollInterval, maxAttempts } = this.pollConfig(modelId, params.timeoutSeconds);
|
|
1335
|
+
const taskId = await submitTaskWithWebhook(apiKey, modelId, input, signal);
|
|
1336
|
+
await waitForCompletion(apiKey, taskId, pollInterval, maxAttempts, signal);
|
|
1337
|
+
return downloadResultBytes(apiKey, taskId, signal);
|
|
1338
|
+
}
|
|
1339
|
+
async referenceToVideo(inputs, params) {
|
|
1340
|
+
const images = inputs.images.filter((b) => b.length > 0);
|
|
1341
|
+
const videos = inputs.videos.filter((b) => b.length > 0);
|
|
1342
|
+
if (images.length === 0 && videos.length === 0)
|
|
1343
|
+
throw new Error("reference_to_video requires at least one reference image or video");
|
|
1344
|
+
const apiKey = this.requireApiKey();
|
|
1345
|
+
const signal = combineSignals(params.signal, this.timeoutSignal(params.timeoutSeconds));
|
|
1346
|
+
if (params.useReferenceVideoAudio === true && videos.length === 0)
|
|
1347
|
+
throw new Error(`Kie model ${params.model.id} requires a reference video when audio is enabled`);
|
|
1348
|
+
if (!(loadVideoModels(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, "kie").find((m) => m.id === params.model.id)?.supportedTasks?.includes("reference_to_video") ?? false))
|
|
1349
|
+
throw new Error(`Kie model ${params.model.id} does not support reference_to_video`);
|
|
1350
|
+
const referenceFields = getModelReferenceInputs(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, params.model.id);
|
|
1351
|
+
// H3's generated minimum describes clip duration (2 seconds), not video count.
|
|
1352
|
+
if (params.model.id === "minimax-h3/reference-to-video") {
|
|
1353
|
+
for (const field of referenceFields) {
|
|
1354
|
+
if (field.kind === "video")
|
|
1355
|
+
delete field.min;
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
validateReferenceInputs("Kie", params.model.id, inputs, referenceFields);
|
|
1359
|
+
const imageField = referenceFields.find((f) => f.kind === "image");
|
|
1360
|
+
const videoField = referenceFields.find((f) => f.kind === "video");
|
|
1361
|
+
const fieldsForModel = getModelInputFields(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, params.model.id);
|
|
1362
|
+
if (params.useReferenceVideoAudio === true && !fieldsForModel.some((field) => field.name === "use_reference_video_audio")) {
|
|
1363
|
+
throw new Error(`Kie model ${params.model.id} does not support reference video audio`);
|
|
1364
|
+
}
|
|
1365
|
+
signal?.throwIfAborted();
|
|
1366
|
+
const [imageUrls, videoUrls] = await Promise.all([
|
|
1367
|
+
Promise.all(images.map((b) => uploadMediaBytes(apiKey, b, signal, "image"))),
|
|
1368
|
+
Promise.all(videos.map((b) => uploadMediaBytes(apiKey, b, signal, "video")))
|
|
1369
|
+
]);
|
|
1370
|
+
const input = {};
|
|
1371
|
+
if (imageField && imageUrls.length > 0)
|
|
1372
|
+
input[imageField.apiName] = imageField.isList ? imageUrls : imageUrls[0];
|
|
1373
|
+
if (videoField && videoUrls.length > 0)
|
|
1374
|
+
input[videoField.apiName] = videoField.isList ? videoUrls : videoUrls[0];
|
|
1375
|
+
if (params.prompt)
|
|
1376
|
+
input.prompt = params.prompt;
|
|
1377
|
+
if (params.negativePrompt && fieldsForModel.some((f) => f.name === "negative_prompt"))
|
|
1378
|
+
input.negative_prompt = params.negativePrompt;
|
|
1379
|
+
this.applyEditOptions(input, fieldsForModel, params);
|
|
1380
|
+
if (params.useReferenceVideoAudio === true) {
|
|
1381
|
+
input.use_reference_video_audio = true;
|
|
1382
|
+
}
|
|
1383
|
+
this.applyVideoDuration(input, fieldsForModel, params);
|
|
1384
|
+
this.applyRequiredDefaults(input, fieldsForModel);
|
|
1385
|
+
this.assertRequiredTextFields(input, fieldsForModel, params.model.id);
|
|
1386
|
+
const taskId = await submitTaskWithWebhook(apiKey, params.model.id, input, signal);
|
|
1387
|
+
const polling = this.pollConfig(params.model.id, params.timeoutSeconds);
|
|
1388
|
+
await waitForCompletion(apiKey, taskId, polling.pollInterval, polling.maxAttempts, signal);
|
|
1389
|
+
return downloadResultBytes(apiKey, taskId, signal);
|
|
1390
|
+
}
|
|
1391
|
+
/**
|
|
1392
|
+
* Map uploaded image URLs to the model's declared input field. Single-image
|
|
1393
|
+
* models get `image_url` (string); multi-image models get `image_urls` /
|
|
1394
|
+
* `image_input` (array). Falls back to KIE's conventions for unknown models.
|
|
1395
|
+
*/
|
|
1396
|
+
imageInput(modelId, urls) {
|
|
1397
|
+
const field = selectPrimaryImageInput(getModelImageInputs(KIE_MANIFEST_PKG, KIE_MANIFEST_PATH, modelId), urls.length);
|
|
1398
|
+
if (field) {
|
|
1399
|
+
return { [field.apiName]: field.isList ? urls : urls[0] };
|
|
1400
|
+
}
|
|
1401
|
+
return urls.length === 1
|
|
1402
|
+
? { image_url: urls[0] }
|
|
1403
|
+
: { image_urls: urls };
|
|
1404
|
+
}
|
|
1405
|
+
/** Upload every non-empty image to KIE's file store, returning hosted URLs. */
|
|
1406
|
+
async uploadImages(apiKey, images, signal) {
|
|
1407
|
+
const valid = images.filter((b) => b && b.length > 0);
|
|
1408
|
+
return Promise.all(valid.map((b) => uploadImageBytes(apiKey, b, signal)));
|
|
461
1409
|
}
|
|
462
1410
|
}
|
|
463
1411
|
//# sourceMappingURL=kie-provider.js.map
|