clauderipple 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/README.ko.md +18 -2
  3. package/README.md +27 -5
  4. package/dist/app/package.json +1 -1
  5. package/dist/cli/src/claude-auth.js +35 -1
  6. package/dist/cli/src/codex.js +23 -0
  7. package/dist/cli/src/hooks/agent-title.js +58 -29
  8. package/dist/cli/src/index.js +46 -1
  9. package/dist/cli/src/picker.js +19 -11
  10. package/dist/cli/src/runtime.js +3 -0
  11. package/dist/cli/src/settings.js +49 -0
  12. package/dist/mod/.claude-plugin/plugin.json +1 -0
  13. package/dist/mod/hooks/hooks.json +1 -0
  14. package/dist/mod/hooks/register.tsx +309 -0
  15. package/dist/mod/hooks/worker.ts +49 -0
  16. package/dist/mod/tsconfig.json +3 -0
  17. package/dist/mod/types/index.d.ts +30 -0
  18. package/dist/router/src/admin.js +411 -32
  19. package/dist/router/src/agents.js +24 -8
  20. package/dist/router/src/catalog.js +80 -0
  21. package/dist/router/src/compat.js +27 -1
  22. package/dist/router/src/config.js +31 -0
  23. package/dist/router/src/index.js +12 -1
  24. package/dist/router/src/ingress/server.js +3 -2
  25. package/dist/router/src/picker.js +33 -2
  26. package/dist/router/src/presets.js +2 -1
  27. package/dist/router/src/providers/chatgpt/index.js +77 -18
  28. package/dist/router/src/providers/chatgpt/translate.js +34 -6
  29. package/dist/router/src/providers/google/accounts.js +345 -0
  30. package/dist/router/src/providers/google/antigravity.js +392 -0
  31. package/dist/router/src/providers/google/index.js +617 -0
  32. package/dist/router/src/providers/google/login.js +249 -0
  33. package/dist/router/src/providers/google/thoughtSignatures.js +47 -0
  34. package/dist/router/src/providers/google/translate.js +539 -0
  35. package/dist/router/src/providers/google/transport.js +118 -0
  36. package/dist/router/src/providers/openai/translate.js +3 -3
  37. package/dist/router/src/proxy.js +116 -9
  38. package/dist/router/src/requestlog.js +2 -0
  39. package/dist/router/src/routing.js +22 -0
  40. package/dist/router/src/version.js +1 -1
  41. package/dist/ui/app.js +420 -45
  42. package/dist/ui/i18n.js +76 -4
  43. package/dist/ui/index.html +5 -0
  44. package/dist/ui/style.css +4 -0
  45. package/docs/ARCHITECTURE.md +307 -4
  46. package/package.json +1 -1
@@ -48,6 +48,35 @@ Why this path is stable:
48
48
 
49
49
  ## 2. What the app does with the official third-party setting (why competitors lose)
50
50
 
51
+ ### Optional Auto mode classifier target
52
+
53
+ `autoModeClassifier: { provider, model, effort }` selects the inference target for
54
+ Claude Code's tool-free security-monitor system requests. It is opt-in: without
55
+ this setting, native routing remains unchanged. For example:
56
+
57
+ ```json
58
+ "autoModeClassifier": {"provider": "codex", "model": "gpt-6-luna", "effort": "low"}
59
+ ```
60
+
61
+ The named provider must exist and support outbound inference. Detection uses the
62
+ security-monitor system prompt, never user messages or routing markers. The
63
+ original security prompt, context, output format and native decision logic remain
64
+ intact. Configure this field in the JSON config; the settings GUI has no control
65
+ for it yet. Removing the field restores ordinary routing.
66
+
67
+ This request is Auto mode's safety check: it decides whether an action the agent
68
+ wants to take runs without asking. A target that judges worse than the model
69
+ Claude Code chose lets more through, and nothing reports it, since an allowed
70
+ action looks like any other. Choose the target for its judgement, not for its
71
+ quota. Matching keys on the classifier prompt's opening sentence; if a Claude Code
72
+ release rewords it, the requests stop matching and go back to the native
73
+ classifier, which is the safe direction to fail in.
74
+
75
+ Verified with Desktop's Claude Code 2.1.284: both classification stages used the
76
+ configured target; an authorized temporary-file write proceeded and an explicitly
77
+ forbidden deletion was denied. The registered `CLAUDE_CODE_AUTO_MODE_MODEL`
78
+ environment variable did not change the actual native classifier requests.
79
+
51
80
  Verified in `app.asar` (`.vite/build/index.chunk-C0sgyfNn.js`, `-2CuMdv3h.js`):
52
81
 
53
82
  - The gateway setting (`inferenceGatewayBaseUrl`) is a **whole-app deployment
@@ -114,6 +143,14 @@ chat is out of reach for every approach, ours included.
114
143
  router logs `PICKER injected … surfaces: …` on each bootstrap.
115
144
  - Injecting `additional_model_options` into the Claude Code bootstrap response
116
145
  does **not** reach the app picker (measured; the app never reads that field).
146
+ - **The effort menu is per entry.** The Code tab reads each model's own
147
+ `thinking.effort_options` (ids from `low/medium/high/xhigh/max`; an option may
148
+ carry `recommended`, which the app falls back to when the selected level is not
149
+ listed) — read from the app's cached web bundle, 2026-10-02. A cloned Claude
150
+ entry therefore offered Claude's ladder for every routed model; the injected
151
+ entry now keeps only the template options whose id the model's ladder holds,
152
+ recommends high (or the nearest), and drops `thinking` when none remain. The
153
+ real bootstrap's option fields beyond `id`/`recommended` were not captured.
117
154
  - **Windows uses the same mechanism** (measured end to end in a Windows 11 VM,
118
155
  2026-09-14: picker listed the GPT models, a call routed, the model answered).
119
156
  Two things differ and nothing else does:
