Skip to content

Commit 47e7d61

Browse files
committed
docs(plan): split wt3 roadmap into per-bug decade docs with researched mechanisms
wp-a: modelWireDefaults on the github-copilot registry entry (mechanism already on tree: hard pin > modelAdapters > registry defaults > provider adapter); conservative 6-model set, nano/sol lead-only via modelAdapters. wp-b: adopt PR lidge-jun#860's capability file map + lidge-jun#875 root cause found — sanitizeReasoningInputContent blanks plaintext reasoning for every Responses provider (openai-responses.ts:35, called :1027); scope it. wp-c: registry.ts:217 map + authoritative-window [1m] predicate.
1 parent 478354e commit 47e7d61

5 files changed

Lines changed: 132 additions & 56 deletions

File tree

devlog/_plan/260802_wt3_provider_wire/000_plan.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
# wt3 — Provider wire correctness (research)
22

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`.
44
Provider-adapter/wire bugs; all must-fix regardless of PR quality.
55

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` |
11+
| wp-b | B — DeepSeek service_tier (#860) + #875 triage | `020_bug_b_deepseek_service_tier.md` |
12+
| 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+
615
## Scope
716

817
### Bug A — PR #746 / issue #748: Copilot Responses-only models routed to chat completions
@@ -32,8 +41,8 @@ Provider-adapter/wire bugs; all must-fix regardless of PR quality.
3241
| # | Claim | Source | Status |
3342
|---|-------|--------|--------|
3443
| 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) | unresolvedcapability-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 |
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) | resolvedstrip 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 |
3746
| 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 |
3847

