@jsonstudio/llms 0.6.954 → 0.6.1164

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 (130) hide show
  1. package/dist/conversion/hub/operation-table/operation-table-runner.d.ts +18 -0
  2. package/dist/conversion/hub/operation-table/operation-table-runner.js +158 -0
  3. package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.d.ts +8 -0
  4. package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +303 -0
  5. package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.d.ts +8 -0
  6. package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.js +413 -0
  7. package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.d.ts +7 -0
  8. package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +841 -0
  9. package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.d.ts +21 -0
  10. package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +535 -0
  11. package/dist/conversion/hub/ops/operations.d.ts +19 -0
  12. package/dist/conversion/hub/ops/operations.js +126 -0
  13. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +9 -0
  14. package/dist/conversion/hub/pipeline/hub-pipeline.js +489 -19
  15. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +6 -0
  16. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +11 -0
  17. package/dist/conversion/hub/policy/policy-engine.js +41 -9
  18. package/dist/conversion/hub/policy/protocol-spec.d.ts +25 -0
  19. package/dist/conversion/hub/policy/protocol-spec.js +73 -23
  20. package/dist/conversion/hub/process/chat-process.js +252 -41
  21. package/dist/conversion/hub/response/provider-response.js +175 -2
  22. package/dist/conversion/hub/response/response-runtime.js +1 -1
  23. package/dist/conversion/hub/semantic-mappers/anthropic-mapper.d.ts +1 -8
  24. package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +1 -365
  25. package/dist/conversion/hub/semantic-mappers/chat-mapper.d.ts +1 -8
  26. package/dist/conversion/hub/semantic-mappers/chat-mapper.js +1 -467
  27. package/dist/conversion/hub/semantic-mappers/gemini-mapper.d.ts +1 -7
  28. package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +1 -903
  29. package/dist/conversion/hub/semantic-mappers/responses-mapper.d.ts +1 -21
  30. package/dist/conversion/hub/semantic-mappers/responses-mapper.js +1 -593
  31. package/dist/conversion/hub/tool-surface/tool-surface-engine.d.ts +18 -0
  32. package/dist/conversion/hub/tool-surface/tool-surface-engine.js +571 -0
  33. package/dist/conversion/responses/responses-openai-bridge.js +14 -2
  34. package/dist/conversion/shared/bridge-message-utils.js +2 -8
  35. package/dist/conversion/shared/bridge-policies.js +5 -105
  36. package/dist/conversion/shared/gemini-tool-utils.js +89 -15
  37. package/dist/conversion/shared/protocol-field-allowlists.d.ts +7 -0
  38. package/dist/conversion/shared/protocol-field-allowlists.js +145 -0
  39. package/dist/conversion/shared/reasoning-tool-normalizer.js +4 -2
  40. package/dist/conversion/shared/snapshot-hooks.js +166 -3
  41. package/dist/conversion/shared/text-markup-normalizer.d.ts +2 -0
  42. package/dist/conversion/shared/text-markup-normalizer.js +345 -9
  43. package/dist/conversion/shared/thought-signature-validator.d.ts +52 -0
  44. package/dist/conversion/shared/thought-signature-validator.js +170 -0
  45. package/dist/conversion/shared/tool-argument-repairer.d.ts +39 -0
  46. package/dist/conversion/shared/tool-argument-repairer.js +56 -0
  47. package/dist/conversion/shared/tool-call-id-manager.d.ts +113 -0
  48. package/dist/conversion/shared/tool-call-id-manager.js +231 -0
  49. package/dist/conversion/shared/tool-canonicalizer.js +2 -11
  50. package/dist/router/virtual-router/bootstrap.js +54 -5
  51. package/dist/router/virtual-router/engine-selection.js +132 -42
  52. package/dist/router/virtual-router/engine.d.ts +3 -0
  53. package/dist/router/virtual-router/engine.js +142 -33
  54. package/dist/router/virtual-router/health-weighted.d.ts +25 -0
  55. package/dist/router/virtual-router/health-weighted.js +63 -0
  56. package/dist/router/virtual-router/load-balancer.d.ts +2 -0
  57. package/dist/router/virtual-router/load-balancer.js +45 -16
  58. package/dist/router/virtual-router/routing-instructions.js +17 -1
  59. package/dist/router/virtual-router/sticky-session-store.js +136 -24
  60. package/dist/router/virtual-router/stop-message-file-resolver.d.ts +1 -0
  61. package/dist/router/virtual-router/stop-message-file-resolver.js +74 -0
  62. package/dist/router/virtual-router/stop-message-state-sync.d.ts +15 -0
  63. package/dist/router/virtual-router/stop-message-state-sync.js +57 -0
  64. package/dist/router/virtual-router/types.d.ts +70 -0
  65. package/dist/servertool/clock/config.d.ts +7 -0
  66. package/dist/servertool/clock/config.js +27 -0
  67. package/dist/servertool/clock/daemon.d.ts +3 -0
  68. package/dist/servertool/clock/daemon.js +79 -0
  69. package/dist/servertool/clock/io.d.ts +2 -0
  70. package/dist/servertool/clock/io.js +13 -0
  71. package/dist/servertool/clock/paths.d.ts +4 -0
  72. package/dist/servertool/clock/paths.js +25 -0
  73. package/dist/servertool/clock/session-store.d.ts +3 -0
  74. package/dist/servertool/clock/session-store.js +56 -0
  75. package/dist/servertool/clock/state.d.ts +5 -0
  76. package/dist/servertool/clock/state.js +62 -0
  77. package/dist/servertool/clock/task-store.d.ts +5 -0
  78. package/dist/servertool/clock/task-store.js +4 -0
  79. package/dist/servertool/clock/tasks.d.ts +17 -0
  80. package/dist/servertool/clock/tasks.js +221 -0
  81. package/dist/servertool/clock/types.d.ts +36 -0
  82. package/dist/servertool/clock/types.js +1 -0
  83. package/dist/servertool/engine.d.ts +2 -0
  84. package/dist/servertool/engine.js +161 -7
  85. package/dist/servertool/followup-shadow.d.ts +16 -0
  86. package/dist/servertool/followup-shadow.js +145 -0
  87. package/dist/servertool/handlers/apply-patch-guard.js +1 -265
  88. package/dist/servertool/handlers/clock-auto.d.ts +1 -0
  89. package/dist/servertool/handlers/clock-auto.js +160 -0
  90. package/dist/servertool/handlers/clock.d.ts +1 -0
  91. package/dist/servertool/handlers/clock.js +197 -0
  92. package/dist/servertool/handlers/exec-command-guard.js +7 -555
  93. package/dist/servertool/handlers/followup-request-builder.d.ts +15 -7
  94. package/dist/servertool/handlers/followup-request-builder.js +248 -28
  95. package/dist/servertool/handlers/gemini-empty-reply-continue.js +62 -169
  96. package/dist/servertool/handlers/iflow-model-error-retry.js +18 -28
  97. package/dist/servertool/handlers/recursive-detection-guard.d.ts +1 -0
  98. package/dist/servertool/handlers/recursive-detection-guard.js +333 -0
  99. package/dist/servertool/handlers/stop-message-auto.js +47 -175
  100. package/dist/servertool/handlers/vision.d.ts +7 -1
  101. package/dist/servertool/handlers/vision.js +61 -117
  102. package/dist/servertool/handlers/web-search.d.ts +7 -1
  103. package/dist/servertool/handlers/web-search.js +122 -105
  104. package/dist/servertool/reenter-backend.d.ts +23 -0
  105. package/dist/servertool/reenter-backend.js +18 -0
  106. package/dist/servertool/server-side-tools.d.ts +3 -2
  107. package/dist/servertool/server-side-tools.js +64 -10
  108. package/dist/servertool/types.d.ts +92 -3
  109. package/dist/sse/json-to-sse/event-generators/responses.js +3 -21
  110. package/dist/sse/shared/serializers/responses-event-serializer.d.ts +8 -0
  111. package/dist/sse/shared/serializers/responses-event-serializer.js +19 -0
  112. package/dist/sse/shared/writer.js +24 -7
  113. package/dist/tools/apply-patch/execution-capturer.js +3 -1
  114. package/dist/tools/apply-patch/json/parse-loose.d.ts +3 -0
  115. package/dist/tools/apply-patch/json/parse-loose.js +139 -0
  116. package/dist/tools/apply-patch/patch-text/context-diff.d.ts +1 -0
  117. package/dist/tools/apply-patch/patch-text/context-diff.js +173 -0
  118. package/dist/tools/apply-patch/patch-text/git-diff.d.ts +1 -0
  119. package/dist/tools/apply-patch/patch-text/git-diff.js +138 -0
  120. package/dist/tools/apply-patch/patch-text/looks-like-patch.d.ts +1 -0
  121. package/dist/tools/apply-patch/patch-text/looks-like-patch.js +13 -0
  122. package/dist/tools/apply-patch/patch-text/normalize.d.ts +3 -0
  123. package/dist/tools/apply-patch/patch-text/normalize.js +262 -0
  124. package/dist/tools/apply-patch/structured/coercion.d.ts +3 -0
  125. package/dist/tools/apply-patch/structured/coercion.js +82 -0
  126. package/dist/tools/apply-patch/validation/shared.d.ts +3 -0
  127. package/dist/tools/apply-patch/validation/shared.js +6 -0
  128. package/dist/tools/apply-patch/validator.d.ts +2 -2
  129. package/dist/tools/apply-patch/validator.js +6 -556
  130. package/package.json +1 -1
