Skip to content

fix: opencode zen gateway compat (muse-spark 1.3 via opencode-go sub) - #46

Merged
Blankeos merged 1 commit into
mainfrom
fix/opencode-zen-gateway-compat
Sep 3, 2026
Merged

fix: opencode zen gateway compat (muse-spark 1.3 via opencode-go sub)#46
Blankeos merged 1 commit into
mainfrom
fix/opencode-zen-gateway-compat

Conversation

@Blankeos

@Blankeos Blankeos commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Makes muse-spark-1.3-contributor usable on crabcode through the opencode-go subscription gateway (https://opencode.ai/zen/go/v1). Three request-path bugs, all gateway-specific — no behavior change for first-party providers (OpenAI/Anthropic/xAI mint unique ids and versionless base URLs, so the new code paths are no-ops for them).

What broke

Session xcx605ghifzhcdkqvil5wwwy died on step 1 with:

Duplicate function_call_output for call_id 'call_1'. Each function_call must have exactly one matching function_call_output

plus two latent gateway issues found while pairing on the session: a /v1/v1/responses 404 and a hollow catalog entry from the Failed to deserialize catalog extension model {provider_id}/{model_id} log line.

Changes

  • Responses duplicate call_ids (src/aisdk/providers/openai.rs): the zen relay mints sequential per-request ids (call_1, call_2, … restarting every request), so after two tool turns the replayed history repeats ids and the Responses validator 400s. New dedupe_responses_call_ids pass in build_openai_messages (single choke point for HTTP + websocket bodies) rewrites repeats to {id}_dupN on both the function_call and its matching output (occurrence-indexed pairing), dropping stale response item.ids on rewritten calls. First occurrence untouched.
  • Duplicated version segment (src/aisdk/providers/{mod,openai,anthropic,compatible}.rs): shared base_url_has_version_segment helper; default Responses path becomes /responses (not /v1/responses) and Anthropic /messages (not /v1/messages) when the base URL already carries /vN. compatible.rs now reuses the helper instead of its local copy.
  • Catalog extension patch-fragments (src/model/extensions/mod.rs): a {"attachment": true}-style fragment for a model absent from models.dev no longer fabricates a hollow entry with wrong defaults (tool_call=false, no limits/cost); it's skipped with a descriptive log (also fixes the unformatted {provider_id}/{model_id} log line) until models.dev carries the model or the spec is completed.

Verification

  • New tests: dedupes_repeated_call_ids_across_turns_for_responses_input, default_responses_path_does_not_duplicate_v1_in_base_url, messages_url_does_not_duplicate_version_segment, detects_version_segment_in_base_url, catalog_extensions_patch_fragment_for_unknown_model_is_skipped
  • cargo test --bin crabcode aisdk::providers → 88 passed; model::extensions → 19 passed
  • cargo fmt clean, scripts/check-aisdk-boundary.sh ok
  • Cross-checked against .devrefs: neither opencode (packages/llm/src/protocols/openai-responses.ts) nor grok-build (xai-grok-sampling-types/.../responses.rs) needs this because first-party backends mint globally-unique ids — this is purely a relay-compat shim.

Follow-ups (not in scope)

  • Stored history still holds duplicate ids; UI pairing (src/llm/client.rs has_call) drops the 2nd call_1. Consider uniquifying on ingest.
  • Same per-request-id relay over the Chat Completions path would still send dupes; dedupe there if it surfaces.

- openai responses: don't duplicate /v1 when base URL already has version segment
- openai responses: dedupe repeated call_ids across turns (relay mints per-request call_1..)
- anthropic: same no-dup-version-segment handling for /messages endpoint
- compatible: share base_url_has_version_segment helper from providers/mod
- catalog extensions: skip patch-fragments for unknown models instead of fabricating hollow entries
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying crabcode with  Cloudflare Pages  Cloudflare Pages

Latest commit: b8a552d
Status: ✅  Deploy successful!
Preview URL: https://bbc13d59.crabcode.pages.dev
Branch Preview URL: https://fix-opencode-zen-gateway-com.crabcode.pages.dev

View logs

@Blankeos

Blankeos commented Sep 3, 2026

Copy link
Copy Markdown
Owner Author

PR Review — fix/opencode-zen-gateway-compat -> main

PR: #46 — "fix: opencode zen gateway compat (muse-spark 1.3 via opencode-go sub)"
URL: #46
Scope: 6 files, +288/−37 (1 commit b8a552d)

Merge confidence: 4/5

Suggested PR comment body

Gateway-compat shim for muse-spark via opencode-go relay: dedupes per-request Responses call_ids (call_1…), stops doubling /v1 on versioned base URLs (Responses + Anthropic + compatible), and skips incomplete catalog patch-fragments with a clearer log. Scoped to gateway paths; first-party providers unaffected. Tests pass (88 providers + 19 extensions), fmt + aisdk boundary clean. One nit: revert the stray _plans/__TODOS.md hunk before merge.

What I checked (read-only)

  • gh pr view, git diff main...HEAD, git status
  • cargo fmt --check → clean (exit 0)
  • scripts/check-aisdk-boundary.sh → ok
  • cargo test --bin crabcode aisdk::providers → 88 passed, 0 failed ✅
  • cargo test --bin crabcode model::extensions → 19 passed, 0 failed ✅
  • Read src/aisdk/providers/mod.rs:20, src/aisdk/providers/openai.rs:198, src/aisdk/providers/openai.rs:2355, src/aisdk/providers/anthropic.rs:742, src/aisdk/providers/compatible.rs:119, src/model/extensions/mod.rs:304, src/model/discovery.rs:64

Changes reviewed

  1. Responses call_id dedupe (src/aisdk/providers/openai.rs:2355): occurrence-indexed rewrite to {id}_dupN on repeats, drops stale id on rewritten calls, wired into build_openai_messages (src/aisdk/providers/openai.rs:2344) so HTTP + websocket share it. First occurrence untouched. Correct fix for relay-minted sequential ids.
  2. Version-segment helper (src/aisdk/providers/mod.rs:20): shared base_url_has_version_segment, reused by openai/anthropic/compatible; removes local dup in compatible.rs. Default paths now /responses / /messages on versioned bases, /v1/... otherwise.
  3. Catalog fragment skip (src/model/extensions/mod.rs:304): patch-fragment for unknown model logs and skips instead of inserting. Also fixes unformatted {provider_id}/{model_id} log line. Nuance: src/model/discovery.rs:64 requires id/name (no default), so a bare {"attachment": true} already failed deserialize and skipped under the old let-Ok-else — this change mostly makes the skip explicit with a better log. A fragment with id/name but nothing else would still parse (defaults: tool_call=false, no cost/limits) and still insert — the "hollow entry" hole isn't fully closed.

Regressions?

No regressions found. Risk points (low):

  • Dedupe pairing counts calls and outputs in separate maps, so it assumes occurrence-k call pairs with occurrence-k output. If history ever drops an output (the known has_call drop noted in the PR follow-ups), counts can skew and a renamed call could mismatch its output. Current replay path is fine; just don't rely on this if ingest starts dropping items.
  • Version helper only matches /vN where N is digits followed by / or end — multi-digit (/v10, /v12) is missed by the bytes[i+3] check. No current provider uses that, so nit only.
  • Custom responses_path configs bypass the version logic (explicit path wins) — intended, no regression.

Migrations?

None needed. No persistence/schema changes: no prefs.rs / auth.rs / mcp/credentials.rs / models.dev cache format changes, no SQL, no config contract change. Drop-in code fix.

Checklist before merge

  • Revert stray hunk: _plans/__TODOS.md thinking-effort TODO (unrelated to this PR)
  • Optional: confirm no custom responses_path users depend on the old /v1/v1 behavior (grep configs — default path only affects empty responses_path)
  • Optional: full cargo test --bin crabcode (scoped suites pass; full suite not run here)
  • cargo fmt / boundary already clean — no action

Confidence bumps

  • Revert the _plans/__TODOS.md hunk → 4.5/5 (removes the only merge-hygiene issue; everything else is verified).
  • Plus either (a) a guard/test for the diverged call/output-count edge in dedupe_responses_call_ids, or (b) tightening merge_catalog to reject fragments missing more than id/name (or documenting that id+name fragments are accepted) → 5/5.

@Blankeos
Blankeos merged commit 84c6bb9 into main Sep 3, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant