[PR #145] [MERGED] fix: return a model run's native bytes instead of raising on a non-JSON 200 #145

Closed
opened 2026-10-31 22:01:46 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/Comfy-Org/comfy-python-sdk/pull/145
Author: @mattmillerai
Created: 9/11/2026
Status: ✅ Merged
Merged: 10/3/2026
Merged by: @mattmillerai

Base: main ← Head: matt/be-13346-binary-model-run-result


📝 Commits (5)

  • f566a9e fix: return a model run's native bytes instead of raising on a non-JSON 200
  • a26ed9a Merge branch 'main' into matt/be-13346-binary-model-run-result
  • 3ab6d45 fix(models): harden the run-result branch against the review findings
  • 1563c21 merge main into matt/be-13346-binary-model-run-result, resolve conflicts
  • 943f8ac fix(models): return BinaryResult from the queued result route too

📊 Changes

13 files changed (+1223 additions, -116 deletions)

View changed files

📝 CHANGELOG.md (+65 -1)
📝 README.md (+63 -3)
📝 scripts/check_drift.py (+52 -6)
📝 src/comfy_low/__init__.py (+2 -1)
📝 src/comfy_low/transport.py (+260 -32)
📝 src/comfy_sdk/__init__.py (+2 -0)
📝 src/comfy_sdk/model_requests.py (+23 -19)
📝 src/comfy_sdk/models.py (+54 -15)
📝 tests/conftest.py (+101 -11)
📝 tests/test_models_queue.py (+44 -1)
📝 tests/test_models_run.py (+2 -2)
➕ tests/test_models_run_binary.py (+491 -0)
📝 tests/test_router_spec_contract.py (+64 -25)

📄 Description

ELI-5

Some models on Comfy Router hand back a file, not a JSON document — the two ElevenLabs models answer a generation as raw audio/mpeg bytes. The SDK assumed every successful response was JSON, tried to decode the MP3 as text, and blew up with ComfyError: Could not decode the 200 response body as JSON. The generation had already run and already been billed; the SDK threw the audio away. Now models.run() looks at the response's Content-Type first: JSON still comes back as a dict exactly as before, and anything else comes back as a BinaryResult you can write straight to a file.

What changed

comfy_low.transport._Prepared.parse_run_result is a new sibling of parse_or_raise, used by post_model_run and nothing else. On a success status it branches on the response Content-Type:

Response says Result
application/json, or a +json suffix type dict — unchanged, including {} for an empty body and invalid_response when the body will not parse
anything else (audio/mpeg, audio/L16; rate=16000, ...) BinaryResult(content, content_type, request_id)
no Content-Type at all {} if the body is empty, dict if it parses as a JSON object, otherwise BinaryResult with content_type=""

BinaryResult is a frozen dataclass exported from both comfy_sdk and comfy_low, exposing exactly the three fields the design called for. The bytes are the partner's file verbatim — not base64-encoded, not wrapped in a dict, not decoded or transcoded. content_type keeps the header's parameters, because for some partner media types the parameters are part of what the bytes are: ElevenLabs' pcm_* output formats arrive as audio/L16; rate=16000 and the sample rate is not decoration. It is bounded to 128 characters and stripped of unprintable characters first — the same filter every other server-supplied string this SDK surfaces goes through — which no real media type is affected by.

Models.run / AsyncModels.run are now annotated dict[str, Any] | BinaryResult, and RouterRunResult.output — the run_detailed shape that landed on main while this branch was open — carries the same union. post_model_run and _run answer tuple[dict[str, Any] | BinaryResult, Mapping[str, str]], which is the merge of the two intents: main made them return (body, headers) so run_detailed can disclose the fallback provider and dropped params, and this branch made the body itself a union. The queued surface main added keeps parse_or_raise, since JSON is the only success media type its routes declare. Nothing moves out of translating(...), so a failure on the binary path still carries .idempotency_key and an Idempotent-Replayed binary 200 comes back the way a first run does.

scripts/check_drift.py and tests/test_router_spec_contract.py also gained a comparison of the media types the run route's 200 declares against the two the SDK branches on — so a future spec sync that drops the */* branch, or adds a third one, fails CI rather than leaving the branch silently dead or silently incomplete.

Chesterton's Fence — the guard this changes, and why the history says it is right

parse_or_raise's "a proxy interstitial served as 200" reading is untouched and still serves every other operation. It also still fires on the run route, for the case it was actually written for: a Content-Type that claims JSON over a body that does not parse. That branch is where the reading belongs, because there the response promised a document and did not deliver one.

What the run route gives up is that reading for a non-JSON media type. The commit that introduced the guard did so to make a lost result translatable and stamped — "on models.run this is a generation that ran and was billed with the result lost, which is exactly the failure the Idempotency-Key has to ride out on." On a binary 200 the result is not lost: it is sitting in the body. So honouring the declared media type serves the original intent rather than working against it, and the SDK cannot distinguish an interstitial from a partner's own text output anyway. A text/html 200 now reaches the caller as bytes they can inspect, which is strictly more information than an exception that discards them.

Self-review notes

  • Riskiest line is if media: ... return self._binary_result(resp) — it is the one place an existing raise became a return. It is bounded to post_model_run: parse_or_raise is byte-for-byte the same behaviour for all eleven other operations, whose routes declare application/json as their only success media type. The branch is on the declared type rather than on whether the bytes parse, so a JSON model cannot fall into it (test_a_json_content_type_with_a_charset_is_still_the_dict_branch, test_a_json_suffix_media_type_is_the_dict_branch), and application/jsonlines is explicitly not JSON — a startswith test would have decoded it as a document.
  • Call sites — post_model_run has exactly two consumers, both in comfy_sdk/models.py, both of which return the transport's value directly. Nothing else in src/ reads a run result, so there is no caller left indexing a dict that might now be a BinaryResult.
  • __repr__ is hand-written on purpose. A frozen dataclass's default repr would print every byte of a multi-megabyte audio file into a traceback, a REPL echo or a CI log. Asserted (test_binary_result_repr_does_not_dump_the_body).
  • Negative-claim falsification does not apply here. The trigger is a diff whose user-facing outcome denies a capability. This diff is the opposite: it deletes a dead-end and makes two previously-unusable models work. No "not supported" / "unavailable" / "STOP" string is added, no throw/deny path is added, and no test was flipped to assert a dead-end — the only raise the run route keeps is the pre-existing JSON-that-will-not-parse one.
  • Typed union is a source-compatible but type-checking-visible change. A caller who only uses JSON models sees no behaviour change at runtime; a caller running mypy/pyright against models.run will now be asked to narrow. That is deliberate — it is the point of the annotation — and the SDK is pre-1.0, so it is noted in the changelog under Added rather than gated behind a flag. There is no runtime flag mechanism in this SDK, and the current behaviour is an exception rather than a contract anyone can depend on.

How this was verified

  • The vendored spec was checked against upstream rather than re-synced blindly. spec/router-openapi.yaml is byte-identical to the canonical Router contract it is vendored from (diff returned no output), and the */* format: binary branch on runRouterModel's 200 is already present there — it landed in the last sync. So there is no spec diff in this PR; the file is already current, and the new drift check is what keeps it honest from here.
  • The per-model contract was read, not assumed. The published per-model schema for elevenlabs/eleven_v3 declares its 200 as content: {"*/*": {type: string, format: binary}} with the description "Raw audio bytes. The Content-Type and encoding follow the requested output_format and are forwarded from ElevenLabs."
  • Corpus sweep of the half not being changed. All 207 published per-model Router schemas were fetched and their runRouterModel 200 content maps inspected: 2 declare a format: binary 200 (elevenlabs/eleven_v3, elevenlabs/eleven_sfx_v2) and the remaining 205 are application/json-only. Those 205 are exactly the models whose result shape this PR must not change, and the JSON branch is unchanged for all of them.
  • Full suite after merging main and applying the review fixes: pytest — 1034 passed, 9 skipped (56 of the passing tests are the binary-200 file). ruff check . clean, ruff format --check . clean (58 files), mypy src clean (21 files), scripts/check_drift.py all three checks OK, scripts/check_public_repo_hygiene.py OK. All 12 PR checks green on 3ab6d45, including the 3.10–3.13 matrix.

Residual

Not verified against the live service. Every test here drives the repo's stdlib stub server. The reported repro — client.models.run("elevenlabs/eleven_v3", ...) against production Router — was not run, because a live run is a billed generation against a paid partner API and this environment holds no credential for it. The stub reproduces the reported failure shape exactly (200, audio/mpeg, an ID3 header followed by a 0xff 0xfb MP3 frame sync — the same 0xff the original UnicodeDecodeError named), and the server contract was verified statically as described above, but nobody has yet confirmed end to end that a real eleven_v3 call returns audio through this SDK. A human with a Router credential should make one live eleven_v3 call before this is treated as closed.

elevenlabs/eleven_sfx_v2 is untouched and still expected to fail. It is one of the two binary models and it is named in the report, but its failure is a 422 on the Router route itself — a server-side binding bug tracked separately, not something this SDK change can fix. This PR makes the SDK ready for it; the model will still not run until the server side lands. eleven_v3 is the one to verify against in the meantime.

Buffered, not streamed. The whole binary body is read into memory (resp.content) before BinaryResult is constructed. That matches what raw_request already did for every other operation and was explicitly out of scope, but it means a large binary result is fully resident in the client process. Raised by three of the six panel reviewers and deferred to a tracked follow-up rather than dropped: closing it means adding a new public shape (a streaming accessor over the existing open() hatch) and settling how it interacts with a retry loop that cannot rewind a partly-consumed stream. The follow-up separates the shippable half (a configurable body-size cap) from the half that needs a design call.

No decoding or transcoding. The SDK hands back bytes and a content type and nothing more. Deliberate, and stated here so it is not mistaken for an oversight.

Sibling SDKs are unfixed. The same JSON-only assumption is present in the TypeScript SDK (src/sdk/models.ts) and the Go SDK (comfylow/transport.go's PostModelRun, which returns map[string]any). Both are separate repositories and separate tickets; nothing here changes them, so the two ElevenLabs models remain unusable from those two SDKs.

Artifacts named in the report that could not be read. The originating trace lives in an internal observability tool that is not reachable from this environment, and the sibling/related issues referenced in the report were named to me by identifier only — their bodies were never fetched and are not reachable from here. None of them is quoted or linked in this PR, since this repository is public. The conclusions above rest on the two artifacts that were read directly: the upstream spec file and the per-model schema.

Provenance

  • Authored by: agent-work loop
  • Verified: on 3ab6d45 (merge of main + the review fixes) — pytest: 1034 passed, 9 skipped; ruff check .: clean; ruff format --check .: 58 files already formatted; mypy src: no issues in 21 source files; scripts/check_drift.py: 3/3 OK; scripts/check_public_repo_hygiene.py: OK; all 12 PR checks green including the 3.10–3.13 matrix, codegen drift and build-check. From the opening run, unchanged by the merge: upstream spec diff byte-identical; 207-schema corpus sweep 2 binary / 205 JSON-only
  • Deviations: the branch was behind main, which had meanwhile changed post_model_run/_run to return (body, headers) for run_detailed — merged in a26ed9a, resolving that against this branch's dict | BinaryResult by carrying both (no test of either side was weakened); the panel's X-Comfy-Request-Id gate and the empty-binary-body raise were declined rather than applied, with the reasoning on the threads and now in the code, because both would discard a billed generation when an intermediary strips a header or a partner serves an empty file; the streaming/size-cap finding was deferred to a follow-up; no live call was made against production Router (billed generation, no credential in this environment) — see ## Residual; spec/router-openapi.yaml is not modified because it was verified already byte-identical to upstream, so the "re-sync" acceptance item is satisfied by verification rather than by a diff

Summary by CodeRabbit

  • New Features

    • Model runs now support non-JSON successful responses, returning raw bytes with content type and request ID metadata.
    • Added the public BinaryResult type for accessing binary response data.
    • JSON responses continue to return dictionaries.
    • Synchronous and asynchronous model-run APIs support both response formats.
  • Bug Fixes

    • Non-JSON successful responses, including HTML, are no longer incorrectly rejected.
    • Responses without a content type are handled based on their content.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/Comfy-Org/comfy-python-sdk/pull/145 **Author:** [@mattmillerai](https://github.com/mattmillerai) **Created:** 9/11/2026 **Status:** ✅ Merged **Merged:** 10/3/2026 **Merged by:** [@mattmillerai](https://github.com/mattmillerai) **Base:** `main` ← **Head:** `matt/be-13346-binary-model-run-result` --- ### 📝 Commits (5) - [`f566a9e`](https://github.com/Comfy-Org/comfy-python-sdk/commit/f566a9e20ec8401f28df635b4ef9c253c25733a9) fix: return a model run's native bytes instead of raising on a non-JSON 200 - [`a26ed9a`](https://github.com/Comfy-Org/comfy-python-sdk/commit/a26ed9a958a0d20645412dd312501e36ab1e8b0f) Merge branch 'main' into matt/be-13346-binary-model-run-result - [`3ab6d45`](https://github.com/Comfy-Org/comfy-python-sdk/commit/3ab6d456d2813a64ff79d2e198d35919bddcc238) fix(models): harden the run-result branch against the review findings - [`1563c21`](https://github.com/Comfy-Org/comfy-python-sdk/commit/1563c2160440da2cbc30f7905b84f231c8803adf) merge main into matt/be-13346-binary-model-run-result, resolve conflicts - [`943f8ac`](https://github.com/Comfy-Org/comfy-python-sdk/commit/943f8ac4e1b1f2ff74315cb94d9f9e80b6292f6f) fix(models): return BinaryResult from the queued result route too ### 📊 Changes **13 files changed** (+1223 additions, -116 deletions) <details> <summary>View changed files</summary> 📝 `CHANGELOG.md` (+65 -1) 📝 `README.md` (+63 -3) 📝 `scripts/check_drift.py` (+52 -6) 📝 `src/comfy_low/__init__.py` (+2 -1) 📝 `src/comfy_low/transport.py` (+260 -32) 📝 `src/comfy_sdk/__init__.py` (+2 -0) 📝 `src/comfy_sdk/model_requests.py` (+23 -19) 📝 `src/comfy_sdk/models.py` (+54 -15) 📝 `tests/conftest.py` (+101 -11) 📝 `tests/test_models_queue.py` (+44 -1) 📝 `tests/test_models_run.py` (+2 -2) ➕ `tests/test_models_run_binary.py` (+491 -0) 📝 `tests/test_router_spec_contract.py` (+64 -25) </details> ### 📄 Description ## ELI-5 Some models on Comfy Router hand back a *file*, not a JSON document — the two ElevenLabs models answer a generation as raw `audio/mpeg` bytes. The SDK assumed every successful response was JSON, tried to decode the MP3 as text, and blew up with `ComfyError: Could not decode the 200 response body as JSON`. The generation had already run and already been billed; the SDK threw the audio away. Now `models.run()` looks at the response's `Content-Type` first: JSON still comes back as a `dict` exactly as before, and anything else comes back as a `BinaryResult` you can write straight to a file. ## What changed `comfy_low.transport._Prepared.parse_run_result` is a new sibling of `parse_or_raise`, used by `post_model_run` and nothing else. On a success status it branches on the response `Content-Type`: | Response says | Result | |---|---| | `application/json`, or a `+json` suffix type | `dict` — unchanged, including `{}` for an empty body and `invalid_response` when the body will not parse | | anything else (`audio/mpeg`, `audio/L16; rate=16000`, ...) | `BinaryResult(content, content_type, request_id)` | | no `Content-Type` at all | `{}` if the body is empty, `dict` if it parses as a JSON **object**, otherwise `BinaryResult` with `content_type=""` | `BinaryResult` is a frozen dataclass exported from both `comfy_sdk` and `comfy_low`, exposing exactly the three fields the design called for. The bytes are the partner's file verbatim — not base64-encoded, not wrapped in a dict, not decoded or transcoded. `content_type` keeps the header's *parameters*, because for some partner media types the parameters are part of what the bytes are: ElevenLabs' `pcm_*` output formats arrive as `audio/L16; rate=16000` and the sample rate is not decoration. It is bounded to 128 characters and stripped of unprintable characters first — the same filter every other server-supplied string this SDK surfaces goes through — which no real media type is affected by. `Models.run` / `AsyncModels.run` are now annotated `dict[str, Any] | BinaryResult`, and `RouterRunResult.output` — the `run_detailed` shape that landed on `main` while this branch was open — carries the same union. `post_model_run` and `_run` answer `tuple[dict[str, Any] | BinaryResult, Mapping[str, str]]`, which is the merge of the two intents: `main` made them return `(body, headers)` so `run_detailed` can disclose the fallback provider and dropped params, and this branch made the body itself a union. The queued surface `main` added keeps `parse_or_raise`, since JSON is the only success media type its routes declare. Nothing moves out of `translating(...)`, so a failure on the binary path still carries `.idempotency_key` and an `Idempotent-Replayed` binary 200 comes back the way a first run does. `scripts/check_drift.py` and `tests/test_router_spec_contract.py` also gained a comparison of the media types the run route's 200 declares against the two the SDK branches on — so a future spec sync that drops the `*/*` branch, or adds a third one, fails CI rather than leaving the branch silently dead or silently incomplete. ## Chesterton's Fence — the guard this changes, and why the history says it is right `parse_or_raise`'s "a proxy interstitial served as 200" reading is untouched and still serves every other operation. It also still fires on the run route, for the case it was actually written for: a `Content-Type` that claims JSON over a body that does not parse. That branch is where the reading belongs, because there the response promised a document and did not deliver one. What the run route gives up is that reading for a non-JSON *media type*. The commit that introduced the guard did so to make a lost result **translatable and stamped** — "on `models.run` this is a generation that ran and was billed with the result lost, which is exactly the failure the `Idempotency-Key` has to ride out on." On a binary 200 the result is not lost: it is sitting in the body. So honouring the declared media type serves the original intent rather than working against it, and the SDK cannot distinguish an interstitial from a partner's own text output anyway. A `text/html` 200 now reaches the caller as bytes they can inspect, which is strictly more information than an exception that discards them. ## Self-review notes - **Riskiest line** is `if media: ... return self._binary_result(resp)` — it is the one place an existing `raise` became a `return`. It is bounded to `post_model_run`: `parse_or_raise` is byte-for-byte the same behaviour for all eleven other operations, whose routes declare `application/json` as their only success media type. The branch is on the *declared* type rather than on whether the bytes parse, so a JSON model cannot fall into it (`test_a_json_content_type_with_a_charset_is_still_the_dict_branch`, `test_a_json_suffix_media_type_is_the_dict_branch`), and `application/jsonlines` is explicitly not JSON — a `startswith` test would have decoded it as a document. - **Call sites** — `post_model_run` has exactly two consumers, both in `comfy_sdk/models.py`, both of which return the transport's value directly. Nothing else in `src/` reads a run result, so there is no caller left indexing a `dict` that might now be a `BinaryResult`. - **`__repr__` is hand-written on purpose.** A frozen dataclass's default repr would print every byte of a multi-megabyte audio file into a traceback, a REPL echo or a CI log. Asserted (`test_binary_result_repr_does_not_dump_the_body`). - **Negative-claim falsification does not apply here.** The trigger is a diff whose user-facing outcome *denies* a capability. This diff is the opposite: it deletes a dead-end and makes two previously-unusable models work. No "not supported" / "unavailable" / "STOP" string is added, no throw/deny path is added, and no test was flipped to assert a dead-end — the only `raise` the run route keeps is the pre-existing JSON-that-will-not-parse one. - **Typed union is a source-compatible but type-checking-visible change.** A caller who only uses JSON models sees no behaviour change at runtime; a caller running mypy/pyright against `models.run` will now be asked to narrow. That is deliberate — it is the point of the annotation — and the SDK is pre-1.0, so it is noted in the changelog under Added rather than gated behind a flag. There is no runtime flag mechanism in this SDK, and the current behaviour is an exception rather than a contract anyone can depend on. ## How this was verified - **The vendored spec was checked against upstream rather than re-synced blindly.** `spec/router-openapi.yaml` is **byte-identical** to the canonical Router contract it is vendored from (`diff` returned no output), and the `*/*` `format: binary` branch on `runRouterModel`'s 200 is already present there — it landed in the last sync. So there is no spec diff in this PR; the file is already current, and the new drift check is what keeps it honest from here. - **The per-model contract was read, not assumed.** The published per-model schema for `elevenlabs/eleven_v3` declares its 200 as `content: {"*/*": {type: string, format: binary}}` with the description "Raw audio bytes. The Content-Type and encoding follow the requested output_format and are forwarded from ElevenLabs." - **Corpus sweep of the half not being changed.** All **207** published per-model Router schemas were fetched and their `runRouterModel` 200 content maps inspected: **2** declare a `format: binary` 200 (`elevenlabs/eleven_v3`, `elevenlabs/eleven_sfx_v2`) and the remaining **205** are `application/json`-only. Those 205 are exactly the models whose result shape this PR must not change, and the JSON branch is unchanged for all of them. - Full suite after merging `main` and applying the review fixes: `pytest` — **1034 passed, 9 skipped** (56 of the passing tests are the binary-200 file). `ruff check .` clean, `ruff format --check .` clean (58 files), `mypy src` clean (21 files), `scripts/check_drift.py` all three checks OK, `scripts/check_public_repo_hygiene.py` OK. All 12 PR checks green on `3ab6d45`, including the 3.10–3.13 matrix. ## Residual **Not verified against the live service.** Every test here drives the repo's stdlib stub server. The reported repro — `client.models.run("elevenlabs/eleven_v3", ...)` against production Router — was **not** run, because a live run is a billed generation against a paid partner API and this environment holds no credential for it. The stub reproduces the reported failure shape exactly (200, `audio/mpeg`, an ID3 header followed by a `0xff 0xfb` MP3 frame sync — the same `0xff` the original `UnicodeDecodeError` named), and the server contract was verified statically as described above, but nobody has yet confirmed end to end that a real `eleven_v3` call returns audio through this SDK. **A human with a Router credential should make one live `eleven_v3` call before this is treated as closed.** **`elevenlabs/eleven_sfx_v2` is untouched and still expected to fail.** It is one of the two binary models and it is named in the report, but its failure is a `422` on the Router route itself — a server-side binding bug tracked separately, not something this SDK change can fix. This PR makes the SDK ready for it; the model will still not run until the server side lands. `eleven_v3` is the one to verify against in the meantime. **Buffered, not streamed.** The whole binary body is read into memory (`resp.content`) before `BinaryResult` is constructed. That matches what `raw_request` already did for every other operation and was explicitly out of scope, but it means a large binary result is fully resident in the client process. Raised by three of the six panel reviewers and **deferred to a tracked follow-up** rather than dropped: closing it means adding a new public shape (a streaming accessor over the existing `open()` hatch) and settling how it interacts with a retry loop that cannot rewind a partly-consumed stream. The follow-up separates the shippable half (a configurable body-size cap) from the half that needs a design call. **No decoding or transcoding.** The SDK hands back bytes and a content type and nothing more. Deliberate, and stated here so it is not mistaken for an oversight. **Sibling SDKs are unfixed.** The same JSON-only assumption is present in the TypeScript SDK (`src/sdk/models.ts`) and the Go SDK (`comfylow/transport.go`'s `PostModelRun`, which returns `map[string]any`). Both are separate repositories and separate tickets; nothing here changes them, so the two ElevenLabs models remain unusable from those two SDKs. **Artifacts named in the report that could not be read.** The originating trace lives in an internal observability tool that is not reachable from this environment, and the sibling/related issues referenced in the report were named to me by identifier only — their bodies were never fetched and are not reachable from here. None of them is quoted or linked in this PR, since this repository is public. The conclusions above rest on the two artifacts that *were* read directly: the upstream spec file and the per-model schema. ## Provenance - **Authored by:** agent-work loop - **Verified:** on `3ab6d45` (merge of `main` + the review fixes) — `pytest`: 1034 passed, 9 skipped; `ruff check .`: clean; `ruff format --check .`: 58 files already formatted; `mypy src`: no issues in 21 source files; `scripts/check_drift.py`: 3/3 OK; `scripts/check_public_repo_hygiene.py`: OK; all 12 PR checks green including the 3.10–3.13 matrix, codegen drift and build-check. From the opening run, unchanged by the merge: upstream spec diff byte-identical; 207-schema corpus sweep 2 binary / 205 JSON-only - **Deviations:** the branch was behind `main`, which had meanwhile changed `post_model_run`/`_run` to return `(body, headers)` for `run_detailed` — merged in `a26ed9a`, resolving that against this branch's `dict | BinaryResult` by carrying both (no test of either side was weakened); the panel's `X-Comfy-Request-Id` gate and the empty-binary-body raise were **declined** rather than applied, with the reasoning on the threads and now in the code, because both would discard a billed generation when an intermediary strips a header or a partner serves an empty file; the streaming/size-cap finding was deferred to a follow-up; no live call was made against production Router (billed generation, no credential in this environment) — see `## Residual`; `spec/router-openapi.yaml` is not modified because it was verified already byte-identical to upstream, so the "re-sync" acceptance item is satisfied by verification rather than by a diff <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Model runs now support non-JSON successful responses, returning raw bytes with content type and request ID metadata. - Added the public `BinaryResult` type for accessing binary response data. - JSON responses continue to return dictionaries. - Synchronous and asynchronous model-run APIs support both response formats. - **Bug Fixes** - Non-JSON successful responses, including HTML, are no longer incorrectly rejected. - Responses without a content type are handled based on their content. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-31 22:01:46 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Comfy-Org/comfy-python-sdk#145
No description provided.