@jsonstudio/llms 0.6.568 → 0.6.626
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/conversion/compat/profiles/chat-gemini.json +15 -15
- package/dist/conversion/compat/profiles/chat-glm.json +194 -194
- package/dist/conversion/compat/profiles/chat-iflow.json +199 -199
- package/dist/conversion/compat/profiles/chat-lmstudio.json +43 -43
- package/dist/conversion/compat/profiles/chat-qwen.json +20 -20
- package/dist/conversion/compat/profiles/responses-c4m.json +42 -42
- package/dist/conversion/compat/profiles/responses-output2choices-test.json +9 -10
- package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +0 -1
- package/dist/conversion/hub/pipeline/hub-pipeline.js +68 -69
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +0 -34
- package/dist/conversion/hub/process/chat-process.js +37 -16
- package/dist/conversion/hub/response/provider-response.js +0 -8
- package/dist/conversion/hub/response/response-runtime.js +47 -1
- package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +59 -4
- package/dist/conversion/hub/semantic-mappers/chat-mapper.d.ts +8 -0
- package/dist/conversion/hub/semantic-mappers/chat-mapper.js +93 -12
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +208 -31
- package/dist/conversion/hub/semantic-mappers/responses-mapper.js +280 -14
- package/dist/conversion/hub/standardized-bridge.js +11 -2
- package/dist/conversion/hub/types/chat-envelope.d.ts +10 -0
- package/dist/conversion/hub/types/standardized.d.ts +2 -1
- package/dist/conversion/responses/responses-openai-bridge.d.ts +3 -2
- package/dist/conversion/responses/responses-openai-bridge.js +1 -13
- package/dist/conversion/shared/text-markup-normalizer.d.ts +20 -0
- package/dist/conversion/shared/text-markup-normalizer.js +84 -5
- package/dist/conversion/shared/tool-filter-pipeline.d.ts +1 -1
- package/dist/conversion/shared/tool-filter-pipeline.js +54 -29
- package/dist/filters/index.d.ts +1 -0
- package/dist/filters/special/response-apply-patch-toon-decode.js +15 -7
- package/dist/filters/special/response-tool-arguments-toon-decode.js +108 -22
- package/dist/guidance/index.js +2 -0
- package/dist/router/virtual-router/classifier.js +16 -12
- package/dist/router/virtual-router/engine.js +45 -4
- package/dist/router/virtual-router/tool-signals.d.ts +2 -1
- package/dist/router/virtual-router/tool-signals.js +293 -134
- package/dist/router/virtual-router/types.d.ts +1 -1
- package/dist/router/virtual-router/types.js +1 -1
- package/dist/servertool/handlers/gemini-empty-reply-continue.js +28 -4
- package/dist/sse/json-to-sse/event-generators/responses.js +9 -2
- package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +7 -3
- package/dist/tools/apply-patch-structured.js +4 -3
- package/package.json +2 -2
|
@@ -59,6 +59,22 @@ function normalizeToolContent(content) {
|
|
|
59
59
|
return String(content ?? '');
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
+
export function maybeAugmentApplyPatchErrorContent(content, toolName) {
|
|
63
|
+
if (!content)
|
|
64
|
+
return content;
|
|
65
|
+
const lower = content.toLowerCase();
|
|
66
|
+
const isApplyPatch = (typeof toolName === 'string' && toolName.trim() === 'apply_patch') ||
|
|
67
|
+
lower.includes('apply_patch verification failed');
|
|
68
|
+
if (!isApplyPatch) {
|
|
69
|
+
return content;
|
|
70
|
+
}
|
|
71
|
+
// 避免重复追加提示。
|
|
72
|
+
if (content.includes('[apply_patch hint]')) {
|
|
73
|
+
return content;
|
|
74
|
+
}
|
|
75
|
+
const hint = '\n\n[apply_patch hint] 在使用 apply_patch 之前,请先读取目标文件的最新内容,并基于该内容生成补丁;同时确保补丁格式符合工具规范(统一补丁格式或结构化参数),避免上下文不匹配或语法错误。';
|
|
76
|
+
return content + hint;
|
|
77
|
+
}
|
|
62
78
|
function recordToolCallIssues(message, messageIndex, missing) {
|
|
63
79
|
const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : undefined;
|
|
64
80
|
if (!toolCalls?.length)
|
|
@@ -158,11 +174,13 @@ function normalizeChatMessages(raw) {
|
|
|
158
174
|
norm.missingFields.push({ path: `messages[${index}].tool_call_id`, reason: 'missing_tool_call_id' });
|
|
159
175
|
return;
|
|
160
176
|
}
|
|
177
|
+
const nameValue = typeof value.name === 'string' && value.name.trim().length ? value.name : undefined;
|
|
161
178
|
const outputEntry = {
|
|
162
179
|
tool_call_id: toolCallId,
|
|
163
180
|
content: normalizeToolContent(value.content ?? value.output),
|
|
164
|
-
name:
|
|
181
|
+
name: nameValue
|
|
165
182
|
};
|
|
183
|
+
outputEntry.content = maybeAugmentApplyPatchErrorContent(outputEntry.content, outputEntry.name);
|
|
166
184
|
norm.toolOutputs.push(outputEntry);
|
|
167
185
|
}
|
|
168
186
|
});
|
|
@@ -183,10 +201,13 @@ function normalizeStandaloneToolOutputs(raw, missing) {
|
|
|
183
201
|
missing.push({ path: `tool_outputs[${index}].tool_call_id`, reason: 'missing_tool_call_id' });
|
|
184
202
|
return;
|
|
185
203
|
}
|
|
204
|
+
const nameValue = typeof entry.name === 'string' && entry.name.trim().length ? entry.name : undefined;
|
|
205
|
+
const rawContent = normalizeToolContent(entry.content ?? entry.output);
|
|
206
|
+
const content = maybeAugmentApplyPatchErrorContent(rawContent, nameValue);
|
|
186
207
|
outputs.push({
|
|
187
208
|
tool_call_id: toolCallId,
|
|
188
|
-
content
|
|
189
|
-
name:
|
|
209
|
+
content,
|
|
210
|
+
name: nameValue
|
|
190
211
|
});
|
|
191
212
|
});
|
|
192
213
|
return outputs;
|
|
@@ -225,15 +246,67 @@ function collectExtraFields(body) {
|
|
|
225
246
|
}
|
|
226
247
|
return Object.keys(extras).length ? extras : undefined;
|
|
227
248
|
}
|
|
228
|
-
function
|
|
229
|
-
if (!
|
|
249
|
+
function extractOpenAIExtraFieldsFromSemantics(semantics) {
|
|
250
|
+
if (!semantics || !semantics.providerExtras || !isJsonObject(semantics.providerExtras)) {
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
const openaiExtras = semantics.providerExtras.openaiChat;
|
|
254
|
+
if (!openaiExtras || !isJsonObject(openaiExtras)) {
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
const stored = openaiExtras.extraFields;
|
|
258
|
+
if (!stored || !isJsonObject(stored)) {
|
|
259
|
+
return undefined;
|
|
260
|
+
}
|
|
261
|
+
return stored;
|
|
262
|
+
}
|
|
263
|
+
function hasExplicitEmptyToolsSemantics(semantics) {
|
|
264
|
+
if (!semantics || !semantics.tools || !isJsonObject(semantics.tools)) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
const flag = semantics.tools.explicitEmpty;
|
|
268
|
+
return flag === true;
|
|
269
|
+
}
|
|
270
|
+
function buildOpenAISemantics(options) {
|
|
271
|
+
const semantics = {};
|
|
272
|
+
if (options.systemSegments && options.systemSegments.length) {
|
|
273
|
+
semantics.system = {
|
|
274
|
+
textBlocks: options.systemSegments.map((segment) => segment)
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
if (options.extraFields && Object.keys(options.extraFields).length) {
|
|
278
|
+
semantics.providerExtras = {
|
|
279
|
+
openaiChat: {
|
|
280
|
+
extraFields: jsonClone(options.extraFields)
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
if (options.explicitEmptyTools) {
|
|
285
|
+
semantics.tools = {
|
|
286
|
+
explicitEmpty: true
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
return Object.keys(semantics).length ? semantics : undefined;
|
|
290
|
+
}
|
|
291
|
+
function applyExtraFields(body, metadata, semantics) {
|
|
292
|
+
const sources = [];
|
|
293
|
+
const semanticsExtras = extractOpenAIExtraFieldsFromSemantics(semantics);
|
|
294
|
+
if (semanticsExtras) {
|
|
295
|
+
sources.push(semanticsExtras);
|
|
296
|
+
}
|
|
297
|
+
if (metadata?.extraFields && isJsonObject(metadata.extraFields)) {
|
|
298
|
+
sources.push(metadata.extraFields);
|
|
299
|
+
}
|
|
300
|
+
if (!sources.length) {
|
|
230
301
|
return;
|
|
231
302
|
}
|
|
232
|
-
for (const
|
|
233
|
-
|
|
234
|
-
|
|
303
|
+
for (const source of sources) {
|
|
304
|
+
for (const [key, value] of Object.entries(source)) {
|
|
305
|
+
if (body[key] !== undefined) {
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
body[key] = jsonClone(value);
|
|
235
309
|
}
|
|
236
|
-
body[key] = jsonClone(value);
|
|
237
310
|
}
|
|
238
311
|
}
|
|
239
312
|
export class ChatSemanticMapper {
|
|
@@ -285,24 +358,32 @@ export class ChatSemanticMapper {
|
|
|
285
358
|
catch {
|
|
286
359
|
// noop: best-effort policy application
|
|
287
360
|
}
|
|
288
|
-
|
|
361
|
+
const explicitEmptyTools = Array.isArray(payload.tools) && payload.tools.length === 0;
|
|
362
|
+
if (explicitEmptyTools) {
|
|
289
363
|
metadata.toolsFieldPresent = true;
|
|
290
364
|
}
|
|
365
|
+
const semantics = buildOpenAISemantics({
|
|
366
|
+
systemSegments: normalized.systemSegments,
|
|
367
|
+
extraFields,
|
|
368
|
+
explicitEmptyTools
|
|
369
|
+
});
|
|
291
370
|
return {
|
|
292
371
|
messages: normalized.messages,
|
|
293
372
|
tools: normalizeTools(payload.tools, normalized.missingFields),
|
|
294
373
|
toolOutputs: toolOutputs.length ? toolOutputs : undefined,
|
|
295
374
|
parameters: extractParameters(payload),
|
|
375
|
+
semantics,
|
|
296
376
|
metadata
|
|
297
377
|
};
|
|
298
378
|
}
|
|
299
379
|
async fromChat(chat, ctx) {
|
|
380
|
+
const shouldEmitEmptyTools = hasExplicitEmptyToolsSemantics(chat.semantics) || chat.metadata?.toolsFieldPresent === true;
|
|
300
381
|
const payload = {
|
|
301
382
|
messages: chat.messages,
|
|
302
|
-
tools: chat.tools ?? (
|
|
383
|
+
tools: chat.tools ?? (shouldEmitEmptyTools ? [] : undefined),
|
|
303
384
|
...(chat.parameters || {})
|
|
304
385
|
};
|
|
305
|
-
applyExtraFields(payload, chat.metadata);
|
|
386
|
+
applyExtraFields(payload, chat.metadata, chat.semantics);
|
|
306
387
|
try {
|
|
307
388
|
const bridgePolicy = resolveBridgePolicy({ protocol: 'openai-chat' });
|
|
308
389
|
const actions = resolvePolicyActions(bridgePolicy, 'request_outbound');
|
|
@@ -25,6 +25,67 @@ function coerceThoughtSignature(value) {
|
|
|
25
25
|
}
|
|
26
26
|
return undefined;
|
|
27
27
|
}
|
|
28
|
+
function ensureGeminiSemanticsNode(chat) {
|
|
29
|
+
if (!chat.semantics || typeof chat.semantics !== 'object') {
|
|
30
|
+
chat.semantics = {};
|
|
31
|
+
}
|
|
32
|
+
if (!chat.semantics.gemini || !isJsonObject(chat.semantics.gemini)) {
|
|
33
|
+
chat.semantics.gemini = {};
|
|
34
|
+
}
|
|
35
|
+
return chat.semantics.gemini;
|
|
36
|
+
}
|
|
37
|
+
function ensureSystemSemantics(chat) {
|
|
38
|
+
if (!chat.semantics || typeof chat.semantics !== 'object') {
|
|
39
|
+
chat.semantics = {};
|
|
40
|
+
}
|
|
41
|
+
if (!chat.semantics.system || !isJsonObject(chat.semantics.system)) {
|
|
42
|
+
chat.semantics.system = {};
|
|
43
|
+
}
|
|
44
|
+
return chat.semantics.system;
|
|
45
|
+
}
|
|
46
|
+
function markGeminiExplicitEmptyTools(chat) {
|
|
47
|
+
if (!chat.semantics || typeof chat.semantics !== 'object') {
|
|
48
|
+
chat.semantics = {};
|
|
49
|
+
}
|
|
50
|
+
if (!chat.semantics.tools || !isJsonObject(chat.semantics.tools)) {
|
|
51
|
+
chat.semantics.tools = {};
|
|
52
|
+
}
|
|
53
|
+
chat.semantics.tools.explicitEmpty = true;
|
|
54
|
+
}
|
|
55
|
+
function readGeminiSemantics(chat) {
|
|
56
|
+
if (!chat.semantics || typeof chat.semantics !== 'object') {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
const node = chat.semantics.gemini;
|
|
60
|
+
return node && isJsonObject(node) ? node : undefined;
|
|
61
|
+
}
|
|
62
|
+
function hasExplicitEmptyToolsSemantics(chat) {
|
|
63
|
+
if (!chat.semantics || typeof chat.semantics !== 'object') {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
const toolsNode = chat.semantics.tools;
|
|
67
|
+
if (!toolsNode || !isJsonObject(toolsNode)) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
return Boolean(toolsNode.explicitEmpty);
|
|
71
|
+
}
|
|
72
|
+
function readSystemTextBlocksFromSemantics(chat) {
|
|
73
|
+
if (!chat.semantics || typeof chat.semantics !== 'object') {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
const systemNode = chat.semantics.system;
|
|
77
|
+
if (!systemNode || !isJsonObject(systemNode)) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
const rawBlocks = systemNode.textBlocks;
|
|
81
|
+
if (!Array.isArray(rawBlocks)) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
const normalized = rawBlocks
|
|
85
|
+
.map((entry) => (typeof entry === 'string' ? entry : undefined))
|
|
86
|
+
.filter((value) => typeof value === 'string' && value.trim().length > 0);
|
|
87
|
+
return normalized.length ? normalized : undefined;
|
|
88
|
+
}
|
|
28
89
|
function extractThoughtSignatureFromToolCall(tc) {
|
|
29
90
|
if (!tc || typeof tc !== 'object') {
|
|
30
91
|
return undefined;
|
|
@@ -104,18 +165,42 @@ function normalizeToolContent(value) {
|
|
|
104
165
|
return String(value ?? '');
|
|
105
166
|
}
|
|
106
167
|
}
|
|
107
|
-
function convertToolMessageToOutput(message) {
|
|
168
|
+
function convertToolMessageToOutput(message, allowedIds) {
|
|
108
169
|
const rawId = (message.tool_call_id ?? message.id);
|
|
109
170
|
const callId = typeof rawId === 'string' && rawId.trim().length ? rawId.trim() : undefined;
|
|
110
171
|
if (!callId) {
|
|
111
172
|
return null;
|
|
112
173
|
}
|
|
174
|
+
if (allowedIds && !allowedIds.has(callId)) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
113
177
|
return {
|
|
114
178
|
tool_call_id: callId,
|
|
115
179
|
content: normalizeToolContent(message.content),
|
|
116
180
|
name: typeof message.name === 'string' ? message.name : undefined
|
|
117
181
|
};
|
|
118
182
|
}
|
|
183
|
+
function selectAntigravityClaudeThinkingMessages(messages) {
|
|
184
|
+
if (!Array.isArray(messages) || messages.length === 0) {
|
|
185
|
+
return messages ?? [];
|
|
186
|
+
}
|
|
187
|
+
// 为了与 Responses 入口对齐,Claude-thinking 在发往 Antigravity 时仅保留
|
|
188
|
+
// 当前这一轮的 user 消息,丢弃历史 model/assistant 片段(例如错误日志中的「{」)。
|
|
189
|
+
let lastUserIndex = -1;
|
|
190
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
191
|
+
const msg = messages[i];
|
|
192
|
+
if (!msg || typeof msg !== 'object')
|
|
193
|
+
continue;
|
|
194
|
+
if (msg.role === 'user') {
|
|
195
|
+
lastUserIndex = i;
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (lastUserIndex === -1) {
|
|
200
|
+
return messages;
|
|
201
|
+
}
|
|
202
|
+
return [messages[lastUserIndex]];
|
|
203
|
+
}
|
|
119
204
|
function buildFunctionResponseEntry(output) {
|
|
120
205
|
const parsedPayload = safeParseJson(output.content);
|
|
121
206
|
const normalizedPayload = ensureFunctionResponsePayload(cloneAsJsonValue(parsedPayload));
|
|
@@ -288,19 +373,46 @@ function buildGeminiRequestFromChat(chat, metadata) {
|
|
|
288
373
|
const emittedToolOutputs = new Set();
|
|
289
374
|
const adapterContext = metadata?.context;
|
|
290
375
|
const rawProviderId = adapterContext?.providerId;
|
|
376
|
+
const entryEndpointRaw = adapterContext?.entryEndpoint;
|
|
377
|
+
const entryEndpoint = typeof entryEndpointRaw === 'string' ? entryEndpointRaw.trim().toLowerCase() : '';
|
|
378
|
+
const isAnthropicEntry = entryEndpoint === '/v1/messages';
|
|
291
379
|
const normalizedProviderId = typeof rawProviderId === 'string' ? rawProviderId.toLowerCase() : '';
|
|
292
380
|
const providerIdPrefix = normalizedProviderId.split('.')[0];
|
|
381
|
+
const isAntigravityClaudeThinking = providerIdPrefix === 'antigravity' &&
|
|
382
|
+
typeof chat.parameters?.model === 'string' &&
|
|
383
|
+
chat.parameters.model.includes('claude-sonnet-4-5-thinking');
|
|
293
384
|
// 保持对通用 gemini-cli 的保护(避免上游直接执行 functionCall),
|
|
294
385
|
// 但对于 antigravity.* 明确允许通过 Gemini functionCall 协议执行工具,
|
|
295
386
|
// 以便完整打通 tools → functionCall → functionResponse 链路。
|
|
296
387
|
const omitFunctionCallPartsForCli = providerIdPrefix === 'gemini-cli';
|
|
297
|
-
|
|
388
|
+
const semanticsNode = readGeminiSemantics(chat);
|
|
389
|
+
const systemTextBlocksFromSemantics = readSystemTextBlocksFromSemantics(chat);
|
|
390
|
+
const sourceMessages = chat.messages;
|
|
391
|
+
// 收集当前 ChatEnvelope 中 assistant/tool_calls 的 id,用于过滤孤立的 tool_result:
|
|
392
|
+
// 只有在本轮对话中存在对应 tool_call 的 tool_result 才允许映射为 Gemini functionResponse。
|
|
393
|
+
const assistantToolCallIds = new Set();
|
|
394
|
+
for (const msg of sourceMessages) {
|
|
395
|
+
if (!msg || typeof msg !== 'object')
|
|
396
|
+
continue;
|
|
397
|
+
if (msg.role !== 'assistant')
|
|
398
|
+
continue;
|
|
399
|
+
const tcs = Array.isArray(msg.tool_calls)
|
|
400
|
+
? msg.tool_calls
|
|
401
|
+
: [];
|
|
402
|
+
for (const tc of tcs) {
|
|
403
|
+
const id = typeof tc.id === 'string' ? tc.id.trim() : '';
|
|
404
|
+
if (id) {
|
|
405
|
+
assistantToolCallIds.add(id);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
for (const message of sourceMessages) {
|
|
298
410
|
if (!message || typeof message !== 'object')
|
|
299
411
|
continue;
|
|
300
412
|
if (message.role === 'system')
|
|
301
413
|
continue;
|
|
302
414
|
if (message.role === 'tool') {
|
|
303
|
-
const toolOutput = convertToolMessageToOutput(message);
|
|
415
|
+
const toolOutput = convertToolMessageToOutput(message, assistantToolCallIds);
|
|
304
416
|
if (toolOutput) {
|
|
305
417
|
contents.push(buildFunctionResponseEntry(toolOutput));
|
|
306
418
|
emittedToolOutputs.add(toolOutput.tool_call_id);
|
|
@@ -337,7 +449,13 @@ function buildGeminiRequestFromChat(chat, metadata) {
|
|
|
337
449
|
else {
|
|
338
450
|
argsStruct = fn.arguments ?? {};
|
|
339
451
|
}
|
|
340
|
-
|
|
452
|
+
let argsJson = cloneAsJsonValue(argsStruct);
|
|
453
|
+
// Gemini / Antigravity 期望 functionCall.args 为对象(Struct),
|
|
454
|
+
// 若顶层为数组或原始类型,则包装到 value 字段下,避免产生非法的 list 形状。
|
|
455
|
+
if (!argsJson || typeof argsJson !== 'object' || Array.isArray(argsJson)) {
|
|
456
|
+
argsJson = { value: argsJson };
|
|
457
|
+
}
|
|
458
|
+
const functionCall = { name, args: argsJson };
|
|
341
459
|
const part = { functionCall };
|
|
342
460
|
if (typeof tc.id === 'string') {
|
|
343
461
|
part.functionCall.id = tc.id;
|
|
@@ -378,15 +496,21 @@ function buildGeminiRequestFromChat(chat, metadata) {
|
|
|
378
496
|
contents
|
|
379
497
|
};
|
|
380
498
|
const geminiState = getProtocolState(metadata, 'gemini');
|
|
381
|
-
if (
|
|
499
|
+
if (semanticsNode?.systemInstruction !== undefined) {
|
|
500
|
+
request.systemInstruction = jsonClone(semanticsNode.systemInstruction);
|
|
501
|
+
}
|
|
502
|
+
else if (geminiState?.systemInstruction !== undefined) {
|
|
382
503
|
request.systemInstruction = jsonClone(geminiState.systemInstruction);
|
|
383
504
|
}
|
|
384
|
-
else
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
505
|
+
else {
|
|
506
|
+
const fallbackSystemInstructions = systemTextBlocksFromSemantics;
|
|
507
|
+
if (fallbackSystemInstructions && fallbackSystemInstructions.length) {
|
|
508
|
+
const sysBlocks = fallbackSystemInstructions
|
|
509
|
+
.filter((value) => typeof value === 'string' && value.trim().length > 0)
|
|
510
|
+
.map((value) => ({ text: value }));
|
|
511
|
+
if (sysBlocks.length) {
|
|
512
|
+
request.systemInstruction = { role: 'system', parts: sysBlocks };
|
|
513
|
+
}
|
|
390
514
|
}
|
|
391
515
|
}
|
|
392
516
|
if (chat.tools && chat.tools.length) {
|
|
@@ -397,17 +521,43 @@ function buildGeminiRequestFromChat(chat, metadata) {
|
|
|
397
521
|
}
|
|
398
522
|
}
|
|
399
523
|
const generationConfig = buildGenerationConfigFromParameters(chat.parameters || {});
|
|
524
|
+
if (semanticsNode?.generationConfig && isJsonObject(semanticsNode.generationConfig)) {
|
|
525
|
+
for (const [key, value] of Object.entries(semanticsNode.generationConfig)) {
|
|
526
|
+
if (generationConfig[key] !== undefined) {
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
generationConfig[key] = jsonClone(value);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
400
532
|
if (Object.keys(generationConfig).length) {
|
|
401
533
|
request.generationConfig = generationConfig;
|
|
402
534
|
}
|
|
403
|
-
if (
|
|
404
|
-
request.
|
|
535
|
+
if (semanticsNode?.safetySettings !== undefined) {
|
|
536
|
+
request.safetySettings = jsonClone(semanticsNode.safetySettings);
|
|
537
|
+
}
|
|
538
|
+
if (chat.parameters?.tool_config && isJsonObject(chat.parameters.tool_config)) {
|
|
539
|
+
request.toolConfig = jsonClone(chat.parameters.tool_config);
|
|
540
|
+
}
|
|
541
|
+
else if (semanticsNode?.toolConfig && isJsonObject(semanticsNode.toolConfig)) {
|
|
542
|
+
request.toolConfig = jsonClone(semanticsNode.toolConfig);
|
|
543
|
+
}
|
|
544
|
+
// 为了保持协议解耦,只在 Gemini 自身或开放式 Chat 入口下透传 providerMetadata;
|
|
545
|
+
// 对于 Anthropic (/v1/messages) 等其它协议的入口,不再将其 metadata 整块转发给 Gemini,
|
|
546
|
+
// 避免跨协议泄漏上游专有字段。
|
|
547
|
+
if (!isAnthropicEntry) {
|
|
548
|
+
if (semanticsNode?.providerMetadata && isJsonObject(semanticsNode.providerMetadata)) {
|
|
549
|
+
request.metadata = jsonClone(semanticsNode.providerMetadata);
|
|
550
|
+
}
|
|
551
|
+
else if (metadata?.providerMetadata && isJsonObject(metadata.providerMetadata)) {
|
|
552
|
+
request.metadata = jsonClone(metadata.providerMetadata);
|
|
553
|
+
}
|
|
405
554
|
}
|
|
406
555
|
if (chat.parameters && chat.parameters.stream !== undefined) {
|
|
407
556
|
request.metadata = request.metadata ?? {};
|
|
408
557
|
request.metadata.__rcc_stream = chat.parameters.stream;
|
|
409
558
|
}
|
|
410
|
-
if (chat.metadata?.toolsFieldPresent
|
|
559
|
+
if ((chat.metadata?.toolsFieldPresent || hasExplicitEmptyToolsSemantics(chat)) &&
|
|
560
|
+
(!Array.isArray(chat.tools) || chat.tools.length === 0)) {
|
|
411
561
|
request.metadata = request.metadata ?? {};
|
|
412
562
|
request.metadata.__rcc_tools_field_present = '1';
|
|
413
563
|
}
|
|
@@ -421,8 +571,9 @@ function buildGeminiRequestFromChat(chat, metadata) {
|
|
|
421
571
|
request.metadata[key] = value;
|
|
422
572
|
}
|
|
423
573
|
}
|
|
424
|
-
// Apply claude-thinking compat
|
|
425
|
-
// for
|
|
574
|
+
// Apply claude-thinking compat at Gemini mapping time to ensure it is always active
|
|
575
|
+
// for Claude models, regardless of compatibilityProfile wiring. Provider层负责进一步的
|
|
576
|
+
// 传输层收紧(如 session_id / generationConfig),这里不做非标裁剪。
|
|
426
577
|
const compatRequest = applyClaudeThinkingToolSchemaCompat(request, adapterContext);
|
|
427
578
|
return compatRequest;
|
|
428
579
|
}
|
|
@@ -508,16 +659,10 @@ export class GeminiSemanticMapper {
|
|
|
508
659
|
let parameters = collectParameters(payload);
|
|
509
660
|
const metadata = { context: ctx };
|
|
510
661
|
const systemSegments = collectSystemSegments(payload.systemInstruction);
|
|
511
|
-
if (systemSegments.length) {
|
|
512
|
-
metadata.systemInstructions = systemSegments;
|
|
513
|
-
}
|
|
514
662
|
if (payload.systemInstruction !== undefined) {
|
|
515
663
|
const rawSystem = jsonClone(payload.systemInstruction);
|
|
516
664
|
ensureProtocolState(metadata, 'gemini').systemInstruction = rawSystem;
|
|
517
665
|
}
|
|
518
|
-
if (payload.safetySettings) {
|
|
519
|
-
metadata.safetySettings = jsonClone(payload.safetySettings);
|
|
520
|
-
}
|
|
521
666
|
if (missing.length) {
|
|
522
667
|
metadata.missingFields = missing;
|
|
523
668
|
}
|
|
@@ -553,32 +698,64 @@ export class GeminiSemanticMapper {
|
|
|
553
698
|
parameters = { ...(parameters || {}), ...passthrough.passthrough };
|
|
554
699
|
}
|
|
555
700
|
const providerMetadataSource = passthrough.metadata ?? payload.metadata;
|
|
701
|
+
let providerMetadata;
|
|
702
|
+
let explicitEmptyTools = Array.isArray(payload.tools) && payload.tools.length === 0;
|
|
556
703
|
if (providerMetadataSource) {
|
|
557
|
-
const
|
|
704
|
+
const cloned = jsonClone(providerMetadataSource);
|
|
558
705
|
let toolsFieldPresent = false;
|
|
559
|
-
if (isJsonObject(
|
|
560
|
-
delete
|
|
561
|
-
if (Object.prototype.hasOwnProperty.call(
|
|
562
|
-
const sentinel =
|
|
706
|
+
if (isJsonObject(cloned)) {
|
|
707
|
+
delete cloned.__rcc_stream;
|
|
708
|
+
if (Object.prototype.hasOwnProperty.call(cloned, '__rcc_tools_field_present')) {
|
|
709
|
+
const sentinel = cloned.__rcc_tools_field_present;
|
|
563
710
|
toolsFieldPresent = sentinel === '1' || sentinel === true;
|
|
564
|
-
delete
|
|
711
|
+
delete cloned.__rcc_tools_field_present;
|
|
565
712
|
}
|
|
566
|
-
if (Object.prototype.hasOwnProperty.call(
|
|
567
|
-
delete
|
|
713
|
+
if (Object.prototype.hasOwnProperty.call(cloned, '__rcc_raw_system')) {
|
|
714
|
+
delete cloned.__rcc_raw_system;
|
|
568
715
|
}
|
|
569
716
|
}
|
|
570
717
|
if (toolsFieldPresent) {
|
|
571
718
|
metadata.toolsFieldPresent = true;
|
|
719
|
+
explicitEmptyTools = true;
|
|
572
720
|
}
|
|
721
|
+
providerMetadata = cloned;
|
|
573
722
|
metadata.providerMetadata = providerMetadata;
|
|
574
723
|
}
|
|
575
|
-
|
|
724
|
+
const chatEnvelope = {
|
|
576
725
|
messages,
|
|
577
726
|
tools,
|
|
578
727
|
toolOutputs,
|
|
579
728
|
parameters,
|
|
580
729
|
metadata
|
|
581
730
|
};
|
|
731
|
+
if (systemSegments.length) {
|
|
732
|
+
const systemNode = ensureSystemSemantics(chatEnvelope);
|
|
733
|
+
systemNode.textBlocks = systemSegments.map((segment) => segment);
|
|
734
|
+
}
|
|
735
|
+
let semanticsNode;
|
|
736
|
+
const ensureSemanticsNode = () => {
|
|
737
|
+
semanticsNode = semanticsNode ?? ensureGeminiSemanticsNode(chatEnvelope);
|
|
738
|
+
return semanticsNode;
|
|
739
|
+
};
|
|
740
|
+
if (payload.systemInstruction !== undefined) {
|
|
741
|
+
ensureSemanticsNode().systemInstruction = jsonClone(payload.systemInstruction);
|
|
742
|
+
}
|
|
743
|
+
if (payload.safetySettings) {
|
|
744
|
+
ensureSemanticsNode().safetySettings = jsonClone(payload.safetySettings);
|
|
745
|
+
}
|
|
746
|
+
if (payload.generationConfig && isJsonObject(payload.generationConfig)) {
|
|
747
|
+
ensureSemanticsNode().generationConfig = jsonClone(payload.generationConfig);
|
|
748
|
+
}
|
|
749
|
+
if (payload.toolConfig && isJsonObject(payload.toolConfig)) {
|
|
750
|
+
ensureSemanticsNode().toolConfig = jsonClone(payload.toolConfig);
|
|
751
|
+
}
|
|
752
|
+
if (providerMetadata) {
|
|
753
|
+
ensureSemanticsNode().providerMetadata = jsonClone(providerMetadata);
|
|
754
|
+
}
|
|
755
|
+
if (explicitEmptyTools) {
|
|
756
|
+
markGeminiExplicitEmptyTools(chatEnvelope);
|
|
757
|
+
}
|
|
758
|
+
return chatEnvelope;
|
|
582
759
|
}
|
|
583
760
|
async fromChat(chat, ctx) {
|
|
584
761
|
try {
|