Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# 100 — Chat-default regression for Grok 4.5/4.6 (#2227 integration unit)

Audited: 3-round adversarial plan audit (sol-medium), round-3 PASS. Amendment of this
unit's roadmap for the integration merge-train; consumes user directives from 260821.

## Decision

Third-party Responses APIs are supported only as first-class surfaces. Outside OpenAI,
the default wire is `openai-chat`. xAI's own CLI defaults to Chat; its Responses dialect
rejects opaque reasoning continuation/compaction state on later turns (#2240 regression
axis, 4-layer sanitize chase in #2217). Chat translation structurally filters private
extensions instead of chasing them.

- ADOPT #2227: `modelWireDefaults` for grok-4.6/grok-4.5 flip `openai-responses` ->
`openai-chat` (src/providers/registry.ts:1032/1038 area), structure/04 rewrite, test
conversions to explicit `modelAdapters` opt-in framing.
- DeepSeek is OUT OF SCOPE (user decision 260821): deepseek-v4-flash/pro keep their
Responses defaults (registry.ts:1563-1564). Add a focused non-regression test locking
both V4 entries to `openai-responses` so this train cannot drift them.
- The Responses implementation is NOT deleted; it becomes the opt-in lane (doc 130).

## Atomic merge unit (audit blocker R2-B1)

The #2227 flip and the service_tier policy fix land as ONE merge unit — no intermediate
dev head may exist where OAuth opt-in Responses leaks caller service_tier:

1. Cherry-pick/merge #2227's registry + structure/04 + test changes onto the post-stack
dev head (anchor: doc 110 global order).
2. In the same unit, fix the fastwire.ts:151 bypass: configured `modelAdapters` must not
skip the registry OAuth policy. Per audit round-3 note: make the OAuth registry tier
policy UNCONDITIONAL for the matching xAI route rather than introducing a new config
field — `modelAdapters` values are wire ids only.
3. Regression matrix locked in tests (5 rows):
| route | expectation |
|---|---|
| OAuth default | chat wire |
| OAuth explicit Responses (modelAdapters) | responses wire, caller service_tier dropped |
| API-key default | chat wire; no tier injected; caller service_tier not forwarded unless a capability declares it |
| API-key explicit Responses (modelAdapters) | responses wire; PRESERVE current dev semantics: absent tier stays absent, caller-supplied service_tier forwards verbatim (resolver proof: forwardCallerTier true on this route today; #2072 deferred) |
| DeepSeek V4 flash/pro | responses default unchanged |

## Reasoning-streaming proof (#1886 origin)

#1886 moved grok to Responses because Chat translation showed a blank screen during long
reasoning turns. The regression must prove the Chat path now streams reasoning as an E2E
SSE assertion, not unit-only: an early upstream `reasoning_content` delta must be
observed on Codex's reasoning-summary SSE channel BEFORE the completion is released.
Test shape: mock xAI chat stream emitting reasoning_content deltas first; assert the
bridged Responses SSE emits reasoning summary deltas before `response.completed`.
Follow with one live probe through the running proxy.

## Out of scope

- #2072 API-key Fast/priority policy: DEFERRED (open, conflicting, its own review
cycle). The opt-in switch spec (doc 130) does not depend on it.
- DeepSeek wire changes: none.
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# 110 — Global cross-train triage matrix and merge order

One global order (audit R1-B5): the sidecar stack lands FIRST (9 stacked branches;
cascading a rebase through them is the expensive direction), then the lighter triage
PRs rebase onto the post-stack dev head one at a time. Every individual merge requires
maintainer approval + required CI green (MAINTAINERS.md) — the user's 'CI as lagging
indicator' applies only to repair iterations between pushes, never to the merge click
itself (audit R1-B4).

## Global order

1. Sidecar stack bottom-up: #2203 -> #2204 -> #2206 -> #2209 -> #2211 -> #2238 ->
#2242 -> #2243 -> #2245 (details + blocker inventory: doc 120).
2. #2227 integration unit (Chat default + unconditional OAuth tier policy, doc 100).
3. #2217 RESHAPED (not raw): rebase onto post-#2227 dev, reframe tests/docs as opt-in
hardening, gate the compat rewrite to the opt-in Responses route.
4. Doc-130 atomic opt-in switch (wp11).
5. Remaining Responses fixes: #2237, #2229, #2228 (do not depend on the Responses
default; still valuable for the opt-in lane and other Responses routes).
6. luvs01 / Ingwannu / docs PRs (matrix below).
7. Release prep (doc 140) -> lidge final gate (doc 150).

## Triage matrix (dispositions)

