clauderipple 0.7.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.ko.md +15 -1
  3. package/README.md +23 -3
  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/runtime.js +3 -0
  10. package/dist/cli/src/settings.js +49 -0
  11. package/dist/mod/.claude-plugin/plugin.json +1 -0
  12. package/dist/mod/hooks/hooks.json +1 -0
  13. package/dist/mod/hooks/register.tsx +309 -0
  14. package/dist/mod/hooks/worker.ts +49 -0
  15. package/dist/mod/tsconfig.json +3 -0
  16. package/dist/mod/types/index.d.ts +30 -0
  17. package/dist/router/src/admin.js +411 -32
  18. package/dist/router/src/agents.js +24 -8
  19. package/dist/router/src/catalog.js +80 -0
  20. package/dist/router/src/compat.js +27 -1
  21. package/dist/router/src/config.js +31 -0
  22. package/dist/router/src/index.js +12 -1
  23. package/dist/router/src/ingress/server.js +3 -2
  24. package/dist/router/src/picker.js +33 -2
  25. package/dist/router/src/presets.js +2 -1
  26. package/dist/router/src/providers/chatgpt/index.js +42 -15
  27. package/dist/router/src/providers/chatgpt/translate.js +34 -6
  28. package/dist/router/src/providers/google/accounts.js +345 -0
  29. package/dist/router/src/providers/google/antigravity.js +392 -0
  30. package/dist/router/src/providers/google/index.js +617 -0
  31. package/dist/router/src/providers/google/login.js +249 -0
  32. package/dist/router/src/providers/google/thoughtSignatures.js +47 -0
  33. package/dist/router/src/providers/google/translate.js +539 -0
  34. package/dist/router/src/providers/google/transport.js +118 -0
  35. package/dist/router/src/providers/openai/translate.js +3 -3
  36. package/dist/router/src/proxy.js +85 -1
  37. package/dist/router/src/requestlog.js +2 -0
  38. package/dist/router/src/routing.js +22 -0
  39. package/dist/router/src/version.js +1 -1
  40. package/dist/ui/app.js +420 -45
  41. package/dist/ui/i18n.js +76 -4
  42. package/dist/ui/index.html +5 -0
  43. package/dist/ui/style.css +4 -0
  44. package/docs/ARCHITECTURE.md +287 -2
  45. package/package.json +1 -1
@@ -3,7 +3,7 @@
3
3
  // stable prompt prefix can retain their native prompt-cache behavior.
4
4
  import crypto from "node:crypto";
5
5
  import { clampEffort } from "../../compat.js";
6
- import { conversationKey, estimateTokens, normalizeSchema, serverToolNames, systemText, toolNameForResponses } from "../chatgpt/translate.js";
6
+ import { conversationKey, estimateTokens, normalizeSchema, serverToolNames, systemWithDroppedToolNote, toolNameForResponses } from "../chatgpt/translate.js";
7
7
  import { identityPrefix, instructionsSuffix } from "../../identity.js";
