@jsonstudio/llms 0.6.3409 → 0.6.3541

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 (85) hide show
  1. package/dist/conversion/codecs/anthropic-openai-codec.d.ts +12 -3
  2. package/dist/conversion/codecs/anthropic-openai-codec.js +32 -92
  3. package/dist/conversion/codecs/gemini-openai-codec.d.ts +6 -5
  4. package/dist/conversion/codecs/gemini-openai-codec.js +48 -685
  5. package/dist/conversion/codecs/openai-openai-codec.d.ts +1 -1
  6. package/dist/conversion/codecs/openai-openai-codec.js +34 -100
  7. package/dist/conversion/codecs/responses-openai-codec.d.ts +1 -1
  8. package/dist/conversion/codecs/responses-openai-codec.js +47 -159
  9. package/dist/conversion/compat/actions/anthropic-claude-code-system-prompt.d.ts +2 -6
  10. package/dist/conversion/compat/actions/anthropic-claude-code-system-prompt.js +29 -245
  11. package/dist/conversion/compat/actions/anthropic-claude-code-user-id.d.ts +3 -0
  12. package/dist/conversion/compat/actions/anthropic-claude-code-user-id.js +30 -0
  13. package/dist/conversion/compat/actions/antigravity-thought-signature-prepare.js +21 -232
  14. package/dist/conversion/compat/actions/deepseek-web-request.js +41 -276
  15. package/dist/conversion/compat/actions/deepseek-web-response.js +117 -855
  16. package/dist/conversion/compat/actions/gemini-cli-request.d.ts +1 -1
  17. package/dist/conversion/compat/actions/gemini-cli-request.js +20 -613
  18. package/dist/conversion/compat/actions/gemini-web-search.d.ts +1 -15
  19. package/dist/conversion/compat/actions/gemini-web-search.js +22 -69
  20. package/dist/conversion/compat/actions/glm-tool-extraction.d.ts +3 -2
  21. package/dist/conversion/compat/actions/glm-tool-extraction.js +28 -257
  22. package/dist/conversion/compat/actions/iflow-tool-text-fallback.d.ts +0 -8
  23. package/dist/conversion/compat/actions/iflow-tool-text-fallback.js +24 -206
  24. package/dist/conversion/compat/actions/qwen-transform.d.ts +3 -2
  25. package/dist/conversion/compat/actions/qwen-transform.js +30 -271
  26. package/dist/conversion/compat/actions/tool-text-request-guidance.js +3 -173
  27. package/dist/conversion/compat/actions/universal-shape-filter.d.ts +6 -23
  28. package/dist/conversion/compat/actions/universal-shape-filter.js +4 -383
  29. package/dist/conversion/hub/pipeline/compat/native-adapter-context.js +1 -0
  30. package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.d.ts +1 -2
  31. package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.js +50 -104
  32. package/dist/conversion/pipeline/codecs/v2/openai-openai-pipeline.js +12 -10
  33. package/dist/conversion/pipeline/codecs/v2/responses-openai-pipeline.d.ts +0 -2
  34. package/dist/conversion/pipeline/codecs/v2/responses-openai-pipeline.js +46 -67
  35. package/dist/conversion/pipeline/codecs/v2/shared/openai-chat-helpers.js +15 -40
  36. package/dist/conversion/responses/responses-openai-bridge/response-payload.js +47 -348
  37. package/dist/conversion/responses/responses-openai-bridge.js +129 -611
  38. package/dist/conversion/shared/chat-output-normalizer.js +6 -0
  39. package/dist/conversion/shared/chat-request-filters.js +1 -1
  40. package/dist/conversion/shared/output-content-normalizer.js +10 -0
  41. package/dist/conversion/shared/responses-conversation-store.js +22 -135
  42. package/dist/conversion/shared/responses-output-builder.d.ts +0 -2
  43. package/dist/conversion/shared/responses-output-builder.js +28 -318
  44. package/dist/conversion/shared/responses-response-utils.js +35 -86
  45. package/dist/conversion/shared/streaming-text-extractor.d.ts +1 -2
  46. package/dist/conversion/shared/streaming-text-extractor.js +13 -14
  47. package/dist/native/router_hotpath_napi.node +0 -0
  48. package/dist/quota/quota-state.js +29 -7
  49. package/dist/quota/types.d.ts +1 -0
  50. package/dist/router/virtual-router/bootstrap/routing-config.js +11 -3
  51. package/dist/router/virtual-router/engine-legacy.d.ts +3 -3
  52. package/dist/router/virtual-router/engine-legacy.js +15 -7
  53. package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.d.ts +16 -0
  54. package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.js +434 -46
  55. package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.d.ts +83 -0
  56. package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js +295 -0
  57. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-outbound-semantics.d.ts +1 -0
  58. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.d.ts +7 -0
  59. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.js +8 -1
  60. package/dist/router/virtual-router/engine-selection/native-router-hotpath-loader.js +383 -298
  61. package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.d.ts +20 -0
  62. package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.js +201 -0
  63. package/dist/router/virtual-router/engine-selection/native-virtual-router-routing-instructions-semantics.d.ts +1 -0
  64. package/dist/router/virtual-router/engine-selection/native-virtual-router-routing-instructions-semantics.js +37 -0
  65. package/dist/router/virtual-router/engine.js +0 -38
  66. package/dist/router/virtual-router/features.js +44 -3
  67. package/dist/router/virtual-router/routing-instructions/parse.d.ts +0 -12
  68. package/dist/router/virtual-router/routing-instructions/parse.js +9 -389
  69. package/dist/router/virtual-router/stop-message-state-sync.d.ts +3 -6
  70. package/dist/router/virtual-router/stop-message-state-sync.js +50 -21
  71. package/dist/servertool/handlers/followup-request-builder.js +12 -2
  72. package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +1 -0
  73. package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.js +26 -0
  74. package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +12 -2
  75. package/package.json +1 -1
  76. package/dist/router/virtual-router/engine-legacy/route-finalize.d.ts +0 -9
  77. package/dist/router/virtual-router/engine-legacy/route-finalize.js +0 -84
  78. package/dist/router/virtual-router/engine-legacy/route-selection.d.ts +0 -17
  79. package/dist/router/virtual-router/engine-legacy/route-selection.js +0 -205
  80. package/dist/router/virtual-router/engine-legacy/route-state-allowlist.d.ts +0 -3
  81. package/dist/router/virtual-router/engine-legacy/route-state-allowlist.js +0 -36
  82. package/dist/router/virtual-router/engine-legacy/route-state.d.ts +0 -12
  83. package/dist/router/virtual-router/engine-legacy/route-state.js +0 -386
  84. package/dist/router/virtual-router/engine-legacy/routing.d.ts +0 -8
  85. package/dist/router/virtual-router/engine-legacy/routing.js +0 -8