| PR | author | disposition | rationale |
|---|---|---|---|
| #2227 | olddonkey | MERGE (as doc-100 atomic unit) | owns the chat default |
| #2217 | olddonkey | RESHAPE then merge | opt-in-lane hardening; raw form encodes Responses-as-default |
| #2237 | olddonkey | MERGE after #2227 | null reasoning channel drop; wire-agnostic |
| #2229 | olddonkey | MERGE after #2227 | encrypted_content reshape guard; opt-in lane |
| #2228 | olddonkey | MERGE after #2227 | compaction blob provenance; wire-agnostic |
| #2214 | luvs01 | MERGE (address CHANGES_REQUESTED) | continuation binding bug |
| #2236 | luvs01 | MERGE (address CHANGES_REQUESTED) | catalog comment preservation |
| #2226 | luvs01 | MERGE after hygiene unblocked | secret redaction in events |
| #2196 | Ingwannu | MERGE | maintainer chore, privacy-bounded diagnostics |
| #2207 | Ingwannu | MERGE | google tool-result adjacency bug |
| #2202 | Ingwannu | MERGE | claude roster sync bug |
| #2181 | lidge-jun | MERGE (address CHANGES_REQUESTED) | devlog docs |
| #2168 | lidge-jun | MERGE (address CHANGES_REQUESTED) | devlog docs |
| #2235 | umyunsang | REVIEW-ONLY this train | contributor draft gate owns it |
| #2220 | Hylouis233 | REVIEW-ONLY this train | draft, capability sync |
| #2230 | ppvia | OUT (hygiene-blocked draft) | own cycle |
| #2222 | MarcTCruz | OUT (hygiene-blocked draft) | own cycle |
| #2216 | leon80900 | CLOSE-DIRECT (wrong branch, targets main) | ask re-file onto dev |
| #2215 | parkjs101 | OUT (docs draft, changes requested) | own cycle |
| #2213 | louis-tepe | OUT (draft, overlaps doc-130 design) | revisit post-switch |
| #2072 | olddonkey | DEFERRED (audit R2-B2) | Fast policy composes later; must re-verify against both wires |

Rebase anchors are named at execution time in each phase's B (exact dev head SHA),
with cascade verification (typecheck + focused suites) after every land.
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# 120 — Sidecar chain merge execution (L1-L9 into dev)

Order: #2203 -> #2204 -> #2206 -> #2209 -> #2211 -> #2238 -> #2242 -> #2243 -> #2245.
Each merge: resolve CHANGES_REQUESTED, obtain maintainer approval, required CI green,
squash-merge into its base, retarget the next child, cascade-verify (typecheck +
focused suites), then proceed. Mid-stream lidge suites are lagging indicators between
pushes; the merge click itself is gated (MAINTAINERS.md).

## Current blocker inventory (fresh, 260821)

- #2203 (L1, CHANGES_REQUESTED Ingwannu): blocker is the tracked cleanup doc
000_wp0_branch_worktree_cleanup.md — contradictory KEEP/REMOVE entries, no preflight,
incomplete protected set, b2ac2500c preservation, codex/merge-loop-closeout listed
both ways. Fix: rewrite the doc as a non-executable historical record (all deletions
already executed in wp0) with a mechanical protected-set preflight template; or mark
every command block as executed-snapshot. No runtime code change.
- #2204, #2206 (L2, L3): APPROVED. Rebase-carry only.
- #2209 (L4, CHANGES_REQUESTED): runtime blocker — webSearchModelOptionsFrom drops
backend provenance; auth-slot model can persist {backend:'openai',
model:'claude-haiku-4-5'}. Fix: return (backend, model) pairs, validate the pair in
both PUT routes, teach sidecarBackendForModel the auth-slot rows.
- #2211 (L5, CHANGES_REQUESTED): carry the L4 provenance field through the CLI
contract — show backend in `web --list` human output and validate pairs on write.
- #2238 (L6, CHANGES_REQUESTED, head a05f23fa9) — three reviewed blockers:
1. stripOpenAiOnlyWebSearchFields fires for every non-ChatGPT-forward Responses
provider; official OpenAI API-key traffic loses external_web_access /
search_context_size. Gate on xAI-specific provider identity/capability and add
a buildRequest regression proving OpenAI API-key tools retain both fields.
2. English config reference + CLI help still advertise only the old backend pair;
document the xai/gemini/exa arms as explicit-only/inert; keep translations
consistent.
3. exaApiKey only in SENSITIVE_KEY_PATTERN: add it to the shared colon/query/JSON
string-redaction grammar with all three canaries in tests/redact.test.ts.
- #2242 (L7, CHANGES_REQUESTED, head 0f2d670c0) — five reviewed blockers:
1. runXaiWebSearch misses cancelBodyOnAbort after fetchWithResetRetry resolves
(abort-before-reader race).
2. parseXaiResponsesSSE must cancel the upstream body at the byte bound, not just
release the reader lock.
3. the management PUT mutates config.webSearchSidecar before xSearch validation
(400 after live state change) — stage and validate the complete candidate first.
4. malformed xSearch fields are silently omitted — reject invalid handle arrays,
dates, and enabled values instead of broadening the search with a 200.
5. public docs + the type comment still call xai inert; update the English source
and translations. Add no-partial-mutation and oversized-stream regressions.
- #2243 (L8, CHANGES_REQUESTED): three runtime blockers per review + red macOS CI
shard — full RCA in the phase B, fixes + rerun.
- #2245 (L9): reviewer PASS locally; needs maintainer approval; one failing test shard
reported on CI — reproduce, fix, re-push.

