@nodetool-ai/runtime 0.7.0-rc.23 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -0
- package/dist/.nodetool-build-stamp +1 -0
- package/dist/agent-memory.d.ts +118 -0
- package/dist/agent-memory.d.ts.map +1 -0
- package/dist/agent-memory.js +186 -0
- package/dist/agent-memory.js.map +1 -0
- package/dist/async-local-storage.d.ts +20 -0
- package/dist/async-local-storage.d.ts.map +1 -0
- package/dist/async-local-storage.js +28 -0
- package/dist/async-local-storage.js.map +1 -0
- package/dist/blender-executor.d.ts +93 -0
- package/dist/blender-executor.d.ts.map +1 -0
- package/dist/blender-executor.js +294 -0
- package/dist/blender-executor.js.map +1 -0
- package/dist/comfy-executor.d.ts +23 -1
- package/dist/comfy-executor.d.ts.map +1 -1
- package/dist/comfy-executor.js +234 -55
- package/dist/comfy-executor.js.map +1 -1
- package/dist/context-packer.d.ts +1 -1
- package/dist/context-packer.d.ts.map +1 -1
- package/dist/context-packer.js +23 -25
- package/dist/context-packer.js.map +1 -1
- package/dist/context.d.ts +982 -127
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +2649 -530
- package/dist/context.js.map +1 -1
- package/dist/cost-reconciler.d.ts +32 -0
- package/dist/cost-reconciler.d.ts.map +1 -0
- package/dist/cost-reconciler.js +21 -0
- package/dist/cost-reconciler.js.map +1 -0
- package/dist/entities.d.ts +33 -0
- package/dist/entities.d.ts.map +1 -0
- package/dist/entities.js +43 -0
- package/dist/entities.js.map +1 -0
- package/dist/external-media-fetch.d.ts +29 -0
- package/dist/external-media-fetch.d.ts.map +1 -0
- package/dist/external-media-fetch.js +38 -0
- package/dist/external-media-fetch.js.map +1 -0
- package/dist/generation-receipt.d.ts +86 -0
- package/dist/generation-receipt.d.ts.map +1 -0
- package/dist/generation-receipt.js +137 -0
- package/dist/generation-receipt.js.map +1 -0
- package/dist/generation-registry.d.ts +75 -0
- package/dist/generation-registry.d.ts.map +1 -0
- package/dist/generation-registry.js +144 -0
- package/dist/generation-registry.js.map +1 -0
- package/dist/google/client.d.ts +196 -0
- package/dist/google/client.d.ts.map +1 -0
- package/dist/google/client.js +396 -0
- package/dist/google/client.js.map +1 -0
- package/dist/google/index.d.ts +3 -0
- package/dist/google/index.d.ts.map +1 -0
- package/dist/google/index.js +3 -0
- package/dist/google/index.js.map +1 -0
- package/dist/google/token.d.ts +15 -0
- package/dist/google/token.d.ts.map +1 -0
- package/dist/google/token.js +24 -0
- package/dist/google/token.js.map +1 -0
- package/dist/host-binaries.d.ts +106 -0
- package/dist/host-binaries.d.ts.map +1 -0
- package/dist/host-binaries.js +352 -0
- package/dist/host-binaries.js.map +1 -0
- package/dist/image-codec.d.ts +76 -0
- package/dist/image-codec.d.ts.map +1 -0
- package/dist/image-codec.js +293 -0
- package/dist/image-codec.js.map +1 -0
- package/dist/image-model-tasks.d.ts +15 -0
- package/dist/image-model-tasks.d.ts.map +1 -0
- package/dist/image-model-tasks.js +24 -0
- package/dist/image-model-tasks.js.map +1 -0
- package/dist/index.d.ts +67 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +51 -4
- package/dist/index.js.map +1 -1
- package/dist/invocation-account.d.ts +43 -0
- package/dist/invocation-account.d.ts.map +1 -0
- package/dist/invocation-account.js +60 -0
- package/dist/invocation-account.js.map +1 -0
- package/dist/media-ref-bytes.d.ts +35 -0
- package/dist/media-ref-bytes.d.ts.map +1 -0
- package/dist/media-ref-bytes.js +191 -0
- package/dist/media-ref-bytes.js.map +1 -0
- package/dist/node-executor.d.ts +119 -2
- package/dist/node-executor.d.ts.map +1 -1
- package/dist/permission-gate.d.ts +152 -0
- package/dist/permission-gate.d.ts.map +1 -0
- package/dist/permission-gate.js +87 -0
- package/dist/permission-gate.js.map +1 -0
- package/dist/prefixed-storage-adapter.d.ts +33 -0
- package/dist/prefixed-storage-adapter.d.ts.map +1 -0
- package/dist/prefixed-storage-adapter.js +70 -0
- package/dist/prefixed-storage-adapter.js.map +1 -0
- package/dist/prompt-asset-refs.d.ts +222 -0
- package/dist/prompt-asset-refs.d.ts.map +1 -0
- package/dist/prompt-asset-refs.js +751 -0
- package/dist/prompt-asset-refs.js.map +1 -0
- package/dist/provider-cache.d.ts +3 -0
- package/dist/provider-cache.d.ts.map +1 -0
- package/dist/provider-cache.js +8 -0
- package/dist/provider-cache.js.map +1 -0
- package/dist/providers/aki-provider.d.ts +35 -10
- package/dist/providers/aki-provider.d.ts.map +1 -1
- package/dist/providers/aki-provider.js +90 -77
- package/dist/providers/aki-provider.js.map +1 -1
- package/dist/providers/alibaba-provider.d.ts +39 -0
- package/dist/providers/alibaba-provider.d.ts.map +1 -0
- package/dist/providers/alibaba-provider.js +86 -0
- package/dist/providers/alibaba-provider.js.map +1 -0
- package/dist/providers/anthropic-provider.d.ts +52 -3
- package/dist/providers/anthropic-provider.d.ts.map +1 -1
- package/dist/providers/anthropic-provider.js +1020 -338
- package/dist/providers/anthropic-provider.js.map +1 -1
- package/dist/providers/atlascloud-provider.d.ts +103 -0
- package/dist/providers/atlascloud-provider.d.ts.map +1 -0
- package/dist/providers/atlascloud-provider.js +1031 -0
- package/dist/providers/atlascloud-provider.js.map +1 -0
- package/dist/providers/atlascloud-transport.d.ts +100 -0
- package/dist/providers/atlascloud-transport.d.ts.map +1 -0
- package/dist/providers/atlascloud-transport.js +364 -0
- package/dist/providers/atlascloud-transport.js.map +1 -0
- package/dist/providers/atlascloud-webhook-registry.d.ts +35 -0
- package/dist/providers/atlascloud-webhook-registry.d.ts.map +1 -0
- package/dist/providers/atlascloud-webhook-registry.js +104 -0
- package/dist/providers/atlascloud-webhook-registry.js.map +1 -0
- package/dist/providers/audio-mime.d.ts +11 -0
- package/dist/providers/audio-mime.d.ts.map +1 -0
- package/dist/providers/audio-mime.js +46 -0
- package/dist/providers/audio-mime.js.map +1 -0
- package/dist/providers/base-provider.d.ts +501 -32
- package/dist/providers/base-provider.d.ts.map +1 -1
- package/dist/providers/base-provider.js +1280 -87
- package/dist/providers/base-provider.js.map +1 -1
- package/dist/providers/cassette-provider.d.ts +217 -0
- package/dist/providers/cassette-provider.d.ts.map +1 -0
- package/dist/providers/cassette-provider.js +502 -0
- package/dist/providers/cassette-provider.js.map +1 -0
- package/dist/providers/cerebras-provider.d.ts +6 -12
- package/dist/providers/cerebras-provider.d.ts.map +1 -1
- package/dist/providers/cerebras-provider.js +8 -33
- package/dist/providers/cerebras-provider.js.map +1 -1
- package/dist/providers/claude-agent-provider.d.ts +107 -74
- package/dist/providers/claude-agent-provider.d.ts.map +1 -1
- package/dist/providers/claude-agent-provider.js +1279 -517
- package/dist/providers/claude-agent-provider.js.map +1 -1
- package/dist/providers/codex-provider.d.ts +84 -0
- package/dist/providers/codex-provider.d.ts.map +1 -0
- package/dist/providers/codex-provider.js +505 -0
- package/dist/providers/codex-provider.js.map +1 -0
- package/dist/providers/cohere-provider.d.ts +7 -3
- package/dist/providers/cohere-provider.d.ts.map +1 -1
- package/dist/providers/cohere-provider.js +14 -2
- package/dist/providers/cohere-provider.js.map +1 -1
- package/dist/providers/comfy-cloud-provider.d.ts +30 -0
- package/dist/providers/comfy-cloud-provider.d.ts.map +1 -0
- package/dist/providers/comfy-cloud-provider.js +42 -0
- package/dist/providers/comfy-cloud-provider.js.map +1 -0
- package/dist/providers/content-filter.d.ts +46 -0
- package/dist/providers/content-filter.d.ts.map +1 -0
- package/dist/providers/content-filter.js +92 -0
- package/dist/providers/content-filter.js.map +1 -0
- package/dist/providers/context-exceeded.d.ts +37 -0
- package/dist/providers/context-exceeded.d.ts.map +1 -0
- package/dist/providers/context-exceeded.js +114 -0
- package/dist/providers/context-exceeded.js.map +1 -0
- package/dist/providers/context-window.d.ts +44 -0
- package/dist/providers/context-window.d.ts.map +1 -0
- package/dist/providers/context-window.js +145 -0
- package/dist/providers/context-window.js.map +1 -0
- package/dist/providers/contract/index.d.ts +4 -0
- package/dist/providers/contract/index.d.ts.map +1 -0
- package/dist/providers/contract/index.js +4 -0
- package/dist/providers/contract/index.js.map +1 -0
- package/dist/providers/contract/probe-manifest.d.ts +75 -0
- package/dist/providers/contract/probe-manifest.d.ts.map +1 -0
- package/dist/providers/contract/probe-manifest.js +379 -0
- package/dist/providers/contract/probe-manifest.js.map +1 -0
- package/dist/providers/contract/probe-runner.d.ts +57 -0
- package/dist/providers/contract/probe-runner.d.ts.map +1 -0
- package/dist/providers/contract/probe-runner.js +180 -0
- package/dist/providers/contract/probe-runner.js.map +1 -0
- package/dist/providers/contract/redact.d.ts +29 -0
- package/dist/providers/contract/redact.d.ts.map +1 -0
- package/dist/providers/contract/redact.js +113 -0
- package/dist/providers/contract/redact.js.map +1 -0
- package/dist/providers/core-tools.d.ts +75 -0
- package/dist/providers/core-tools.d.ts.map +1 -0
- package/dist/providers/core-tools.js +128 -0
- package/dist/providers/core-tools.js.map +1 -0
- package/dist/providers/cost-calculator.d.ts +69 -50
- package/dist/providers/cost-calculator.d.ts.map +1 -1
- package/dist/providers/cost-calculator.js +207 -397
- package/dist/providers/cost-calculator.js.map +1 -1
- package/dist/providers/credential-check.d.ts +45 -0
- package/dist/providers/credential-check.d.ts.map +1 -0
- package/dist/providers/credential-check.js +164 -0
- package/dist/providers/credential-check.js.map +1 -0
- package/dist/providers/custom-model-kinds.d.ts +33 -0
- package/dist/providers/custom-model-kinds.d.ts.map +1 -0
- package/dist/providers/custom-model-kinds.js +96 -0
- package/dist/providers/custom-model-kinds.js.map +1 -0
- package/dist/providers/custom-openai-provider.d.ts +83 -0
- package/dist/providers/custom-openai-provider.d.ts.map +1 -0
- package/dist/providers/custom-openai-provider.js +190 -0
- package/dist/providers/custom-openai-provider.js.map +1 -0
- package/dist/providers/custom-provider-registry.d.ts +19 -0
- package/dist/providers/custom-provider-registry.d.ts.map +1 -0
- package/dist/providers/custom-provider-registry.js +52 -0
- package/dist/providers/custom-provider-registry.js.map +1 -0
- package/dist/providers/deepseek-provider.d.ts +8 -14
- package/dist/providers/deepseek-provider.d.ts.map +1 -1
- package/dist/providers/deepseek-provider.js +6 -35
- package/dist/providers/deepseek-provider.js.map +1 -1
- package/dist/providers/defaults.d.ts +12 -0
- package/dist/providers/defaults.d.ts.map +1 -0
- package/dist/providers/defaults.js +12 -0
- package/dist/providers/defaults.js.map +1 -0
- package/dist/providers/dreamina-models.d.ts +5 -0
- package/dist/providers/dreamina-models.d.ts.map +1 -0
- package/dist/providers/dreamina-models.js +57 -0
- package/dist/providers/dreamina-models.js.map +1 -0
- package/dist/providers/dreamina-music.d.ts +52 -0
- package/dist/providers/dreamina-music.d.ts.map +1 -0
- package/dist/providers/dreamina-music.js +115 -0
- package/dist/providers/dreamina-music.js.map +1 -0
- package/dist/providers/dreamina-provider.d.ts +62 -0
- package/dist/providers/dreamina-provider.d.ts.map +1 -0
- package/dist/providers/dreamina-provider.js +790 -0
- package/dist/providers/dreamina-provider.js.map +1 -0
- package/dist/providers/elevenlabs-provider.d.ts +50 -0
- package/dist/providers/elevenlabs-provider.d.ts.map +1 -0
- package/dist/providers/elevenlabs-provider.js +147 -0
- package/dist/providers/elevenlabs-provider.js.map +1 -0
- package/dist/providers/entity-references.d.ts +31 -0
- package/dist/providers/entity-references.d.ts.map +1 -0
- package/dist/providers/entity-references.js +113 -0
- package/dist/providers/entity-references.js.map +1 -0
- package/dist/providers/evolink-provider.d.ts +40 -0
- package/dist/providers/evolink-provider.d.ts.map +1 -0
- package/dist/providers/evolink-provider.js +312 -0
- package/dist/providers/evolink-provider.js.map +1 -0
- package/dist/providers/fake-provider.d.ts +74 -2
- package/dist/providers/fake-provider.d.ts.map +1 -1
- package/dist/providers/fake-provider.js +213 -1
- package/dist/providers/fake-provider.js.map +1 -1
- package/dist/providers/fal-generations.d.ts +106 -0
- package/dist/providers/fal-generations.d.ts.map +1 -0
- package/dist/providers/fal-generations.js +286 -0
- package/dist/providers/fal-generations.js.map +1 -0
- package/dist/providers/fal-provider.d.ts +173 -8
- package/dist/providers/fal-provider.d.ts.map +1 -1
- package/dist/providers/fal-provider.js +1396 -64
- package/dist/providers/fal-provider.js.map +1 -1
- package/dist/providers/fal-queue.d.ts +47 -0
- package/dist/providers/fal-queue.d.ts.map +1 -0
- package/dist/providers/fal-queue.js +231 -0
- package/dist/providers/fal-queue.js.map +1 -0
- package/dist/providers/gemini-provider.d.ts +150 -9
- package/dist/providers/gemini-provider.d.ts.map +1 -1
- package/dist/providers/gemini-provider.js +1515 -316
- package/dist/providers/gemini-provider.js.map +1 -1
- package/dist/providers/gmi-provider.d.ts +21 -0
- package/dist/providers/gmi-provider.d.ts.map +1 -0
- package/dist/providers/gmi-provider.js +34 -0
- package/dist/providers/gmi-provider.js.map +1 -0
- package/dist/providers/gpt-image-size.d.ts +3 -0
- package/dist/providers/gpt-image-size.d.ts.map +1 -0
- package/dist/providers/gpt-image-size.js +37 -0
- package/dist/providers/gpt-image-size.js.map +1 -0
- package/dist/providers/groq-provider.d.ts +9 -12
- package/dist/providers/groq-provider.d.ts.map +1 -1
- package/dist/providers/groq-provider.js +19 -33
- package/dist/providers/groq-provider.js.map +1 -1
- package/dist/providers/groq-request.d.ts +38 -0
- package/dist/providers/groq-request.d.ts.map +1 -0
- package/dist/providers/groq-request.js +138 -0
- package/dist/providers/groq-request.js.map +1 -0
- package/dist/providers/higgsfield-provider.d.ts +31 -0
- package/dist/providers/higgsfield-provider.d.ts.map +1 -0
- package/dist/providers/higgsfield-provider.js +143 -0
- package/dist/providers/higgsfield-provider.js.map +1 -0
- package/dist/providers/higgsfield-transport.d.ts +106 -0
- package/dist/providers/higgsfield-transport.d.ts.map +1 -0
- package/dist/providers/higgsfield-transport.js +182 -0
- package/dist/providers/higgsfield-transport.js.map +1 -0
- package/dist/providers/http-transport.d.ts +74 -0
- package/dist/providers/http-transport.d.ts.map +1 -0
- package/dist/providers/http-transport.js +152 -0
- package/dist/providers/http-transport.js.map +1 -0
- package/dist/providers/huggingface-provider.d.ts +73 -4
- package/dist/providers/huggingface-provider.d.ts.map +1 -1
- package/dist/providers/huggingface-provider.js +240 -34
- package/dist/providers/huggingface-provider.js.map +1 -1
- package/dist/providers/image-mime.d.ts +39 -0
- package/dist/providers/image-mime.d.ts.map +1 -0
- package/dist/providers/image-mime.js +111 -0
- package/dist/providers/image-mime.js.map +1 -0
- package/dist/providers/image-ref.d.ts +21 -0
- package/dist/providers/image-ref.d.ts.map +1 -0
- package/dist/providers/image-ref.js +44 -0
- package/dist/providers/image-ref.js.map +1 -0
- package/dist/providers/index.d.ts +87 -8
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +250 -56
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/jina-provider.d.ts +12 -3
- package/dist/providers/jina-provider.d.ts.map +1 -1
- package/dist/providers/jina-provider.js +21 -7
- package/dist/providers/jina-provider.js.map +1 -1
- package/dist/providers/kie-provider.d.ts +172 -10
- package/dist/providers/kie-provider.d.ts.map +1 -1
- package/dist/providers/kie-provider.js +1190 -228
- package/dist/providers/kie-provider.js.map +1 -1
- package/dist/providers/kie-webhook-registry.d.ts +28 -0
- package/dist/providers/kie-webhook-registry.d.ts.map +1 -0
- package/dist/providers/kie-webhook-registry.js +91 -0
- package/dist/providers/kie-webhook-registry.js.map +1 -0
- package/dist/providers/llama-provider.d.ts +23 -36
- package/dist/providers/llama-provider.d.ts.map +1 -1
- package/dist/providers/llama-provider.js +50 -355
- package/dist/providers/llama-provider.js.map +1 -1
- package/dist/providers/lmstudio-provider.d.ts +4 -7
- package/dist/providers/lmstudio-provider.d.ts.map +1 -1
- package/dist/providers/lmstudio-provider.js +24 -15
- package/dist/providers/lmstudio-provider.js.map +1 -1
- package/dist/providers/loop-tracing.d.ts +17 -0
- package/dist/providers/loop-tracing.d.ts.map +1 -0
- package/dist/providers/loop-tracing.js +132 -0
- package/dist/providers/loop-tracing.js.map +1 -0
- package/dist/providers/manifest-models.d.ts +341 -1
- package/dist/providers/manifest-models.d.ts.map +1 -1
- package/dist/providers/manifest-models.js +1106 -22
- package/dist/providers/manifest-models.js.map +1 -1
- package/dist/providers/media-mime.d.ts +14 -0
- package/dist/providers/media-mime.d.ts.map +1 -0
- package/dist/providers/media-mime.js +35 -0
- package/dist/providers/media-mime.js.map +1 -0
- package/dist/providers/meshy-provider.d.ts +16 -2
- package/dist/providers/meshy-provider.d.ts.map +1 -1
- package/dist/providers/meshy-provider.js +82 -46
- package/dist/providers/meshy-provider.js.map +1 -1
- package/dist/providers/meta-provider.d.ts +22 -0
- package/dist/providers/meta-provider.d.ts.map +1 -0
- package/dist/providers/meta-provider.js +54 -0
- package/dist/providers/meta-provider.js.map +1 -0
- package/dist/providers/minimax-provider.d.ts +27 -18
- package/dist/providers/minimax-provider.d.ts.map +1 -1
- package/dist/providers/minimax-provider.js +222 -72
- package/dist/providers/minimax-provider.js.map +1 -1
- package/dist/providers/mistral-provider.d.ts +8 -12
- package/dist/providers/mistral-provider.d.ts.map +1 -1
- package/dist/providers/mistral-provider.js +15 -34
- package/dist/providers/mistral-provider.js.map +1 -1
- package/dist/providers/moonshot-provider.d.ts +11 -15
- package/dist/providers/moonshot-provider.d.ts.map +1 -1
- package/dist/providers/moonshot-provider.js +33 -23
- package/dist/providers/moonshot-provider.js.map +1 -1
- package/dist/providers/node-llama-cpp-provider.d.ts +99 -0
- package/dist/providers/node-llama-cpp-provider.d.ts.map +1 -0
- package/dist/providers/node-llama-cpp-provider.js +484 -0
- package/dist/providers/node-llama-cpp-provider.js.map +1 -0
- package/dist/providers/nodetool-provider.d.ts +49 -0
- package/dist/providers/nodetool-provider.d.ts.map +1 -0
- package/dist/providers/nodetool-provider.js +222 -0
- package/dist/providers/nodetool-provider.js.map +1 -0
- package/dist/providers/oauth/browser-launcher.d.ts +23 -0
- package/dist/providers/oauth/browser-launcher.d.ts.map +1 -0
- package/dist/providers/oauth/browser-launcher.js +45 -0
- package/dist/providers/oauth/browser-launcher.js.map +1 -0
- package/dist/providers/oauth/claude-code-credentials.d.ts +74 -0
- package/dist/providers/oauth/claude-code-credentials.d.ts.map +1 -0
- package/dist/providers/oauth/claude-code-credentials.js +133 -0
- package/dist/providers/oauth/claude-code-credentials.js.map +1 -0
- package/dist/providers/oauth/claude-code-login.d.ts +124 -0
- package/dist/providers/oauth/claude-code-login.d.ts.map +1 -0
- package/dist/providers/oauth/claude-code-login.js +230 -0
- package/dist/providers/oauth/claude-code-login.js.map +1 -0
- package/dist/providers/oauth/claude-code-oauth-client.d.ts +111 -0
- package/dist/providers/oauth/claude-code-oauth-client.d.ts.map +1 -0
- package/dist/providers/oauth/claude-code-oauth-client.js +207 -0
- package/dist/providers/oauth/claude-code-oauth-client.js.map +1 -0
- package/dist/providers/oauth/errors.d.ts +54 -0
- package/dist/providers/oauth/errors.d.ts.map +1 -0
- package/dist/providers/oauth/errors.js +79 -0
- package/dist/providers/oauth/errors.js.map +1 -0
- package/dist/providers/oauth/index.d.ts +105 -0
- package/dist/providers/oauth/index.d.ts.map +1 -0
- package/dist/providers/oauth/index.js +167 -0
- package/dist/providers/oauth/index.js.map +1 -0
- package/dist/providers/oauth/jwt-claims.d.ts +23 -0
- package/dist/providers/oauth/jwt-claims.d.ts.map +1 -0
- package/dist/providers/oauth/jwt-claims.js +67 -0
- package/dist/providers/oauth/jwt-claims.js.map +1 -0
- package/dist/providers/oauth/local-callback-server.d.ts +60 -0
- package/dist/providers/oauth/local-callback-server.d.ts.map +1 -0
- package/dist/providers/oauth/local-callback-server.js +148 -0
- package/dist/providers/oauth/local-callback-server.js.map +1 -0
- package/dist/providers/oauth/oauth-client.d.ts +55 -0
- package/dist/providers/oauth/oauth-client.d.ts.map +1 -0
- package/dist/providers/oauth/oauth-client.js +161 -0
- package/dist/providers/oauth/oauth-client.js.map +1 -0
- package/dist/providers/oauth/openai-oauth-provider.d.ts +129 -0
- package/dist/providers/oauth/openai-oauth-provider.d.ts.map +1 -0
- package/dist/providers/oauth/openai-oauth-provider.js +265 -0
- package/dist/providers/oauth/openai-oauth-provider.js.map +1 -0
- package/dist/providers/oauth/pkce-helper.d.ts +43 -0
- package/dist/providers/oauth/pkce-helper.d.ts.map +1 -0
- package/dist/providers/oauth/pkce-helper.js +57 -0
- package/dist/providers/oauth/pkce-helper.js.map +1 -0
- package/dist/providers/oauth/redaction.d.ts +22 -0
- package/dist/providers/oauth/redaction.d.ts.map +1 -0
- package/dist/providers/oauth/redaction.js +59 -0
- package/dist/providers/oauth/redaction.js.map +1 -0
- package/dist/providers/oauth/secure-credential-store.d.ts +67 -0
- package/dist/providers/oauth/secure-credential-store.d.ts.map +1 -0
- package/dist/providers/oauth/secure-credential-store.js +73 -0
- package/dist/providers/oauth/secure-credential-store.js.map +1 -0
- package/dist/providers/oauth/token-store.d.ts +53 -0
- package/dist/providers/oauth/token-store.d.ts.map +1 -0
- package/dist/providers/oauth/token-store.js +78 -0
- package/dist/providers/oauth/token-store.js.map +1 -0
- package/dist/providers/oauth/types.d.ts +64 -0
- package/dist/providers/oauth/types.d.ts.map +1 -0
- package/dist/providers/oauth/types.js +12 -0
- package/dist/providers/oauth/types.js.map +1 -0
- package/dist/providers/object-tracking.d.ts +47 -0
- package/dist/providers/object-tracking.d.ts.map +1 -0
- package/dist/providers/object-tracking.js +46 -0
- package/dist/providers/object-tracking.js.map +1 -0
- package/dist/providers/offline-model-index.d.ts +20 -0
- package/dist/providers/offline-model-index.d.ts.map +1 -0
- package/dist/providers/offline-model-index.js +112 -0
- package/dist/providers/offline-model-index.js.map +1 -0
- package/dist/providers/ollama-provider.d.ts +30 -2
- package/dist/providers/ollama-provider.d.ts.map +1 -1
- package/dist/providers/ollama-provider.js +198 -43
- package/dist/providers/ollama-provider.js.map +1 -1
- package/dist/providers/openai-compat/client.d.ts +57 -0
- package/dist/providers/openai-compat/client.d.ts.map +1 -0
- package/dist/providers/openai-compat/client.js +157 -0
- package/dist/providers/openai-compat/client.js.map +1 -0
- package/dist/providers/openai-compat/decode.d.ts +36 -0
- package/dist/providers/openai-compat/decode.d.ts.map +1 -0
- package/dist/providers/openai-compat/decode.js +55 -0
- package/dist/providers/openai-compat/decode.js.map +1 -0
- package/dist/providers/openai-compat/errors.d.ts +54 -0
- package/dist/providers/openai-compat/errors.d.ts.map +1 -0
- package/dist/providers/openai-compat/errors.js +144 -0
- package/dist/providers/openai-compat/errors.js.map +1 -0
- package/dist/providers/openai-compat/index.d.ts +8 -0
- package/dist/providers/openai-compat/index.d.ts.map +1 -0
- package/dist/providers/openai-compat/index.js +5 -0
- package/dist/providers/openai-compat/index.js.map +1 -0
- package/dist/providers/openai-compat/sse.d.ts +14 -0
- package/dist/providers/openai-compat/sse.d.ts.map +1 -0
- package/dist/providers/openai-compat/sse.js +69 -0
- package/dist/providers/openai-compat/sse.js.map +1 -0
- package/dist/providers/openai-compat/types.d.ts +75 -0
- package/dist/providers/openai-compat/types.d.ts.map +1 -0
- package/dist/providers/openai-compat/types.js +9 -0
- package/dist/providers/openai-compat/types.js.map +1 -0
- package/dist/providers/openai-compat-provider.d.ts +90 -0
- package/dist/providers/openai-compat-provider.d.ts.map +1 -0
- package/dist/providers/openai-compat-provider.js +282 -0
- package/dist/providers/openai-compat-provider.js.map +1 -0
- package/dist/providers/openai-provider.d.ts +98 -27
- package/dist/providers/openai-provider.d.ts.map +1 -1
- package/dist/providers/openai-provider.js +1037 -105
- package/dist/providers/openai-provider.js.map +1 -1
- package/dist/providers/openrouter-provider.d.ts +58 -13
- package/dist/providers/openrouter-provider.d.ts.map +1 -1
- package/dist/providers/openrouter-provider.js +383 -51
- package/dist/providers/openrouter-provider.js.map +1 -1
- package/dist/providers/provider-call-failure.d.ts +39 -0
- package/dist/providers/provider-call-failure.d.ts.map +1 -0
- package/dist/providers/provider-call-failure.js +132 -0
- package/dist/providers/provider-call-failure.js.map +1 -0
- package/dist/providers/provider-error.d.ts +59 -0
- package/dist/providers/provider-error.d.ts.map +1 -0
- package/dist/providers/provider-error.js +171 -0
- package/dist/providers/provider-error.js.map +1 -0
- package/dist/providers/provider-generations.d.ts +99 -0
- package/dist/providers/provider-generations.d.ts.map +1 -0
- package/dist/providers/provider-generations.js +71 -0
- package/dist/providers/provider-generations.js.map +1 -0
- package/dist/providers/provider-queue.d.ts +62 -0
- package/dist/providers/provider-queue.d.ts.map +1 -0
- package/dist/providers/provider-queue.js +28 -0
- package/dist/providers/provider-queue.js.map +1 -0
- package/dist/providers/provider-registry.d.ts +33 -14
- package/dist/providers/provider-registry.d.ts.map +1 -1
- package/dist/providers/provider-registry.js +85 -68
- package/dist/providers/provider-registry.js.map +1 -1
- package/dist/providers/provider-request-log.d.ts +53 -0
- package/dist/providers/provider-request-log.d.ts.map +1 -0
- package/dist/providers/provider-request-log.js +168 -0
- package/dist/providers/provider-request-log.js.map +1 -0
- package/dist/providers/provider-session.d.ts +3 -0
- package/dist/providers/provider-session.d.ts.map +1 -0
- package/dist/providers/provider-session.js +10 -0
- package/dist/providers/provider-session.js.map +1 -0
- package/dist/providers/provider-transport.d.ts +11 -0
- package/dist/providers/provider-transport.d.ts.map +1 -0
- package/dist/providers/provider-transport.js +11 -0
- package/dist/providers/provider-transport.js.map +1 -0
- package/dist/providers/python-provider.d.ts +26 -13
- package/dist/providers/python-provider.d.ts.map +1 -1
- package/dist/providers/python-provider.js +204 -25
- package/dist/providers/python-provider.js.map +1 -1
- package/dist/providers/replicate-provider.d.ts +91 -8
- package/dist/providers/replicate-provider.d.ts.map +1 -1
- package/dist/providers/replicate-provider.js +823 -65
- package/dist/providers/replicate-provider.js.map +1 -1
- package/dist/providers/replicate-retry.d.ts +30 -0
- package/dist/providers/replicate-retry.d.ts.map +1 -0
- package/dist/providers/replicate-retry.js +97 -0
- package/dist/providers/replicate-retry.js.map +1 -0
- package/dist/providers/requesty-provider.d.ts +30 -0
- package/dist/providers/requesty-provider.d.ts.map +1 -0
- package/dist/providers/requesty-provider.js +87 -0
- package/dist/providers/requesty-provider.js.map +1 -0
- package/dist/providers/responses-api.d.ts +27 -0
- package/dist/providers/responses-api.d.ts.map +1 -0
- package/dist/providers/responses-api.js +297 -0
- package/dist/providers/responses-api.js.map +1 -0
- package/dist/providers/reve-provider.d.ts +46 -0
- package/dist/providers/reve-provider.d.ts.map +1 -0
- package/dist/providers/reve-provider.js +165 -0
- package/dist/providers/reve-provider.js.map +1 -0
- package/dist/providers/rodin-provider.d.ts +25 -2
- package/dist/providers/rodin-provider.d.ts.map +1 -1
- package/dist/providers/rodin-provider.js +95 -51
- package/dist/providers/rodin-provider.js.map +1 -1
- package/dist/providers/safe-url.d.ts +70 -0
- package/dist/providers/safe-url.d.ts.map +1 -0
- package/dist/providers/safe-url.js +277 -0
- package/dist/providers/safe-url.js.map +1 -0
- package/dist/providers/scripted-provider.d.ts +9 -0
- package/dist/providers/scripted-provider.d.ts.map +1 -1
- package/dist/providers/scripted-provider.js +86 -10
- package/dist/providers/scripted-provider.js.map +1 -1
- package/dist/providers/structured-output.d.ts +42 -0
- package/dist/providers/structured-output.d.ts.map +1 -0
- package/dist/providers/structured-output.js +83 -0
- package/dist/providers/structured-output.js.map +1 -0
- package/dist/providers/together-operations.d.ts +27 -0
- package/dist/providers/together-operations.d.ts.map +1 -0
- package/dist/providers/together-operations.js +95 -0
- package/dist/providers/together-operations.js.map +1 -0
- package/dist/providers/together-provider.d.ts +18 -18
- package/dist/providers/together-provider.d.ts.map +1 -1
- package/dist/providers/together-provider.js +140 -367
- package/dist/providers/together-provider.js.map +1 -1
- package/dist/providers/topaz-provider.d.ts +45 -0
- package/dist/providers/topaz-provider.d.ts.map +1 -0
- package/dist/providers/topaz-provider.js +244 -0
- package/dist/providers/topaz-provider.js.map +1 -0
- package/dist/providers/types.d.ts +641 -4
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/types.js +81 -1
- package/dist/providers/types.js.map +1 -1
- package/dist/providers/useapi-provider.d.ts +28 -0
- package/dist/providers/useapi-provider.d.ts.map +1 -0
- package/dist/providers/useapi-provider.js +274 -0
- package/dist/providers/useapi-provider.js.map +1 -0
- package/dist/providers/video-duration.d.ts +6 -0
- package/dist/providers/video-duration.d.ts.map +1 -0
- package/dist/providers/video-duration.js +63 -0
- package/dist/providers/video-duration.js.map +1 -0
- package/dist/providers/video-frame-fallback.d.ts +39 -0
- package/dist/providers/video-frame-fallback.d.ts.map +1 -0
- package/dist/providers/video-frame-fallback.js +155 -0
- package/dist/providers/video-frame-fallback.js.map +1 -0
- package/dist/providers/video-frames.d.ts +61 -0
- package/dist/providers/video-frames.d.ts.map +1 -0
- package/dist/providers/video-frames.js +208 -0
- package/dist/providers/video-frames.js.map +1 -0
- package/dist/providers/video-mime.d.ts +14 -0
- package/dist/providers/video-mime.d.ts.map +1 -0
- package/dist/providers/video-mime.js +44 -0
- package/dist/providers/video-mime.js.map +1 -0
- package/dist/providers/video-to-audio.d.ts +34 -0
- package/dist/providers/video-to-audio.d.ts.map +1 -0
- package/dist/providers/video-to-audio.js +62 -0
- package/dist/providers/video-to-audio.js.map +1 -0
- package/dist/providers/vllm-provider.d.ts +20 -8
- package/dist/providers/vllm-provider.d.ts.map +1 -1
- package/dist/providers/vllm-provider.js +44 -23
- package/dist/providers/vllm-provider.js.map +1 -1
- package/dist/providers/voyage-provider.d.ts +12 -3
- package/dist/providers/voyage-provider.d.ts.map +1 -1
- package/dist/providers/voyage-provider.js +21 -7
- package/dist/providers/voyage-provider.js.map +1 -1
- package/dist/providers/xai-provider.d.ts +56 -13
- package/dist/providers/xai-provider.d.ts.map +1 -1
- package/dist/providers/xai-provider.js +307 -22
- package/dist/providers/xai-provider.js.map +1 -1
- package/dist/python-bridge-base.d.ts +358 -0
- package/dist/python-bridge-base.d.ts.map +1 -0
- package/dist/python-bridge-base.js +1557 -0
- package/dist/python-bridge-base.js.map +1 -0
- package/dist/python-bridge-codec.d.ts +28 -0
- package/dist/python-bridge-codec.d.ts.map +1 -0
- package/dist/python-bridge-codec.js +39 -0
- package/dist/python-bridge-codec.js.map +1 -0
- package/dist/python-bridge-factory.d.ts +52 -0
- package/dist/python-bridge-factory.d.ts.map +1 -0
- package/dist/python-bridge-factory.js +69 -0
- package/dist/python-bridge-factory.js.map +1 -0
- package/dist/python-bridge-framing.d.ts +68 -0
- package/dist/python-bridge-framing.d.ts.map +1 -0
- package/dist/python-bridge-framing.js +91 -0
- package/dist/python-bridge-framing.js.map +1 -0
- package/dist/python-bridge-types.d.ts +545 -2
- package/dist/python-bridge-types.d.ts.map +1 -1
- package/dist/python-bridge-types.js +49 -1
- package/dist/python-bridge-types.js.map +1 -1
- package/dist/python-graph-resolver.d.ts +57 -0
- package/dist/python-graph-resolver.d.ts.map +1 -0
- package/dist/python-graph-resolver.js +79 -0
- package/dist/python-graph-resolver.js.map +1 -0
- package/dist/python-node-executor.d.ts +36 -4
- package/dist/python-node-executor.d.ts.map +1 -1
- package/dist/python-node-executor.js +177 -86
- package/dist/python-node-executor.js.map +1 -1
- package/dist/python-stdio-bridge.d.ts +18 -37
- package/dist/python-stdio-bridge.d.ts.map +1 -1
- package/dist/python-stdio-bridge.js +148 -475
- package/dist/python-stdio-bridge.js.map +1 -1
- package/dist/python-websocket-bridge.d.ts +140 -0
- package/dist/python-websocket-bridge.d.ts.map +1 -0
- package/dist/python-websocket-bridge.js +561 -0
- package/dist/python-websocket-bridge.js.map +1 -0
- package/dist/python-worker-stderr.d.ts +4 -0
- package/dist/python-worker-stderr.d.ts.map +1 -0
- package/dist/python-worker-stderr.js +24 -0
- package/dist/python-worker-stderr.js.map +1 -0
- package/dist/recommended-models.d.ts +2 -2
- package/dist/recommended-models.d.ts.map +1 -1
- package/dist/recommended-models.js +44 -4
- package/dist/recommended-models.js.map +1 -1
- package/dist/recoverable-node-error.d.ts +31 -0
- package/dist/recoverable-node-error.d.ts.map +1 -0
- package/dist/recoverable-node-error.js +37 -0
- package/dist/recoverable-node-error.js.map +1 -0
- package/dist/redact-params.d.ts +13 -0
- package/dist/redact-params.d.ts.map +1 -0
- package/dist/redact-params.js +57 -0
- package/dist/redact-params.js.map +1 -0
- package/dist/run-context.d.ts +20 -0
- package/dist/run-context.d.ts.map +1 -0
- package/dist/run-context.js +18 -0
- package/dist/run-context.js.map +1 -0
- package/dist/run-trace-context.d.ts +30 -0
- package/dist/run-trace-context.d.ts.map +1 -0
- package/dist/run-trace-context.js +80 -0
- package/dist/run-trace-context.js.map +1 -0
- package/dist/run-trace-processor.d.ts +65 -0
- package/dist/run-trace-processor.d.ts.map +1 -0
- package/dist/run-trace-processor.js +417 -0
- package/dist/run-trace-processor.js.map +1 -0
- package/dist/run-trace-serialization.d.ts +5 -0
- package/dist/run-trace-serialization.d.ts.map +1 -0
- package/dist/run-trace-serialization.js +113 -0
- package/dist/run-trace-serialization.js.map +1 -0
- package/dist/sandbox-module-catalog.d.ts +43 -0
- package/dist/sandbox-module-catalog.d.ts.map +1 -0
- package/dist/sandbox-module-catalog.js +25 -0
- package/dist/sandbox-module-catalog.js.map +1 -0
- package/dist/storage-workspace.d.ts +62 -0
- package/dist/storage-workspace.d.ts.map +1 -0
- package/dist/storage-workspace.js +441 -0
- package/dist/storage-workspace.js.map +1 -0
- package/dist/swappable-python-bridge.d.ts +83 -0
- package/dist/swappable-python-bridge.d.ts.map +1 -0
- package/dist/swappable-python-bridge.js +246 -0
- package/dist/swappable-python-bridge.js.map +1 -0
- package/dist/telemetry.d.ts +16 -3
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +76 -20
- package/dist/telemetry.js.map +1 -1
- package/dist/testing.d.ts +81 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +130 -0
- package/dist/testing.js.map +1 -0
- package/dist/token-counter.d.ts +30 -0
- package/dist/token-counter.d.ts.map +1 -0
- package/dist/token-counter.js +156 -0
- package/dist/token-counter.js.map +1 -0
- package/dist/trace-exporters.d.ts +11 -28
- package/dist/trace-exporters.d.ts.map +1 -1
- package/dist/trace-exporters.js +69 -22
- package/dist/trace-exporters.js.map +1 -1
- package/dist/tracing-helpers.d.ts +60 -26
- package/dist/tracing-helpers.d.ts.map +1 -1
- package/dist/tracing-helpers.js +100 -25
- package/dist/tracing-helpers.js.map +1 -1
- package/dist/trim-video-window.d.ts +8 -0
- package/dist/trim-video-window.d.ts.map +1 -0
- package/dist/trim-video-window.js +50 -0
- package/dist/trim-video-window.js.map +1 -0
- package/dist/turn-budget.d.ts +278 -0
- package/dist/turn-budget.d.ts.map +1 -0
- package/dist/turn-budget.js +365 -0
- package/dist/turn-budget.js.map +1 -0
- package/dist/variable-channel.d.ts +48 -0
- package/dist/variable-channel.d.ts.map +1 -0
- package/dist/variable-channel.js +110 -0
- package/dist/variable-channel.js.map +1 -0
- package/dist/workspace.d.ts +119 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +112 -0
- package/dist/workspace.js.map +1 -0
- package/package.json +121 -19
|
@@ -0,0 +1,1557 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport-agnostic Python worker bridge base class.
|
|
3
|
+
*
|
|
4
|
+
* Holds all the protocol logic that is independent of how bytes move
|
|
5
|
+
* between the JS runtime and the Python worker — pending-request bookkeeping,
|
|
6
|
+
* message dispatch, discover/execute/cancel, provider RPC, and the shared
|
|
7
|
+
* connection lifecycle. Concrete transports (stdio, WebSocket) subclass this
|
|
8
|
+
* and implement the small set of transport hooks below.
|
|
9
|
+
*
|
|
10
|
+
* The wire protocol itself (msgpack-encoded `{type, request_id, data}` frames)
|
|
11
|
+
* is shared; only framing/transport differs per subclass.
|
|
12
|
+
*/
|
|
13
|
+
import { getNodeBuiltinSync, safeProcessEnv } from "@nodetool-ai/config";
|
|
14
|
+
// The base only needs crypto (request IDs) and events (EventEmitter).
|
|
15
|
+
// Lazy-load so the module *graph* loads off-Node; instantiating a concrete
|
|
16
|
+
// bridge there throws at construction. Notably the base does NOT require
|
|
17
|
+
// child_process — that belongs to the stdio subclass only.
|
|
18
|
+
const nodeCrypto = getNodeBuiltinSync("node:crypto");
|
|
19
|
+
const nodeEvents = getNodeBuiltinSync("node:events");
|
|
20
|
+
function notOnNode(api) {
|
|
21
|
+
throw new Error(`${api} requires Node — PythonBridgeBase is Node-only`);
|
|
22
|
+
}
|
|
23
|
+
const randomUUID = nodeCrypto?.randomUUID ?? (() => notOnNode("node:crypto.randomUUID"));
|
|
24
|
+
// Re-export the EventEmitter type/class — falls back to a no-op so the
|
|
25
|
+
// module evaluates off-Node; consumers that instantiate the bridge will
|
|
26
|
+
// fail at construction time, not at module load.
|
|
27
|
+
class FallbackEmitter {
|
|
28
|
+
on(_, __) {
|
|
29
|
+
notOnNode("node:events.EventEmitter");
|
|
30
|
+
}
|
|
31
|
+
emit(_, ...__) {
|
|
32
|
+
notOnNode("node:events.EventEmitter");
|
|
33
|
+
}
|
|
34
|
+
removeAllListeners() {
|
|
35
|
+
notOnNode("node:events.EventEmitter");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// SAFETY: off-Node the fallback stands in for `EventEmitter` and throws from
|
|
39
|
+
// every member. It cannot implement the full class (`once`, `off`,
|
|
40
|
+
// `listenerCount`, the static helpers), which `PythonBridge extends
|
|
41
|
+
// EventEmitter` puts in the package's public contract.
|
|
42
|
+
const EventEmitter = (nodeEvents?.EventEmitter ??
|
|
43
|
+
FallbackEmitter);
|
|
44
|
+
import { createLogger } from "@nodetool-ai/config";
|
|
45
|
+
import { BRIDGE_PROTOCOL_VERSION, MIN_BRIDGE_PROTOCOL_VERSION, MIN_NODETOOL_CORE_VERSION } from "@nodetool-ai/protocol/bridge-protocol";
|
|
46
|
+
import { validateBridgeFrame } from "@nodetool-ai/protocol";
|
|
47
|
+
import { isNumber } from "@nodetool-ai/protocol";
|
|
48
|
+
const log = createLogger("nodetool.runtime.python-bridge-base");
|
|
49
|
+
/**
|
|
50
|
+
* Inbound bridge-frame validation gate (task B3). Every frame
|
|
51
|
+
* `_handleMessage` dispatches is safe-parsed against its
|
|
52
|
+
* `@nodetool-ai/protocol` schema first when this returns true; a frame that
|
|
53
|
+
* fails gets a structured, non-fatal rejection (see
|
|
54
|
+
* {@link PythonBridgeBase._handleInvalidFrame}) instead of silently
|
|
55
|
+
* dispatching malformed data.
|
|
56
|
+
*
|
|
57
|
+
* Mirrors `shouldValidateOutboundWs` in
|
|
58
|
+
* `packages/websocket/src/websocket-client-session.ts`: set
|
|
59
|
+
* `NODETOOL_VALIDATE_BRIDGE_FRAMES=1`/`=0` to force on/off; unset, it
|
|
60
|
+
* defaults to on under `NODE_ENV=test`/Vitest and off otherwise, so a worker
|
|
61
|
+
* bug that emits a malformed frame fails the test that exercised it rather
|
|
62
|
+
* than risking a perf hit validating every frame in production before the
|
|
63
|
+
* mechanism has burned in.
|
|
64
|
+
*/
|
|
65
|
+
function shouldValidateBridgeFrames() {
|
|
66
|
+
const override = safeProcessEnv()["NODETOOL_VALIDATE_BRIDGE_FRAMES"]?.trim();
|
|
67
|
+
if (override === "1" || override === "true")
|
|
68
|
+
return true;
|
|
69
|
+
if (override === "0" || override === "false")
|
|
70
|
+
return false;
|
|
71
|
+
const env = safeProcessEnv();
|
|
72
|
+
return env["NODE_ENV"] === "test" || Boolean(env["VITEST"]);
|
|
73
|
+
}
|
|
74
|
+
import { comfyStatusInfoSchema, workerStatusSchema } from "./python-bridge-types.js";
|
|
75
|
+
const DEFAULT_EXECUTE_TIMEOUT_MS = Number(safeProcessEnv()["NODETOOL_PYTHON_EXECUTE_TIMEOUT_MS"] ?? 12 * 60 * 1000);
|
|
76
|
+
const DEFAULT_STATUS_TIMEOUT_MS = Number(safeProcessEnv()["NODETOOL_PYTHON_STATUS_TIMEOUT_MS"] ?? 30000);
|
|
77
|
+
const DEFAULT_DOWNLOAD_IDLE_TIMEOUT_MS = Number(safeProcessEnv()["NODETOOL_PYTHON_DOWNLOAD_IDLE_TIMEOUT_MS"] ?? 5 * 60 * 1000);
|
|
78
|
+
const MAX_RESULT_BLOB_BYTES = Number(safeProcessEnv()["NODETOOL_PYTHON_MAX_RESULT_BLOB_BYTES"] ??
|
|
79
|
+
2 * 1024 * 1024 * 1024);
|
|
80
|
+
/**
|
|
81
|
+
* Transport-agnostic Python bridge. Subclasses provide the transport via
|
|
82
|
+
* {@link _openTransport} and {@link _send}, plus {@link close}. The optional
|
|
83
|
+
* {@link _assertCanConnect} hook lets a transport refuse to connect (e.g. in
|
|
84
|
+
* production).
|
|
85
|
+
*/
|
|
86
|
+
export class PythonBridgeBase extends EventEmitter {
|
|
87
|
+
_nodeMetadata = [];
|
|
88
|
+
_loadErrors = [];
|
|
89
|
+
_workerStatus = null;
|
|
90
|
+
_pending = new Map();
|
|
91
|
+
_pendingStream = new Map();
|
|
92
|
+
/**
|
|
93
|
+
* `comfy.execute` event callbacks, keyed by request id. Separate from the
|
|
94
|
+
* pending maps because `comfy.event` frames are neither `progress` (wrong
|
|
95
|
+
* shape) nor terminal — they stream the ComfyUI lifecycle while the same
|
|
96
|
+
* request's terminal `result`/`error` settles via {@link _pendingStream}.
|
|
97
|
+
*/
|
|
98
|
+
_pendingComfyEvents = new Map();
|
|
99
|
+
/**
|
|
100
|
+
* `blender.execute` event callbacks, keyed by request id. Same shape as
|
|
101
|
+
* {@link _pendingComfyEvents}: `blender.event` frames stream progress while
|
|
102
|
+
* the same request's terminal `result`/`error` settles via
|
|
103
|
+
* {@link _pendingStream}.
|
|
104
|
+
*/
|
|
105
|
+
_pendingBlenderEvents = new Map();
|
|
106
|
+
_options;
|
|
107
|
+
_connected = false;
|
|
108
|
+
_connectPromise = null;
|
|
109
|
+
constructor(options = {}) {
|
|
110
|
+
super();
|
|
111
|
+
this._options = options;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Requests still awaiting a worker reply (plain, streaming, and Comfy event
|
|
115
|
+
* subscriptions). Exposed for leak accounting — a run that ended with
|
|
116
|
+
* pending requests left a promise nothing will ever settle.
|
|
117
|
+
*/
|
|
118
|
+
get pendingRequestCount() {
|
|
119
|
+
return (this._pending.size +
|
|
120
|
+
this._pendingStream.size +
|
|
121
|
+
this._pendingComfyEvents.size +
|
|
122
|
+
this._pendingBlenderEvents.size);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Optional guard invoked at the start of connect(). Throw to refuse.
|
|
126
|
+
* Default is a no-op.
|
|
127
|
+
*/
|
|
128
|
+
_assertCanConnect() { }
|
|
129
|
+
// ── Connection lifecycle ───────────────────────────────────────────
|
|
130
|
+
async connect() {
|
|
131
|
+
this._assertCanConnect();
|
|
132
|
+
await this._openTransport();
|
|
133
|
+
await this._discover();
|
|
134
|
+
try {
|
|
135
|
+
await this._getWorkerStatusWithTimeout();
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
log.warn("Failed to fetch initial Python worker status; load_errors will be unavailable until next status fetch", err);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
ensureConnected() {
|
|
142
|
+
if (this._connected)
|
|
143
|
+
return Promise.resolve();
|
|
144
|
+
if (!this._connectPromise) {
|
|
145
|
+
this._connectPromise = this.connect().then(() => {
|
|
146
|
+
this._connectPromise = null;
|
|
147
|
+
}, (err) => {
|
|
148
|
+
this._connectPromise = null;
|
|
149
|
+
throw err;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return this._connectPromise;
|
|
153
|
+
}
|
|
154
|
+
// ── Message dispatch ────────────────────────────────────────────────
|
|
155
|
+
_handleMessage(msg) {
|
|
156
|
+
const type = msg.type;
|
|
157
|
+
const requestId = msg.request_id;
|
|
158
|
+
if (shouldValidateBridgeFrames()) {
|
|
159
|
+
const validation = validateBridgeFrame(msg);
|
|
160
|
+
if (!validation.success) {
|
|
161
|
+
this._handleInvalidFrame(type, requestId, validation.error);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (type === "discover" && requestId) {
|
|
166
|
+
const pending = this._pending.get(requestId);
|
|
167
|
+
if (pending) {
|
|
168
|
+
const data = msg.data;
|
|
169
|
+
// Reject the discover promise only if the worker's protocol is below
|
|
170
|
+
// the HARD FLOOR (a real wire break). Workers at or above the floor
|
|
171
|
+
// but below BRIDGE_PROTOCOL_VERSION still connect — newer, additive
|
|
172
|
+
// features are gated per-capability (e.g. supportsModelManagement),
|
|
173
|
+
// so an older worker keeps running everything it understands. Workers
|
|
174
|
+
// that pre-date the protocol_version field are treated as version 1
|
|
175
|
+
// (the initial release) — same wire format, they just don't announce.
|
|
176
|
+
const workerVersion = isNumber(data.protocol_version)
|
|
177
|
+
? data.protocol_version
|
|
178
|
+
: 1;
|
|
179
|
+
if (workerVersion < MIN_BRIDGE_PROTOCOL_VERSION) {
|
|
180
|
+
this._pending.delete(requestId);
|
|
181
|
+
pending.reject(new Error(`The installed nodetool-core speaks bridge protocol v${workerVersion}, ` +
|
|
182
|
+
`but this Nodetool build requires at least v${MIN_BRIDGE_PROTOCOL_VERSION}. ` +
|
|
183
|
+
`Please reinstall the Python environment from Settings → Packages ` +
|
|
184
|
+
`(Reinstall environment) — it will fetch nodetool-core>=${MIN_NODETOOL_CORE_VERSION}.`));
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (workerVersion > BRIDGE_PROTOCOL_VERSION) {
|
|
188
|
+
// Forward-compat: a newer worker is expected to keep speaking
|
|
189
|
+
// older protocols, so we proceed with a warning.
|
|
190
|
+
this.emit("stderr", `[python-bridge] Worker protocol v${workerVersion} is newer than ` +
|
|
191
|
+
`JS runtime v${BRIDGE_PROTOCOL_VERSION}; assuming backward compatibility.\n`);
|
|
192
|
+
}
|
|
193
|
+
this._nodeMetadata = data.nodes;
|
|
194
|
+
this._loadErrors = data.load_errors ?? [];
|
|
195
|
+
pending.resolve({ outputs: {}, blobs: {} });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else if (type === "result" && requestId) {
|
|
199
|
+
const streamReq = this._pendingStream.get(requestId);
|
|
200
|
+
if (streamReq) {
|
|
201
|
+
this._pendingStream.delete(requestId);
|
|
202
|
+
streamReq.resolve(msg.data);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const pending = this._pending.get(requestId);
|
|
206
|
+
if (pending) {
|
|
207
|
+
this._pending.delete(requestId);
|
|
208
|
+
if (pending.blobTransfers?.size) {
|
|
209
|
+
pending.reject(new Error("Python worker ended the result before all blob transfers completed"));
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const data = msg.data;
|
|
213
|
+
pending.resolve({
|
|
214
|
+
outputs: data.outputs,
|
|
215
|
+
blobs: { ...(data.blobs ?? {}), ...(pending.completedBlobs ?? {}) }
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else if (type === "error" && requestId) {
|
|
220
|
+
const streamReq = this._pendingStream.get(requestId);
|
|
221
|
+
if (streamReq) {
|
|
222
|
+
this._pendingStream.delete(requestId);
|
|
223
|
+
const data = msg.data;
|
|
224
|
+
const err = new Error(data.error);
|
|
225
|
+
Reflect.set(err, "traceback", data.traceback);
|
|
226
|
+
streamReq.reject(err);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const pending = this._pending.get(requestId);
|
|
230
|
+
if (pending) {
|
|
231
|
+
this._pending.delete(requestId);
|
|
232
|
+
const data = msg.data;
|
|
233
|
+
const err = new Error(data.error);
|
|
234
|
+
Reflect.set(err, "traceback", data.traceback);
|
|
235
|
+
pending.reject(err);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
else if (type === "chunk" && requestId) {
|
|
239
|
+
const streamReq = this._pendingStream.get(requestId);
|
|
240
|
+
if (streamReq) {
|
|
241
|
+
streamReq.onChunk(msg.data);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
else if (type === "blob.start" && requestId) {
|
|
245
|
+
this._startBlobTransfer(requestId, msg.data);
|
|
246
|
+
}
|
|
247
|
+
else if (type === "blob.chunk" && requestId) {
|
|
248
|
+
this._appendBlobChunk(requestId, msg.data);
|
|
249
|
+
}
|
|
250
|
+
else if (type === "blob.end" && requestId) {
|
|
251
|
+
this._finishBlobTransfer(requestId, msg.data);
|
|
252
|
+
}
|
|
253
|
+
else if (type === "progress" && requestId) {
|
|
254
|
+
const pending = this._pending.get(requestId);
|
|
255
|
+
if (pending?.onProgress) {
|
|
256
|
+
const data = msg.data;
|
|
257
|
+
pending.onProgress({ request_id: requestId, ...data });
|
|
258
|
+
}
|
|
259
|
+
this.emit("progress", msg.data);
|
|
260
|
+
}
|
|
261
|
+
else if (type === "comfy.event" && requestId) {
|
|
262
|
+
// Dedicated `comfy.execute` lifecycle frame. Distinct from `progress`
|
|
263
|
+
// because ComfyUI's events don't fit `{progress,total,message}`. Without
|
|
264
|
+
// this explicit case the frames would fall through and vanish silently.
|
|
265
|
+
const onEvent = this._pendingComfyEvents.get(requestId);
|
|
266
|
+
if (onEvent) {
|
|
267
|
+
onEvent(msg.data);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
else if (type === "blender.event" && requestId) {
|
|
271
|
+
// Dedicated `blender.execute` progress frame, mirroring `comfy.event`:
|
|
272
|
+
// frame progress does not fit `{progress,total,message}` either, and
|
|
273
|
+
// without this case the frames fall through and vanish silently — which
|
|
274
|
+
// is also what an older build without this case does with them.
|
|
275
|
+
const onEvent = this._pendingBlenderEvents.get(requestId);
|
|
276
|
+
if (onEvent) {
|
|
277
|
+
onEvent(msg.data);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
_rejectBlobTransfer(requestId, message) {
|
|
282
|
+
const pending = this._pending.get(requestId);
|
|
283
|
+
if (!pending)
|
|
284
|
+
return;
|
|
285
|
+
this._pending.delete(requestId);
|
|
286
|
+
pending.reject(new Error(message));
|
|
287
|
+
try {
|
|
288
|
+
this.cancel(requestId);
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
// The worker may already have completed; cancellation is best-effort.
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
_startBlobTransfer(requestId, data) {
|
|
295
|
+
const pending = this._pending.get(requestId);
|
|
296
|
+
const name = data["name"];
|
|
297
|
+
const size = data["size"];
|
|
298
|
+
if (!pending || typeof name !== "string" || typeof size !== "number")
|
|
299
|
+
return;
|
|
300
|
+
if (!Number.isSafeInteger(size) ||
|
|
301
|
+
size < 0 ||
|
|
302
|
+
size > MAX_RESULT_BLOB_BYTES) {
|
|
303
|
+
this._rejectBlobTransfer(requestId, `Python worker declared invalid blob size ${String(size)} for "${name}"`);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
pending.blobTransfers ??= new Map();
|
|
307
|
+
pending.completedBlobs ??= Object.create(null);
|
|
308
|
+
if (pending.blobTransfers.has(name) ||
|
|
309
|
+
Object.hasOwn(pending.completedBlobs, name)) {
|
|
310
|
+
this._rejectBlobTransfer(requestId, `Python worker started duplicate blob "${name}"`);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
pending.blobTransfers.set(name, { size, received: 0, chunks: [] });
|
|
314
|
+
}
|
|
315
|
+
_appendBlobChunk(requestId, data) {
|
|
316
|
+
const pending = this._pending.get(requestId);
|
|
317
|
+
const name = data["name"];
|
|
318
|
+
const offset = data["offset"];
|
|
319
|
+
const bytes = data["bytes"];
|
|
320
|
+
if (!pending ||
|
|
321
|
+
typeof name !== "string" ||
|
|
322
|
+
typeof offset !== "number" ||
|
|
323
|
+
!(bytes instanceof Uint8Array))
|
|
324
|
+
return;
|
|
325
|
+
const transfer = pending.blobTransfers?.get(name);
|
|
326
|
+
if (!transfer ||
|
|
327
|
+
offset !== transfer.received ||
|
|
328
|
+
offset + bytes.length > transfer.size) {
|
|
329
|
+
this._rejectBlobTransfer(requestId, `Python worker sent an out-of-order or oversized chunk for blob "${name}"`);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
transfer.chunks.push(bytes);
|
|
333
|
+
transfer.received += bytes.length;
|
|
334
|
+
}
|
|
335
|
+
_finishBlobTransfer(requestId, data) {
|
|
336
|
+
const pending = this._pending.get(requestId);
|
|
337
|
+
const name = data["name"];
|
|
338
|
+
const size = data["size"];
|
|
339
|
+
const expectedDigest = data["sha256"];
|
|
340
|
+
if (!pending ||
|
|
341
|
+
typeof name !== "string" ||
|
|
342
|
+
typeof size !== "number" ||
|
|
343
|
+
typeof expectedDigest !== "string")
|
|
344
|
+
return;
|
|
345
|
+
const transfer = pending.blobTransfers?.get(name);
|
|
346
|
+
if (!transfer ||
|
|
347
|
+
size !== transfer.size ||
|
|
348
|
+
transfer.received !== transfer.size) {
|
|
349
|
+
this._rejectBlobTransfer(requestId, `Python worker truncated blob "${name}"`);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
const blob = new Uint8Array(transfer.size);
|
|
353
|
+
let offset = 0;
|
|
354
|
+
for (const chunk of transfer.chunks) {
|
|
355
|
+
blob.set(chunk, offset);
|
|
356
|
+
offset += chunk.length;
|
|
357
|
+
}
|
|
358
|
+
const digest = nodeCrypto?.createHash("sha256").update(blob).digest("hex");
|
|
359
|
+
if (!digest || digest !== expectedDigest) {
|
|
360
|
+
this._rejectBlobTransfer(requestId, `Python worker blob "${name}" failed SHA-256 verification`);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
pending.blobTransfers?.delete(name);
|
|
364
|
+
pending.completedBlobs ??= Object.create(null);
|
|
365
|
+
pending.completedBlobs[name] = blob;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* A frame decoded fine off the wire (valid msgpack) but failed its
|
|
369
|
+
* `@nodetool-ai/protocol` schema — a worker-side protocol bug, not a
|
|
370
|
+
* transport desync. Unlike an undecodable frame (bad length prefix,
|
|
371
|
+
* corrupt msgpack — see each transport's `_failProtocol`), this does NOT
|
|
372
|
+
* tear down the connection: only the request the malformed frame carries
|
|
373
|
+
* a `request_id` for is failed, so a concurrent request already in flight
|
|
374
|
+
* still settles normally.
|
|
375
|
+
*
|
|
376
|
+
* A frame with no (or non-string) `request_id` can't be attributed to any
|
|
377
|
+
* pending request — logged and dropped, matching the dispatcher's
|
|
378
|
+
* existing silent-ignore behavior for frame types/ids it doesn't
|
|
379
|
+
* recognize.
|
|
380
|
+
*/
|
|
381
|
+
_handleInvalidFrame(type, requestId, reason) {
|
|
382
|
+
log.warn(`Rejected malformed Python bridge frame (type=${type ?? "<unknown>"}, request_id=${requestId ?? "<none>"}): ${reason ?? "failed schema validation"}`);
|
|
383
|
+
if (!requestId)
|
|
384
|
+
return;
|
|
385
|
+
const err = new Error(`Received malformed '${type ?? "<unknown>"}' frame from Python worker: ${reason ?? "failed schema validation"}`);
|
|
386
|
+
const streamReq = this._pendingStream.get(requestId);
|
|
387
|
+
if (streamReq) {
|
|
388
|
+
this._pendingStream.delete(requestId);
|
|
389
|
+
this._pendingComfyEvents.delete(requestId);
|
|
390
|
+
this._pendingBlenderEvents.delete(requestId);
|
|
391
|
+
streamReq.reject(err);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
const pending = this._pending.get(requestId);
|
|
395
|
+
if (pending) {
|
|
396
|
+
this._pending.delete(requestId);
|
|
397
|
+
pending.reject(err);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
_rejectAllPending(error) {
|
|
401
|
+
for (const [, req] of this._pending) {
|
|
402
|
+
req.reject(error);
|
|
403
|
+
}
|
|
404
|
+
this._pending.clear();
|
|
405
|
+
for (const [, req] of this._pendingStream) {
|
|
406
|
+
req.reject(error);
|
|
407
|
+
}
|
|
408
|
+
this._pendingStream.clear();
|
|
409
|
+
this._pendingComfyEvents.clear();
|
|
410
|
+
this._pendingBlenderEvents.clear();
|
|
411
|
+
}
|
|
412
|
+
// ── Discover ───────────────────────────────────────────────────────
|
|
413
|
+
async _discover() {
|
|
414
|
+
const requestId = randomUUID();
|
|
415
|
+
// Bound the discover RPC: _openTransport resolves as soon as the worker
|
|
416
|
+
// signals readiness, so a worker that becomes READY but never answers
|
|
417
|
+
// discover (wedged/hung post-ready init) would leave connect() pending
|
|
418
|
+
// forever. Mirror _getWorkerStatusWithTimeout's timeout handling.
|
|
419
|
+
const timeoutMs = this._options.statusTimeoutMs ?? DEFAULT_STATUS_TIMEOUT_MS;
|
|
420
|
+
return new Promise((resolve, reject) => {
|
|
421
|
+
let timer;
|
|
422
|
+
this._pending.set(requestId, {
|
|
423
|
+
resolve: () => {
|
|
424
|
+
if (timer)
|
|
425
|
+
clearTimeout(timer);
|
|
426
|
+
this._pending.delete(requestId);
|
|
427
|
+
resolve();
|
|
428
|
+
},
|
|
429
|
+
reject: (err) => {
|
|
430
|
+
if (timer)
|
|
431
|
+
clearTimeout(timer);
|
|
432
|
+
this._pending.delete(requestId);
|
|
433
|
+
reject(err);
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
if (timeoutMs > 0) {
|
|
437
|
+
timer = setTimeout(() => {
|
|
438
|
+
this._pending.delete(requestId);
|
|
439
|
+
reject(new Error(`Python worker discover timed out after ${timeoutMs}ms.`));
|
|
440
|
+
}, timeoutMs);
|
|
441
|
+
}
|
|
442
|
+
try {
|
|
443
|
+
this._send({ type: "discover", request_id: requestId, data: {} });
|
|
444
|
+
}
|
|
445
|
+
catch (err) {
|
|
446
|
+
if (timer)
|
|
447
|
+
clearTimeout(timer);
|
|
448
|
+
this._pending.delete(requestId);
|
|
449
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
// ── Node execution ─────────────────────────────────────────────────
|
|
454
|
+
/**
|
|
455
|
+
* Snake-case the run identity onto the `execute` / `execute.stream` payload,
|
|
456
|
+
* dropping fields the caller could not name so the worker sees an absent key
|
|
457
|
+
* rather than a null it has to special-case.
|
|
458
|
+
*
|
|
459
|
+
* Sent unconditionally, not gated on {@link supportsJobLifecycle}: these are
|
|
460
|
+
* extra dict entries, and a pre-v4 worker that reads `data["node_type"]`,
|
|
461
|
+
* `["fields"]`, `["secrets"]` and `["blobs"]` never looks at them. Gating
|
|
462
|
+
* them would only mean a worker that DOES understand them gets nothing
|
|
463
|
+
* whenever its `worker.status` hasn't landed yet.
|
|
464
|
+
*/
|
|
465
|
+
_identityPayload(identity) {
|
|
466
|
+
if (!identity)
|
|
467
|
+
return {};
|
|
468
|
+
const payload = {};
|
|
469
|
+
if (identity.nodeId)
|
|
470
|
+
payload.node_id = identity.nodeId;
|
|
471
|
+
if (identity.jobId)
|
|
472
|
+
payload.job_id = identity.jobId;
|
|
473
|
+
if (identity.workflowId)
|
|
474
|
+
payload.workflow_id = identity.workflowId;
|
|
475
|
+
if (identity.userId)
|
|
476
|
+
payload.user_id = identity.userId;
|
|
477
|
+
if (identity.requiresVramGb != null) {
|
|
478
|
+
payload.requires_vram_gb = identity.requiresVramGb;
|
|
479
|
+
}
|
|
480
|
+
return payload;
|
|
481
|
+
}
|
|
482
|
+
async execute(nodeType, fields, secrets, blobs, onProgress, identity) {
|
|
483
|
+
const requestId = randomUUID();
|
|
484
|
+
const timeoutMs = this._options.executeTimeoutMs ?? DEFAULT_EXECUTE_TIMEOUT_MS;
|
|
485
|
+
log.debug("Python bridge execute dispatched", { nodeType, requestId });
|
|
486
|
+
const executeData = {
|
|
487
|
+
node_type: nodeType,
|
|
488
|
+
fields,
|
|
489
|
+
secrets,
|
|
490
|
+
blobs,
|
|
491
|
+
...this._identityPayload(identity)
|
|
492
|
+
};
|
|
493
|
+
if (this._workerStatus?.protocol_version != null &&
|
|
494
|
+
this._workerStatus.protocol_version >= 5) {
|
|
495
|
+
executeData["blob_transfer"] = "chunked-v1";
|
|
496
|
+
}
|
|
497
|
+
const executePromise = new Promise((resolve, reject) => {
|
|
498
|
+
this._pending.set(requestId, { resolve, reject, onProgress });
|
|
499
|
+
try {
|
|
500
|
+
this._send({
|
|
501
|
+
type: "execute",
|
|
502
|
+
request_id: requestId,
|
|
503
|
+
data: executeData
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
catch (err) {
|
|
507
|
+
this._pending.delete(requestId);
|
|
508
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
if (timeoutMs <= 0) {
|
|
512
|
+
return executePromise;
|
|
513
|
+
}
|
|
514
|
+
let timer;
|
|
515
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
516
|
+
timer = setTimeout(() => {
|
|
517
|
+
if (!this._pending.has(requestId)) {
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
this._pending.delete(requestId);
|
|
521
|
+
try {
|
|
522
|
+
this.cancel(requestId);
|
|
523
|
+
}
|
|
524
|
+
catch {
|
|
525
|
+
// Worker may already be gone; cancel is best-effort.
|
|
526
|
+
}
|
|
527
|
+
const stderrHint = this.getRecentStderrSummary(4);
|
|
528
|
+
reject(new Error(`Python node "${nodeType}" timed out after ${timeoutMs}ms waiting for the worker.` +
|
|
529
|
+
(stderrHint ? ` Recent stderr: ${stderrHint}` : "")));
|
|
530
|
+
}, timeoutMs);
|
|
531
|
+
});
|
|
532
|
+
try {
|
|
533
|
+
return await Promise.race([executePromise, timeoutPromise]);
|
|
534
|
+
}
|
|
535
|
+
finally {
|
|
536
|
+
if (timer) {
|
|
537
|
+
clearTimeout(timer);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
async *executeStream(nodeType, fields, secrets, blobs, onProgress, identity) {
|
|
542
|
+
const requestId = randomUUID();
|
|
543
|
+
const chunks = [];
|
|
544
|
+
let done = false;
|
|
545
|
+
let error = null;
|
|
546
|
+
let finalResult = null;
|
|
547
|
+
let emittedCount = 0;
|
|
548
|
+
let resolveWait = null;
|
|
549
|
+
if (onProgress) {
|
|
550
|
+
this._pending.set(requestId, {
|
|
551
|
+
resolve: () => undefined,
|
|
552
|
+
reject: () => undefined,
|
|
553
|
+
onProgress
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
const onChunk = (chunk) => {
|
|
557
|
+
chunks.push({
|
|
558
|
+
outputs: chunk.outputs ?? {},
|
|
559
|
+
blobs: chunk.blobs ?? {}
|
|
560
|
+
});
|
|
561
|
+
if (resolveWait) {
|
|
562
|
+
resolveWait();
|
|
563
|
+
resolveWait = null;
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
const streamPromise = new Promise((resolve, reject) => {
|
|
567
|
+
this._pendingStream.set(requestId, { resolve, reject, onChunk });
|
|
568
|
+
});
|
|
569
|
+
streamPromise
|
|
570
|
+
.then((result) => {
|
|
571
|
+
finalResult = {
|
|
572
|
+
outputs: result.outputs ?? {},
|
|
573
|
+
blobs: result.blobs ?? {}
|
|
574
|
+
};
|
|
575
|
+
done = true;
|
|
576
|
+
this._pending.delete(requestId);
|
|
577
|
+
if (resolveWait) {
|
|
578
|
+
resolveWait();
|
|
579
|
+
resolveWait = null;
|
|
580
|
+
}
|
|
581
|
+
})
|
|
582
|
+
.catch((err) => {
|
|
583
|
+
error = err;
|
|
584
|
+
done = true;
|
|
585
|
+
this._pending.delete(requestId);
|
|
586
|
+
if (resolveWait) {
|
|
587
|
+
resolveWait();
|
|
588
|
+
resolveWait = null;
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
try {
|
|
592
|
+
this._send({
|
|
593
|
+
type: "execute.stream",
|
|
594
|
+
request_id: requestId,
|
|
595
|
+
data: {
|
|
596
|
+
node_type: nodeType,
|
|
597
|
+
fields,
|
|
598
|
+
secrets,
|
|
599
|
+
blobs,
|
|
600
|
+
...this._identityPayload(identity)
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
while (true) {
|
|
604
|
+
while (chunks.length > 0) {
|
|
605
|
+
emittedCount += 1;
|
|
606
|
+
yield chunks.shift();
|
|
607
|
+
}
|
|
608
|
+
if (done)
|
|
609
|
+
break;
|
|
610
|
+
if (error)
|
|
611
|
+
throw error;
|
|
612
|
+
await new Promise((resolve) => {
|
|
613
|
+
resolveWait = resolve;
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
if (error)
|
|
617
|
+
throw error;
|
|
618
|
+
if (emittedCount === 0 && finalResult) {
|
|
619
|
+
yield finalResult;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
finally {
|
|
623
|
+
// If the stream never reached its terminal frame (consumer abandoned the
|
|
624
|
+
// generator via break/return, or the initial _send threw), the worker is
|
|
625
|
+
// still producing output nobody reads. Cancel it and release pending
|
|
626
|
+
// state so chunks[] stops growing and the entries don't leak.
|
|
627
|
+
if (!done) {
|
|
628
|
+
this._pending.delete(requestId);
|
|
629
|
+
this._pendingStream.delete(requestId);
|
|
630
|
+
try {
|
|
631
|
+
this.cancel(requestId);
|
|
632
|
+
}
|
|
633
|
+
catch {
|
|
634
|
+
// Worker may already be gone; cancel is best-effort.
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
cancel(requestId) {
|
|
640
|
+
this._send({ type: "cancel", request_id: requestId, data: {} });
|
|
641
|
+
}
|
|
642
|
+
getNodeMetadata() {
|
|
643
|
+
return this._nodeMetadata;
|
|
644
|
+
}
|
|
645
|
+
getLoadErrors() {
|
|
646
|
+
return this._loadErrors;
|
|
647
|
+
}
|
|
648
|
+
async getWorkerStatus() {
|
|
649
|
+
const requestId = randomUUID();
|
|
650
|
+
const result = await new Promise((resolve, reject) => {
|
|
651
|
+
this._pendingStream.set(requestId, {
|
|
652
|
+
resolve,
|
|
653
|
+
reject,
|
|
654
|
+
onChunk: () => { }
|
|
655
|
+
});
|
|
656
|
+
try {
|
|
657
|
+
this._send({
|
|
658
|
+
type: "worker.status",
|
|
659
|
+
request_id: requestId,
|
|
660
|
+
data: {}
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
catch (err) {
|
|
664
|
+
this._pendingStream.delete(requestId);
|
|
665
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
this._workerStatus = workerStatusSchema.parse(result);
|
|
669
|
+
// A status reply that carries no `load_errors` leaves the ones `discover`
|
|
670
|
+
// reported in place; only a reply that names the field replaces them.
|
|
671
|
+
this._loadErrors =
|
|
672
|
+
result["load_errors"] == null
|
|
673
|
+
? this._loadErrors
|
|
674
|
+
: this._workerStatus.load_errors;
|
|
675
|
+
return this._workerStatus;
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* getWorkerStatus() guarded by a timeout so a silent worker cannot hang
|
|
679
|
+
* connect() forever. On timeout we reject this single call and clean up its
|
|
680
|
+
* pending entry + timer; connect()'s catch then logs and proceeds.
|
|
681
|
+
*
|
|
682
|
+
* Protected so transports that override connect() (e.g. the WebSocket
|
|
683
|
+
* bridge, which wraps the whole RPC phase in its own timeout) can still
|
|
684
|
+
* honor statusTimeoutMs for the status sub-call.
|
|
685
|
+
*/
|
|
686
|
+
async _getWorkerStatusWithTimeout() {
|
|
687
|
+
const timeoutMs = this._options.statusTimeoutMs ?? DEFAULT_STATUS_TIMEOUT_MS;
|
|
688
|
+
if (timeoutMs <= 0) {
|
|
689
|
+
return this.getWorkerStatus();
|
|
690
|
+
}
|
|
691
|
+
const requestId = randomUUID();
|
|
692
|
+
let timer;
|
|
693
|
+
const statusPromise = new Promise((resolve, reject) => {
|
|
694
|
+
this._pendingStream.set(requestId, {
|
|
695
|
+
resolve,
|
|
696
|
+
reject,
|
|
697
|
+
onChunk: () => { }
|
|
698
|
+
});
|
|
699
|
+
try {
|
|
700
|
+
this._send({
|
|
701
|
+
type: "worker.status",
|
|
702
|
+
request_id: requestId,
|
|
703
|
+
data: {}
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
catch (err) {
|
|
707
|
+
this._pendingStream.delete(requestId);
|
|
708
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
712
|
+
timer = setTimeout(() => {
|
|
713
|
+
// Reject only this single call and drop its pending entry so a late
|
|
714
|
+
// response is ignored rather than resolving a dead promise.
|
|
715
|
+
this._pendingStream.delete(requestId);
|
|
716
|
+
reject(new Error(`Python worker status timed out after ${timeoutMs}ms.`));
|
|
717
|
+
}, timeoutMs);
|
|
718
|
+
});
|
|
719
|
+
try {
|
|
720
|
+
const result = await Promise.race([statusPromise, timeoutPromise]);
|
|
721
|
+
this._workerStatus = workerStatusSchema.parse(result);
|
|
722
|
+
// See getWorkerStatus: absence keeps the discover-reported errors.
|
|
723
|
+
this._loadErrors =
|
|
724
|
+
result["load_errors"] == null
|
|
725
|
+
? this._loadErrors
|
|
726
|
+
: this._workerStatus.load_errors;
|
|
727
|
+
return this._workerStatus;
|
|
728
|
+
}
|
|
729
|
+
finally {
|
|
730
|
+
if (timer) {
|
|
731
|
+
clearTimeout(timer);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
hasNodeType(nodeType) {
|
|
736
|
+
return this._nodeMetadata.some((n) => n.node_type === nodeType);
|
|
737
|
+
}
|
|
738
|
+
get isConnected() {
|
|
739
|
+
return this._connected;
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Whether this bridge has a worker it can attempt to connect to. Gates
|
|
743
|
+
* boot-time auto-connect — the server only eagerly calls ensureConnected()
|
|
744
|
+
* when this returns true. The base default is true (a WebSocket bridge always
|
|
745
|
+
* has a configured URL); the stdio subclass overrides it to report whether a
|
|
746
|
+
* local Python interpreter was found.
|
|
747
|
+
*/
|
|
748
|
+
isAvailable() {
|
|
749
|
+
return true;
|
|
750
|
+
}
|
|
751
|
+
// ── Provider bridge methods ────────────────────────────────────────
|
|
752
|
+
async listProviders() {
|
|
753
|
+
const result = await this._providerCall("provider.list", {});
|
|
754
|
+
return result.providers;
|
|
755
|
+
}
|
|
756
|
+
async getProviderModels(providerId, modelType, secrets) {
|
|
757
|
+
const result = await this._providerCall("provider.models", {
|
|
758
|
+
provider: providerId,
|
|
759
|
+
model_type: modelType,
|
|
760
|
+
secrets: secrets ?? {}
|
|
761
|
+
});
|
|
762
|
+
return result.models;
|
|
763
|
+
}
|
|
764
|
+
async providerGenerate(providerId, messages, model, options) {
|
|
765
|
+
const result = await this._providerCall("provider.generate", {
|
|
766
|
+
provider: providerId,
|
|
767
|
+
messages,
|
|
768
|
+
model,
|
|
769
|
+
...options
|
|
770
|
+
});
|
|
771
|
+
return result.message;
|
|
772
|
+
}
|
|
773
|
+
async *providerStream(providerId, messages, model, options) {
|
|
774
|
+
const requestId = randomUUID();
|
|
775
|
+
const chunks = [];
|
|
776
|
+
let done = false;
|
|
777
|
+
let error = null;
|
|
778
|
+
let resolveWait = null;
|
|
779
|
+
const onChunk = (chunk) => {
|
|
780
|
+
chunks.push(chunk);
|
|
781
|
+
if (resolveWait) {
|
|
782
|
+
resolveWait();
|
|
783
|
+
resolveWait = null;
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
const streamPromise = new Promise((resolve, reject) => {
|
|
787
|
+
this._pendingStream.set(requestId, { resolve, reject, onChunk });
|
|
788
|
+
});
|
|
789
|
+
streamPromise
|
|
790
|
+
.then(() => {
|
|
791
|
+
done = true;
|
|
792
|
+
if (resolveWait) {
|
|
793
|
+
resolveWait();
|
|
794
|
+
resolveWait = null;
|
|
795
|
+
}
|
|
796
|
+
})
|
|
797
|
+
.catch((err) => {
|
|
798
|
+
error = err;
|
|
799
|
+
done = true;
|
|
800
|
+
if (resolveWait) {
|
|
801
|
+
resolveWait();
|
|
802
|
+
resolveWait = null;
|
|
803
|
+
}
|
|
804
|
+
});
|
|
805
|
+
try {
|
|
806
|
+
this._send({
|
|
807
|
+
type: "provider.stream",
|
|
808
|
+
request_id: requestId,
|
|
809
|
+
data: { provider: providerId, messages, model, ...options }
|
|
810
|
+
});
|
|
811
|
+
while (true) {
|
|
812
|
+
while (chunks.length > 0)
|
|
813
|
+
yield chunks.shift();
|
|
814
|
+
if (done)
|
|
815
|
+
break;
|
|
816
|
+
if (error)
|
|
817
|
+
throw error;
|
|
818
|
+
await new Promise((resolve) => {
|
|
819
|
+
resolveWait = resolve;
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
if (error)
|
|
823
|
+
throw error;
|
|
824
|
+
}
|
|
825
|
+
finally {
|
|
826
|
+
// Cancel the worker-side stream and drop pending state if the generator
|
|
827
|
+
// is torn down before its terminal frame (consumer break/return, or a
|
|
828
|
+
// throwing initial _send).
|
|
829
|
+
if (!done) {
|
|
830
|
+
this._pendingStream.delete(requestId);
|
|
831
|
+
try {
|
|
832
|
+
this.cancel(requestId);
|
|
833
|
+
}
|
|
834
|
+
catch {
|
|
835
|
+
// Worker may already be gone; cancel is best-effort.
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
async providerTextToImage(providerId, params, secrets, signal) {
|
|
841
|
+
const result = await this._providerBlobCall("provider.text_to_image", {
|
|
842
|
+
provider: providerId,
|
|
843
|
+
params,
|
|
844
|
+
secrets: secrets ?? {}
|
|
845
|
+
}, signal);
|
|
846
|
+
return result.blobs.image;
|
|
847
|
+
}
|
|
848
|
+
async providerImageToImage(providerId, image, params, secrets, signal) {
|
|
849
|
+
const result = await this._providerBlobCall("provider.image_to_image", {
|
|
850
|
+
provider: providerId,
|
|
851
|
+
image,
|
|
852
|
+
params,
|
|
853
|
+
secrets: secrets ?? {}
|
|
854
|
+
}, signal);
|
|
855
|
+
return result.blobs.image;
|
|
856
|
+
}
|
|
857
|
+
async providerTextToVideo(providerId, params, secrets, signal) {
|
|
858
|
+
const result = await this._providerBlobCall("provider.text_to_video", {
|
|
859
|
+
provider: providerId,
|
|
860
|
+
params,
|
|
861
|
+
secrets: secrets ?? {}
|
|
862
|
+
}, signal);
|
|
863
|
+
return result.blobs.video;
|
|
864
|
+
}
|
|
865
|
+
async providerImageToVideo(providerId, image, params, secrets, signal) {
|
|
866
|
+
const result = await this._providerBlobCall("provider.image_to_video", {
|
|
867
|
+
provider: providerId,
|
|
868
|
+
image,
|
|
869
|
+
params,
|
|
870
|
+
secrets: secrets ?? {}
|
|
871
|
+
}, signal);
|
|
872
|
+
return result.blobs.video;
|
|
873
|
+
}
|
|
874
|
+
async providerReferenceToVideo(providerId, inputs, params, secrets, signal) {
|
|
875
|
+
const { images, videos } = inputs;
|
|
876
|
+
if (!Array.isArray(images) ||
|
|
877
|
+
!Array.isArray(videos) ||
|
|
878
|
+
(images.length === 0 && videos.length === 0)) {
|
|
879
|
+
throw new Error("reference_to_video requires at least one reference image or video");
|
|
880
|
+
}
|
|
881
|
+
if ([...images, ...videos].some((bytes) => !(bytes instanceof Uint8Array))) {
|
|
882
|
+
throw new Error("reference_to_video reference media must be binary buffers");
|
|
883
|
+
}
|
|
884
|
+
if ([...images, ...videos].some((bytes) => bytes.byteLength === 0)) {
|
|
885
|
+
throw new Error("reference_to_video reference media must be non-empty");
|
|
886
|
+
}
|
|
887
|
+
const totalBytes = [...images, ...videos].reduce((total, bytes) => total + bytes.byteLength, 0);
|
|
888
|
+
if (totalBytes > 192 * 1024 * 1024) {
|
|
889
|
+
throw new Error(`reference_to_video reference media is ${totalBytes} bytes, exceeding the 201326592 byte limit`);
|
|
890
|
+
}
|
|
891
|
+
const result = await this._providerBlobCall("provider.reference_to_video", {
|
|
892
|
+
provider: providerId,
|
|
893
|
+
reference_images: images,
|
|
894
|
+
reference_videos: videos,
|
|
895
|
+
params,
|
|
896
|
+
secrets: secrets ?? {}
|
|
897
|
+
}, signal);
|
|
898
|
+
return result.blobs.video;
|
|
899
|
+
}
|
|
900
|
+
async providerTextToAudio(providerId, params, secrets) {
|
|
901
|
+
const result = await this._providerBlobCall("provider.text_to_audio", {
|
|
902
|
+
provider: providerId,
|
|
903
|
+
params,
|
|
904
|
+
secrets: secrets ?? {}
|
|
905
|
+
});
|
|
906
|
+
return result.blobs.audio;
|
|
907
|
+
}
|
|
908
|
+
async providerTTSEncoded(providerId, params, secrets) {
|
|
909
|
+
const result = await this._providerBlobCall("provider.tts_encoded", {
|
|
910
|
+
provider: providerId,
|
|
911
|
+
params,
|
|
912
|
+
secrets: secrets ?? {}
|
|
913
|
+
});
|
|
914
|
+
return result.blobs.audio;
|
|
915
|
+
}
|
|
916
|
+
async *providerTTS(providerId, text, model, options) {
|
|
917
|
+
const requestId = randomUUID();
|
|
918
|
+
const chunks = [];
|
|
919
|
+
let done = false;
|
|
920
|
+
let error = null;
|
|
921
|
+
let resolveWait = null;
|
|
922
|
+
const onChunk = (chunk) => {
|
|
923
|
+
const blobs = chunk.blobs;
|
|
924
|
+
if (blobs?.audio)
|
|
925
|
+
chunks.push(blobs.audio);
|
|
926
|
+
if (resolveWait) {
|
|
927
|
+
resolveWait();
|
|
928
|
+
resolveWait = null;
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
const streamPromise = new Promise((resolve, reject) => {
|
|
932
|
+
this._pendingStream.set(requestId, { resolve, reject, onChunk });
|
|
933
|
+
});
|
|
934
|
+
streamPromise
|
|
935
|
+
.then(() => {
|
|
936
|
+
done = true;
|
|
937
|
+
if (resolveWait) {
|
|
938
|
+
resolveWait();
|
|
939
|
+
resolveWait = null;
|
|
940
|
+
}
|
|
941
|
+
})
|
|
942
|
+
.catch((err) => {
|
|
943
|
+
error = err;
|
|
944
|
+
done = true;
|
|
945
|
+
if (resolveWait) {
|
|
946
|
+
resolveWait();
|
|
947
|
+
resolveWait = null;
|
|
948
|
+
}
|
|
949
|
+
});
|
|
950
|
+
try {
|
|
951
|
+
this._send({
|
|
952
|
+
type: "provider.tts",
|
|
953
|
+
request_id: requestId,
|
|
954
|
+
data: { provider: providerId, text, model, ...options }
|
|
955
|
+
});
|
|
956
|
+
while (true) {
|
|
957
|
+
while (chunks.length > 0)
|
|
958
|
+
yield chunks.shift();
|
|
959
|
+
if (done)
|
|
960
|
+
break;
|
|
961
|
+
if (error)
|
|
962
|
+
throw error;
|
|
963
|
+
await new Promise((resolve) => {
|
|
964
|
+
resolveWait = resolve;
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
if (error)
|
|
968
|
+
throw error;
|
|
969
|
+
}
|
|
970
|
+
finally {
|
|
971
|
+
// Cancel the worker-side stream and drop pending state if the generator
|
|
972
|
+
// is torn down before its terminal frame (consumer break/return, or a
|
|
973
|
+
// throwing initial _send).
|
|
974
|
+
if (!done) {
|
|
975
|
+
this._pendingStream.delete(requestId);
|
|
976
|
+
try {
|
|
977
|
+
this.cancel(requestId);
|
|
978
|
+
}
|
|
979
|
+
catch {
|
|
980
|
+
// Worker may already be gone; cancel is best-effort.
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
async providerASR(providerId, audio, model, options) {
|
|
986
|
+
const result = await this._providerCall("provider.asr", {
|
|
987
|
+
provider: providerId,
|
|
988
|
+
audio,
|
|
989
|
+
model,
|
|
990
|
+
...options
|
|
991
|
+
});
|
|
992
|
+
const r = result;
|
|
993
|
+
return {
|
|
994
|
+
text: r.text,
|
|
995
|
+
chunks: r.chunks
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
async providerEmbedding(providerId, text, model, dimensions) {
|
|
999
|
+
const result = await this._providerCall("provider.embedding", {
|
|
1000
|
+
provider: providerId,
|
|
1001
|
+
text,
|
|
1002
|
+
model,
|
|
1003
|
+
dimensions
|
|
1004
|
+
});
|
|
1005
|
+
return result.embeddings;
|
|
1006
|
+
}
|
|
1007
|
+
// ── Worker model management (HuggingFace cache) ───────────────────────
|
|
1008
|
+
/**
|
|
1009
|
+
* List the models cached on the worker's HF_HOME. Cache-only (no network):
|
|
1010
|
+
* each entry is a UnifiedModel JSON with `downloaded` forced true. Requires a
|
|
1011
|
+
* worker that speaks bridge protocol v2 ({@link supportsModelManagement}).
|
|
1012
|
+
*/
|
|
1013
|
+
async listCachedModels() {
|
|
1014
|
+
const result = await this._providerCall("models.list_cached", {});
|
|
1015
|
+
return result.models;
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Download a model onto the worker's persistent cache, streaming progress.
|
|
1019
|
+
*
|
|
1020
|
+
* The worker emits ordered `progress` frames (start → 0+ progress →
|
|
1021
|
+
* completed) followed by a terminal `result`. Each `progress` frame's `data`
|
|
1022
|
+
* is forwarded to {@link onProgress} verbatim; the promise resolves on the
|
|
1023
|
+
* terminal `result` and rejects on an `error` frame.
|
|
1024
|
+
*
|
|
1025
|
+
* The request is registered in BOTH pending maps: `_pending` (with
|
|
1026
|
+
* onProgress) so {@link _handleMessage} routes `progress` frames to the
|
|
1027
|
+
* callback, and `_pendingStream` for the terminal `result`/`error`. Both are
|
|
1028
|
+
* cleaned up on settle.
|
|
1029
|
+
*
|
|
1030
|
+
* Pass a stable `requestId` to make the download cancellable by a known key:
|
|
1031
|
+
* {@link cancelModelDownload}(requestId) then reaches this exact download.
|
|
1032
|
+
* Defaults to a random id when the caller has no need to cancel.
|
|
1033
|
+
*
|
|
1034
|
+
* Unlike a plain provider RPC, this download is settled defensively: an
|
|
1035
|
+
* inactivity timer (reset on every progress frame) rejects the promise if the
|
|
1036
|
+
* worker hangs mid-download, and {@link cancelModelDownload} rejects it
|
|
1037
|
+
* immediately. Either path clears BOTH pending maps so nothing leaks even when
|
|
1038
|
+
* no terminal `result`/`error` ever arrives.
|
|
1039
|
+
*/
|
|
1040
|
+
async downloadModel(req, onProgress, requestId = randomUUID()) {
|
|
1041
|
+
// A blank token is not a credential: `Authorization: Bearer ` fails
|
|
1042
|
+
// differently than sending no header at all, and worse for a public repo.
|
|
1043
|
+
// Drop it here so every caller gets the rule, matching the worker's own
|
|
1044
|
+
// handling (nodetool-core#1008).
|
|
1045
|
+
const { token, ...rest } = req;
|
|
1046
|
+
const payload = typeof token === "string" && token.trim()
|
|
1047
|
+
? { ...rest, token: token.trim() }
|
|
1048
|
+
: rest;
|
|
1049
|
+
return this._streamingDownload("models.download", payload,
|
|
1050
|
+
// SAFETY: the worker's `models.download` progress frame carries exactly
|
|
1051
|
+
// these fields; unlike the comfy update shapes, `ModelDownloadUpdate`
|
|
1052
|
+
// declares no index signature, so the frame record does not overlap it.
|
|
1053
|
+
(u) => onProgress(u), requestId);
|
|
1054
|
+
}
|
|
1055
|
+
/** Prepare an image-owned model through an advertised worker backend. */
|
|
1056
|
+
async prepareModel(req, onProgress, requestId = randomUUID()) {
|
|
1057
|
+
const { token, ...rest } = req;
|
|
1058
|
+
const payload = typeof token === "string" && token.trim()
|
|
1059
|
+
? { ...rest, token: token.trim() }
|
|
1060
|
+
: rest;
|
|
1061
|
+
return this._streamingDownload("models.prepare", payload, (update) => onProgress(update), requestId);
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Shared engine for the streaming-download RPCs (`models.download`,
|
|
1065
|
+
* `comfy.models.download`): a request that emits ordered `progress` frames
|
|
1066
|
+
* then a terminal `result`/`error`. Registers in BOTH pending maps and settles
|
|
1067
|
+
* defensively — an inactivity watchdog (reset on every progress frame) and
|
|
1068
|
+
* {@link cancel} both clear both maps so nothing leaks even if no terminal
|
|
1069
|
+
* frame ever arrives. See {@link downloadModel} for the full rationale.
|
|
1070
|
+
*/
|
|
1071
|
+
_streamingDownload(type, data, onProgress, requestId) {
|
|
1072
|
+
const idleTimeoutMs = this._options.downloadIdleTimeoutMs ?? DEFAULT_DOWNLOAD_IDLE_TIMEOUT_MS;
|
|
1073
|
+
return new Promise((resolve, reject) => {
|
|
1074
|
+
let settled = false;
|
|
1075
|
+
let timer;
|
|
1076
|
+
const settle = (fn) => {
|
|
1077
|
+
if (settled)
|
|
1078
|
+
return;
|
|
1079
|
+
settled = true;
|
|
1080
|
+
if (timer)
|
|
1081
|
+
clearTimeout(timer);
|
|
1082
|
+
this._pending.delete(requestId);
|
|
1083
|
+
this._pendingStream.delete(requestId);
|
|
1084
|
+
fn();
|
|
1085
|
+
};
|
|
1086
|
+
// Inactivity watchdog: a download making steady progress keeps resetting
|
|
1087
|
+
// the clock, but a worker that goes silent mid-download is cancelled and
|
|
1088
|
+
// rejected instead of leaking its pending entries forever.
|
|
1089
|
+
const armIdleTimer = () => {
|
|
1090
|
+
if (idleTimeoutMs <= 0)
|
|
1091
|
+
return;
|
|
1092
|
+
if (timer)
|
|
1093
|
+
clearTimeout(timer);
|
|
1094
|
+
timer = setTimeout(() => {
|
|
1095
|
+
try {
|
|
1096
|
+
this.cancel(requestId);
|
|
1097
|
+
}
|
|
1098
|
+
catch {
|
|
1099
|
+
// Worker may already be gone; cancel is best-effort.
|
|
1100
|
+
}
|
|
1101
|
+
const stderrHint = this.getRecentStderrSummary(4);
|
|
1102
|
+
settle(() => reject(new Error(`Download "${requestId}" stalled: no progress for ${idleTimeoutMs}ms.` +
|
|
1103
|
+
(stderrHint ? ` Recent stderr: ${stderrHint}` : ""))));
|
|
1104
|
+
}, idleTimeoutMs);
|
|
1105
|
+
};
|
|
1106
|
+
this._pending.set(requestId, {
|
|
1107
|
+
resolve: () => undefined,
|
|
1108
|
+
reject: () => undefined,
|
|
1109
|
+
onProgress: (event) => {
|
|
1110
|
+
armIdleTimer();
|
|
1111
|
+
onProgress({ ...event });
|
|
1112
|
+
}
|
|
1113
|
+
});
|
|
1114
|
+
this._pendingStream.set(requestId, {
|
|
1115
|
+
resolve: () => settle(resolve),
|
|
1116
|
+
reject: (err) => settle(() => reject(err)),
|
|
1117
|
+
onChunk: () => { }
|
|
1118
|
+
});
|
|
1119
|
+
armIdleTimer();
|
|
1120
|
+
try {
|
|
1121
|
+
this._send({ type, request_id: requestId, data });
|
|
1122
|
+
}
|
|
1123
|
+
catch (err) {
|
|
1124
|
+
settle(() => reject(err instanceof Error ? err : new Error(String(err))));
|
|
1125
|
+
}
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
/**
|
|
1129
|
+
* Cancel an in-flight {@link downloadModel} by its request id. Sends the
|
|
1130
|
+
* cancel frame AND settles the local promise immediately by rejecting it —
|
|
1131
|
+
* the worker may never emit a terminal frame after a cancel (or may be hung),
|
|
1132
|
+
* so we cannot rely on `result`/`error` to clean up. Rejecting through the
|
|
1133
|
+
* pending-stream entry clears both pending maps; a no-op if the id is unknown.
|
|
1134
|
+
*/
|
|
1135
|
+
cancelModelDownload(requestId) {
|
|
1136
|
+
// Best-effort per the JSDoc: cancel() -> _send() throws when the transport
|
|
1137
|
+
// is down, but this must stay a no-op so a disconnected caller isn't hit
|
|
1138
|
+
// with 'Not connected' from a documented safe cancel.
|
|
1139
|
+
try {
|
|
1140
|
+
this.cancel(requestId);
|
|
1141
|
+
}
|
|
1142
|
+
catch {
|
|
1143
|
+
// Worker may already be gone; cancel is best-effort.
|
|
1144
|
+
}
|
|
1145
|
+
const streamReq = this._pendingStream.get(requestId);
|
|
1146
|
+
if (streamReq) {
|
|
1147
|
+
streamReq.reject(new Error(`Model download "${requestId}" was cancelled.`));
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
/** Delete a cached model from the worker's HF_HOME. Returns whether it existed. */
|
|
1151
|
+
async deleteCachedModel(repoId) {
|
|
1152
|
+
const result = await this._providerCall("models.delete", {
|
|
1153
|
+
repo_id: repoId
|
|
1154
|
+
});
|
|
1155
|
+
return Boolean(result.deleted);
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Whether the attached worker speaks bridge protocol v2+ and therefore
|
|
1159
|
+
* supports the models.* RPC. This is the per-capability gate that lets an
|
|
1160
|
+
* older v1 worker connect normally (it sits above the hard floor) and simply
|
|
1161
|
+
* not expose worker model management — the Worker scope is hidden/disabled
|
|
1162
|
+
* rather than erroring. `models.*` was introduced in protocol v2, so the
|
|
1163
|
+
* floor here is a fixed 2, independent of BRIDGE_PROTOCOL_VERSION.
|
|
1164
|
+
*/
|
|
1165
|
+
supportsModelManagement() {
|
|
1166
|
+
return (this._workerStatus?.protocol_version ?? 0) >= 2;
|
|
1167
|
+
}
|
|
1168
|
+
/** Whether the attached image advertises a specific preparation backend. */
|
|
1169
|
+
supportsModelPreparation(backend) {
|
|
1170
|
+
return ((this._workerStatus?.protocol_version ?? 0) >= 5 &&
|
|
1171
|
+
(this._workerStatus?.model_prepare_backends ?? []).includes(backend));
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Drop loaded model weights on the worker. The worker reclaims reactively on
|
|
1175
|
+
* its own thresholds; this is the path for what only the JS side knows — the
|
|
1176
|
+
* user switched workflows, another process wants the GPU, the worker is
|
|
1177
|
+
* idle. Introduced in protocol v4, so the floor here is a fixed 4.
|
|
1178
|
+
*
|
|
1179
|
+
* A pre-v4 worker would answer `Unknown message type`, so this resolves to an
|
|
1180
|
+
* empty eviction instead of sending: eviction is an optimization, and a host
|
|
1181
|
+
* asking to free memory on a worker that cannot should not have to branch.
|
|
1182
|
+
*/
|
|
1183
|
+
async evictModels(req = {}) {
|
|
1184
|
+
if (!this.supportsJobLifecycle()) {
|
|
1185
|
+
return { evicted: [] };
|
|
1186
|
+
}
|
|
1187
|
+
const result = await this._providerCall("models.evict", { ...req });
|
|
1188
|
+
const evictResult = {
|
|
1189
|
+
// SAFETY: the worker answers `models.evict` with a list of model ids; a
|
|
1190
|
+
// reply that is not an array is treated as evicting nothing.
|
|
1191
|
+
evicted: Array.isArray(result.evicted) ? result.evicted : []
|
|
1192
|
+
};
|
|
1193
|
+
if (isNumber(result.freed_vram_gb)) {
|
|
1194
|
+
evictResult.freed_vram_gb = result.freed_vram_gb;
|
|
1195
|
+
}
|
|
1196
|
+
return evictResult;
|
|
1197
|
+
}
|
|
1198
|
+
// ── Run boundary (bridge protocol v4+) ─────────────────────────────────
|
|
1199
|
+
/**
|
|
1200
|
+
* Whether the attached worker speaks bridge protocol v4+ and therefore
|
|
1201
|
+
* understands `job.start` / `job.end` / `models.evict`. Per-capability soft
|
|
1202
|
+
* gate, like {@link supportsModelManagement}.
|
|
1203
|
+
*
|
|
1204
|
+
* This gates only the new message *types*. The identity fields v4 adds to
|
|
1205
|
+
* `execute` ride along regardless — see {@link _identityPayload}.
|
|
1206
|
+
*/
|
|
1207
|
+
supportsJobLifecycle() {
|
|
1208
|
+
return (this._workerStatus?.protocol_version ?? 0) >= 4;
|
|
1209
|
+
}
|
|
1210
|
+
/**
|
|
1211
|
+
* Open a run boundary. Optional: the worker needs no `job.start` to
|
|
1212
|
+
* attribute an execution (every `execute` carries its own `job_id`), so this
|
|
1213
|
+
* exists as the one place to do a single reclaim pass per run instead of one
|
|
1214
|
+
* per node.
|
|
1215
|
+
*/
|
|
1216
|
+
async jobStart(job) {
|
|
1217
|
+
await this._jobBoundary("job.start", job);
|
|
1218
|
+
}
|
|
1219
|
+
/**
|
|
1220
|
+
* Close a run boundary: the worker's nodes for this job are retired and
|
|
1221
|
+
* their models become eligible for release. This is the caller
|
|
1222
|
+
* `release_nodes()` never had — without it the worker's model cache grows
|
|
1223
|
+
* across runs and is only ever trimmed reactively under memory pressure.
|
|
1224
|
+
*
|
|
1225
|
+
* Must fire on abnormal termination too (cancelled, client disconnected, run
|
|
1226
|
+
* abandoned), which is why `ExecutionSession` calls it from the same
|
|
1227
|
+
* `finally` that closes the bridge rather than off the success path.
|
|
1228
|
+
*/
|
|
1229
|
+
async jobEnd(job) {
|
|
1230
|
+
await this._jobBoundary("job.end", job);
|
|
1231
|
+
}
|
|
1232
|
+
/**
|
|
1233
|
+
* Send one boundary frame. Never rejects: a boundary is bookkeeping, and a
|
|
1234
|
+
* `job.end` that fails on a worker already tearing down must not turn a
|
|
1235
|
+
* finished run into a failed one. Failures are logged and swallowed.
|
|
1236
|
+
*/
|
|
1237
|
+
async _jobBoundary(type, job) {
|
|
1238
|
+
if (!this.supportsJobLifecycle())
|
|
1239
|
+
return;
|
|
1240
|
+
const data = { job_id: job.jobId };
|
|
1241
|
+
if (job.workflowId)
|
|
1242
|
+
data.workflow_id = job.workflowId;
|
|
1243
|
+
if (job.userId)
|
|
1244
|
+
data.user_id = job.userId;
|
|
1245
|
+
if (job.reason)
|
|
1246
|
+
data.reason = job.reason;
|
|
1247
|
+
try {
|
|
1248
|
+
await this._providerCall(type, data);
|
|
1249
|
+
}
|
|
1250
|
+
catch (err) {
|
|
1251
|
+
log.warn(`Python bridge ${type} failed`, {
|
|
1252
|
+
jobId: job.jobId,
|
|
1253
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
// ── ComfyUI proxy (bridge protocol v3+) ────────────────────────────────
|
|
1258
|
+
/**
|
|
1259
|
+
* Whether the attached worker fronts a ComfyUI server and speaks `comfy.*`.
|
|
1260
|
+
* Requires protocol v3+ (the `comfy.*` family) AND a `worker.status.comfy`
|
|
1261
|
+
* block reporting `enabled: true` — not every v3 worker has a ComfyUI, so
|
|
1262
|
+
* route ComfyUI jobs only where this is true. Per-capability soft gate, like
|
|
1263
|
+
* {@link supportsModelManagement}.
|
|
1264
|
+
*/
|
|
1265
|
+
supportsComfy() {
|
|
1266
|
+
const status = this._workerStatus;
|
|
1267
|
+
return ((status?.protocol_version ?? 0) >= 3 && status?.comfy?.enabled === true);
|
|
1268
|
+
}
|
|
1269
|
+
/** The last-known `comfy` block from `worker.status`, or null. */
|
|
1270
|
+
getComfyStatus() {
|
|
1271
|
+
return this._workerStatus?.comfy ?? null;
|
|
1272
|
+
}
|
|
1273
|
+
/**
|
|
1274
|
+
* Submit a ComfyUI workflow and drain its `comfy.event` lifecycle.
|
|
1275
|
+
*
|
|
1276
|
+
* `comfy.execute` streams `queued → queue → started/cached → executing →
|
|
1277
|
+
* progress → node_output → preview → completed/cancelled` as dedicated
|
|
1278
|
+
* `comfy.event` frames (routed to {@link onEvent}), then settles with a
|
|
1279
|
+
* terminal `result` (resolve) or `error` (reject) — `result` is always last.
|
|
1280
|
+
* The event callback is registered in {@link _pendingComfyEvents} and the
|
|
1281
|
+
* terminal frame in {@link _pendingStream}; both are cleared on settle.
|
|
1282
|
+
*
|
|
1283
|
+
* Cancellable via {@link cancelComfyExecute}(requestId): pass a stable
|
|
1284
|
+
* `requestId` (or reuse the returned default) to reach this exact run. Prefer
|
|
1285
|
+
* that over the bare {@link cancel} — a plain cancel frame does not settle the
|
|
1286
|
+
* local promise, so if the worker never emits a terminal frame the promise
|
|
1287
|
+
* would hang and the pending maps would leak.
|
|
1288
|
+
*/
|
|
1289
|
+
comfyExecute(workflow, options = {}, onEvent, requestId = randomUUID()) {
|
|
1290
|
+
// The worker's field is `workflow`, not `prompt` — see the comfy.execute
|
|
1291
|
+
// request schema in nodetool-core's docs/comfy-proxy.md.
|
|
1292
|
+
const data = { workflow };
|
|
1293
|
+
if (options.blobs)
|
|
1294
|
+
data.blobs = options.blobs;
|
|
1295
|
+
if (options.previews)
|
|
1296
|
+
data.previews = true;
|
|
1297
|
+
if (options.includeTemp)
|
|
1298
|
+
data.include_temp = true;
|
|
1299
|
+
if (options.timeout != null)
|
|
1300
|
+
data.timeout = options.timeout;
|
|
1301
|
+
return new Promise((resolve, reject) => {
|
|
1302
|
+
let settled = false;
|
|
1303
|
+
const settle = (fn) => {
|
|
1304
|
+
if (settled)
|
|
1305
|
+
return;
|
|
1306
|
+
settled = true;
|
|
1307
|
+
this._pendingStream.delete(requestId);
|
|
1308
|
+
this._pendingComfyEvents.delete(requestId);
|
|
1309
|
+
fn();
|
|
1310
|
+
};
|
|
1311
|
+
if (onEvent)
|
|
1312
|
+
this._pendingComfyEvents.set(requestId, onEvent);
|
|
1313
|
+
this._pendingStream.set(requestId, {
|
|
1314
|
+
resolve: (result) => settle(() => resolve(result)),
|
|
1315
|
+
reject: (err) => settle(() => reject(err)),
|
|
1316
|
+
onChunk: () => { }
|
|
1317
|
+
});
|
|
1318
|
+
try {
|
|
1319
|
+
this._send({ type: "comfy.execute", request_id: requestId, data });
|
|
1320
|
+
}
|
|
1321
|
+
catch (err) {
|
|
1322
|
+
settle(() => reject(err instanceof Error ? err : new Error(String(err))));
|
|
1323
|
+
}
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* Cancel an in-flight {@link comfyExecute} by request id. Sends the cancel
|
|
1328
|
+
* frame AND settles the local promise by rejecting it — the worker may never
|
|
1329
|
+
* emit a terminal `result`/`error` after a cancel (or may be hung), so we
|
|
1330
|
+
* cannot rely on one to clean up. Rejecting through the pending-stream entry
|
|
1331
|
+
* runs comfyExecute's settle(), clearing BOTH `_pendingStream` and
|
|
1332
|
+
* `_pendingComfyEvents`. A no-op if the id is unknown. Mirrors
|
|
1333
|
+
* {@link cancelModelDownload}.
|
|
1334
|
+
*/
|
|
1335
|
+
cancelComfyExecute(requestId) {
|
|
1336
|
+
// Best-effort (see cancelModelDownload): swallow a 'Not connected' from
|
|
1337
|
+
// _send so a disconnected cancel stays the documented no-op.
|
|
1338
|
+
try {
|
|
1339
|
+
this.cancel(requestId);
|
|
1340
|
+
}
|
|
1341
|
+
catch {
|
|
1342
|
+
// Worker may already be gone; cancel is best-effort.
|
|
1343
|
+
}
|
|
1344
|
+
const streamReq = this._pendingStream.get(requestId);
|
|
1345
|
+
if (streamReq) {
|
|
1346
|
+
streamReq.reject(new Error(`ComfyUI execution "${requestId}" was cancelled.`));
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
// ── Blender worker ────────────────────────────────────────────────────
|
|
1350
|
+
/**
|
|
1351
|
+
* Whether the attached worker can run Blender and speaks `blender.*`.
|
|
1352
|
+
* Requires a `worker.status.blender` block reporting `enabled: true` — a
|
|
1353
|
+
* worker that says nothing about Blender (every worker that predates the
|
|
1354
|
+
* family) is treated as having none. Per-capability soft gate, like
|
|
1355
|
+
* {@link supportsComfy}. Deliberately no protocol-version floor: the
|
|
1356
|
+
* version bump rides with the worker image, which ships outside this
|
|
1357
|
+
* repository, so the flag alone decides.
|
|
1358
|
+
*/
|
|
1359
|
+
supportsBlender() {
|
|
1360
|
+
return this._workerStatus?.blender?.enabled === true;
|
|
1361
|
+
}
|
|
1362
|
+
/** The last-known `blender` block from `worker.status`, or null. */
|
|
1363
|
+
getBlenderStatus() {
|
|
1364
|
+
return this._workerStatus?.blender ?? null;
|
|
1365
|
+
}
|
|
1366
|
+
/**
|
|
1367
|
+
* Run a Blender job and drain its `blender.event` progress stream.
|
|
1368
|
+
*
|
|
1369
|
+
* `blender.execute` streams `progress` events as dedicated `blender.event`
|
|
1370
|
+
* frames (routed to {@link onEvent}), then settles with a terminal
|
|
1371
|
+
* `result` (resolve) or `error` (reject) — `result` is always last. The
|
|
1372
|
+
* event callback is registered in {@link _pendingBlenderEvents} and the
|
|
1373
|
+
* terminal frame in {@link _pendingStream}; both are cleared on settle.
|
|
1374
|
+
*
|
|
1375
|
+
* Cancellable via {@link cancelBlenderExecute}(requestId): pass a stable
|
|
1376
|
+
* `requestId` (or reuse the returned default) to reach this exact run.
|
|
1377
|
+
* Prefer that over the bare {@link cancel} — a plain cancel frame does not
|
|
1378
|
+
* settle the local promise, so if the worker never emits a terminal frame
|
|
1379
|
+
* the promise would hang and the pending maps would leak.
|
|
1380
|
+
*/
|
|
1381
|
+
blenderExecute(job, inputs, options = {}, onEvent, requestId = randomUUID()) {
|
|
1382
|
+
const data = { job, inputs };
|
|
1383
|
+
if (options.blobs)
|
|
1384
|
+
data.blobs = options.blobs;
|
|
1385
|
+
if (options.timeout != null)
|
|
1386
|
+
data.timeout = options.timeout;
|
|
1387
|
+
return new Promise((resolve, reject) => {
|
|
1388
|
+
let settled = false;
|
|
1389
|
+
const settle = (fn) => {
|
|
1390
|
+
if (settled)
|
|
1391
|
+
return;
|
|
1392
|
+
settled = true;
|
|
1393
|
+
this._pendingStream.delete(requestId);
|
|
1394
|
+
this._pendingBlenderEvents.delete(requestId);
|
|
1395
|
+
fn();
|
|
1396
|
+
};
|
|
1397
|
+
if (onEvent)
|
|
1398
|
+
this._pendingBlenderEvents.set(requestId, onEvent);
|
|
1399
|
+
this._pendingStream.set(requestId, {
|
|
1400
|
+
resolve: (result) => settle(() => resolve(result)),
|
|
1401
|
+
reject: (err) => settle(() => reject(err)),
|
|
1402
|
+
onChunk: () => { }
|
|
1403
|
+
});
|
|
1404
|
+
try {
|
|
1405
|
+
this._send({ type: "blender.execute", request_id: requestId, data });
|
|
1406
|
+
}
|
|
1407
|
+
catch (err) {
|
|
1408
|
+
settle(() => reject(err instanceof Error ? err : new Error(String(err))));
|
|
1409
|
+
}
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
/**
|
|
1413
|
+
* Cancel an in-flight {@link blenderExecute} by request id. Sends the
|
|
1414
|
+
* cancel frame AND settles the local promise by rejecting it — the worker
|
|
1415
|
+
* may never emit a terminal `result`/`error` after a cancel (or may be
|
|
1416
|
+
* hung), so we cannot rely on one to clean up. Rejecting through the
|
|
1417
|
+
* pending-stream entry runs blenderExecute's settle(), clearing BOTH
|
|
1418
|
+
* `_pendingStream` and `_pendingBlenderEvents`. A no-op if the id is
|
|
1419
|
+
* unknown. Mirrors {@link cancelComfyExecute}.
|
|
1420
|
+
*/
|
|
1421
|
+
cancelBlenderExecute(requestId) {
|
|
1422
|
+
// Best-effort (see cancelModelDownload): swallow a 'Not connected' from
|
|
1423
|
+
// _send so a disconnected cancel stays the documented no-op.
|
|
1424
|
+
try {
|
|
1425
|
+
this.cancel(requestId);
|
|
1426
|
+
}
|
|
1427
|
+
catch {
|
|
1428
|
+
// Worker may already be gone; cancel is best-effort.
|
|
1429
|
+
}
|
|
1430
|
+
const streamReq = this._pendingStream.get(requestId);
|
|
1431
|
+
if (streamReq) {
|
|
1432
|
+
streamReq.reject(new Error(`Blender execution "${requestId}" was cancelled.`));
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
async comfyQueue() {
|
|
1436
|
+
return this._providerCall("comfy.queue", {});
|
|
1437
|
+
}
|
|
1438
|
+
async comfyInterrupt() {
|
|
1439
|
+
await this._providerCall("comfy.interrupt", {});
|
|
1440
|
+
}
|
|
1441
|
+
async comfyCancelPrompt(promptId) {
|
|
1442
|
+
await this._providerCall("comfy.cancel", { prompt_id: promptId });
|
|
1443
|
+
}
|
|
1444
|
+
async comfyUpload(filename, bytes, options = {}) {
|
|
1445
|
+
// The worker reads the payload from `data`, not `blob`.
|
|
1446
|
+
return this._providerCall("comfy.upload", {
|
|
1447
|
+
filename,
|
|
1448
|
+
data: bytes,
|
|
1449
|
+
...options
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
async comfyView(filename, options = {}) {
|
|
1453
|
+
return this._providerCall("comfy.view", { filename, ...options });
|
|
1454
|
+
}
|
|
1455
|
+
async comfyObjectInfo() {
|
|
1456
|
+
// The worker wraps the catalog as `{object_info: {...}}`; unwrap so callers
|
|
1457
|
+
// get the catalog itself, as this method's contract promises.
|
|
1458
|
+
const result = await this._providerCall("comfy.object_info", {});
|
|
1459
|
+
return (result.object_info ?? result);
|
|
1460
|
+
}
|
|
1461
|
+
async comfySystemStats() {
|
|
1462
|
+
return this._providerCall("comfy.system_stats", {});
|
|
1463
|
+
}
|
|
1464
|
+
async comfyStatus() {
|
|
1465
|
+
return comfyStatusInfoSchema.parse(await this._providerCall("comfy.status", {}));
|
|
1466
|
+
}
|
|
1467
|
+
async comfyFree(options = {}) {
|
|
1468
|
+
await this._providerCall("comfy.free", options);
|
|
1469
|
+
}
|
|
1470
|
+
async comfyModelsList(folder) {
|
|
1471
|
+
const result = await this._providerCall("comfy.models.list", {});
|
|
1472
|
+
const models = result.models ?? [];
|
|
1473
|
+
// The worker always returns the whole volume — there is no `folder` filter
|
|
1474
|
+
// in the comfy.models.list request schema — so narrow it here.
|
|
1475
|
+
return folder ? models.filter((m) => m.folder === folder) : models;
|
|
1476
|
+
}
|
|
1477
|
+
async comfyModelsDownload(req, onProgress, requestId = randomUUID()) {
|
|
1478
|
+
return this._streamingDownload("comfy.models.download", { ...req }, (u) => onProgress(u), requestId);
|
|
1479
|
+
}
|
|
1480
|
+
async comfyModelsDelete(folder, filename) {
|
|
1481
|
+
const result = await this._providerCall("comfy.models.delete", {
|
|
1482
|
+
folder,
|
|
1483
|
+
filename
|
|
1484
|
+
});
|
|
1485
|
+
return Boolean(result.deleted);
|
|
1486
|
+
}
|
|
1487
|
+
async _providerCall(type, data) {
|
|
1488
|
+
const requestId = randomUUID();
|
|
1489
|
+
return new Promise((resolve, reject) => {
|
|
1490
|
+
this._pendingStream.set(requestId, {
|
|
1491
|
+
resolve,
|
|
1492
|
+
reject,
|
|
1493
|
+
onChunk: () => { }
|
|
1494
|
+
});
|
|
1495
|
+
try {
|
|
1496
|
+
this._send({ type, request_id: requestId, data });
|
|
1497
|
+
}
|
|
1498
|
+
catch (err) {
|
|
1499
|
+
this._pendingStream.delete(requestId);
|
|
1500
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
1501
|
+
}
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1504
|
+
/** Provider RPC variant that opts into protocol-v5 chunked result blobs. */
|
|
1505
|
+
async _providerBlobCall(type, data, signal) {
|
|
1506
|
+
const requestId = randomUUID();
|
|
1507
|
+
if (signal?.aborted) {
|
|
1508
|
+
throw new Error(`Provider request "${requestId}" was cancelled.`);
|
|
1509
|
+
}
|
|
1510
|
+
return new Promise((resolve, reject) => {
|
|
1511
|
+
const cleanup = () => signal?.removeEventListener("abort", onAbort);
|
|
1512
|
+
const onAbort = () => {
|
|
1513
|
+
this._pending.delete(requestId);
|
|
1514
|
+
cleanup();
|
|
1515
|
+
try {
|
|
1516
|
+
this.cancel(requestId);
|
|
1517
|
+
}
|
|
1518
|
+
catch {
|
|
1519
|
+
// Worker may already be gone; cancellation is best-effort.
|
|
1520
|
+
}
|
|
1521
|
+
reject(new Error(`Provider request "${requestId}" was cancelled.`));
|
|
1522
|
+
};
|
|
1523
|
+
this._pending.set(requestId, {
|
|
1524
|
+
resolve: (value) => {
|
|
1525
|
+
cleanup();
|
|
1526
|
+
resolve(value);
|
|
1527
|
+
},
|
|
1528
|
+
reject: (error) => {
|
|
1529
|
+
cleanup();
|
|
1530
|
+
reject(error);
|
|
1531
|
+
}
|
|
1532
|
+
});
|
|
1533
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1534
|
+
try {
|
|
1535
|
+
this._send({
|
|
1536
|
+
type,
|
|
1537
|
+
request_id: requestId,
|
|
1538
|
+
data: { ...data, blob_transfer: "chunked-v1" }
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1541
|
+
catch (err) {
|
|
1542
|
+
this._pending.delete(requestId);
|
|
1543
|
+
cleanup();
|
|
1544
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
1545
|
+
}
|
|
1546
|
+
});
|
|
1547
|
+
}
|
|
1548
|
+
// ── Diagnostics ────────────────────────────────────────────────────
|
|
1549
|
+
/**
|
|
1550
|
+
* Recent worker stderr summary used to enrich timeout errors. Transports
|
|
1551
|
+
* that capture stderr (stdio) override this; the base returns null.
|
|
1552
|
+
*/
|
|
1553
|
+
getRecentStderrSummary(_limit = 12) {
|
|
1554
|
+
return null;
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
//# sourceMappingURL=python-bridge-base.js.map
|