@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.
Files changed (42) hide show
  1. package/dist/conversion/compat/profiles/chat-gemini.json +15 -15
  2. package/dist/conversion/compat/profiles/chat-glm.json +194 -194
  3. package/dist/conversion/compat/profiles/chat-iflow.json +199 -199
  4. package/dist/conversion/compat/profiles/chat-lmstudio.json +43 -43
  5. package/dist/conversion/compat/profiles/chat-qwen.json +20 -20
  6. package/dist/conversion/compat/profiles/responses-c4m.json +42 -42
  7. package/dist/conversion/compat/profiles/responses-output2choices-test.json +9 -10
  8. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +0 -1
  9. package/dist/conversion/hub/pipeline/hub-pipeline.js +68 -69
  10. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +0 -34
  11. package/dist/conversion/hub/process/chat-process.js +37 -16
  12. package/dist/conversion/hub/response/provider-response.js +0 -8
  13. package/dist/conversion/hub/response/response-runtime.js +47 -1
  14. package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +59 -4
  15. package/dist/conversion/hub/semantic-mappers/chat-mapper.d.ts +8 -0
  16. package/dist/conversion/hub/semantic-mappers/chat-mapper.js +93 -12
  17. package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +208 -31
  18. package/dist/conversion/hub/semantic-mappers/responses-mapper.js +280 -14
  19. package/dist/conversion/hub/standardized-bridge.js +11 -2
  20. package/dist/conversion/hub/types/chat-envelope.d.ts +10 -0
  21. package/dist/conversion/hub/types/standardized.d.ts +2 -1
  22. package/dist/conversion/responses/responses-openai-bridge.d.ts +3 -2
  23. package/dist/conversion/responses/responses-openai-bridge.js +1 -13
  24. package/dist/conversion/shared/text-markup-normalizer.d.ts +20 -0
  25. package/dist/conversion/shared/text-markup-normalizer.js +84 -5
  26. package/dist/conversion/shared/tool-filter-pipeline.d.ts +1 -1
  27. package/dist/conversion/shared/tool-filter-pipeline.js +54 -29
  28. package/dist/filters/index.d.ts +1 -0
  29. package/dist/filters/special/response-apply-patch-toon-decode.js +15 -7
  30. package/dist/filters/special/response-tool-arguments-toon-decode.js +108 -22
  31. package/dist/guidance/index.js +2 -0
  32. package/dist/router/virtual-router/classifier.js +16 -12
  33. package/dist/router/virtual-router/engine.js +45 -4
  34. package/dist/router/virtual-router/tool-signals.d.ts +2 -1
  35. package/dist/router/virtual-router/tool-signals.js +293 -134
  36. package/dist/router/virtual-router/types.d.ts +1 -1
  37. package/dist/router/virtual-router/types.js +1 -1
  38. package/dist/servertool/handlers/gemini-empty-reply-continue.js +28 -4
  39. package/dist/sse/json-to-sse/event-generators/responses.js +9 -2
  40. package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +7 -3
  41. package/dist/tools/apply-patch-structured.js +4 -3
  42. 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: typeof value.name === 'string' && value.name.trim().length ? value.name : undefined
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: normalizeToolContent(entry.content ?? entry.output),
189
- name: typeof entry.name === 'string' && entry.name.trim().length ? entry.name : undefined
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 applyExtraFields(body, metadata) {
229
- if (!metadata || !metadata.extraFields || !isJsonObject(metadata.extraFields)) {
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 [key, value] of Object.entries(metadata.extraFields)) {
233
- if (body[key] !== undefined) {
234
- continue;
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
- if (Array.isArray(payload.tools) && payload.tools.length === 0) {
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 ?? (chat.metadata?.toolsFieldPresent ? [] : undefined),
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
- for (const message of chat.messages) {
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
- const functionCall = { name, args: cloneAsJsonValue(argsStruct) };
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 (geminiState?.systemInstruction !== undefined) {
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 if (metadata?.systemInstructions && Array.isArray(metadata.systemInstructions)) {
385
- const sysBlocks = metadata.systemInstructions
386
- .filter((value) => typeof value === 'string' && value.trim().length > 0)
387
- .map((value) => ({ text: value }));
388
- if (sysBlocks.length) {
389
- request.systemInstruction = { role: 'system', parts: sysBlocks };
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 (metadata?.providerMetadata && isJsonObject(metadata.providerMetadata)) {
404
- request.metadata = jsonClone(metadata.providerMetadata);
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 && (!Array.isArray(chat.tools) || chat.tools.length === 0)) {
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 directly at Gemini mapping time to ensure it is always active
425
- // for antigravity.*.claude-sonnet-4-5-thinking, regardless of compatibilityProfile wiring.
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 providerMetadata = jsonClone(providerMetadataSource);
704
+ const cloned = jsonClone(providerMetadataSource);
558
705
  let toolsFieldPresent = false;
559
- if (isJsonObject(providerMetadata)) {
560
- delete providerMetadata.__rcc_stream;
561
- if (Object.prototype.hasOwnProperty.call(providerMetadata, '__rcc_tools_field_present')) {
562
- const sentinel = providerMetadata.__rcc_tools_field_present;
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 providerMetadata.__rcc_tools_field_present;
711
+ delete cloned.__rcc_tools_field_present;
565
712
  }
566
- if (Object.prototype.hasOwnProperty.call(providerMetadata, '__rcc_raw_system')) {
567
- delete providerMetadata.__rcc_raw_system;
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
- return {
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 {