@@ -128,6 +165,47 @@ chat is out of reach for every approach, ours included.
128
165
  The app's own log line confirms the proxy took effect:
129
166
  `[egress-proxy] pinned to fixed proxy at 127.0.0.1:<port>; OS proxy settings ignored`.
130
167
 
168
+ ### 3a. The status mod (packages/mod)
169
+
170
+ Claude Code mods (function hooks in a plugin, announced 2026-10-01; on by default
171
+ from CLI 2.1.287, and already loading in the desktop app's 2.1.286 on 2026-10-05)
172
+ draw inside the session itself, on the terminal and on the desktop alike.
173
+ `packages/mod` is one: the `AbovePrompt` band shows where this session's last
174
+ request went, and a `Pane` holds the request log.
175
+
176
+ - **How a session's requests are found.** Claude Code sends its session id as
177
+ `X-Claude-Code-Session-Id` on every `/v1/messages` (captured from CLI 2.1.286
178
+ with a local listener, 2026-10-05: the header equalled `--session-id`). The
179
+ router keeps it on the request record and `/api/requests?session=` filters by
180
+ it. A record without the field means a router older than this; the mod says
181
+ so rather than show another session's route.
182
+ - **The display only reads** the admin API (port from `config.json` as
183
+ `adminPort` works it out).
184
+ - **It also sets worker spawns right** (`hooks/worker.ts`, `agent.spawn`), for
185
+ the agents named in `generated-agents.json` only. A `model` argument on the
186
+ Agent call is replaced by the agent file's own id (a Claude alias there ran
187
+ the worker on Claude with its output cut to 64 tokens), with a toast. A
188
+ `[[ripple: name@level]]` marker anywhere in the prompt that names this worker
189
+ becomes the id's `@level` and leaves the prompt; one naming another alias is
190
+ moved to the first line, the only place `routing.ts` reads it. Measured on
191
+ CLI 2.1.286 (2026-10-05): the hook's `model` is what the subagent runs on —
192
+ `model: "sonnet"` plus the marker on the second line reached the router as
193
+ `deepseek-v4.1-flash@low`, effort low. Without the mod, the first-line rule
194
+ and the no-`model` rule stand as before.
195
+ - **Where it can draw.** The desktop app's header icons (terminal, diff,
196
+ browser) are the app's own; the mod API (CLI 2.1.286 types) has no place for
197
+ a plugin's button there. Its sites are the band, the status line, panes,
198
+ transcript rows and slash commands.
199
+ - **A pane's open state is the engine's.** A module reload drops its pane
200
+ without a `ui.close` the module hears, while `$.state` survives the reload: a
201
+ flag kept there stuck the Log button on "Close log" (2026-10-05). The mod asks
202
+ `$.ui.panes()` instead.
203
+ - **Enabling it** is `env.CLAUDE_CODE_PLUGIN_DIRS` in `~/.claude/settings.json`,
204
+ the one place a desktop-started session reads plugin folders from. It is a
205
+ path list other plugins may share, so only the entry whose manifest is named
206
+ `clauderipple-status` is added or removed (`clauderipple mod on|off`, the
207
+ Clients screen switch).
208
+
131
209
  ## 4. Provider adapters
132
210
 
133
211
  - **Declared models route themselves (2026-09-19).** A provider's `models` list —
@@ -162,7 +240,8 @@ chat is out of reach for every approach, ours included.
162
240
  also a subagent the operator can name: for every id exactly one non-ingress
163
241
  provider declares, the router writes `~/.claude/agents/<name>.md` (`agents.ts`;
164
242
  `name` = the id with anything outside `[a-z0-9-]` folded to `-`, frontmatter
165
- `model:` = the exact id, `@medium` when the provider offers that level). It
243
+ `model:` = the exact id, `@high` when the provider offers any effort level —
244
+ `@medium` until 2026-10-02). It
166
245
  records what it wrote in `<home>/generated-agents.json` and touches **only**
167
246
  those files: a hand-written agent of the same name wins and is left alone, an
168
247
  unticked model removes its generated file and nothing else, identical content is
@@ -223,8 +302,22 @@ chat is out of reach for every approach, ours included.
223
302
  `/zen/v1/responses`, not the Codex backend): gpt-6-sol, gpt-6-luna,
224
303
  gpt-5.6-sol and gpt-5.6-luna all accept `none, low, medium, high, xhigh,
225
304
  max` and answer `400 param: reasoning.effort` to both `minimal` and
226
- `ultra`. So `ultra` is not a level that API takes on any of them; whether
227
- the Codex backend does is still open.
305
+ `ultra`. So `ultra` is not a level that API takes on any of them. **Settled
306
+ 2026-10-06 on the Codex backend too:** gpt-6.1-sol with `reasoning.effort:
307
+ "ultra"` answered `400 Invalid value: 'ultra'. Supported values are: 'none',
308
+ 'minimal', 'low', 'medium', 'high', 'xhigh', and 'max'`, while `max` answered
309
+ 200. The catalogue's own description of `ultra` is "Maximum reasoning with
310
+ automatic task delegation": it is `max` on the wire plus the Codex CLI
311
+ spawning its own sub-agents, which in Claude Code are Claude Code's
312
+ subagents. Keep the clamp. (`reasoning.mode` takes `standard`/`pro`, and
313
+ gpt-6.1-sol refuses `pro`: "not supported with this model".)
314
+ - **Credits (2026-10-06).** Every Codex response carries
315
+ `x-codex-credits-has-credits` / `-unlimited` (`True`/`False`) and
316
+ `-balance`; `/wham/usage` has `credits: {has_credits, unlimited,
317
+ overage_limit_reached, balance}`. OpenAI's help centre: included usage is
318
+ spent first, then purchased credits. So a window at 100% with credits left
319
+ is not an exhausted account — `exhaustedForMs` no longer rests it, and a
320
+ real 429 still does.
228
321
  - **Model catalogue (2026-09-23).** `GET {base}/codex/models?client_version=<v>`
