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.
- package/CHANGELOG.md +51 -0
- package/README.ko.md +15 -1
- package/README.md +23 -3
- 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 +46 -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 +411 -32
- package/dist/router/src/agents.js +24 -8
- package/dist/router/src/catalog.js +80 -0
- package/dist/router/src/compat.js +27 -1
- package/dist/router/src/config.js +31 -0
- package/dist/router/src/index.js +12 -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 +42 -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 +85 -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 +420 -45
- package/dist/ui/i18n.js +76 -4
- package/dist/ui/index.html +5 -0
- package/dist/ui/style.css +4 -0
- package/docs/ARCHITECTURE.md +287 -2
- 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,
|
|
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 =
|
|
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(
|
|
251
|
+
const instructions = systemWithIdentity(systemWithDroppedToolNote(req), opts);
|
|
252
252
|
if (instructions)
|
|
253
253
|
out.instructions = instructions;
|
|
254
254
|
if (responseTools.length) {
|
package/dist/router/src/proxy.js
CHANGED
|
@@ -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));
|
|
@@ -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.
|
|
8
|
+
export const VERSION = "0.8.0";
|