Execution sequence (explicit — the wp numbers are not the order): wp9 (this doc,
sidecar chain) -> wp8 (triage PRs per doc 110) -> wp11 (doc 130 switch) -> wp10
(docs 140/150).

## Execution record (wp9 B-phase, 260821)

Every recorded blocker resolved and pushed; Ingwannu re-review re-requested on all
six layers. Worker lanes ran in parallel worktrees under .tmp/ (four sol-medium
subagents + one direct fix):

- #2203 d505dacc7 — cleanup doc recast as an executed historical record; corrected
36-ref remote list (merge-loop-closeout excluded); 5-step preflight template.
- #2209 98eaba601 — options carry (backend, model) pairs; both PUT routes validate
effective pairs; auth-slot Anthropic persists anthropic/claude-haiku-4-5.
Suites 50/0 + 30/0, GUI 9/0, gate 13/0.
- #2211 84357bd2b (parent merge) + 2a610909f — backend-tagged `web --list`,
provenance-aware pair writes, clear rejection errors. CLI suites 401/0.
- #2238 19376f737 — strip gated on supportsOpenAiWebSearchToolFields:false (xAI
registry declares it; OpenAI API-key traffic keeps both fields — regression red
pre-fix); docs/CLI-help union across 8 locales; exaApiKey in the shared
colon/query/JSON redaction grammar, 3 canaries (JSON canary red pre-fix).
- #2242 b2c2054b5 — cancelBodyOnAbort after resolve; byte-bound upstream body
cancel; staged atomic PUT validation (no-partial-mutation); malformed xSearch
rejected with 400; docs/type-comment de-inerted. 47/0 + 12/0.
- #2243 249cc91a3 — atomic token/project snapshot; post-header abort guard;
bounded 64KiB UTF-8 JSON reads. 4 regressions red pre-fix; 50/0, privacy green.

Remaining before merge clicks: Ingwannu approvals + green required CI per layer
(#2245's earlier shard failure not reproduced at the current head — checks green
except queued/pending reruns). Lidge full suite relaunched at stack top
(/tmp/ocx-gate-stack.log) as the lagging indicator.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# 130 — xAI Responses opt-in switch (atomic persistence + GUI)