@@ -3,6 +3,8 @@ export type ToolCallLite = {
3
3
  name: string;
4
4
  args: string;
5
5
  };
6
+ export declare function extractParameterXmlToolsFromText(text: string): ToolCallLite[] | null;
7
+ export declare function extractBareExecCommandFromText(text: string): ToolCallLite[] | null;
6
8
  export declare function extractApplyPatchCallsFromText(text: string): ToolCallLite[] | null;
7
9
  export declare function extractExecuteBlocksFromText(text: string): ToolCallLite[] | null;
8
10
  export declare function extractXMLToolCallsFromText(text: string): ToolCallLite[] | null;
@@ -6,6 +6,8 @@ import { isStructuredApplyPatchPayload } from '../../tools/apply-patch-structure
6
6
  // stray markup or free-form text as JSON keys (reduces false positives).
7
7
  const KNOWN_TOOLS = new Set([
8
8
  'shell',
9
+ 'exec_command',
10
+ 'write_stdin',
9
11
  'apply_patch',
10
12
  'update_plan',
11
13
  'view_image',
@@ -17,7 +19,22 @@ const KNOWN_TOOLS = new Set([
17
19
  ]);
18
20
  const ALLOWED_KEYS = {
19
21
  shell: new Set(['command', 'justification', 'timeout_ms', 'with_escalated_permissions', 'workdir']),
20
- apply_patch: new Set(['file', 'instructions', 'changes']),
22
+ exec_command: new Set([
23
+ 'cmd',
24
+ 'command',
25
+ 'workdir',
26
+ 'justification',
27
+ 'login',
28
+ 'tty',
29
+ 'shell',
30
+ 'yield_time_ms',
31
+ 'max_output_tokens',
32
+ 'sandbox_permissions',
33
+ 'timeout_ms'
34
+ ]),
35
+ write_stdin: new Set(['session_id', 'chars', 'text', 'yield_time_ms', 'max_output_tokens']),
36
+ // apply_patch supports both freeform patch text (patch/input/instructions/text) and structured payloads (file/changes)
37
+ apply_patch: new Set(['patch', 'input', 'instructions', 'text', 'file', 'changes']),
21
38
  update_plan: new Set(['explanation', 'plan']),
22
39
  view_image: new Set(['path']),
23
40
  list_mcp_resources: new Set(['server', 'cursor', 'filter', 'root']),
@@ -38,13 +55,40 @@ function normalizeKey(raw) {
38
55
  return String(raw || '');
39
56
  }
40
57
  }
58
+ function readDefaultWorkdirFromEnv() {
59
+ try {
60
+ const env = String(process?.env?.ROUTECODEX_WORKDIR || '').trim() ||
61
+ String(process?.env?.RCC_WORKDIR || '').trim() ||
62
+ String(process?.env?.CLAUDE_WORKDIR || '').trim();
63
+ return env ? env : undefined;
64
+ }
65
+ catch {
66
+ return undefined;
67
+ }
68
+ }
41
69
  function filterArgsForTool(name, args) {
42
70
  try {
43
71
  const nm = String(name || '').toLowerCase();
44
72
  const allow = ALLOWED_KEYS[nm];
45
73
  if (!allow) {
46
74
  // Unknown tool; be conservative: keep only simple safe keys if present
47
- const safe = new Set(['command', 'patch', 'plan', 'explanation', 'path', 'server', 'uri', 'cursor', 'filter', 'root']);
75
+ const safe = new Set([
76
+ 'cmd',
77
+ 'command',
78
+ 'workdir',
79
+ 'patch',
80
+ 'input',
81
+ 'instructions',
82
+ 'text',
83
+ 'plan',
84
+ 'explanation',
85
+ 'path',
86
+ 'server',
87
+ 'uri',
88
+ 'cursor',
89
+ 'filter',
90
+ 'root'
91
+ ]);
48
92
  const out = {};
49
93
  for (const [k, v] of Object.entries(args || {})) {
50
94
  const key = normalizeKey(k);
@@ -102,6 +146,266 @@ function extractStructuredApplyPatchPayloads(text) {
102
146
  catch { /* ignore */ }
103
147
  return payloads;
104
148
  }
149
+ function tryParseJsonValue(text) {
150
+ try {
151
+ const trimmed = String(text || '').trim();
152
+ if (!trimmed)
153
+ return null;
154
+ if (!(trimmed.startsWith('{') || trimmed.startsWith('[') || trimmed.startsWith('"'))) {
155
+ return null;
156
+ }
157
+ return JSON.parse(trimmed);
158
+ }
159
+ catch {
160
+ return null;
161
+ }
162
+ }
163
+ function coerceCommandValueToString(value) {
164
+ try {
165
+ if (value === null || value === undefined)
166
+ return '';
167
+ if (typeof value === 'string')
168
+ return value.trim();
169
+ if (Array.isArray(value)) {
170
+ return value.map((v) => String(v)).join(' ').trim();
171
+ }
172
+ if (typeof value === 'object') {
173
+ const rec = value;
174
+ const direct = typeof rec.cmd === 'string'
175
+ ? String(rec.cmd)
176
+ : typeof rec.command === 'string'
177
+ ? String(rec.command)
178
+ : '';
179
+ if (direct.trim().length)
180
+ return direct.trim();
181
+ try {
182
+ return JSON.stringify(value);
183
+ }
184
+ catch {
185
+ return String(value);
186
+ }
187
+ }
188
+ return String(value).trim();
189
+ }
190
+ catch {
191
+ return '';
192
+ }
193
+ }
194
+ function looksLikeBrokenToolMarkup(text) {
195
+ try {
196
+ const t = String(text || '');
197
+ if (!t)
198
+ return false;
199
+ return (t.includes('<exec_command') ||
200
+ t.includes('<parameter') ||
201
+ t.includes('<tool_call') ||
202
+ t.includes('</tool_call') ||
203
+ t.includes('<arg_value') ||
204
+ t.includes('</arg_value') ||
205
+ t.includes('</func_call') ||
206
+ /\bRan\b\s+\[/.test(t));
207
+ }
208
+ catch {
209
+ return false;
210
+ }
211
+ }
212
+ function stripTrailingXmlGarbage(line) {
213
+ try {
214
+ const idx = line.indexOf('<');
215
+ if (idx >= 0) {
216
+ return line.slice(0, idx).trim();
217
+ }
218
+ return line.trim();
219
+ }
220
+ catch {
221
+ return String(line || '').trim();
222
+ }
223
+ }
224
+ function normalizePossibleCommandLine(raw) {
225
+ try {
226
+ let line = String(raw || '');
227
+ line = line.replace(/^\s*(?:[•*+-]\s*)?/, '');
228
+ line = line.replace(/^\s*\$\s*/, '');
229
+ return stripTrailingXmlGarbage(line);
230
+ }
231
+ catch {
232
+ return '';
233
+ }
234
+ }
235
+ function looksLikeShellCommand(line) {
236
+ try {
237
+ const t = String(line || '').trim();
238
+ if (!t)
239
+ return false;
240
+ if (t.startsWith('```') || t.startsWith('>'))
241
+ return false;
242
+ if (t.startsWith('zsh:') || t.startsWith('bash:'))
243
+ return false;
244
+ // Conservative allowlist: commands commonly emitted by Codex/Claude in tool-mode.
245
+ return /^(?:rg|ls|cat|node|npm|pnpm|yarn|git|find|sed|head|tail|wc|mkdir|rm|cp|mv|pwd)\b/.test(t);
246
+ }
247
+ catch {
248
+ return false;
249
+ }
250
+ }
251
+ // Extract Anthropic/GLM-style XML tool tags that use <parameter name="...">...</parameter>, e.g.:
252
+ // <exec_command>
253
+ // <parameter name="command">["cd","/repo","&&","npm","run","build"]</parameter>
254
+ // <parameter name="workdir">/repo</parameter>
255
+ // </exec_command>
256
+ export function extractParameterXmlToolsFromText(text) {
257
+ try {
258
+ if (typeof text !== 'string' || !text)
259
+ return null;
260
+ const out = [];
261
+ // Some providers/models emit mismatched closing tags like </func_call>; accept a small set of known variants.
262
+ const toolRe = /<\s*(exec_command)\s*>([\s\S]*?)<\/\s*(?:\1|func_call|tool_call)\s*>/gi;
263
+ let tm;
264
+ while ((tm = toolRe.exec(text)) !== null) {
265
+ const rawName = (tm[1] || '').trim();
266
+ const inner = (tm[2] || '').trim();
267
+ if (!rawName || !inner)
268
+ continue;
269
+ const lname = rawName.toLowerCase();
270
+ const argsObj = {};
271
+ const paramRe = /<\s*parameter\s+name\s*=\s*"([^"]+)"\s*>([\s\S]*?)<\/\s*parameter\s*>/gi;
272
+ let pm;
273
+ while ((pm = paramRe.exec(inner)) !== null) {
274
+ const rawKey = (pm[1] || '').trim();
275
+ const key = normalizeKey(rawKey);
276
+ if (!key)
277
+ continue;
278
+ const rawVal = (pm[2] || '').trim();
279
+ if (!rawVal)
280
+ continue;
281
+ const parsed = tryParseJsonValue(rawVal);
282
+ if (key.toLowerCase() === 'command' || key.toLowerCase() === 'cmd') {
283
+ const cmd = coerceCommandValueToString(parsed ?? rawVal);
284
+ if (cmd.trim().length) {
285
+ argsObj.cmd = cmd;
286
+ argsObj.command = cmd;
287
+ }
288
+ continue;
289
+ }
290
+ if (key.toLowerCase() === 'workdir') {
291
+ argsObj.workdir = rawVal;
292
+ continue;
293
+ }
294
+ argsObj[key] = parsed ?? rawVal;
295
+ }
296
+ const filtered = filterArgsForTool(lname, argsObj);
297
+ if (!filtered || typeof filtered !== 'object' || Array.isArray(filtered))
298
+ continue;
299
+ const cmd = typeof filtered.cmd === 'string' ? String(filtered.cmd).trim() : '';
300
+ if (!cmd)
301
+ continue;
302
+ // If caller didn't provide workdir, fallback to a conservative env-derived workdir.
303
+ try {
304
+ if (!filtered.workdir) {
305
+ const envWorkdir = readDefaultWorkdirFromEnv();
306
+ if (envWorkdir)
307
+ filtered.workdir = envWorkdir;
308
+ }
309
+ }
310
+ catch {
311
+ /* ignore */
312
+ }
313
+ let argsStr = '{}';
314
+ try {
315
+ argsStr = JSON.stringify(filtered);
316
+ }
317
+ catch {
318
+ argsStr = '{}';
319
+ }
320
+ out.push({
321
+ id: `call_${Math.random().toString(36).slice(2, 10)}`,
322
+ name: lname,
323
+ args: argsStr
324
+ });
325
+ }
326
+ return out.length ? out : null;
327
+ }
328
+ catch {
329
+ return null;
330
+ }
331
+ }
332
+ function tryParseJsonArrayOnLine(line) {
333
+ try {
334
+ const trimmed = String(line || '').trim();
335
+ if (!trimmed.startsWith('[') || !trimmed.endsWith(']'))
336
+ return null;
337
+ const parsed = JSON.parse(trimmed);
338
+ if (!Array.isArray(parsed))
339
+ return null;
340
+ const out = [];
341
+ for (const entry of parsed) {
342
+ if (entry == null)
343
+ continue;
344
+ out.push(String(entry));
345
+ }
346
+ return out.length ? out : null;
347
+ }
348
+ catch {
349
+ return null;
350
+ }
351
+ }
352
+ export function extractBareExecCommandFromText(text) {
353
+ try {
354
+ if (typeof text !== 'string' || !text)
355
+ return null;
356
+ // Only attempt this when we see clear evidence of a broken tool markup (avoid turning examples into tool calls).
357
+ if (!looksLikeBrokenToolMarkup(text))
358
+ return null;
359
+ const candidates = [];
360
+ const lines = text.split(/\r?\n/);
361
+ for (let i = 0; i < lines.length; i += 1) {
362
+ const raw = String(lines[i] || '');
363
+ const line = normalizePossibleCommandLine(raw);
364
+ if (!line)
365
+ continue;
366
+ // Pattern A: "Ran [\"ls\", \"-l\", ...]"
367
+ const ran = line.match(/\bRan\b\s+(\[[\s\S]*\])\s*$/i);
368
+ if (ran && ran[1]) {
369
+ const arr = tryParseJsonArrayOnLine(ran[1]);
370
+ if (arr) {
371
+ candidates.push({ cmd: arr.join(' ').trim() });
372
+ continue;
373
+ }
374
+ }
375
+ // Pattern B: a single-line shell command (rg/ls/node/...)
376
+ if (looksLikeShellCommand(line)) {
377
+ candidates.push({ cmd: line.trim() });
378
+ continue;
379
+ }
380
+ }
381
+ if (!candidates.length)
382
+ return null;
383
+ const picked = candidates[candidates.length - 1];
384
+ const argsObj = { cmd: picked.cmd };
385
+ const envWorkdir = readDefaultWorkdirFromEnv();
386
+ if (envWorkdir) {
387
+ argsObj.workdir = envWorkdir;
388
+ }
389
+ const filtered = filterArgsForTool('exec_command', argsObj);
390
+ let argsStr = '{}';
391
+ try {
392
+ argsStr = JSON.stringify(filtered);
393
+ }
394
+ catch {
395
+ argsStr = '{}';
396
+ }
397
+ return [
398
+ {
399
+ id: `call_${Math.random().toString(36).slice(2, 10)}`,
400
+ name: 'exec_command',
401
+ args: argsStr
402
+ }
403
+ ];
404
+ }
405
+ catch {
406
+ return null;
407
+ }
408
+ }
105
409
  export function extractApplyPatchCallsFromText(text) {
106
410
  try {
107
411
  if (typeof text !== 'string' || !text)
@@ -392,14 +696,46 @@ export function normalizeAssistantTextToToolCalls(message) {
392
696
  if (Array.isArray(message.tool_calls) && message.tool_calls.length)
393
697
  return message;
394
698
  const content = message.content;
395
- const text = typeof content === 'string' ? content : null;
396
- if (!text)
699
+ const candidates = [];
700
+ if (typeof content === 'string') {
701
+ candidates.push(content);
702
+ }
703
+ else if (Array.isArray(content)) {
704
+ for (const part of content) {
705
+ if (!part || typeof part !== 'object')
706
+ continue;
707
+ const p = part;
708
+ if (typeof p.text === 'string' && p.text.trim()) {
709
+ candidates.push(p.text);
710
+ continue;
711
+ }
712
+ if (typeof p.content === 'string' && p.content.trim()) {
713
+ candidates.push(p.content);
714
+ continue;
715
+ }
716
+ }
717
+ }
718
+ if (typeof message.reasoning === 'string' && message.reasoning.trim()) {
719
+ candidates.push(String(message.reasoning));
720
+ }
721
+ if (typeof message.thinking === 'string' && message.thinking.trim()) {
722
+ candidates.push(String(message.thinking));
723
+ }
724
+ if (!candidates.length)
397
725
  return message;
398
- // Order: xml-like <tool_call> → apply_patch → execute blocks → 简单 XML 工具(list_directory 等)
399
- const calls = (extractXMLToolCallsFromText(text) ||
400
- extractApplyPatchCallsFromText(text) ||
401
- extractExecuteBlocksFromText(text) ||
402
- extractSimpleXmlToolsFromText(text));
726
+ let calls = null;
727
+ for (const text of candidates) {
728
+ // Order: xml-like <tool_call> → apply_patch → execute blocks → 简单 XML 工具(list_directory 等) → bare command salvage
729
+ calls =
730
+ extractParameterXmlToolsFromText(text) ||
731
+ extractXMLToolCallsFromText(text) ||
732
+ extractApplyPatchCallsFromText(text) ||
733
+ extractExecuteBlocksFromText(text) ||
734
+ extractSimpleXmlToolsFromText(text) ||
735
+ extractBareExecCommandFromText(text);
736
+ if (calls && calls.length)
737
+ break;
738
+ }
403
739
  if (calls && calls.length) {
404
740
  const toolCalls = calls.map((c) => ({ id: c.id, type: 'function', function: { name: c.name, arguments: c.args } }));
405
741
  const copy = { ...message };
@@ -0,0 +1,52 @@
1
+ /**
2
+ * ThoughtSignature Validator
3
+ *
4
+ * 提供严格的 thoughtSignature 验证功能,用于 Claude/Gemini thinking 块的签名验证。
5
+ * 参考 gcli2api 的实现,确保与上游 API 的兼容性。
6
+ */
7
+ import type { JsonObject, JsonValue } from '../hub/types/json.js';
8
+ export interface ThoughtSignatureValidationOptions {
9
+ /**
10
+ * 最小签名长度(默认 10 个字符)
11
+ */
12
+ minLength?: number;
13
+ /**
14
+ * 是否允许空 thinking + 任意签名(trailing signature case)
15
+ * 默认 true
16
+ */
17
+ allowEmptyThinkingWithSignature?: boolean;
18
+ /**
19
+ * 是否在验证失败时将 thinking 转换为文本
20
+ * 默认 true
21
+ */
22
+ convertToTextOnFailure?: boolean;
23
+ }
24
+ /**
25
+ * 检查 thinking 块是否有有效的 thoughtSignature
26
+ *
27
+ * @param block - thinking 块对象
28
+ * @param options - 验证选项
29
+ * @returns 是否有效
30
+ */
31
+ export declare function hasValidThoughtSignature(block: unknown, options?: ThoughtSignatureValidationOptions): boolean;
32
+ /**
33
+ * 清理 thinking 块,移除额外字段,保留有效签名
34
+ *
35
+ * @param block - thinking 块对象
36
+ * @returns 清理后的块
37
+ */
38
+ export declare function sanitizeThinkingBlock(block: unknown): JsonObject;
39
+ /**
40
+ * 过滤消息中的无效 thinking 块
41
+ *
42
+ * @param messages - Anthropic messages 列表(会被修改)
43
+ * @param options - 验证选项
44
+ */
45
+ export declare function filterInvalidThinkingBlocks(messages: JsonValue[], options?: ThoughtSignatureValidationOptions): void;
46
+ /**
47
+ * 移除末尾未签名的 thinking 块
48
+ *
49
+ * @param blocks - content blocks 列表(会被修改)
50
+ * @param options - 验证选项
51
+ */
52
+ export declare function removeTrailingUnsignedThinkingBlocks(blocks: JsonValue[], options?: ThoughtSignatureValidationOptions): void;
@@ -0,0 +1,170 @@
1
+ /**
2
+ * ThoughtSignature Validator
3
+ *
4
+ * 提供严格的 thoughtSignature 验证功能,用于 Claude/Gemini thinking 块的签名验证。
5
+ * 参考 gcli2api 的实现,确保与上游 API 的兼容性。
6
+ */
7
+ const DEFAULT_OPTIONS = {
8
+ minLength: 10,
9
+ allowEmptyThinkingWithSignature: true,
10
+ convertToTextOnFailure: true
11
+ };
12
+ /**
13
+ * 检查 thinking 块是否有有效的 thoughtSignature
14
+ *
15
+ * @param block - thinking 块对象
16
+ * @param options - 验证选项
17
+ * @returns 是否有效
18
+ */
19
+ export function hasValidThoughtSignature(block, options) {
20
+ if (!block || typeof block !== 'object') {
21
+ return true; // 非 thinking 块默认有效
22
+ }
23
+ const obj = block;
24
+ const blockType = obj.type;
25
+ if (blockType !== 'thinking' && blockType !== 'reasoning' && blockType !== 'redacted_thinking') {
26
+ return true; // 非 thinking 块默认有效
27
+ }
28
+ const opts = { ...DEFAULT_OPTIONS, ...options };
29
+ const thinking = String(obj.thinking || obj.text || '');
30
+ const signature = coerceThoughtSignature(obj.thoughtSignature || obj.signature);
31
+ // 空 thinking + 任意签名 = 有效 (trailing signature case)
32
+ if (!thinking.trim() && signature !== undefined && opts.allowEmptyThinkingWithSignature) {
33
+ return true;
34
+ }
35
+ // 有内容 + 足够长度的 signature = 有效
36
+ if (signature && signature.length >= opts.minLength) {
37
+ return true;
38
+ }
39
+ return false;
40
+ }
41
+ /**
42
+ * 清理 thinking 块,移除额外字段,保留有效签名
43
+ *
44
+ * @param block - thinking 块对象
45
+ * @returns 清理后的块
46
+ */
47
+ export function sanitizeThinkingBlock(block) {
48
+ if (!block || typeof block !== 'object') {
49
+ return block;
50
+ }
51
+ const obj = block;
52
+ const blockType = obj.type;
53
+ if (blockType !== 'thinking' && blockType !== 'reasoning' && blockType !== 'redacted_thinking') {
54
+ return obj;
55
+ }
56
+ const sanitized = {
57
+ type: blockType,
58
+ thinking: String(obj.thinking || obj.text || '')
59
+ };
60
+ const signature = coerceThoughtSignature(obj.thoughtSignature || obj.signature);
61
+ if (signature) {
62
+ sanitized.thoughtSignature = signature;
63
+ }
64
+ return sanitized;
65
+ }
66
+ /**
67
+ * 过滤消息中的无效 thinking 块
68
+ *
69
+ * @param messages - Anthropic messages 列表(会被修改)
70
+ * @param options - 验证选项
71
+ */
72
+ export function filterInvalidThinkingBlocks(messages, options) {
73
+ const opts = { ...DEFAULT_OPTIONS, ...options };
74
+ let totalFiltered = 0;
75
+ for (const msg of messages) {
76
+ if (!msg || typeof msg !== 'object')
77
+ continue;
78
+ const msgObj = msg;
79
+ const role = msgObj.role;
80
+ // 只处理 assistant 和 model 消息
81
+ if (role !== 'assistant' && role !== 'model')
82
+ continue;
83
+ const content = msgObj.content;
84
+ if (!Array.isArray(content))
85
+ continue;
86
+ const originalLen = content.length;
87
+ const newBlocks = [];
88
+ for (const block of content) {
89
+ if (!block || typeof block !== 'object') {
90
+ newBlocks.push(block);
91
+ continue;
92
+ }
93
+ const blockType = block.type;
94
+ if (blockType !== 'thinking' && blockType !== 'reasoning' && blockType !== 'redacted_thinking') {
95
+ newBlocks.push(block);
96
+ continue;
97
+ }
98
+ // 检查 thinking 块的有效性
99
+ if (hasValidThoughtSignature(block, opts)) {
100
+ // 有效签名,清理后保留
101
+ newBlocks.push(sanitizeThinkingBlock(block));
102
+ }
103
+ else {
104
+ // 无效签名
105
+ const thinkingText = String(block.thinking || block.text || '');
106
+ if (thinkingText.trim() && opts.convertToTextOnFailure) {
107
+ // 有内容,转换为文本
108
+ newBlocks.push({ type: 'text', text: thinkingText });
109
+ }
110
+ // 否则丢弃(空 thinking + 无效签名)
111
+ }
112
+ }
113
+ msgObj.content = newBlocks;
114
+ const filteredCount = originalLen - newBlocks.length;
115
+ totalFiltered += filteredCount;
116
+ // 如果过滤后为空,添加一个空文本块以保持消息有效
117
+ if (!newBlocks.length) {
118
+ msgObj.content = [{ type: 'text', text: '' }];
119
+ }
120
+ }
121
+ if (totalFiltered > 0) {
122
+ // 可以在这里添加日志记录
123
+ console.debug(`[ThoughtSignatureValidator] Filtered ${totalFiltered} invalid thinking block(s)`);
124
+ }
125
+ }
126
+ /**
127
+ * 移除末尾未签名的 thinking 块
128
+ *
129
+ * @param blocks - content blocks 列表(会被修改)
130
+ * @param options - 验证选项
131
+ */
132
+ export function removeTrailingUnsignedThinkingBlocks(blocks, options) {
133
+ if (!Array.isArray(blocks) || !blocks.length)
134
+ return;
135
+ const opts = { ...DEFAULT_OPTIONS, ...options };
136
+ let end_index = blocks.length;
137
+ // 从末尾向前查找第一个有效签名的 thinking 块
138
+ for (let i = blocks.length - 1; i >= 0; i--) {
139
+ const block = blocks[i];
140
+ if (!block || typeof block !== 'object')
141
+ continue;
142
+ const blockType = block.type;
143
+ if (blockType === 'thinking' || blockType === 'reasoning' || blockType === 'redacted_thinking') {
144
+ if (!hasValidThoughtSignature(block, opts)) {
145
+ end_index = i;
146
+ }
147
+ else {
148
+ break; // 遇到有效签名的 thinking 块,停止
149
+ }
150
+ }
151
+ else {
152
+ break; // 遇到非 thinking 块,停止
153
+ }
154
+ }
155
+ // 移除末尾未签名的 thinking 块
156
+ if (end_index < blocks.length) {
157
+ const removed = blocks.length - end_index;
158
+ blocks.splice(end_index);
159
+ console.debug(`[ThoughtSignatureValidator] Removed ${removed} trailing unsigned thinking block(s)`);
160
+ }
161
+ }
162
+ /**
163
+ * 提取并强制转换 thoughtSignature
164
+ */
165
+ function coerceThoughtSignature(value) {
166
+ if (typeof value === 'string' && value.trim().length) {
167
+ return value.trim();
168
+ }
169
+ return undefined;
170
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Tool Argument Repairer
3
+ *
4
+ * Tool calls require `function.arguments` to be a JSON string. Models often emit JSON-ish
5
+ * payloads (single quotes, fenced blocks, comments, trailing commas, etc.).
6
+ *
7
+ * This module provides a deterministic, best-effort repair surface that returns a JSON string
8
+ * or `{}` and never throws.
9
+ */
10
+ export interface RepairResult {
11
+ repaired: string;
12
+ success: boolean;
13
+ error?: string;
14
+ }
15
+ export declare class ToolArgumentRepairer {
16
+ repairToString(args: unknown): string;
17
+ validateAndRepair(toolName: string, args: unknown): RepairResult;
18
+ /**
19
+ * 批量修复工具参数
20
+ *
21
+ * @param toolCalls - 工具调用列表
22
+ * @returns 修复后的工具调用列表
23
+ */
24
+ repairToolCalls(toolCalls: Array<{
25
+ name?: string;
26
+ arguments?: unknown;
27
+ }>): Array<{
28
+ name?: string;
29
+ arguments: string;
30
+ }>;
31
+ }
32
+ /**
33
+ * 快捷函数:修复工具参数
34
+ */
35
+ export declare function repairToolArguments(args: unknown): string;
36
+ /**
37
+ * 快捷函数:验证并修复工具参数
38
+ */
39
+ export declare function validateToolArguments(toolName: string, args: unknown): RepairResult;