mirror of
https://github.com/Comfy-Org/comfy-typescript-sdk.git
synced 2026-11-04 23:47:27 +00:00
[PR #67] [MERGED] feat: add the comfy.models namespace and module-level comfy.config({ credentials }) #69
Labels
No labels
audit:ai-readiness
audit:community
audit:github
audit:health-scoring
audit:policy
audit:release
audit:supply-chain
bug
cursor-review-pin
effort:medium
effort:small
effort:trivial
priority:nice-to-have
priority:recommended
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Comfy-Org/comfy-typescript-sdk#69
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/Comfy-Org/comfy-typescript-sdk/pull/67
Author: @mattmillerai
Created: 8/23/2026
Status: ✅ Merged
Merged: 8/23/2026
Merged by: @mattmillerai
Base:
main← Head:matt/be-8502-models-namespace-config📝 Commits (1)
706bcdcfeat: add the comfy.models namespace and module-level comfy.config({ credentials })📊 Changes
11 files changed (+657 additions, -5 deletions)
View changed files
📝
README.md(+61 -0)📝
src/index.test.ts(+19 -1)📝
src/low/transport.ts(+11 -4)➕
src/sdk/comfy.ts(+31 -0)➕
src/sdk/credentials.test.ts(+186 -0)➕
src/sdk/credentials.ts(+87 -0)📝
src/sdk/exceptions.ts(+23 -0)📝
src/sdk/index.ts(+10 -0)➕
src/sdk/models.spec-contract.test.ts(+66 -0)➕
src/sdk/models.test.ts(+98 -0)➕
src/sdk/models.ts(+65 -0)📄 Description
ELI-5
There are two ways to hand this SDK an API key. Until now there was one: construct a
Comfyclient and passapiKeyto it. This adds the other one — configure it once for the whole process (comfy.config({ credentials })), which is the shape a lot of hosted-inference clients use and the shape someone porting an existing integration already has in their fingers. It also adds thecomfy.modelsnamespace those calls will hang off.comfy.models.rundoes not run anything yet: it resolves credentials and fails loudly and locally, pointing you at the workflow API that does work today. Separately, and less pleasantly:console.log(client)used to print your API key. It no longer does.What changed
New module-level surface (
src/sdk/credentials.ts,src/sdk/models.ts,src/sdk/comfy.ts), re-exported from the package root:comfy.config({ credentials })sets the credential for every subsequentcomfy.*call. Only keys present on the argument are touched, soconfig({})is a no-op;credentials: undefinedclears; an empty or whitespace-only string is aTypeErrorrather than a silent clear (a silent clear would fall through to the environment and quietly break the documented precedence).COMFY_API_KEY, read per call rather than at module load. Explicit config wins. A blank variable counts as unset, and a runtime with noprocess(a browser) degrades toundefinedinstead of throwing.import { comfy }andimport * as comfy.comfy.models.run(model, input)resolves credentials and throws locally, with no socket opened:MissingCredentialswhen there is none,ModelRunNotImplementedwhen there is. Both areComfyErrorsubclasses.src/sdk/models.spec-contract.test.ts: it asserts the vendored spec declares no model-execution route, and fails with a fix-it message the moment one appears.Credential leak fix (
src/low/transport.ts):ComfyLow'sapiKeyis now an ECMAScript#privatefield rather than a TypeScriptprivateone. TSprivateis erased at runtime, so the key was an enumerable own property and showed up in anything that serialized or inspected a client.Docs: a new README section covering the namespace, the precedence rules, the process-global caveat, the two error cases, and the module-format story.
Why
models.runthrows instead of running (falsification evidence)This diff ships a capability denial, so the denial was checked rather than assumed. Every surface I can read says model-by-id execution does not exist here yet:
spec/openapi.yaml(the contract this SDK generates from)/api/v2/assetsand/api/v2/jobs; no model routeOPERATION_IDSinsrc/low/transport.tssrc/low/generated/types.gen.ts@comfyorg/sdk@0.1.7(npm pack, unpacked)models/comfy/credentialsmodule; root exports are the class client onlyOne path I could not settle, stated as such: an unauthenticated read-only probe of the deployed gateway is inconclusive, because it authenticates before it routes —
https://cloud.comfy.org/api/v2/<anything>returns401 unauthorizedwhether or not the path exists (a deliberately nonsense/api/v2/...path 401s too, while/api/v9/...404s), and no credential was available in the environment this was written in. That is why the thrown message is scoped to the spec ("the Comfy API v2 spec this SDK is generated from declares no model-execution route") rather than to the server: that claim is verifiable from this repo and stays true regardless of what the gateway is serving behind auth. And the acceptance criteria require the types to come from the spec, so an undocumented route would not be implementable here anyway.The denial is a signpost, not a dead-end. Running a model's workflow graph works today, so the error redirects there —
await new Comfy({ apiKey }).run(workflow)— and a test asserts that sentence stays in the message.Acceptance criteria
comfy.modelsexported and reachable as documentedcomfy.config({ credentials })sets credentials for subsequent callsCOMFY_API_KEY, per-call read, precedence testedmodels.runwith no credentials throws a clear, named error locally, without a network callMissingCredentials; afetchspy asserts zero callsNot met: "a contract change surfaces as a type error"
Nothing in this diff hand-declares a wire type — but neither does it generate one, because there is nothing to generate: the vendored spec has no model-execution operation, and the story this one sits under is explicitly blocked on that route contract. So the criterion is met in its "rather than hand-declared" half and not met in its "surfaces as a type error" half.
What I shipped in its place is a mechanical tripwire rather than a compile failure, and it is proven, not asserted: injecting a
POST /api/v2/models/{model}/runoperation into the spec turns three tests red — both assertions inmodels.spec-contract.test.ts(each printing "runpnpm generate, wire it throughsrc/low/transport.ts, replace theModelRunNotImplementedplaceholder with a call typed by the generated request/response types") and the pre-existingspec coverage > declared operation IDs match the vendored spec. The spec was restored immediately; no spec change is in this diff. A genuine generated-type binding lands with the story that implementsrun.The package is ESM-only
Measured, not inferred, against the built
dist/on Node 24.18.0 and Node 22.23.2:import(both{ comfy }and* as comfy): works.require("@comfyorg/sdk")on Node >= 22.12: works, via Node's built-inrequire(esm).await import(...): works."module": "nodenext"for both"type": "module"and"type": "commonjs"packages. Under"module": "node16", a CommonJS consumer's static import is rejected withTS1479and needsawait import(...).No CJS build was added — that is a packaging decision (dual-build,
exportsconditions,"engines"floor) well outside this story. The README now states the above rather than leaving a consumer to discover it.The credential leak this uncovered
While writing the redaction test I found the criterion was already violated on
mainand in the published package. Same sweep, run both ways over every consumer-reachable object that transitively holds the key, checkingJSON.stringify,util.inspect(depth: 20)andString():@comfyorg/sdk@0.1.7: 4 of 6 objects print the API key (Comfy,AssetFactory,JobFactory,Asset;WorkflowFactoryandJobhold no client).comfynamespace).Reverting only the
#apiKeyline turns exactly one test red —credential redaction > keeps the constructor apiKey out of a serialized client— so the fix is pinned red-to-green. The SDK itself emits no log lines at all (grepforconsole.*/process.stdoutover non-test source: zero hits), so the "log line" half of the criterion is a property of the objects, which is what the test measures.Judgment calls
COMFY_API_KEYis a new public contract I chose. Neither SDK had an environment variable for the key before; this is the obvious sibling to the existingCOMFY_BASE_URL. Easy to veto now, expensive to rename later.new Comfy()deliberately does not consultcomfy.configorCOMFY_API_KEY. Making it fall back would mean a process withCOMFY_API_KEYexported starts attaching a bearer token to a self-hosted proxy the user deliberately gave no key to — and the README's auth table explicitly says not to pass a key there. The fallback is scoped to the newcomfy.*surface; the class client is untouched.#apiKeychange. The auth condition itself (if (apiKey && isSameOrigin(url))) is byte-for-byte unchanged; only the field's storage moved, and both read sites moved together. A test assertsAuthorization: Bearer <key>is still sent, and the full pre-existing suite passes on Node 22 and 24. The one behavior change for a consumer: JavaScript code reaching intolow.apiKeynow readsundefined. TypeScript already forbade that, and it is the point of the fix — flagging it rather than burying it.Object.freezeoncomfyandcomfy.models, since they are shared process-wide.resolveCredentials()is exported, so an app can ask "am I configured?" without atry/catcharound a call that throws./model/ion operationIds in the tripwire is deliberately broad. A false trip is a loud failure with instructions, and the pre-existing spec-coverage test already fails on any new operation, so the marginal blast radius is nil.configtakes onlycredentials.COMFY_BASE_URLremains the only base-URL knob; addingcomfy.config({ baseUrl })was not asked for and would need its own precedence story against the existing environment variable.Unexercised artifacts
comfy.models, this needs adjusting before release./api/v2/modelsbehaviour, for the pre-routing-401 reason detailed above.Follow-on work this does not do
Out of scope for this story and left open deliberately:
models.run's actual execution, its full error-class taxonomy, retry andabortSignal, and browser-specific credential handling. TheModelRunNotImplementedplaceholder andmodels.spec-contract.test.tsboth exist to be deleted by the story that implementsrun. This work item stays open — its "types generated from the spec" criterion is only half satisfied, as detailed above.Provenance
pnpm format:check,pnpm lint,pnpm typecheckclean;pnpm test224 passed / 19 files (192 was the baseline onmain), green on both Node 24.18.0 and Node 22.23.2;node scripts/check-spec-drift.mjsin sync;node scripts/check-public-repo-hygiene.mjsclean;pnpm build+pnpm packproducedist/index.jsanddist/index.d.tswith no test files in the tarball; ESM/CJS/TypeScript consumer matrix exercised against the builtdistas tabulated above; redaction sweep run against both the published 0.1.7 and this branch (4/6 leaking vs 0/7); tripwire and redaction fixes each proven red before green.🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.