@@ -1,258 +1,42 @@
1
- import { createHash, randomUUID } from 'node:crypto';
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;
9
- function isRecord(value) {
10
- return Boolean(value && typeof value === 'object' && !Array.isArray(value));
11
- }
12
- function readTrimmedString(value) {
13
- if (typeof value !== 'string') {
1
+ import { buildNativeReqOutboundCompatAdapterContext } from '../../hub/pipeline/compat/native-adapter-context.js';
2
+ import { runReqOutboundStage3CompatWithNative } from '../../../router/virtual-router/engine-selection/native-hub-pipeline-req-outbound-semantics.js';
3
+ const PROFILE = 'chat:claude-code';
4
+ const DEFAULT_PROVIDER_PROTOCOL = 'anthropic-messages';
5
+ const DEFAULT_ENTRY_ENDPOINT = '/v1/messages';
6
+ function buildClaudeCodeConfigNode(config) {
7
+ if (!config) {
14
8
  return undefined;
15
9
  }
16
- const trimmed = value.trim();
17
- return trimmed.length ? trimmed : undefined;
18
- }
19
- function shouldInjectThinking(value) {
20
- if (value === undefined || value === null) {
21
- return true;
22
- }
23
- if (typeof value === 'boolean') {
24
- return value !== false;
25
- }
26
- if (isRecord(value)) {
27
- const type = readTrimmedString(value.type);
28
- return !type;
29
- }
30
- if (typeof value === 'string') {
31
- return value.trim().length === 0;
32
- }
33
- return true;
34
- }
35
- function resolveEffort(model) {
36
- const modelId = readTrimmedString(model)?.toLowerCase() || '';
37
- return modelId.startsWith('glm-5') ? 'high' : 'medium';
38
- }
39
- function ensureAdaptiveThinking(root) {
40
- if (shouldInjectThinking(root.thinking)) {
41
- root.thinking = { type: 'adaptive' };
42
- }
43
- }
44
- function ensureOutputEffort(root) {
45
- const effort = resolveEffort(root.model);
46
- if (isRecord(root.output_config)) {
47
- const outputConfig = root.output_config;
48
- if (!readTrimmedString(outputConfig.effort)) {
49
- outputConfig.effort = effort;
50
- }
51
- return;
52
- }
53
- root.output_config = { effort };
54
- }
55
- function isClaudeCodeUserId(value) {
56
- const trimmed = readTrimmedString(value);
57
- if (!trimmed)
58
- return false;
59
- return CLAUDE_CODE_USER_ID_REGEX.test(trimmed);
60
- }
61
- function sha256Hex(value) {
62
- return createHash('sha256').update(value).digest('hex');
63
- }
64
- function formatUuidFromHex32(hex32) {
65
- const hex = hex32.toLowerCase();
66
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
67
- }
68
- function uuidFromSeed(seed) {
69
- const hex = sha256Hex(seed);
70
- // Make it UUIDv4-ish (version nibble "4", variant "8") so regex validators accept it.
71
- const chars = hex.split('');
72
- if (chars.length >= 32) {
73
- chars[12] = '4';
74
- chars[16] = '8';
75
- }
76
- return formatUuidFromHex32(chars.slice(0, 32).join(''));
77
- }
78
- function normalizeSessionUuid(candidate) {
79
- const raw = readTrimmedString(candidate);
80
- if (!raw)
81
- return undefined;
82
- const match = raw.match(SESSION_TOKEN_REGEX);
83
- const trimmed = (match && match[1] ? match[1] : raw).trim();
84
- if (UUID_REGEX.test(trimmed)) {
85
- return trimmed.toLowerCase();
10
+ const node = {};
11
+ if (typeof config.systemText === 'string' && config.systemText.trim()) {
12
+ node.systemText = config.systemText.trim();
86
13
  }
87
- const compact = trimmed.replace(/-/g, '');
88
- if (HEX_32_REGEX.test(compact)) {
89
- return formatUuidFromHex32(compact);
14
+ if (typeof config.preserveExistingSystemAsUserMessage === 'boolean') {
15
+ node.preserveExistingSystemAsUserMessage = config.preserveExistingSystemAsUserMessage;
90
16
  }
91
- return uuidFromSeed(trimmed);
17
+ return Object.keys(node).length ? node : undefined;
92
18
  }
93
- function resolveClaudeCodeUserId(metadata, adapterContext) {
94
- const existing = readTrimmedString(metadata.user_id);
95
- if (isClaudeCodeUserId(existing)) {
96
- return existing;
97
- }
98
- const envUserId = readTrimmedString(process.env[DEFAULT_USER_ID_ENV]);
99
- if (isClaudeCodeUserId(envUserId)) {
100
- return envUserId;
101
- }
102
- const clientHeaders = isRecord(metadata.clientHeaders) ? metadata.clientHeaders : undefined;
103
- const ctx = isRecord(adapterContext) ? adapterContext : undefined;
104
- const sessionUuid = normalizeSessionUuid(existing) ||
105
- normalizeSessionUuid(envUserId) ||
106
- normalizeSessionUuid(readTrimmedString(clientHeaders?.session_id)) ||
107
- normalizeSessionUuid(readTrimmedString(clientHeaders?.['anthropic-session-id'])) ||
108
- normalizeSessionUuid(readTrimmedString(clientHeaders?.['x-session-id'])) ||
109
- normalizeSessionUuid(readTrimmedString(clientHeaders?.conversation_id)) ||
110
- normalizeSessionUuid(readTrimmedString(clientHeaders?.['anthropic-conversation-id'])) ||
111
- normalizeSessionUuid(readTrimmedString(clientHeaders?.['openai-conversation-id'])) ||
112
- normalizeSessionUuid(readTrimmedString(metadata.sessionId)) ||
113
- normalizeSessionUuid(readTrimmedString(metadata.conversationId)) ||
114
- normalizeSessionUuid(readTrimmedString(ctx?.sessionId)) ||
115
- normalizeSessionUuid(readTrimmedString(ctx?.conversationId));
116
- const accountSeed = readTrimmedString(process.env[DEFAULT_ACCOUNT_SEED_ENV]) ||
117
- readTrimmedString(process.env.USER) ||
118
- 'routecodex';
119
- try {
120
- const accountHash = sha256Hex(accountSeed);
121
- const session = sessionUuid ?? randomUUID();
122
- return `user_${accountHash}_account__session_${session}`;
123
- }
124
- catch {
125
- // As a last resort, keep compatibility best-effort.
126
- const session = sessionUuid ?? randomUUID();
127
- return `user_${'0'.repeat(64)}_account__session_${session}`;
128
- }
129
- // unreachable
130
- }
131
- function normalizeSystemBlocks(system) {
132
- const blocks = [];
133
- const pushText = (text, extra) => {
134
- const trimmed = text.trim();
135
- if (!trimmed) {
136
- return;
137
- }
138
- blocks.push({
139
- ...(extra ?? {}),
140
- type: 'text',
141
- text: trimmed
142
- });
19
+ function buildClaudeCodeCompatContext(adapterContext, config) {
20
+ const nativeContext = buildNativeReqOutboundCompatAdapterContext(adapterContext);
21
+ const claudeCode = buildClaudeCodeConfigNode(config);
22
+ return {
23
+ ...nativeContext,
24
+ compatibilityProfile: PROFILE,
25
+ providerProtocol: nativeContext.providerProtocol ?? adapterContext?.providerProtocol ?? DEFAULT_PROVIDER_PROTOCOL,
26
+ entryEndpoint: nativeContext.entryEndpoint ?? adapterContext?.entryEndpoint ?? DEFAULT_ENTRY_ENDPOINT,
27
+ ...(claudeCode ? { claudeCode } : {})
143
28
  };
144
- if (typeof system === 'string') {
145
- pushText(system);
146
- return blocks;
147
- }
148
- if (Array.isArray(system)) {
149
- for (const entry of system) {
150
- if (typeof entry === 'string') {
151
- pushText(entry);
152
- continue;
153
- }
154
- if (!isRecord(entry)) {
155
- continue;
156
- }
157
- const text = typeof entry.text === 'string' ? entry.text : '';
158
- if (text) {
159
- const extra = { ...entry };
160
- delete extra.type;
161
- delete extra.text;
162
- pushText(text, extra);
163
- }
164
- }
165
- return blocks;
166
- }
167
- if (isRecord(system)) {
168
- const text = typeof system.text === 'string' ? system.text : '';
169
- if (text) {
170
- const extra = { ...system };
171
- delete extra.type;
172
- delete extra.text;
173
- pushText(text, extra);
174
- }
175
- }
176
- return blocks;
177
29
  }
178
- function dedupeSystemBlocksByText(blocks) {
179
- const seen = new Set();
180
- const result = [];
181
- for (const block of blocks) {
182
- const text = typeof block.text === 'string' ? block.text.trim() : '';
183
- if (!text)
184
- continue;
185
- if (seen.has(text))
186
- continue;
187
- seen.add(text);
188
- result.push({ ...block, type: 'text', text });
189
- }
190
- return result;
191
- }
192
- function prependUserContent(messages, blocks) {
193
- if (!Array.isArray(messages) || blocks.length === 0) {
194
- return;
195
- }
196
- const first = messages[0];
197
- if (isRecord(first) && typeof first.role === 'string' && first.role.toLowerCase() === 'user') {
198
- const existing = first.content;
199
- if (typeof existing === 'string') {
200
- const injected = blocks.map((b) => b.text).filter(Boolean).join('\n\n');
201
- first.content = existing.trim() ? `${injected}\n\n${existing}` : injected;
202
- return;
203
- }
204
- if (Array.isArray(existing)) {
205
- first.content = [...blocks, ...existing];
206
- return;
207
- }
208
- first.content = [...blocks];
209
- return;
210
- }
211
- messages.unshift({ role: 'user', content: [...blocks] });
30
+ function buildClaudeCodeCompatInput(payload, config, adapterContext) {
31
+ return {
32
+ payload,
33
+ adapterContext: buildClaudeCodeCompatContext(adapterContext, config),
34
+ explicitProfile: PROFILE
35
+ };
212
36
  }
213
- /**
214
- * Canonicalizes the Anthropic `system` prompt into a single text block.
215
- * Optionally preserves previous system blocks by prepending them to the first user message,
216
- * so request semantics are preserved after normalization.
217
- */
218
37
  export function applyAnthropicClaudeCodeSystemPromptCompat(payload, config, adapterContext) {
219
38
  if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
220
39
  return payload;
221
40
  }
222
- const root = payload;
223
- const systemText = (typeof config?.systemText === 'string' && config.systemText.trim())
224
- ? config.systemText.trim()
225
- : DEFAULT_SYSTEM_TEXT;
226
- // Some Claude-Code-gated Anthropic proxies require the top-level `metadata` field to exist.
227
- // We don't inspect/repair semantics: just ensure the field is present as an object.
228
- if (!isRecord(root.metadata)) {
229
- root.metadata = {};
230
- }
231
- // Some proxies also require `metadata.user_id` to be present.
232
- // Fill it with Claude Code's canonical `user_<sha256>_account__session_<uuid>` shape.
233
- try {
234
- const userId = resolveClaudeCodeUserId(root.metadata, adapterContext);
235
- const current = readTrimmedString(root.metadata.user_id);
236
- if (userId && !isClaudeCodeUserId(current)) {
237
- root.metadata.user_id = userId;
238
- }
239
- }
240
- catch {
241
- // best-effort: never block compat due to metadata shaping failures
242
- }
243
- const preserveExisting = config?.preserveExistingSystemAsUserMessage !== false;
244
- const existingBlocks = dedupeSystemBlocksByText(normalizeSystemBlocks(root.system))
245
- .filter((b) => b.text !== systemText);
246
- // Normalize: force system into a single text block.
247
- root.system = [{ type: 'text', text: systemText }];
248
- ensureAdaptiveThinking(root);
249
- ensureOutputEffort(root);
250
- if (preserveExisting && existingBlocks.length) {
251
- const messages = Array.isArray(root.messages) ? root.messages : [];
252
- if (messages.length || root.messages !== undefined) {
253
- prependUserContent(messages, existingBlocks);
254
- root.messages = messages;
255
- }
256
- }
257
- return root;
41
+ return runReqOutboundStage3CompatWithNative(buildClaudeCodeCompatInput(payload, config, adapterContext)).payload;
258
42
  }
@@ -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 applyAnthropicClaudeCodeUserIdCompat(root: JsonObject, adapterContext?: AdapterContext): void;
@@ -0,0 +1,30 @@
1
+ import { buildNativeReqOutboundCompatAdapterContext } from '../../hub/pipeline/compat/native-adapter-context.js';
2
+ import { applyAnthropicClaudeCodeUserIdWithNative } from '../../../router/virtual-router/engine-selection/native-compat-action-semantics.js';
3
+ const PROFILE = 'chat:claude-code';
4
+ const DEFAULT_PROVIDER_PROTOCOL = 'anthropic-messages';
5
+ const DEFAULT_ENTRY_ENDPOINT = '/v1/messages';
6
+ function buildClaudeCodeCompatContext(adapterContext) {
7
+ const nativeContext = buildNativeReqOutboundCompatAdapterContext(adapterContext);
8
+ return {
9
+ ...nativeContext,
10
+ compatibilityProfile: PROFILE,
11
+ providerProtocol: nativeContext.providerProtocol ??
12
+ adapterContext?.providerProtocol ??
13
+ DEFAULT_PROVIDER_PROTOCOL,
14
+ entryEndpoint: nativeContext.entryEndpoint ??
15
+ adapterContext?.entryEndpoint ??
16
+ DEFAULT_ENTRY_ENDPOINT,
17
+ };
18
+ }
19
+ export function applyAnthropicClaudeCodeUserIdCompat(root, adapterContext) {
20
+ if (!root || typeof root !== 'object' || Array.isArray(root)) {
21
+ return;
22
+ }
23
+ const normalized = applyAnthropicClaudeCodeUserIdWithNative(root, buildClaudeCodeCompatContext(adapterContext));
24
+ for (const key of Object.keys(root)) {
25
+ if (!(key in normalized)) {
26
+ delete root[key];
27
+ }
28
+ }
29
+ Object.assign(root, normalized);
30
+ }
@@ -1,237 +1,26 @@
1
- import { ANTIGRAVITY_GLOBAL_ALIAS_KEY, cacheAntigravityRequestSessionMeta, clearAntigravitySessionSignature, extractAntigravityGeminiSessionId, getAntigravityLatestSignatureSessionIdForAlias, lookupAntigravitySessionSignatureEntry, markAntigravitySessionSignatureRewind, shouldTreatAsMissingThoughtSignature } from '../antigravity-session-signature.js';
2
- function isRecord(value) {
3
- return typeof value === 'object' && value !== null && !Array.isArray(value);
4
- }
5
- function shouldEnableForAdapter(adapterContext) {
6
- if (!adapterContext) {
7
- return false;
8
- }
9
- const protocol = typeof adapterContext.providerProtocol === 'string' ? adapterContext.providerProtocol.trim().toLowerCase() : '';
10
- if (protocol !== 'gemini-chat') {
11
- return false;
12
- }
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();
22
- const effectiveProviderId = providerIdOrKey.split('.')[0] ?? '';
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;
38
- }
39
- function locateGeminiContentsNode(root) {
40
- if (Array.isArray(root.contents)) {
41
- return root;
42
- }
43
- const req = root.request;
44
- if (isRecord(req) && Array.isArray(req.contents)) {
45
- return req;
46
- }
47
- const data = root.data;
48
- if (isRecord(data)) {
49
- return locateGeminiContentsNode(data);
50
- }
51
- return undefined;
52
- }
53
- function resolveAntigravityAliasKey(adapterContext) {
54
- if (!adapterContext) {
55
- return 'antigravity.unknown';
56
- }
57
- const ctxAny = adapterContext;
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;
71
- }
72
- return 'antigravity.unknown';
73
- }
74
- function stripThoughtSignatures(contentsNode) {
75
- const contentsRaw = contentsNode.contents;
76
- if (!Array.isArray(contentsRaw)) {
77
- return;
78
- }
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;
126
- }
127
- function injectThoughtSignatureIntoFunctionCalls(contentsNode, signature) {
128
- const contentsRaw = contentsNode.contents;
129
- if (!Array.isArray(contentsRaw)) {
130
- return;
131
- }
132
- const contents = contentsRaw;
133
- for (const entry of contents) {
134
- if (!isRecord(entry))
135
- continue;
136
- const partsRaw = entry.parts;
137
- if (!Array.isArray(partsRaw))
138
- continue;
139
- const parts = partsRaw;
140
- for (const part of parts) {
141
- if (!isRecord(part))
142
- continue;
143
- if (!isRecord(part.functionCall))
144
- continue;
145
- const existing = part.thoughtSignature;
146
- if (!shouldTreatAsMissingThoughtSignature(existing)) {
147
- continue;
148
- }
149
- part.thoughtSignature = signature;
150
- }
151
- }
1
+ import { buildNativeReqOutboundCompatAdapterContext } from '../../hub/pipeline/compat/native-adapter-context.js';
2
+ import { prepareAntigravityThoughtSignatureForGeminiRequestWithNative } from '../../../router/virtual-router/engine-selection/native-compat-action-semantics.js';
3
+ const PROFILE = 'chat:gemini-cli';
4
+ const DEFAULT_PROVIDER_PROTOCOL = 'gemini-chat';
5
+ const DEFAULT_ENTRY_ENDPOINT = '/v1beta/models:generateContent';
6
+ function buildAntigravityCompatContext(adapterContext) {
7
+ const nativeContext = buildNativeReqOutboundCompatAdapterContext(adapterContext);
8
+ return {
9
+ ...nativeContext,
10
+ compatibilityProfile: nativeContext.compatibilityProfile ??
11
+ adapterContext?.compatibilityProfile ??
12
+ PROFILE,
13
+ providerProtocol: nativeContext.providerProtocol ??
14
+ adapterContext?.providerProtocol ??
15
+ DEFAULT_PROVIDER_PROTOCOL,
16
+ entryEndpoint: nativeContext.entryEndpoint ??
17
+ adapterContext?.entryEndpoint ??
18
+ DEFAULT_ENTRY_ENDPOINT,
19
+ };
152
20
  }
153
21
  export function prepareAntigravityThoughtSignatureForGeminiRequest(payload, adapterContext) {
154
- if (!shouldEnableForAdapter(adapterContext)) {
155
- return payload;
156
- }
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;
163
- const ctxAny = adapterContext;
164
- const keys = [
165
- adapterContext.requestId,
166
- typeof ctxAny.clientRequestId === 'string' ? String(ctxAny.clientRequestId) : '',
167
- typeof ctxAny.groupRequestId === 'string' ? String(ctxAny.groupRequestId) : ''
168
- ].filter((k) => typeof k === 'string' && k.trim().length);
169
- const root = payload;
170
- const contentsNode = locateGeminiContentsNode(root);
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;
212
- for (const key of keys) {
213
- cacheAntigravityRequestSessionMeta(key, { aliasKey, sessionId: effectiveSessionId, messageCount });
214
- }
215
- const cached = typeof lookup.signature === 'string' && typeof lookup.messageCount === 'number'
216
- ? { signature: lookup.signature, messageCount: lookup.messageCount }
217
- : undefined;
218
- if (!cached) {
219
- return payload;
220
- }
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) {
224
- // Rewind detected: do not inject a "future" signature; clear and wait for a fresh signature from upstream.
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);
230
- return payload;
231
- }
232
- if (!contentsNode) {
22
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
233
23
  return payload;
234
24
  }
235
- injectThoughtSignatureIntoFunctionCalls(contentsNode, cached.signature);
236
- return payload;
25
+ return prepareAntigravityThoughtSignatureForGeminiRequestWithNative(payload, buildAntigravityCompatContext(adapterContext));
237
26
  }