Changelog
Every release.
What shipped, what was fixed, what was verified. The last 10 entries — pulled live from the kRouter repo CHANGELOG.md.
Latestv0.5.1182026-07-18
Releases on GitHubJuly 2026
Kiro API-key (ksk_) headless auth (the last half of 706e6513)
Completes the Kiro headless commit. Kiro can now authenticate with a long-lived API key (`ksk_…`) instead of an OAuth/social login — no refresh token, no browser flow. **What landed:** - `KiroService.validateApiKey` + `listAvailableProfiles` — validate a key by calling CodeWhisperer `ListAvailableProfiles` (the only way to check a keyless bearer credential) and resolve its account-specific `profileArn`. Accepts both `arn` and `profileArn` response fields (the API-key JSON-1.0 surface returns `arn`). - `POST /api/oauth/kiro/api-key` — validates + imports the key, stores it as a `kiro` connection with `authMethod:"api_key"`, `refreshToken:null`, and a 1-year expiry so the proactive-refresh path (which needs a refresh token) is skipped. - Executor: sends `Authorization: Bearer <key>` **plus** `tokentype: API_KEY` for api-key connections, and reorders base URLs to try the `*.amazonaws.com` CodeWhisperer hosts FIRST (the `runtime.*.kiro.dev` gateway rejects an `API_KEY` token with 401/403, which BaseExecutor returns immediately). OAuth keeps the default order. - profileArn guard: for api-key connections, never fall back to the shared builder-id/social *default* placeholder ARN (it 403s — it isn't owned by the key's account); send only the ARN resolved at import. - UI: an "API Key" method in the Kiro connect modal (paste `ksk_…` + region → validate → import). **Verification — honest split:** - **Verified live (regression / inertness):** on a real Kiro **OAuth** account, all four paths still work with these changes in place — streaming and non-streaming, Claude and OpenAI clients (each returned "Mango."). The api-key branches are gated on `authMethod === "api_key"`, so for every existing connection they are provably inert: `buildHeaders` falls to the OAuth branch, `getOrderedBaseUrls` returns the default order, and the profileArn guard doesn't change anything. Grok-CLI (which shares Kiro's account) also still chats. - **NOT live-verified:** the api-key path itself — there is no `ksk_` key available on this instance to exercise `validateApiKey` / the `API_KEY` request end-to-end. It is a faithful port of upstream's verified feature and is covered by unit tests (tokentype header, AWS-first host ordering, profileArn field parsing, OAuth-unchanged), but a real `ksk_` request was not sent. When a key is available, the flow is: dashboard → Kiro → "API Key" → paste → Import. **Verification:** full suite **1296 pass** (+8: 5 executor + 3 profile-arn), production build clean, `/api/oauth/kiro/api-key` route registered, and the whole recent-session surface re-checked live (all API endpoints 200, video listing, headroom, providers) — nothing regressed.Kiro direct claude↔kiro route (the half of 706e6513 that was deferred)
Completes the direct-route half of the Kiro headless commit I deferred in v0.5.116. Claude clients on Kiro now translate **straight** to/from Kiro's CodeWhisperer format instead of pivoting through OpenAI (`claude→openai→kiro` and back), which is lossy for tool-use and thinking blocks. **Why it was safe to do now (the deferral concern, resolved):** I deferred this overnight worried the response-side direct dispatch would collide with the common `openai:claude` path. Re-reading the full `translateResponse`, the short-circuit is **provably equivalent** for every existing pair: whenever either side is OpenAI the two-step pivot already collapses to exactly one translator call on the same chunk — the same call the direct route makes. Only a genuinely-new two-hop registration (`kiro:claude`) changes behavior. The full suite (1288) confirms no regression across all translators. **What landed:** - `translator/request/claude-to-kiro.js` + `translator/response/kiro-to-claude.js` — the direct translators (with the two 400-guards that fold orphaned/toolless `tool_result` blocks back to text so Kiro's schema validator doesn't reject follow-up turns). - `translator/schema/index.js` — the three constants (`ROLE`, `CLAUDE_BLOCK`, `DEFAULT_IMAGE_MIME`) the translators need, rather than porting upstream's whole schema subsystem. - `translator/index.js` — additive direct-route dispatch on both request and response sides, registered the two translators. - **Non-streaming fix**: `translateNonStreamingResponse` now converts a buffered Kiro (OpenAI-shaped) body to a Claude message for Claude clients. This was a **pre-existing gap** the direct-route work surfaced — a non-streaming Claude client on Kiro was getting the raw `{choices:[]}` OpenAI body it can't parse. The streaming path is handled by the new `kiro:claude` route; non-streaming buffers to JSON separately, so it needed its own case. **Verified live against a real Kiro OAuth account — all four paths:** - Streaming Claude client → Kiro: proper Claude SSE (`message_start` → `content_block_delta` "mango"), 0 OpenAI leaks. - Non-streaming Claude client → Kiro: proper Claude message, `stop_reason: end_turn`, reply "mango" (was an empty OpenAI body before the fix). - Streaming OpenAI client → Kiro (Cline/Cursor path): unchanged — clean OpenAI chunks, 0 Claude leaks. - Non-streaming OpenAI client → Kiro: unchanged — clean OpenAI completion. **Still deferred:** the Kiro **API-key (`ksk_`) auth** half of 706e6513 — there's no Kiro API key on this instance to verify the `ListAvailableProfiles` validation, and shipping unverified auth is the anti-pattern that bit PXPIPE. That half is a clean task for whenever a `ksk_` key is available. **Verification:** full suite **1288 pass** (+10: 8 upstream direct-route tests + 2 non-streaming), production build clean, and all four Claude/OpenAI × streaming/non-streaming paths verified live on a real Kiro account.Fix a backwards arg order in v0.5.114; defer the Kiro headless port
Two things: a real bug fix in the v0.5.114 GitHub Copilot port, and an honest deferral of the Kiro headless port after its risk surfaced during the work. ## Bug fix: GitHub Copilot /v1/messages response translation was backwards v0.5.114 routed Claude models through Copilot's `/v1/messages` shim and translated the Claude SSE back to OpenAI for the client with `translateResponse(FORMATS.OPENAI, FORMATS.CLAUDE, …)`. That is backwards. Verified against the production streaming path (`chatCore` sets `sourceFormat = detectFormat(body)` = the **client** format, `targetFormat = getTargetFormat(provider)` = the **provider** format, and calls `translateResponse(targetFormat, sourceFormat, …)`): `translateResponse` is `(targetFormat = PROVIDER, sourceFormat = CLIENT)`. The shim returns CLAUDE (provider) to an OPENAI client, so the call must be `translateResponse(FORMATS.CLAUDE, FORMATS.OPENAI, …)`. With the backwards args, step 1 (`target → openai`) was skipped because target was OPENAI, and step 2 then applied `openai → claude` to an already-Claude chunk — so a Copilot+Claude response would have reached the client un-converted (double-wrong). v0.5.114 shipped it because there was no GitHub Copilot connection to live-verify against, so the mistake rode in on static-only verification. Both call sites are fixed and the test now pins the correct order. ## Deferred: Kiro headless API-key auth + direct claude↔kiro route (upstream 706e6513) This 1437-line, 20-file commit bundles two features, and both hit a wall that makes an unattended overnight port irresponsible: - **API-key (`ksk_`) auth** — validates a key via CodeWhisperer `ListAvailableProfiles`. There is no Kiro API key on this instance, so the positive path is unverifiable. Shipping unverified auth code is exactly the anti-pattern that bit PXPIPE. - **Direct claude↔kiro translation route** — the request side is safely additive, but the *response*-side dispatch key (`${targetFormat}:${sourceFormat}`) can collide with the common `openai:claude` response translator, i.e. it changes behavior for **every** OpenAI-format provider with a Claude client, not just Kiro. It also needs a `translator/schema/` module this fork lacks and reroutes existing Kiro+Claude traffic. Whether the short-circuit is truly equivalent to the two-step pivot needs careful, attended verification across providers — not a rushed overnight change. After finding one backwards-arg bug tonight from exactly this kind of subtle translator reasoning, forcing a second, broader-blast-radius change into the same release would be reckless. The Kiro headless port is a clean, self-contained task for an attended session (with a `ksk_` key for the auth half). The exploratory changes were reverted; the tree is clean. **Verification:** full suite **1278 pass** (stable across two runs), production build clean.Headroom token saver (completed the half-ported feature)
Our fork already shipped the Headroom **UI** (a card in the Token Saver page calling `/api/headroom/*`), but the entire **backend was missing** — those routes 404'd and the compression never ran. This release adds the backend and wires it into the request pipeline, completing the feature end to end. Headroom is an external Python proxy (`pip install headroom-ai[proxy]`, or with the `[ml]`/`[code]` compression extras) that de-duplicates and compresses conversation context — repeated file contents, large tool outputs, long histories — before the request reaches the provider. It joins RTK / Caveman / Ponytail / PXPIPE as a fail-open token saver. **What was added (ported from upstream b55cf36d + f1f9d270 + 74d5fedf):** - `src/lib/headroom/{detect,process}.js` — binary/interpreter detection and proxy lifecycle (start/stop/restart, extras install/uninstall). - `open-sse/rtk/headroom.js` — `compressWithHeadroom`, which POSTs the conversation to the proxy's `/v1/compress` and swaps in the compressed messages. Fails open (returns the request untouched) on any error, timeout, or missing proxy. - 6 API routes (`status`, `start`, `stop`, `restart`, `extras`, and a dashboard `proxy/[...path]` passthrough) — the endpoints the existing UI was already calling. - `chatCore` runs headroom in the token-saver block (mutates the body in place, logs before/after tokens); `chat.js` threads the settings to both dispatch paths; settings default it off. **Verified end-to-end on a live proxy — the proof is in the token counts:** - Installed `headroom-ai[proxy][ml]` (v0.32.0, Python 3.13), started the proxy, confirmed our `detect` finds the binary. - Fail-open confirmed: headroom enabled + proxy down → the request still succeeds untouched. - A real code-heavy request through krouter with headroom enabled logged: `[HEADROOM] reported token delta=25131 before=27187 after=2056 (92.4%)` — 27,187 → 2,056 tokens, **92.4% saved**, and the provider accepted the compressed body (correct reply). The direct `/v1/compress` probe showed the same: 99,776 chars → 7,263 (92.7%). - The compression magnitude depends on content and the installed extras (repeated-code context compresses ~92%; plain prose barely moves) — the base `[proxy]` extra alone reports 0% and the `[ml]`/`[code]` extras do the real work, which is exactly what the extras install/uninstall manages. **Note on this machine:** the local Python is pipx-isolated, so `getHeadroomStatus` reports `version:null`/`canStart:false` even though the binary runs — a probe quirk of this specific setup, not the port; a standard `pip install headroom-ai` puts the console script on PATH normally. **Verification:** full suite **1278 pass** (+11), production build clean, all 6 headroom routes registered, and a real 92.4% compression proven through the full request pipeline.GitHub Copilot: route Claude through the native /v1/messages shim
Port of upstream 542a088c. Claude models on GitHub Copilot now go to Copilot's Anthropic-native `/v1/messages` endpoint instead of `/chat/completions` — the only Copilot endpoint that surfaces prompt-cache token counts for Claude, and the path that lets `cache_control` actually get injected. **How it works:** - `execute()` detects Claude models by name (`/claude/i`) — not a static registry field, because Copilot's live catalog regularly exposes `claude-*` variants ahead of our static list — and routes them to the new `executeWithMessagesEndpoint`. - Claude requests arrive OpenAI-shaped (chatCore targets `openai` for github), so the method translates OpenAI→Claude for the shim, forces `stream:true` upstream (chatCore buffers to JSON when the client asked for non-streaming), strips the internal `_toolNameMap` before dispatch (Anthropic 400s on the extra field), and translates the Claude SSE back to OpenAI for the client. - `buildHeaders` now sends `anthropic-version` (a no-op on the other endpoints, required by `/v1/messages`), and the backend config gains `messagesUrl`. - gpt / gemini / grok models are unchanged — they stay on `/chat/completions` (or `/responses`). **Fork adaptation:** upstream's `translateResponse(source, target)` is the reverse of ours — our signature is `translateResponse(targetFormat, sourceFormat, …)`. Verified against our translator's actual signature and existing call sites (`bypassHandler`, `stream.js`), so the response call reads `(OPENAI, CLAUDE)` here, not upstream's `(CLAUDE, OPENAI)`. Getting this backwards would have silently corrupted every Copilot+Claude response. **Verification:** full suite **1271 pass** (+4), production build clean (all imports resolve, executor compiles). Routing, headers, config, and the arg-order are covered by tests. **Not live-verified:** there's no GitHub Copilot connection on this instance, so a real `/v1/messages` round-trip couldn't be exercised — the verification here is static (build + logic + translator-signature match), not an end-to-end request.Upstream quick-wins batch
Six small upstream features/fixes we were missing, verified together. - **SearXNG `SEARXNG_URL` env** (upstream e79f9edd) — the built-in unauthenticated SearXNG provider was pinned to `http://localhost:8888/search`. Self-hosters can now point at their own instance (`SEARXNG_URL=http://searxng:8080/search`). The override lives in `runtimeConfig` + `buildSearxngRequest`, and only kicks in when the env is set, so the manifest default is unchanged. - **Bulk-delete connections** (upstream 644bff4c) — a "Delete Selected (N)" action on the provider page that removes every checked connection in one confirm. The selection infrastructure already existed here; this adds the handler + button. - **Kiro GPT-5.6 family** (upstream b94685b8) — `gpt-5.6-sol/terra/luna` added to the Kiro fallback catalog (272k context). A Kiro account with access serves them via the live catalog; the `*gpt-5.6*` capability pattern already routes them. - **Strip `client_metadata` on responses→openai** (upstream e567ba80) — the Responses-API `client_metadata` field is now dropped when converting to plain Chat Completions, which rejects it. - **Gate the auto-ping scheduler at startup** (upstream 27b37705) — the quota auto-ping interval no longer spins up on a fresh install where no connection opted in; it starts only when `claudeAutoPing`/`codexAutoPing` has an enabled connection. **Deferred:** the Kiro direct-session-cache change (upstream 9c58ba64) is 400+ lines of Kiro translator internals — a perf optimization, not a correctness fix — and doesn't belong in a quick-wins batch. It gets its own careful pass. **Verification:** full suite **1267 pass** (+8), production build clean, provider page + `/v1/models` serve with no regression. New regression tests cover all five changes.PXPIPE was inert for real traffic; now it actually compresses
A correction to v0.5.111. PXPIPE shipped wired and fail-open, but **it never compressed a real request** — and the v0.5.111 verification missed it because the checks only exercised fail-open paths and the package's own self-test. **The gap:** `[email protected]` images **only `claude-fable-5`** by default. Every real model — `claude-opus-4-8`, `claude-sonnet-4-6`, and every claude-format provider — returns `unsupported_model` and passes through untouched. The package exposes `setAllowedModelBases()` to widen the allowlist, but nothing called it: not the upstream commit, not our port. A user could enable PXPIPE, send their normal Claude Code request, and get exactly zero compression with no error — the dashboard's own "Model not in allowlist" status was the only hint. **The fix:** - New `configureModelBases()` in the pxpipe loader pushes the operator's allowlist into the package (via `applicability.js`, which `library.js` imports internally — same module instance, so the transform actually reads it). - New `pxpipeModels` setting, defaulting to vision-capable Claude bases (`claude-fable-5`, `claude-opus-4`, `claude-sonnet-4`, `claude-haiku-4`). The list is an explicit allowlist, never "all models" — imaging only works for models that can read images. - `chat.js` configures the allowlist before loading the transform on every enabled request. **Verified end-to-end on a live Claude connection — the proof is in the billing:** Sent a real `/v1/messages` request to `cc/claude-opus-4-8` with a 123,634-char system prompt and PXPIPE enabled: ``` [PXPIPE] imaged 124593ch → 5 image(s) | est 31289→6964 tokens (-77.74%) | 402ms ``` - Claude **accepted** the 5-PNG body (no error) and streamed a response. - Actual provider-billed `input_tokens: 8726` — that raw system prompt would bill ~30k tokens; as PNGs it billed 8,726. The saving is real, confirmed by billing, not just the pre-send estimate. - Claude **read** the imaged content (it answered about what was in the images), proving vision-decode of the compressed context worked. Before this fix, the identical request returned `unsupported_model` and compressed nothing. **Why v0.5.111's verification missed it:** every check I ran was either fail-open (works whether or not compression happens) or used the package's default `claude-fable-5` self-test. I never sent a real model through the full path, so "inert for all real traffic" looked identical to "working." The lesson is now a standing test: the allowlist must be configured, asserted against the real transform with a real model id, not just against the package loading. Full suite: **1259 pass** (+3), production build clean.Grok Imagine video + PXPIPE, plus two bugs the live tests caught
Two Tier B features land together, each verified against real endpoints on a live account, plus two real bugs surfaced along the way. ## Grok Imagine video (upstream d6761c6f) — verified end-to-end on a real account A new `/v1/videos` surface (generations / edits / extensions + status polling) proxying xAI's async Grok Imagine jobs. - `videoCore.js` is a transparent proxy: forwards the body byte-for-byte, passes `request_id` / `status` / `video.url` back verbatim, refreshes once on 401/403 and retries once, never re-sends a creation POST on a network error (the job may already exist). Upstream reads its endpoint from a `PROVIDER_MEDIA` registry this fork doesn't have; ours keeps a small self-contained `VIDEO_CONFIG` instead. - 4 routes, the sse handler, `grok-imagine-video` (kind `video`), the `video` serviceKind on xai, and the Sidebar entry — all wired. **Proven live against your xAI account:** submitted a real job → `request_id: f00c3438…` → polled → status `done` → a playable video URL at `https://vidgen.x.ai/…`. The token was expired in storage (8h TTL); our server refreshed it and xAI accepted the job. Full round trip, not a mock. **Bug found while wiring it:** the `[kind]` media route's slug map had no `video` entry, so `/v1/models/video` returned "Unknown model kind" even though the Sidebar links a video page and xai publishes a video model. Upstream's own port missed this. Fixed — `/v1/models/video` now returns exactly `xai/grok-imagine-video`, and the main `/v1/models` correctly keeps video out of the LLM list. ## PXPIPE (upstream dcf1927f) — context-to-PNG token saver, verified with the real package Renders bulky Claude-format context as dense PNGs via the `pxpipe-proxy` library (images bill by pixels, not encoded length). It joins RTK / Caveman / Ponytail as a fail-open token saver — runs last in the pipeline, and any error, timeout, or missing install returns the request untouched. - The `pxpipe-proxy` package is **never bundled**: it installs on demand into the data dir (same lazy pattern as our sqlite/systray runtime deps) and loads via dynamic import. No new hard dependency in `package.json`. - 8 management routes (status / health / install / start / stop / restart / logs / stats), a dashboard page, settings (off by default, 25k-char threshold), and per-request savings threaded into the request-detail log. **Verified live end-to-end:** - Before install: status `installed:false`, health cleanly reports "not installed", and a real Claude request still succeeds (fail-open). - Installed the real `[email protected]` into the data dir → health goes green (all three checks: installed ✓, module loads ✓, transform runs ✓). - Enabled it, sent a real request → still 200 (the package made its own profitability decision and passed the request through — fail-open, exactly as designed). Stats and logs routes functional. The package's v0.9.0 profitability heuristic is conservative and opaque about which payloads it images; what this release guarantees is that our integration invokes it correctly and never lets it break a request. ## Bug: grok-cli OAuth tokens never refreshed (regression from 0.5.110) `grok-cli` shipped last release with **no case in `refreshTokenByProvider`**, so it fell to the default `refreshAccessToken`, which needs a `clientId` grok-cli's backend config doesn't carry — refresh always failed, and OAuth connections died after xAI's ~8h token TTL. This was a real 401 hit on a day-old grok-cli connection. grok-cli tokens **are** xai tokens (same public client `b1a00492…`, same `auth.x.ai/oauth2/token`), so the fix routes grok-cli through the exact same `refreshXaiToken` path as xai. **Verified live:** `refreshTokenByProvider("grok-cli", …)` returned a fresh token (was `null` before), and a grok-cli chat that had been 401-ing came back with `"mango"` (431 in / 349 out). A scan confirmed grok-cli was the only provider on that refresh path with the gap. ## Verification Full suite **1256 pass** (+49 across the two features and their regressions). Production build clean, all new routes registered. New guards: the `[kind]` route must recognize `video`, chat.js must thread the pxpipe transform to both chatCore calls, and grok-cli refresh must route through the xai path.Tier C complete: Grok CLI (Grok Build) + two routing bugs it exposed
Adds the fourth and largest Tier C provider, and fixes two silent routing bugs that only a real request could surface. Both were found by chatting through a live Grok Build account — the unit tests passed the whole time. **Grok CLI / Grok Build** (upstream a11937cd + 7dfb3466 + 59b78282) A third Grok-family provider, distinct from the two we already had: | provider | endpoint | pays with | |---|---|---| | `xai` | api.x.ai | xAI API credits | | `grok-web` | grok.com | web SSO cookie | | `grok-cli` (new) | cli-chat-proxy.grok.com | **Grok Build subscription** | - OAuth is a **device code** flow on auth.x.ai — same public client as `xai`, plus `conversations:read/write` scope and `referrer=grok-build`. No loopback proxy, unlike our xai PKCE flow. Verified live: xAI's discovery advertises `urn:ietf:params:oauth:grant-type:device_code`, and our flow returns a real user code against `accounts.x.ai`. - Ported upstream's executor with our fork's paths. Upstream's `resolveSessionId()` does not exist here, so `resolveGrokCliSessionId` walks the same precedence by hand. Our `deriveSessionId` emits `uuid + Date.now()` (another provider's binary format); since these headers exist to match the CLI's fingerprint, we hash to a **well-formed UUID** instead — which also stays stable across process restarts, as an in-memory map cannot. - Published models come from the **live catalog read off a real account**: `grok-4.5` at 500k context with low/medium/high efforts, high default. The `-low`/`-medium`/`-high` entries are virtual — the executor strips the suffix and maps it to `reasoning.effort` — so any client that only speaks model names can pin an effort. Upstream's config also lists an `xhigh` tier; the live API does not advertise it, so we do not publish it. **Bug 1 — published aliases that could not route.** Two independent alias tables exist: `PROVIDER_ID_TO_ALIAS` (drives the published catalog) and `ALIAS_TO_PROVIDER_ID` (drives request routing). Nothing enforced that they agree. `resolveProviderAlias` falls back to `map[alias] || alias`, so an alias equal to its provider id routes correctly *by accident* — which is why `clinepass` and `kimchi` worked. `cbcn` and `gcli` do not equal their ids, so they resolved to providers that do not exist. `gcli/*` ended up at api.x.ai and returned `401 invalid_issuer` — an error that points nowhere near the cause. **`codebuddy-cn` shipped broken in 0.5.109 for this reason.** Fixed here, with a guard that asserts every alias we publish models under resolves to a real backend provider — asserted against the resolver itself, not the table's text. **Bug 2 — Responses-API providers returned empty replies to non-streaming clients.** Two gates had to be right and both were wrong: - `chatCore.providerRequiresStreaming` was a hardcoded list (`openai`/`codex`/`commandcode`). grok-cli and codebuddy-cn force `stream: true` in their executors but were not listed, so chatCore took the non-streaming path and tried to parse an SSE body as JSON. - `sseToJsonHandler` gated its Responses-API branch on `sourceFormat` — the **client's** format — so any Responses-API provider other than codex fell through to the chat.completions aggregator, which hunts for `choices[].delta.content` in a stream that only carries `response.output_text.delta`. The gate now keys off the **provider's** format, which is what the stream shape actually depends on. The failure mode was the dangerous kind: HTTP 200, real tokens billed, empty message. Streaming worked perfectly the entire time, which is what made it easy to miss. **Verified end-to-end against a real Grok Build account:** - Non-streaming `gcli/grok-4.5-low` → reply `"mango"`, `finish_reason: stop`, usage 431 in / 127 out. Before the fixes: `401 invalid_issuer`, then an empty reply. - Streaming → `delta:{"content":"mango"}` with 13 reasoning deltas ahead of it, usage 2431 in / 410 out with 384 cached. - All three effort variants return real replies: `grok-4.5` → "OK.", `grok-4.5-low` → "OK", `grok-4.5-high` → "OK". - Server log confirms the correct upstream: `GROK-CLI → https://cli-chat-proxy.grok.com/v1/responses ← 200 | ttft=473ms`. - All four Tier C OAuth flows re-checked live afterwards — grok-cli returns a real device code, CodeBuddy CN a real Tencent state, ClinePass and Kimchi real authorize URLs. Full suite: **1207 pass** (+23), production build clean. New guards lock both bugs: every published alias must resolve to a real provider, and every executor that forces streaming must be declared in chatCore — the latter scans the executor directory rather than trusting a hand-kept list. **Tier C is complete.** All four providers (ClinePass, CodeBuddy CN, Kimchi, Grok CLI) are wired, tested, and verified against live endpoints.Tier C part 1: three OAuth providers + a real translation bug
Ports ClinePass, CodeBuddy CN, and Kimchi from upstream. Every endpoint below was probed live before being wired — the 0.5.108 lesson (upstream published a model Google 404s) applies to endpoints too, and it caught one dead config here. **ClinePass** (upstream b08751c4) — Cline's subscription pass, a distinct provider from `cline` with its own `cline-pass/*` model namespace, but the same auth backend. - Upstream ships this as a verbatim 50-line copy of the `cline` OAuth block. Ours derives both from one `createClineOAuthFlow(config, label)` factory, so a fix to the base64-in-code exchange — or a move of Cline's auth host — lands in both at once. - Caught a trap upstream's shape hides: the Cline header path is gated on `provider === "cline"`. ClinePass would have fallen through to the generic Bearer branch and sent an **unprefixed token** (no `workos:`), producing a silent 401 with no obvious cause. Fixed in both `executors/default.js` and `services/provider.js`. - We skipped upstream's `workos:` fix inside `refreshCline`: our `getClineAccessToken` already normalizes the prefix at the point of use, which also repairs tokens stored before the change. - Verified live: our generated authorize URL is accepted by Cline (**302**, not 404), and `api.cline.bot/v1/models` answers **401 with a genuine Cline error** — endpoint reachable, headers land. **CodeBuddy CN** (upstream efd20be8) — Tencent's `copilot.tencent.com` gateway. We had a dormant `codebuddy` inherited at fork time: commented out of the UI since our initial release, pointed at **v1**. Probing found `v1/chat/completions` returns **404 "Route Not Found"** while **v2** returns 401 — the old config could never have worked. Renamed to `codebuddy-cn` (safe: the UI entry was never enabled, so no connection with the old id can exist) and moved to v2 with the CLI fingerprint headers the gateway gates on. - New executor absorbs two gateway quirks: non-stream requests are rejected outright (**400, code 11101**), so `stream` is forced true and kRouter re-aggregates the SSE for non-streaming clients; and reasoning only surfaces when the request carries `reasoning_effort` + `reasoning_summary: "auto"`, which our thinking pipeline never sets on its own. `none`/`off` omits the param entirely — the gateway has no such tier. - **Verified live end-to-end**: the device-code flow through our own server returned a real Tencent login URL with a valid UUID state — `{"code":0,"msg":"OK"}` — that a user could open right now. **Kimchi** (upstream 8a664d61 + 76752a43 + 7afaecd6) — OpenAI-shaped gateway fronting several upstreams. - New `browser_token` flow: the user signs in at `app.kimchi.dev/cli-auth` and the browser returns the token on the callback as `?token=`, so there is no code to exchange — we validate it and read the profile. **No OAuth engine changes were needed**: our `generateAuthData` already falls through to `buildAuthUrl(config, redirectUri, state)` for any non-device/non-PKCE flow. - Executor strips what an OpenAI gateway rejects from a Claude-format request: Anthropic-only top-level fields, `cache_control`/`signature` artifacts, and reasoning params for Anthropic-backed models. A top-level `system` is **merged into messages** rather than dropped — dropping it would silently lose the whole prompt. - Echoed `reasoning_content` is stripped from assistant turns (>8 chars, so the injected 1-char placeholder survives) — SDKs echo full history and Kimchi bills the scratch block as input, ballooning multi-turn past 100k tokens. - Verified live: our authorize URL loads Kimchi's real login page (**200**); the catalog and validation endpoints both answer 401 (exist, need auth). **Bug found and fixed: Claude clients got OpenAI response bodies (all providers, not just Kimchi).** While porting Kimchi's handler change we found our `translateNonStreamingResponse` returned the raw body whenever the provider was OpenAI-format — so a Claude-format client on `/v1/messages` received `choices[]` and could not parse it. The streaming path translated correctly; **only non-streaming leaked**. This affected every OpenAI-format provider — most of our 96. Proven live against a real account before and after: | | Before | After | |---|---|---| | keys | `id, object, created, model, choices, usage, system_fingerprint, service_tier` | `id, type, role, model, content, stop_reason, stop_sequence, usage` | | body | OpenAI completion | `{"type":"message","content":[{"type":"text","text":"Mango"}],"stop_reason":"end_turn"}` | The conversion reuses our existing `convertFinishReason` (now exported) so streaming and non-streaming map stop reasons identically, and an `isClaudeMessageResponse` guard keeps the downstream OpenAI-shaping steps from stamping `object`/`created` onto a Claude body. **Verification:** full suite **1184 pass** (+64 new), production build clean, all four upstream logos ship real PNGs, and `cline` + `codebuddy-cn` + `kimchi` authorize flows were re-checked live after every refactor. **Grok CLI (Grok Build) lands next.** It is the largest of the four (a 552-line executor with turn-index tracking and `store=false` continuity, plus a usage tracker and models service) and deserves its own release. The groundwork is already proven: xAI's device-code flow returns a real code, `cli-chat-proxy.grok.com` answers **200** for models and billing on an existing xAI OAuth token, and the live catalog shows `grok-4.5` at 500k context with low/medium/high efforts — so it can be verified end-to-end rather than shipped on faith.