fix: detect Anthropic wire from the model adapter
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:
model.api.npm === "@ai-sdk/anthropic"
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
- One capability is resolved for the whole transform pass and passed to every sentinel consumer
- A new custom-provider capability folds the m[0] baseline once before it changes served bytes
- An unresolved registry carries forward the prior answer only for the same model
- Provider-list lookups have a five-second limit and retry after a cooldown
Rust mode remains limited to the canonical anthropic provider. Its module applies its own provider-id checks. Pi does not use these OpenCode paths. Both limits are documented in A55 of docs/AUDIT-KNOWN-ISSUES.md.
Validation
- Verified a live
opencode servereports the custom Portkey Claude models withapi.npm: "@ai-sdk/anthropic" - Verified the shipped OpenCode binary filters empty parts in the
@ai-sdk/anthropicadapter branch - Added unit coverage for custom providers, mixed adapters, resolver states, execute-to-defer byte stability, and the one-time m[0] fold
bun 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 key
I also ran the full e2e suite. It reports 9 failures. Each reproduces on unmodified master.
Need help on this PR? Tag @codesmith-bot with 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/anthropic adapter (e.g., Bedrock/Vertex gateways) no longer send thinking blocks Anthropic rejects. The gate now reads each model's resolved adapter from provider.list() instead of using providerID === "anthropic".
Bug Fixes
- Resolves the capability per model because a provider can mix adapters across its models.
- Normalizes persisted sentinels when the model's resolved capability changes, so stale empty parts or placeholders don't survive a switch.
- Memoizes successful registry loads and retries after a cooldown, with a five-second limit.
- Records the widened capability in the m[0] upgrade identity so it folds exactly once, preserving byte stability.
- Keeps Rust mode limited to the canonical
anthropicprovider and documents the limitation in A55.
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/anthropic to receive Anthropic-safe reasoning and placeholder transformations.
- Adds a process-level, retryable provider-registry lookup with per-model adapter detection.
- Carries the resolved capability consistently through stripping, compartment injection, and postprocessing.
- Records custom-provider widening in the m[0] upgrade identity to materialize changed wire bytes once.
- Normalizes persisted whole-message sentinels when the active model’s wire capability changes.
- Adds coverage for custom and mixed-adapter providers, unresolved registry states, replay stability, and model transitions.
- Documents the intentional canonical-provider limitation in Rust mode.
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
| Filename | Overview |
|---|---|
| packages/plugin/src/hooks/magic-context/anthropic-wire.ts | Adds bounded, retryable per-model detection of OpenCode’s Anthropic adapter and stable unresolved-state handling. |
| packages/plugin/src/hooks/magic-context/transform.ts | Resolves the wire capability once per transform pass and propagates it through all relevant phases. |
| packages/plugin/src/hooks/magic-context/inject-compartments.ts | Folds custom-provider capability changes into m[0] only when the live model is known and unchanged. |
| packages/plugin/src/hooks/magic-context/sentinel.ts | Replays persisted sentinels in the representation required by the currently resolved wire capability. |
| packages/plugin/src/hooks/magic-context/transform-postprocess-phase.ts | Uses the pass-level capability for replay and detection while preserving cache-busting boundaries. |
| packages/plugin/src/hooks/magic-context/compartment-render-epoch.ts | Extends the persisted upgrade identity with the custom-provider Anthropic-wire widening state. |
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