clauderipple 0.6.0 → 0.8.0
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.
- package/CHANGELOG.md +66 -0
- package/README.ko.md +43 -2
- package/README.md +60 -4
- package/dist/app/package.json +1 -1
- package/dist/cli/src/claude-auth.js +35 -1
- package/dist/cli/src/codex.js +23 -0
- package/dist/cli/src/hooks/agent-title.js +58 -29
- package/dist/cli/src/index.js +89 -1
- package/dist/cli/src/runtime.js +3 -0
- package/dist/cli/src/settings.js +49 -0
- package/dist/mod/.claude-plugin/plugin.json +1 -0
- package/dist/mod/hooks/hooks.json +1 -0
- package/dist/mod/hooks/register.tsx +309 -0
- package/dist/mod/hooks/worker.ts +49 -0
- package/dist/mod/tsconfig.json +3 -0
- package/dist/mod/types/index.d.ts +30 -0
- package/dist/router/src/admin.js +497 -34
- package/dist/router/src/agents.js +50 -9
- package/dist/router/src/catalog.js +80 -0
- package/dist/router/src/compat.js +27 -1
- package/dist/router/src/config.js +33 -0
- package/dist/router/src/index.js +13 -1
- package/dist/router/src/ingress/server.js +3 -2
- package/dist/router/src/picker.js +33 -2
- package/dist/router/src/presets.js +2 -1
- package/dist/router/src/providers/chatgpt/index.js +154 -15
- package/dist/router/src/providers/chatgpt/translate.js +34 -6
- package/dist/router/src/providers/google/accounts.js +345 -0
- package/dist/router/src/providers/google/antigravity.js +392 -0
- package/dist/router/src/providers/google/index.js +617 -0
- package/dist/router/src/providers/google/login.js +249 -0
- package/dist/router/src/providers/google/thoughtSignatures.js +47 -0
- package/dist/router/src/providers/google/translate.js +539 -0
- package/dist/router/src/providers/google/transport.js +118 -0
- package/dist/router/src/providers/openai/translate.js +3 -3
- package/dist/router/src/proxy.js +99 -1
- package/dist/router/src/requestlog.js +2 -0
- package/dist/router/src/routing.js +22 -0
- package/dist/router/src/version.js +1 -1
- package/dist/ui/app.js +436 -44
- package/dist/ui/i18n.js +88 -4
- package/dist/ui/index.html +10 -0
- package/dist/ui/style.css +4 -0
- package/docs/ARCHITECTURE.md +300 -3
- package/package.json +1 -1
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -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,11 +240,24 @@ 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, `@
|
|
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
|
|
169
|
-
not rewritten. `cli.agentFiles: false` turns generation off
|
|
248
|
+
not rewritten. `cli.agentFiles: false` turns generation off;
|
|
249
|
+
`cli.limitWorkerTools: true` adds a `tools:` line so a worker does not inherit
|
|
250
|
+
every MCP and plugin tool of the session (first request 32,942 → 8,823 tokens,
|
|
251
|
+
2026-09-29), at the cost of browser and simulator tools. It is opt-in for that
|
|
252
|
+
reason. With a chatgpt provider configured the body also tells a worker how to
|
|
253
|
+
make an image: `POST /api/image` on the admin port, which runs one Codex
|
|
254
|
+
Responses turn with the hosted `image_generation` tool required and answers
|
|
255
|
+
with the image bytes (`clauderipple image` is the same call). Measured
|
|
256
|
+
2026-09-29 on a subscription: the image is the `image_generation_call` output
|
|
257
|
+
item's base64 `result`, about 30 s; `output_format` and `background` are
|
|
258
|
+
honoured, `size` and `quality` are accepted and ignored (even "999x1" and
|
|
259
|
+
"bogus" returned 200), and the shape follows the prompt ("square composition"
|
|
260
|
+
→ 1254x1254). So the API takes `aspect` and says it in words. The marker alias
|
|
170
261
|
table is the union of `aliases` (explicit, wins) and every agent file's
|
|
171
262
|
`name → model` — so `[[ripple: <agent>@<effort>]]` resolves for any agent that
|
|
172
263
|
exists, generated or not, with no second registry to keep in step. Why: on
|
|
@@ -336,6 +427,20 @@ chat is out of reach for every approach, ours included.
|
|
|
336
427
|
from the start; the ChatGPT and openai-compatible translators do the same, and a `tool_choice`
|
|
337
428
|
that named a dropped tool is dropped with it. The adapter logs each drop by name, because the
|
|
338
429
|
failure it replaces is silent.
|
|
430
|
+
- **A dropped server tool's instructions are withdrawn too (issue #47).** The advisor does reach
|
|
431
|
+
adapters: Claude Code 2.1.286 declares `{ type: "advisor_20260301", name: "advisor", model }`
|
|
432
|
+
on every subagent whatever its `tools:` list says, and explains it in an `isMeta` user message
|
|
433
|
+
(`# Advisor Tool`, "call advisor BEFORE substantive work"). Measured 2026-10-04: `dropped server
|
|
434
|
+
tools for gpt-6-astra: advisor` on every worker request, and 26 of 28 routed worker transcripts
|
|
435
|
+
reporting the tool missing. Dropping only the declaration leaves an instruction the model cannot
|
|
436
|
+
follow. Each translated path that drops server tools therefore appends one line after the
|
|
437
|
+
caller's system text naming them and telling the model to disregard instructions to call them
|
|
438
|
+
(`droppedServerToolNote`), the same move the CLI makes when it removes the advisor itself ("The
|
|
439
|
+
advisor tool is no longer available; disregard the earlier advisor instructions."). The CLI's
|
|
440
|
+
wording is not matched or stripped, since it changes between releases. Names are sorted, so one
|
|
441
|
+
set yields one byte sequence and the cached prefix holds. An anthropic-compatible provider with
|
|
442
|
+
`serverTools: true` keeps every server tool and gets no line; whether DeepSeek runs `advisor` is
|
|
443
|
+
unmeasured.
|
|
339
444
|
- **Where web search actually runs (measured 2026-09-17).** Claude Code does not put `web_search`
|
|
340
445
|
in the main request. `WebSearch` opens a *separate side request* — system prompt "You are an
|
|
341
446
|
assistant for performing a web search tool use", one message "Perform a web search for the
|
|
@@ -612,6 +717,12 @@ chat is out of reach for every approach, ours included.
|
|
|
612
717
|
this turn), the caller's own `Authorization`/`chatgpt-account-id` is used
|
|
613
718
|
once as sent — pointing Codex here never leaves it worse off. A WebSocket
|
|
614
719
|
upgrade is answered 426 so Codex falls back to SSE.
|
|
720
|
+
- **Codex app model list.** `codex on` also points `model_catalog_json` at
|
|
721
|
+
`~/.codex/clauderipple-models.json`: Codex's own `models_cache.json` entries
|
|
722
|
+
plus ours. That file *replaces* Codex's list, so it must follow both sides —
|
|
723
|
+
the router rewrites it when its config loads and when Codex's cache changes
|
|
724
|
+
(checked once a minute; the app reads the catalog only at startup). With the
|
|
725
|
+
first alone, gpt-6.1-sol (2026-09-30) reached Codex's cache but not the app.
|
|
615
726
|
- Configure a native API-key target as
|
|
616
727
|
`{ "type":"anthropic", "auth":"api-key", "apiKey":"…" }` (or set
|
|
617
728
|
`ANTHROPIC_API_KEY`). Configure a compatible target as today, for example an
|
|
@@ -779,7 +890,16 @@ what we do not have yet — so that adding a provider does not start with readin
|
|
|
779
890
|
`effortLevels` overrides provider defaults, including `[]` for “no effort”. When
|
|
780
891
|
an OpenRouter `/models` entry has `supported_parameters`,
|
|
781
892
|
`reasoning_effort` maps to `[low, medium, high]`; its absence maps to `[]`.
|
|
782
|
-
Vendors that do not report that field keep their configured fallback.
|
|
893
|
+
Vendors that do not report that field keep their configured fallback. For the
|
|
894
|
+
OpenCode presets (`opencode-go`, `opencode-zen`) a model's ladder comes from
|
|
895
|
+
models.dev — the catalogue OpenCode's own client reads, whose `reasoning_options`
|
|
896
|
+
states it — and its `provider.npm` puts that wire first; one request then settles
|
|
897
|
+
the wire and catches a plan refusal. Asking every level instead took ~10 sequential
|
|
898
|
+
requests per model (glm-5.3-flash 1m54s, upstream latency 0.5–25s per one-token
|
|
899
|
+
request, 2026-10-02) and was less right: a 200 means "not refused", and
|
|
900
|
+
glm-5.3-flash accepted seven levels where the catalogue lists `low`/`high`/`max`.
|
|
901
|
+
A model the catalogue lacks, or a catalogue that cannot be fetched, is measured as
|
|
902
|
+
before (`catalog.ts`, `capabilities.ts`). Chat wire
|
|
783
903
|
forwards Anthropic `stop_sequences` as `stop`; Responses wire deliberately omits
|
|
784
904
|
them because its common stateless schema has no corresponding universal field.
|
|
785
905
|
- We do not assert a provider-wide cache hit rate: the adapter records
|
|
@@ -792,6 +912,167 @@ what we do not have yet — so that adding a provider does not start with readin
|
|
|
792
912
|
credit. Preset endpoint and capability claims cite the vendor's official docs in
|
|
793
913
|
`packages/router/src/presets.ts`.
|
|
794
914
|
|
|
915
|
+
### 4d. `google` providers (implemented 2026-10-01)
|
|
916
|
+
|
|
917
|
+
Gemini is a translated provider, not an ingress-only one: it holds no server-side thread,
|
|
918
|
+
so a `thread:continue` is refused exactly as in §4c and the CLI resends a stateless
|
|
919
|
+
history. It routes by its declared models, its agent files are generated like any other
|
|
920
|
+
provider's, and a `web_search` side request aimed at it is refused, not sent. The
|
|
921
|
+
adapter lives in `packages/router/src/providers/google/` and separates translation
|
|
922
|
+
(`translate.ts`), the wire (`transport.ts`) and the signature store
|
|
923
|
+
(`thoughtSignatures.ts`) so Stage 2 (Antigravity Code Assist) is a new transport only.
|
|
924
|
+
|
|
925
|
+
Both modes are implemented: `api-key` (AI Studio) below, and `auth: "antigravity"` (Cloud
|
|
926
|
+
Code Assist) in its own subsection. No live key or Antigravity subscription exists in this
|
|
927
|
+
environment, so everything below is doc- and source-derived and **not measured** unless it
|
|
928
|
+
says otherwise:
|
|
929
|
+
|
|
930
|
+
- Stream endpoint `POST {base}/v1beta/models/{model}:streamGenerateContent?alt=sse`,
|
|
931
|
+
the key on header `x-goog-api-key`, base default `https://generativelanguage.googleapis.com`
|
|
932
|
+
(https://ai.google.dev/api/generate-content#method:-models.streamgeneratecontent).
|
|
933
|
+
- `systemInstruction` takes `{role:"user"|"model", parts:[…]}`; `contents` is the
|
|
934
|
+
`user`/`model` turn list; a part is `{text}`, `{inlineData:{mimeType,data}}`,
|
|
935
|
+
`{functionCall:{name,args}}` or `{functionResponse:{name,response}}`. The `role` of a
|
|
936
|
+
`functionResponse` part must be `user` (https://ai.google.dev/api/caching#Content).
|
|
937
|
+
- Tools map to `tools[].functionDeclarations[].parametersJsonSchema` — a protobuf
|
|
938
|
+
`Value` carrying a full JSON Schema, mutually exclusive with the legacy `parameters`
|
|
939
|
+
(https://ai.google.dev/api/caching#FunctionDeclaration). We send `parametersJsonSchema`.
|
|
940
|
+
- A function name is at most 64 characters, must start with a letter or underscore and
|
|
941
|
+
may contain letters, digits, underscores, dots, dashes and colons (same
|
|
942
|
+
`FunctionDeclaration.name`); longer ids are mangled and restored as in §4c.
|
|
943
|
+
- `toolConfig.functionCallingConfig.mode` is `AUTO` / `ANY` / `NONE`; `ANY` may also
|
|
944
|
+
carry `allowedFunctionNames` (https://ai.google.dev/api/caching#FunctionCallingConfig).
|
|
945
|
+
Anthropic `tool` maps to `ANY` + `allowedFunctionNames`, `none` to `NONE`.
|
|
946
|
+
- Gemini 3 reasons with `generationConfig.thinkingConfig.thinkingLevel`
|
|
947
|
+
(`minimal|low|medium|high`); Gemini 2.5 with `thinkingConfig.thinkingBudget`
|
|
948
|
+
(0..24576 flash, 128..32768 pro; 0 disables) (https://ai.google.dev/gemini-api/docs/thinking).
|
|
949
|
+
`output_config.effort` is clamped to the model family and to the provider's
|
|
950
|
+
`effortLevels` when set. **(assumption)** Some samples spell the REST field
|
|
951
|
+
`thinking_level` flatly; we emit the documented nested `generationConfig.thinkingConfig`
|
|
952
|
+
shape.
|
|
953
|
+
- `usageMetadata` fields are `promptTokenCount`, `cachedContentTokenCount`,
|
|
954
|
+
`candidatesTokenCount`, `thoughtsTokenCount`
|
|
955
|
+
(https://ai.google.dev/api/generate-content#UsageMetadata). Input =
|
|
956
|
+
`promptTokenCount − cachedContentTokenCount`, cache read = `cachedContentTokenCount`,
|
|
957
|
+
output = `candidatesTokenCount + thoughtsTokenCount`.
|
|
958
|
+
- A `finishReason` of `STOP` closes as `end_turn` (or `tool_use` when function calls
|
|
959
|
+
were seen); `MAX_TOKENS` as `max_tokens`; anything else as `end_turn` with a log. A
|
|
960
|
+
stream that ends with no `finishReason` is the §5 stalled-upstream shape: it becomes a
|
|
961
|
+
retryable `overloaded_error`, never a silent `end_turn`
|
|
962
|
+
(https://ai.google.dev/api/generate-content#FinishReason).
|
|
963
|
+
- A wrong key answers `400` with `status: "API_KEY_INVALID"` (or a `401`/`403` whose
|
|
964
|
+
body names the key); that maps to Anthropic `authentication_error` (401). `429
|
|
965
|
+
RESOURCE_EXHAUSTED` maps to `rate_limit_error`, `5xx` to `overloaded_error`, other
|
|
966
|
+
`4xx` to `invalid_request_error`. A bare `403` is read as a permission/region problem,
|
|
967
|
+
not a bad key.
|
|
968
|
+
- `GET {base}/v1beta/models?pageSize=1000` (key on `x-goog-api-key`) lists
|
|
969
|
+
`{name, displayName, inputTokenLimit, supportedGenerationMethods, thinking}` with a
|
|
970
|
+
`nextPageToken`; only entries whose `supportedGenerationMethods` include
|
|
971
|
+
`generateContent` are kept, `id` is `name` minus `models/`, and
|
|
972
|
+
`contextWindow = inputTokenLimit`
|
|
973
|
+
(https://ai.google.dev/api/models#method:-models.list).
|
|
974
|
+
- **thoughtSignature (Gemini 3).** A `functionCall` part on a reasoning model may carry a
|
|
975
|
+
`thoughtSignature`, and a client replaying that call must return the same signature or
|
|
976
|
+
the API rejects the turn ("Function call is missing a thought_signature in
|
|
977
|
+
functionCall parts"). The store keeps signatures keyed by our minted `tool_use` id in a
|
|
978
|
+
bounded LRU (10,000) and re-attaches them on replay; per-part for parallel calls, where
|
|
979
|
+
Gemini signs only the first call of a step. On a turn with no cached signature at all (a
|
|
980
|
+
router restart, a history another model wrote) the documented sentinel
|
|
981
|
+
`skip_thought_signature_validator` goes on that turn's **first** call, Gemini 3 only. The
|
|
982
|
+
guide calls it a last resort that costs model performance, so it is never added to a turn
|
|
983
|
+
that has a real signature — the unsigned parallel calls of a healthy turn are normal
|
|
984
|
+
(https://cloud.google.com/vertex-ai/generative-ai/docs/thought-signatures, sentinel
|
|
985
|
+
paragraph read 2026-10-01).
|
|
986
|
+
|
|
987
|
+
### 4d-ii. Google `auth: "antigravity"` (Cloud Code Assist) — implemented 2026-10-01
|
|
988
|
+
|
|
989
|
+
A Google subscription signed in through `clauderipple google-login`, streaming through the
|
|
990
|
+
Cloud Code Assist (CCA) backend, with several accounts rotating. Same translation as AI
|
|
991
|
+
Studio; only `transport.ts` differs. Accounts live in `<home>/google-accounts.json` (0600,
|
|
992
|
+
cross-process lock, atomic write, one account per email+project, a re-login replaces its own
|
|
993
|
+
entry), the sign-in in `login.ts`, the account pool and refresh in `accounts.ts`, and the wire
|
|
994
|
+
facts and model-id resolution in `antigravity.ts`. **Every wire fact below is taken from the
|
|
995
|
+
reference implementation opencodex (GitHub `lidge-jun/opencodex`, read 2026-10-01 as a
|
|
996
|
+
behavioral spec — no code copied; project rule) and is not measured live** — no Antigravity
|
|
997
|
+
subscription exists in this environment. Files are under `src/`:
|
|
998
|
+
|
|
999
|
+
- OAuth is Google's standard installed-app flow with PKCE S256. Client id
|
|
1000
|
+
`1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com`, secret
|
|
1001
|
+
`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` (public client values, overridable by env;
|
|
1002
|
+
`oauth/google-antigravity.ts:17-20`). Authorize
|
|
1003
|
+
`https://accounts.google.com/o/oauth2/v2/auth`, tokens
|
|
1004
|
+
`https://oauth2.googleapis.com/token`, userinfo
|
|
1005
|
+
`https://www.googleapis.com/oauth2/v2/userinfo`; scopes `cloud-platform`, `userinfo.email`,
|
|
1006
|
+
`userinfo.profile`, `cclog`, `experimentsandconfigs`; `access_type=offline`,
|
|
1007
|
+
`prompt=consent select_account`; loopback callback port 51121 preferred, path `/callback`,
|
|
1008
|
+
any free port otherwise (`google-antigravity.ts:21-35,177-196`). The token body carries the
|
|
1009
|
+
client id **and** secret and `code_verifier` (form-encoded).
|
|
1010
|
+
- Project discovery: `POST https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist` with
|
|
1011
|
+
`{metadata:{ideType:"ANTIGRAVITY"}}`; without a project,
|
|
1012
|
+
`POST https://daily-cloudcode-pa.googleapis.com/v1internal:onboardUser` with
|
|
1013
|
+
`{tier_id:"free-tier", metadata:{ide_type, ide_name:"antigravity", ide_version}}`, polled up
|
|
1014
|
+
to 5 times, 2 s apart, until `done:true` (`google-antigravity.ts:97-137`). The project id is
|
|
1015
|
+
read from `cloudaicompanionProject | projectId | project[.id]`. A sign-in that finds no
|
|
1016
|
+
project fails rather than storing a credential that would fail every call.
|
|
1017
|
+
- Request: `POST {base}/v1internal:streamGenerateContent?alt=sse` (non-streaming
|
|
1018
|
+
`:generateContent`), base default `https://daily-cloudcode-pa.googleapis.com`
|
|
1019
|
+
(`adapters/google.ts:1056-1160`; devlog `140.21`). Envelope
|
|
1020
|
+
`{ model, userAgent:"antigravity", requestType:"agent", project, requestId:"agent-<uuid>",
|
|
1021
|
+
request }`, where `request` is the standard Gemini body plus a nested camelCase
|
|
1022
|
+
`sessionId` (`adapters/google.ts:1145-1158`; `google-antigravity-wire.ts:53-77`). Headers:
|
|
1023
|
+
`Authorization: Bearer <token>` and `User-Agent: antigravity/ide/<ver> (os_type=…; arch=…;
|
|
1024
|
+
aidev_client; auth_method=oauth)` — the IDE client family is required, the backend 404s
|
|
1025
|
+
CLI-shaped UAs (`adapters/client-fingerprint.ts:44-65`). Response frames nest the standard
|
|
1026
|
+
Gemini payload under `response` (`adapters/google.ts:1281,1591-1596`).
|
|
1027
|
+
- Model ids. CCA's wire ids carry the tier as a suffix (`gemini-3.8-flash-low`) or a
|
|
1028
|
+
`-tiered` rename (`gemini-3.7-flash-tiered`), while the picker shows a collapsed base id.
|
|
1029
|
+
Resolution, from `providers/antigravity-models.ts`: `gemini-3.8-flash` → its suffix per
|
|
1030
|
+
effort (`…-low/medium/high`, no `thinkingLevel` beside it — the suffix IS the effort);
|
|
1031
|
+
`gemini-3.1-pro` → `gemini-3.1-pro-low` / `gemini-pro-agent` with `thinkingLevel`;
|
|
1032
|
+
`gemini-3.7-flash` → `gemini-3.7-flash-tiered` with `thinkingLevel`; a `claude-*` id takes a
|
|
1033
|
+
`thinkingLevel` only (`resolveAntigravityThinkingLevel` clamps `xhigh/max/ultra`→`high`).
|
|
1034
|
+
CCA's own catalogue is `POST {dailyApi}/v1internal:fetchAvailableModels` with `{project}`,
|
|
1035
|
+
body `{models:{<wireId>:{maxTokens,…}}, agentModelSorts:[{groups:[{modelIds:[…]}]}],
|
|
1036
|
+
tieredModelIds:{flash:[…]}}` (`providers/quota/antigravity.ts:166-168`,
|
|
1037
|
+
`providers/antigravity-models.ts:489-560`); when it cannot be read we fall back to the
|
|
1038
|
+
static collapsed list (`gemini-3.8-flash`, `gemini-3.7-flash`, `gemini-3.1-pro`,
|
|
1039
|
+
`gemini-3.1-flash-image`, `claude-sonnet-4-6`, `claude-opus-4-6-thinking`,
|
|
1040
|
+
`gpt-oss-120b-medium`, context windows from CCA `maxTokens`).
|
|
1041
|
+
- Claude models on CCA force `toolConfig.functionCallingConfig.mode = "VALIDATED"`; a client
|
|
1042
|
+
asking for no tools gets the declarations dropped instead (`adapters/google.ts:1109-1120`).
|
|
1043
|
+
- **thoughtSignature.** The CCA path sends a real signature back on the exact `functionCall`
|
|
1044
|
+
the model signed and never adds the `skip_thought_signature_validator` sentinel (a fabricated
|
|
1045
|
+
signature is not "real"). opencodex filters a stored signature with
|
|
1046
|
+
`isLikelyRealThoughtSignature` (`google-antigravity-wire.ts:30-42`) to keep a foreign id
|
|
1047
|
+
(`toolu_…`, `call_…`) out of the signature field, which Antigravity 400s
|
|
1048
|
+
(`TYPE_BYTES`/Base64 decode failure). We do not need that filter: our store is keyed by the
|
|
1049
|
+
`tool_use` id we minted and holds only a `thoughtSignature` the vendor itself sent, so a
|
|
1050
|
+
foreign id is never placed in the field.
|
|
1051
|
+
- **Accounts and credentials (§4 Credential pools).** A conversation stays on the account that
|
|
1052
|
+
answered while it is healthy (moving it costs the prompt cache); a refusal before any byte
|
|
1053
|
+
reaches the client moves the turn to the next account: 401 (or a 403 that reads as a
|
|
1054
|
+
credential refusal) gets one refresh and a replay, a fresh token refused with a 401 again
|
|
1055
|
+
marks the account for sign-in; 429/402 rest the account to its `retry-after` (or a default);
|
|
1056
|
+
another 403, 5xx or a connect failure rests it briefly; any other 4xx is the request's fault
|
|
1057
|
+
and is not retried. Refresh is per account, single-flight per token generation, five minutes
|
|
1058
|
+
ahead; only a structured `invalid_grant` marks an account for sign-in. Only the access token
|
|
1059
|
+
and the account email/project id are ever in memory; no token reaches a log or an API response.
|
|
1060
|
+
- Session id and prompt cache. The real client puts a stable per-conversation id at
|
|
1061
|
+
`request.sessionId` so provider-side replay can associate a signature with the call that made
|
|
1062
|
+
it; instability loses it (`google-antigravity-wire.ts:62-88`). We derive it from the router's
|
|
1063
|
+
own `conversationKey` (sha256 → BigEndian uint64 masked to 63 bits, left-padded with `-`,
|
|
1064
|
+
CLIProxyAPI's `generateStableSessionID` shape). `requestId` is a fresh `agent-<uuid>` per
|
|
1065
|
+
request, so it does not enter the cacheable body prefix.
|
|
1066
|
+
- **Terms.** Using Antigravity credentials from a non-Google client is against Google's terms
|
|
1067
|
+
(Antigravity terms §6; the Gemini CLI FAQ) and Google blocked a large number of such accounts
|
|
1068
|
+
in February 2026. The CLI prints this once before a sign-in and the sign-in API returns it in
|
|
1069
|
+
a `warning` field for the GUI; the wording states only the fact. ClaudeRipple is not
|
|
1070
|
+
affiliated with Google.
|
|
1071
|
+
- Admin: `POST/GET /api/google-login`, `POST /api/google-login/code`, `POST
|
|
1072
|
+
/api/google-login/cancel`, `POST /api/google-logout`, `GET /api/google-accounts`,
|
|
1073
|
+
`PATCH/DELETE /api/google-accounts/:id`, and `POST /api/providers/probe` with
|
|
1074
|
+
`{type:"google", auth:"antigravity"}`. CLI: `clauderipple google-login` / `google-logout`.
|
|
1075
|
+
|
|
795
1076
|
## 5. Failure modes that must not exist in the product (all observed)
|
|
796
1077
|
|
|
797
1078
|
| Observed | Product requirement |
|
|
@@ -807,6 +1088,7 @@ what we do not have yet — so that adding a provider does not start with readin
|
|
|
807
1088
|
| 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. |
|
|
808
1089
|
| 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. |
|
|
809
1090
|
| 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. |
|
|
1091
|
+
| 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. |
|
|
810
1092
|
| 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. |
|
|
811
1093
|
| proxenos sends only the 7-day quota window, so the app shows a "weekly limit" banner | Quota reporting must mirror the shape Anthropic returns. |
|
|
812
1094
|
| 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 …`). |
|
|
@@ -838,6 +1120,7 @@ what we do not have yet — so that adding a provider does not start with readin
|
|
|
838
1120
|
| 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. |
|
|
839
1121
|
| 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. |
|
|
840
1122
|
| 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`. |
|
|
1123
|
+
| 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. |
|
|
841
1124
|
|
|
842
1125
|
## 6. Blocked paths (measured, do not retry)
|
|
843
1126
|
|
|
@@ -856,3 +1139,17 @@ CC BY-NC-ND (no forks, no sponsors). CLI-targeting `claude-code-router` has
|
|
|
856
1139
|
37,180★, `opencodex` 14,300★ with two README sponsors. Open positions: English
|
|
857
1140
|
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
|
|
858
1141
|
subscribers**, not a replacement for people without one.
|
|
1142
|
+
|
|
1143
|
+
|
|
1144
|
+
### Codex context-overflow recovery
|
|
1145
|
+
|
|
1146
|
+
The subscription backend can reject an oversized request with HTTP 200 followed by
|
|
1147
|
+
`response.failed` / `context_length_exceeded`. The Messages adapter maps this to
|
|
1148
|
+
`invalid_request_error` and prefixes `prompt is too long`, which Desktop CLI
|
|
1149
|
+
2.1.284 recognizes. Keep the initial message-start event pending until another
|
|
1150
|
+
translated event arrives so an early rejection can retain HTTP 400. Transient
|
|
1151
|
+
overloads and interrupted streams remain retryable.
|
|
1152
|
+
|
|
1153
|
+
Both streaming and non-streaming early-rejection paths are covered by adapter
|
|
1154
|
+
tests. The client still needs to compact its history or otherwise reduce input;
|
|
1155
|
+
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.
|
|
3
|
+
"version": "0.8.0",
|
|
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": [
|