mirror of
https://github.com/Comfy-Org/comfy-typescript-sdk.git
synced 2026-11-04 23:47:27 +00:00
[PR #75] [MERGED] feat: add the Router error classes, one per error_type, on a shared base #76
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#76
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/75
Author: @mattmillerai
Created: 8/23/2026
Status: ✅ Merged
Merged: 8/24/2026
Merged by: @mattmillerai
Base:
main← Head:matt/be-8504-router-error-classes📝 Commits (1)
5e28042feat: add the Router error classes, one per error_type, on a shared base📊 Changes
6 files changed (+938 additions, -0 deletions)
View changed files
📝
README.md(+79 -0)📝
package.json(+4 -0)📝
src/index.test.ts(+19 -0)📝
src/sdk/index.ts(+12 -0)➕
src/sdk/routerErrors.test.ts(+426 -0)➕
src/sdk/routerErrors.ts(+398 -0)📄 Description
ELI-5
When a model call fails, the server hands back one short word for why —
content_policy_violation,provider_timeout,insufficient_credits, and so on. Before this PR you would have had to compare that word against a string yourself. Now each one is a class you cancatchby name, and the Python SDK spells every one of those names exactly the same way, so a code sample copied from a Python answer works in TypeScript with only the syntax changed.What this adds
Eleven exception classes for the Comfy Router error contract — six request-level (
InvalidInput,ContentPolicyViolation,ProviderError,ProviderTimeout,InsufficientCredits,ModelNotFound) and five transport-level (Unauthorized,Forbidden,ConcurrencyLimitExceeded,ClientDisconnected,InternalError) — all descending from a singleRouterError, plustoRouterError(status, headers, body), which builds the right one from a response.Every error carries
errorType(the raw bucket),requestId(offX-Comfy-Request-Id— the value to quote in a support request) andhttpStatus.InvalidInputadditionally carries the422'sdetail[]as structured entries (loc,msg,type,ctx,input) rather than a flattened string, becausetypeandctxcarry the specific provider reason and the bound it violated — granularity the coarse bucket cannot express.New surface:
routerErrorsfrom the package root, and a@comfyorg/sdk/errorssubpath for importing the classes directly.README.mdgains a section under Typed errors.Judgment calls
The classes are namespaced (
routerErrors.*), not flattened into the root exports. Three of the eleven names —Unauthorized,Forbidden,InsufficientCredits— are already exported from the package root as workflow-API exceptions descending fromComfyError, and they are unrelated classes. The Python SDK has the same three at the top level of its own package, so it hits the identical collision, and a dedicated module is the resolution that keeps the class names themselves untouched in both languages:routerErrors.ContentPolicyViolationhere,router_errors.ContentPolicyViolationthere, with the class identifier character-identical either way. A test pins that the twoUnauthorizedclasses stay distinct and that neither answers for the other.No casing deviation was needed. The names are the PascalCase of the wire value, which is idiomatic class casing in both languages, so the acceptance criterion's escape hatch is unused.
The closed set is hand-declared, not generated. The vendored
spec/openapi.yamldoes not carry the Router error components (grep -niE 'models/\{provider\}|router|/v1/models' spec/openapi.yaml→ no matches), so the eleven values are restated in the module and pinned by a test that also asserts the three deferred buckets are absent. When the components land in the vendored spec, that test is where the two get reconciled.The status-based fallback is a fallback. The bucket is read off the header first and the body's
error_typesecond — that order, because the422body carries noerror_typeof its own. Only when a response has neither is a bucket inferred from the HTTP status, which covers a proxy or gateway that failed before Router was reached.Verified
pnpm test(300 passed, 0 failed),pnpm lint,pnpm typecheck,pnpm format:check,pnpm check:spec-driftandnode scripts/check-public-repo-hygiene.mjsall clean;pnpm build+pnpm packconfirmdist/sdk/routerErrors.{js,d.ts}ship, and a real resolution probe confirms@comfyorg/sdk/errorsand the rootrouterErrorsnamespace return the same class identity (soinstanceofholds across both import styles). The new module is at 100% statement, branch, function and line coverage.Two tests are worth calling out because they check the thing rather than assert the premise:
typescriptdevDependency, evaluates it, and checksinstanceofon every class in that build. Extending a built-in is the classicinstanceoftrap; deleting the oneObject.setPrototypeOfline in the base constructor turns 12 of these tests red, which I confirmed by deleting it.BY_ERROR_TYPE[errorType], which resolvesconstructorandtoStringoffObject.prototypeand would have handed back something that is not aRouterErrorat all — an untyped throw by another route, the exact thing the unknown-bucket criterion forbids. Own-property lookup fixes it; reverting the fix turns 4 tests red.What this PR does not do — please read before treating the story as finished
toRouterErrorhas no production caller yet.comfy.models.runstill raisesModelRunNotImplemented, unchanged by this PR, because the vendored spec declares no model-execution route (same grep as above). This PR is the error contract shipped ahead of the call that raises it; wiring it is the model-execution story's job, and that wiring is not tested here because there is nothing to wire it into.ContentPolicyViolationis deterministic and must not be retried likeProviderError;ProviderTimeoutis an upstream stall, not a Comfy-side deadline), but nothing in this diff acts on them.file_download_error,cancelled,queue_timeout) are deliberately absent — 0 of 3 implemented — and a test pins their absence so adding one stays a decision made on purpose, in lockstep with the server and the sibling SDK. They currently degrade to the baseRouterErrorcarrying the raw value, which is tested.detail[]shape against them.Provenance
toRouterErrorhas no production call site until model execution is wired; both are detailed above🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.