229
322
  with the same credentials answers `{ models: [{ slug, display_name,
230
323
  visibility: "list"|"hide", context_window, supported_reasoning_levels:
@@ -348,6 +441,20 @@ chat is out of reach for every approach, ours included.
348
441
  from the start; the ChatGPT and openai-compatible translators do the same, and a `tool_choice`
349
442
  that named a dropped tool is dropped with it. The adapter logs each drop by name, because the
350
443
  failure it replaces is silent.
444
+ - **A dropped server tool's instructions are withdrawn too (issue #47).** The advisor does reach
445
+ adapters: Claude Code 2.1.286 declares `{ type: "advisor_20260301", name: "advisor", model }`
446
+ on every subagent whatever its `tools:` list says, and explains it in an `isMeta` user message
447
+ (`# Advisor Tool`, "call advisor BEFORE substantive work"). Measured 2026-10-04: `dropped server
448
+ tools for gpt-6-astra: advisor` on every worker request, and 26 of 28 routed worker transcripts
449
+ reporting the tool missing. Dropping only the declaration leaves an instruction the model cannot
450
+ follow. Each translated path that drops server tools therefore appends one line after the
451
+ caller's system text naming them and telling the model to disregard instructions to call them
452
+ (`droppedServerToolNote`), the same move the CLI makes when it removes the advisor itself ("The
453
+ advisor tool is no longer available; disregard the earlier advisor instructions."). The CLI's
454
+ wording is not matched or stripped, since it changes between releases. Names are sorted, so one
455
+ set yields one byte sequence and the cached prefix holds. An anthropic-compatible provider with
456
+ `serverTools: true` keeps every server tool and gets no line; whether DeepSeek runs `advisor` is
457
+ unmeasured.
351
458
  - **Where web search actually runs (measured 2026-09-17).** Claude Code does not put `web_search`
352
459
  in the main request. `WebSearch` opens a *separate side request* — system prompt "You are an
353
460
  assistant for performing a web search tool use", one message "Perform a web search for the
@@ -624,6 +731,12 @@ chat is out of reach for every approach, ours included.
624
731
  this turn), the caller's own `Authorization`/`chatgpt-account-id` is used
625
732
  once as sent — pointing Codex here never leaves it worse off. A WebSocket
626
733
  upgrade is answered 426 so Codex falls back to SSE.
734
+ - **Codex app model list.** `codex on` also points `model_catalog_json` at
735
+ `~/.codex/clauderipple-models.json`: Codex's own `models_cache.json` entries
736
+ plus ours. That file *replaces* Codex's list, so it must follow both sides —
737
+ the router rewrites it when its config loads and when Codex's cache changes
738
+ (checked once a minute; the app reads the catalog only at startup). With the
739
+ first alone, gpt-6.1-sol (2026-09-30) reached Codex's cache but not the app.
627
740
  - Configure a native API-key target as
628
741
  `{ "type":"anthropic", "auth":"api-key", "apiKey":"…" }` (or set
629
742
  `ANTHROPIC_API_KEY`). Configure a compatible target as today, for example an
@@ -791,7 +904,16 @@ what we do not have yet — so that adding a provider does not start with readin
791
904
  `effortLevels` overrides provider defaults, including `[]` for “no effort”. When
792
905
  an OpenRouter `/models` entry has `supported_parameters`,
793
906
  `reasoning_effort` maps to `[low, medium, high]`; its absence maps to `[]`.
794
- Vendors that do not report that field keep their configured fallback. Chat wire
907
+ Vendors that do not report that field keep their configured fallback. For the
908
+ OpenCode presets (`opencode-go`, `opencode-zen`) a model's ladder comes from
909
+ models.dev — the catalogue OpenCode's own client reads, whose `reasoning_options`
910
+ states it — and its `provider.npm` puts that wire first; one request then settles
911
+ the wire and catches a plan refusal. Asking every level instead took ~10 sequential
912
+ requests per model (glm-5.3-flash 1m54s, upstream latency 0.5–25s per one-token
913
+ request, 2026-10-02) and was less right: a 200 means "not refused", and
914
+ glm-5.3-flash accepted seven levels where the catalogue lists `low`/`high`/`max`.
915
+ A model the catalogue lacks, or a catalogue that cannot be fetched, is measured as
916
+ before (`catalog.ts`, `capabilities.ts`). Chat wire
795
917
  forwards Anthropic `stop_sequences` as `stop`; Responses wire deliberately omits
796
918
  them because its common stateless schema has no corresponding universal field.
797
919
  - We do not assert a provider-wide cache hit rate: the adapter records
@@ -804,6 +926,167 @@ what we do not have yet — so that adding a provider does not start with readin
804
926
  credit. Preset endpoint and capability claims cite the vendor's official docs in
805
927
  `packages/router/src/presets.ts`.
806
928
 
929
+ ### 4d. `google` providers (implemented 2026-10-01)
930
+
931
+ Gemini is a translated provider, not an ingress-only one: it holds no server-side thread,
932
+ so a `thread:continue` is refused exactly as in §4c and the CLI resends a stateless
933
+ history. It routes by its declared models, its agent files are generated like any other
934
+ provider's, and a `web_search` side request aimed at it is refused, not sent. The
935
+ adapter lives in `packages/router/src/providers/google/` and separates translation
936
+ (`translate.ts`), the wire (`transport.ts`) and the signature store
937
+ (`thoughtSignatures.ts`) so Stage 2 (Antigravity Code Assist) is a new transport only.
938
+
939
+ Both modes are implemented: `api-key` (AI Studio) below, and `auth: "antigravity"` (Cloud
940
+ Code Assist) in its own subsection. No live key or Antigravity subscription exists in this
941
+ environment, so everything below is doc- and source-derived and **not measured** unless it
942
+ says otherwise:
943
+
944
+ - Stream endpoint `POST {base}/v1beta/models/{model}:streamGenerateContent?alt=sse`,
945
+ the key on header `x-goog-api-key`, base default `https://generativelanguage.googleapis.com`
946
+ (https://ai.google.dev/api/generate-content#method:-models.streamgeneratecontent).
947
+ - `systemInstruction` takes `{role:"user"|"model", parts:[…]}`; `contents` is the
948
+ `user`/`model` turn list; a part is `{text}`, `{inlineData:{mimeType,data}}`,
949
+ `{functionCall:{name,args}}` or `{functionResponse:{name,response}}`. The `role` of a
950
+ `functionResponse` part must be `user` (https://ai.google.dev/api/caching#Content).
951
+ - Tools map to `tools[].functionDeclarations[].parametersJsonSchema` — a protobuf
952
+ `Value` carrying a full JSON Schema, mutually exclusive with the legacy `parameters`
953
+ (https://ai.google.dev/api/caching#FunctionDeclaration). We send `parametersJsonSchema`.
954
+ - A function name is at most 64 characters, must start with a letter or underscore and
955
+ may contain letters, digits, underscores, dots, dashes and colons (same
956
+ `FunctionDeclaration.name`); longer ids are mangled and restored as in §4c.
957
+ - `toolConfig.functionCallingConfig.mode` is `AUTO` / `ANY` / `NONE`; `ANY` may also
958
+ carry `allowedFunctionNames` (https://ai.google.dev/api/caching#FunctionCallingConfig).
959
+ Anthropic `tool` maps to `ANY` + `allowedFunctionNames`, `none` to `NONE`.
960
+ - Gemini 3 reasons with `generationConfig.thinkingConfig.thinkingLevel`
961
+ (`minimal|low|medium|high`); Gemini 2.5 with `thinkingConfig.thinkingBudget`
962
+ (0..24576 flash, 128..32768 pro; 0 disables) (https://ai.google.dev/gemini-api/docs/thinking).
963
+ `output_config.effort` is clamped to the model family and to the provider's
964
+ `effortLevels` when set. **(assumption)** Some samples spell the REST field
965
+ `thinking_level` flatly; we emit the documented nested `generationConfig.thinkingConfig`
966
+ shape.
967
+ - `usageMetadata` fields are `promptTokenCount`, `cachedContentTokenCount`,
968
+ `candidatesTokenCount`, `thoughtsTokenCount`
969
+ (https://ai.google.dev/api/generate-content#UsageMetadata). Input =
970
+ `promptTokenCount − cachedContentTokenCount`, cache read = `cachedContentTokenCount`,
971
+ output = `candidatesTokenCount + thoughtsTokenCount`.
972
+ - A `finishReason` of `STOP` closes as `end_turn` (or `tool_use` when function calls
973
+ were seen); `MAX_TOKENS` as `max_tokens`; anything else as `end_turn` with a log. A
974
+ stream that ends with no `finishReason` is the §5 stalled-upstream shape: it becomes a
975
+ retryable `overloaded_error`, never a silent `end_turn`
976
+ (https://ai.google.dev/api/generate-content#FinishReason).
977
+ - A wrong key answers `400` with `status: "API_KEY_INVALID"` (or a `401`/`403` whose
978
+ body names the key); that maps to Anthropic `authentication_error` (401). `429
979
+ RESOURCE_EXHAUSTED` maps to `rate_limit_error`, `5xx` to `overloaded_error`, other
980
+ `4xx` to `invalid_request_error`. A bare `403` is read as a permission/region problem,
981
+ not a bad key.
982
+ - `GET {base}/v1beta/models?pageSize=1000` (key on `x-goog-api-key`) lists
983
+ `{name, displayName, inputTokenLimit, supportedGenerationMethods, thinking}` with a
984
+ `nextPageToken`; only entries whose `supportedGenerationMethods` include
985
+ `generateContent` are kept, `id` is `name` minus `models/`, and
986
+ `contextWindow = inputTokenLimit`
987
+ (https://ai.google.dev/api/models#method:-models.list).
988
+ - **thoughtSignature (Gemini 3).** A `functionCall` part on a reasoning model may carry a
989
+ `thoughtSignature`, and a client replaying that call must return the same signature or
990
+ the API rejects the turn ("Function call is missing a thought_signature in
991
+ functionCall parts"). The store keeps signatures keyed by our minted `tool_use` id in a
992
+ bounded LRU (10,000) and re-attaches them on replay; per-part for parallel calls, where
993
+ Gemini signs only the first call of a step. On a turn with no cached signature at all (a
994
+ router restart, a history another model wrote) the documented sentinel
995
+ `skip_thought_signature_validator` goes on that turn's **first** call, Gemini 3 only. The
996
+ guide calls it a last resort that costs model performance, so it is never added to a turn
997
+ that has a real signature — the unsigned parallel calls of a healthy turn are normal
998
+ (https://cloud.google.com/vertex-ai/generative-ai/docs/thought-signatures, sentinel
999
+ paragraph read 2026-10-01).
1000
+
1001
+ ### 4d-ii. Google `auth: "antigravity"` (Cloud Code Assist) — implemented 2026-10-01
1002
+
1003
+ A Google subscription signed in through `clauderipple google-login`, streaming through the
1004
+ Cloud Code Assist (CCA) backend, with several accounts rotating. Same translation as AI
1005
+ Studio; only `transport.ts` differs. Accounts live in `<home>/google-accounts.json` (0600,
1006
+ cross-process lock, atomic write, one account per email+project, a re-login replaces its own
1007
+ entry), the sign-in in `login.ts`, the account pool and refresh in `accounts.ts`, and the wire
1008
+ facts and model-id resolution in `antigravity.ts`. **Every wire fact below is taken from the
1009
+ reference implementation opencodex (GitHub `lidge-jun/opencodex`, read 2026-10-01 as a
1010
+ behavioral spec — no code copied; project rule) and is not measured live** — no Antigravity
1011
+ subscription exists in this environment. Files are under `src/`:
1012
+
1013
+ - OAuth is Google's standard installed-app flow with PKCE S256. Client id
1014
+ `1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com`, secret
1015
+ `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` (public client values, overridable by env;
1016
+ `oauth/google-antigravity.ts:17-20`). Authorize
1017
+ `https://accounts.google.com/o/oauth2/v2/auth`, tokens
1018
+ `https://oauth2.googleapis.com/token`, userinfo
1019
+ `https://www.googleapis.com/oauth2/v2/userinfo`; scopes `cloud-platform`, `userinfo.email`,
1020
+ `userinfo.profile`, `cclog`, `experimentsandconfigs`; `access_type=offline`,
1021
+ `prompt=consent select_account`; loopback callback port 51121 preferred, path `/callback`,
1022
+ any free port otherwise (`google-antigravity.ts:21-35,177-196`). The token body carries the
1023
+ client id **and** secret and `code_verifier` (form-encoded).
1024
+ - Project discovery: `POST https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist` with
1025
+ `{metadata:{ideType:"ANTIGRAVITY"}}`; without a project,
1026
+ `POST https://daily-cloudcode-pa.googleapis.com/v1internal:onboardUser` with
1027
+ `{tier_id:"free-tier", metadata:{ide_type, ide_name:"antigravity", ide_version}}`, polled up
1028
+ to 5 times, 2 s apart, until `done:true` (`google-antigravity.ts:97-137`). The project id is
1029
+ read from `cloudaicompanionProject | projectId | project[.id]`. A sign-in that finds no
1030
+ project fails rather than storing a credential that would fail every call.
1031
+ - Request: `POST {base}/v1internal:streamGenerateContent?alt=sse` (non-streaming
1032
+ `:generateContent`), base default `https://daily-cloudcode-pa.googleapis.com`
1033
+ (`adapters/google.ts:1056-1160`; devlog `140.21`). Envelope
1034
+ `{ model, userAgent:"antigravity", requestType:"agent", project, requestId:"agent-<uuid>",
1035
+ request }`, where `request` is the standard Gemini body plus a nested camelCase
1036
+ `sessionId` (`adapters/google.ts:1145-1158`; `google-antigravity-wire.ts:53-77`). Headers:
1037
+ `Authorization: Bearer <token>` and `User-Agent: antigravity/ide/<ver> (os_type=…; arch=…;
1038
+ aidev_client; auth_method=oauth)` — the IDE client family is required, the backend 404s
1039
+ CLI-shaped UAs (`adapters/client-fingerprint.ts:44-65`). Response frames nest the standard
1040
+ Gemini payload under `response` (`adapters/google.ts:1281,1591-1596`).
1041
+ - Model ids. CCA's wire ids carry the tier as a suffix (`gemini-3.8-flash-low`) or a
1042
+ `-tiered` rename (`gemini-3.7-flash-tiered`), while the picker shows a collapsed base id.
1043
+ Resolution, from `providers/antigravity-models.ts`: `gemini-3.8-flash` → its suffix per
1044
+ effort (`…-low/medium/high`, no `thinkingLevel` beside it — the suffix IS the effort);
1045
+ `gemini-3.1-pro` → `gemini-3.1-pro-low` / `gemini-pro-agent` with `thinkingLevel`;
1046
+ `gemini-3.7-flash` → `gemini-3.7-flash-tiered` with `thinkingLevel`; a `claude-*` id takes a
1047
+ `thinkingLevel` only (`resolveAntigravityThinkingLevel` clamps `xhigh/max/ultra`→`high`).
1048
+ CCA's own catalogue is `POST {dailyApi}/v1internal:fetchAvailableModels` with `{project}`,
1049
+ body `{models:{<wireId>:{maxTokens,…}}, agentModelSorts:[{groups:[{modelIds:[…]}]}],
1050
+ tieredModelIds:{flash:[…]}}` (`providers/quota/antigravity.ts:166-168`,
1051
+ `providers/antigravity-models.ts:489-560`); when it cannot be read we fall back to the
1052
+ static collapsed list (`gemini-3.8-flash`, `gemini-3.7-flash`, `gemini-3.1-pro`,
1053
+ `gemini-3.1-flash-image`, `claude-sonnet-4-6`, `claude-opus-4-6-thinking`,
1054
+ `gpt-oss-120b-medium`, context windows from CCA `maxTokens`).
1055
+ - Claude models on CCA force `toolConfig.functionCallingConfig.mode = "VALIDATED"`; a client
1056
+ asking for no tools gets the declarations dropped instead (`adapters/google.ts:1109-1120`).
1057
+ - **thoughtSignature.** The CCA path sends a real signature back on the exact `functionCall`
1058
+ the model signed and never adds the `skip_thought_signature_validator` sentinel (a fabricated
1059
+ signature is not "real"). opencodex filters a stored signature with
1060
+ `isLikelyRealThoughtSignature` (`google-antigravity-wire.ts:30-42`) to keep a foreign id
1061
+ (`toolu_…`, `call_…`) out of the signature field, which Antigravity 400s
1062
+ (`TYPE_BYTES`/Base64 decode failure). We do not need that filter: our store is keyed by the
1063
+ `tool_use` id we minted and holds only a `thoughtSignature` the vendor itself sent, so a
1064
+ foreign id is never placed in the field.
1065
+ - **Accounts and credentials (§4 Credential pools).** A conversation stays on the account that
1066
+ answered while it is healthy (moving it costs the prompt cache); a refusal before any byte
1067
+ reaches the client moves the turn to the next account: 401 (or a 403 that reads as a
1068
+ credential refusal) gets one refresh and a replay, a fresh token refused with a 401 again
1069
+ marks the account for sign-in; 429/402 rest the account to its `retry-after` (or a default);
1070
+ another 403, 5xx or a connect failure rests it briefly; any other 4xx is the request's fault
1071
+ and is not retried. Refresh is per account, single-flight per token generation, five minutes
1072
+ ahead; only a structured `invalid_grant` marks an account for sign-in. Only the access token
1073
+ and the account email/project id are ever in memory; no token reaches a log or an API response.
1074
+ - Session id and prompt cache. The real client puts a stable per-conversation id at
1075
+ `request.sessionId` so provider-side replay can associate a signature with the call that made
1076
+ it; instability loses it (`google-antigravity-wire.ts:62-88`). We derive it from the router's
1077
+ own `conversationKey` (sha256 → BigEndian uint64 masked to 63 bits, left-padded with `-`,
1078
+ CLIProxyAPI's `generateStableSessionID` shape). `requestId` is a fresh `agent-<uuid>` per
1079
+ request, so it does not enter the cacheable body prefix.
1080
+ - **Terms.** Using Antigravity credentials from a non-Google client is against Google's terms
1081
+ (Antigravity terms §6; the Gemini CLI FAQ) and Google blocked a large number of such accounts
1082
+ in February 2026. The CLI prints this once before a sign-in and the sign-in API returns it in
1083
+ a `warning` field for the GUI; the wording states only the fact. ClaudeRipple is not
1084
+ affiliated with Google.
1085
+ - Admin: `POST/GET /api/google-login`, `POST /api/google-login/code`, `POST
1086
+ /api/google-login/cancel`, `POST /api/google-logout`, `GET /api/google-accounts`,
1087
+ `PATCH/DELETE /api/google-accounts/:id`, and `POST /api/providers/probe` with
1088
+ `{type:"google", auth:"antigravity"}`. CLI: `clauderipple google-login` / `google-logout`.
1089
+
807
1090
  ## 5. Failure modes that must not exist in the product (all observed)
808
1091
 
809
1092
  | Observed | Product requirement |
@@ -819,6 +1102,7 @@ what we do not have yet — so that adding a provider does not start with readin
819
1102
  | Tool names over 64 characters were forwarded to the Responses API unchanged, so a single connected MCP server — the product's own use case (§2) — failed every request of that turn (found by reading the source, issue #1, 2026-09-17) | Any name a translated provider sends is mangled into the provider's constraint deterministically and restored on the way back (§4). A translator must validate what it forwards against the wire it forwards to, not only what it builds itself. |
820
1103
  | Unknown-model context window defaulted to 200K, compaction fired at 151K; fixed via `CLAUDE_CODE_MAX_CONTEXT_TOKENS=272000` (applies only to models not in the CLI's built-in table; Claude models unaffected) | Installer sets this env for mapped models; document that it does not affect Claude models. |
821
1104
  | Server tools (`web_search`) were declared to translated providers as ordinary functions: a tool the model can call and nothing can run. Not observed live, because Claude Code currently routes its web search elsewhere (§4) — the failure was one server-side flag away, and its shape is an empty answer with nothing logged (issue #4, 2026-09-17) | A translator declares only what the provider can actually execute, and says in the log what it removed. A capability that silently disappears is worse than one that visibly fails. |
1105
+ | Routed workers were told to call the `advisor` after its declaration was dropped: Claude Code explains the tool in a user message the router left in place, so 26 of 28 worker transcripts reported it missing, every turn (issue #47, 2026-10-04) | Removing a capability includes withdrawing what was said about it. A translator that drops a tool adds one stable line saying so, rather than editing the caller's text. |
822
1106
  | One context window was written to every routed model and every slot, because `cli.autoCompactWindow` is a single number while `auto_compact_windows` and `context_window_by_model` are per-model maps. Routed models do not share a window: set it high and the smaller model overflows before it compacts, set it low and the larger throws away most of its own (issue #2, 2026-09-17) | A window belongs to a model, not to the router. `CliModel.contextWindow` and `Route.contextWindow` win, then whatever the vendor's `/models` reported as `context_length`, then the global value as the fallback it always was. A value the config can only express once must not be injected into a map that is keyed per model. |
823
1107
  | proxenos sends only the 7-day quota window, so the app shows a "weekly limit" banner | Quota reporting must mirror the shape Anthropic returns. |
824
1108
  | After a reboot the router listened 4 minutes after login (26s of it between exec and `listen()`), and for that whole window Claude Desktop was a blank page with `ERR_PROXY_CONNECTION_FAILED` — in picker mode every byte the app sends goes through us, so a router that is merely slow reads as an app that is broken (2026-09-14 09:59 boot → 10:15:59 listening) | The launchd agent is `ProcessType=Interactive`, never `Background` (that key throttles CPU and I/O — launchd.plist(5)). `listen()` comes before certificate minting and any other startup work, so a client waits rather than being refused. Every startup logs its budget (`startup Nms: node …, config …, listen …`). |
@@ -827,6 +1111,7 @@ what we do not have yet — so that adding a provider does not start with readin
827
1111
  | Windows: the scheduled task inherited the console that started it, so closing the installer window killed the router with `0xC000013A` (Ctrl+C), 2026-09-14 | The task runs a PowerShell launcher that uses `Start-Process -PassThru -Wait`: detached from any console, still tracked so `RestartCount` keeps acting as KeepAlive and the exit code (including the health self-exit 75) propagates. |
828
1112
  | Windows: `install` registered a logon-triggered task and stopped there, so the router did not exist until the next sign-in — launchd starts at bootstrap (`RunAtLoad`) and the difference was invisible | `install` starts the supervisor itself on both platforms and prints the result. |
829
1113
  | Windows: trusting the CA failed with "this operation cannot use the UI" — every PowerShell call carried `-NonInteractive`, and the confirmation dialog never appeared, so picker mode stopped with no certificate and no explanation | Certificate trust and removal run WITHOUT `-NonInteractive` (and without `windowsHide`). They are UI operations by design: the OS must be able to show the user what it is being asked to trust. |
1114
+ | macOS, new Mac (2026-10-06): picker mode on, router up, and Claude Desktop would not open — every claude.ai load failed `ERR_CERT_AUTHORITY_INVALID`, router logged `tls alert certificate unknown`; `dump-trust-settings` had no entry for the CA. "Trusted" was decided by `find-certificate` (the item is in the login keychain), which says nothing about trust, so `picker on` could skip the trust step and `status` could read an untrusted CA as trusted | On macOS the CA counts as trusted only when the user trust domain (`security dump-trust-settings`) lists it. `picker off` deletes the keychain item even when it carries no trust. |
830
1115
  | Windows: sign-in failed with `missing_required_parameter` because the OAuth URL was opened via `cmd /c start`, and cmd reads `&` as a command separator — everything after the first parameter was cut off and run as commands (2026-09-14) | Browsers are opened with `Start-Process <url>` as a single quoted argument. Any URL we hand to a shell must survive its metacharacters. |
831
1116
  | The provider form's "also show in the Claude app picker" box only records `cli.extraModels`; picker mode itself stayed off, nothing was injected, and the user believed the picker was on (Windows x64, 2026-09-14: `config.json` had no `picker` key, no CA, no Config Library) | When that box is ticked while picker mode is off, the form says so under the box and offers to turn picker mode on right after saving. Any control that *looks* like it enables picker mode must either enable it or say what is still missing. |
832
1117
  | A DeepSeek mapping answered `401` on every real request while the provider form's own "Test connection" stayed green — the test sends the provider's headers alone, live traffic also carried the caller's `authorization` (2026-09-15, Windows) | A routed request carries provider authentication only (§4). The connection test and live traffic must present the same auth set, or the test certifies a path nobody uses. |
@@ -843,6 +1128,7 @@ what we do not have yet — so that adding a provider does not start with readin
843
1128
  | The worker registry was four places that did not know each other — `providers.*.models`, `aliases`, `~/.claude/agents/*.md`, and prose in CLAUDE.md — and every new provider needed all four edited by hand; the one left out was the one that failed (2026-09-19, DeepSeek) | One source: a ticked model *is* a worker. Agent files and marker aliases are generated from `config.json` (§4 "worker definitions"); nothing about a worker is written twice. |
844
1129
  | ChatGPT prompt-cache hit fell from 93% (2026-09-13) to 9% (2026-09-20) with nothing in our request changing — the backend had started keying the cache on the conversation's identity (`session-id`/`thread-id`/`x-client-request-id`/`client_metadata`), which we never sent; `prompt_cache_key` alone no longer earned a write | The adapter states the conversation's identity the way the Codex CLI does (§4) and echoes `x-codex-turn-state`. The acceptance metric (≥90% on translated providers) is watched per day in `requests.jsonl`; a fall with an unchanged request means the wire changed under us, and the reference to diff against is the real CLI captured through a local proxy, not our own memory of the protocol. |
845
1130
  | Remote Control registration failed locally with 405 before Anthropic saw it: Claude Code 2.1.275 uses HTTPS absolute-form (`POST https://api.anthropic.com/v1/environments/bridge HTTP/1.1`) rather than CONNECT for registration, polling and heartbeats, while the forward-proxy socket accepted CONNECT only (issue #10, Windows 11) | Accept valid HTTPS absolute-form, strip proxy-only headers, replace Host, rewrite only the target to origin-form, and relay one request over direct TLS outside model routing. Force `Connection: close`: reusing that TLS socket could send a later proxy-form request to the first origin. Parser tests plus a live local TLS origin verify body and credential preservation. |
1131
+ | The status mod's band said "router not answering" on every tick while the admin API answered curl (2026-10-06, CLI 2.1.288): the host's `$.http.fetch("http://127.0.0.1:8792/…")` went through `HTTPS_PROXY` as plain-HTTP absolute-form (`GET http://127.0.0.1:8792/… HTTP/1.1`), loopback included, and the proxy socket answered 405 to anything but CONNECT or `https:` absolute-form. Measured with a probe mod under `claude -p --plugin-dir`: 405 with an empty body | `http:` absolute-form is relayed like `https:` (same header rewrite, `Connection: close`) over plain TCP. A live loopback origin test covers it. |
846
1132
  | Re-running `clauderipple install` as a normal Windows user rewrote the launcher, then `Register-ScheduledTask -Force` could answer `0x80070005 Access is denied` even though the existing per-user task was already correct (issue #9) | Reuse an existing task only when its entire intended definition matches: one PowerShell action and exact arguments, current user, limited interactive logon, one matching logon trigger, restart count/interval, no execution limit, IgnoreNew, and battery policy. A missing, stale or foreign same-named task still takes the normal registration/error path. |
847
1133
  | OpenCode Go's relay answered `403 {"message":"Error from provider (Console Go): Upstream request failed: …"}` during a few seconds of its own trouble. The 403 was classified as an auth failure, which both cooled the credential for 60s **and** removed it from the retry candidate list, so the router could not retry inside the turn and answered the user with what read as an authentication error. Five times in a day, each cleared by asking again (2026-09-21) | A 403 whose body reports an upstream failure is charged a **zero** cooldown. It is not a statement about the credential, and any wait at all moves the conversation off a key that did nothing and takes its prompt cache with it (§4). With no cooldown the credential stays retryable, so the turn is answered on the next attempt instead of becoming a user-visible failure. The body is the only way to tell this apart from a real 403 (policy, region, blocked model), so it is read for 403 and only 403. |
848
1134
  | The same relay 403 kept reaching the user after that fix, because it was the wrong layer: DeepSeek runs on `openai-compatible`, whose adapter was **never** retried at all — the in-turn retry lives only in the anthropic-compatible wire path (`nextCredential`), and `credentialsOf` gives a translated provider exactly one credential and no fallback. So a relay hiccup had nowhere to go but the user (2026-09-21, root cause of the day's repeated "authentication failed") | Every translated adapter retries the `fetch` **before** any status or byte is written, so the turn is still replaceable (§4 "the first byte commits the turn"). `fetchWithRetry` retries only `transient` verdicts — connect failures, 5xx, and a 403 reporting a broken upstream — to the ceiling of 10 attempts the CLI itself uses, with a 250ms→2s backoff. A 401/429 is **not** retried: the adapter holds one credential, so asking the key that just refused the identical request again only delays the error. `classify` is reused for the judgement, with `kind === "transient"` narrowing a pool's answer to an adapter's. |
@@ -850,6 +1136,9 @@ what we do not have yet — so that adding a provider does not start with readin
850
1136
  | A session switched from gpt-6-astra to claude-opus-5-5 failed every turn with `400 messages.2.content.0.thinking: each thinking block must contain thinking`: the translated adapters hand the CLI reasoning as `thinking` blocks with no signature (and no text when the reasoning item had no summary), the CLI keeps them, and passthrough replayed them to Anthropic (2026-09-28) | Before a request reaches Anthropic (`PASS` and the account-pool provider), assistant `thinking` blocks without a signature are dropped (`thinking.ts`) — Anthropic's own always carry one — and a turn left empty goes with them. Passthrough stays byte-exact when there is nothing to drop. |
851
1137
  | Re-running the installer — the documented update — replaced the files and left the router running the old code: `install` treats a running router as done, and on Windows and Linux an unchanged supervisor definition is not re-registered, so nothing restarted it until the next login (found 2026-09-28 while building `update`; the tray's own restart covered only the packaged app) | `install` asks a router that is already running for its version and restarts one that differs. Measured in the Windows 11 VM: 0.5.1 running, installer run with 0.6.0 → "restarting it on 0.6.0", status 0.6.0. |
852
1138
  | The tray's Electron (~270MB) was fetched into the installed package's own `node_modules`, and `npm install -g` of any other version deleted it with the old files (measured 2026-09-28): every update silently took the tray away, and on Windows a running tray held `electron.exe` inside the folder npm had to replace | Electron lives in `<home>/tray-runtime`, outside anything an update replaces. Both install scripts move one found in the old place there before installing (Windows first stops a tray running from it, and starts it again after setup) — checked on macOS and in the VM with a locked `electron.exe`. |
1139
+ | Adding a provider in the settings window restored three OpenCode effort ladders realigned by hand five hours earlier, so the app's effort menus for DeepSeek and GLM never narrowed (2026-10-02 23:13, found 10-05). `PUT /api/config` takes the whole config, and the screen built it from the copy it loaded at open and never re-read; the router itself also writes `config.json` (a measurement's results), so any of those was undone by the next unrelated save | Every GUI save fetches the router's config first and applies its own change to that. A provider form, which holds per-model fields (wire, ladder, window) from when it opened and has no control for them, takes a saved model's values from the router's copy. The provider and client screens re-read the config whenever they load. |
1140
+ | In picker mode, claude.ai requests failed as `502 … ECONNRESET` in under a second — 259 times in four days (2026-10-03..06), mostly `current_user_access`, `desktop/features` and `event_logging`. The kept-alive upstream socket had been closed by claude.ai as the next request was written on it, and Node does not resend | A GET/HEAD/OPTIONS whose reused socket resets before any response is sent again (twice at most), logged `STALE`. A POST is never resent: a reset does not prove the upstream did not act on it. Not yet shown to be the cause of the Windows "chat history loads forever" report (DC, 2026-10-06). |
1141
+ | ChatGPT accounts that had bought Codex credits were rested by the router the moment a window read 100%, though OpenAI serves them from credits past the window | `exhaustedForMs` ignores a full window while `credits.has_credits` or `unlimited` holds (and `overage_limit_reached` does not); an actual 429 still rests the account until the window resets. |
853
1142
 
854
1143
  ## 6. Blocked paths (measured, do not retry)
855
1144
 
@@ -868,3 +1157,17 @@ CC BY-NC-ND (no forks, no sponsors). CLI-targeting `claude-code-router` has
868
1157
  37,180★, `opencodex` 14,300★ with two README sponsors. Open positions: English
869
1158
  distribution, GPL-3.0 license (chosen 2026-09-13 over MIT: sole author, reversible later, blocks closed commercial repackaging), sponsor slots. Positioning: an **add-on for Claude
870
1159
  subscribers**, not a replacement for people without one.
1160
+
1161
+
1162
+ ### Codex context-overflow recovery
1163
+
1164
+ The subscription backend can reject an oversized request with HTTP 200 followed by
1165
+ `response.failed` / `context_length_exceeded`. The Messages adapter maps this to
1166
+ `invalid_request_error` and prefixes `prompt is too long`, which Desktop CLI
1167
+ 2.1.284 recognizes. Keep the initial message-start event pending until another
1168
+ translated event arrives so an early rejection can retain HTTP 400. Transient
1169
+ overloads and interrupted streams remain retryable.
1170
+
1171
+ Both streaming and non-streaming early-rejection paths are covered by adapter
1172
+ tests. The client still needs to compact its history or otherwise reduce input;
1173
+ this mapping does not increase the backend's context limit.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clauderipple",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "Use GPT (ChatGPT Plus/Pro), DeepSeek and 400+ other models in the Claude Desktop Code tab and Claude Code while staying signed in to your Claude subscription — no third-party gateway mode. Several ChatGPT accounts that take over when one runs out, in Codex too.",
5
5
  "license": "GPL-3.0-only",
6
6
  "workspaces": [