@jsonstudio/llms 0.6.631 → 0.6.743

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 (64) hide show
  1. package/dist/conversion/codecs/anthropic-openai-codec.js +0 -5
  2. package/dist/conversion/codecs/openai-openai-codec.js +0 -6
  3. package/dist/conversion/codecs/responses-openai-codec.js +1 -7
  4. package/dist/conversion/hub/node-support.js +5 -4
  5. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +14 -1
  6. package/dist/conversion/hub/pipeline/hub-pipeline.js +82 -18
  7. package/dist/conversion/hub/pipeline/session-identifiers.js +132 -2
  8. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +130 -15
  9. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js +47 -0
  10. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js +4 -2
  11. package/dist/conversion/hub/process/chat-process.js +2 -0
  12. package/dist/conversion/hub/response/provider-response.js +6 -1
  13. package/dist/conversion/hub/snapshot-recorder.js +8 -1
  14. package/dist/conversion/pipeline/codecs/v2/shared/openai-chat-helpers.js +0 -7
  15. package/dist/conversion/responses/responses-openai-bridge.js +47 -7
  16. package/dist/conversion/shared/compaction-detect.d.ts +2 -0
  17. package/dist/conversion/shared/compaction-detect.js +53 -0
  18. package/dist/conversion/shared/errors.d.ts +1 -1
  19. package/dist/conversion/shared/reasoning-tool-normalizer.js +7 -0
  20. package/dist/conversion/shared/snapshot-hooks.d.ts +2 -0
  21. package/dist/conversion/shared/snapshot-hooks.js +180 -4
  22. package/dist/conversion/shared/snapshot-utils.d.ts +4 -0
  23. package/dist/conversion/shared/snapshot-utils.js +4 -0
  24. package/dist/conversion/shared/tool-filter-pipeline.js +3 -9
  25. package/dist/conversion/shared/tool-governor.d.ts +2 -0
  26. package/dist/conversion/shared/tool-governor.js +101 -13
  27. package/dist/conversion/shared/tool-harvester.js +42 -2
  28. package/dist/conversion/shared/tooling.d.ts +33 -0
  29. package/dist/conversion/shared/tooling.js +27 -0
  30. package/dist/filters/index.d.ts +0 -2
  31. package/dist/filters/index.js +0 -2
  32. package/dist/filters/special/request-tools-normalize.d.ts +11 -0
  33. package/dist/filters/special/request-tools-normalize.js +13 -50
  34. package/dist/filters/special/response-apply-patch-toon-decode.js +410 -67
  35. package/dist/filters/special/response-tool-arguments-stringify.js +25 -16
  36. package/dist/filters/special/response-tool-arguments-toon-decode.js +8 -76
  37. package/dist/filters/utils/snapshot-writer.js +42 -4
  38. package/dist/guidance/index.js +8 -2
  39. package/dist/router/virtual-router/engine-health.js +0 -4
  40. package/dist/router/virtual-router/engine-selection.d.ts +2 -1
  41. package/dist/router/virtual-router/engine-selection.js +101 -9
  42. package/dist/router/virtual-router/engine.d.ts +5 -1
  43. package/dist/router/virtual-router/engine.js +188 -5
  44. package/dist/router/virtual-router/routing-instructions.d.ts +6 -0
  45. package/dist/router/virtual-router/routing-instructions.js +18 -3
  46. package/dist/router/virtual-router/sticky-session-store.d.ts +1 -0
  47. package/dist/router/virtual-router/sticky-session-store.js +36 -0
  48. package/dist/router/virtual-router/types.d.ts +22 -0
  49. package/dist/servertool/engine.js +335 -9
  50. package/dist/servertool/handlers/compaction-detect.d.ts +1 -0
  51. package/dist/servertool/handlers/compaction-detect.js +1 -0
  52. package/dist/servertool/handlers/gemini-empty-reply-continue.js +29 -5
  53. package/dist/servertool/handlers/iflow-model-error-retry.js +17 -0
  54. package/dist/servertool/handlers/stop-message-auto.js +199 -19
  55. package/dist/servertool/server-side-tools.d.ts +0 -1
  56. package/dist/servertool/server-side-tools.js +0 -1
  57. package/dist/servertool/types.d.ts +1 -0
  58. package/dist/tools/apply-patch-structured.js +52 -15
  59. package/dist/tools/tool-registry.js +537 -15
  60. package/dist/utils/toon.d.ts +4 -0
  61. package/dist/utils/toon.js +75 -0
  62. package/package.json +4 -2
  63. package/dist/test-output/virtual-router/results.json +0 -1
  64. package/dist/test-output/virtual-router/summary.json +0 -12
