@jsonstudio/llms 0.6.473 → 0.6.568

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 (82) hide show
  1. package/dist/conversion/codecs/gemini-openai-codec.js +33 -4
  2. package/dist/conversion/codecs/openai-openai-codec.js +2 -1
  3. package/dist/conversion/codecs/responses-openai-codec.js +3 -2
  4. package/dist/conversion/compat/actions/claude-thinking-tools.d.ts +15 -0
  5. package/dist/conversion/compat/actions/claude-thinking-tools.js +72 -0
  6. package/dist/conversion/compat/actions/glm-history-image-trim.d.ts +2 -0
  7. package/dist/conversion/compat/actions/glm-history-image-trim.js +88 -0
  8. package/dist/conversion/compat/profiles/chat-gemini.json +15 -14
  9. package/dist/conversion/compat/profiles/chat-glm.json +194 -194
  10. package/dist/conversion/compat/profiles/chat-iflow.json +199 -199
  11. package/dist/conversion/compat/profiles/chat-lmstudio.json +43 -43
  12. package/dist/conversion/compat/profiles/chat-qwen.json +20 -20
  13. package/dist/conversion/compat/profiles/responses-c4m.json +42 -42
  14. package/dist/conversion/compat/profiles/responses-output2choices-test.json +12 -0
  15. package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +6 -0
  16. package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +2 -0
  17. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +6 -1
  18. package/dist/conversion/hub/pipeline/hub-pipeline.js +40 -13
  19. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +15 -0
  20. package/dist/conversion/hub/process/chat-process.js +107 -26
  21. package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +8 -0
  22. package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +28 -10
  23. package/dist/conversion/hub/semantic-mappers/responses-mapper.js +51 -2
  24. package/dist/conversion/hub/tool-session-compat.d.ts +26 -0
  25. package/dist/conversion/hub/tool-session-compat.js +299 -0
  26. package/dist/conversion/hub/types/chat-envelope.d.ts +1 -0
  27. package/dist/conversion/responses/responses-openai-bridge.d.ts +0 -1
  28. package/dist/conversion/responses/responses-openai-bridge.js +0 -71
  29. package/dist/conversion/shared/anthropic-message-utils.js +54 -0
  30. package/dist/conversion/shared/args-mapping.js +11 -3
  31. package/dist/conversion/shared/gemini-tool-utils.js +8 -0
  32. package/dist/conversion/shared/responses-output-builder.js +47 -88
  33. package/dist/conversion/shared/streaming-text-extractor.d.ts +25 -0
  34. package/dist/conversion/shared/streaming-text-extractor.js +31 -38
  35. package/dist/conversion/shared/text-markup-normalizer.js +42 -27
  36. package/dist/conversion/shared/tool-filter-pipeline.js +2 -1
  37. package/dist/conversion/shared/tool-governor.js +75 -4
  38. package/dist/conversion/shared/tool-harvester.js +43 -12
  39. package/dist/conversion/shared/tool-mapping.d.ts +1 -0
  40. package/dist/conversion/shared/tool-mapping.js +33 -13
  41. package/dist/filters/index.d.ts +1 -0
  42. package/dist/filters/index.js +1 -0
  43. package/dist/filters/special/request-toolcalls-stringify.js +5 -55
  44. package/dist/filters/special/request-tools-normalize.js +14 -23
  45. package/dist/filters/special/response-apply-patch-toon-decode.d.ts +23 -0
  46. package/dist/filters/special/response-apply-patch-toon-decode.js +109 -0
  47. package/dist/filters/special/response-tool-arguments-toon-decode.d.ts +10 -0
  48. package/dist/filters/special/response-tool-arguments-toon-decode.js +55 -13
  49. package/dist/guidance/index.js +70 -27
  50. package/dist/router/virtual-router/bootstrap.js +10 -5
  51. package/dist/router/virtual-router/classifier.js +9 -4
  52. package/dist/router/virtual-router/engine-health.d.ts +22 -0
  53. package/dist/router/virtual-router/engine-health.js +423 -0
  54. package/dist/router/virtual-router/engine-logging.d.ts +20 -0
  55. package/dist/router/virtual-router/engine-logging.js +197 -0
  56. package/dist/router/virtual-router/engine-selection.d.ts +32 -0
  57. package/dist/router/virtual-router/engine-selection.js +649 -0
  58. package/dist/router/virtual-router/engine.d.ts +21 -14
  59. package/dist/router/virtual-router/engine.js +200 -523
  60. package/dist/router/virtual-router/message-utils.js +22 -0
  61. package/dist/router/virtual-router/routing-instructions.d.ts +8 -1
  62. package/dist/router/virtual-router/routing-instructions.js +137 -3
  63. package/dist/router/virtual-router/tool-signals.js +57 -11
  64. package/dist/router/virtual-router/types.d.ts +30 -0
  65. package/dist/router/virtual-router/types.js +1 -1
  66. package/dist/servertool/engine.js +3 -0
  67. package/dist/servertool/handlers/gemini-empty-reply-continue.d.ts +1 -0
  68. package/dist/servertool/handlers/gemini-empty-reply-continue.js +120 -0
  69. package/dist/servertool/handlers/iflow-model-error-retry.d.ts +1 -0
  70. package/dist/servertool/handlers/iflow-model-error-retry.js +93 -0
  71. package/dist/servertool/handlers/stop-message-auto.d.ts +1 -0
  72. package/dist/servertool/handlers/stop-message-auto.js +204 -0
  73. package/dist/servertool/handlers/vision.js +105 -7
  74. package/dist/servertool/server-side-tools.d.ts +3 -0
  75. package/dist/servertool/server-side-tools.js +29 -0
  76. package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +16 -0
  77. package/dist/tools/apply-patch-structured.d.ts +20 -0
  78. package/dist/tools/apply-patch-structured.js +239 -0
  79. package/dist/tools/tool-description-utils.d.ts +5 -0
  80. package/dist/tools/tool-description-utils.js +50 -0
  81. package/dist/tools/tool-registry.js +14 -5
  82. package/package.json +2 -2
