@jsonstudio/llms 0.6.1462 → 0.6.1733
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/codecs/gemini-openai-codec.js +6 -1
- package/dist/conversion/compat/actions/anthropic-claude-code-system-prompt.d.ts +4 -7
- package/dist/conversion/compat/actions/anthropic-claude-code-system-prompt.js +140 -21
- package/dist/conversion/compat/actions/antigravity-thought-signature-cache.js +68 -10
- package/dist/conversion/compat/actions/antigravity-thought-signature-prepare.js +151 -23
- package/dist/conversion/compat/actions/gemini-cli-request.js +72 -13
- package/dist/conversion/compat/actions/harvest-tool-calls-from-text.d.ts +10 -0
- package/dist/conversion/compat/actions/harvest-tool-calls-from-text.js +121 -0
- package/dist/conversion/compat/actions/iflow-kimi-cli-defaults.d.ts +10 -0
- package/dist/conversion/compat/actions/iflow-kimi-cli-defaults.js +80 -0
- package/dist/conversion/compat/actions/iflow-kimi-history-media-placeholder.d.ts +7 -0
- package/dist/conversion/compat/actions/iflow-kimi-history-media-placeholder.js +161 -0
- package/dist/conversion/compat/actions/iflow-kimi-thinking-reasoning-fill.d.ts +12 -0
- package/dist/conversion/compat/actions/iflow-kimi-thinking-reasoning-fill.js +67 -0
- package/dist/conversion/compat/actions/iflow-response-body-unwrap.d.ts +9 -0
- package/dist/conversion/compat/actions/iflow-response-body-unwrap.js +140 -0
- package/dist/conversion/compat/actions/lmstudio-responses-fc-ids.d.ts +10 -0
- package/dist/conversion/compat/actions/lmstudio-responses-fc-ids.js +59 -0
- package/dist/conversion/compat/actions/lmstudio-responses-input-stringify.d.ts +14 -0
- package/dist/conversion/compat/actions/lmstudio-responses-input-stringify.js +125 -0
- package/dist/conversion/compat/actions/normalize-tool-call-ids.d.ts +11 -0
- package/dist/conversion/compat/actions/normalize-tool-call-ids.js +140 -0
- package/dist/conversion/compat/actions/strip-orphan-function-calls-tag.d.ts +2 -0
- package/dist/conversion/compat/actions/strip-orphan-function-calls-tag.js +152 -0
- package/dist/conversion/compat/antigravity-session-signature.d.ts +57 -3
- package/dist/conversion/compat/antigravity-session-signature.js +821 -27
- package/dist/conversion/compat/profiles/anthropic-claude-code.json +0 -9
- package/dist/conversion/compat/profiles/chat-gemini-cli.json +1 -0
- package/dist/conversion/compat/profiles/chat-iflow.json +6 -0
- package/dist/conversion/compat/profiles/chat-lmstudio.json +7 -1
- package/dist/conversion/hub/operation-table/operation-table-runner.js +1 -1
- package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +52 -10
- package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +102 -6
- package/dist/conversion/hub/pipeline/compat/compat-profile-resolver.d.ts +2 -0
- package/dist/conversion/hub/pipeline/compat/compat-profile-resolver.js +63 -0
- package/dist/conversion/hub/pipeline/compat/compat-profile-store.js +12 -3
- package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +18 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +1 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.js +25 -1
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +20 -0
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +8 -5
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js +5 -1
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js +113 -0
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +26 -1
- package/dist/conversion/hub/pipeline/target-utils.js +3 -0
- package/dist/conversion/hub/process/chat-process.js +300 -67
- package/dist/conversion/hub/response/provider-response.js +31 -4
- package/dist/conversion/responses/responses-openai-bridge.js +32 -6
- package/dist/conversion/shared/anthropic-message-utils.js +20 -5
- package/dist/conversion/shared/bridge-id-utils.d.ts +2 -0
- package/dist/conversion/shared/bridge-id-utils.js +52 -15
- package/dist/conversion/shared/gemini-tool-utils.js +134 -9
- package/dist/conversion/shared/responses-conversation-store.js +40 -5
- package/dist/conversion/shared/responses-output-builder.js +23 -7
- package/dist/conversion/shared/responses-tool-utils.d.ts +1 -0
- package/dist/conversion/shared/responses-tool-utils.js +30 -13
- package/dist/conversion/shared/text-markup-normalizer.d.ts +1 -0
- package/dist/conversion/shared/text-markup-normalizer.js +359 -2
- package/dist/conversion/shared/thought-signature-validator.d.ts +1 -1
- package/dist/conversion/shared/thought-signature-validator.js +2 -1
- package/dist/quota/apikey-reset.d.ts +17 -0
- package/dist/quota/apikey-reset.js +43 -0
- package/dist/quota/index.d.ts +2 -0
- package/dist/quota/index.js +1 -0
- package/dist/quota/quota-manager.d.ts +44 -0
- package/dist/quota/quota-manager.js +491 -0
- package/dist/quota/quota-state.d.ts +6 -0
- package/dist/quota/quota-state.js +167 -0
- package/dist/quota/types.d.ts +61 -0
- package/dist/quota/types.js +1 -0
- package/dist/router/virtual-router/bootstrap.js +134 -13
- package/dist/router/virtual-router/classifier.js +1 -1
- package/dist/router/virtual-router/engine/antigravity/alias-lease.d.ts +33 -0
- package/dist/router/virtual-router/engine/antigravity/alias-lease.js +247 -0
- package/dist/router/virtual-router/engine/health/index.d.ts +23 -0
- package/dist/router/virtual-router/engine/health/index.js +720 -0
- package/dist/router/virtual-router/engine/provider-key/parse.d.ts +6 -0
- package/dist/router/virtual-router/engine/provider-key/parse.js +43 -0
- package/dist/router/virtual-router/engine/routing-pools/index.d.ts +13 -0
- package/dist/router/virtual-router/engine/routing-pools/index.js +225 -0
- package/dist/router/virtual-router/engine/routing-state/keys.d.ts +3 -0
- package/dist/router/virtual-router/engine/routing-state/keys.js +30 -0
- package/dist/router/virtual-router/engine/routing-state/metadata.d.ts +6 -0
- package/dist/router/virtual-router/engine/routing-state/metadata.js +132 -0
- package/dist/router/virtual-router/engine/routing-state/store.d.ts +11 -0
- package/dist/router/virtual-router/engine/routing-state/store.js +107 -0
- package/dist/router/virtual-router/engine-health.d.ts +1 -23
- package/dist/router/virtual-router/engine-health.js +1 -616
- package/dist/router/virtual-router/engine-selection/route-utils.js +57 -0
- package/dist/router/virtual-router/engine-selection/selection-deps.d.ts +18 -0
- package/dist/router/virtual-router/engine-selection/tier-priority.d.ts +1 -2
- package/dist/router/virtual-router/engine-selection/tier-priority.js +2 -2
- package/dist/router/virtual-router/engine-selection/tier-selection-select.js +39 -55
- package/dist/router/virtual-router/engine-selection/tier-selection.js +284 -23
- package/dist/router/virtual-router/engine-selection.d.ts +1 -13
- package/dist/router/virtual-router/engine-selection.js +1 -225
- package/dist/router/virtual-router/engine.d.ts +8 -14
- package/dist/router/virtual-router/engine.js +187 -382
- package/dist/router/virtual-router/features.js +20 -2
- package/dist/router/virtual-router/message-utils.js +15 -5
- package/dist/router/virtual-router/success-center.d.ts +10 -0
- package/dist/router/virtual-router/success-center.js +32 -0
- package/dist/router/virtual-router/types.d.ts +48 -0
- package/dist/servertool/clock/config.d.ts +2 -0
- package/dist/servertool/clock/config.js +10 -2
- package/dist/servertool/clock/daemon.js +3 -0
- package/dist/servertool/clock/ntp.d.ts +18 -0
- package/dist/servertool/clock/ntp.js +318 -0
- package/dist/servertool/clock/paths.d.ts +1 -0
- package/dist/servertool/clock/paths.js +3 -0
- package/dist/servertool/clock/state.d.ts +2 -0
- package/dist/servertool/clock/state.js +15 -2
- package/dist/servertool/clock/tasks.d.ts +1 -0
- package/dist/servertool/clock/tasks.js +24 -1
- package/dist/servertool/clock/types.d.ts +21 -0
- package/dist/servertool/engine.js +109 -5
- package/dist/servertool/handlers/antigravity-thought-signature-bootstrap.d.ts +1 -0
- package/dist/servertool/handlers/antigravity-thought-signature-bootstrap.js +201 -0
- package/dist/servertool/handlers/clock-auto.js +39 -4
- package/dist/servertool/handlers/clock.js +145 -16
- package/dist/servertool/handlers/followup-request-builder.js +84 -0
- package/dist/servertool/handlers/gemini-empty-reply-continue.js +48 -47
- package/dist/servertool/handlers/stop-message-auto.js +3 -3
- package/dist/servertool/handlers/vision.js +10 -0
- package/dist/servertool/server-side-tools.d.ts +1 -0
- package/dist/servertool/server-side-tools.js +1 -0
- package/dist/servertool/types.d.ts +2 -0
- package/dist/sse/sse-to-json/builders/response-builder.js +6 -0
- package/dist/sse/sse-to-json/chat-sse-to-json-converter.js +32 -2
- package/dist/sse/sse-to-json/parsers/sse-parser.js +34 -0
- package/dist/sse/sse-to-json/responses-sse-to-json-converter.d.ts +1 -0
- package/dist/sse/sse-to-json/responses-sse-to-json-converter.js +33 -1
- package/dist/tools/apply-patch/args-normalizer/default-actions.d.ts +2 -0
- package/dist/tools/apply-patch/args-normalizer/default-actions.js +12 -0
- package/dist/tools/apply-patch/args-normalizer/extract-patch.d.ts +2 -0
- package/dist/tools/apply-patch/args-normalizer/extract-patch.js +15 -0
- package/dist/tools/apply-patch/args-normalizer/index.d.ts +2 -0
- package/dist/tools/apply-patch/args-normalizer/index.js +164 -0
- package/dist/tools/apply-patch/args-normalizer/structured-builders.d.ts +7 -0
- package/dist/tools/apply-patch/args-normalizer/structured-builders.js +85 -0
- package/dist/tools/apply-patch/args-normalizer/types.d.ts +54 -0
- package/dist/tools/apply-patch/args-normalizer/types.js +1 -0
- package/dist/tools/apply-patch/execution-capturer.js +24 -3
- package/dist/tools/apply-patch/patch-text/looks-like-patch.js +1 -0
- package/dist/tools/apply-patch/patch-text/normalize.js +104 -5
- package/dist/tools/apply-patch/structured/coercion.js +28 -4
- package/dist/tools/apply-patch/validator.js +7 -146
- package/package.json +3 -2
|
@@ -176,9 +176,10 @@ export function buildOpenAIChatFromGeminiRequest(payload) {
|
|
|
176
176
|
}
|
|
177
177
|
export function buildOpenAIChatFromGeminiResponse(payload) {
|
|
178
178
|
const candidates = Array.isArray(payload?.candidates) ? payload.candidates : [];
|
|
179
|
+
const errorNode = payload?.error;
|
|
179
180
|
const primary = candidates[0] && typeof candidates[0] === 'object' ? candidates[0] : {};
|
|
180
181
|
const content = primary?.content || {};
|
|
181
|
-
const role = mapGeminiRoleToChat(content.role);
|
|
182
|
+
const role = candidates.length > 0 ? mapGeminiRoleToChat(content.role) : 'assistant';
|
|
182
183
|
const rawFinishReason = primary?.finishReason;
|
|
183
184
|
const finishReasonUpper = typeof rawFinishReason === 'string' ? rawFinishReason.trim().toUpperCase() : '';
|
|
184
185
|
const parts = Array.isArray(content.parts) ? content.parts : [];
|
|
@@ -420,6 +421,10 @@ export function buildOpenAIChatFromGeminiResponse(payload) {
|
|
|
420
421
|
}
|
|
421
422
|
]
|
|
422
423
|
};
|
|
424
|
+
// Preserve upstream error envelope (used by servertool auto flows and host status mapping).
|
|
425
|
+
if (errorNode && typeof errorNode === 'object' && !Array.isArray(errorNode)) {
|
|
426
|
+
chatResp.error = errorNode;
|
|
427
|
+
}
|
|
423
428
|
if (Object.keys(usage).length > 0) {
|
|
424
429
|
chatResp.usage = usage;
|
|
425
430
|
}
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import type { JsonObject } from '../../hub/types/json.js';
|
|
2
2
|
import type { AnthropicClaudeCodeSystemPromptConfig } from '../../hub/pipeline/compat/compat-types.js';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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).
|
|
4
|
+
* Canonicalizes the Anthropic `system` prompt into a single text block.
|
|
5
|
+
* Optionally preserves previous system blocks by prepending them to the first user message,
|
|
6
|
+
* so request semantics are preserved after normalization.
|
|
10
7
|
*/
|
|
11
|
-
export declare function applyAnthropicClaudeCodeSystemPromptCompat(payload: JsonObject, config?: AnthropicClaudeCodeSystemPromptConfig): JsonObject;
|
|
8
|
+
export declare function applyAnthropicClaudeCodeSystemPromptCompat(payload: JsonObject, config?: AnthropicClaudeCodeSystemPromptConfig, adapterContext?: unknown): JsonObject;
|
|
@@ -1,7 +1,97 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
1
2
|
const DEFAULT_SYSTEM_TEXT = "You are Claude Code, Anthropic's official CLI for Claude.";
|
|
3
|
+
const DEFAULT_USER_ID_ENV = 'ROUTECODEX_CLAUDE_CODE_USER_ID';
|
|
4
|
+
const DEFAULT_ACCOUNT_SEED_ENV = 'ROUTECODEX_CLAUDE_CODE_ACCOUNT_SEED';
|
|
5
|
+
const CLAUDE_CODE_USER_ID_REGEX = /^user_[0-9a-f]{64}_account__session_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
6
|
+
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
7
|
+
const HEX_32_REGEX = /^[0-9a-f]{32}$/i;
|
|
8
|
+
const SESSION_TOKEN_REGEX = /session[_:\-\s]?([0-9a-f]{8,}(?:-[0-9a-f]{4,}){0,5})/i;
|
|
2
9
|
function isRecord(value) {
|
|
3
10
|
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
4
11
|
}
|
|
12
|
+
function readTrimmedString(value) {
|
|
13
|
+
if (typeof value !== 'string') {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
const trimmed = value.trim();
|
|
17
|
+
return trimmed.length ? trimmed : undefined;
|
|
18
|
+
}
|
|
19
|
+
function isClaudeCodeUserId(value) {
|
|
20
|
+
const trimmed = readTrimmedString(value);
|
|
21
|
+
if (!trimmed)
|
|
22
|
+
return false;
|
|
23
|
+
return CLAUDE_CODE_USER_ID_REGEX.test(trimmed);
|
|
24
|
+
}
|
|
25
|
+
function sha256Hex(value) {
|
|
26
|
+
return createHash('sha256').update(value).digest('hex');
|
|
27
|
+
}
|
|
28
|
+
function formatUuidFromHex32(hex32) {
|
|
29
|
+
const hex = hex32.toLowerCase();
|
|
30
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
31
|
+
}
|
|
32
|
+
function uuidFromSeed(seed) {
|
|
33
|
+
const hex = sha256Hex(seed);
|
|
34
|
+
// Make it UUIDv4-ish (version nibble "4", variant "8") so regex validators accept it.
|
|
35
|
+
const chars = hex.split('');
|
|
36
|
+
if (chars.length >= 32) {
|
|
37
|
+
chars[12] = '4';
|
|
38
|
+
chars[16] = '8';
|
|
39
|
+
}
|
|
40
|
+
return formatUuidFromHex32(chars.slice(0, 32).join(''));
|
|
41
|
+
}
|
|
42
|
+
function normalizeSessionUuid(candidate) {
|
|
43
|
+
const raw = readTrimmedString(candidate);
|
|
44
|
+
if (!raw)
|
|
45
|
+
return undefined;
|
|
46
|
+
const match = raw.match(SESSION_TOKEN_REGEX);
|
|
47
|
+
const trimmed = (match && match[1] ? match[1] : raw).trim();
|
|
48
|
+
if (UUID_REGEX.test(trimmed)) {
|
|
49
|
+
return trimmed.toLowerCase();
|
|
50
|
+
}
|
|
51
|
+
const compact = trimmed.replace(/-/g, '');
|
|
52
|
+
if (HEX_32_REGEX.test(compact)) {
|
|
53
|
+
return formatUuidFromHex32(compact);
|
|
54
|
+
}
|
|
55
|
+
return uuidFromSeed(trimmed);
|
|
56
|
+
}
|
|
57
|
+
function resolveClaudeCodeUserId(metadata, adapterContext) {
|
|
58
|
+
const existing = readTrimmedString(metadata.user_id);
|
|
59
|
+
if (isClaudeCodeUserId(existing)) {
|
|
60
|
+
return existing;
|
|
61
|
+
}
|
|
62
|
+
const envUserId = readTrimmedString(process.env[DEFAULT_USER_ID_ENV]);
|
|
63
|
+
if (isClaudeCodeUserId(envUserId)) {
|
|
64
|
+
return envUserId;
|
|
65
|
+
}
|
|
66
|
+
const clientHeaders = isRecord(metadata.clientHeaders) ? metadata.clientHeaders : undefined;
|
|
67
|
+
const ctx = isRecord(adapterContext) ? adapterContext : undefined;
|
|
68
|
+
const sessionUuid = normalizeSessionUuid(existing) ||
|
|
69
|
+
normalizeSessionUuid(envUserId) ||
|
|
70
|
+
normalizeSessionUuid(readTrimmedString(clientHeaders?.session_id)) ||
|
|
71
|
+
normalizeSessionUuid(readTrimmedString(clientHeaders?.['anthropic-session-id'])) ||
|
|
72
|
+
normalizeSessionUuid(readTrimmedString(clientHeaders?.['x-session-id'])) ||
|
|
73
|
+
normalizeSessionUuid(readTrimmedString(clientHeaders?.conversation_id)) ||
|
|
74
|
+
normalizeSessionUuid(readTrimmedString(clientHeaders?.['anthropic-conversation-id'])) ||
|
|
75
|
+
normalizeSessionUuid(readTrimmedString(clientHeaders?.['openai-conversation-id'])) ||
|
|
76
|
+
normalizeSessionUuid(readTrimmedString(metadata.sessionId)) ||
|
|
77
|
+
normalizeSessionUuid(readTrimmedString(metadata.conversationId)) ||
|
|
78
|
+
normalizeSessionUuid(readTrimmedString(ctx?.sessionId)) ||
|
|
79
|
+
normalizeSessionUuid(readTrimmedString(ctx?.conversationId));
|
|
80
|
+
const accountSeed = readTrimmedString(process.env[DEFAULT_ACCOUNT_SEED_ENV]) ||
|
|
81
|
+
readTrimmedString(process.env.USER) ||
|
|
82
|
+
'routecodex';
|
|
83
|
+
try {
|
|
84
|
+
const accountHash = sha256Hex(accountSeed);
|
|
85
|
+
const session = sessionUuid ?? randomUUID();
|
|
86
|
+
return `user_${accountHash}_account__session_${session}`;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// As a last resort, keep compatibility best-effort.
|
|
90
|
+
const session = sessionUuid ?? randomUUID();
|
|
91
|
+
return `user_${'0'.repeat(64)}_account__session_${session}`;
|
|
92
|
+
}
|
|
93
|
+
// unreachable
|
|
94
|
+
}
|
|
5
95
|
function normalizeSystemBlocks(system) {
|
|
6
96
|
const blocks = [];
|
|
7
97
|
const pushText = (text, extra) => {
|
|
@@ -63,15 +153,33 @@ function dedupeSystemBlocksByText(blocks) {
|
|
|
63
153
|
}
|
|
64
154
|
return result;
|
|
65
155
|
}
|
|
156
|
+
function prependUserContent(messages, blocks) {
|
|
157
|
+
if (!Array.isArray(messages) || blocks.length === 0) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const first = messages[0];
|
|
161
|
+
if (isRecord(first) && typeof first.role === 'string' && first.role.toLowerCase() === 'user') {
|
|
162
|
+
const existing = first.content;
|
|
163
|
+
if (typeof existing === 'string') {
|
|
164
|
+
const injected = blocks.map((b) => b.text).filter(Boolean).join('\n\n');
|
|
165
|
+
first.content = existing.trim() ? `${injected}\n\n${existing}` : injected;
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (Array.isArray(existing)) {
|
|
169
|
+
first.content = [...blocks, ...existing];
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
first.content = [...blocks];
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
messages.unshift({ role: 'user', content: [...blocks] });
|
|
176
|
+
}
|
|
66
177
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
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).
|
|
178
|
+
* Canonicalizes the Anthropic `system` prompt into a single text block.
|
|
179
|
+
* Optionally preserves previous system blocks by prepending them to the first user message,
|
|
180
|
+
* so request semantics are preserved after normalization.
|
|
73
181
|
*/
|
|
74
|
-
export function applyAnthropicClaudeCodeSystemPromptCompat(payload, config) {
|
|
182
|
+
export function applyAnthropicClaudeCodeSystemPromptCompat(payload, config, adapterContext) {
|
|
75
183
|
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
76
184
|
return payload;
|
|
77
185
|
}
|
|
@@ -79,23 +187,34 @@ export function applyAnthropicClaudeCodeSystemPromptCompat(payload, config) {
|
|
|
79
187
|
const systemText = (typeof config?.systemText === 'string' && config.systemText.trim())
|
|
80
188
|
? config.systemText.trim()
|
|
81
189
|
: DEFAULT_SYSTEM_TEXT;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (!preserveExisting) {
|
|
87
|
-
nextBlocks = [official];
|
|
190
|
+
// Some Claude-Code-gated Anthropic proxies require the top-level `metadata` field to exist.
|
|
191
|
+
// We don't inspect/repair semantics: just ensure the field is present as an object.
|
|
192
|
+
if (!isRecord(root.metadata)) {
|
|
193
|
+
root.metadata = {};
|
|
88
194
|
}
|
|
89
|
-
|
|
90
|
-
|
|
195
|
+
// Some proxies also require `metadata.user_id` to be present.
|
|
196
|
+
// Fill it with Claude Code's canonical `user_<sha256>_account__session_<uuid>` shape.
|
|
197
|
+
try {
|
|
198
|
+
const userId = resolveClaudeCodeUserId(root.metadata, adapterContext);
|
|
199
|
+
const current = readTrimmedString(root.metadata.user_id);
|
|
200
|
+
if (userId && !isClaudeCodeUserId(current)) {
|
|
201
|
+
root.metadata.user_id = userId;
|
|
202
|
+
}
|
|
91
203
|
}
|
|
92
|
-
|
|
93
|
-
|
|
204
|
+
catch {
|
|
205
|
+
// best-effort: never block compat due to metadata shaping failures
|
|
94
206
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
207
|
+
const preserveExisting = config?.preserveExistingSystemAsUserMessage !== false;
|
|
208
|
+
const existingBlocks = dedupeSystemBlocksByText(normalizeSystemBlocks(root.system))
|
|
209
|
+
.filter((b) => b.text !== systemText);
|
|
210
|
+
// Normalize: force system into a single text block.
|
|
211
|
+
root.system = [{ type: 'text', text: systemText }];
|
|
212
|
+
if (preserveExisting && existingBlocks.length) {
|
|
213
|
+
const messages = Array.isArray(root.messages) ? root.messages : [];
|
|
214
|
+
if (messages.length || root.messages !== undefined) {
|
|
215
|
+
prependUserContent(messages, existingBlocks);
|
|
216
|
+
root.messages = messages;
|
|
217
|
+
}
|
|
98
218
|
}
|
|
99
|
-
root.system = nextBlocks;
|
|
100
219
|
return root;
|
|
101
220
|
}
|
|
@@ -1,7 +1,42 @@
|
|
|
1
|
-
import { cacheAntigravitySessionSignature, getAntigravityRequestSessionMeta } from '../antigravity-session-signature.js';
|
|
1
|
+
import { ANTIGRAVITY_GLOBAL_ALIAS_KEY, 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
|
}
|
|
5
|
+
function resolveStableSessionId(adapterContext) {
|
|
6
|
+
if (!adapterContext) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
const ctxAny = adapterContext;
|
|
10
|
+
const candidates = [ctxAny.sessionId, ctxAny.conversationId].filter((v) => typeof v === 'string');
|
|
11
|
+
const raw = candidates.map((s) => s.trim()).find((s) => s.length > 0);
|
|
12
|
+
if (!raw) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
// Antigravity-Manager alignment: never hash/derive session ids from external session/conversation identifiers here.
|
|
16
|
+
// If the caller already provides a proper fingerprint (sid-*), we can use it as a last-resort fallback.
|
|
17
|
+
return raw.toLowerCase().startsWith('sid-') ? raw : undefined;
|
|
18
|
+
}
|
|
19
|
+
function resolveAntigravityAliasKey(adapterContext) {
|
|
20
|
+
if (!adapterContext) {
|
|
21
|
+
return 'antigravity.unknown';
|
|
22
|
+
}
|
|
23
|
+
const ctxAny = adapterContext;
|
|
24
|
+
const candidates = [ctxAny.runtimeKey, ctxAny.providerKey, ctxAny.providerId].filter((v) => typeof v === 'string');
|
|
25
|
+
for (const value of candidates) {
|
|
26
|
+
const trimmed = value.trim();
|
|
27
|
+
if (!trimmed)
|
|
28
|
+
continue;
|
|
29
|
+
const lower = trimmed.toLowerCase();
|
|
30
|
+
if (lower.startsWith('antigravity.')) {
|
|
31
|
+
const parts = trimmed.split('.');
|
|
32
|
+
if (parts.length >= 2 && parts[0] && parts[1]) {
|
|
33
|
+
return `${parts[0].trim()}.${parts[1].trim()}`;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return trimmed;
|
|
37
|
+
}
|
|
38
|
+
return 'antigravity.unknown';
|
|
39
|
+
}
|
|
5
40
|
function shouldEnableForAdapter(adapterContext) {
|
|
6
41
|
if (!adapterContext) {
|
|
7
42
|
return false;
|
|
@@ -10,14 +45,24 @@ function shouldEnableForAdapter(adapterContext) {
|
|
|
10
45
|
if (protocol !== 'gemini-chat') {
|
|
11
46
|
return false;
|
|
12
47
|
}
|
|
13
|
-
const
|
|
48
|
+
const ctxAny = adapterContext;
|
|
49
|
+
const providerIdOrKeyRaw = typeof ctxAny.providerId === 'string'
|
|
50
|
+
? String(ctxAny.providerId)
|
|
51
|
+
: typeof ctxAny.providerKey === 'string'
|
|
52
|
+
? String(ctxAny.providerKey)
|
|
53
|
+
: typeof ctxAny.runtimeKey === 'string'
|
|
54
|
+
? String(ctxAny.runtimeKey)
|
|
55
|
+
: '';
|
|
56
|
+
const providerIdOrKey = providerIdOrKeyRaw.trim().toLowerCase();
|
|
14
57
|
const effectiveProviderId = providerIdOrKey.split('.')[0] ?? '';
|
|
15
|
-
|
|
58
|
+
// Antigravity-Manager alignment: thoughtSignature compat applies to both Antigravity and Gemini CLI.
|
|
59
|
+
return effectiveProviderId === 'antigravity' || effectiveProviderId === 'gemini-cli';
|
|
16
60
|
}
|
|
17
61
|
export function cacheAntigravityThoughtSignatureFromGeminiResponse(payload, adapterContext) {
|
|
18
62
|
if (!shouldEnableForAdapter(adapterContext)) {
|
|
19
63
|
return payload;
|
|
20
64
|
}
|
|
65
|
+
const fallbackAliasKey = resolveAntigravityAliasKey(adapterContext);
|
|
21
66
|
const ctxAny = adapterContext;
|
|
22
67
|
const payloadAny = payload;
|
|
23
68
|
const keyCandidates = [
|
|
@@ -27,16 +72,25 @@ export function cacheAntigravityThoughtSignatureFromGeminiResponse(payload, adap
|
|
|
27
72
|
typeof payloadAny.request_id === 'string' ? String(payloadAny.request_id) : '',
|
|
28
73
|
typeof payloadAny.requestId === 'string' ? String(payloadAny.requestId) : ''
|
|
29
74
|
].filter((k) => typeof k === 'string' && k.trim().length);
|
|
75
|
+
let aliasKey = fallbackAliasKey;
|
|
30
76
|
let sessionId = '';
|
|
31
77
|
let messageCount = 1;
|
|
32
78
|
for (const key of keyCandidates) {
|
|
33
79
|
const resolved = getAntigravityRequestSessionMeta(key);
|
|
34
80
|
if (resolved && resolved.sessionId.trim().length) {
|
|
81
|
+
aliasKey = typeof resolved.aliasKey === 'string' && resolved.aliasKey.trim().length ? resolved.aliasKey.trim() : fallbackAliasKey;
|
|
35
82
|
sessionId = resolved.sessionId.trim();
|
|
36
83
|
messageCount = resolved.messageCount;
|
|
37
84
|
break;
|
|
38
85
|
}
|
|
39
86
|
}
|
|
87
|
+
if (!sessionId) {
|
|
88
|
+
const stable = resolveStableSessionId(adapterContext);
|
|
89
|
+
if (stable) {
|
|
90
|
+
sessionId = stable;
|
|
91
|
+
messageCount = 1;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
40
94
|
if (!sessionId) {
|
|
41
95
|
return payload;
|
|
42
96
|
}
|
|
@@ -47,14 +101,18 @@ export function cacheAntigravityThoughtSignatureFromGeminiResponse(payload, adap
|
|
|
47
101
|
const partsRaw = content?.parts;
|
|
48
102
|
const parts = Array.isArray(partsRaw) ? partsRaw : [];
|
|
49
103
|
for (const part of parts) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
104
|
+
const sig = typeof part.thoughtSignature === 'string'
|
|
105
|
+
? String(part.thoughtSignature)
|
|
106
|
+
: typeof part.thought_signature === 'string'
|
|
107
|
+
? String(part.thought_signature)
|
|
108
|
+
: '';
|
|
109
|
+
if (sig.trim().length) {
|
|
110
|
+
cacheAntigravitySessionSignature(aliasKey, sessionId, sig.trim(), messageCount);
|
|
111
|
+
// Antigravity-Manager alignment: also store into the global signature store for this session.
|
|
112
|
+
if (aliasKey !== ANTIGRAVITY_GLOBAL_ALIAS_KEY) {
|
|
113
|
+
cacheAntigravitySessionSignature(ANTIGRAVITY_GLOBAL_ALIAS_KEY, sessionId, sig.trim(), messageCount);
|
|
114
|
+
}
|
|
56
115
|
}
|
|
57
|
-
cacheAntigravitySessionSignature(sessionId, sig.trim(), messageCount);
|
|
58
116
|
}
|
|
59
117
|
}
|
|
60
118
|
return payload;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { cacheAntigravityRequestSessionMeta, clearAntigravitySessionSignature, extractAntigravityGeminiSessionId, getAntigravitySessionSignatureEntry, shouldTreatAsMissingThoughtSignature } from '../antigravity-session-signature.js';
|
|
1
|
+
import { ANTIGRAVITY_GLOBAL_ALIAS_KEY, cacheAntigravityRequestSessionMeta, clearAntigravitySessionSignature, extractAntigravityGeminiSessionId, getAntigravityLatestSignatureSessionIdForAlias, lookupAntigravitySessionSignatureEntry, markAntigravitySessionSignatureRewind, shouldTreatAsMissingThoughtSignature } from '../antigravity-session-signature.js';
|
|
3
2
|
function isRecord(value) {
|
|
4
3
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
5
4
|
}
|
|
@@ -11,9 +10,31 @@ function shouldEnableForAdapter(adapterContext) {
|
|
|
11
10
|
if (protocol !== 'gemini-chat') {
|
|
12
11
|
return false;
|
|
13
12
|
}
|
|
14
|
-
const
|
|
13
|
+
const ctxAny = adapterContext;
|
|
14
|
+
const providerIdOrKeyRaw = typeof ctxAny.providerId === 'string'
|
|
15
|
+
? String(ctxAny.providerId)
|
|
16
|
+
: typeof ctxAny.providerKey === 'string'
|
|
17
|
+
? String(ctxAny.providerKey)
|
|
18
|
+
: typeof ctxAny.runtimeKey === 'string'
|
|
19
|
+
? String(ctxAny.runtimeKey)
|
|
20
|
+
: '';
|
|
21
|
+
const providerIdOrKey = providerIdOrKeyRaw.trim().toLowerCase();
|
|
15
22
|
const effectiveProviderId = providerIdOrKey.split('.')[0] ?? '';
|
|
16
|
-
|
|
23
|
+
// Antigravity-Manager alignment: thoughtSignature compat applies to both Antigravity and Gemini CLI
|
|
24
|
+
// (both route to Google Gemini internals that enforce thoughtSignature on tool loops).
|
|
25
|
+
return effectiveProviderId === 'antigravity' || effectiveProviderId === 'gemini-cli';
|
|
26
|
+
}
|
|
27
|
+
function shouldEnableSignatureRecovery(adapterContext) {
|
|
28
|
+
if (!adapterContext) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
const ctxAny = adapterContext;
|
|
32
|
+
const rtRaw = ctxAny.__rt;
|
|
33
|
+
if (!rtRaw || typeof rtRaw !== 'object' || Array.isArray(rtRaw)) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
const rt = rtRaw;
|
|
37
|
+
return rt.antigravityThoughtSignatureRecovery === true;
|
|
17
38
|
}
|
|
18
39
|
function locateGeminiContentsNode(root) {
|
|
19
40
|
if (Array.isArray(root.contents)) {
|
|
@@ -29,23 +50,79 @@ function locateGeminiContentsNode(root) {
|
|
|
29
50
|
}
|
|
30
51
|
return undefined;
|
|
31
52
|
}
|
|
32
|
-
function
|
|
33
|
-
return createHash('sha256').update(value).digest('hex');
|
|
34
|
-
}
|
|
35
|
-
function resolveStableSessionId(adapterContext) {
|
|
53
|
+
function resolveAntigravityAliasKey(adapterContext) {
|
|
36
54
|
if (!adapterContext) {
|
|
37
|
-
return
|
|
55
|
+
return 'antigravity.unknown';
|
|
38
56
|
}
|
|
39
57
|
const ctxAny = adapterContext;
|
|
40
|
-
const candidates = [ctxAny.
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
58
|
+
const candidates = [ctxAny.runtimeKey, ctxAny.providerKey, ctxAny.providerId].filter((v) => typeof v === 'string');
|
|
59
|
+
for (const value of candidates) {
|
|
60
|
+
const trimmed = value.trim();
|
|
61
|
+
if (!trimmed)
|
|
62
|
+
continue;
|
|
63
|
+
const lower = trimmed.toLowerCase();
|
|
64
|
+
if (lower.startsWith('antigravity.')) {
|
|
65
|
+
const parts = trimmed.split('.');
|
|
66
|
+
if (parts.length >= 2 && parts[0] && parts[1]) {
|
|
67
|
+
return `${parts[0].trim()}.${parts[1].trim()}`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return trimmed;
|
|
44
71
|
}
|
|
45
|
-
|
|
46
|
-
|
|
72
|
+
return 'antigravity.unknown';
|
|
73
|
+
}
|
|
74
|
+
function stripThoughtSignatures(contentsNode) {
|
|
75
|
+
const contentsRaw = contentsNode.contents;
|
|
76
|
+
if (!Array.isArray(contentsRaw)) {
|
|
77
|
+
return;
|
|
47
78
|
}
|
|
48
|
-
|
|
79
|
+
const contents = contentsRaw;
|
|
80
|
+
for (const entry of contents) {
|
|
81
|
+
if (!isRecord(entry))
|
|
82
|
+
continue;
|
|
83
|
+
const partsRaw = entry.parts;
|
|
84
|
+
if (!Array.isArray(partsRaw))
|
|
85
|
+
continue;
|
|
86
|
+
const parts = partsRaw;
|
|
87
|
+
for (const part of parts) {
|
|
88
|
+
if (!isRecord(part))
|
|
89
|
+
continue;
|
|
90
|
+
if ('thoughtSignature' in part) {
|
|
91
|
+
delete part.thoughtSignature;
|
|
92
|
+
}
|
|
93
|
+
if ('thought_signature' in part) {
|
|
94
|
+
delete part.thought_signature;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const ANTIGRAVITY_SIGNATURE_RECOVERY_PROMPT = "\n\n[System Recovery] Your previous output contained an invalid signature. Please regenerate the response without the corrupted signature block.";
|
|
100
|
+
function injectSignatureRecoveryPrompt(contentsNode) {
|
|
101
|
+
const contentsRaw = contentsNode.contents;
|
|
102
|
+
if (!Array.isArray(contentsRaw) || contentsRaw.length === 0) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const contents = contentsRaw;
|
|
106
|
+
const last = contents[contents.length - 1];
|
|
107
|
+
if (!isRecord(last)) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const partsRaw = last.parts;
|
|
111
|
+
if (!Array.isArray(partsRaw)) {
|
|
112
|
+
last.parts = [];
|
|
113
|
+
}
|
|
114
|
+
const parts = (Array.isArray(last.parts) ? last.parts : []);
|
|
115
|
+
const alreadyInjected = parts.some((part) => {
|
|
116
|
+
if (!isRecord(part))
|
|
117
|
+
return false;
|
|
118
|
+
const text = typeof part.text === 'string' ? String(part.text) : '';
|
|
119
|
+
return text.includes('[System Recovery]');
|
|
120
|
+
});
|
|
121
|
+
if (alreadyInjected) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
parts.push({ text: ANTIGRAVITY_SIGNATURE_RECOVERY_PROMPT });
|
|
125
|
+
last.parts = parts;
|
|
49
126
|
}
|
|
50
127
|
function injectThoughtSignatureIntoFunctionCalls(contentsNode, signature) {
|
|
51
128
|
const contentsRaw = contentsNode.contents;
|
|
@@ -77,9 +154,12 @@ export function prepareAntigravityThoughtSignatureForGeminiRequest(payload, adap
|
|
|
77
154
|
if (!shouldEnableForAdapter(adapterContext)) {
|
|
78
155
|
return payload;
|
|
79
156
|
}
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
157
|
+
const aliasKey = resolveAntigravityAliasKey(adapterContext);
|
|
158
|
+
// Antigravity-Manager alignment: sessionId is derived from the first user text (or JSON fallback),
|
|
159
|
+
// not from external session/conversation identifiers injected by other clients/hosts.
|
|
160
|
+
const originalSessionId = extractAntigravityGeminiSessionId(payload);
|
|
161
|
+
let sessionId = originalSessionId;
|
|
162
|
+
let usedLeasedSession = false;
|
|
83
163
|
const ctxAny = adapterContext;
|
|
84
164
|
const keys = [
|
|
85
165
|
adapterContext.requestId,
|
|
@@ -89,16 +169,64 @@ export function prepareAntigravityThoughtSignatureForGeminiRequest(payload, adap
|
|
|
89
169
|
const root = payload;
|
|
90
170
|
const contentsNode = locateGeminiContentsNode(root);
|
|
91
171
|
const messageCount = contentsNode && Array.isArray(contentsNode.contents) ? contentsNode.contents.length : 1;
|
|
172
|
+
// Recovery mode: strip any user-provided / stale signatures and do not lease/inject from cache.
|
|
173
|
+
if (shouldEnableSignatureRecovery(adapterContext)) {
|
|
174
|
+
if (contentsNode) {
|
|
175
|
+
stripThoughtSignatures(contentsNode);
|
|
176
|
+
// Antigravity-Manager alignment: append a repair hint so Gemini can regenerate without a corrupted signature.
|
|
177
|
+
injectSignatureRecoveryPrompt(contentsNode);
|
|
178
|
+
}
|
|
179
|
+
for (const key of keys) {
|
|
180
|
+
cacheAntigravityRequestSessionMeta(key, { aliasKey, sessionId: originalSessionId, messageCount });
|
|
181
|
+
}
|
|
182
|
+
return payload;
|
|
183
|
+
}
|
|
184
|
+
const resolveLookup = (sid) => {
|
|
185
|
+
const direct = lookupAntigravitySessionSignatureEntry(aliasKey, sid, { hydrate: true });
|
|
186
|
+
if (typeof direct.signature === 'string' && direct.signature.trim().length) {
|
|
187
|
+
return direct;
|
|
188
|
+
}
|
|
189
|
+
return lookupAntigravitySessionSignatureEntry(ANTIGRAVITY_GLOBAL_ALIAS_KEY, sid, { hydrate: true });
|
|
190
|
+
};
|
|
191
|
+
let lookup = resolveLookup(sessionId);
|
|
192
|
+
const hasSignature = typeof lookup.signature === 'string' && lookup.signature.trim().length && typeof lookup.messageCount === 'number';
|
|
193
|
+
if (!hasSignature) {
|
|
194
|
+
// Requested behavior:
|
|
195
|
+
// - Once an alias has ever obtained a signature, remember its sessionId.
|
|
196
|
+
// - For new sessions hitting the same alias, temporarily use that signature sessionId.
|
|
197
|
+
// This avoids cold-start tool-call failures when the current request's derived sessionId has no cached signature yet.
|
|
198
|
+
const leasedSessionId = getAntigravityLatestSignatureSessionIdForAlias(aliasKey, { hydrate: true });
|
|
199
|
+
if (leasedSessionId && leasedSessionId.trim().length && leasedSessionId.trim() !== sessionId) {
|
|
200
|
+
const leasedLookup = resolveLookup(leasedSessionId.trim());
|
|
201
|
+
const leasedHasSignature = typeof leasedLookup.signature === 'string' &&
|
|
202
|
+
leasedLookup.signature.trim().length &&
|
|
203
|
+
typeof leasedLookup.messageCount === 'number';
|
|
204
|
+
if (leasedHasSignature) {
|
|
205
|
+
sessionId = leasedSessionId.trim();
|
|
206
|
+
lookup = leasedLookup;
|
|
207
|
+
usedLeasedSession = true;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const effectiveSessionId = sessionId;
|
|
92
212
|
for (const key of keys) {
|
|
93
|
-
cacheAntigravityRequestSessionMeta(key, { sessionId, messageCount });
|
|
213
|
+
cacheAntigravityRequestSessionMeta(key, { aliasKey, sessionId: effectiveSessionId, messageCount });
|
|
94
214
|
}
|
|
95
|
-
const cached =
|
|
215
|
+
const cached = typeof lookup.signature === 'string' && typeof lookup.messageCount === 'number'
|
|
216
|
+
? { signature: lookup.signature, messageCount: lookup.messageCount }
|
|
217
|
+
: undefined;
|
|
96
218
|
if (!cached) {
|
|
97
219
|
return payload;
|
|
98
220
|
}
|
|
99
|
-
|
|
221
|
+
// Rewind detection is meaningful within the SAME session. When we lease a prior sessionId to reuse
|
|
222
|
+
// an existing signature, messageCount comparisons may be unrelated and should not invalidate the cache.
|
|
223
|
+
if (!usedLeasedSession && typeof messageCount === 'number' && messageCount > 0 && messageCount < cached.messageCount) {
|
|
100
224
|
// Rewind detected: do not inject a "future" signature; clear and wait for a fresh signature from upstream.
|
|
101
|
-
clearAntigravitySessionSignature(
|
|
225
|
+
clearAntigravitySessionSignature(aliasKey, effectiveSessionId);
|
|
226
|
+
markAntigravitySessionSignatureRewind(aliasKey, effectiveSessionId, messageCount);
|
|
227
|
+
// Antigravity-Manager alignment: global signature store must also be blocked on rewinds.
|
|
228
|
+
clearAntigravitySessionSignature(ANTIGRAVITY_GLOBAL_ALIAS_KEY, effectiveSessionId);
|
|
229
|
+
markAntigravitySessionSignatureRewind(ANTIGRAVITY_GLOBAL_ALIAS_KEY, effectiveSessionId, messageCount);
|
|
102
230
|
return payload;
|
|
103
231
|
}
|
|
104
232
|
if (!contentsNode) {
|