fix: detect Anthropic wire from the model adapter - #434
Conversation
OpenCode filters empty text/reasoning parts off the wire in exactly one branch, gated on `model.api.npm === "@ai-sdk/anthropic"`. Magic Context gated the machinery that depends on that filter — the merged-reasoning strip, cleared-reasoning strip, structural-noise strip, whole-message placeholders — on `providerID === "anthropic"` instead. A custom provider declaring `npm: "@ai-sdk/anthropic"` (a Bedrock or Vertex gateway under its own id) therefore rode the Anthropic wire with none of the workarounds it needs, and Anthropic rejected the request: "`thinking` or `redacted_thinking` blocks in the latest assistant message cannot be modified." One provider can also serve different models through different adapters, so the answer has to be per model, not per provider. `anthropic-wire.ts` reads the resolved adapter of every configured model once per process from `client.provider.list()`, and `resolveEmptySentinelCapability` turns that into one boolean for the pass. Every consumer now takes that boolean instead of a provider string, so no two phases of a pass can disagree and oscillate the cached prefix. Cache safety: - An unresolved model or registry is NOT read as "not widened". It carries forward what the session last materialized under, keyed to the model. Narrowing would stop replaying merged-reasoning strips that are already persisted and put signed thinking back on the wire. - A real change is recorded in the m[0] upgrade identity, so it folds exactly once instead of rewriting the tail on a pass that promised byte-identical replay. Scoped to a known, unchanged model: an unknown signal never folds, and a changed model already folds via `model_change`. - A failed lookup is not memoised. It retries after a cooldown, bounded by a 5s deadline so a hung local request cannot stall the transform. Rust mode stays canonical-Anthropic only, because the module keeps its own provider-id gates and both lanes must agree on the served bytes. Pi is unaffected. Both are tracked in docs/AUDIT-KNOWN-ISSUES.md A55 with the full state table.
There was a problem hiding this comment.
All reported issues were addressed across 20 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
|
Would be great to see this fixed |
|
Note for @alfonso-magic-context There are providers/models using @ai-sdk/anthropic but are not actually anthropic models. (model.api.npm === "@ai-sdk/anthropic" may be wrong for those and we may need to check model itself) |
|
Six-axis review on Verified good.
Key shape — per @ualtinok's note above. Providers exist that speak
One blocker. Wording. A55 overstates parity: Pi passes only With the two-key split and the await moved out of the pass this is mergeable. |
Summary
Magic Context used
providerID === "anthropic"to decide whether it could use empty-part sentinels. OpenCode makes that decision from the resolved model adapter instead:This matters for custom providers. A Portkey gateway can use the Anthropic adapter under a provider id such as
portkey. The gateway receives Anthropic-format messages, but Magic Context previously skipped the merged-reasoning strip. Anthropic can then reject a long session because a thinking block in the latest assistant message changed.This change reads each model's resolved adapter from
provider.list(). It enables the sentinel paths only for models on@ai-sdk/anthropic, including custom provider ids. The lookup is per model because a provider can mix adapters across its models.Cache safety
Rust mode remains limited to the canonical
anthropicprovider. Its module applies its own provider-id checks. Pi does not use these OpenCode paths. Both limits are documented in A55 ofdocs/AUDIT-KNOWN-ISSUES.md.Validation
opencode servereports the custom Portkey Claude models withapi.npm: "@ai-sdk/anthropic"@ai-sdk/anthropicadapter branchbun run typecheck, plugin lint, build, and the thinking-block e2e test passpackages/pi-plugintests: 1027 passpackages/plugintests: 4644 pass; 2 existingdreamer/verifytests fail because temporary test repositories inherit SSH signing without a keyI also ran the full e2e suite. It reports 9 failures. Each reproduces on unmodified
master.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Summary by cubic
Fixes Magic Context's empty-part sentinel gating so custom providers that use the
@ai-sdk/anthropicadapter (e.g., Bedrock/Vertex gateways) no longer send thinking blocks Anthropic rejects. The gate now reads each model's resolved adapter fromprovider.list()instead of usingproviderID === "anthropic".Bug Fixes
anthropicprovider and documents the limitation in A55.Written for commit 39dab68. Summary will update on new commits.
Greptile Summary
This PR resolves empty-sentinel support from each model’s OpenCode adapter rather than its provider ID, allowing custom providers using
@ai-sdk/anthropicto receive Anthropic-safe reasoning and placeholder transformations.Confidence Score: 5/5
The PR appears safe to merge; the latest changes correctly normalize persisted sentinels across model capabilities without bypassing the existing materialization gates.
No actionable new defects or outstanding previous findings were established. Model recovery seeds the shared live-model map before hard-signal evaluation, keeping adapter capability resolution, model-change detection, and m[0] folding aligned.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[Transform pass starts] --> B[Resolve live model] B --> C[Load provider registry] C --> D{api.npm is @ai-sdk/anthropic?} D -->|Canonical or matching custom model| E[Enable empty sentinels] D -->|Other adapter| F[Use dropped placeholder] C -->|Temporarily unresolved| G{Same cached model?} G -->|Yes| H[Carry prior widened capability] G -->|No| F E --> I[Compare m0 adapter identity] F --> I H --> I I --> J{Provider-visible capability changed?} J -->|Yes, same known model| K[Materialize and persist new baseline] J -->|No| L[Replay frozen decisions] K --> L L --> M[Strip reasoning and normalize sentinels consistently]Reviews (2): Last reviewed commit: "fix: normalize sentinels after model cha..." | Re-trigger Greptile