@@ -360,7 +360,9 @@ export function buildOpenAIChatFromGeminiResponse(payload) {
360
360
  if (Number.isFinite(totalTokens))
361
361
  usage.total_tokens = totalTokens;
362
362
  const combinedText = textParts.join('\n');
363
- const normalized = combinedText.length ? normalizeChatMessageContent(combinedText) : { contentText: undefined, reasoningText: undefined };
363
+ const normalized = combinedText.length
364
+ ? normalizeChatMessageContent(combinedText)
365
+ : { contentText: undefined, reasoningText: undefined };
364
366
  const baseContent = normalized.contentText ?? combinedText ?? '';
365
367
  const toolResultBlock = toolResultTexts.length ? toolResultTexts.join('\n') : '';
366
368
  const finalContent = toolResultBlock && baseContent
@@ -525,16 +527,43 @@ export function buildGeminiFromOpenAIChat(chatResp) {
525
527
  let argsStruct;
526
528
  const rawArgs = fn.arguments;
527
529
  if (typeof rawArgs === 'string') {
530
+ const trimmed = rawArgs.trim();
531
+ if (trimmed.startsWith('{')) {
532
+ try {
533
+ const parsed = JSON.parse(rawArgs);
534
+ if (isObject(parsed)) {
535
+ argsStruct = parsed;
536
+ }
537
+ else {
538
+ argsStruct = { _raw: rawArgs };
539
+ }
540
+ }
541
+ catch {
542
+ argsStruct = { _raw: rawArgs };
543
+ }
544
+ }
545
+ else {
546
+ argsStruct = { _raw: rawArgs };
547
+ }
548
+ }
549
+ else if (isObject(rawArgs)) {
550
+ argsStruct = rawArgs;
551
+ }
552
+ else if (Array.isArray(rawArgs)) {
528
553
  try {
529
- argsStruct = JSON.parse(rawArgs);
554
+ argsStruct = { _raw: JSON.stringify(rawArgs) };
530
555
  }
531
556
  catch {
532
- argsStruct = { _raw: rawArgs };
557
+ argsStruct = { _raw: String(rawArgs) };
533
558
  }
534
559
  }
560
+ else if (rawArgs != null) {
561
+ argsStruct = { _raw: String(rawArgs) };
562
+ }
535
563
  else {
536
- argsStruct = rawArgs ?? {};
564
+ argsStruct = {};
537
565
  }
566
+ // Gemini request/response wire uses `args` for functionCall payload.
538
567
  const functionCall = { name, args: argsStruct };
539
568
  const id = typeof tc.id === 'string' ? String(tc.id) : undefined;
540
569
  if (id)
@@ -89,8 +89,9 @@ export class OpenAIOpenAIConversionCodec {
89
89
  // Response-side filters (idempotent w.r.t existing logic)
90
90
  engine.registerFilter(new ResponseToolTextCanonicalizeFilter()); // response_pre
91
91
  try {
92
- const { ResponseToolArgumentsToonDecodeFilter } = await import('../../filters/index.js');
92
+ const { ResponseToolArgumentsToonDecodeFilter, ResponseApplyPatchToonDecodeFilter } = await import('../../filters/index.js');
93
93
  engine.registerFilter(new ResponseToolArgumentsToonDecodeFilter()); // response_pre, runs before stringify
94
+ engine.registerFilter(new ResponseApplyPatchToonDecodeFilter()); // response_pre, runs before stringify
94
95
  }
95
96
  catch { /* optional */ }
96
97
  engine.registerFilter(new ResponseToolArgumentsStringifyFilter()); // response_post
@@ -147,11 +147,12 @@ export class ResponsesOpenAIConversionCodec {
147
147
  debug: { emit: () => { } }
148
148
  };
149
149
  const engine = new FilterEngine();
150
- // Response-side filters:文本标准化 → TOON decode(可选)→ shell/basics → finish_reason 不变式
150
+ // Response-side filters:文本标准化 → TOON decode(可选)→ apply_patch 结构化补丁规范化 → shell/basics → finish_reason 不变式
151
151
  engine.registerFilter(new ResponseToolTextCanonicalizeFilter()); // response_pre
152
152
  try {
153
- const { ResponseToolArgumentsToonDecodeFilter } = await import('../../filters/index.js');
153
+ const { ResponseToolArgumentsToonDecodeFilter, ResponseApplyPatchToonDecodeFilter } = await import('../../filters/index.js');
154
154
  engine.registerFilter(new ResponseToolArgumentsToonDecodeFilter()); // response_pre, runs before stringify
155
+ engine.registerFilter(new ResponseApplyPatchToonDecodeFilter()); // response_pre, runs before stringify
155
156
  }
156
157
  catch { /* optional */ }
157
158
  engine.registerFilter(new ResponseToolArgumentsStringifyFilter()); // response_post
@@ -0,0 +1,15 @@
1
+ import type { JsonObject } from '../../hub/types/json.js';
2
+ import type { AdapterContext } from '../../hub/types/chat-envelope.js';
3
+ /**
4
+ * Compat for Claude models routed via antigravity on gemini-chat.
5
+ *
6
+ * Anthropic requires tools[*].custom.input_schema to be valid JSON Schema draft 2020-12.
7
+ * We currently send OpenAI-style parameters which may not fully conform, causing upstream
8
+ * invalid_request_error on tools.N.custom.input_schema.
9
+ *
10
+ * For safety, when we detect the antigravity.*.claude-* path over gemini-chat,
11
+ * we aggressively simplify Gemini functionDeclarations[*].parameters to a minimal
12
+ * but valid object schema, letting RouteCodex govern tool semantics while keeping
13
+ * Anthropic's schema validator happy.
14
+ */
15
+ export declare function applyClaudeThinkingToolSchemaCompat(payload: JsonObject, adapterContext?: AdapterContext): JsonObject;
@@ -0,0 +1,72 @@
1
+ const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
2
+ /**
3
+ * Compat for Claude models routed via antigravity on gemini-chat.
4
+ *
5
+ * Anthropic requires tools[*].custom.input_schema to be valid JSON Schema draft 2020-12.
6
+ * We currently send OpenAI-style parameters which may not fully conform, causing upstream
7
+ * invalid_request_error on tools.N.custom.input_schema.
8
+ *
9
+ * For safety, when we detect the antigravity.*.claude-* path over gemini-chat,
10
+ * we aggressively simplify Gemini functionDeclarations[*].parameters to a minimal
11
+ * but valid object schema, letting RouteCodex govern tool semantics while keeping
12
+ * Anthropic's schema validator happy.
13
+ */
14
+ export function applyClaudeThinkingToolSchemaCompat(payload, adapterContext) {
15
+ const modelRaw = payload.model;
16
+ const modelId = typeof modelRaw === 'string' ? modelRaw.trim() : '';
17
+ // Only apply on Claude models.
18
+ // Upstream Anthropic enforces strict JSON Schema 2020-12 on custom.input_schema for these models.
19
+ if (!modelId.startsWith('claude-')) {
20
+ return payload;
21
+ }
22
+ const root = structuredClone(payload);
23
+ // Support both shapes:
24
+ // - Provider envelope: { model, request: { tools, ... } }
25
+ // - Gemini mapper request: { model, tools, ... }
26
+ const requestNode = isRecord(root.request)
27
+ ? root.request
28
+ : root;
29
+ const toolsRaw = requestNode.tools;
30
+ if (!Array.isArray(toolsRaw)) {
31
+ return root;
32
+ }
33
+ const nextTools = [];
34
+ for (const entry of toolsRaw) {
35
+ if (!isRecord(entry)) {
36
+ nextTools.push(entry);
37
+ continue;
38
+ }
39
+ const decls = Array.isArray(entry.functionDeclarations)
40
+ ? entry.functionDeclarations
41
+ : undefined;
42
+ if (!decls || !decls.length) {
43
+ // Non functionDeclarations-based tools (e.g. googleSearch) are left as-is.
44
+ nextTools.push(entry);
45
+ continue;
46
+ }
47
+ const nextDecls = [];
48
+ for (const fn of decls) {
49
+ if (!isRecord(fn)) {
50
+ nextDecls.push(fn);
51
+ continue;
52
+ }
53
+ const fnCopy = { ...fn };
54
+ // Replace parameters with a minimal, always-valid object schema.
55
+ fnCopy.parameters = {
56
+ type: 'object',
57
+ properties: {},
58
+ additionalProperties: true
59
+ };
60
+ // Drop strict flag to avoid upstream schema incompatibilities.
61
+ if (Object.prototype.hasOwnProperty.call(fnCopy, 'strict')) {
62
+ delete fnCopy.strict;
63
+ }
64
+ nextDecls.push(fnCopy);
65
+ }
66
+ nextTools.push({
67
+ functionDeclarations: nextDecls
68
+ });
69
+ }
70
+ requestNode.tools = nextTools;
71
+ return root;
72
+ }
@@ -0,0 +1,2 @@
1
+ import type { JsonObject } from '../../hub/types/json.js';
2
+ export declare function applyGlmHistoryImageTrim(payload: JsonObject): JsonObject;
@@ -0,0 +1,88 @@
1
+ const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
2
+ function shouldDropInlineImagePart(part) {
3
+ const rawType = typeof part.type === 'string' ? part.type.toLowerCase() : '';
4
+ if (rawType !== 'image' && rawType !== 'image_url' && rawType !== 'input_image') {
5
+ return false;
6
+ }
7
+ const imageUrlBlock = isRecord(part.image_url)
8
+ ? part.image_url
9
+ : part;
10
+ const urlRaw = typeof imageUrlBlock.url === 'string'
11
+ ? imageUrlBlock.url
12
+ : typeof imageUrlBlock.data === 'string'
13
+ ? imageUrlBlock.data
14
+ : '';
15
+ const url = urlRaw.trim();
16
+ if (!url) {
17
+ return false;
18
+ }
19
+ // GLM 4.7 在历史消息中携带 data:image/base64 时会返回 1210,
20
+ // 因此仅在历史中丢弃这类 inline image 片段。
21
+ return url.startsWith('data:image');
22
+ }
23
+ export function applyGlmHistoryImageTrim(payload) {
24
+ const root = structuredClone(payload);
25
+ const modelRaw = root.model;
26
+ const modelId = typeof modelRaw === 'string' ? modelRaw.trim().toLowerCase() : '';
27
+ if (!modelId || !modelId.startsWith('glm-4.7')) {
28
+ return root;
29
+ }
30
+ const messagesValue = root.messages;
31
+ if (!Array.isArray(messagesValue)) {
32
+ return root;
33
+ }
34
+ const messages = messagesValue.filter(msg => isRecord(msg));
35
+ if (!messages.length) {
36
+ return root;
37
+ }
38
+ // 仅在历史消息中进行裁剪:保留最后一条 user 完整内容。
39
+ let lastUserIdx = -1;
40
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
41
+ const msg = messages[i];
42
+ const role = typeof msg.role === 'string' ? msg.role.toLowerCase() : '';
43
+ if (role === 'user') {
44
+ lastUserIdx = i;
45
+ break;
46
+ }
47
+ }
48
+ if (lastUserIdx === -1) {
49
+ return root;
50
+ }
51
+ const nextMessages = [];
52
+ for (let i = 0; i < messages.length; i += 1) {
53
+ const msg = messages[i];
54
+ const role = typeof msg.role === 'string' ? msg.role.toLowerCase() : '';
55
+ if (i < lastUserIdx && role === 'user') {
56
+ const contentValue = msg.content;
57
+ if (!Array.isArray(contentValue)) {
58
+ nextMessages.push(msg);
59
+ continue;
60
+ }
61
+ const newContent = [];
62
+ for (const part of contentValue) {
63
+ if (!isRecord(part)) {
64
+ newContent.push(part);
65
+ continue;
66
+ }
67
+ if (shouldDropInlineImagePart(part)) {
68
+ // 丢弃历史中的 data:image/* 片段
69
+ // eslint-disable-next-line no-continue
70
+ continue;
71
+ }
72
+ newContent.push(part);
73
+ }
74
+ if (!newContent.length) {
75
+ // 历史消息只剩下 inline image 时,直接移除整条消息。
76
+ // 避免向 GLM 发送纯图片历史导致 1210。
77
+ // eslint-disable-next-line no-continue
78
+ continue;
79
+ }
80
+ const cloned = { ...msg, content: newContent };
81
+ nextMessages.push(cloned);
82
+ continue;
83
+ }
84
+ nextMessages.push(msg);
85
+ }
86
+ root.messages = nextMessages;
87
+ return root;
88
+ }
@@ -1,16 +1,17 @@
1
1
  {
2
- "id": "chat:gemini",
3
- "protocol": "gemini-chat",
4
- "request": {
5
- "mappings": [
6
- { "action": "snapshot", "phase": "compat-pre" },
7
- {
8
- "action": "gemini_web_search_request"
9
- },
10
- { "action": "snapshot", "phase": "compat-post" }
11
- ]
12
- },
13
- "response": {
14
- "mappings": []
15
- }
2
+ "id": "chat:gemini",
3
+ "protocol": "gemini-chat",
4
+ "request": {
5
+ "mappings": [
6
+ { "action": "snapshot", "phase": "compat-pre" },
7
+ { "action": "claude_thinking_tool_schema" },
8
+ {
9
+ "action": "gemini_web_search_request"
10
+ },
11
+ { "action": "snapshot", "phase": "compat-post" }
12
+ ]
13
+ },
14
+ "response": {
15
+ "mappings": []
16
+ }
16
17
  }