feat(core,runtime,desktop): declare thinking levels on Anthropic-protocol relays - #3686
feat(core,runtime,desktop): declare thinking levels on Anthropic-protocol relays#3686qian0817 wants to merge 5 commits into
Conversation
68e0f81 to
7c9a037
Compare
…ocol relays An anthropic-compatible relay fronts models the metadata chain cannot resolve (no provider alias exists for it), so its models offered no thinking menu at all — while the declaration mechanism that would fix it was gated OpenAI-relay-only since apache#2463. The gate is now per-field rather than per-provider: thinking-level declarations are legal on all three custom relays, serviceTier stays OpenAI Responses-only. The declarable vocabulary is per provider: `off` joins it only for anthropic-compatible, whose protocol has a true disable wire (`thinking: { type: 'disabled' }`); the OpenAI relays keep their vocabulary unchanged (`off` there encodes `reasoning_effort: 'none'`, which no generic relay is presumed to honor). - `DECLARABLE_RELAY_THINKING_LEVELS` becomes `declarableRelayThinkingLevels(providerType)`; normalize gains an optional provider and stays provider-blind without one (the host-wire decode edge has no provider context — the canonical store codec has already validated fit). - The catalog codec asserts field-level provider fit, and the update path threads providerType through to table decode. - The runtime anthropic wire maps a declared `off` to `thinking.disabled` and other tiers to a passthrough `effort`. - The settings page's thinking menu, bulk control, and draft seed use the per-provider vocabulary (the same controls the OpenAI relays already expose). Older builds reject a document carrying these declarations (same forward-compat posture as apache#3309's serviceTier); SCHEMA_VERSION stays 1, matching the repo convention that value-range growth is not a schema change. Generated-by: Claude Sonnet 4.5 via pi
7c9a037 to
77c3f96
Compare
- packages/core/src/__tests__/model-thinking.test.ts: apply biome formatting
|
I reviewed this PR at exact head Spec: NO-GO — 1×P1 + 1×P2
Positives: provider-aware create/update/draft/bulk plumbing is consistent; Anthropic Standards: 1×P3
Other checks: Hosted checks: What I did not check: full local suite beyond the checks noted. Gate: exact head has P1/P2 findings and
|
Address PR apache#3686 review (1xP1 + 1xP2 + 1xP3): P1 — Anthropic relay vocabulary exposed an SDK-invalid value. The relay's declared levels are emitted as providerOptions.anthropic.effort, which @ai-sdk/anthropic 4.0.40 parses through a closed low|medium|high|xhigh|max enum before any request; "minimal" would throw InvalidArgumentError locally. Exclude "minimal" from the anthropic-compatible vocabulary in declarableRelayThinkingLevels, add codec rejection + normalize-filter tests, and extend the runtime relay test to assert every declarable level maps 1:1 onto the SDK's effort enum. P2 — serviceTier persistence gate was too wide. The codec accepted serviceTier on both OpenAI relay kinds, but the read seam (supportsRelayFastServiceTier) is Responses-only: a Chat Completions relay declaration was durable dead state. Narrow assertProfileFieldsFitProvider to openai-responses-compatible and add create/update/canonical negatives for the Chat relay. P3 — docs/contracts still claimed profiles are OpenAI-relay-only after this PR made them legal on all three custom relays. Update the stale wording in llm-connections.ts, model-thinking.ts, provider-connection-detail.tsx, and use-connection-detail.ts.
|
All three findings verified against the codebase and fixed in 3ae458f (+ formatting commit d908c3488). P1 — confirmed, fixed. Verified P2 — confirmed, fixed. Verified P3 — confirmed, fixed. All five stale "custom OpenAI relay" / "OpenAI-compatible relay" wordings updated to the custom-relay (OpenAI chat/responses + Anthropic protocol) scope: Verification: core 658/658, runtime 3038 pass / 0 fail, desktop main 1512 pass / 0 fail (8 cancelled in mcp-oauth-controller.test.js reproduce on that file in isolation pre-change), typecheck, biome format:check, and lint all clean. |
…rn assertion The focused-turn eviction test flaked in CI (run 33033873117, PR apache#3686): after scrollTranscriptTo(bottom), turn-prompt-rail-120 never mounted within the 10s toHaveCount window. This is the same timing family as apache#3121, which established the jump races the virtualizer's scroll-anchor restore: the window settles against estimated-height spacers before the tail turn mounts, and with no further scroll event it stays settled. The 2e3c810 guard covered only the second-half (top) jump. Re-assert the bottom scroll once the first paint lands and dispatch the scroll event the window recompute listens for — the same mechanism the second half already uses. A real regression (the tail turn never mounting at the bottom) still fails the assertion.
…undtrip budget The workhub-layout spec flaked in CI (run 33035109906, PR apache#3686): the .workhub-result panel never became visible within the default 10s after submitting into the WorkHub composer. The app code at that head is byte- identical to the previous head where the test passed (the intervening commit only touched another spec file), so this is timing, not a regression. The result settles through the same model roundtrip the spec's first submit waits 20s for (line 29), plus WorkHub routing and projection refresh on top; workhub-reconstruction.spec.ts gives the same class of assertion 20s. Align this one with that budget instead of the 10s default.
|
This is a synthesis of the independent blind review by @Sol-404ARE at exact head What I checked myself:
Findings from Sol's review (file:line anchored): Spec — 1×P2 (implementation is correct; the contract text is stale)
Old findings closed:
Standards — GO (0 hard findings)
What I did not judge:
Gate: the secured implementation itself is correct and
|
Summary
An
anthropic-compatiblerelay fronts models the built-in metadata chain cannot resolve (no provider alias exists for it), so its models offered no thinking menu at all — while the per-model declaration mechanism that would fix it (relayModelProfiles.thinkingLevels, #2463) was gated OpenAI-relay-only.This PR opens that gate for
anthropic-compatible:serviceTierstays OpenAI Responses-only.anthropic-compatibledeclaresoff|low|medium|high|xhigh|max.offjoins because the protocol has a true disable wire (thinking: { type: 'disabled' });minimalis excluded because declared levels are emitted asproviderOptions.anthropic.effort, and the pinned@ai-sdk/anthropicparses that option through a closed enum (low|medium|high|xhigh|max) before any request —minimalwould throw locally. The OpenAI relays keep their vocabulary unchanged (minimal|low|medium|high|xhigh|max;offthere encodesreasoning_effort: 'none', which no generic relay is presumed to honor).DECLARABLE_RELAY_THINKING_LEVELSbecomesdeclarableRelayThinkingLevels(providerType);normalizeRelayModelProfilesgains an optional provider argument and stays provider-blind without one (the host-wire decode edge has no provider context — the canonical store codec has already validated fit).providerTypethrough to table decode.offtothinking.disabledand each remaining declared tier (low|medium|high|xhigh|max) 1:1 to a passthroughproviderOptions.anthropic.effort.isRelayProviderTypetype predicate now covers all three relay providers.Breaking change (forward compatibility, same posture as #3309's
serviceTier): older builds reject a catalog document carrying these declarations.SCHEMA_VERSIONstays 1, matching the repo convention that value-range growth is not a schema change. Worth a release-note line: clear Anthropic-relay thinking declarations before downgrading.Verification
packages/core: clean build + full suite pass (22/22 in the touched suites; per-provider vocabulary, codec field-level fit incl.offaccept/reject on both relay families, update-path positive case)packages/runtime: clean rebuild + full suite pass — 2987 pass / 0 fail (incl. wire-shape tests: declaredoff→thinking.disabled, effort passthrough, undeclared models emit nothing)packages/runtime-host: 1119 pass / 0 failapps/desktop:npm test1340 pass / 0 fail;typecheckclean (incl. draft-seed per-provider sanitation and the bulk-control vocabulary)packages/storage: 903/904 — the single failure (managed-dependency-environment-crash) reproduces on a clean checkout without these changes (environment-sensitive SQLite crash harness), verified via stash comparisonbiome checkclean on all touched filesrgon PATH; test runs in the repo's parallel CI-style harness without it fail onspawn rg ENOENTfor pre-existing environment reasons unrelated to this changeReview focus
The trilayer consistency — vocabulary (
model-thinking.ts) / codec (connection-catalog-codec.ts) / wire (model-factory.ts) — is the invariant to check:offis declarable only where the provider has a true disable wire, and only that provider's runtime branch can emit it;minimalis declarable only where the receiving SDK accepts it, so it stays OpenAI-relay-only.AI use
Select exactly one:
Tool(s) and scope: GLM 5.3 via pi — implementation, tests, and PR description, following a human-approved design (field-level gate, manual declarations only,
offsupported). Commits carry aGenerated-bytrailer.Checklist
Does this PR entail a change in behavior?