@@ -4,6 +4,7 @@
4
4
  // enforceChatBudget: 为避免在请求侧引入多余依赖,这里提供最小实现(保留形状,不裁剪)。
5
5
  import { augmentOpenAITools } from '../../guidance/index.js';
6
6
  import { validateToolCall } from '../../tools/tool-registry.js';
7
+ import { repairFindMeta } from './tooling.js';
7
8
  function isObject(v) { return !!v && typeof v === 'object' && !Array.isArray(v); }
8
9
  // Note: tool schema strict augmentation removed per alignment
9
10
  function enforceChatBudget(chat, _modelId) { return chat; }
@@ -25,8 +26,8 @@ function tryWriteSnapshot(options, stage, data) {
25
26
  const ep = String(snap.endpoint || 'chat').toLowerCase();
26
27
  const group = ep.includes('responses') ? 'openai-responses' : ep.includes('messages') ? 'anthropic-messages' : 'openai-chat';
27
28
  const rid = String(snap.requestId || `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`);
28
- const dir = path.join(base, group);
29
- const file = path.join(dir, `${rid}_govern-${stage}.json`);
29
+ const dir = path.join(base, group, '__pending__', rid);
30
+ const file = path.join(dir, `govern-${stage}.json`);
30
31
  if (fs.existsSync(file))
31
32
  return; // 不重复
32
33
  fs.mkdirSync(dir, { recursive: true });
@@ -209,7 +210,7 @@ export function processChatRequestTools(request, opts) {
209
210
  // 4) Enforce payload budget (context bytes) with minimal loss policy
210
211
  const modelId = typeof canonical?.model === 'string' ? String(canonical.model) : 'unknown';
211
212
  const budgeted = enforceChatBudget(canonical, modelId);
212
- return normalizeApplyPatchToolCallsOnRequest(budgeted);
213
+ return normalizeSpecialToolCallsOnRequest(budgeted);
213
214
  }
214
215
  catch {
215
216
  return out;
@@ -220,7 +221,7 @@ export function processChatRequestTools(request, opts) {
220
221
  * - Canonicalize textual tool markup to tool_calls; ensure finish_reason and content=null policy
221
222
  */
222
223
  import { repairArgumentsToString, parseLenient } from './jsonish.js';
223
- function normalizeApplyPatchToolCallsOnResponse(chat) {
224
+ export function normalizeApplyPatchToolCallsOnResponse(chat) {
224
225
  try {
225
226
  const out = JSON.parse(JSON.stringify(chat));
226
227
  const choices = Array.isArray(out?.choices) ? out.choices : [];
@@ -241,6 +242,19 @@ function normalizeApplyPatchToolCallsOnResponse(chat) {
241
242
  if (validation && validation.ok && typeof validation.normalizedArgs === 'string') {
242
243
  fn.arguments = validation.normalizedArgs;
243
244
  }
245
+ else if (validation && !validation.ok) {
246
+ try {
247
+ const reason = validation.reason ?? 'unknown';
248
+ const snippet = typeof argsStr === 'string' && argsStr.trim().length
249
+ ? argsStr.trim().slice(0, 200).replace(/\s+/g, ' ')
250
+ : '';
251
+ // eslint-disable-next-line no-console
252
+ console.error(`\x1b[31m[apply_patch][precheck][response] validation_failed reason=${reason}${snippet ? ` args=${snippet}` : ''}\x1b[0m`);
253
+ }
254
+ catch {
255
+ // logging best-effort
256
+ }
257
+ }
244
258
  }
245
259
  catch {
246
260
  // best-effort per tool_call
@@ -253,14 +267,32 @@ function normalizeApplyPatchToolCallsOnResponse(chat) {
253
267
  return chat;
254
268
  }
255
269
  }
256
- function normalizeApplyPatchToolCallsOnRequest(request) {
270
+ export function normalizeApplyPatchToolCallsOnRequest(request) {
271
+ return normalizeSpecialToolCallsOnRequest(request);
272
+ }
273
+ function normalizeSpecialToolCallsOnRequest(request) {
257
274
  try {
258
275
  const out = JSON.parse(JSON.stringify(request));
259
276
  const messages = Array.isArray(out?.messages) ? out.messages : [];
260
- for (const msg of messages) {
277
+ // 仅针对「当轮」工具调用做校验与形态修复:选择最后一条 assistant 消息
278
+ let lastAssistantIndex = -1;
279
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
280
+ const candidate = messages[i];
281
+ if (!candidate || typeof candidate !== 'object')
282
+ continue;
283
+ if (candidate.role === 'assistant') {
284
+ lastAssistantIndex = i;
285
+ break;
286
+ }
287
+ }
288
+ if (lastAssistantIndex === -1) {
289
+ return out;
290
+ }
291
+ for (let i = 0; i < messages.length; i += 1) {
292
+ const msg = messages[i];
261
293
  if (!msg || typeof msg !== 'object')
262
294
  continue;
263
- if (msg.role !== 'assistant')
295
+ if (i !== lastAssistantIndex)
264
296
  continue;
265
297
  const tcs = Array.isArray(msg.tool_calls) ? msg.tool_calls : [];
266
298
  if (!tcs.length)
@@ -269,13 +301,48 @@ function normalizeApplyPatchToolCallsOnRequest(request) {
269
301
  try {
270
302
  const fn = tc && tc.function ? tc.function : undefined;
271
303
  const name = typeof fn?.name === 'string' ? String(fn.name).trim().toLowerCase() : '';
272
- if (name !== 'apply_patch')
273
- continue;
274
304
  const rawArgs = fn?.arguments;
275
- const argsStr = repairArgumentsToString(rawArgs);
276
- const validation = validateToolCall('apply_patch', argsStr);
277
- if (validation && validation.ok && typeof validation.normalizedArgs === 'string') {
278
- fn.arguments = validation.normalizedArgs;
305
+ // apply_patch 兼容:统一生成 { patch, input }
306
+ if (name === 'apply_patch') {
307
+ const argsStr = repairArgumentsToString(rawArgs);
308
+ const validation = validateToolCall('apply_patch', argsStr);
309
+ if (validation && validation.ok && typeof validation.normalizedArgs === 'string') {
310
+ fn.arguments = validation.normalizedArgs;
311
+ }
312
+ else if (validation && !validation.ok) {
313
+ try {
314
+ const reason = validation.reason ?? 'unknown';
315
+ const snippet = typeof argsStr === 'string' && argsStr.trim().length
316
+ ? argsStr.trim().slice(0, 200).replace(/\s+/g, ' ')
317
+ : '';
318
+ // eslint-disable-next-line no-console
319
+ console.error(`\x1b[31m[apply_patch][precheck][request] validation_failed reason=${reason}${snippet ? ` args=${snippet}` : ''}\x1b[0m`);
320
+ }
321
+ catch {
322
+ // logging best-effort
323
+ }
324
+ }
325
+ continue;
326
+ }
327
+ // exec_command 兼容:TOON / map / string 一律收敛为 { cmd, command, workdir, ... }
328
+ if (name === 'exec_command') {
329
+ const argsStr = repairArgumentsToString(rawArgs);
330
+ let parsed;
331
+ try {
332
+ parsed = JSON.parse(argsStr);
333
+ }
334
+ catch {
335
+ parsed = parseLenient(argsStr);
336
+ }
337
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
338
+ const normalized = normalizeExecCommandArgs(parsed);
339
+ try {
340
+ fn.arguments = JSON.stringify(normalized ?? {});
341
+ }
342
+ catch {
343
+ fn.arguments = '{}';
344
+ }
345
+ }
279
346
  }
280
347
  }
281
348
  catch {
@@ -289,6 +356,27 @@ function normalizeApplyPatchToolCallsOnRequest(request) {
289
356
  return request;
290
357
  }
291
358
  }
359
+ function normalizeExecCommandArgs(args) {
360
+ try {
361
+ const out = { ...args };
362
+ const rawCmd = typeof out.cmd === 'string' && out.cmd.trim().length
363
+ ? String(out.cmd)
364
+ : typeof out.command === 'string' && out.command.trim().length
365
+ ? String(out.command)
366
+ : undefined;
367
+ if (rawCmd) {
368
+ const fixed = repairFindMeta(rawCmd);
369
+ out.cmd = fixed;
370
+ if (typeof out.command === 'string') {
371
+ out.command = fixed;
372
+ }
373
+ }
374
+ return out;
375
+ }
376
+ catch {
377
+ return args;
378
+ }
379
+ }
292
380
  function enhanceResponseToolArguments(chat) {
293
381
  try {
294
382
  const enable = String(process?.env?.RCC_TOOL_ENHANCE ?? '1').trim() !== '0';
@@ -80,8 +80,48 @@ function extractFromTextual(content, ctx) {
80
80
  }
81
81
  return events;
82
82
  }
83
- // 3) rcc.tool.v1 JSON envelope (removed)
84
- // 4) Generic <tool_call> textual block with <arg_key>/<arg_value>
83
+ // 3) <invoke name="tool"> wrapper with <parameter name="key">value</parameter>
84
+ try {
85
+ const invokeRe = /<invoke\s+name="([^">]+)"[^>]*>([\s\S]*?)<\/invoke>/gi;
86
+ let mInvoke;
87
+ const invokeEvents = [];
88
+ let idx = 0;
89
+ while ((mInvoke = invokeRe.exec(content)) !== null) {
90
+ const toolName = (mInvoke[1] || '').trim();
91
+ const inner = mInvoke[2] || '';
92
+ if (!toolName || !inner)
93
+ continue;
94
+ const paramRe = /<parameter\s+name="([^">]+)"[^>]*>([\s\S]*?)<\/parameter>/gi;
95
+ const argsObj = {};
96
+ let mParam;
97
+ while ((mParam = paramRe.exec(inner)) !== null) {
98
+ const key = String((mParam[1] || '').trim());
99
+ const raw = (mParam[2] || '').trim();
100
+ if (!key)
101
+ continue;
102
+ let value = raw;
103
+ try {
104
+ value = JSON.parse(raw);
105
+ }
106
+ catch { /* keep string */ }
107
+ argsObj[key] = value;
108
+ }
109
+ const id = genId(ctx, idx);
110
+ invokeEvents.push({ tool_calls: [{ index: idx, id, type: 'function', function: { name: toolName } }] });
111
+ const argStr = toJsonString(argsObj);
112
+ const parts = chunkString(argStr, Math.max(32, Math.min(1024, ctx?.chunkSize || 256)));
113
+ for (const d of parts) {
114
+ invokeEvents.push({ tool_calls: [{ index: idx, id, type: 'function', function: { arguments: d } }] });
115
+ }
116
+ idx += 1;
117
+ }
118
+ if (invokeEvents.length) {
119
+ return invokeEvents;
120
+ }
121
+ }
122
+ catch { /* ignore textual invoke parse errors */ }
123
+ // 4) rcc.tool.v1 JSON envelope (removed)
124
+ // 5) Generic <tool_call> textual block with <arg_key>/<arg_value>
85
125
  try {
86
126
  // Explicit wrapper form
87
127
  const blockRe = /<tool_call[^>]*>[\s\S]*?<\/tool_call>/gi;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Shared helpers for standard tool normalization (shell packing rules).
3
+ * The goal is deterministic, minimal shaping so executors succeed consistently.
4
+ */
5
+ export interface ShellArgs {
6
+ command: string | string[];
7
+ workdir?: string;
8
+ timeout_ms?: number;
9
+ }
10
+ /**
11
+ * Minimal, idempotent repairs for common `find` invocations inside shell scripts:
12
+ * - ensure `-exec … ;` is escaped as `-exec … \;`
13
+ * - collapse multiple backslashes before `;` into a single backslash
14
+ * - escape bare parentheses used in predicates: `(` / `)` → `\(` / `\)`
15
+ */
16
+ export declare function repairFindMeta(script: string): string;
17
+ export declare function splitCommandString(input: string): string[];
18
+ /**
19
+ * Pack shell arguments per unified rules:
20
+ * - command: string -> ["bash","-lc","<string>"]
21
+ * - command: tokens[]
22
+ * - if starts with ["cd", path, ...rest]:
23
+ * - set workdir to path when absent
24
+ * - if rest empty => command=["pwd"]
25
+ * - else if rest has control tokens => command=["bash","-lc", join(rest)]
26
+ * - else command=rest (argv)
27
+ * - else if tokens contain control tokens => command=["bash","-lc", join(tokens)]
28
+ * - else command=tokens (argv)
29
+ * - join(rest) uses single-space join without extra quoting
30
+ */
31
+ export declare function packShellArgs(input: Record<string, unknown>): Record<string, unknown>;
32
+ export declare function flattenByComma(arr: string[]): string[];
33
+ export declare function chunkString(s: string, minParts?: number, maxParts?: number, targetChunk?: number): string[];
@@ -5,6 +5,33 @@
5
5
  // We intentionally do NOT evaluate shell control operators (&&, |, etc.).
6
6
  // Codex CLI executor runs argv directly (execvp-like), not through a shell.
7
7
  // So we avoid wrapping with "bash -lc" and leave such tokens as-is.
8
+ /**
9
+ * Minimal, idempotent repairs for common `find` invocations inside shell scripts:
10
+ * - ensure `-exec … ;` is escaped as `-exec … \;`
11
+ * - collapse multiple backslashes before `;` into a single backslash
12
+ * - escape bare parentheses used in predicates: `(` / `)` → `\(` / `\)`
13
+ */
14
+ export function repairFindMeta(script) {
15
+ try {
16
+ const s = String(script ?? '');
17
+ if (!s)
18
+ return s;
19
+ const hasFind = /(^|\s)find\s/.test(s);
20
+ if (!hasFind)
21
+ return s;
22
+ let out = s;
23
+ // Only escape semicolon not already escaped (negative lookbehind)
24
+ out = out.replace(/-exec([^;]*?)(?<!\\);/g, (_m, g1) => `-exec${g1} \\;`);
25
+ // Collapse multiple backslashes immediately before ; into a single backslash
26
+ out = out.replace(/-exec([^;]*?)\\+;/g, (_m, g1) => `-exec${g1} \\;`);
27
+ // Escape parentheses only when not already escaped
28
+ out = out.replace(/(?<!\\)\(/g, '\\(').replace(/(?<!\\)\)/g, '\\)');
29
+ return out;
30
+ }
31
+ catch {
32
+ return script;
33
+ }
34
+ }
8
35
  function toStringArray(v) {
9
36
  if (Array.isArray(v))
10
37
  return v.map((x) => String(x));
@@ -12,8 +12,6 @@ export * from './special/response-tool-text-canonicalize.js';
12
12
  export * from './special/response-tool-arguments-stringify.js';
13
13
  export * from './special/response-finish-invariants.js';
14
14
  export * from './special/request-tools-normalize.js';
15
- export * from './special/response-tool-arguments-toon-decode.js';
16
- export * from './special/response-apply-patch-toon-decode.js';
17
15
  export * from './special/response-tool-arguments-blacklist.js';
18
16
  export * from './special/response-tool-arguments-schema-converge.js';
19
17
  export * from './special/response-tool-arguments-whitelist.js';
@@ -14,8 +14,6 @@ export * from './special/response-tool-arguments-stringify.js';
14
14
  export * from './special/response-finish-invariants.js';
15
15
  // TOON support (default ON via RCC_TOON_ENABLE unless explicitly disabled)
16
16
  export * from './special/request-tools-normalize.js';
17
- export * from './special/response-tool-arguments-toon-decode.js';
18
- export * from './special/response-apply-patch-toon-decode.js';
19
17
  // Arguments policy filters (synced)
20
18
  export * from './special/response-tool-arguments-blacklist.js';
21
19
  export * from './special/response-tool-arguments-schema-converge.js';
@@ -0,0 +1,11 @@
1
+ import type { Filter, FilterContext, FilterResult, JsonObject } from '../types.js';
2
+ /**
3
+ * Normalize OpenAI tools definitions at the final request stage.
4
+ * - Enforces { command: string | string[], workdir?: string } shape for shell-like tools.
5
+ * - Best-effort; never throws.
6
+ */
7
+ export declare class RequestOpenAIToolsNormalizeFilter implements Filter<JsonObject> {
8
+ readonly name = "request_openai_tools_normalize";
9
+ readonly stage: FilterContext['stage'];
10
+ apply(input: JsonObject): Promise<FilterResult<JsonObject>>;
11
+ }
@@ -1,11 +1,10 @@
1
- import { appendApplyPatchReminder, buildShellDescription, hasApplyPatchToolDeclared, isShellToolName, normalizeToolName } from '../../tools/tool-description-utils.js';
1
+ import { buildShellDescription, hasApplyPatchToolDeclared, isShellToolName } from '../../tools/tool-description-utils.js';
2
2
  function isObject(v) {
3
3
  return !!v && typeof v === 'object' && !Array.isArray(v);
4
4
  }
5
5
  /**
6
6
  * Normalize OpenAI tools definitions at the final request stage.
7
- * - Switches shell parameters schema to TOON ({ toon: string }) when enabled and not heredoc.
8
- * - Otherwise enforces { command: string | string[], workdir?: string } shape.
7
+ * - Enforces { command: string | string[], workdir?: string } shape for shell-like tools.
9
8
  * - Best-effort; never throws.
10
9
  */
11
10
  export class RequestOpenAIToolsNormalizeFilter {
@@ -14,23 +13,6 @@ export class RequestOpenAIToolsNormalizeFilter {
14
13
  async apply(input) {
15
14
  try {
16
15
  const out = JSON.parse(JSON.stringify(input || {}));
17
- const messages = Array.isArray(out.messages) ? out.messages : [];
18
- const lastUser = [...messages].reverse().find((m) => m && m.role === 'user');
19
- let lastText = '';
20
- if (typeof lastUser?.content === 'string') {
21
- lastText = lastUser.content;
22
- }
23
- else if (Array.isArray(lastUser?.content)) {
24
- for (const p of lastUser.content) {
25
- if (p && typeof p.text === 'string' && p.text.trim()) {
26
- lastText = p.text;
27
- break;
28
- }
29
- }
30
- }
31
- const looksHeredoc = /<<[-']?\w+/.test(lastText);
32
- const toonEnv = String(process?.env?.RCC_TOON_ENABLE || process?.env?.ROUTECODEX_TOON_ENABLE || '').toLowerCase();
33
- const toonEnabled = toonEnv ? !(toonEnv === '0' || toonEnv === 'false' || toonEnv === 'off') : true; // default ON
34
16
  const tools = Array.isArray(out.tools) ? out.tools : [];
35
17
  const hasApplyPatchTool = hasApplyPatchToolDeclared(tools);
36
18
  if (!tools.length) {
@@ -79,37 +61,18 @@ export class RequestOpenAIToolsNormalizeFilter {
79
61
  try {
80
62
  const rawToolName = String(dst.function.name || '');
81
63
  const isShell = isShellToolName(rawToolName);
82
- const isApplyPatch = normalizeToolName(rawToolName) === 'apply_patch';
83
64
  if (isShell) {
84
- const wantToon = toonEnabled && !looksHeredoc;
85
- if (wantToon) {
86
- dst.function.parameters = {
87
- type: 'object',
88
- properties: {
89
- toon: {
90
- type: 'string',
91
- description: 'TOON-encoded arguments (multi-line key: value). Example: command: bash -lc "echo ok"\\nworkdir: .'
92
- }
93
- },
94
- required: ['toon'],
95
- additionalProperties: false
96
- };
97
- const toonDescription = 'Use TOON: put arguments into arguments.toon as multi-line key: value (e.g., command / workdir).';
98
- dst.function.description = appendApplyPatchReminder(toonDescription, hasApplyPatchTool);
99
- }
100
- else {
101
- dst.function.parameters = {
102
- type: 'object',
103
- properties: {
104
- command: { oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }] },
105
- workdir: { type: 'string' }
106
- },
107
- required: ['command'],
108
- additionalProperties: false
109
- };
110
- const label = rawToolName && rawToolName.trim().length > 0 ? rawToolName.trim() : 'shell';
111
- dst.function.description = buildShellDescription(label, hasApplyPatchTool);
112
- }
65
+ dst.function.parameters = {
66
+ type: 'object',
67
+ properties: {
68
+ command: { oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }] },
69
+ workdir: { type: 'string' }
70
+ },
71
+ required: ['command'],
72
+ additionalProperties: false
73
+ };
74
+ const label = rawToolName && rawToolName.trim().length > 0 ? rawToolName.trim() : 'shell';
75
+ dst.function.description = buildShellDescription(label, hasApplyPatchTool);
113
76
  }
114
77
  }
115
78
  catch { /* ignore */ }