@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
|
@@ -6,48 +6,342 @@
|
|
|
6
6
|
* deduplicates the image and video entries so providers don't need to
|
|
7
7
|
* maintain hardcoded model lists.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { createLogger, loadPackageAssetJson } from "@nodetool-ai/config";
|
|
10
|
+
import { isNumber } from "@nodetool-ai/protocol";
|
|
11
|
+
// Stryker disable next-line StringLiteral: logger name is diagnostic, not asserted.
|
|
11
12
|
const log = createLogger("nodetool.runtime.providers.manifest-models");
|
|
12
|
-
function
|
|
13
|
+
export function explicitTasks(n) {
|
|
14
|
+
return Array.isArray(n.supportedTasks) && n.supportedTasks.length > 0
|
|
15
|
+
? n.supportedTasks
|
|
16
|
+
: undefined;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Enum values declared for a manifest input field, by canonical API name.
|
|
20
|
+
* Reads both the FAL/Replicate `inputFields` (`apiParamName ?? name` →
|
|
21
|
+
* `enumValues`) and the Kie `fields` (`name` → `values`) conventions.
|
|
22
|
+
*
|
|
23
|
+
* Values are stringified rather than returned as read: the manifests are JSON
|
|
24
|
+
* and the declared `string[]` is a claim about them, not a guarantee. FAL's
|
|
25
|
+
* two LoRA trainers declare `resolution` as an enum of numbers (768/1024 and
|
|
26
|
+
* 512/1024), which reached `unifiedModel.resolutions` — `z.array(z.string())`
|
|
27
|
+
* — and failed output validation for the whole `models.imageByProvider`
|
|
28
|
+
* query. `videoConstraints` reads durations back with `Number()`, so a
|
|
29
|
+
* numeric enum still works there.
|
|
30
|
+
*/
|
|
31
|
+
export function enumValuesFor(n, apiName) {
|
|
32
|
+
// Stryker disable next-line ArrayDeclaration: the fallback's contents are irrelevant — a non-array node yields no matching field either way.
|
|
33
|
+
const inputField = (n.inputFields ?? []).find((f) => (f.apiParamName ?? f.name) === apiName);
|
|
34
|
+
const fromInput = inputField?.enumValues;
|
|
35
|
+
if (fromInput && fromInput.length > 0)
|
|
36
|
+
return fromInput.map(String);
|
|
37
|
+
// Stryker disable next-line ArrayDeclaration: same rationale — a non-array node yields no matching field either way.
|
|
38
|
+
const field = (n.fields ?? []).find((f) => f.name === apiName);
|
|
39
|
+
const fromField = field?.values;
|
|
40
|
+
return fromField && fromField.length > 0 ? fromField.map(String) : undefined;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Seconds from one declared duration option.
|
|
44
|
+
*
|
|
45
|
+
* The manifests spell a clip length three ways, because the providers do:
|
|
46
|
+
* a bare number (`8`), a numeric string (`"8"`), and — across the FAL Veo and
|
|
47
|
+
* Marey families — a unit-suffixed string (`"8s"`). A bare `Number("8s")` is
|
|
48
|
+
* `NaN`, so the suffixed spelling used to drop the whole enum and leave those
|
|
49
|
+
* endpoints looking unconstrained: 22 FAL video endpoints, the Veo 3.1
|
|
50
|
+
* image-to-video and first-last-frame families among them, offered the app's
|
|
51
|
+
* full duration ladder when FAL sells them only 4, 6 and 8 seconds.
|
|
52
|
+
*
|
|
53
|
+
* Only a plain count of seconds is read. `"auto"` (FAL's Seedance rows) and
|
|
54
|
+
* `-1` (AtlasCloud's) name no length, and neither does a range or a frame
|
|
55
|
+
* count, so they stay out rather than becoming a number nothing published.
|
|
56
|
+
*/
|
|
57
|
+
function durationSeconds(value) {
|
|
58
|
+
const trimmed = value.trim();
|
|
59
|
+
if (trimmed === "")
|
|
60
|
+
return undefined;
|
|
61
|
+
const digits = /^(\d+(?:\.\d+)?)\s*s(?:ec(?:onds?)?)?$/i.exec(trimmed)?.[1] ?? trimmed;
|
|
62
|
+
const parsed = Number(digits);
|
|
63
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
64
|
+
}
|
|
65
|
+
/** Option constraints (duration/resolution/aspect) for a video endpoint. */
|
|
66
|
+
export function videoConstraints(n) {
|
|
67
|
+
const durationEnum = enumValuesFor(n, "duration");
|
|
68
|
+
const durations = durationEnum
|
|
69
|
+
?.map(durationSeconds)
|
|
70
|
+
.filter((v) => v !== undefined);
|
|
71
|
+
return {
|
|
72
|
+
durations: durations && durations.length > 0 ? durations : undefined,
|
|
73
|
+
resolutions: enumValuesFor(n, "resolution"),
|
|
74
|
+
aspectRatios: enumValuesFor(n, "aspect_ratio")
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
// FAL image endpoints usually declare an `image_size` enum instead of an
|
|
78
|
+
// `aspect_ratio`. This maps its named / explicit sizes onto the aspect-ratio
|
|
79
|
+
// vocabulary the picker uses; `auto*` sizes have no fixed ratio and are dropped.
|
|
80
|
+
const SIZE_ENUM_TO_ASPECT = {
|
|
81
|
+
square: "1:1",
|
|
82
|
+
square_hd: "1:1",
|
|
83
|
+
square_uhd: "1:1",
|
|
84
|
+
"1024x1024": "1:1",
|
|
85
|
+
landscape_4_3: "4:3",
|
|
86
|
+
portrait_4_3: "3:4",
|
|
87
|
+
landscape_16_9: "16:9",
|
|
88
|
+
landscape_hd: "16:9",
|
|
89
|
+
portrait_16_9: "9:16",
|
|
90
|
+
portrait_hd: "9:16",
|
|
91
|
+
landscape_3_2: "3:2",
|
|
92
|
+
"1536x1024": "3:2",
|
|
93
|
+
portrait_3_2: "2:3",
|
|
94
|
+
"1024x1536": "2:3"
|
|
95
|
+
};
|
|
96
|
+
/** Map a FAL `image_size` enum value onto an aspect ratio, or undefined. */
|
|
97
|
+
export function sizeEnumToAspect(value) {
|
|
98
|
+
return SIZE_ENUM_TO_ASPECT[value];
|
|
99
|
+
}
|
|
100
|
+
/** Option constraints (aspect/resolution) for an image endpoint. */
|
|
101
|
+
export function imageConstraints(n) {
|
|
102
|
+
// FAL splits size across `aspect_ratio` and `image_size`; union both into one
|
|
103
|
+
// aspect-ratio list, preserving first-seen order and dropping duplicates.
|
|
104
|
+
const fromAspect = enumValuesFor(n, "aspect_ratio") ?? [];
|
|
105
|
+
const fromSize = (enumValuesFor(n, "image_size") ?? [])
|
|
106
|
+
.map(sizeEnumToAspect)
|
|
107
|
+
.filter((v) => Boolean(v));
|
|
108
|
+
const aspectRatios = [...new Set([...fromAspect, ...fromSize])];
|
|
109
|
+
return {
|
|
110
|
+
aspectRatios: aspectRatios.length > 0 ? aspectRatios : undefined,
|
|
111
|
+
// Resolution is its own enum — never derived from image_size.
|
|
112
|
+
resolutions: enumValuesFor(n, "resolution")
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
export function nodeId(n) {
|
|
13
116
|
return n.modelId ?? n.endpointId ?? "";
|
|
14
117
|
}
|
|
15
|
-
function nodeName(n) {
|
|
118
|
+
export function nodeName(n) {
|
|
16
119
|
// className often looks like "FluxSchnellRedux" — split on caps
|
|
17
120
|
const raw = n.title ?? n.className ?? nodeId(n);
|
|
18
121
|
// If it's PascalCase with no spaces, add spaces before capitals
|
|
122
|
+
// Stryker disable next-line Regex: the test is an optimization gate; the replaces below are a no-op without a [a-z][A-Z] boundary, so widening it is behaviour-preserving.
|
|
19
123
|
if (raw && !raw.includes(" ") && /[a-z][A-Z]/.test(raw)) {
|
|
20
|
-
|
|
21
|
-
|
|
124
|
+
const spaced = raw.replace(/([a-z])([A-Z])/g, "$1 $2");
|
|
125
|
+
// Stryker disable next-line Regex: collapsing the acronym group [A-Z]+ -> [A-Z] leaves the unmatched leading uppercase chars in place, producing identical output.
|
|
126
|
+
return spaced.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2");
|
|
22
127
|
}
|
|
23
128
|
return raw;
|
|
24
129
|
}
|
|
25
|
-
|
|
26
|
-
|
|
130
|
+
/** Match a list of keyword fragments against id + name (both lowercased). */
|
|
131
|
+
export function matchesAny(haystack, ...needles) {
|
|
132
|
+
return needles.some((n) => haystack.includes(n));
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Endpoints that take an existing clip and hand back another one. Their ids
|
|
136
|
+
* never spell out "video-to-video", so without this list they fall through to
|
|
137
|
+
* the generator branch and turn up in the text/image-to-video pickers with no
|
|
138
|
+
* video to work on.
|
|
139
|
+
*/
|
|
140
|
+
const VIDEO_SOURCE_KEYWORDS = [
|
|
141
|
+
"extend-video",
|
|
142
|
+
"extend video",
|
|
143
|
+
"reframe",
|
|
144
|
+
"retake",
|
|
145
|
+
"edit-video",
|
|
146
|
+
"video-edit",
|
|
147
|
+
"video edit",
|
|
148
|
+
"restyle",
|
|
149
|
+
"inpaint",
|
|
150
|
+
"background-removal",
|
|
151
|
+
"background removal",
|
|
152
|
+
"dubbing",
|
|
153
|
+
"eraser"
|
|
154
|
+
];
|
|
155
|
+
/** Resolution / detail enhancers that take a clip and hand back a bigger one. */
|
|
156
|
+
const VIDEO_UPSCALE_KEYWORDS = [
|
|
157
|
+
"upscal",
|
|
158
|
+
"super-resolution",
|
|
159
|
+
"super resolution",
|
|
160
|
+
"superres",
|
|
161
|
+
"seedvr",
|
|
162
|
+
"vsr",
|
|
163
|
+
"enhancer"
|
|
164
|
+
];
|
|
165
|
+
/** Frame-rate / slow-motion models that synthesize intermediate frames. */
|
|
166
|
+
const VIDEO_INTERPOLATE_KEYWORDS = [
|
|
167
|
+
"interpolation",
|
|
168
|
+
"interpolate",
|
|
169
|
+
"frame-interp",
|
|
170
|
+
"frame interp",
|
|
171
|
+
"rife",
|
|
172
|
+
"film-net"
|
|
173
|
+
];
|
|
174
|
+
/** Models that extend a clip past its frame. */
|
|
175
|
+
const VIDEO_OUTPAINT_KEYWORDS = ["outpaint", "uncrop", "expand-video"];
|
|
176
|
+
/** The specialized transform an id names, or null for everything else. */
|
|
177
|
+
function specializedVideoTask(hay) {
|
|
178
|
+
if (matchesAny(hay, ...VIDEO_UPSCALE_KEYWORDS))
|
|
179
|
+
return "upscale_video";
|
|
180
|
+
if (matchesAny(hay, ...VIDEO_INTERPOLATE_KEYWORDS))
|
|
181
|
+
return "interpolate_video";
|
|
182
|
+
if (matchesAny(hay, ...VIDEO_OUTPAINT_KEYWORDS))
|
|
183
|
+
return "outpaint_video";
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* A LoRA trainer, not a generation endpoint. `fal-ai/ltx23-trainer-v2/outpaint`
|
|
188
|
+
* trains an outpainting adapter from a dataset URL — it takes no clip and
|
|
189
|
+
* returns no video, so every task keyword in its id is about what the trained
|
|
190
|
+
* weights will do, not what the endpoint does.
|
|
191
|
+
*/
|
|
192
|
+
function isTrainerEndpoint(hay) {
|
|
193
|
+
return matchesAny(hay, "trainer", "-train", "/train", "training");
|
|
194
|
+
}
|
|
195
|
+
/** A bare `/extend` path segment (`.../ltx-video-13b-dev/extend`). */
|
|
196
|
+
const EXTEND_SEGMENT = /(^|[/\- ])extend([/\- ]|$)/;
|
|
197
|
+
function isVideoExtensionEndpoint(name, id) {
|
|
198
|
+
const hay = `${id} ${name}`.toLowerCase();
|
|
199
|
+
return (!isTrainerEndpoint(hay) &&
|
|
200
|
+
(EXTEND_SEGMENT.test(id.toLowerCase()) ||
|
|
201
|
+
matchesAny(hay, "extend-video", "video-extend", "video-extension", "video extension")));
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Endpoints that need a still (or several) to start from, named in ways the
|
|
205
|
+
* plain "image-to-video" test misses. They are image-conditioned only: a text
|
|
206
|
+
* prompt alone is not a call these accept.
|
|
207
|
+
*/
|
|
208
|
+
const IMAGE_CONDITIONED_KEYWORDS = [
|
|
209
|
+
"first-last-frame-to-video",
|
|
210
|
+
"first last frame to video",
|
|
211
|
+
"start-end-to-video",
|
|
212
|
+
"keyframes-to-video",
|
|
213
|
+
"frames-to-video",
|
|
214
|
+
];
|
|
215
|
+
const REFERENCE_VIDEO_KEYWORDS = [
|
|
216
|
+
"reference-to-video",
|
|
217
|
+
"reference to video",
|
|
218
|
+
"referencetovideo",
|
|
219
|
+
"ref-to-video",
|
|
220
|
+
"r2v"
|
|
221
|
+
];
|
|
222
|
+
export function inferVideoTasks(name, id) {
|
|
223
|
+
const hay = `${id} ${name}`.toLowerCase();
|
|
27
224
|
const tasks = [];
|
|
28
|
-
if (
|
|
225
|
+
if (isVideoExtensionEndpoint(name, id)) {
|
|
226
|
+
return ["extend_video"];
|
|
227
|
+
}
|
|
228
|
+
// Specialized video transforms — kept out of the text/image generation lists.
|
|
229
|
+
if (matchesAny(hay, "lipsync", "lip-sync", "lip sync")) {
|
|
230
|
+
return ["lip_sync"];
|
|
231
|
+
}
|
|
232
|
+
// Audio-conditioned generators (LTX's audio-to-video, talking-avatar
|
|
233
|
+
// endpoints) need a recording, so neither generation picker can call one.
|
|
234
|
+
// `image-audio-to-video` matches here too, and belongs here: without the
|
|
235
|
+
// audio the call fails whatever image it gets.
|
|
236
|
+
if (matchesAny(hay, "audio-to-video", "audio to video", "speech-to-video")) {
|
|
237
|
+
return ["audio_to_video"];
|
|
238
|
+
}
|
|
239
|
+
if (matchesAny(hay, "video-to-video", "video to video", "videotovideo", "v2v")) {
|
|
240
|
+
return ["video_to_video"];
|
|
241
|
+
}
|
|
242
|
+
// Specialized video transforms. Each takes a source clip and answers a
|
|
243
|
+
// question `video_to_video` cannot: how big, how many frames, how wide.
|
|
244
|
+
// Their ids never spell out "video-to-video", so without these branches they
|
|
245
|
+
// fall through to the generator branch and show up in the text/image-to-video
|
|
246
|
+
// pickers with no video input.
|
|
247
|
+
const specialized = specializedVideoTask(hay);
|
|
248
|
+
if (specialized) {
|
|
249
|
+
// A trainer carries the same keywords and performs none of them, so it
|
|
250
|
+
// stays a plain source-consuming endpoint rather than claiming the task.
|
|
251
|
+
return [isTrainerEndpoint(hay) ? "video_to_video" : specialized];
|
|
252
|
+
}
|
|
253
|
+
if (matchesAny(hay, ...VIDEO_SOURCE_KEYWORDS) ||
|
|
254
|
+
EXTEND_SEGMENT.test(id.toLowerCase())) {
|
|
255
|
+
return ["video_to_video"];
|
|
256
|
+
}
|
|
257
|
+
if (matchesAny(hay, ...REFERENCE_VIDEO_KEYWORDS)) {
|
|
258
|
+
return ["reference_to_video"];
|
|
259
|
+
}
|
|
260
|
+
if (matchesAny(hay, ...IMAGE_CONDITIONED_KEYWORDS)) {
|
|
261
|
+
return ["image_to_video"];
|
|
262
|
+
}
|
|
263
|
+
if (matchesAny(hay, "text-to-video", "text to video", "texttovideo")) {
|
|
29
264
|
tasks.push("text_to_video");
|
|
30
265
|
}
|
|
31
|
-
if (
|
|
266
|
+
if (matchesAny(hay, "image-to-video", "image to video", "imagetovideo")) {
|
|
32
267
|
tasks.push("image_to_video");
|
|
33
268
|
}
|
|
34
|
-
|
|
269
|
+
// Ambiguous generator — tag both so generation filtering stays strict.
|
|
270
|
+
return tasks.length > 0 ? tasks : ["text_to_video", "image_to_video"];
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Infer the task(s) an image model serves from its id/name.
|
|
274
|
+
*
|
|
275
|
+
* Specialized transforms (upscale, background removal, relighting,
|
|
276
|
+
* vectorization) are distinctive and mutually exclusive, so they get a single
|
|
277
|
+
* specific task and stay out of the generation pickers. Everything else is a
|
|
278
|
+
* general generator: t2i vs i2i can't be told apart from FAL/Replicate naming
|
|
279
|
+
* (e.g. `virtual-tryon`, `multiple-angles`, `canny` are image-conditioned but
|
|
280
|
+
* don't say so), so generators are tagged with BOTH generation tasks. Tagging
|
|
281
|
+
* every entry means generation filtering is strict for these providers — no
|
|
282
|
+
* reliance on the untagged-passes-everything fallback.
|
|
283
|
+
*/
|
|
284
|
+
export function inferImageTasks(name, id) {
|
|
285
|
+
const hay = `${id} ${name}`.toLowerCase();
|
|
286
|
+
// Outpainting is tested before inpainting: `bria/genfill` aside, the two
|
|
287
|
+
// families share vocabulary ("fill", "expand"), and an endpoint whose id says
|
|
288
|
+
// outpaint never also accepts a mask-confined edit.
|
|
289
|
+
if (matchesAny(hay, "outpaint", "uncrop", "expand-image", "expand image")) {
|
|
290
|
+
return ["outpaint"];
|
|
291
|
+
}
|
|
292
|
+
if (matchesAny(hay, "upscal", "super-resolution", "super resolution", "superres", "esrgan", "seedvr", "clarity")) {
|
|
293
|
+
return ["upscale"];
|
|
294
|
+
}
|
|
295
|
+
if (matchesAny(hay, "background/remove", "remove-background", "remove background", "removebackground", "rembg", "bg-remove", "/remove-bg", "background-removal", "bria/background")) {
|
|
296
|
+
return ["remove_background"];
|
|
297
|
+
}
|
|
298
|
+
// IC-Light (fal-ai/iclight-v2, zsxkib/ic-light-background, …) is a relighting
|
|
299
|
+
// model but never spells out "relight" in its id/name, so match it explicitly.
|
|
300
|
+
if (matchesAny(hay, "relight", "relighting", "re-light", "iclight", "ic-light", "ic_light")) {
|
|
301
|
+
return ["relight"];
|
|
302
|
+
}
|
|
303
|
+
// Segmentation models return masks, not a picture — keep them out of every
|
|
304
|
+
// generation picker. "sam" is matched with its separators so it cannot hit
|
|
305
|
+
// a name that merely contains those three letters, and fal's `sam-audio`
|
|
306
|
+
// stem separators are excluded outright: they take a recording, not an image.
|
|
307
|
+
if (
|
|
308
|
+
// An RLE endpoint answers with run-length text and no mask image, which
|
|
309
|
+
// nothing downstream here can render — leave it out of the task entirely.
|
|
310
|
+
!matchesAny(hay, "sam-audio", "image-rle", "video-rle") &&
|
|
311
|
+
matchesAny(hay, "segment", "segmentation", "/sam", "sam-2", "sam2", "sam-3", "sam3", "evf-sam", "birefnet")) {
|
|
312
|
+
return ["segment"];
|
|
313
|
+
}
|
|
314
|
+
if (matchesAny(hay, "vectorize", "vectorization", "/vectorize", "to-svg", "image-to-svg")) {
|
|
315
|
+
return ["vectorize"];
|
|
316
|
+
}
|
|
317
|
+
// Mask-guided editors that don't declare their mask field. `buildImageModels`
|
|
318
|
+
// tags `inpainting` from a declared mask input, which is the reliable signal;
|
|
319
|
+
// this catches the endpoints whose manifest entry carries no mask field but
|
|
320
|
+
// whose id says what they are (`fal-ai/lora/inpaint`, `bria/genfill`). The
|
|
321
|
+
// tag is added, not substituted: these take a prompt and an image like any
|
|
322
|
+
// other editor, so they stay answers to an image_to_image request too.
|
|
323
|
+
const tasks = ["text_to_image", "image_to_image"];
|
|
324
|
+
if (matchesAny(hay, "inpaint", "genfill", "eraser")) {
|
|
325
|
+
tasks.push("inpainting");
|
|
326
|
+
}
|
|
327
|
+
return tasks;
|
|
35
328
|
}
|
|
36
329
|
// ---------------------------------------------------------------------------
|
|
37
330
|
// Manifest cache
|
|
38
331
|
// ---------------------------------------------------------------------------
|
|
39
332
|
const _cache = new Map();
|
|
40
|
-
function loadManifest(packageName, exportPath) {
|
|
333
|
+
export function loadManifest(packageName, exportPath) {
|
|
41
334
|
const key = `${packageName}/${exportPath}`;
|
|
335
|
+
// Stryker disable next-line ConditionalExpression: this cache short-circuit is a pure memoization; skipping it just re-resolves the same manifest — behaviour-preserving.
|
|
42
336
|
if (_cache.has(key))
|
|
43
337
|
return _cache.get(key);
|
|
44
338
|
try {
|
|
45
|
-
const
|
|
46
|
-
const data = req(`${packageName}/${exportPath}`);
|
|
339
|
+
const data = loadPackageAssetJson({ pkg: packageName, path: exportPath }, import.meta.url);
|
|
47
340
|
_cache.set(key, data);
|
|
48
341
|
return data;
|
|
49
342
|
}
|
|
50
343
|
catch (err) {
|
|
344
|
+
// Stryker disable next-line StringLiteral: diagnostic log, not asserted.
|
|
51
345
|
log.warn(`Could not load ${key}: ${err}`);
|
|
52
346
|
_cache.set(key, []);
|
|
53
347
|
return [];
|
|
@@ -57,7 +351,83 @@ function loadManifest(packageName, exportPath) {
|
|
|
57
351
|
// Public API
|
|
58
352
|
// ---------------------------------------------------------------------------
|
|
59
353
|
export function loadVideoModels(packageName, exportPath, provider) {
|
|
60
|
-
|
|
354
|
+
return buildVideoModels(loadManifest(packageName, exportPath), provider);
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Does this entry refuse the call without an input of `kind`?
|
|
358
|
+
*
|
|
359
|
+
* The declaration, not the name: a manifest field marked `required` is the
|
|
360
|
+
* endpoint's own statement that it cannot run without that media. Read across
|
|
361
|
+
* both manifest conventions — FAL/Replicate `inputFields` (`propType`) and
|
|
362
|
+
* AtlasCloud/Kie `fields` (`type`). Kie's `uploads` descriptors carry no
|
|
363
|
+
* requiredness, so they answer false and the name-based inference stands.
|
|
364
|
+
*/
|
|
365
|
+
export function requiresMediaInput(n, kind) {
|
|
366
|
+
const required = (t, fRequired) => fRequired === true && (t === kind || t === `list[${kind}]`);
|
|
367
|
+
// Stryker disable next-line ArrayDeclaration: an entry with no inputFields declares nothing required either way.
|
|
368
|
+
if (n.inputFields?.some((f) => required(f.propType.toLowerCase(), f.required))) {
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
// Stryker disable next-line ArrayDeclaration: same rationale for the fields convention.
|
|
372
|
+
return n.fields?.some((f) => required(f.type.toLowerCase(), f.required)) ?? false;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Drop the generation tasks an entry's own declared inputs rule out, and fall
|
|
376
|
+
* back to the transform task when that leaves nothing.
|
|
377
|
+
*
|
|
378
|
+
* Inference reads names, and a name is often silent about direction — which is
|
|
379
|
+
* why an ambiguous generator is tagged with both. A *required* image input is
|
|
380
|
+
* not silent: `alibaba/qwen-image-3/edit` cannot generate from a prompt alone,
|
|
381
|
+
* and it was the top-ranked `text_to_image` answer a live session got, because
|
|
382
|
+
* nothing here read the field that says so.
|
|
383
|
+
*
|
|
384
|
+
* Only ever subtracts. Explicit `supportedTasks` never reach this.
|
|
385
|
+
*/
|
|
386
|
+
export function narrowTasksByRequiredInputs(tasks, n, kind) {
|
|
387
|
+
const generationTask = kind === "image" ? "text_to_image" : "text_to_video";
|
|
388
|
+
const transformTask = kind === "image" ? "image_to_image" : "video_to_video";
|
|
389
|
+
const drop = new Set();
|
|
390
|
+
const hasRequiredReference = (entry) => manifestEntryMediaInputs(entry).some((field) => /reference|refers|subject/i.test(`${field.name} ${field.apiName}`) &&
|
|
391
|
+
((entry.inputFields ?? []).some((f) => (f.apiParamName ?? f.name) === field.apiName && f.required === true) ||
|
|
392
|
+
(entry.fields ?? []).some((f) => f.name === field.apiName && f.required === true)));
|
|
393
|
+
if (kind === "video" && !tasks.includes("video_to_video") &&
|
|
394
|
+
!hasRequiredSourceMedia(n) && hasRequiredReference(n)) {
|
|
395
|
+
drop.add("text_to_video");
|
|
396
|
+
drop.add("image_to_video");
|
|
397
|
+
const referenceTasks = tasks.filter((task) => task === "reference_to_video");
|
|
398
|
+
if (referenceTasks.length > 0)
|
|
399
|
+
return referenceTasks;
|
|
400
|
+
return ["reference_to_video"];
|
|
401
|
+
}
|
|
402
|
+
if (requiresMediaInput(n, "image"))
|
|
403
|
+
drop.add(generationTask);
|
|
404
|
+
if (kind === "video" && requiresMediaInput(n, "video")) {
|
|
405
|
+
drop.add("text_to_video");
|
|
406
|
+
drop.add("image_to_video");
|
|
407
|
+
}
|
|
408
|
+
if (drop.size === 0)
|
|
409
|
+
return tasks;
|
|
410
|
+
const kept = tasks.filter((t) => !drop.has(t));
|
|
411
|
+
return kept.length > 0 ? kept : [transformTask];
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Tasks whose whole call is "here is a clip, hand me back another one". A
|
|
415
|
+
* model that declares no video input cannot serve one however its id reads:
|
|
416
|
+
* `grok-imagine/upscale` upscales a previous task by id, and fal's
|
|
417
|
+
* `amt-interpolation/frame-interpolation` takes a frame list. Offered under
|
|
418
|
+
* these tasks they fail at call time with nothing to work on.
|
|
419
|
+
*/
|
|
420
|
+
const VIDEO_SOURCE_TASKS = new Set([
|
|
421
|
+
"extend_video",
|
|
422
|
+
"upscale_video",
|
|
423
|
+
"interpolate_video",
|
|
424
|
+
"outpaint_video"
|
|
425
|
+
]);
|
|
426
|
+
function declaresVideoInput(entry) {
|
|
427
|
+
return manifestEntryMediaInputs(entry).some((f) => f.kind === "video");
|
|
428
|
+
}
|
|
429
|
+
/** Pure transform: manifest nodes → deduplicated, task-tagged video models. */
|
|
430
|
+
export function buildVideoModels(manifest, provider) {
|
|
61
431
|
const seen = new Map();
|
|
62
432
|
for (const n of manifest) {
|
|
63
433
|
if (n.outputType !== "video")
|
|
@@ -66,30 +436,744 @@ export function loadVideoModels(packageName, exportPath, provider) {
|
|
|
66
436
|
if (!id)
|
|
67
437
|
continue;
|
|
68
438
|
const name = nodeName(n);
|
|
69
|
-
const
|
|
439
|
+
const declared = explicitTasks(n);
|
|
440
|
+
const inferred = inferVideoTasks(name, id);
|
|
441
|
+
const referenceInputs = manifestEntryReferenceInputs(n);
|
|
442
|
+
let tasks = declared ?? narrowTasksByRequiredInputs(inferred, n, "video");
|
|
443
|
+
if (!declared && referenceInputs.length > 0 &&
|
|
444
|
+
!hasRequiredSourceMedia(n) && !tasks.includes("video_to_video")) {
|
|
445
|
+
const dedicatedReference = inferred.includes("reference_to_video");
|
|
446
|
+
const requiredReference = referenceInputs.some((field) => field.required === true);
|
|
447
|
+
if (dedicatedReference || requiredReference) {
|
|
448
|
+
tasks = ["reference_to_video"];
|
|
449
|
+
}
|
|
450
|
+
else if (!tasks.includes("reference_to_video")) {
|
|
451
|
+
tasks = [...tasks, "reference_to_video"];
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
tasks = tasks.filter((task) => task !== "reference_to_video" || referenceInputs.length > 0);
|
|
455
|
+
tasks = tasks.filter((task) => !VIDEO_SOURCE_TASKS.has(task) || declaresVideoInput(n));
|
|
456
|
+
const supportsEditReferences = tasks.includes("video_to_video") &&
|
|
457
|
+
declaresVideoInput(n) &&
|
|
458
|
+
referenceInputs.filter((field) => field.kind === "image").length === 1 &&
|
|
459
|
+
!referenceInputs.some((field) => field.kind === "video" && field.required);
|
|
460
|
+
tasks = tasks.filter((task) => task !== "video_to_video_reference");
|
|
461
|
+
if (supportsEditReferences) {
|
|
462
|
+
tasks.push("video_to_video_reference");
|
|
463
|
+
}
|
|
464
|
+
// The direct extension contract needs a source video and added duration.
|
|
465
|
+
// A missing mode field means the endpoint continues from the last frame;
|
|
466
|
+
// explicit mode fields contribute only the directions they declare.
|
|
467
|
+
const extensionModeValues = (n.inputFields ?? []).find((field) => (field.apiParamName ?? field.name) === "mode")?.enumValues ??
|
|
468
|
+
(n.fields ?? []).find((field) => field.name === "mode")?.values;
|
|
469
|
+
const hasExtensionDuration = (n.inputFields ?? []).some((field) => (field.apiParamName ?? field.name) === "duration") || (n.fields ?? []).some((field) => field.name === "duration");
|
|
470
|
+
tasks = tasks.filter((task) => task !== "extend_video" ||
|
|
471
|
+
(hasExtensionDuration &&
|
|
472
|
+
(!extensionModeValues ||
|
|
473
|
+
extensionModeValues.includes("start") ||
|
|
474
|
+
extensionModeValues.includes("end"))));
|
|
475
|
+
if (tasks.includes("extend_video")) {
|
|
476
|
+
if (!extensionModeValues || extensionModeValues.includes("end")) {
|
|
477
|
+
tasks.push("extend_video_end");
|
|
478
|
+
}
|
|
479
|
+
if (extensionModeValues?.includes("start")) {
|
|
480
|
+
tasks.push("extend_video_start");
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (tasks.length === 0)
|
|
484
|
+
continue;
|
|
70
485
|
const existing = seen.get(id);
|
|
71
486
|
if (existing) {
|
|
487
|
+
// Stryker disable next-line ArrayDeclaration: existing entries always carry a non-empty supportedTasks (set when first created), so the `?? []` fallback is dead.
|
|
72
488
|
const merged = new Set([...(existing.supportedTasks ?? []), ...tasks]);
|
|
73
489
|
existing.supportedTasks = [...merged];
|
|
490
|
+
// Prefer the first entry that actually declares constraints (image- and
|
|
491
|
+
// text-to-video variants of one model can share an id).
|
|
492
|
+
const c = videoConstraints(n);
|
|
493
|
+
existing.durations ??= c.durations;
|
|
494
|
+
existing.resolutions ??= c.resolutions;
|
|
495
|
+
existing.aspectRatios ??= c.aspectRatios;
|
|
74
496
|
}
|
|
75
497
|
else {
|
|
76
|
-
seen.set(id, {
|
|
498
|
+
seen.set(id, {
|
|
499
|
+
id,
|
|
500
|
+
name,
|
|
501
|
+
provider,
|
|
502
|
+
supportedTasks: tasks,
|
|
503
|
+
...videoConstraints(n)
|
|
504
|
+
});
|
|
77
505
|
}
|
|
78
506
|
}
|
|
79
507
|
return [...seen.values()];
|
|
80
508
|
}
|
|
509
|
+
export function manifestEntryMediaInputs(entry) {
|
|
510
|
+
const fromUploads = (entry.uploads ?? [])
|
|
511
|
+
.filter((u) => u.kind === "image" || u.kind === "video" || u.kind === "audio")
|
|
512
|
+
.map((u) => {
|
|
513
|
+
const field = (entry.fields ?? []).find((f) => f.name === u.field);
|
|
514
|
+
const input = (entry.inputFields ?? []).find((f) => f.name === u.field ||
|
|
515
|
+
(f.apiParamName ?? f.name) === (u.paramName ?? u.field));
|
|
516
|
+
return {
|
|
517
|
+
apiName: u.paramName ?? `${u.field}_url${u.isList ? "s" : ""}`,
|
|
518
|
+
isList: Boolean(u.isList),
|
|
519
|
+
name: u.field,
|
|
520
|
+
kind: u.kind,
|
|
521
|
+
required: field?.required ?? input?.required,
|
|
522
|
+
min: field?.min ?? input?.min,
|
|
523
|
+
max: field?.max ?? input?.max,
|
|
524
|
+
wrapInto: field?.wrapInto
|
|
525
|
+
};
|
|
526
|
+
});
|
|
527
|
+
if (fromUploads.length > 0)
|
|
528
|
+
return fromUploads;
|
|
529
|
+
const fromInput = (entry.inputFields ?? [])
|
|
530
|
+
.filter((f) => {
|
|
531
|
+
const type = f.propType.toLowerCase();
|
|
532
|
+
return [
|
|
533
|
+
"image",
|
|
534
|
+
"list[image]",
|
|
535
|
+
"video",
|
|
536
|
+
"list[video]",
|
|
537
|
+
"audio",
|
|
538
|
+
"list[audio]"
|
|
539
|
+
].includes(type);
|
|
540
|
+
})
|
|
541
|
+
.map((f) => {
|
|
542
|
+
const type = f.propType.toLowerCase();
|
|
543
|
+
return {
|
|
544
|
+
apiName: f.apiParamName ?? f.name,
|
|
545
|
+
isList: type.startsWith("list["),
|
|
546
|
+
name: f.name,
|
|
547
|
+
kind: (type.includes("video")
|
|
548
|
+
? "video"
|
|
549
|
+
: type.includes("audio")
|
|
550
|
+
? "audio"
|
|
551
|
+
: "image"),
|
|
552
|
+
required: f.required,
|
|
553
|
+
min: f.min,
|
|
554
|
+
max: f.max
|
|
555
|
+
};
|
|
556
|
+
});
|
|
557
|
+
if (fromInput.length > 0)
|
|
558
|
+
return fromInput;
|
|
559
|
+
return (entry.fields ?? [])
|
|
560
|
+
.filter((f) => /^(image|video|audio|list\[(image|video|audio)\])$/i.test(f.type))
|
|
561
|
+
.map((f) => ({
|
|
562
|
+
apiName: f.name,
|
|
563
|
+
isList: f.type.toLowerCase().startsWith("list["),
|
|
564
|
+
name: f.name,
|
|
565
|
+
kind: f.type.toLowerCase().includes("video")
|
|
566
|
+
? "video"
|
|
567
|
+
: f.type.toLowerCase().includes("audio")
|
|
568
|
+
? "audio"
|
|
569
|
+
: "image",
|
|
570
|
+
required: f.required,
|
|
571
|
+
min: f.min,
|
|
572
|
+
max: f.max,
|
|
573
|
+
wrapInto: f.wrapInto
|
|
574
|
+
}));
|
|
575
|
+
}
|
|
576
|
+
export function getModelReferenceInputs(packageName, exportPath, modelId) {
|
|
577
|
+
const entry = loadManifest(packageName, exportPath).find((n) => nodeId(n) === modelId);
|
|
578
|
+
if (!entry)
|
|
579
|
+
return [];
|
|
580
|
+
return manifestEntryReferenceInputs(entry);
|
|
581
|
+
}
|
|
582
|
+
function hasRequiredSourceMedia(entry) {
|
|
583
|
+
const references = manifestEntryReferenceInputs(entry);
|
|
584
|
+
return manifestEntryMediaInputs(entry).some((field) => field.required &&
|
|
585
|
+
!references.some((reference) => reference.apiName === field.apiName));
|
|
586
|
+
}
|
|
587
|
+
function manifestEntryReferenceInputs(entry) {
|
|
588
|
+
const explicitReference = entry.supportedTasks?.includes("reference_to_video") === true ||
|
|
589
|
+
inferVideoTasks(nodeName(entry), nodeId(entry)).includes("reference_to_video");
|
|
590
|
+
return manifestEntryMediaInputs(entry).filter((field) => {
|
|
591
|
+
const names = [field.name, field.apiName];
|
|
592
|
+
// Sora calls its optional first-frame input `input_reference`. It is the
|
|
593
|
+
// single start image used by image_to_video, not a role-specific reference
|
|
594
|
+
// array and must not opt the model into reference_to_video.
|
|
595
|
+
if (names.some((name) => /^input_reference$/i.test(name)))
|
|
596
|
+
return false;
|
|
597
|
+
if (field.kind === "image" && /video/i.test(field.apiName))
|
|
598
|
+
return false;
|
|
599
|
+
if (field.kind === "video" && /image/i.test(field.apiName))
|
|
600
|
+
return false;
|
|
601
|
+
if (names.some((name) => /reference|refers|subject/i.test(name)))
|
|
602
|
+
return true;
|
|
603
|
+
return explicitReference && names.some((name) => /^(?:input_)?(?:images?|videos?)(?:_urls?)?$/i.test(name));
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
export function validateReferenceInputs(provider, modelId, inputs, fields) {
|
|
607
|
+
const images = inputs.images.filter((b) => b.length > 0);
|
|
608
|
+
const videos = inputs.videos.filter((b) => b.length > 0);
|
|
609
|
+
const audios = inputs.audios?.filter((b) => b.length > 0) ?? [];
|
|
610
|
+
if (images.length === 0 && videos.length === 0 && audios.length === 0) {
|
|
611
|
+
throw new Error("reference_to_video requires at least one reference image, video, or audio");
|
|
612
|
+
}
|
|
613
|
+
for (const [kind, count] of [
|
|
614
|
+
["image", images.length],
|
|
615
|
+
["video", videos.length],
|
|
616
|
+
["audio", audios.length]
|
|
617
|
+
]) {
|
|
618
|
+
if (count === 0)
|
|
619
|
+
continue;
|
|
620
|
+
const matching = fields.filter((field) => field.kind === kind);
|
|
621
|
+
if (matching.length > 1)
|
|
622
|
+
throw new Error(`${provider} model ${modelId} has ambiguous reference ${kind} inputs`);
|
|
623
|
+
const field = matching[0];
|
|
624
|
+
if (!field)
|
|
625
|
+
throw new Error(`${provider} model ${modelId} does not support reference ${kind}s`);
|
|
626
|
+
if (!field.isList && count > 1)
|
|
627
|
+
throw new Error(`${provider} model ${modelId} accepts only one reference ${kind}`);
|
|
628
|
+
if (!field.wrapInto && field.min !== undefined && count < field.min) {
|
|
629
|
+
throw new Error(`${provider} model ${modelId} requires at least ${field.min} reference ${kind}(s)`);
|
|
630
|
+
}
|
|
631
|
+
if (!field.wrapInto && field.max !== undefined && count > field.max) {
|
|
632
|
+
throw new Error(`${provider} model ${modelId} accepts at most ${field.max} reference ${kind}(s)`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
const requiredGroups = new Set();
|
|
636
|
+
for (const field of fields) {
|
|
637
|
+
if (!field.required)
|
|
638
|
+
continue;
|
|
639
|
+
requiredGroups.add(field.wrapInto ?? `${field.kind}:${field.apiName}`);
|
|
640
|
+
}
|
|
641
|
+
for (const group of requiredGroups) {
|
|
642
|
+
const groupFields = fields.filter((field) => (field.wrapInto ?? `${field.kind}:${field.apiName}`) === group);
|
|
643
|
+
const count = groupFields.reduce((total, field) => total +
|
|
644
|
+
(field.kind === "image"
|
|
645
|
+
? images.length
|
|
646
|
+
: field.kind === "video"
|
|
647
|
+
? videos.length
|
|
648
|
+
: audios.length), 0);
|
|
649
|
+
if (count === 0)
|
|
650
|
+
throw new Error(`${provider} model ${modelId} requires at least one reference image or video`);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Image inputs a model declares, resolved from its manifest entry. KIE-style
|
|
655
|
+
* `uploads` (which carry the real API param name) take precedence; otherwise
|
|
656
|
+
* the FAL/Replicate `inputFields` image/list[image] entries are used.
|
|
657
|
+
*/
|
|
658
|
+
/**
|
|
659
|
+
* Input parameter names a model declares, from the generated manifest.
|
|
660
|
+
*
|
|
661
|
+
* Providers assemble a generic input bag from `*Params` (prompt,
|
|
662
|
+
* negative_prompt, strength, seed…), but each endpoint accepts only a subset.
|
|
663
|
+
* Replicate rejects the whole prediction with a validation error when it sees
|
|
664
|
+
* an undeclared field rather than ignoring it, so sending the superset fails
|
|
665
|
+
* outright — `decart/lucy-edit-2` takes neither `negative_prompt` nor
|
|
666
|
+
* `strength`.
|
|
667
|
+
*
|
|
668
|
+
* Returns an empty set when the model is missing from the manifest or declares
|
|
669
|
+
* no fields. Callers must read that as "unknown, send everything" rather than
|
|
670
|
+
* "accepts nothing", so a model absent from the manifest keeps working.
|
|
671
|
+
*/
|
|
672
|
+
export function getModelInputNames(packageName, exportPath, modelId) {
|
|
673
|
+
const entry = loadManifest(packageName, exportPath).find((n) => nodeId(n) === modelId);
|
|
674
|
+
return new Set((entry?.inputFields ?? [])
|
|
675
|
+
.map((f) => f.name)
|
|
676
|
+
.filter((n) => typeof n === "string" && n.length > 0));
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Every media input a model declares, image and video alike, normalized across
|
|
680
|
+
* the manifest conventions. {@link getModelImageInputs} answers the image-only
|
|
681
|
+
* question; a video transform needs the field its clip attaches to.
|
|
682
|
+
*/
|
|
683
|
+
export function getModelMediaInputs(packageName, exportPath, modelId) {
|
|
684
|
+
const entry = loadManifest(packageName, exportPath).find((n) => nodeId(n) === modelId);
|
|
685
|
+
if (!entry)
|
|
686
|
+
return [];
|
|
687
|
+
return manifestEntryMediaInputs(entry);
|
|
688
|
+
}
|
|
689
|
+
export function getModelImageInputs(packageName, exportPath, modelId) {
|
|
690
|
+
const entry = loadManifest(packageName, exportPath).find((n) => nodeId(n) === modelId);
|
|
691
|
+
if (!entry)
|
|
692
|
+
return [];
|
|
693
|
+
return manifestEntryImageInputs(entry);
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Pure: resolve the image inputs declared by a single manifest entry. KIE-style
|
|
697
|
+
* `uploads` (which carry the real API param name) take precedence; otherwise the
|
|
698
|
+
* FAL/Replicate `inputFields` image / list[image] entries are used.
|
|
699
|
+
*/
|
|
700
|
+
export function manifestEntryImageInputs(entry) {
|
|
701
|
+
if (entry.uploads?.length) {
|
|
702
|
+
return entry.uploads
|
|
703
|
+
.filter((u) => u.kind === "image")
|
|
704
|
+
.map((u) => ({
|
|
705
|
+
apiName: u.paramName ?? `${u.field}_url${u.isList ? "s" : ""}`,
|
|
706
|
+
isList: Boolean(u.isList),
|
|
707
|
+
name: u.field
|
|
708
|
+
}));
|
|
709
|
+
}
|
|
710
|
+
return (entry.inputFields ?? [])
|
|
711
|
+
.filter((f) => {
|
|
712
|
+
const t = f.propType.toLowerCase();
|
|
713
|
+
return t === "image" || t === "list[image]";
|
|
714
|
+
})
|
|
715
|
+
.map((f) => ({
|
|
716
|
+
apiName: f.apiParamName ?? f.name,
|
|
717
|
+
isList: f.propType.toLowerCase().startsWith("list["),
|
|
718
|
+
name: f.name
|
|
719
|
+
}));
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* The input fields a model declares, normalized across manifest conventions.
|
|
723
|
+
* Kie `fields` (where `name` is the API param and options live in `values`) take
|
|
724
|
+
* precedence; otherwise the FAL/Replicate `inputFields` are normalized
|
|
725
|
+
* (`apiParamName ?? name`, `propType` lowercased, `enumValues`).
|
|
726
|
+
*/
|
|
727
|
+
export function getModelInputFields(packageName, exportPath, modelId) {
|
|
728
|
+
const entry = loadManifest(packageName, exportPath).find((n) => nodeId(n) === modelId);
|
|
729
|
+
if (!entry)
|
|
730
|
+
return [];
|
|
731
|
+
if (entry.fields?.length) {
|
|
732
|
+
return entry.fields.map((f) => {
|
|
733
|
+
const field = {
|
|
734
|
+
name: f.name,
|
|
735
|
+
type: f.type.toLowerCase()
|
|
736
|
+
};
|
|
737
|
+
if (f.values && f.values.length > 0) {
|
|
738
|
+
field.enumValues = f.values;
|
|
739
|
+
}
|
|
740
|
+
if (f.required !== undefined) {
|
|
741
|
+
field.required = f.required;
|
|
742
|
+
}
|
|
743
|
+
if (f.default !== undefined) {
|
|
744
|
+
field.default = f.default;
|
|
745
|
+
}
|
|
746
|
+
if (isNumber(f.min)) {
|
|
747
|
+
field.min = f.min;
|
|
748
|
+
}
|
|
749
|
+
if (isNumber(f.max)) {
|
|
750
|
+
field.max = f.max;
|
|
751
|
+
}
|
|
752
|
+
if (f.wrapInto !== undefined) {
|
|
753
|
+
field.wrapInto = f.wrapInto;
|
|
754
|
+
}
|
|
755
|
+
return field;
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
return (entry.inputFields ?? []).map((f) => {
|
|
759
|
+
const field = {
|
|
760
|
+
name: f.apiParamName ?? f.name,
|
|
761
|
+
type: f.propType.toLowerCase()
|
|
762
|
+
};
|
|
763
|
+
if (f.enumValues && f.enumValues.length > 0) {
|
|
764
|
+
field.enumValues = f.enumValues;
|
|
765
|
+
}
|
|
766
|
+
return field;
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
function requiredTextInputsOf(entry) {
|
|
770
|
+
const names = new Set();
|
|
771
|
+
const textFields = new Set();
|
|
772
|
+
// Kie: `fields`, where `name` is the API param.
|
|
773
|
+
for (const field of entry.fields ?? []) {
|
|
774
|
+
if (field.type.toLowerCase() !== "str")
|
|
775
|
+
continue;
|
|
776
|
+
textFields.add(field.name);
|
|
777
|
+
if (field.required === true)
|
|
778
|
+
names.add(field.name);
|
|
779
|
+
}
|
|
780
|
+
// Kie also states it a second way, as the rules the node factory enforces
|
|
781
|
+
// before spending a call. Most entries say both; some say only this one.
|
|
782
|
+
for (const rule of entry.validation ?? []) {
|
|
783
|
+
if (rule.rule === "not_empty" && textFields.has(rule.field)) {
|
|
784
|
+
names.add(rule.field);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
// FAL / Replicate: `inputFields`. The declared `name` is the property name;
|
|
788
|
+
// `apiParamName` is the wire name, which is not what a node property is called.
|
|
789
|
+
for (const field of entry.inputFields ?? []) {
|
|
790
|
+
if (field.required === true && field.propType.toLowerCase() === "str") {
|
|
791
|
+
names.add(field.name);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
return [...names];
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Text fields `modelId` refuses to run without, by the name the manifest gives
|
|
798
|
+
* them — which is also the name the generic media nodes give the matching
|
|
799
|
+
* property, so a caller can match one against the other.
|
|
800
|
+
*
|
|
801
|
+
* Restricted to text on purpose. A required *media* field is fed through the
|
|
802
|
+
* node's own image/video wiring under a different API name (kie's `images`
|
|
803
|
+
* arrives as `image_urls`), and a required number or enum carries a default the
|
|
804
|
+
* provider fills in. What is left is the case that actually fails: a text field
|
|
805
|
+
* the endpoint rejects when blank. `kling-2.6/image-to-video` declares `prompt`
|
|
806
|
+
* required in both conventions and answers an empty one with
|
|
807
|
+
* `500 This field is required`, naming neither the field nor the model.
|
|
808
|
+
*
|
|
809
|
+
* Undefined means the model is not in this manifest — "cannot tell", never
|
|
810
|
+
* "requires nothing". An entry that declares none returns an empty list.
|
|
811
|
+
*/
|
|
812
|
+
export function getRequiredTextInputNames(packageName, exportPath, modelId) {
|
|
813
|
+
const entry = loadManifest(packageName, exportPath).find((n) => nodeId(n) === modelId);
|
|
814
|
+
if (!entry)
|
|
815
|
+
return undefined;
|
|
816
|
+
return requiredTextInputsOf(entry);
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Kie execution metadata for a model, or undefined when the model id is unknown.
|
|
820
|
+
*/
|
|
821
|
+
export function getManifestNodeMeta(packageName, exportPath, modelId) {
|
|
822
|
+
const entry = loadManifest(packageName, exportPath).find((n) => nodeId(n) === modelId);
|
|
823
|
+
if (!entry)
|
|
824
|
+
return undefined;
|
|
825
|
+
const meta = { useSuno: Boolean(entry.useSuno) };
|
|
826
|
+
if (entry.sunoEndpoint !== undefined) {
|
|
827
|
+
meta.sunoEndpoint = entry.sunoEndpoint;
|
|
828
|
+
}
|
|
829
|
+
if (entry.pollInterval !== undefined) {
|
|
830
|
+
meta.pollInterval = entry.pollInterval;
|
|
831
|
+
}
|
|
832
|
+
if (entry.maxAttempts !== undefined) {
|
|
833
|
+
meta.maxAttempts = entry.maxAttempts;
|
|
834
|
+
}
|
|
835
|
+
return meta;
|
|
836
|
+
}
|
|
837
|
+
// Auxiliary image inputs (mask / control / reference / style / end-frame, …)
|
|
838
|
+
// must never receive the primary source image in a generic request.
|
|
839
|
+
const AUXILIARY_IMAGE_RE =
|
|
840
|
+
// Stryker disable next-line Regex: heuristic alternation; auxiliary-vs-primary behaviour is pinned by tests, but the optional `[_-]?` separator variants are functionally interchangeable for real field names.
|
|
841
|
+
/mask|control|reference|style|depth|canny|pose|ip[_-]?adapter|redux|face|last_frame|end_image|end_frame/i;
|
|
842
|
+
// Primary source-image field names, best first. Covers i2i (`image`,
|
|
843
|
+
// `input_image`, `image_input`) and i2v first-frame (`first_frame_image`,
|
|
844
|
+
// `start_image`).
|
|
845
|
+
const PRIMARY_IMAGE_PRIORITY = [
|
|
846
|
+
"image",
|
|
847
|
+
"input_image",
|
|
848
|
+
"image_input",
|
|
849
|
+
"images",
|
|
850
|
+
"input_images",
|
|
851
|
+
"img",
|
|
852
|
+
"first_frame_image",
|
|
853
|
+
"start_image",
|
|
854
|
+
"source_image",
|
|
855
|
+
"image_path",
|
|
856
|
+
"subject_image",
|
|
857
|
+
"file"
|
|
858
|
+
];
|
|
859
|
+
/**
|
|
860
|
+
* Choose the mask image input declared by a model's manifest entry. Returns the
|
|
861
|
+
* first field whose name or API param name contains "mask", or undefined when
|
|
862
|
+
* the model doesn't declare one (callers fall back to a conventional name like
|
|
863
|
+
* `mask_url`).
|
|
864
|
+
*/
|
|
865
|
+
export function selectMaskImageInput(inputs) {
|
|
866
|
+
return inputs.find((i) => /mask/i.test(i.name) || /mask/i.test(i.apiName));
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* Choose the primary image input for a generic image-to-image / image-to-video
|
|
870
|
+
* request. Auxiliary inputs (mask, control, reference/style/end-frame images)
|
|
871
|
+
* are skipped. When more than one source image is supplied, a list-typed field
|
|
872
|
+
* is strongly preferred so every image is forwarded.
|
|
873
|
+
*/
|
|
874
|
+
export function selectPrimaryImageInput(inputs, imageCount) {
|
|
875
|
+
const primary = inputs.filter((i) => !AUXILIARY_IMAGE_RE.test(i.name));
|
|
876
|
+
const pool = primary.length > 0 ? primary : inputs;
|
|
877
|
+
// Stryker disable next-line ConditionalExpression: equivalent — pool is empty only when inputs is empty, and the sort+[0] below also yields undefined for an empty pool.
|
|
878
|
+
if (pool.length === 0)
|
|
879
|
+
return undefined;
|
|
880
|
+
const multiple = imageCount > 1;
|
|
881
|
+
const score = (i) => {
|
|
882
|
+
let idx = PRIMARY_IMAGE_PRIORITY.indexOf(i.name.toLowerCase());
|
|
883
|
+
if (idx < 0)
|
|
884
|
+
idx = PRIMARY_IMAGE_PRIORITY.length;
|
|
885
|
+
if (multiple)
|
|
886
|
+
idx += i.isList ? -100 : 10;
|
|
887
|
+
return idx;
|
|
888
|
+
};
|
|
889
|
+
return [...pool].sort((a, b) => score(a) - score(b))[0];
|
|
890
|
+
}
|
|
81
891
|
export function loadImageModels(packageName, exportPath, provider) {
|
|
82
|
-
|
|
892
|
+
return buildImageModels(loadManifest(packageName, exportPath), provider);
|
|
893
|
+
}
|
|
894
|
+
/** Pure transform: manifest nodes → deduplicated, task-tagged image models. */
|
|
895
|
+
export function buildImageModels(manifest, provider) {
|
|
896
|
+
const seen = new Map();
|
|
897
|
+
for (const n of manifest) {
|
|
898
|
+
const id = nodeId(n);
|
|
899
|
+
if (!id || seen.has(id))
|
|
900
|
+
continue;
|
|
901
|
+
const name = nodeName(n);
|
|
902
|
+
// Copy so the mask-input augmentation below never mutates the manifest
|
|
903
|
+
// node's own `supportedTasks` array (which `loadManifest` memoizes and
|
|
904
|
+
// shares across every caller).
|
|
905
|
+
const tasks = [
|
|
906
|
+
...(explicitTasks(n) ??
|
|
907
|
+
narrowTasksByRequiredInputs(inferImageTasks(name, id), n, "image"))
|
|
908
|
+
];
|
|
909
|
+
// Endpoints that declare a mask image input support inpainting (mask-guided
|
|
910
|
+
// editing). Tag them so the inpaint capability/picker can find them.
|
|
911
|
+
if (!tasks.includes("inpainting") &&
|
|
912
|
+
selectMaskImageInput(manifestEntryImageInputs(n))) {
|
|
913
|
+
tasks.push("inpainting");
|
|
914
|
+
}
|
|
915
|
+
// Image-typed entries always qualify. `dict`-typed entries (FAL endpoints
|
|
916
|
+
// whose response schema is an object, e.g. clarity-upscaler) are salvaged
|
|
917
|
+
// only when they're recognizable image transforms, so the picker can offer
|
|
918
|
+
// them under upscale/remove-background/relight/vectorize.
|
|
919
|
+
const qualifies = n.outputType === "image" ||
|
|
920
|
+
// Stryker disable next-line ConditionalExpression,EqualityOperator: tasks is always non-empty (infer* returns >=1, explicit is non-empty), so `tasks.length > 0` is invariantly true here.
|
|
921
|
+
(n.outputType === "dict" && tasks.length > 0);
|
|
922
|
+
if (!qualifies)
|
|
923
|
+
continue;
|
|
924
|
+
seen.set(id, {
|
|
925
|
+
id,
|
|
926
|
+
name,
|
|
927
|
+
provider,
|
|
928
|
+
supportedTasks: tasks,
|
|
929
|
+
...imageConstraints(n)
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
return [...seen.values()];
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* Everything an audio entry says about itself: id, name, and the capability
|
|
936
|
+
* tags. FAL files every text→audio endpoint — speech, music, and sound
|
|
937
|
+
* effects alike — under `moduleName: "text_to_audio"`, so the module name
|
|
938
|
+
* cannot tell the three apart; the tags can.
|
|
939
|
+
*/
|
|
940
|
+
function audioHaystack(n) {
|
|
941
|
+
return `${nodeId(n)} ${nodeName(n)} ${(n.tags ?? []).join(" ")}`.toLowerCase();
|
|
942
|
+
}
|
|
943
|
+
/** Speech that self-declares in its id or name, rather than in a tag. */
|
|
944
|
+
function saysTextToSpeech(n) {
|
|
945
|
+
const hay = `${nodeId(n)} ${nodeName(n)}`.toLowerCase();
|
|
946
|
+
return matchesAny(hay, "text-to-speech", "text to speech", "texttospeech", "tts");
|
|
947
|
+
}
|
|
948
|
+
// Endpoints that take existing audio and hand back more of it. They are not
|
|
949
|
+
// generators: offered as a text_to_music or text_to_speech model they fail at
|
|
950
|
+
// call time with nothing to work on.
|
|
951
|
+
const AUDIO_TRANSFORM_KEYWORDS = [
|
|
952
|
+
"audio-to-audio",
|
|
953
|
+
"audio_to_audio",
|
|
954
|
+
"speech-to-speech",
|
|
955
|
+
"video-to-audio",
|
|
956
|
+
"inpaint",
|
|
957
|
+
"outpaint",
|
|
958
|
+
"isolation",
|
|
959
|
+
"voice-changer",
|
|
960
|
+
"extend-audio",
|
|
961
|
+
"audio-extend",
|
|
962
|
+
"super-resolution",
|
|
963
|
+
"loudnorm",
|
|
964
|
+
"compressor",
|
|
965
|
+
"impulse-response"
|
|
966
|
+
];
|
|
967
|
+
// Sound-effect generators. They emit audio from a prompt like a music model
|
|
968
|
+
// does, and are neither music nor speech.
|
|
969
|
+
const SOUND_EFFECT_KEYWORDS = [
|
|
970
|
+
"sound-effect",
|
|
971
|
+
"sound_effect",
|
|
972
|
+
"sound effects",
|
|
973
|
+
"soundeffect",
|
|
974
|
+
"sfx",
|
|
975
|
+
"foley"
|
|
976
|
+
];
|
|
977
|
+
// Speech engines whose names carry no "tts"/"speech" substring. Without them
|
|
978
|
+
// FAL's Kokoro language variants — tagged only `[audio, generation,
|
|
979
|
+
// text-to-audio, sound]` — are in no list at all.
|
|
980
|
+
const TTS_KEYWORDS = [
|
|
981
|
+
"speech",
|
|
982
|
+
"voiceover",
|
|
983
|
+
"narration",
|
|
984
|
+
"dialogue",
|
|
985
|
+
"kokoro",
|
|
986
|
+
"zonos",
|
|
987
|
+
"chatterbox"
|
|
988
|
+
];
|
|
989
|
+
/** Takes existing audio rather than generating it from a prompt. */
|
|
990
|
+
export function isAudioTransformNode(n) {
|
|
991
|
+
return matchesAny(audioHaystack(n), ...AUDIO_TRANSFORM_KEYWORDS);
|
|
992
|
+
}
|
|
993
|
+
/** Generates sound effects — neither music nor speech. */
|
|
994
|
+
export function isSoundEffectNode(n) {
|
|
995
|
+
return matchesAny(audioHaystack(n), ...SOUND_EFFECT_KEYWORDS);
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Is this manifest entry a text-to-speech model? An explicit task or FAL's
|
|
999
|
+
* `moduleName: "text_to_speech"` settles it, as does a "text-to-speech"/"tts"
|
|
1000
|
+
* hint in the id or name — those are the endpoint naming itself, and outrank
|
|
1001
|
+
* the exclusions below, so a voice-clone TTS endpoint that also takes
|
|
1002
|
+
* reference audio stays a TTS model. Everything else is decided on the tags:
|
|
1003
|
+
* a music model, an audio transform and a sound-effect generator are none of
|
|
1004
|
+
* them speech, and what is left qualifies on a speech keyword.
|
|
1005
|
+
*/
|
|
1006
|
+
export function isTTSNode(n) {
|
|
1007
|
+
if (n.outputType !== "audio")
|
|
1008
|
+
return false;
|
|
1009
|
+
if (explicitTasks(n)?.includes("text_to_speech"))
|
|
1010
|
+
return true;
|
|
1011
|
+
if (n.moduleName === "text_to_speech")
|
|
1012
|
+
return true;
|
|
1013
|
+
if (saysTextToSpeech(n))
|
|
1014
|
+
return true;
|
|
1015
|
+
if (isMusicNode(n) || isAudioTransformNode(n) || isSoundEffectNode(n)) {
|
|
1016
|
+
return false;
|
|
1017
|
+
}
|
|
1018
|
+
return matchesAny(audioHaystack(n), ...TTS_KEYWORDS);
|
|
1019
|
+
}
|
|
1020
|
+
export function loadTTSModels(packageName, exportPath, provider) {
|
|
1021
|
+
return buildTTSModels(loadManifest(packageName, exportPath), provider);
|
|
1022
|
+
}
|
|
1023
|
+
/** Pure transform: manifest nodes → deduplicated TTS models with voice lists. */
|
|
1024
|
+
export function buildTTSModels(manifest, provider) {
|
|
83
1025
|
const seen = new Map();
|
|
84
1026
|
for (const n of manifest) {
|
|
85
|
-
if (n
|
|
1027
|
+
if (!isTTSNode(n))
|
|
86
1028
|
continue;
|
|
87
1029
|
const id = nodeId(n);
|
|
88
|
-
if (!id)
|
|
1030
|
+
if (!id || seen.has(id))
|
|
1031
|
+
continue;
|
|
1032
|
+
// Preset voices come from an enumerated `voice` (or `speaker`) field.
|
|
1033
|
+
// Models that take a free-form voice id / reference audio have none.
|
|
1034
|
+
const voices = enumValuesFor(n, "voice") ?? enumValuesFor(n, "speaker");
|
|
1035
|
+
const model = {
|
|
1036
|
+
id,
|
|
1037
|
+
name: nodeName(n),
|
|
1038
|
+
provider: provider
|
|
1039
|
+
};
|
|
1040
|
+
if (voices) {
|
|
1041
|
+
model.voices = voices;
|
|
1042
|
+
}
|
|
1043
|
+
seen.set(id, model);
|
|
1044
|
+
}
|
|
1045
|
+
return [...seen.values()];
|
|
1046
|
+
}
|
|
1047
|
+
// Music-generation engine names and signal words. Used to tell music endpoints
|
|
1048
|
+
// apart from speech / sound-effect audio nodes (the `outputType === "audio"`
|
|
1049
|
+
// guard plus the `!isTTSNode` check already exclude TTS).
|
|
1050
|
+
const MUSIC_KEYWORDS = [
|
|
1051
|
+
"music",
|
|
1052
|
+
"song",
|
|
1053
|
+
"lyric",
|
|
1054
|
+
"vocal",
|
|
1055
|
+
"instrumental",
|
|
1056
|
+
"soundtrack",
|
|
1057
|
+
"melody",
|
|
1058
|
+
"remix",
|
|
1059
|
+
"cover",
|
|
1060
|
+
"musicgen",
|
|
1061
|
+
"audiogen",
|
|
1062
|
+
"stable-audio",
|
|
1063
|
+
"stable_audio",
|
|
1064
|
+
"suno",
|
|
1065
|
+
"ace-step",
|
|
1066
|
+
"acestep",
|
|
1067
|
+
"diffrhythm",
|
|
1068
|
+
"lyria",
|
|
1069
|
+
"sonauto",
|
|
1070
|
+
"cassette",
|
|
1071
|
+
"beatoven",
|
|
1072
|
+
"jukebox",
|
|
1073
|
+
"flux-music",
|
|
1074
|
+
"yue"
|
|
1075
|
+
];
|
|
1076
|
+
// Kie Suno ships many audio endpoints, but only a few generate music from a
|
|
1077
|
+
// text prompt — the rest transform existing audio or tasks (referenced by
|
|
1078
|
+
// upload / task / audio / music ids) or output text (lyrics). A required field
|
|
1079
|
+
// whose name references such an existing asset marks a transform, not a
|
|
1080
|
+
// generator.
|
|
1081
|
+
const AUDIO_ASSET_REF_RE =
|
|
1082
|
+
// Stryker disable next-line Regex: heuristic alternation over Suno field names; the exact-boundary variants are interchangeable for the real manifest names, and the predicate is pinned by a test against the real manifest.
|
|
1083
|
+
/upload|task_?id|audio_?id|music_?id|verify|voice_?url/i;
|
|
1084
|
+
/**
|
|
1085
|
+
* Is this Kie-style entry a text-to-music generator? True only when it declares
|
|
1086
|
+
* a free-form `prompt`, selects a generation `model`, and requires no field
|
|
1087
|
+
* that references an existing audio asset or task. On the current Kie manifest
|
|
1088
|
+
* this yields exactly {generate-music, generate-sounds}; every Suno utility
|
|
1089
|
+
* (covers, extends, mashups, vocal splits, lyric/voice tools) is rejected.
|
|
1090
|
+
*/
|
|
1091
|
+
export function isKieMusicNode(n) {
|
|
1092
|
+
const fields = n.fields ?? [];
|
|
1093
|
+
const hasPrompt = fields.some((f) => f.name === "prompt");
|
|
1094
|
+
const hasModel = fields.some((f) => f.name === "model");
|
|
1095
|
+
const requiresAsset = fields.some((f) => f.required === true && AUDIO_ASSET_REF_RE.test(f.name));
|
|
1096
|
+
return hasPrompt && hasModel && !requiresAsset;
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Is this manifest entry a music-generation model? Music endpoints emit audio
|
|
1100
|
+
* from a prompt but are neither speech nor sound effects. Kie-style entries
|
|
1101
|
+
* (those that carry a `fields` schema or route through Suno) are judged
|
|
1102
|
+
* structurally by `isKieMusicNode` — keyword matching over-tags Suno's utility
|
|
1103
|
+
* endpoints. Everything else needs positive music evidence: an explicit
|
|
1104
|
+
* `text_to_music` task, or a music keyword in the id, name or tags.
|
|
1105
|
+
*
|
|
1106
|
+
* FAL's `moduleName: "text_to_audio"` used to be taken as that evidence, and
|
|
1107
|
+
* it is not one: FAL files every text→audio endpoint under it, so the music
|
|
1108
|
+
* catalog carried the Kokoro voices, `elevenlabs/text-to-dialogue` and every
|
|
1109
|
+
* sound-effect generator — and a session asking for a music model was handed a
|
|
1110
|
+
* dialogue model that a `music_model` property then refused.
|
|
1111
|
+
*/
|
|
1112
|
+
export function isMusicNode(n) {
|
|
1113
|
+
if (n.outputType !== "audio")
|
|
1114
|
+
return false;
|
|
1115
|
+
if (Array.isArray(n.fields) || n.useSuno === true) {
|
|
1116
|
+
return isKieMusicNode(n);
|
|
1117
|
+
}
|
|
1118
|
+
if (explicitTasks(n)?.includes("text_to_music"))
|
|
1119
|
+
return true;
|
|
1120
|
+
if (isAudioTransformNode(n) || isSoundEffectNode(n))
|
|
1121
|
+
return false;
|
|
1122
|
+
return matchesAny(audioHaystack(n), ...MUSIC_KEYWORDS);
|
|
1123
|
+
}
|
|
1124
|
+
export function loadMusicModels(packageName, exportPath, provider) {
|
|
1125
|
+
return buildMusicModels(loadManifest(packageName, exportPath), provider);
|
|
1126
|
+
}
|
|
1127
|
+
export function loadAudioToAudioModels(packageName, exportPath, provider) {
|
|
1128
|
+
return buildAudioToAudioModels(loadManifest(packageName, exportPath), provider);
|
|
1129
|
+
}
|
|
1130
|
+
/**
|
|
1131
|
+
* Pure transform: manifest nodes → deduplicated audio-transform models. These
|
|
1132
|
+
* are exactly the entries {@link isAudioTransformNode} keeps out of the TTS and
|
|
1133
|
+
* music lists, which is the point: without this list they are in no list at all.
|
|
1134
|
+
*
|
|
1135
|
+
* The TTS and music tests run first, and win. A speech endpoint that names
|
|
1136
|
+
* itself one — `fal-ai/dia-tts/voice-clone`, `fal-ai/tada/*\/text-to-speech` —
|
|
1137
|
+
* matches a transform keyword through its tags while still needing text to say;
|
|
1138
|
+
* offered here it would be called with a recording and no text. The same
|
|
1139
|
+
* precedence {@link isTTSNode} already applies, applied from this side.
|
|
1140
|
+
*/
|
|
1141
|
+
export function buildAudioToAudioModels(manifest, provider) {
|
|
1142
|
+
const seen = new Map();
|
|
1143
|
+
for (const n of manifest) {
|
|
1144
|
+
if (n.outputType !== "audio")
|
|
89
1145
|
continue;
|
|
90
|
-
if (
|
|
1146
|
+
if (!isAudioTransformNode(n))
|
|
1147
|
+
continue;
|
|
1148
|
+
if (isTTSNode(n) || isMusicNode(n))
|
|
1149
|
+
continue;
|
|
1150
|
+
const id = nodeId(n);
|
|
1151
|
+
if (!id || seen.has(id))
|
|
1152
|
+
continue;
|
|
1153
|
+
seen.set(id, {
|
|
1154
|
+
id,
|
|
1155
|
+
name: nodeName(n),
|
|
1156
|
+
provider,
|
|
1157
|
+
supportedTasks: ["audio_to_audio"]
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
return [...seen.values()];
|
|
1161
|
+
}
|
|
1162
|
+
/** Pure transform: manifest nodes → deduplicated text-to-music models. */
|
|
1163
|
+
export function buildMusicModels(manifest, provider) {
|
|
1164
|
+
const seen = new Map();
|
|
1165
|
+
for (const n of manifest) {
|
|
1166
|
+
if (!isMusicNode(n))
|
|
1167
|
+
continue;
|
|
1168
|
+
const id = nodeId(n);
|
|
1169
|
+
if (!id || seen.has(id))
|
|
91
1170
|
continue;
|
|
92
|
-
seen.set(id, {
|
|
1171
|
+
seen.set(id, {
|
|
1172
|
+
id,
|
|
1173
|
+
name: nodeName(n),
|
|
1174
|
+
provider: provider,
|
|
1175
|
+
supportedTasks: ["text_to_music"]
|
|
1176
|
+
});
|
|
93
1177
|
}
|
|
94
1178
|
return [...seen.values()];
|
|
95
1179
|
}
|