@jsonstudio/llms 0.6.3409 → 0.6.3539
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/anthropic-openai-codec.d.ts +12 -3
- package/dist/conversion/codecs/anthropic-openai-codec.js +32 -92
- package/dist/conversion/codecs/gemini-openai-codec.d.ts +6 -5
- package/dist/conversion/codecs/gemini-openai-codec.js +48 -685
- package/dist/conversion/codecs/openai-openai-codec.d.ts +1 -1
- package/dist/conversion/codecs/openai-openai-codec.js +34 -100
- package/dist/conversion/codecs/responses-openai-codec.d.ts +1 -1
- package/dist/conversion/codecs/responses-openai-codec.js +47 -159
- package/dist/conversion/compat/actions/anthropic-claude-code-system-prompt.d.ts +2 -6
- package/dist/conversion/compat/actions/anthropic-claude-code-system-prompt.js +29 -245
- package/dist/conversion/compat/actions/anthropic-claude-code-user-id.d.ts +3 -0
- package/dist/conversion/compat/actions/anthropic-claude-code-user-id.js +30 -0
- package/dist/conversion/compat/actions/antigravity-thought-signature-prepare.js +21 -232
- package/dist/conversion/compat/actions/deepseek-web-request.js +41 -276
- package/dist/conversion/compat/actions/deepseek-web-response.js +64 -859
- package/dist/conversion/compat/actions/gemini-cli-request.d.ts +1 -1
- package/dist/conversion/compat/actions/gemini-cli-request.js +20 -613
- package/dist/conversion/compat/actions/gemini-web-search.d.ts +1 -15
- package/dist/conversion/compat/actions/gemini-web-search.js +22 -69
- package/dist/conversion/compat/actions/glm-tool-extraction.d.ts +3 -2
- package/dist/conversion/compat/actions/glm-tool-extraction.js +28 -257
- package/dist/conversion/compat/actions/iflow-tool-text-fallback.d.ts +0 -8
- package/dist/conversion/compat/actions/iflow-tool-text-fallback.js +24 -206
- package/dist/conversion/compat/actions/qwen-transform.d.ts +3 -2
- package/dist/conversion/compat/actions/qwen-transform.js +30 -271
- package/dist/conversion/compat/actions/tool-text-request-guidance.js +3 -173
- package/dist/conversion/compat/actions/universal-shape-filter.d.ts +6 -23
- package/dist/conversion/compat/actions/universal-shape-filter.js +4 -383
- package/dist/conversion/hub/pipeline/compat/native-adapter-context.js +1 -0
- package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.d.ts +1 -2
- package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.js +50 -104
- package/dist/conversion/pipeline/codecs/v2/openai-openai-pipeline.js +12 -10
- package/dist/conversion/pipeline/codecs/v2/responses-openai-pipeline.d.ts +0 -2
- package/dist/conversion/pipeline/codecs/v2/responses-openai-pipeline.js +46 -67
- package/dist/conversion/pipeline/codecs/v2/shared/openai-chat-helpers.js +15 -40
- package/dist/conversion/responses/responses-openai-bridge/response-payload.js +47 -348
- package/dist/conversion/responses/responses-openai-bridge.js +129 -611
- package/dist/conversion/shared/chat-output-normalizer.js +6 -0
- package/dist/conversion/shared/chat-request-filters.js +1 -1
- package/dist/conversion/shared/output-content-normalizer.js +10 -0
- package/dist/conversion/shared/responses-conversation-store.js +22 -135
- package/dist/conversion/shared/responses-output-builder.d.ts +0 -2
- package/dist/conversion/shared/responses-output-builder.js +28 -318
- package/dist/conversion/shared/responses-response-utils.js +35 -86
- package/dist/conversion/shared/streaming-text-extractor.d.ts +1 -2
- package/dist/conversion/shared/streaming-text-extractor.js +13 -14
- package/dist/native/router_hotpath_napi.node +0 -0
- package/dist/router/virtual-router/bootstrap/routing-config.js +11 -3
- package/dist/router/virtual-router/engine-legacy.d.ts +3 -3
- package/dist/router/virtual-router/engine-legacy.js +15 -7
- package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.d.ts +16 -0
- package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.js +434 -46
- package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.d.ts +83 -0
- package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js +295 -0
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-outbound-semantics.d.ts +1 -0
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.d.ts +7 -0
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.js +8 -1
- package/dist/router/virtual-router/engine-selection/native-router-hotpath-loader.js +383 -298
- package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.d.ts +20 -0
- package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.js +201 -0
- package/dist/router/virtual-router/engine-selection/native-virtual-router-routing-instructions-semantics.d.ts +1 -0
- package/dist/router/virtual-router/engine-selection/native-virtual-router-routing-instructions-semantics.js +37 -0
- package/dist/router/virtual-router/engine.js +0 -38
- package/dist/router/virtual-router/features.js +44 -3
- package/dist/router/virtual-router/routing-instructions/parse.d.ts +0 -12
- package/dist/router/virtual-router/routing-instructions/parse.js +9 -389
- package/dist/router/virtual-router/stop-message-state-sync.d.ts +3 -6
- package/dist/router/virtual-router/stop-message-state-sync.js +50 -21
- package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +1 -0
- package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.js +26 -0
- package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +12 -2
- package/package.json +1 -1
- package/dist/router/virtual-router/engine-legacy/route-finalize.d.ts +0 -9
- package/dist/router/virtual-router/engine-legacy/route-finalize.js +0 -84
- package/dist/router/virtual-router/engine-legacy/route-selection.d.ts +0 -17
- package/dist/router/virtual-router/engine-legacy/route-selection.js +0 -205
- package/dist/router/virtual-router/engine-legacy/route-state-allowlist.d.ts +0 -3
- package/dist/router/virtual-router/engine-legacy/route-state-allowlist.js +0 -36
- package/dist/router/virtual-router/engine-legacy/route-state.d.ts +0 -12
- package/dist/router/virtual-router/engine-legacy/route-state.js +0 -386
- package/dist/router/virtual-router/engine-legacy/routing.d.ts +0 -8
- package/dist/router/virtual-router/engine-legacy/routing.js +0 -8
|
@@ -1,258 +1,42 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
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
|
|
17
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
return formatUuidFromHex32(compact);
|
|
14
|
+
if (typeof config.preserveExistingSystemAsUserMessage === 'boolean') {
|
|
15
|
+
node.preserveExistingSystemAsUserMessage = config.preserveExistingSystemAsUserMessage;
|
|
90
16
|
}
|
|
91
|
-
return
|
|
17
|
+
return Object.keys(node).length ? node : undefined;
|
|
92
18
|
}
|
|
93
|
-
function
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
|
|
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,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 {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
:
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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 (!
|
|
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
|
-
|
|
236
|
-
return payload;
|
|
25
|
+
return prepareAntigravityThoughtSignatureForGeminiRequestWithNative(payload, buildAntigravityCompatContext(adapterContext));
|
|
237
26
|
}
|