8
8
  function textOf(content) {
9
9
  if (typeof content === "string")
@@ -87,7 +87,7 @@ function mappedEffort(opts) {
87
87
  /** Convert every Anthropic message into OpenAI Chat Completion messages without inventing unstable text. */
88
88
  export function toChatMessages(req, opts) {
89
89
  const messages = [];
90
- const sys = systemText(req.system);
90
+ const sys = systemWithDroppedToolNote(req);
91
91
  const content = opts ? systemWithIdentity(sys, opts) : sys;
92
92
  if (content)
93
93
  messages.push({ role: "system", content });
@@ -248,7 +248,7 @@ export function toOpenAiRequest(req, opts) {
248
248
  input: toResponsesInput(req),
249
249
  stream: true,
250
250
  };
251
- const instructions = systemWithIdentity(systemText(req.system), opts);
251
+ const instructions = systemWithIdentity(systemWithDroppedToolNote(req), opts);
252
252
  if (instructions)
253
253
  out.instructions = instructions;
254
254
  if (responseTools.length) {
@@ -29,9 +29,11 @@ import { classify, CredentialPool, retryAfterMs } from "./pool.js";
29
29
  import { PRESETS } from "./presets.js";
30
30
  import { ChatGptAdapter } from "./providers/chatgpt/index.js";
31
31
  import { OpenAiCompatibleAdapter } from "./providers/openai/index.js";
32
+ import { GoogleAdapter } from "./providers/google/index.js";
32
33
  import { conversationKey } from "./providers/chatgpt/translate.js";
33
34
  import { providerFor, terminateHosts } from "./config.js";
34
35
  import { injectPickerModels, isBootstrapPath } from "./picker.js";
36
+ import { effortLevelsForModel } from "./admin.js";
35
37
  import { ResponseUsageTap } from "./requestlog.js";
36
38
  import { credentialHeaderValues, redactErrorText, redactHeaders } from "./redact.js";
37
39
  import { ClaudeAccountAuthPool } from "./providers/anthropic-account-pool.js";
@@ -177,6 +179,7 @@ export class Proxy {
177
179
  providerAgents = new Map();
178
180
  chatgptAdapters = new Map();
179
181
  openaiAdapters = new Map();
182
+ googleAdapters = new Map();
180
183
  claudeAccounts;
181
184
  /** Each Claude subscription account's usage, for the admin status (never a token). */
182
185
  claudeUsage;
@@ -292,6 +295,52 @@ export class Proxy {
292
295
  this.openaiAdapters.set(name, { key, adapter });
293
296
  return adapter;
294
297
  }
298
+ google(name, cfg) {
299
+ const key = JSON.stringify(cfg);
300
+ const cur = this.googleAdapters.get(name);
301
+ if (cur && cur.key === key)
302
+ return cur.adapter;
303
+ const adapter = new GoogleAdapter(name, cfg, this.deps.home, this.deps.log, this.pool);
304
+ this.googleAdapters.set(name, { key, adapter });
305
+ return adapter;
306
+ }
307
+ /**
308
+ * Every configured google provider's Antigravity accounts: rotation state only, never a token.
309
+ * Empty for api-key providers and for an adapter that has not been created yet.
310
+ */
311
+ googleAccounts() {
312
+ const out = {};
313
+ for (const [name, p] of Object.entries(this.deps.config().providers)) {
314
+ if (p.type === "google" && p.auth === "antigravity")
315
+ out[name] = this.google(name, p).accountStatus();
316
+ }
317
+ return out;
318
+ }
319
+ /** The models a google provider can reach now, from Cloud Code Assist's own catalogue. */
320
+ googleFetchModels(name) {
321
+ const cfg = this.deps.config().providers[name];
322
+ if (!cfg || cfg.type !== "google" || cfg.auth !== "antigravity")
323
+ return Promise.resolve(null);
324
+ return this.google(name, cfg).fetchModels();
325
+ }
326
+ /** Dashboard action: put one resting Google account back into rotation now. */
327
+ googleClearCooldown(ownerId) {
328
+ for (const [name, p] of Object.entries(this.deps.config().providers)) {
329
+ if (p.type === "google" && p.auth === "antigravity")
330
+ this.google(name, p).clearCooldown(ownerId);
331
+ }
332
+ }
333
+ /** Ask again whether an account still needs Google's verification page; the first provider that knows answers. */
334
+ async googleRecheckVerification(ownerId) {
335
+ for (const [name, p] of Object.entries(this.deps.config().providers)) {
336
+ if (p.type !== "google" || p.auth !== "antigravity")
337
+ continue;
338
+ const result = await this.google(name, p).recheckVerification(ownerId);
339
+ if (result !== "unknown")
340
+ return result;
341
+ }
342
+ return "unknown";
343
+ }
295
344
  constructor(deps) {
296
345
  this.deps = deps;
297
346
  this.claudeAccounts = new ClaudeAccountAuthPool({
@@ -525,6 +574,9 @@ export class Proxy {
525
574
  provider: "anthropic",
526
575
  stream: false,
527
576
  };
577
+ const session = req.headers["x-claude-code-session-id"];
578
+ if (typeof session === "string" && /^[\w-]{1,128}$/.test(session))
579
+ record.session = session;
528
580
  let observedUsage;
529
581
  let observedStopReason;
530
582
  // `failed` counts requests that did not get a proper response (vanished, upstream error,
@@ -747,6 +799,38 @@ export class Proxy {
747
799
  }
748
800
  return;
749
801
  }
802
+ if (provider.type === "google") {
803
+ // Google holds no server-side thread either, so a `continue` is refused as it is for the
804
+ // other translated providers; the CLI resends the turn with the full history.
805
+ const td = threadDecision(json);
806
+ if (td === "refuse") {
807
+ const out = JSON.stringify(THREAD_UNSUPPORTED);
808
+ res.writeHead(400, { "content-type": "application/json", "content-length": String(Buffer.byteLength(out)) }).end(out);
809
+ finish("400", out.length, "thread continue refused → CLI resends stateless", false);
810
+ return;
811
+ }
812
+ if (td === "strip")
813
+ stripThreadFields(json);
814
+ record = { ...record, target: route.model, provider: route.provider };
815
+ const routeEffort = effortOf(json);
816
+ if (routeEffort)
817
+ record.effort = routeEffort;
818
+ tag = `GOOGLE ${route.tag} effort=${routeEffort ?? "-"}`;
819
+ try {
820
+ const o = await this.google(route.provider, provider).handle(req, res, path, json, route.model, routeEffort);
821
+ this.recordOutcome(route.provider, o.status);
822
+ finish(String(o.status), o.bytes, o.note, o.status >= 400, { ...(o.usage ? { usage: o.usage } : {}), ...(o.stopReason ? { stopReason: o.stopReason } : {}) });
823
+ }
824
+ catch (e) {
825
+ this.recordOutcome(route.provider, 0);
826
+ finish("-", 0, `google error ${e.code ?? ""} ${e.message}`);
827
+ if (!res.headersSent)
828
+ res.writeHead(502, { "content-type": "application/json" }).end(JSON.stringify({ type: "error", error: { type: "api_error", message: e.message } }));
829
+ else
830
+ res.destroy();
831
+ }
832
+ return;
833
+ }
750
834
  if (provider.type === "anthropic") {
751
835
  if (!provider.accountPool || provider.auth !== "claude-code") {
752
836
  finish("400", 0, "native Anthropic provider is available through OpenAI ingress only", false);
@@ -1148,7 +1232,7 @@ export class Proxy {
1148
1232
  else if (status === 200 && isPickerBootstrap) {
1149
1233
  try {
1150
1234
  const j = JSON.parse(out.toString("utf8"));
1151
- const r = injectPickerModels(j, cfg.cli.extraModels, cfg.cli.autoCompactWindow);
1235
+ const r = injectPickerModels(j, cfg.cli.extraModels, cfg.cli.autoCompactWindow, (model) => effortLevelsForModel(cfg, model));
1152
1236
  if (r.surfaces.length > 0) {
1153
1237
  this.lastPickerInjection = { at: new Date().toISOString(), ...r };
1154
1238
  out = Buffer.from(JSON.stringify(j));
@@ -56,6 +56,8 @@ export class RequestLog {
56
56
  continue;
57
57
  if (filter.kind && record.kind !== filter.kind)
58
58
  continue;
59
+ if (filter.session && record.session !== filter.session)
60
+ continue;
59
61
  out.push(record);
60
62
  }
61
63
  return out;
@@ -50,6 +50,18 @@ export function markerOverride(body, aliases) {
50
50
  export function resolve(model, body, cfg) {
51
51
  if (typeof model !== "string")
52
52
  return null;
53
+ // Measured on Desktop's engine 2.1.284: the registered AUTO_MODE_MODEL env var is
54
+ // unused. Match the classifier's system prompt, never quoted conversation text.
55
+ // Its decision and wire format remain the engine's; only the inference target changes.
56
+ if (cfg.autoModeClassifier && isAutoModeClassifier(body)) {
57
+ const target = cfg.autoModeClassifier;
58
+ return {
59
+ provider: target.provider,
60
+ model: target.model,
61
+ effort: target.effort,
62
+ tag: `${model}->${target.model} (auto-mode classifier)`,
63
+ };
64
+ }
53
65
  let base = model;
54
66
  let effort;
55
67
  const at = model.indexOf("@");
@@ -128,6 +140,16 @@ export function resolve(model, body, cfg) {
128
140
  return null;
129
141
  return { provider: owner, model: finalModel, effort: ov?.effort ?? effort, tag: `${model}->${finalModel}${ov ? " (marker)" : ""}` };
130
142
  }
143
+ export function isAutoModeClassifier(body) {
144
+ const request = body;
145
+ if (!request || (Array.isArray(request.tools) && request.tools.length > 0))
146
+ return false;
147
+ const system = request.system;
148
+ const texts = typeof system === "string" ? [system] : Array.isArray(system)
149
+ ? system.filter((block) => block?.type === "text" && typeof block.text === "string").map((block) => block.text)
150
+ : [];
151
+ return texts.some((text) => text.trimStart().startsWith("You are a security monitor for autonomous AI coding agents."));
152
+ }
131
153
  /**
132
154
  * Providers whose own `models` list carries this id. Dated and undated forms match each other, the
133
155
  * same way a slot lookup does. Ingress-only providers are included on purpose — see the call site.
@@ -5,4 +5,4 @@
5
5
  // from the manifests. Until 0.1.1 the router and the CLI each carried their own literal, and both
6
6
  // still said "0.1.0" in the 0.1.1 release: after an update nobody could tell which router was
7
7
  // running, and the tray app had nothing to compare (2026-09-15, reported from a Windows install).
8
- export const VERSION = "0.7.0";
8
+ export const VERSION = "0.8.0";