@jsonstudio/llms 0.6.1435 → 0.6.1462
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/dist/conversion/compat/actions/anthropic-claude-code-system-prompt.d.ts +11 -0
- package/dist/conversion/compat/actions/anthropic-claude-code-system-prompt.js +101 -0
- package/dist/conversion/compat/actions/antigravity-thought-signature-cache.js +6 -5
- package/dist/conversion/compat/actions/antigravity-thought-signature-prepare.d.ts +3 -0
- package/dist/conversion/compat/actions/antigravity-thought-signature-prepare.js +109 -0
- package/dist/conversion/compat/antigravity-session-signature.d.ts +13 -0
- package/dist/conversion/compat/antigravity-session-signature.js +23 -4
- package/dist/conversion/compat/profiles/anthropic-claude-code.json +26 -0
- package/dist/conversion/compat/profiles/chat-claude-code.json +18 -0
- package/dist/conversion/compat/profiles/chat-gemini-cli.json +3 -1
- package/dist/conversion/compat/profiles/chat-gemini.json +1 -0
- package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +28 -0
- package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +17 -0
- package/dist/conversion/shared/compaction-detect.js +10 -4
- package/dist/router/virtual-router/engine-health.js +102 -5
- package/dist/router/virtual-router/engine-selection/tier-selection.js +19 -0
- package/package.json +2 -2
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { JsonObject } from '../../hub/types/json.js';
|
|
2
|
+
import type { AnthropicClaudeCodeSystemPromptConfig } from '../../hub/pipeline/compat/compat-types.js';
|
|
3
|
+
/**
|
|
4
|
+
* tabglm (Anthropic-compatible) strict-gates requests to Claude Code official client.
|
|
5
|
+
* It checks the system prompt format, and rejects mismatches with HTTP 403.
|
|
6
|
+
*
|
|
7
|
+
* This compat action normalizes the Anthropic `system` prompt into Claude Code official format.
|
|
8
|
+
* It ensures the *first* `system` block is Claude Code's official string, while keeping any
|
|
9
|
+
* existing system blocks (unless explicitly disabled).
|
|
10
|
+
*/
|
|
11
|
+
export declare function applyAnthropicClaudeCodeSystemPromptCompat(payload: JsonObject, config?: AnthropicClaudeCodeSystemPromptConfig): JsonObject;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
const DEFAULT_SYSTEM_TEXT = "You are Claude Code, Anthropic's official CLI for Claude.";
|
|
2
|
+
function isRecord(value) {
|
|
3
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
4
|
+
}
|
|
5
|
+
function normalizeSystemBlocks(system) {
|
|
6
|
+
const blocks = [];
|
|
7
|
+
const pushText = (text, extra) => {
|
|
8
|
+
const trimmed = text.trim();
|
|
9
|
+
if (!trimmed) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
blocks.push({
|
|
13
|
+
...(extra ?? {}),
|
|
14
|
+
type: 'text',
|
|
15
|
+
text: trimmed
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
if (typeof system === 'string') {
|
|
19
|
+
pushText(system);
|
|
20
|
+
return blocks;
|
|
21
|
+
}
|
|
22
|
+
if (Array.isArray(system)) {
|
|
23
|
+
for (const entry of system) {
|
|
24
|
+
if (typeof entry === 'string') {
|
|
25
|
+
pushText(entry);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (!isRecord(entry)) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const text = typeof entry.text === 'string' ? entry.text : '';
|
|
32
|
+
if (text) {
|
|
33
|
+
const extra = { ...entry };
|
|
34
|
+
delete extra.type;
|
|
35
|
+
delete extra.text;
|
|
36
|
+
pushText(text, extra);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return blocks;
|
|
40
|
+
}
|
|
41
|
+
if (isRecord(system)) {
|
|
42
|
+
const text = typeof system.text === 'string' ? system.text : '';
|
|
43
|
+
if (text) {
|
|
44
|
+
const extra = { ...system };
|
|
45
|
+
delete extra.type;
|
|
46
|
+
delete extra.text;
|
|
47
|
+
pushText(text, extra);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return blocks;
|
|
51
|
+
}
|
|
52
|
+
function dedupeSystemBlocksByText(blocks) {
|
|
53
|
+
const seen = new Set();
|
|
54
|
+
const result = [];
|
|
55
|
+
for (const block of blocks) {
|
|
56
|
+
const text = typeof block.text === 'string' ? block.text.trim() : '';
|
|
57
|
+
if (!text)
|
|
58
|
+
continue;
|
|
59
|
+
if (seen.has(text))
|
|
60
|
+
continue;
|
|
61
|
+
seen.add(text);
|
|
62
|
+
result.push({ ...block, type: 'text', text });
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* tabglm (Anthropic-compatible) strict-gates requests to Claude Code official client.
|
|
68
|
+
* It checks the system prompt format, and rejects mismatches with HTTP 403.
|
|
69
|
+
*
|
|
70
|
+
* This compat action normalizes the Anthropic `system` prompt into Claude Code official format.
|
|
71
|
+
* It ensures the *first* `system` block is Claude Code's official string, while keeping any
|
|
72
|
+
* existing system blocks (unless explicitly disabled).
|
|
73
|
+
*/
|
|
74
|
+
export function applyAnthropicClaudeCodeSystemPromptCompat(payload, config) {
|
|
75
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
76
|
+
return payload;
|
|
77
|
+
}
|
|
78
|
+
const root = payload;
|
|
79
|
+
const systemText = (typeof config?.systemText === 'string' && config.systemText.trim())
|
|
80
|
+
? config.systemText.trim()
|
|
81
|
+
: DEFAULT_SYSTEM_TEXT;
|
|
82
|
+
const preserveExisting = config?.preserveExistingSystemAsUserMessage !== false;
|
|
83
|
+
const existingBlocks = dedupeSystemBlocksByText(normalizeSystemBlocks(root.system));
|
|
84
|
+
const official = { type: 'text', text: systemText };
|
|
85
|
+
let nextBlocks = [];
|
|
86
|
+
if (!preserveExisting) {
|
|
87
|
+
nextBlocks = [official];
|
|
88
|
+
}
|
|
89
|
+
else if (existingBlocks.length === 0) {
|
|
90
|
+
nextBlocks = [official];
|
|
91
|
+
}
|
|
92
|
+
else if (existingBlocks[0].text !== systemText) {
|
|
93
|
+
nextBlocks = [official, ...existingBlocks.filter((b) => b.text !== systemText)];
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
nextBlocks = [existingBlocks[0], ...existingBlocks.slice(1).filter((b) => b.text !== systemText)];
|
|
97
|
+
nextBlocks[0] = { ...nextBlocks[0], type: 'text', text: systemText };
|
|
98
|
+
}
|
|
99
|
+
root.system = nextBlocks;
|
|
100
|
+
return root;
|
|
101
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cacheAntigravitySessionSignature,
|
|
1
|
+
import { cacheAntigravitySessionSignature, getAntigravityRequestSessionMeta } from '../antigravity-session-signature.js';
|
|
2
2
|
function isRecord(value) {
|
|
3
3
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
4
4
|
}
|
|
@@ -28,17 +28,18 @@ export function cacheAntigravityThoughtSignatureFromGeminiResponse(payload, adap
|
|
|
28
28
|
typeof payloadAny.requestId === 'string' ? String(payloadAny.requestId) : ''
|
|
29
29
|
].filter((k) => typeof k === 'string' && k.trim().length);
|
|
30
30
|
let sessionId = '';
|
|
31
|
+
let messageCount = 1;
|
|
31
32
|
for (const key of keyCandidates) {
|
|
32
|
-
const resolved =
|
|
33
|
-
if (resolved && resolved.trim().length) {
|
|
34
|
-
sessionId = resolved.trim();
|
|
33
|
+
const resolved = getAntigravityRequestSessionMeta(key);
|
|
34
|
+
if (resolved && resolved.sessionId.trim().length) {
|
|
35
|
+
sessionId = resolved.sessionId.trim();
|
|
36
|
+
messageCount = resolved.messageCount;
|
|
35
37
|
break;
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
if (!sessionId) {
|
|
39
41
|
return payload;
|
|
40
42
|
}
|
|
41
|
-
const messageCount = 1;
|
|
42
43
|
const candidatesRaw = payload.candidates;
|
|
43
44
|
const candidates = Array.isArray(candidatesRaw) ? candidatesRaw : [];
|
|
44
45
|
for (const candidate of candidates) {
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { AdapterContext } from '../../hub/types/chat-envelope.js';
|
|
2
|
+
import type { JsonObject } from '../../hub/types/json.js';
|
|
3
|
+
export declare function prepareAntigravityThoughtSignatureForGeminiRequest(payload: JsonObject, adapterContext?: AdapterContext): JsonObject;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { cacheAntigravityRequestSessionMeta, clearAntigravitySessionSignature, extractAntigravityGeminiSessionId, getAntigravitySessionSignatureEntry, shouldTreatAsMissingThoughtSignature } from '../antigravity-session-signature.js';
|
|
3
|
+
function isRecord(value) {
|
|
4
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
function shouldEnableForAdapter(adapterContext) {
|
|
7
|
+
if (!adapterContext) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
const protocol = typeof adapterContext.providerProtocol === 'string' ? adapterContext.providerProtocol.trim().toLowerCase() : '';
|
|
11
|
+
if (protocol !== 'gemini-chat') {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
const providerIdOrKey = typeof adapterContext.providerId === 'string' ? adapterContext.providerId.trim().toLowerCase() : '';
|
|
15
|
+
const effectiveProviderId = providerIdOrKey.split('.')[0] ?? '';
|
|
16
|
+
return effectiveProviderId === 'antigravity';
|
|
17
|
+
}
|
|
18
|
+
function locateGeminiContentsNode(root) {
|
|
19
|
+
if (Array.isArray(root.contents)) {
|
|
20
|
+
return root;
|
|
21
|
+
}
|
|
22
|
+
const req = root.request;
|
|
23
|
+
if (isRecord(req) && Array.isArray(req.contents)) {
|
|
24
|
+
return req;
|
|
25
|
+
}
|
|
26
|
+
const data = root.data;
|
|
27
|
+
if (isRecord(data)) {
|
|
28
|
+
return locateGeminiContentsNode(data);
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
function sha256Hex(value) {
|
|
33
|
+
return createHash('sha256').update(value).digest('hex');
|
|
34
|
+
}
|
|
35
|
+
function resolveStableSessionId(adapterContext) {
|
|
36
|
+
if (!adapterContext) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
const ctxAny = adapterContext;
|
|
40
|
+
const candidates = [ctxAny.sessionId, ctxAny.conversationId].filter((v) => typeof v === 'string');
|
|
41
|
+
const raw = candidates.map((s) => s.trim()).find((s) => s.length > 0);
|
|
42
|
+
if (!raw) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
if (raw.toLowerCase().startsWith('sid-')) {
|
|
46
|
+
return raw;
|
|
47
|
+
}
|
|
48
|
+
return `sid-${sha256Hex(raw).slice(0, 16)}`;
|
|
49
|
+
}
|
|
50
|
+
function injectThoughtSignatureIntoFunctionCalls(contentsNode, signature) {
|
|
51
|
+
const contentsRaw = contentsNode.contents;
|
|
52
|
+
if (!Array.isArray(contentsRaw)) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const contents = contentsRaw;
|
|
56
|
+
for (const entry of contents) {
|
|
57
|
+
if (!isRecord(entry))
|
|
58
|
+
continue;
|
|
59
|
+
const partsRaw = entry.parts;
|
|
60
|
+
if (!Array.isArray(partsRaw))
|
|
61
|
+
continue;
|
|
62
|
+
const parts = partsRaw;
|
|
63
|
+
for (const part of parts) {
|
|
64
|
+
if (!isRecord(part))
|
|
65
|
+
continue;
|
|
66
|
+
if (!isRecord(part.functionCall))
|
|
67
|
+
continue;
|
|
68
|
+
const existing = part.thoughtSignature;
|
|
69
|
+
if (!shouldTreatAsMissingThoughtSignature(existing)) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
part.thoughtSignature = signature;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export function prepareAntigravityThoughtSignatureForGeminiRequest(payload, adapterContext) {
|
|
77
|
+
if (!shouldEnableForAdapter(adapterContext)) {
|
|
78
|
+
return payload;
|
|
79
|
+
}
|
|
80
|
+
const stableSessionId = resolveStableSessionId(adapterContext);
|
|
81
|
+
const derivedSessionId = extractAntigravityGeminiSessionId(payload);
|
|
82
|
+
const sessionId = stableSessionId || derivedSessionId;
|
|
83
|
+
const ctxAny = adapterContext;
|
|
84
|
+
const keys = [
|
|
85
|
+
adapterContext.requestId,
|
|
86
|
+
typeof ctxAny.clientRequestId === 'string' ? String(ctxAny.clientRequestId) : '',
|
|
87
|
+
typeof ctxAny.groupRequestId === 'string' ? String(ctxAny.groupRequestId) : ''
|
|
88
|
+
].filter((k) => typeof k === 'string' && k.trim().length);
|
|
89
|
+
const root = payload;
|
|
90
|
+
const contentsNode = locateGeminiContentsNode(root);
|
|
91
|
+
const messageCount = contentsNode && Array.isArray(contentsNode.contents) ? contentsNode.contents.length : 1;
|
|
92
|
+
for (const key of keys) {
|
|
93
|
+
cacheAntigravityRequestSessionMeta(key, { sessionId, messageCount });
|
|
94
|
+
}
|
|
95
|
+
const cached = getAntigravitySessionSignatureEntry(sessionId);
|
|
96
|
+
if (!cached) {
|
|
97
|
+
return payload;
|
|
98
|
+
}
|
|
99
|
+
if (typeof messageCount === 'number' && messageCount > 0 && messageCount < cached.messageCount) {
|
|
100
|
+
// Rewind detected: do not inject a "future" signature; clear and wait for a fresh signature from upstream.
|
|
101
|
+
clearAntigravitySessionSignature(sessionId);
|
|
102
|
+
return payload;
|
|
103
|
+
}
|
|
104
|
+
if (!contentsNode) {
|
|
105
|
+
return payload;
|
|
106
|
+
}
|
|
107
|
+
injectThoughtSignatureIntoFunctionCalls(contentsNode, cached.signature);
|
|
108
|
+
return payload;
|
|
109
|
+
}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
export declare const DUMMY_THOUGHT_SIGNATURE = "skip_thought_signature_validator";
|
|
2
2
|
export declare function cacheAntigravityRequestSessionId(requestId: string, sessionId: string): void;
|
|
3
|
+
export declare function cacheAntigravityRequestSessionMeta(requestId: string, meta: {
|
|
4
|
+
sessionId: string;
|
|
5
|
+
messageCount?: number;
|
|
6
|
+
}): void;
|
|
3
7
|
export declare function getAntigravityRequestSessionId(requestId: string): string | undefined;
|
|
8
|
+
export declare function getAntigravityRequestSessionMeta(requestId: string): {
|
|
9
|
+
sessionId: string;
|
|
10
|
+
messageCount: number;
|
|
11
|
+
} | undefined;
|
|
4
12
|
/**
|
|
5
13
|
* Antigravity-Manager alignment: derive a stable session fingerprint for Gemini native requests.
|
|
6
14
|
* - sha256(first user text parts joined), if len>10 and no "<system-reminder>"
|
|
@@ -10,4 +18,9 @@ export declare function getAntigravityRequestSessionId(requestId: string): strin
|
|
|
10
18
|
export declare function extractAntigravityGeminiSessionId(payload: unknown): string;
|
|
11
19
|
export declare function cacheAntigravitySessionSignature(sessionId: string, signature: string, messageCount?: number): void;
|
|
12
20
|
export declare function getAntigravitySessionSignature(sessionId: string): string | undefined;
|
|
21
|
+
export declare function getAntigravitySessionSignatureEntry(sessionId: string): {
|
|
22
|
+
signature: string;
|
|
23
|
+
messageCount: number;
|
|
24
|
+
} | undefined;
|
|
25
|
+
export declare function clearAntigravitySessionSignature(sessionId: string): void;
|
|
13
26
|
export declare function shouldTreatAsMissingThoughtSignature(value: unknown): boolean;
|
|
@@ -95,13 +95,19 @@ function ensureCacheLimit() {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
export function cacheAntigravityRequestSessionId(requestId, sessionId) {
|
|
98
|
+
cacheAntigravityRequestSessionMeta(requestId, { sessionId });
|
|
99
|
+
}
|
|
100
|
+
export function cacheAntigravityRequestSessionMeta(requestId, meta) {
|
|
98
101
|
const rid = typeof requestId === 'string' ? requestId.trim() : '';
|
|
99
|
-
const sid = typeof sessionId === 'string' ? sessionId.trim() : '';
|
|
102
|
+
const sid = typeof meta?.sessionId === 'string' ? meta.sessionId.trim() : '';
|
|
100
103
|
if (!rid || !sid) {
|
|
101
104
|
return;
|
|
102
105
|
}
|
|
106
|
+
const messageCount = typeof meta?.messageCount === 'number' && Number.isFinite(meta.messageCount) && meta.messageCount > 0
|
|
107
|
+
? Math.floor(meta.messageCount)
|
|
108
|
+
: 1;
|
|
103
109
|
const ts = nowMs();
|
|
104
|
-
requestSessionIds.set(rid, { sessionId: sid, timestamp: ts });
|
|
110
|
+
requestSessionIds.set(rid, { sessionId: sid, messageCount, timestamp: ts });
|
|
105
111
|
if (requestSessionIds.size <= SESSION_CACHE_LIMIT) {
|
|
106
112
|
return;
|
|
107
113
|
}
|
|
@@ -116,6 +122,10 @@ export function cacheAntigravityRequestSessionId(requestId, sessionId) {
|
|
|
116
122
|
}
|
|
117
123
|
}
|
|
118
124
|
export function getAntigravityRequestSessionId(requestId) {
|
|
125
|
+
const meta = getAntigravityRequestSessionMeta(requestId);
|
|
126
|
+
return meta?.sessionId;
|
|
127
|
+
}
|
|
128
|
+
export function getAntigravityRequestSessionMeta(requestId) {
|
|
119
129
|
const rid = typeof requestId === 'string' ? requestId.trim() : '';
|
|
120
130
|
if (!rid) {
|
|
121
131
|
return undefined;
|
|
@@ -129,7 +139,7 @@ export function getAntigravityRequestSessionId(requestId) {
|
|
|
129
139
|
requestSessionIds.delete(rid);
|
|
130
140
|
return undefined;
|
|
131
141
|
}
|
|
132
|
-
return entry.sessionId;
|
|
142
|
+
return { sessionId: entry.sessionId, messageCount: entry.messageCount };
|
|
133
143
|
}
|
|
134
144
|
function findGeminiContentsNode(payload) {
|
|
135
145
|
if (!isRecord(payload)) {
|
|
@@ -221,6 +231,9 @@ export function cacheAntigravitySessionSignature(sessionId, signature, messageCo
|
|
|
221
231
|
ensureCacheLimit();
|
|
222
232
|
}
|
|
223
233
|
export function getAntigravitySessionSignature(sessionId) {
|
|
234
|
+
return getAntigravitySessionSignatureEntry(sessionId)?.signature;
|
|
235
|
+
}
|
|
236
|
+
export function getAntigravitySessionSignatureEntry(sessionId) {
|
|
224
237
|
if (typeof sessionId !== 'string' || !sessionId.trim()) {
|
|
225
238
|
return undefined;
|
|
226
239
|
}
|
|
@@ -234,7 +247,13 @@ export function getAntigravitySessionSignature(sessionId) {
|
|
|
234
247
|
sessionSignatures.delete(key);
|
|
235
248
|
return undefined;
|
|
236
249
|
}
|
|
237
|
-
return entry.signature;
|
|
250
|
+
return { signature: entry.signature, messageCount: entry.messageCount };
|
|
251
|
+
}
|
|
252
|
+
export function clearAntigravitySessionSignature(sessionId) {
|
|
253
|
+
if (typeof sessionId !== 'string' || !sessionId.trim()) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
sessionSignatures.delete(sessionId.trim());
|
|
238
257
|
}
|
|
239
258
|
export function shouldTreatAsMissingThoughtSignature(value) {
|
|
240
259
|
if (typeof value !== 'string') {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "anthropic:claude-code",
|
|
3
|
+
"protocol": "anthropic-messages",
|
|
4
|
+
"request": {
|
|
5
|
+
"mappings": [
|
|
6
|
+
{ "action": "snapshot", "phase": "compat-pre" },
|
|
7
|
+
{ "action": "set_default", "path": "metadata", "value": {} },
|
|
8
|
+
{ "action": "set", "path": "metadata.userAgent", "value": "Claude-Code/2.1.25 (macos; arm64)" },
|
|
9
|
+
{ "action": "set", "path": "metadata.clientOriginator", "value": "claude-code" },
|
|
10
|
+
{ "action": "set_default", "path": "metadata.clientHeaders", "value": {} },
|
|
11
|
+
{ "action": "set", "path": "metadata.clientHeaders.User-Agent", "value": "Claude-Code/2.1.25 (macos; arm64)" },
|
|
12
|
+
{ "action": "set", "path": "metadata.clientHeaders.originator", "value": "claude-code" },
|
|
13
|
+
{ "action": "set", "path": "metadata.clientHeaders.X-App", "value": "claude-code" },
|
|
14
|
+
{ "action": "set", "path": "metadata.clientHeaders.x-app-version", "value": "2.1.25" },
|
|
15
|
+
{ "action": "set", "path": "metadata.clientHeaders.anthropic-beta", "value": "claude-code-20250219,interleaved-thinking-2025-05-14,context-management-2025-06-27,prompt-caching-scope-2026-01-05" },
|
|
16
|
+
{
|
|
17
|
+
"action": "anthropic_claude_code_system_prompt",
|
|
18
|
+
"config": { "preserveExistingSystemAsUserMessage": true }
|
|
19
|
+
},
|
|
20
|
+
{ "action": "snapshot", "phase": "compat-post" }
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
"response": {
|
|
24
|
+
"mappings": []
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "chat:claude-code",
|
|
3
|
+
"protocol": "anthropic-messages",
|
|
4
|
+
"request": {
|
|
5
|
+
"mappings": [
|
|
6
|
+
{ "action": "snapshot", "phase": "compat-pre" },
|
|
7
|
+
{
|
|
8
|
+
"action": "anthropic_claude_code_system_prompt",
|
|
9
|
+
"config": { "preserveExistingSystemAsUserMessage": true }
|
|
10
|
+
},
|
|
11
|
+
{ "action": "snapshot", "phase": "compat-post" }
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
"response": {
|
|
15
|
+
"mappings": []
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
@@ -18,7 +18,9 @@ import { applyGlmImageContentTransform } from '../../../compat/actions/glm-image
|
|
|
18
18
|
import { applyGlmVisionPromptTransform } from '../../../compat/actions/glm-vision-prompt.js';
|
|
19
19
|
import { applyClaudeThinkingToolSchemaCompat } from '../../../compat/actions/claude-thinking-tools.js';
|
|
20
20
|
import { wrapGeminiCliRequest } from '../../../compat/actions/gemini-cli-request.js';
|
|
21
|
+
import { prepareAntigravityThoughtSignatureForGeminiRequest } from '../../../compat/actions/antigravity-thought-signature-prepare.js';
|
|
21
22
|
import { cacheAntigravityThoughtSignatureFromGeminiResponse } from '../../../compat/actions/antigravity-thought-signature-cache.js';
|
|
23
|
+
import { applyAnthropicClaudeCodeSystemPromptCompat } from '../../../compat/actions/anthropic-claude-code-system-prompt.js';
|
|
22
24
|
const RATE_LIMIT_ERROR = 'ERR_COMPAT_RATE_LIMIT_DETECTED';
|
|
23
25
|
const INTERNAL_STATE = Symbol('compat.internal_state');
|
|
24
26
|
export function runRequestCompatPipeline(profileId, payload, options) {
|
|
@@ -26,6 +28,9 @@ export function runRequestCompatPipeline(profileId, payload, options) {
|
|
|
26
28
|
if (!profile) {
|
|
27
29
|
return { payload };
|
|
28
30
|
}
|
|
31
|
+
if (!isProtocolCompatible(profile, options?.adapterContext)) {
|
|
32
|
+
return { payload };
|
|
33
|
+
}
|
|
29
34
|
const stage = pickStageConfig(profile, 'request');
|
|
30
35
|
if (!stage) {
|
|
31
36
|
return { payload };
|
|
@@ -47,6 +52,9 @@ export function runResponseCompatPipeline(profileId, payload, options) {
|
|
|
47
52
|
if (!profile) {
|
|
48
53
|
return { payload };
|
|
49
54
|
}
|
|
55
|
+
if (!isProtocolCompatible(profile, options?.adapterContext)) {
|
|
56
|
+
return { payload };
|
|
57
|
+
}
|
|
50
58
|
const stage = pickStageConfig(profile, 'response');
|
|
51
59
|
if (!stage) {
|
|
52
60
|
return { payload };
|
|
@@ -93,6 +101,16 @@ function pickStageConfig(profile, stage) {
|
|
|
93
101
|
}
|
|
94
102
|
return null;
|
|
95
103
|
}
|
|
104
|
+
function isProtocolCompatible(profile, adapterContext) {
|
|
105
|
+
const required = typeof profile.protocol === 'string' ? profile.protocol.trim().toLowerCase() : '';
|
|
106
|
+
if (!required) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
const actual = typeof adapterContext?.providerProtocol === 'string' && adapterContext.providerProtocol.trim()
|
|
110
|
+
? adapterContext.providerProtocol.trim().toLowerCase()
|
|
111
|
+
: '';
|
|
112
|
+
return Boolean(actual) && actual === required;
|
|
113
|
+
}
|
|
96
114
|
function applyMapping(root, mapping, state) {
|
|
97
115
|
switch (mapping.action) {
|
|
98
116
|
case 'remove':
|
|
@@ -207,11 +225,21 @@ function applyMapping(root, mapping, state) {
|
|
|
207
225
|
replaceRoot(root, wrapGeminiCliRequest(root, state.adapterContext));
|
|
208
226
|
}
|
|
209
227
|
break;
|
|
228
|
+
case 'antigravity_thought_signature_prepare':
|
|
229
|
+
if (state.direction === 'request') {
|
|
230
|
+
replaceRoot(root, prepareAntigravityThoughtSignatureForGeminiRequest(root, state.adapterContext));
|
|
231
|
+
}
|
|
232
|
+
break;
|
|
210
233
|
case 'antigravity_thought_signature_cache':
|
|
211
234
|
if (state.direction === 'response') {
|
|
212
235
|
replaceRoot(root, cacheAntigravityThoughtSignatureFromGeminiResponse(root, state.adapterContext));
|
|
213
236
|
}
|
|
214
237
|
break;
|
|
238
|
+
case 'anthropic_claude_code_system_prompt':
|
|
239
|
+
if (state.direction === 'request') {
|
|
240
|
+
replaceRoot(root, applyAnthropicClaudeCodeSystemPromptCompat(root, mapping.config));
|
|
241
|
+
}
|
|
242
|
+
break;
|
|
215
243
|
case 'glm_image_content':
|
|
216
244
|
if (state.direction === 'request') {
|
|
217
245
|
replaceRoot(root, applyGlmImageContentTransform(root));
|
|
@@ -118,8 +118,25 @@ export type MappingInstruction = {
|
|
|
118
118
|
action: 'claude_thinking_tool_schema';
|
|
119
119
|
} | {
|
|
120
120
|
action: 'gemini_cli_request_wrap';
|
|
121
|
+
} | {
|
|
122
|
+
action: 'antigravity_thought_signature_prepare';
|
|
121
123
|
} | {
|
|
122
124
|
action: 'antigravity_thought_signature_cache';
|
|
125
|
+
} | {
|
|
126
|
+
action: 'anthropic_claude_code_system_prompt';
|
|
127
|
+
config?: AnthropicClaudeCodeSystemPromptConfig;
|
|
128
|
+
};
|
|
129
|
+
export type AnthropicClaudeCodeSystemPromptConfig = {
|
|
130
|
+
/**
|
|
131
|
+
* Force the Anthropic `system` prompt to match Claude Code official format.
|
|
132
|
+
* Defaults to "You are Claude Code, Anthropic's official CLI for Claude."
|
|
133
|
+
*/
|
|
134
|
+
systemText?: string;
|
|
135
|
+
/**
|
|
136
|
+
* When replacing an existing system prompt, optionally move the previous
|
|
137
|
+
* system text into the beginning of the user message history.
|
|
138
|
+
*/
|
|
139
|
+
preserveExistingSystemAsUserMessage?: boolean;
|
|
123
140
|
};
|
|
124
141
|
export type FilterInstruction = {
|
|
125
142
|
action: 'rate_limit_text';
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
function hasCompactionMarker(value) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
// Compaction markers are intended to be explicit “header-like” sentinels produced by
|
|
3
|
+
// our own compaction flows, not arbitrary substrings inside user/dev instructions.
|
|
4
|
+
//
|
|
5
|
+
// If we treat any occurrence as a compaction request, normal conversations that
|
|
6
|
+
// contain phrases like “handoff summary …” (e.g. debug summaries pasted into chat)
|
|
7
|
+
// will get misclassified and will disable stopMessage / other servertools.
|
|
8
|
+
const lower = value.trimStart().toLowerCase();
|
|
9
|
+
return (lower.startsWith('context checkpoint compaction') ||
|
|
10
|
+
lower.startsWith('checkpoint compaction') ||
|
|
11
|
+
lower.startsWith('handoff summary for another llm'));
|
|
6
12
|
}
|
|
7
13
|
function containsMarker(value) {
|
|
8
14
|
if (typeof value === 'string') {
|
|
@@ -104,6 +104,7 @@ const antigravityRiskBySignature = new Map();
|
|
|
104
104
|
const ANTIGRAVITY_RISK_RESET_WINDOW_MS = readEnvDuration('ROUTECODEX_ANTIGRAVITY_RISK_RESET_WINDOW', 30 * 60_000);
|
|
105
105
|
const ANTIGRAVITY_RISK_COOLDOWN_MS = readEnvDuration('ROUTECODEX_ANTIGRAVITY_RISK_COOLDOWN', 5 * 60_000);
|
|
106
106
|
const ANTIGRAVITY_RISK_BAN_MS = readEnvDuration('ROUTECODEX_ANTIGRAVITY_RISK_BAN', 24 * 60 * 60_000);
|
|
107
|
+
const ANTIGRAVITY_AUTH_VERIFY_BAN_MS = readEnvDuration('ROUTECODEX_ANTIGRAVITY_AUTH_VERIFY_BAN', 24 * 60 * 60_000);
|
|
107
108
|
function isAntigravityEvent(event) {
|
|
108
109
|
const runtime = event?.runtime;
|
|
109
110
|
if (!runtime || typeof runtime !== 'object') {
|
|
@@ -116,6 +117,60 @@ function isAntigravityEvent(event) {
|
|
|
116
117
|
const providerKey = typeof runtime.providerKey === 'string' ? runtime.providerKey.trim().toLowerCase() : '';
|
|
117
118
|
return providerKey.startsWith('antigravity.');
|
|
118
119
|
}
|
|
120
|
+
function isGoogleAccountVerificationRequired(event) {
|
|
121
|
+
const sources = [];
|
|
122
|
+
const message = typeof event.message === 'string' ? event.message : '';
|
|
123
|
+
if (message)
|
|
124
|
+
sources.push(message);
|
|
125
|
+
const details = event.details;
|
|
126
|
+
if (details && typeof details === 'object' && !Array.isArray(details)) {
|
|
127
|
+
const upstreamMessage = details.upstreamMessage;
|
|
128
|
+
if (typeof upstreamMessage === 'string' && upstreamMessage.trim()) {
|
|
129
|
+
sources.push(upstreamMessage);
|
|
130
|
+
}
|
|
131
|
+
const meta = details.meta;
|
|
132
|
+
if (meta && typeof meta === 'object' && !Array.isArray(meta)) {
|
|
133
|
+
const metaUpstream = meta.upstreamMessage;
|
|
134
|
+
if (typeof metaUpstream === 'string' && metaUpstream.trim()) {
|
|
135
|
+
sources.push(metaUpstream);
|
|
136
|
+
}
|
|
137
|
+
const metaMessage = meta.message;
|
|
138
|
+
if (typeof metaMessage === 'string' && metaMessage.trim()) {
|
|
139
|
+
sources.push(metaMessage);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (sources.length === 0) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
const lowered = sources.join(' | ').toLowerCase();
|
|
147
|
+
return (lowered.includes('verify your account') ||
|
|
148
|
+
lowered.includes('accounts.google.com/signin/continue') ||
|
|
149
|
+
lowered.includes('support.google.com/accounts?p=al_alert'));
|
|
150
|
+
}
|
|
151
|
+
function resolveAntigravityRuntimeKey(event) {
|
|
152
|
+
const runtime = event.runtime;
|
|
153
|
+
if (!runtime || typeof runtime !== 'object') {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
const target = runtime.target && typeof runtime.target === 'object' ? runtime.target : null;
|
|
157
|
+
const byTarget = target && typeof target.runtimeKey === 'string' && target.runtimeKey.trim() ? target.runtimeKey.trim() : '';
|
|
158
|
+
if (byTarget) {
|
|
159
|
+
return byTarget;
|
|
160
|
+
}
|
|
161
|
+
const providerKey = (typeof runtime.providerKey === 'string' && runtime.providerKey.trim() ? runtime.providerKey.trim() : '') ||
|
|
162
|
+
(target && typeof target.providerKey === 'string' && String(target.providerKey).trim()
|
|
163
|
+
? String(target.providerKey).trim()
|
|
164
|
+
: '');
|
|
165
|
+
if (!providerKey) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
const parts = providerKey.split('.').filter(Boolean);
|
|
169
|
+
if (parts.length < 2) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
return `${parts[0]}.${parts[1]}`;
|
|
173
|
+
}
|
|
119
174
|
function shouldTriggerAntigravityRiskPolicy(event) {
|
|
120
175
|
const status = typeof event.status === 'number' ? event.status : undefined;
|
|
121
176
|
if (typeof status === 'number' && Number.isFinite(status)) {
|
|
@@ -142,12 +197,47 @@ export function applyAntigravityRiskPolicyImpl(event, providerRegistry, healthMa
|
|
|
142
197
|
if (!shouldTriggerAntigravityRiskPolicy(event)) {
|
|
143
198
|
return;
|
|
144
199
|
}
|
|
200
|
+
const runtimeKey = resolveAntigravityRuntimeKey(event);
|
|
201
|
+
const verificationRequired = Boolean(runtimeKey) && isGoogleAccountVerificationRequired(event);
|
|
202
|
+
// Account verification errors are per-account and must be handled immediately:
|
|
203
|
+
// - blacklist only the affected runtimeKey (do NOT penalize other Antigravity accounts)
|
|
204
|
+
// - require user to complete OAuth verification to recover
|
|
205
|
+
if (verificationRequired && runtimeKey) {
|
|
206
|
+
const providerKeys = providerRegistry
|
|
207
|
+
.listProviderKeys('antigravity')
|
|
208
|
+
.filter((key) => typeof key === 'string' && key.startsWith(`${runtimeKey}.`));
|
|
209
|
+
if (providerKeys.length === 0) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
for (const key of providerKeys) {
|
|
213
|
+
try {
|
|
214
|
+
if (!healthManager.isAvailable(key)) {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
healthManager.tripProvider(key, 'auth_verify', ANTIGRAVITY_AUTH_VERIFY_BAN_MS);
|
|
218
|
+
markProviderCooldown(key, ANTIGRAVITY_AUTH_VERIFY_BAN_MS);
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// ignore lookup failures
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
debug?.log?.('[virtual-router] antigravity auth verify blacklist', {
|
|
225
|
+
runtimeKey,
|
|
226
|
+
cooldownMs: ANTIGRAVITY_AUTH_VERIFY_BAN_MS,
|
|
227
|
+
affected: providerKeys
|
|
228
|
+
});
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
145
231
|
const signature = computeAntigravityRiskSignature(event);
|
|
146
232
|
if (!signature || signature === 'unknown') {
|
|
147
233
|
return;
|
|
148
234
|
}
|
|
235
|
+
// Antigravity-Manager alignment: account verification errors are per-account (runtimeKey) and should not
|
|
236
|
+
// penalize all other Antigravity accounts. Request-shape/auth policy failures are still treated globally.
|
|
237
|
+
const runtimeScoped = Boolean(runtimeKey) && isGoogleAccountVerificationRequired(event);
|
|
238
|
+
const riskKey = runtimeScoped ? `${signature}|${runtimeKey}` : signature;
|
|
149
239
|
const now = Date.now();
|
|
150
|
-
const prev = antigravityRiskBySignature.get(
|
|
240
|
+
const prev = antigravityRiskBySignature.get(riskKey);
|
|
151
241
|
let count = 1;
|
|
152
242
|
if (prev) {
|
|
153
243
|
const elapsed = now - prev.lastAt;
|
|
@@ -155,12 +245,17 @@ export function applyAntigravityRiskPolicyImpl(event, providerRegistry, healthMa
|
|
|
155
245
|
count = prev.count + 1;
|
|
156
246
|
}
|
|
157
247
|
}
|
|
158
|
-
antigravityRiskBySignature.set(
|
|
248
|
+
antigravityRiskBySignature.set(riskKey, { count, lastAt: now });
|
|
159
249
|
// Escalation ladder (Antigravity account safety):
|
|
160
250
|
// 1) First/second occurrence: normal retry/fallback logic handles per-request behavior.
|
|
161
|
-
// 2) Third occurrence: cooldown
|
|
162
|
-
// 3) Fourth+ occurrence: effectively remove Antigravity from routing (long ban window).
|
|
163
|
-
const
|
|
251
|
+
// 2) Third occurrence: cooldown Antigravity providerKeys for 5 minutes (scoped for account verification errors).
|
|
252
|
+
// 3) Fourth+ occurrence: effectively remove Antigravity from routing (long ban window; scoped for account verification errors).
|
|
253
|
+
const allProviderKeys = providerRegistry.listProviderKeys('antigravity');
|
|
254
|
+
const providerKeys = runtimeScoped
|
|
255
|
+
? allProviderKeys.filter((key) => {
|
|
256
|
+
return typeof key === 'string' && runtimeKey ? key.startsWith(`${runtimeKey}.`) : false;
|
|
257
|
+
})
|
|
258
|
+
: allProviderKeys;
|
|
164
259
|
if (providerKeys.length === 0) {
|
|
165
260
|
return;
|
|
166
261
|
}
|
|
@@ -176,6 +271,7 @@ export function applyAntigravityRiskPolicyImpl(event, providerRegistry, healthMa
|
|
|
176
271
|
}
|
|
177
272
|
debug?.log?.('[virtual-router] antigravity risk cooldown', {
|
|
178
273
|
signature,
|
|
274
|
+
...(runtimeScoped ? { runtimeKey } : {}),
|
|
179
275
|
count,
|
|
180
276
|
cooldownMs: ANTIGRAVITY_RISK_COOLDOWN_MS,
|
|
181
277
|
affected: providerKeys
|
|
@@ -194,6 +290,7 @@ export function applyAntigravityRiskPolicyImpl(event, providerRegistry, healthMa
|
|
|
194
290
|
}
|
|
195
291
|
debug?.log?.('[virtual-router] antigravity risk blacklist', {
|
|
196
292
|
signature,
|
|
293
|
+
...(runtimeScoped ? { runtimeKey } : {}),
|
|
197
294
|
count,
|
|
198
295
|
cooldownMs: ttl,
|
|
199
296
|
affected: providerKeys
|
|
@@ -4,6 +4,17 @@ import { resolveHealthWeightedConfig } from '../health-weighted.js';
|
|
|
4
4
|
import { pinCandidatesByAliasQueue, resolveAliasSelectionStrategy } from './alias-selection.js';
|
|
5
5
|
import { extractKeyAlias, extractKeyIndex, extractProviderId, getProviderModelId } from './key-parsing.js';
|
|
6
6
|
import { selectProviderKeyFromCandidatePool } from './tier-selection-select.js';
|
|
7
|
+
function shouldAvoidAllAntigravityOnRetry(metadata) {
|
|
8
|
+
if (!metadata || typeof metadata !== 'object') {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
const rtRaw = metadata.__rt;
|
|
12
|
+
if (!rtRaw || typeof rtRaw !== 'object' || Array.isArray(rtRaw)) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
const rt = rtRaw;
|
|
16
|
+
return rt.antigravityAvoidAllOnRetry === true;
|
|
17
|
+
}
|
|
7
18
|
function shouldAvoidAntigravityAfterRepeatedError(metadata) {
|
|
8
19
|
if (!metadata || typeof metadata !== 'object') {
|
|
9
20
|
return false;
|
|
@@ -13,6 +24,9 @@ function shouldAvoidAntigravityAfterRepeatedError(metadata) {
|
|
|
13
24
|
return false;
|
|
14
25
|
}
|
|
15
26
|
const rt = rtRaw;
|
|
27
|
+
if (rt.antigravityAvoidAllOnRetry === true) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
16
30
|
const signature = typeof rt.antigravityRetryErrorSignature === 'string' ? rt.antigravityRetryErrorSignature.trim() : '';
|
|
17
31
|
const consecutive = typeof rt.antigravityRetryErrorConsecutive === 'number' && Number.isFinite(rt.antigravityRetryErrorConsecutive)
|
|
18
32
|
? Math.max(0, Math.floor(rt.antigravityRetryErrorConsecutive))
|
|
@@ -49,6 +63,11 @@ export function trySelectFromTier(routeName, tier, stickyKey, estimatedTokens, f
|
|
|
49
63
|
targets = targets.filter((key) => !excludedKeys.has(key));
|
|
50
64
|
}
|
|
51
65
|
const isRecoveryAttempt = excludedKeys.size > 0;
|
|
66
|
+
// Antigravity safety: for certain retry signals (e.g. account verification required),
|
|
67
|
+
// avoid hitting *any* Antigravity alias on retries to prevent cross-account risk cascades.
|
|
68
|
+
if (isRecoveryAttempt && shouldAvoidAllAntigravityOnRetry(features.metadata)) {
|
|
69
|
+
targets = targets.filter((key) => (extractProviderId(key) ?? '') !== 'antigravity');
|
|
70
|
+
}
|
|
52
71
|
const singleCandidateFallback = targets.length === 1 ? targets[0] : undefined;
|
|
53
72
|
if (targets.length > 0) {
|
|
54
73
|
// Always respect cooldown signals. If a route/tier is depleted due to cooldown,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsonstudio/llms",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1462",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"test:looprt:gemini": "npm run build && node scripts/tests/loop-rt-gemini.mjs",
|
|
28
28
|
"replay:responses:chat-sse": "node scripts/exp3-responses-sse-to-chat-sse.mjs",
|
|
29
29
|
"replay:responses:loop": "node scripts/exp4-responses-sse-loop.mjs",
|
|
30
|
-
"test:virtual-router": "npm run build:dev && node scripts/tests/virtual-router-prefer-autoclear.mjs && node scripts/tests/virtual-router-dry-run.mjs && node scripts/tests/virtual-router-context.mjs && node scripts/tests/virtual-router-antigravity-alias-pin.mjs",
|
|
30
|
+
"test:virtual-router": "npm run build:dev && node scripts/tests/virtual-router-prefer-autoclear.mjs && node scripts/tests/virtual-router-dry-run.mjs && node scripts/tests/virtual-router-context.mjs && node scripts/tests/virtual-router-antigravity-alias-pin.mjs && node scripts/tests/virtual-router-antigravity-auth-verify.mjs",
|
|
31
31
|
"test:virtual-router-health": "npm run build:dev && node scripts/tests/virtual-router-health.mjs",
|
|
32
32
|
"test:golden": "npm run build:ci && node scripts/tests/chat-golden-roundtrip.mjs && node scripts/tests/anthropic-golden-roundtrip.mjs",
|
|
33
33
|
"test:coverage": "node scripts/run-ci-coverage.mjs"
|