You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: devlog/_plan/260802_wt3_provider_wire/000_plan.md
+41-5Lines changed: 41 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,8 +1,17 @@
1
1
# wt3 — Provider wire correctness (research)
2
2
3
-
Worktree: `/Users/jun/.codex/worktrees/260802-wt3-provider-wire` (branch `codex/wt3-provider-wire`, off `dev`).
3
+
Executing worktree: `/Users/jun/.codex/worktrees/8e2b/opencodex` (branch `codex/wt3-exec`, off dev@478354ee8). A spare prepared worktree also exists at `/Users/jun/.codex/worktrees/260802-wt3-provider-wire`.
4
4
Provider-adapter/wire bugs; all must-fix regardless of PR quality.
5
5
6
+
## Roadmap map (work-phase → decade doc)
7
+
8
+
| Work-phase | Bug | Decade doc |
9
+
|------------|-----|------------|
10
+
| wp-a | A — Copilot mixed-wire (#746/#748) |`010_bug_a_copilot_mixed_wire.md`|
| wp-c | C — Claude 1M windows (#839+#854) |`030_bug_c_claude_1m_windows.md`|
13
+
| (follow-up, not this goal) | D — hosted image tools (#616/#837) | to be written when picked up |
14
+
6
15
## Scope
7
16
8
17
### Bug A — PR #746 / issue #748: Copilot Responses-only models routed to chat completions
@@ -13,7 +22,7 @@ Provider-adapter/wire bugs; all must-fix regardless of PR quality.
13
22
14
23
### Bug B — PR #860 (+ issue #875): DeepSeek `service_tier` must be capability-gated
15
24
16
-
- Root cause: `fastMode` injects `service_tier` unconditionally on Responses routes; DeepSeek does not support the field. PR #860 adds a provider-level `supportsServiceTier` capability: canonical OpenAI Responses providers support it, DeepSeek explicitly rejects it (strip the field), unclassified custom providers keep caller-supplied values.
25
+
- Root cause: `fastMode` injects `service_tier` unconditionally on Responses routes; DeepSeek does not support the field. PR #860 adds a provider-level `supportsServiceTier` capability: canonical OpenAI Responses providers support it, DeepSeek explicitly rejects it (strip the field), unclassified custom providers FAIL CLOSED (strip) unless explicitly configured with `supportsServiceTier: true` — the reviewed final-head semantics, which supersede the PR body's original "preserve caller-supplied values" wording.
17
26
- Fresh corroboration: issue #875 (2026-08-02) "DeepSeek V4 Flash Responses route stalls after tool calls" — same wire family; executing session must check whether #875 is the same root cause or a second defect before closing either.
@@ -31,11 +40,38 @@ Provider-adapter/wire bugs; all must-fix regardless of PR quality.
31
40
32
41
| # | Claim | Source | Status |
33
42
|---|-------|--------|--------|
34
-
| 1 | Copilot serves some models Responses-only | gpt-5.4 verified (BerriAI/litellm#23332, exact `unsupported_api_for_model` error); gpt-5.6-sol lead only (JetBrains LLM-29711: function tools + reasoning_effort rejected on `/chat/completions`); same pattern for gpt-5-codex (opencode #2758)| verified (5.4) / lead (sol) |
35
-
| 2 | DeepSeek rejects/mishandles `service_tier`|No primary evidence either way (api-docs.deepseek.com does not list the field; Anthropic-compatible API marks it "Ignored" — different endpoint) |unresolved — capability-gating is safe regardless; do not claim rejection without a live probe|
36
-
| 3 | DeepSeek Responses route stalls after tool calls (hosted api.deepseek.com) |Stall reports are NIM/vLLM compatibility paths, not hosted; verified hosted failure mode is a 400 when `reasoning_content` is omitted after a tool call (official Thinking Mode docs; claude-code-router#1378) | contradicted as stated — #875 may be a `reasoning_content` echo defect, not #860's root cause; executing session must split them|
43
+
| 1 | Copilot serves some models Responses-only | gpt-5.4 verified (BerriAI/litellm#23332, exact `unsupported_api_for_model` error); gpt-5.6-sol verified to the same standard (JetBrains LLM-29711: function tools + reasoning_effort rejected on `/chat/completions` + pi.dev Responses declaration + #748 field run); same pattern for gpt-5-codex (opencode #2758). Full per-model table below | verified (7 models built-in; nano lead-only, excluded) |
44
+
| 2 | DeepSeek rejects/mishandles `service_tier`|Official Responses docs: field unsupported but unsupported params are SILENTLY IGNORED (api-docs.deepseek.com/guides/responses_api/, opened 2026-08-02 by researcher) |resolved — strip as compatibility policy; NOT a 400 and NOT #875's cause|
45
+
| 3 | DeepSeek Responses route stalls after tool calls (hosted api.deepseek.com) |Local root cause found: `sanitizeReasoningInputContent()` (`src/adapters/openai-responses.ts:35`, called :1027 for every Responses provider) blanks plaintext reasoning content on continuations; schema supports `reasoning_text` (`src/responses/schema.ts:23`); DeepSeek native contract accepts it. Residual: "no follow-up request sent" piece unexplained locally | verified local defect (separate from #860) + open external residual — fixed in wp-b, #875 commented not closed|
37
46
| 4 | Claude Opus 4.6/4.7 + Sonnet 4.6 are documented at 1M context | Anthropic official: Opus 4.6 (1M beta, 2026-02-05), Opus 4.7 (1M, 2026-04-16, migration guide), Sonnet 4.6 (1M beta, 2026-02-17); model overview cross-check | verified |
38
47
48
+
## Bug A model evidence (consumed by `010_bug_a_copilot_mixed_wire.md`)
49
+
50
+
Selection rule: built-in = field report in issue #748 AND independent corroboration. Resolver lookup is exact normalized-ID (`trim().toLowerCase()`), so dated/bracket-suffixed IDs intentionally miss.
51
+
52
+
| Model |#748 field report | Independent corroboration | Status | Built-in |
|`gpt-5.4-nano`| NO — absent from the 2026-07-30 captured catalog, never field-run | GitHub supported-models list + pi.dev/models/github-copilot/gpt-5-4-nano | lead-only | NO (`modelAdapters` documented) |
62
+
63
+
Why nano is the only exclusion, given #748 reports seven models: the two-leg rule (field report AND independent corroboration) is applied uniformly — sol meets both legs exactly as luna/terra do, so it is in; nano has no field report (never present in the captured catalog), so it stays out regardless of catalog/metadata labels. Sol's demonstrated chat failure is request-shape-conditional (tools + reasoning), which is precisely the Codex-agent traffic this bug is about; its bare-string default is safe for text-only chat clients because inbound chat is translated to the verified-working Responses wire rather than dropped.
64
+
65
+
## Bug B research findings (consumed by `020_bug_b_deepseek_service_tier.md`)
- PR #860's capability design fits this tree and applies cleanly (`git apply --check` passed on the dev lineage). Its file map is adopted with two corrections from its open review threads: the canonical-`openai` test must prove REGISTRY BACKFILL (not hardcode the field), and localized docs must not keep contradictory blanket wording.
70
+
- Official DeepSeek Responses docs list `service_tier` as unsupported but say unsupported Responses parameters are SILENTLY IGNORED (api-docs.deepseek.com/guides/responses_api/). Stripping remains sensible compatibility policy, but `service_tier` cannot explain #875's stall.
71
+
-#875 root cause (local, separate from #860): the continuation store preserves reasoning items (`src/responses/state.ts:699`, `:806`, `:837`; recorder installed at `src/server/responses/core.ts:1554`), DeepSeek stateless cleanup (`src/adapters/openai-responses.ts:1003`) does not remove them, but then `sanitizeReasoningInputContent()` (`src/adapters/openai-responses.ts:35`, blanks every non-empty reasoning item's `content` to `[]` at :45-56) is invoked at `:1027` for EVERY Responses provider. The function is OpenAI/ChatGPT-backend-motivated but unscoped. The local schema explicitly supports plaintext `{type:"reasoning_text"}` (`src/responses/schema.ts:23`, `:52`), and DeepSeek's native Responses contract accepts plaintext reasoning content — so current ocx deterministically sends DeepSeek an emptied reasoning item on every continuation. DeepSeek's registry `preserveReasoningContentModels` protects only Chat-Completions serialization, not native passthrough.
72
+
- Evidence calibration (audit round-1): DeepSeek's Responses docs confirm plaintext reasoning items are accepted and merged into adjacent assistant messages; the must-replay-on-tool-call-continuation rule is explicit only in the CHAT Thinking-Mode docs — mapping it to native Responses is an inference and is labeled as such in code comments.
73
+
- Caveat recorded: the reasoning defect only fires once a follow-up request REACHES ocx; it cannot by itself explain #875's "no follow-up HTTP request sent at all" observation, which may be a separate client/SSE handoff issue. #875 stays open with a comment; the reasoning replay defect is fixed here as the local half.
74
+
39
75
## Out of scope
40
76
41
77
- New provider presets (covered by separate enhancement PRs).
Consumed by work-phase wp-a. Verified against dev@478354ee8. Model-by-model evidence and source URLs live in `000_plan.md` (claim ledger + evidence table) — this doc carries only the decision and its implementation consequences.
4
+
5
+
## Mechanism (decided)
6
+
7
+
The tree ALREADY owns the correct mechanism; this fix declares data, not new routing:
8
+
9
+
```text
10
+
hard wire pin
11
+
→ explicit user modelAdapters
12
+
→ registry modelWireDefaults ← the fix adds entries here
-`src/providers/registry.ts:140` — registry defaults stay separate from persisted user overrides.
18
+
-`src/server/adapter-resolve.ts:14` — resolver implements the precedence above, preserving credentials/base URL through a copy.
19
+
-`src/providers/registry.ts:1552` — registry defaults constrained to recognized destinations + the two OpenAI wires.
20
+
-`src/server/responses/core.ts:1434` — final route resolves transport, then the effective model adapter.
21
+
-`src/providers/github-copilot-transport.ts:29` — transport has no model argument; do NOT branch here.
22
+
23
+
Rejected alternatives (with reasons): provider-wide `openai-responses` (breaks Copilot's Claude/Gemini/GPT-4/gpt-5-mini chat models); transport-level switch (wrong owner, no model arg); runtime endpoint probing (quota-cost + nondeterminism; live discovery hints are not routing metadata); new config flag (`modelAdapters` is already the operator escape hatch).
24
+
25
+
## File map
26
+
27
+
- MODIFY `src/providers/registry.ts` (github-copilot entry at :1470) — add `modelWireDefaults` with the conservative verified set:
28
+
`gpt-5.3-codex`, `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.5`, `gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra` → `"openai-responses"` (bare strings, every inbound — these models are Responses-required for agent traffic; translation keeps text-only chat clients working).
29
+
- MODIFY the same entry's cold-start seed. Policy: ADDITIVE update, no removals (the seed is a cold-start fallback under `liveModels: true`; removals buy nothing and risk stale saved-config surprises). Exact before/after:
-`defaultModel: "gpt-4o"` unchanged. `gpt-5-mini` is added as a verified CHAT model (present in #748's captured catalog, chat-served) — it keeps the chat regression fixture honest. `gpt-5.4-nano` is the ONLY lead-only model left out (no field run, absent from the captured catalog); it ships as a documented `modelAdapters` example. `providerConfigSeed()` copies this list into saved config (`src/providers/derive.ts:105`), so every added id has named evidence in `000_plan.md`.
33
+
- NEW `tests/github-copilot-wire-defaults.test.ts` — focused suite (cases below).
34
+
- DOCS `docs-site/src/content/docs/reference/configuration/providers.md` (the authoritative `modelAdapters` contract lives at its :79) + maintained locale equivalents (ko, ja, zh-cn, ru) — the table currently carries DeepSeek-only wording and would contradict the new Copilot behavior. `docs-site/src/content/docs/guides/providers.md` gets a short routing-precedence note naming the built-in Copilot defaults and the `modelAdapters` escape hatch for lead-only models.
35
+
- NO CHANGES: `github-copilot-transport.ts`, `adapter-resolve.ts`, `types.ts`, `derive.ts`. The sampling/credential-replay parts of PR #746 are a separate parity/security unit — out of scope here.
36
+
37
+
## Selection rule (decision reference; full evidence in `000_plan.md`)
38
+
39
+
Built-in = field report in issue #748 AND independent corroboration. All seven Responses-required models meet it, including `gpt-5.6-sol`; `gpt-5.4-nano` alone fails it (no field-report leg) and stays out as a documented `modelAdapters` example. Lookup is exact normalized-ID (`trim().toLowerCase()`, `registry.ts:1568`) — no family/snapshot prefix matching.
40
+
41
+
## Acceptance + activation scenarios
42
+
43
+
1.`gpt-5.4` via the github-copilot preset resolves to the Responses wire and the upstream request goes to the Responses endpoint, never `/chat/completions`. Activation: captured-upstream-URL test (runtime-wire proof, not just resolver proof).
44
+
2. All seven built-in models resolve Responses on all three inbound wires (Responses, Chat Completions, Anthropic inbound). Activation: parametrized resolver + URL tests.
45
+
3. Explicit user `modelAdapters` override beats the registry default in BOTH directions: a listed Responses-default model (e.g. `gpt-5.4`) pinned back to chat proves the opt-out direction; an unlisted chat model (e.g. `gpt-5.4-nano`, or seeded `gpt-5-mini`) mapped to Responses proves the opt-in direction. Activation: precedence tests — note `gpt-5.6-sol` cannot serve as the opt-in case because it is itself a default.
46
+
4. Chat-served Copilot models (`gpt-4o`, `gpt-4.1`, `gpt-4.1-mini`, `claude-sonnet-4`, `gemini-2.5-pro`, `gpt-5-mini`) still use chat completions. Activation: regression assertions on the seed's chat set (`gpt-5-mini` is newly seeded, not pre-existing — its assertion guards against accidental inclusion in `modelWireDefaults`).
47
+
5. Unrelated providers are isolated (no wire change for non-copilot providers with same-named models). Activation: isolation test.
48
+
6. Credentials/base URL preserved through the resolved copy. Activation: adapter-resolve test shape per `adapter-resolve.ts:14`.
49
+
50
+
## Verification gate
51
+
52
+
`bun test tests/github-copilot-wire-defaults.test.ts` + `bun run typecheck` + `bun run test` (registry is shared) + `bun run privacy:scan`.
0 commit comments