@jsonstudio/llms 0.6.631 → 0.6.633
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +117 -6
- package/dist/conversion/shared/tooling.d.ts +33 -0
- package/dist/conversion/shared/tooling.js +27 -0
- package/dist/filters/special/response-apply-patch-toon-decode.js +34 -12
- package/dist/filters/special/response-tool-arguments-stringify.js +25 -16
- package/dist/filters/special/response-tool-arguments-toon-decode.js +2 -1
- package/package.json +1 -1
package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js
CHANGED
|
@@ -84,6 +84,12 @@ function buildToolOutputSnapshot(payload, providerProtocol) {
|
|
|
84
84
|
const snapshot = {
|
|
85
85
|
providerProtocol: providerProtocol ?? 'unknown'
|
|
86
86
|
};
|
|
87
|
+
try {
|
|
88
|
+
injectApplyPatchDiagnostics(payload);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// diagnostics are best-effort; never block main flow
|
|
92
|
+
}
|
|
87
93
|
const toolOutputs = collectToolOutputs(payload);
|
|
88
94
|
if (toolOutputs.length) {
|
|
89
95
|
snapshot.tool_outputs = toolOutputs;
|
|
@@ -102,12 +108,14 @@ function collectToolOutputs(payload) {
|
|
|
102
108
|
try {
|
|
103
109
|
const name = typeof entry.name === 'string' ? entry.name.trim() : undefined;
|
|
104
110
|
const output = typeof entry.output === 'string' ? entry.output : undefined;
|
|
105
|
-
if (name === 'apply_patch' &&
|
|
106
|
-
output
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
+
if (name === 'apply_patch' && output) {
|
|
112
|
+
const lower = output.toLowerCase();
|
|
113
|
+
if (lower.includes('apply_patch verification failed') ||
|
|
114
|
+
lower.includes('failed to parse function arguments')) {
|
|
115
|
+
const firstLine = output.split('\n')[0] ?? output;
|
|
116
|
+
// eslint-disable-next-line no-console
|
|
117
|
+
console.error(`\x1b[31m[apply_patch][tool_error] tool_call_id=${id} ${firstLine}\x1b[0m`);
|
|
118
|
+
}
|
|
111
119
|
}
|
|
112
120
|
}
|
|
113
121
|
catch {
|
|
@@ -282,3 +290,106 @@ function normalizeToolOutputEntry(entry) {
|
|
|
282
290
|
name
|
|
283
291
|
};
|
|
284
292
|
}
|
|
293
|
+
function buildApplyPatchDiagnostics(output) {
|
|
294
|
+
if (!output || typeof output !== 'string') {
|
|
295
|
+
return undefined;
|
|
296
|
+
}
|
|
297
|
+
const lower = output.toLowerCase();
|
|
298
|
+
if (!lower.includes('failed to parse function arguments')) {
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
if (output.includes('missing field `input`')) {
|
|
302
|
+
return '\n\n[RouteCodex precheck] apply_patch 参数解析失败:缺少字段 "input"。当前 RouteCodex 期望 { input, patch } 或 { toon: \"<*** Begin Patch ... *** End Patch>\" } 形态。请将统一 diff 文本同时填入 \"patch\" 和 \"input\",或改用 toon 形态。';
|
|
303
|
+
}
|
|
304
|
+
if (output.includes('invalid type: map, expected a string')) {
|
|
305
|
+
return '\n\n[RouteCodex precheck] apply_patch 参数类型错误:检测到 JSON 对象(map),但客户端期望字符串。请先对参数做 JSON.stringify 再写入 arguments,或直接提供 { patch: \"<统一 diff>\" } / { toon: \"<*** Begin Patch ... *** End Patch>\" }。';
|
|
306
|
+
}
|
|
307
|
+
return undefined;
|
|
308
|
+
}
|
|
309
|
+
function appendDiagnosticsToRecord(record) {
|
|
310
|
+
const name = typeof record.name === 'string' ? record.name.trim() : undefined;
|
|
311
|
+
if (name !== 'apply_patch') {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
let text;
|
|
315
|
+
if (typeof record.output === 'string') {
|
|
316
|
+
text = record.output;
|
|
317
|
+
}
|
|
318
|
+
else if (typeof record.content === 'string') {
|
|
319
|
+
text = record.content;
|
|
320
|
+
}
|
|
321
|
+
if (!text || text.includes('[RouteCodex precheck]')) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const diag = buildApplyPatchDiagnostics(text);
|
|
325
|
+
if (!diag) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const merged = `${text}${diag}`;
|
|
329
|
+
if (typeof record.output === 'string') {
|
|
330
|
+
record.output = merged;
|
|
331
|
+
}
|
|
332
|
+
else if (typeof record.content === 'string') {
|
|
333
|
+
record.content = merged;
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
record.output = merged;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
function injectApplyPatchDiagnostics(payload) {
|
|
340
|
+
const root = payload;
|
|
341
|
+
const topOutputs = root.tool_outputs;
|
|
342
|
+
if (Array.isArray(topOutputs)) {
|
|
343
|
+
for (const entry of topOutputs) {
|
|
344
|
+
if (entry && typeof entry === 'object') {
|
|
345
|
+
appendDiagnosticsToRecord(entry);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
const required = root.required_action;
|
|
350
|
+
if (required && typeof required === 'object') {
|
|
351
|
+
const submit = required.submit_tool_outputs;
|
|
352
|
+
if (submit && typeof submit === 'object') {
|
|
353
|
+
const submitOutputs = submit.tool_outputs;
|
|
354
|
+
if (Array.isArray(submitOutputs)) {
|
|
355
|
+
for (const entry of submitOutputs) {
|
|
356
|
+
if (entry && typeof entry === 'object') {
|
|
357
|
+
appendDiagnosticsToRecord(entry);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const messages = root.messages;
|
|
364
|
+
if (Array.isArray(messages)) {
|
|
365
|
+
for (const entry of messages) {
|
|
366
|
+
if (!entry || typeof entry !== 'object')
|
|
367
|
+
continue;
|
|
368
|
+
const record = entry;
|
|
369
|
+
const role = typeof record.role === 'string' ? record.role.toLowerCase() : '';
|
|
370
|
+
if (role === 'tool') {
|
|
371
|
+
appendDiagnosticsToRecord(record);
|
|
372
|
+
}
|
|
373
|
+
const content = record.content;
|
|
374
|
+
if (Array.isArray(content)) {
|
|
375
|
+
for (const block of content) {
|
|
376
|
+
if (!block || typeof block !== 'object')
|
|
377
|
+
continue;
|
|
378
|
+
appendDiagnosticsToRecord(block);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const responsesInput = root.input;
|
|
384
|
+
if (Array.isArray(responsesInput)) {
|
|
385
|
+
for (const entry of responsesInput) {
|
|
386
|
+
if (!entry || typeof entry !== 'object')
|
|
387
|
+
continue;
|
|
388
|
+
const record = entry;
|
|
389
|
+
const type = typeof record.type === 'string' ? record.type.toLowerCase() : '';
|
|
390
|
+
if (type === 'tool_result' || type === 'tool_message' || type === 'function_call_output') {
|
|
391
|
+
appendDiagnosticsToRecord(record);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
@@ -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));
|
|
@@ -10,6 +10,13 @@ function envEnabled() {
|
|
|
10
10
|
function isObject(v) {
|
|
11
11
|
return !!v && typeof v === 'object' && !Array.isArray(v);
|
|
12
12
|
}
|
|
13
|
+
function readString(value) {
|
|
14
|
+
if (typeof value === 'string') {
|
|
15
|
+
const trimmed = value.trim();
|
|
16
|
+
return trimmed ? trimmed : undefined;
|
|
17
|
+
}
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
13
20
|
/**
|
|
14
21
|
* Response-side apply_patch arguments 规范化(TOON + 结构化 JSON → 统一 diff 文本)。
|
|
15
22
|
*
|
|
@@ -50,15 +57,24 @@ export class ResponseApplyPatchToonDecodeFilter {
|
|
|
50
57
|
}
|
|
51
58
|
const argIn = fn.arguments;
|
|
52
59
|
let parsed;
|
|
60
|
+
let patchText;
|
|
53
61
|
if (typeof argIn === 'string') {
|
|
54
|
-
|
|
62
|
+
const trimmed = argIn.trim();
|
|
63
|
+
if (!trimmed)
|
|
55
64
|
continue;
|
|
56
65
|
try {
|
|
57
|
-
|
|
66
|
+
// 尝试作为 JSON 解析
|
|
67
|
+
parsed = JSON.parse(trimmed);
|
|
58
68
|
}
|
|
59
69
|
catch {
|
|
60
|
-
//
|
|
61
|
-
|
|
70
|
+
// 非 JSON 字符串:如果看起来就是统一 diff,则直接视为 patch 文本
|
|
71
|
+
if (trimmed.includes('*** Begin Patch') && trimmed.includes('*** End Patch')) {
|
|
72
|
+
patchText = trimmed;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
// 退而求其次:当作原始补丁文本传递给客户端,由本地 apply_patch 决定是否接受
|
|
76
|
+
patchText = trimmed;
|
|
77
|
+
}
|
|
62
78
|
}
|
|
63
79
|
}
|
|
64
80
|
else if (isObject(argIn)) {
|
|
@@ -67,20 +83,20 @@ export class ResponseApplyPatchToonDecodeFilter {
|
|
|
67
83
|
else {
|
|
68
84
|
continue;
|
|
69
85
|
}
|
|
70
|
-
if (!isObject(parsed))
|
|
86
|
+
if (!patchText && !isObject(parsed))
|
|
71
87
|
continue;
|
|
88
|
+
const record = isObject(parsed) ? parsed : {};
|
|
72
89
|
// 优先处理 toon: "<patch text>" 形态(兼容旧 TOON 协议)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (toon.includes('*** Begin Patch') && toon.includes('*** End Patch')) {
|
|
90
|
+
if (!patchText) {
|
|
91
|
+
const toon = readString(record.toon);
|
|
92
|
+
if (toon && toon.includes('*** Begin Patch') && toon.includes('*** End Patch')) {
|
|
77
93
|
patchText = toon;
|
|
78
94
|
}
|
|
79
95
|
}
|
|
80
|
-
//
|
|
81
|
-
if (!patchText && isStructuredApplyPatchPayload(
|
|
96
|
+
// 处理结构化 JSON(changes 数组 → 统一 diff)
|
|
97
|
+
if (!patchText && isStructuredApplyPatchPayload(record)) {
|
|
82
98
|
try {
|
|
83
|
-
patchText = buildStructuredPatch(
|
|
99
|
+
patchText = buildStructuredPatch(record);
|
|
84
100
|
}
|
|
85
101
|
catch (error) {
|
|
86
102
|
if (error instanceof StructuredApplyPatchError) {
|
|
@@ -91,6 +107,12 @@ export class ResponseApplyPatchToonDecodeFilter {
|
|
|
91
107
|
continue;
|
|
92
108
|
}
|
|
93
109
|
}
|
|
110
|
+
// 处理 { patch }, { input, patch } 形态 —— Responses 入口常见形状
|
|
111
|
+
if (!patchText) {
|
|
112
|
+
const patchField = readString(record.patch);
|
|
113
|
+
const inputField = readString(record.input);
|
|
114
|
+
patchText = patchField || inputField;
|
|
115
|
+
}
|
|
94
116
|
if (!patchText) {
|
|
95
117
|
continue;
|
|
96
118
|
}
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
+
import { repairFindMeta } from '../../conversion/shared/tooling.js';
|
|
1
2
|
function isObject(v) {
|
|
2
3
|
return !!v && typeof v === 'object' && !Array.isArray(v);
|
|
3
4
|
}
|
|
4
|
-
function
|
|
5
|
-
// Minimal, idempotent repairs for common find expressions within a shell:
|
|
6
|
-
// - ensure -exec terminator is escaped exactly once: ; => \\;
|
|
7
|
-
// - collapse multiple backslashes before ; to a single backslash
|
|
8
|
-
// - escape unescaped parentheses used in predicates: ( ) -> \\( \\)
|
|
5
|
+
function normalizeExecCommandArgs(args) {
|
|
9
6
|
try {
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
7
|
+
const out = { ...args };
|
|
8
|
+
const rawCmd = typeof out.cmd === 'string' && out.cmd.trim().length
|
|
9
|
+
? String(out.cmd)
|
|
10
|
+
: typeof out.command === 'string' && out.command.trim().length
|
|
11
|
+
? String(out.command)
|
|
12
|
+
: undefined;
|
|
13
|
+
if (rawCmd) {
|
|
14
|
+
const fixed = repairFindMeta(rawCmd);
|
|
15
|
+
out.cmd = fixed;
|
|
16
|
+
if (typeof out.command === 'string') {
|
|
17
|
+
out.command = fixed;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
20
|
return out;
|
|
21
21
|
}
|
|
22
22
|
catch {
|
|
23
|
-
return
|
|
23
|
+
return args;
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
function packShellCommand(cmd) {
|
|
@@ -102,6 +102,15 @@ export class ResponseToolArgumentsStringifyFilter {
|
|
|
102
102
|
fn.arguments = '{}';
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
+
else if ((name === 'exec_command' || name === 'shell_command' || name === 'bash') && isObject(parsed)) {
|
|
106
|
+
const normalized = normalizeExecCommandArgs(parsed);
|
|
107
|
+
try {
|
|
108
|
+
fn.arguments = JSON.stringify(normalized ?? {});
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
fn.arguments = '{}';
|
|
112
|
+
}
|
|
113
|
+
}
|
|
105
114
|
else {
|
|
106
115
|
if (typeof argIn !== 'string') {
|
|
107
116
|
try {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isShellToolName, normalizeToolName } from '../../tools/tool-description-utils.js';
|
|
2
|
+
import { repairFindMeta } from '../../conversion/shared/tooling.js';
|
|
2
3
|
function envEnabled() {
|
|
3
4
|
// Default ON. Allow disabling via env RCC_TOON_ENABLE/ROUTECODEX_TOON_ENABLE = 0|false|off
|
|
4
5
|
const v = String(process?.env?.RCC_TOON_ENABLE || process?.env?.ROUTECODEX_TOON_ENABLE || '').toLowerCase();
|
|
@@ -156,7 +157,7 @@ export class ResponseToolArgumentsToonDecodeFilter {
|
|
|
156
157
|
? kv['with_escalated_permissions']
|
|
157
158
|
: undefined;
|
|
158
159
|
const justificationRaw = typeof kv['justification'] === 'string' ? kv['justification'] : undefined;
|
|
159
|
-
const command = commandRaw.trim();
|
|
160
|
+
const command = repairFindMeta(commandRaw.trim());
|
|
160
161
|
if (command) {
|
|
161
162
|
const merged = {
|
|
162
163
|
cmd: command,
|