The Responses implementation survives as an explicit opt-in lane, like the x_search
opt-in block. Default: chat (post-#2227). Surface: xai is ONE provider id
(registry.ts:1000); the 'two pages' are the auth-mode-scoped sections (OAuth account +
API key) of the same provider workspace.

## Contract (audit R1-B2)

- Config truth: `modelAdapters` entries for grok-4.5 + grok-4.6 -> 'openai-responses'.
- Atomic management API: extend the provider PATCH surface (provider-routes.ts:378 area
+ gui provider-workspace DTO types.ts:88) with a split write/read contract:
- WRITE (PATCH input): `xaiResponsesOptIn: boolean` — sets/clears BOTH grok model
entries in one config transaction, preserving unrelated modelAdapters overrides.
- READ (GET/echo DTO): `xaiResponsesOptInState: true | false | "mixed"` — partial
pre-existing state (one model set, one not) reads "mixed"; the first boolean
write normalizes both entries and the echo returns the effective state.
- GUI: one switch rendered in both auth-mode sections; mixed state shows indeterminate.
- Tier policy: the doc-100 unit already made the OAuth registry tier policy
unconditional; the switch adds NO tier behavior. API-key route: opt-in flips wire
only; everything else keeps current dev semantics (#2072 deferred).
- #2217 sanitize layers arm only on this opt-in Responses route (RESHAPE disposition).
- Tests: atomic set/clear, override preservation, mixed-state normalization, effective-
state echo, GUI switch render + PATCH round-trip, opt-in wire selection E2E.
- Docs-site: provider page gains the switch row; structure/04 notes the opt-in lane.

Execution phase: wp11 (after #2227 unit + reshaped #2217; before release prep).
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# 140 — dev release prep

After wp11 lands. Version bump per release train conventions; release notes cover:
sidecar unification (L1-L9), chat-default regression + opt-in switch, responses fixes
(#2237/#2229/#2228, reshaped #2217), maintainer fixes (#2196/#2207/#2202), luvs01
fixes (#2214/#2236/#2226), devlog docs (#2181/#2168). scripts/release.ts is the
authority; no release action without the doc-150 gate green.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# 150 — lidge final aggregate/release gate

The final AGGREGATE gate before release. It does not replace per-merge gating: docs
110/120 keep approval + required CI blocking for every individual merge; 'lagging
indicator' covers only repair iterations between pushes. At the final dev head:

- lidge: OCX_TEST_NO_QUEUE=1 bun run test — full suite green (baseline 13808+/0 at L9).
- Local: bun run typecheck, bun run privacy:scan, bun run lint:gui, lint:i18n, GUI
tests, bun run build:gui, and the docs-site build (doc 130 touches GUI and
localized copy, so the full GUI/i18n/docs chain is in the gate).
- GitHub Actions: final dev head green on Linux/Windows/macOS (Windows gate is a
standing release requirement).
- Live probes through the running proxy: OAuth chat default turn, opt-in Responses
turn (no caller service_tier upstream), x_search opt-in turn, exa sidecar turn,
reasoning-streaming E2E (doc 100 matrix).
- Release staged on lidge per release-train conventions; promotion remains
maintainer-controlled.
12 changes: 12 additions & 0 deletions docs-site/src/content/docs/guides/sidecars.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@ closed. Explicit `xai` requires a usable stored Grok OAuth account and does not
requires both ChatGPT login auth and an enabled `forward` provider.
:::

### Additional web-search backends (explicit-only)

Three more web-search backends exist beyond the ChatGPT and Claude paths. Each is
**explicit-only** — it never activates from credential presence — and **fails closed**:
a missing credential produces no sidecar plan and the request takes the normal routed path.

| Backend | Runs | Credential | Notes |
| --- | --- | --- | --- |
| `xai` | Grok hosted `web_search` (+ opt-in `x_search`) on `api.x.ai` Responses | Stored Grok OAuth (`ocx login xai`) | `webSearchSidecar.xSearch` enables X search with `allowedXHandles`/`excludedXHandles` (max 20, mutually exclusive) and ISO `fromDate`/`toDate`. Default model `grok-4.6`. |
| `gemini` | `google_search` grounding on the Antigravity transport | Stored Antigravity OAuth with a discovered project (`ocx login google-antigravity`) | Default model `gemini-3.7-flash`; reasoning maps to the tiered thinking level. |
| `exa` | Exa Search API (non-LLM result digest) | `webSearchSidecar.exaApiKey` | The key is write-only through the management API (never echoed, redacted from logs). No sidecar model applies. |

## Web-search sidecar

When Codex requests hosted `web_search` for a non-passthrough routed model, opencodex:
Expand Down
2 changes: 2 additions & 0 deletions src/server/responses/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3589,6 +3589,8 @@ async function handleResponsesInner(
xaiSidecar: wsPlan.xaiSidecar,
geminiSidecar: wsPlan.geminiSidecar,
xaiSearchOptions: wsPlan.xaiSearchOptions,
// The exa key never rides the plan: read it from config at unpack time (L9).
...(wsPlan.exaConfigured ? { exaApiKey: config.webSearchSidecar?.exaApiKey } : {}),
hostedTool: wsPlan.hostedTool,
selectedForwardHeaders: wsPlan.forwardSidecar?.headers ?? selectedForwardHeaders,
settings: wsPlan.settings,
Expand Down
7 changes: 7 additions & 0 deletions src/web-search/backends.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ export const WEB_SEARCH_BACKENDS: readonly WebSearchBackendDescriptor[] = [
},
eligibleModel: candidate => candidate.provider === "google-antigravity",
},
{
backend: "exa",
// Probe = operator key present. Exa is not an LLM: no candidate models ever
// match, so the GUI's model list stays untouched by this backend.
isActive: (_auth, config) => !!config.webSearchSidecar?.exaApiKey,
eligibleModel: () => false,
},
];

/**
Expand Down
Loading
Loading