3948
## Out of scope
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# 010 — Bug A: Copilot mixed-wire routing (#746 / #748)
2+
3+
Consumed by work-phase wp-a. Verified against dev@478354ee8 (2026-08-02, sol-medium researcher; per-source evidence below).
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
13+
→ provider-wide adapter
14+
```
15+
16+
- `src/providers/registry.ts:101` — registry metadata owns mixed-wire defaults (`modelWireDefaults`).
17+
- `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-terra``"openai-responses"`.
29+
Refresh the cold-start seed model list as justified by the same evidence (static seed is a cold-start fallback per the entry's FREEZE comment).
30+
- NEW `tests/github-copilot-wire-defaults.test.ts` — focused suite (cases below).
31+
- DOCS `docs-site/src/content/docs/guides/providers.md` + `docs-site/src/content/docs/reference/configuration.md` + maintained locales — name the built-in defaults and the `modelAdapters` escape hatch for lead-only models.
32+
- 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.
33+
34+
## Model evidence table
35+
36+
| Model | Evidence | Status | In built-in set |
37+
|---|---|---|---|
38+
| `gpt-5.3-codex` | #748 field run + Pi metadata declares Responses | field-verified, corroborated | yes |
39+
| `gpt-5.4` | exact tools+reasoning chat failure + successful Responses run in #748; litellm#23332 | verified Responses-required | yes |
40+
| `gpt-5.4-mini` | #748 field run + Pi metadata | field-verified, corroborated | yes |
41+
| `gpt-5.5` | #748 field run + Pi metadata | field-verified, corroborated | yes |
42+
| `gpt-5.6-luna` | #748 field run + Pi metadata | field-verified, corroborated | yes |
43+
| `gpt-5.6-terra` | #748 field run + Pi metadata | field-verified, corroborated | yes |
44+
| `gpt-5.4-nano` | GitHub catalog + Pi labels; NOT in captured catalog, never field-run | lead-only | NO — document `modelAdapters` override |
45+
| `gpt-5.6-sol` | #748 claims a run; JetBrains LLM-29711 shows tools+reasoning rejected on chat; no authoritative endpoint contract | lead-only | NO — document `modelAdapters` override |
46+
47+
Exact normalized-ID lookup only — no family/snapshot prefix matching (this tree's resolver behavior; PR #746's dated-snapshot matching was dropped at its final head too).
48+
49+
## Acceptance + activation scenarios
50+
51+
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).
52+
2. All six built-in models resolve Responses on all three inbound wires (Responses, Chat Completions, Anthropic inbound). Activation: parametrized resolver + URL tests.
53+
3. Explicit user `modelAdapters` override beats the registry default in BOTH directions (user pins a listed model back to chat; user maps `gpt-5.6-sol` to Responses). Activation: precedence tests.
54+
4. Chat-served Copilot models (`gpt-4o`, `gpt-4.1`, `claude-sonnet-4`, `gemini-2.5-pro`, `gpt-5-mini`) still use chat completions. Activation: regression assertions on the existing seed set.
55+
5. Unrelated providers are isolated (no wire change for non-copilot providers with same-named models). Activation: isolation test.
56+
6. Credentials/base URL preserved through the resolved copy. Activation: adapter-resolve test shape per `adapter-resolve.ts:14`.
57+
58+
## Verification gate
59+
60+
`bun test tests/github-copilot-wire-defaults.test.ts` + `bun run typecheck` + `bun run test` (registry is shared) + `bun run privacy:scan`.

devlog/_plan/260802_wt3_provider_wire/010_implementation.md

Lines changed: 0 additions & 53 deletions
This file was deleted.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# 020 — Bug B: DeepSeek service_tier capability gate (#860) + reasoning replay fix (#875)
2+
3+
Consumed by work-phase wp-b. Re-verify against the current tree at wp-b's P (wt2 #847 may have touched the same files by then — see coordination note).
4+
5+
## Research findings (2026-08-02, sol-medium researcher, sources cited inline)
6+
7+
- PR #860's capability design fits this tree and applies cleanly (`git apply --check` passed on dev@478354ee8's lineage). Its file map is adopted below with two corrections from its open review threads: the canonical-`openai` test must prove REGISTRY BACKFILL (not hardcode the field), and ja/zh docs must not keep contradictory blanket wording.
8+
- 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** — the ledger in `000_plan.md` is updated accordingly.
9+
- #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.
10+
- Caveat recorded: this 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.
11+
12+
## File map
13+
14+
- MODIFY `src/types.ts` — provider-level `supportsServiceTier` capability field (optional; tri-state semantics: `true` inject/strip allowed, `false` strip always, `undefined` preserve caller value).
15+
- MODIFY `src/config.ts` — accept the field in persisted provider configuration (per #860's config.ts:482 hunk).
16+
- MODIFY `src/providers/registry.ts` — registry-enriched metadata: canonical OpenAI Responses providers = `true`, DeepSeek = `false`. Capability is runtime metadata so older canonical OpenAI configs stay valid.
17+
- MODIFY `src/providers/derive.ts` — carry the value into key-login metadata; fill missing values during registry enrichment WITHOUT overriding explicit config.
18+
- MODIFY `src/router.ts` — independent backfill on the final routed provider (covers stale/minimal saved configs).
19+
- MODIFY `src/server/responses/core.ts` (:806-807 on dev@478354ee8) — `fastMode` currently does `if (tier) _rawBody.service_tier = tier; else delete ...` gated only by adapter kind. Consult the provider capability: inject/remove only for `true`; always delete for `false`; leave caller-supplied values untouched for `undefined`.
20+
- MODIFY `src/adapters/openai-responses.ts` — TWO changes: (1) `service_tier` decision happens in core.ts after final adapter resolution; the adapter stays provider-agnostic (commentary only, per #860). (2) NEW for #875: scope `sanitizeReasoningInputContent()` so it no longer blanks reasoning content for providers whose native contract accepts plaintext reasoning (DeepSeek first). Mechanism decision at B: provider-capability flag vs explicit provider-id check — prefer a registry capability to avoid a second provider-fact location (src/AGENTS.md: provider catalog metadata belongs in the registry).
21+
- DOCS: configuration reference EN + zh-CN (docs-site) — the capability and the DeepSeek behavior; ja locale must not contradict.
22+
23+
## Acceptance + activation scenarios
24+
25+
1. DeepSeek Responses request never carries `service_tier`, including with `fastMode` on. Activation: serialized-payload test with a DeepSeek provider config + fastMode, asserting the field is absent from `_rawBody`.
26+
2. Canonical OpenAI Responses provider keeps inject/remove behavior. Activation: payload test asserting `service_tier` present with fastMode on, absent with off.
27+
3. Unclassified custom Responses provider preserves a caller-supplied `service_tier`. Activation: payload test with pre-set field asserting pass-through.
28+
4. Older canonical OpenAI configs without the capability field still behave as today. Activation: backward-compat test with legacy config shape.
29+
5. Registry backfill is proven, not hardcoded: a provider config WITHOUT the field gets the registry value at derive/router boundaries. Activation: test asserting the enriched value appears with the field absent from config (addresses #860's open review issue).
30+
6. #875 regression: a continuation request carrying a plaintext reasoning item (`{type:"reasoning", content:[{type:"reasoning_text", text:...}]}`) through a DeepSeek Responses route keeps its reasoning content on the wire. Activation: adapter serialization test asserting non-empty content after `sanitizeReasoningInputContent` for DeepSeek, and emptied content for the OpenAI/ChatGPT path (unchanged behavior there).
31+
32+
## #875 triage verdict (recorded, discharge of the obligation)
33+
34+
Verdict: **separate local bug, fixed in this cycle** (reasoning replay deletion above) + **residual external piece** (the "no follow-up request at all" observation cannot be explained by any ocx code path found; may be client/SSE handoff or NIM/vLLM-side). Action at D: comment on #875 with the file:line evidence and the remaining unexplained piece; do NOT close #875 as fixed-by-#860.
35+
36+
37+
## Cross-worktree coordination (wt2 #847)
38+
39+
Both this fix and wt2 #847 touch `src/adapters/openai-responses.ts` and `src/server/responses/core.ts` (different code paths: SSE/tool-arg caps vs `service_tier` injection). Whichever lands second rebases and re-runs its payload-shape tests.

0 commit comments

Comments
 (0)