@jsonstudio/llms 0.6.567 → 0.6.586
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/codecs/gemini-openai-codec.js +33 -4
- package/dist/conversion/codecs/openai-openai-codec.js +2 -1
- package/dist/conversion/codecs/responses-openai-codec.js +3 -2
- package/dist/conversion/compat/actions/glm-history-image-trim.d.ts +2 -0
- package/dist/conversion/compat/actions/glm-history-image-trim.js +88 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +6 -2
- package/dist/conversion/hub/pipeline/hub-pipeline.js +72 -81
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +0 -34
- package/dist/conversion/hub/process/chat-process.js +68 -24
- package/dist/conversion/hub/response/provider-response.js +0 -8
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +22 -3
- package/dist/conversion/hub/semantic-mappers/responses-mapper.js +267 -14
- package/dist/conversion/hub/types/chat-envelope.d.ts +1 -0
- package/dist/conversion/responses/responses-openai-bridge.d.ts +3 -2
- package/dist/conversion/responses/responses-openai-bridge.js +1 -13
- package/dist/conversion/shared/anthropic-message-utils.js +54 -0
- package/dist/conversion/shared/args-mapping.js +11 -3
- package/dist/conversion/shared/responses-output-builder.js +42 -21
- package/dist/conversion/shared/streaming-text-extractor.d.ts +25 -0
- package/dist/conversion/shared/streaming-text-extractor.js +31 -38
- package/dist/conversion/shared/text-markup-normalizer.d.ts +20 -0
- package/dist/conversion/shared/text-markup-normalizer.js +118 -31
- package/dist/conversion/shared/tool-filter-pipeline.js +56 -30
- package/dist/conversion/shared/tool-harvester.js +43 -12
- package/dist/conversion/shared/tool-mapping.d.ts +1 -0
- package/dist/conversion/shared/tool-mapping.js +33 -19
- package/dist/filters/index.d.ts +1 -0
- package/dist/filters/index.js +1 -0
- package/dist/filters/special/request-tools-normalize.js +14 -4
- package/dist/filters/special/response-apply-patch-toon-decode.d.ts +23 -0
- package/dist/filters/special/response-apply-patch-toon-decode.js +117 -0
- package/dist/filters/special/response-tool-arguments-toon-decode.d.ts +10 -0
- package/dist/filters/special/response-tool-arguments-toon-decode.js +154 -26
- package/dist/guidance/index.js +71 -42
- package/dist/router/virtual-router/bootstrap.js +10 -5
- package/dist/router/virtual-router/classifier.js +16 -7
- package/dist/router/virtual-router/engine-health.d.ts +11 -0
- package/dist/router/virtual-router/engine-health.js +217 -4
- package/dist/router/virtual-router/engine-logging.d.ts +2 -1
- package/dist/router/virtual-router/engine-logging.js +35 -3
- package/dist/router/virtual-router/engine.d.ts +17 -1
- package/dist/router/virtual-router/engine.js +184 -6
- package/dist/router/virtual-router/routing-instructions.d.ts +2 -0
- package/dist/router/virtual-router/routing-instructions.js +19 -1
- package/dist/router/virtual-router/tool-signals.d.ts +2 -1
- package/dist/router/virtual-router/tool-signals.js +324 -119
- package/dist/router/virtual-router/types.d.ts +31 -1
- package/dist/router/virtual-router/types.js +2 -2
- package/dist/servertool/engine.js +3 -0
- package/dist/servertool/handlers/iflow-model-error-retry.d.ts +1 -0
- package/dist/servertool/handlers/iflow-model-error-retry.js +93 -0
- package/dist/servertool/handlers/stop-message-auto.js +61 -4
- package/dist/servertool/server-side-tools.d.ts +1 -0
- package/dist/servertool/server-side-tools.js +27 -0
- package/dist/sse/json-to-sse/event-generators/responses.js +9 -2
- package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +23 -3
- package/dist/tools/apply-patch-structured.d.ts +20 -0
- package/dist/tools/apply-patch-structured.js +240 -0
- package/dist/tools/tool-description-utils.d.ts +5 -0
- package/dist/tools/tool-description-utils.js +50 -0
- package/dist/tools/tool-registry.js +11 -193
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Streaming textual tool intent extractor (对齐)
|
|
2
|
-
// Detects <function=execute> blocks and
|
|
2
|
+
// Detects <function=execute> blocks and structured apply_patch payloads
|
|
3
3
|
// and converts them into OpenAI tool_calls incrementally.
|
|
4
|
+
import { isStructuredApplyPatchPayload } from '../../tools/apply-patch-structured.js';
|
|
4
5
|
function isObject(v) {
|
|
5
6
|
return !!v && typeof v === 'object' && !Array.isArray(v);
|
|
6
7
|
}
|
|
@@ -8,22 +9,20 @@ export class StreamingTextToolExtractor {
|
|
|
8
9
|
opts;
|
|
9
10
|
buffer = '';
|
|
10
11
|
idCounter = 0;
|
|
11
|
-
pendingPatch = { active: false, lines: [] };
|
|
12
12
|
constructor(opts = {}) {
|
|
13
13
|
this.opts = opts;
|
|
14
14
|
}
|
|
15
15
|
reset() {
|
|
16
16
|
this.buffer = '';
|
|
17
17
|
this.idCounter = 0;
|
|
18
|
-
this.pendingPatch = { active: false, lines: [] };
|
|
19
18
|
}
|
|
20
19
|
feedText(text) {
|
|
21
20
|
const out = [];
|
|
22
21
|
if (typeof text !== 'string' || !text)
|
|
23
22
|
return out;
|
|
24
23
|
this.buffer += text;
|
|
25
|
-
// 1)
|
|
26
|
-
out.push(...this.
|
|
24
|
+
// 1) Structured apply_patch block detection (```json ... ```)
|
|
25
|
+
out.push(...this.tryExtractStructuredBlocks());
|
|
27
26
|
// 2) <function=execute> compact blocks detection
|
|
28
27
|
out.push(...this.tryExtractFunctionExecuteBlocks());
|
|
29
28
|
return out;
|
|
@@ -42,42 +41,36 @@ export class StreamingTextToolExtractor {
|
|
|
42
41
|
}
|
|
43
42
|
return { id: this.genId(), type: 'function', function: { name, arguments: argStr } };
|
|
44
43
|
}
|
|
45
|
-
|
|
44
|
+
tryExtractStructuredBlocks() {
|
|
46
45
|
const out = [];
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
this.pendingPatch = { active: false, lines: [] };
|
|
73
|
-
this.buffer = remainder;
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
// keep accumulating, but limit memory
|
|
77
|
-
if (joined.length > 200000) {
|
|
78
|
-
this.pendingPatch.lines = [joined.slice(-100000)];
|
|
46
|
+
let searchIdx = 0;
|
|
47
|
+
while (searchIdx < this.buffer.length) {
|
|
48
|
+
const startIdx = this.buffer.indexOf('```', searchIdx);
|
|
49
|
+
if (startIdx < 0)
|
|
50
|
+
break;
|
|
51
|
+
const headerEnd = this.buffer.indexOf('\n', startIdx + 3);
|
|
52
|
+
if (headerEnd < 0)
|
|
53
|
+
break;
|
|
54
|
+
const language = this.buffer.slice(startIdx + 3, headerEnd).trim().toLowerCase();
|
|
55
|
+
const endIdx = this.buffer.indexOf('```', headerEnd + 1);
|
|
56
|
+
if (endIdx < 0)
|
|
57
|
+
break;
|
|
58
|
+
const body = this.buffer.slice(headerEnd + 1, endIdx);
|
|
59
|
+
if (!language || language === 'json' || language === 'apply_patch' || language === 'toon') {
|
|
60
|
+
try {
|
|
61
|
+
const parsed = JSON.parse(body);
|
|
62
|
+
if (isStructuredApplyPatchPayload(parsed)) {
|
|
63
|
+
out.push(this.toToolCall('apply_patch', parsed));
|
|
64
|
+
this.buffer = this.buffer.slice(0, startIdx) + this.buffer.slice(endIdx + 3);
|
|
65
|
+
searchIdx = 0;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
/* ignore parse errors */
|
|
79
71
|
}
|
|
80
72
|
}
|
|
73
|
+
searchIdx = endIdx + 3;
|
|
81
74
|
}
|
|
82
75
|
return out;
|
|
83
76
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type ToolCallLite = {
|
|
2
|
+
id?: string;
|
|
3
|
+
name: string;
|
|
4
|
+
args: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function extractApplyPatchCallsFromText(text: string): ToolCallLite[] | null;
|
|
7
|
+
export declare function extractExecuteBlocksFromText(text: string): ToolCallLite[] | null;
|
|
8
|
+
export declare function extractXMLToolCallsFromText(text: string): ToolCallLite[] | null;
|
|
9
|
+
/**
|
|
10
|
+
* 提取简单 XML 形式的工具调用块,例如:
|
|
11
|
+
*
|
|
12
|
+
* <list_directory>
|
|
13
|
+
* <path>/path/to/dir</path>
|
|
14
|
+
* <recursive>false</recursive>
|
|
15
|
+
* </list_directory>
|
|
16
|
+
*
|
|
17
|
+
* 仅针对已知工具名(目前为 list_directory),避免误伤普通 XML 文本。
|
|
18
|
+
*/
|
|
19
|
+
export declare function extractSimpleXmlToolsFromText(text: string): ToolCallLite[] | null;
|
|
20
|
+
export declare function normalizeAssistantTextToToolCalls(message: Record<string, any>): Record<string, any>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Normalize textual markup into OpenAI tool_calls shape.
|
|
2
2
|
// Gated by RCC_TEXT_MARKUP_COMPAT=1 to avoid overreach.
|
|
3
3
|
import { isImagePath } from './media.js';
|
|
4
|
+
import { isStructuredApplyPatchPayload } from '../../tools/apply-patch-structured.js';
|
|
4
5
|
// Strict allowlist for tool names and their argument keys to avoid picking up
|
|
5
6
|
// stray markup or free-form text as JSON keys (reduces false positives).
|
|
6
7
|
const KNOWN_TOOLS = new Set([
|
|
@@ -10,16 +11,19 @@ const KNOWN_TOOLS = new Set([
|
|
|
10
11
|
'view_image',
|
|
11
12
|
'list_mcp_resources',
|
|
12
13
|
'read_mcp_resource',
|
|
13
|
-
'list_mcp_resource_templates'
|
|
14
|
+
'list_mcp_resource_templates',
|
|
15
|
+
// 文件/目录类工具(CLI 侧已有约定;此处只做文本→tool_calls 收割)
|
|
16
|
+
'list_directory'
|
|
14
17
|
]);
|
|
15
18
|
const ALLOWED_KEYS = {
|
|
16
19
|
shell: new Set(['command', 'justification', 'timeout_ms', 'with_escalated_permissions', 'workdir']),
|
|
17
|
-
apply_patch: new Set(['
|
|
20
|
+
apply_patch: new Set(['file', 'instructions', 'changes']),
|
|
18
21
|
update_plan: new Set(['explanation', 'plan']),
|
|
19
22
|
view_image: new Set(['path']),
|
|
20
23
|
list_mcp_resources: new Set(['server', 'cursor', 'filter', 'root']),
|
|
21
24
|
read_mcp_resource: new Set(['server', 'uri', 'cursor']),
|
|
22
|
-
list_mcp_resource_templates: new Set(['server', 'cursor'])
|
|
25
|
+
list_mcp_resource_templates: new Set(['server', 'cursor']),
|
|
26
|
+
list_directory: new Set(['path', 'recursive'])
|
|
23
27
|
};
|
|
24
28
|
function normalizeKey(raw) {
|
|
25
29
|
try {
|
|
@@ -70,40 +74,54 @@ function enabled() {
|
|
|
70
74
|
}
|
|
71
75
|
}
|
|
72
76
|
// 已移除所有 rcc.tool.v1 相关处理:不再识别或剥离 rcc 封装
|
|
73
|
-
|
|
77
|
+
function extractStructuredApplyPatchPayloads(text) {
|
|
78
|
+
const payloads = [];
|
|
74
79
|
try {
|
|
75
|
-
|
|
76
|
-
return null;
|
|
77
|
-
const out = [];
|
|
78
|
-
const candidates = [];
|
|
79
|
-
const fenceRe = /```(?:patch)?\s*([\s\S]*?)\s*```/gi;
|
|
80
|
+
const fenceRe = /```(?:json|apply_patch|toon)?\s*([\s\S]*?)\s*```/gi;
|
|
80
81
|
let fm;
|
|
81
82
|
while ((fm = fenceRe.exec(text)) !== null) {
|
|
82
83
|
const body = fm[1] || '';
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
candidates.push(text);
|
|
88
|
-
const genId = () => `call_${Math.random().toString(36).slice(2, 10)}`;
|
|
89
|
-
for (const src of candidates) {
|
|
90
|
-
const pg = /\*\*\*\s+Begin Patch[\s\S]*?\*\*\*\s+End Patch/gm;
|
|
91
|
-
let pm;
|
|
92
|
-
while ((pm = pg.exec(src)) !== null) {
|
|
93
|
-
const patch = pm[0];
|
|
94
|
-
if (!patch || patch.length < 32)
|
|
95
|
-
continue;
|
|
96
|
-
let argsStr = '{}';
|
|
97
|
-
try {
|
|
98
|
-
argsStr = JSON.stringify({ patch });
|
|
84
|
+
try {
|
|
85
|
+
const parsed = JSON.parse(body);
|
|
86
|
+
if (isStructuredApplyPatchPayload(parsed)) {
|
|
87
|
+
payloads.push(parsed);
|
|
99
88
|
}
|
|
100
|
-
|
|
101
|
-
|
|
89
|
+
}
|
|
90
|
+
catch { /* ignore invalid JSON */ }
|
|
91
|
+
}
|
|
92
|
+
if (!payloads.length && typeof text === 'string' && text.includes('"changes"')) {
|
|
93
|
+
try {
|
|
94
|
+
const parsed = JSON.parse(text);
|
|
95
|
+
if (isStructuredApplyPatchPayload(parsed)) {
|
|
96
|
+
payloads.push(parsed);
|
|
102
97
|
}
|
|
103
|
-
out.push({ id: genId(), name: 'apply_patch', args: argsStr });
|
|
104
98
|
}
|
|
99
|
+
catch { /* ignore */ }
|
|
105
100
|
}
|
|
106
|
-
|
|
101
|
+
}
|
|
102
|
+
catch { /* ignore */ }
|
|
103
|
+
return payloads;
|
|
104
|
+
}
|
|
105
|
+
export function extractApplyPatchCallsFromText(text) {
|
|
106
|
+
try {
|
|
107
|
+
if (typeof text !== 'string' || !text)
|
|
108
|
+
return null;
|
|
109
|
+
const payloads = extractStructuredApplyPatchPayloads(text);
|
|
110
|
+
if (!payloads.length)
|
|
111
|
+
return null;
|
|
112
|
+
const out = [];
|
|
113
|
+
const genId = () => `call_${Math.random().toString(36).slice(2, 10)}`;
|
|
114
|
+
for (const payload of payloads) {
|
|
115
|
+
let argsStr = '{}';
|
|
116
|
+
try {
|
|
117
|
+
argsStr = JSON.stringify(payload);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
argsStr = '{"changes":[]}';
|
|
121
|
+
}
|
|
122
|
+
out.push({ id: genId(), name: 'apply_patch', args: argsStr });
|
|
123
|
+
}
|
|
124
|
+
return out;
|
|
107
125
|
}
|
|
108
126
|
catch {
|
|
109
127
|
return null;
|
|
@@ -290,6 +308,74 @@ export function extractXMLToolCallsFromText(text) {
|
|
|
290
308
|
return null;
|
|
291
309
|
}
|
|
292
310
|
}
|
|
311
|
+
/**
|
|
312
|
+
* 提取简单 XML 形式的工具调用块,例如:
|
|
313
|
+
*
|
|
314
|
+
* <list_directory>
|
|
315
|
+
* <path>/path/to/dir</path>
|
|
316
|
+
* <recursive>false</recursive>
|
|
317
|
+
* </list_directory>
|
|
318
|
+
*
|
|
319
|
+
* 仅针对已知工具名(目前为 list_directory),避免误伤普通 XML 文本。
|
|
320
|
+
*/
|
|
321
|
+
export function extractSimpleXmlToolsFromText(text) {
|
|
322
|
+
try {
|
|
323
|
+
if (typeof text !== 'string' || !text)
|
|
324
|
+
return null;
|
|
325
|
+
const out = [];
|
|
326
|
+
const blockRe = /<\s*([A-Za-z0-9_.-]+)\s*>([\s\S]*?)<\/\s*\1\s*>/gi;
|
|
327
|
+
let bm;
|
|
328
|
+
while ((bm = blockRe.exec(text)) !== null) {
|
|
329
|
+
const rawName = (bm[1] || '').trim();
|
|
330
|
+
const lname = rawName.toLowerCase();
|
|
331
|
+
if (!lname || lname === 'tool_call')
|
|
332
|
+
continue;
|
|
333
|
+
// 目前仅支持 list_directory,后续按需扩展
|
|
334
|
+
if (lname !== 'list_directory')
|
|
335
|
+
continue;
|
|
336
|
+
const inner = bm[2] || '';
|
|
337
|
+
const args = {};
|
|
338
|
+
const argRe = /<\s*([A-Za-z0-9_]+)\s*>([\s\S]*?)<\/\s*\1\s*>/gi;
|
|
339
|
+
let am;
|
|
340
|
+
while ((am = argRe.exec(inner)) !== null) {
|
|
341
|
+
const key = normalizeKey((am[1] || '').trim());
|
|
342
|
+
if (!key)
|
|
343
|
+
continue;
|
|
344
|
+
let rawVal = (am[2] || '').trim();
|
|
345
|
+
let v = rawVal;
|
|
346
|
+
if ((rawVal.startsWith('[') && rawVal.endsWith(']')) || (rawVal.startsWith('{') && rawVal.endsWith('}'))) {
|
|
347
|
+
try {
|
|
348
|
+
v = JSON.parse(rawVal);
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
v = rawVal;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
else if (rawVal === 'true' || rawVal === 'false') {
|
|
355
|
+
v = rawVal === 'true';
|
|
356
|
+
}
|
|
357
|
+
args[key] = v;
|
|
358
|
+
}
|
|
359
|
+
const filtered = filterArgsForTool(lname, args);
|
|
360
|
+
let argsStr = '{}';
|
|
361
|
+
try {
|
|
362
|
+
argsStr = JSON.stringify(filtered);
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
argsStr = '{}';
|
|
366
|
+
}
|
|
367
|
+
out.push({
|
|
368
|
+
id: `call_${Math.random().toString(36).slice(2, 10)}`,
|
|
369
|
+
name: lname,
|
|
370
|
+
args: argsStr
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
return out.length ? out : null;
|
|
374
|
+
}
|
|
375
|
+
catch {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
293
379
|
export function normalizeAssistantTextToToolCalls(message) {
|
|
294
380
|
if (!enabled())
|
|
295
381
|
return message;
|
|
@@ -302,10 +388,11 @@ export function normalizeAssistantTextToToolCalls(message) {
|
|
|
302
388
|
const text = typeof content === 'string' ? content : null;
|
|
303
389
|
if (!text)
|
|
304
390
|
return message;
|
|
305
|
-
// Order: xml-like tool_call → apply_patch → execute blocks
|
|
391
|
+
// Order: xml-like <tool_call> → apply_patch → execute blocks → 简单 XML 工具(list_directory 等)
|
|
306
392
|
const calls = (extractXMLToolCallsFromText(text) ||
|
|
307
393
|
extractApplyPatchCallsFromText(text) ||
|
|
308
|
-
extractExecuteBlocksFromText(text)
|
|
394
|
+
extractExecuteBlocksFromText(text) ||
|
|
395
|
+
extractSimpleXmlToolsFromText(text));
|
|
309
396
|
if (calls && calls.length) {
|
|
310
397
|
const toolCalls = calls.map((c) => ({ id: c.id, type: 'function', function: { name: c.name, arguments: c.args } }));
|
|
311
398
|
const copy = { ...message };
|
|
@@ -134,42 +134,67 @@ function applyLocalToolGovernance(chatRequest, rawPayload) {
|
|
|
134
134
|
};
|
|
135
135
|
}
|
|
136
136
|
function detectImageHint(messages, rawPayload) {
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
if (typeof value
|
|
140
|
-
|
|
141
|
-
|
|
137
|
+
const patterns = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.svg'];
|
|
138
|
+
const hasImageExt = (value) => {
|
|
139
|
+
if (typeof value !== 'string' || !value)
|
|
140
|
+
return false;
|
|
141
|
+
const lower = value.toLowerCase();
|
|
142
|
+
return patterns.some(ext => lower.includes(ext));
|
|
142
143
|
};
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
144
|
+
const hasImageInMessage = (msg) => {
|
|
145
|
+
if (!msg || typeof msg !== 'object')
|
|
146
|
+
return false;
|
|
147
|
+
const m = msg;
|
|
148
|
+
const content = m.content;
|
|
149
|
+
if (typeof content === 'string') {
|
|
150
|
+
if (hasImageExt(content))
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
else if (Array.isArray(content)) {
|
|
154
|
+
for (const part of content) {
|
|
155
|
+
if (!part || typeof part !== 'object')
|
|
156
|
+
continue;
|
|
157
|
+
const p = part;
|
|
158
|
+
const t = String(p.type || '').toLowerCase();
|
|
159
|
+
if (t.includes('image')) {
|
|
160
|
+
return true;
|
|
156
161
|
}
|
|
162
|
+
if (hasImageExt(p.text))
|
|
163
|
+
return true;
|
|
164
|
+
const imageUrl = typeof p.image_url === 'string'
|
|
165
|
+
? p.image_url
|
|
166
|
+
: p.image_url && typeof p.image_url.url === 'string'
|
|
167
|
+
? p.image_url.url
|
|
168
|
+
: typeof p.url === 'string'
|
|
169
|
+
? p.url
|
|
170
|
+
: undefined;
|
|
171
|
+
if (hasImageExt(imageUrl))
|
|
172
|
+
return true;
|
|
173
|
+
if (hasImageExt(p.path))
|
|
174
|
+
return true;
|
|
157
175
|
}
|
|
158
176
|
}
|
|
159
|
-
}
|
|
160
|
-
if (rawPayload && typeof rawPayload === 'object') {
|
|
161
|
-
collect(rawPayload.content);
|
|
162
|
-
}
|
|
163
|
-
if (!candidates.length) {
|
|
164
177
|
return false;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if (
|
|
178
|
+
};
|
|
179
|
+
// 仅考虑“当前这一轮”的用户输入是否包含图片链接或图片负载,避免因为历史上下文中曾经出现过图片而在后续轮次持续暴露 view_image。
|
|
180
|
+
if (Array.isArray(messages)) {
|
|
181
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
182
|
+
const msg = messages[i];
|
|
183
|
+
if (!msg || typeof msg !== 'object')
|
|
184
|
+
continue;
|
|
185
|
+
const role = String(msg.role || '').toLowerCase();
|
|
186
|
+
if (role !== 'user')
|
|
187
|
+
continue;
|
|
188
|
+
if (hasImageInMessage(msg)) {
|
|
171
189
|
return true;
|
|
172
190
|
}
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (rawPayload && typeof rawPayload === 'object') {
|
|
195
|
+
const text = rawPayload.content;
|
|
196
|
+
if (hasImageExt(text)) {
|
|
197
|
+
return true;
|
|
173
198
|
}
|
|
174
199
|
}
|
|
175
200
|
return false;
|
|
@@ -212,8 +237,9 @@ export async function runChatResponseToolFilters(chatJson, options = {}) {
|
|
|
212
237
|
const { ResponseToolTextCanonicalizeFilter, ResponseToolArgumentsStringifyFilter, ResponseFinishInvariantsFilter } = await import('../../filters/index.js');
|
|
213
238
|
register(new ResponseToolTextCanonicalizeFilter());
|
|
214
239
|
try {
|
|
215
|
-
const { ResponseToolArgumentsToonDecodeFilter, ResponseToolArgumentsBlacklistFilter, ResponseToolArgumentsSchemaConvergeFilter } = await import('../../filters/index.js');
|
|
240
|
+
const { ResponseToolArgumentsToonDecodeFilter, ResponseApplyPatchToonDecodeFilter, ResponseToolArgumentsBlacklistFilter, ResponseToolArgumentsSchemaConvergeFilter } = await import('../../filters/index.js');
|
|
216
241
|
register(new ResponseToolArgumentsToonDecodeFilter());
|
|
242
|
+
register(new ResponseApplyPatchToonDecodeFilter());
|
|
217
243
|
try {
|
|
218
244
|
register(new ResponseToolArgumentsSchemaConvergeFilter());
|
|
219
245
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// Unified tool harvesting (对齐, single entry)
|
|
2
2
|
// - First-time harvesting only (no late-stage repair)
|
|
3
|
-
// - Handles textual markers (<function=execute>,
|
|
3
|
+
// - Handles textual markers (<function=execute>, structured apply_patch payloads)
|
|
4
4
|
// - Handles structural shapes (function_call legacy, tool_calls)
|
|
5
5
|
// - Normalizes arguments (single JSON string), sets finish_reason when applicable
|
|
6
|
+
import { isStructuredApplyPatchPayload } from '../../tools/apply-patch-structured.js';
|
|
6
7
|
function isObject(v) {
|
|
7
8
|
return !!v && typeof v === 'object' && !Array.isArray(v);
|
|
8
9
|
}
|
|
@@ -48,17 +49,19 @@ function extractFromTextual(content, ctx) {
|
|
|
48
49
|
const events = [];
|
|
49
50
|
if (typeof content !== 'string' || !content)
|
|
50
51
|
return events;
|
|
51
|
-
// 1)
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
52
|
+
// 1) structured apply_patch payload
|
|
53
|
+
const structuredPayloads = extractStructuredApplyPatchPayloads(content);
|
|
54
|
+
if (structuredPayloads.length) {
|
|
55
|
+
let idx = 0;
|
|
56
|
+
for (const payload of structuredPayloads) {
|
|
57
|
+
const id = genId(ctx, idx);
|
|
58
|
+
const argStr = toJsonString(payload);
|
|
59
|
+
events.push({ tool_calls: [{ index: idx, id, type: 'function', function: { name: 'apply_patch' } }] });
|
|
60
|
+
const parts = chunkString(argStr, Math.max(32, Math.min(1024, ctx?.chunkSize || 256)));
|
|
61
|
+
for (const d of parts) {
|
|
62
|
+
events.push({ tool_calls: [{ index: idx, id, type: 'function', function: { arguments: d } }] });
|
|
63
|
+
}
|
|
64
|
+
idx += 1;
|
|
62
65
|
}
|
|
63
66
|
return events;
|
|
64
67
|
}
|
|
@@ -157,6 +160,34 @@ function extractFromTextual(content, ctx) {
|
|
|
157
160
|
catch { /* ignore textual tool_call parse errors */ }
|
|
158
161
|
return events;
|
|
159
162
|
}
|
|
163
|
+
function extractStructuredApplyPatchPayloads(text) {
|
|
164
|
+
const payloads = [];
|
|
165
|
+
try {
|
|
166
|
+
const fenceRe = /```(?:json|apply_patch|toon)?\s*([\s\S]*?)\s*```/gi;
|
|
167
|
+
let fm;
|
|
168
|
+
while ((fm = fenceRe.exec(text)) !== null) {
|
|
169
|
+
const body = fm[1] || '';
|
|
170
|
+
try {
|
|
171
|
+
const parsed = JSON.parse(body);
|
|
172
|
+
if (isStructuredApplyPatchPayload(parsed)) {
|
|
173
|
+
payloads.push(parsed);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch { /* ignore invalid JSON */ }
|
|
177
|
+
}
|
|
178
|
+
if (!payloads.length && typeof text === 'string' && text.includes('"changes"')) {
|
|
179
|
+
try {
|
|
180
|
+
const parsed = JSON.parse(text);
|
|
181
|
+
if (isStructuredApplyPatchPayload(parsed)) {
|
|
182
|
+
payloads.push(parsed);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch { /* ignore */ }
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
catch { /* ignore */ }
|
|
189
|
+
return payloads;
|
|
190
|
+
}
|
|
160
191
|
function splitCommand(s) {
|
|
161
192
|
try {
|
|
162
193
|
const out = [];
|
|
@@ -13,6 +13,7 @@ export declare function stringifyArgs(args: unknown): string;
|
|
|
13
13
|
export interface BridgeToolMapOptions {
|
|
14
14
|
sanitizeName?: (raw: unknown) => string | undefined;
|
|
15
15
|
}
|
|
16
|
+
export declare function ensureApplyPatchSchema(seed?: Record<string, unknown>): Record<string, unknown>;
|
|
16
17
|
export declare function bridgeToolToChatDefinition(rawTool: BridgeToolDefinition | Record<string, unknown> | null | undefined, options?: BridgeToolMapOptions): ChatToolDefinition | null;
|
|
17
18
|
export declare function mapBridgeToolsToChat(rawTools: unknown, options?: BridgeToolMapOptions): ChatToolDefinition[] | undefined;
|
|
18
19
|
export declare function chatToolToBridgeDefinition(rawTool: ChatToolDefinition | Record<string, unknown> | null | undefined, options?: BridgeToolMapOptions): BridgeToolDefinition | null;
|
|
@@ -25,30 +25,44 @@ function asSchema(value) {
|
|
|
25
25
|
}
|
|
26
26
|
return undefined;
|
|
27
27
|
}
|
|
28
|
-
function ensureApplyPatchSchema(seed) {
|
|
28
|
+
export function ensureApplyPatchSchema(seed) {
|
|
29
29
|
const schema = seed ? { ...seed } : {};
|
|
30
30
|
schema.type = typeof schema.type === 'string' ? schema.type : 'object';
|
|
31
31
|
const properties = isPlainObject(schema.properties) ? { ...schema.properties } : {};
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
properties.
|
|
35
|
-
type: '
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
properties.file = { type: 'string', description: 'Optional default file path for all changes' };
|
|
33
|
+
properties.instructions = { type: 'string', description: 'Optional summary of the edit' };
|
|
34
|
+
properties.changes = {
|
|
35
|
+
type: 'array',
|
|
36
|
+
minItems: 1,
|
|
37
|
+
items: {
|
|
38
|
+
type: 'object',
|
|
39
|
+
additionalProperties: false,
|
|
40
|
+
required: ['kind'],
|
|
41
|
+
properties: {
|
|
42
|
+
file: { type: 'string', description: 'Relative path for this change' },
|
|
43
|
+
kind: {
|
|
44
|
+
type: 'string',
|
|
45
|
+
description: 'insert_after | insert_before | replace | delete | create_file | delete_file'
|
|
46
|
+
},
|
|
47
|
+
anchor: { type: 'string', description: 'Context snippet for insert operations' },
|
|
48
|
+
target: { type: 'string', description: 'Snippet to replace/delete' },
|
|
49
|
+
lines: {
|
|
50
|
+
description: 'New content for insert/replace/create operations',
|
|
51
|
+
oneOf: [
|
|
52
|
+
{ type: 'string' },
|
|
53
|
+
{ type: 'array', items: { type: 'string' } }
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
use_anchor_indent: { type: 'boolean', description: 'Reuse indentation from the anchor snippet' }
|
|
57
|
+
}
|
|
58
|
+
}
|
|
40
59
|
};
|
|
41
|
-
if (!isPlainObject(properties.paths)) {
|
|
42
|
-
properties.paths = {
|
|
43
|
-
type: 'array',
|
|
44
|
-
description: 'Optional explicit list of relative file paths that the patch touches. Each entry must be a relative workspace path (packages/foo/file.ts).',
|
|
45
|
-
items: { type: 'string' }
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
60
|
schema.properties = properties;
|
|
49
|
-
const requiredList = Array.isArray(schema.required)
|
|
50
|
-
|
|
51
|
-
|
|
61
|
+
const requiredList = Array.isArray(schema.required)
|
|
62
|
+
? schema.required.filter((entry) => typeof entry === 'string')
|
|
63
|
+
: [];
|
|
64
|
+
if (!requiredList.includes('changes')) {
|
|
65
|
+
requiredList.push('changes');
|
|
52
66
|
}
|
|
53
67
|
schema.required = requiredList;
|
|
54
68
|
if (typeof schema.additionalProperties !== 'boolean') {
|
package/dist/filters/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export * from './special/response-tool-arguments-stringify.js';
|
|
|
12
12
|
export * from './special/response-finish-invariants.js';
|
|
13
13
|
export * from './special/request-tools-normalize.js';
|
|
14
14
|
export * from './special/response-tool-arguments-toon-decode.js';
|
|
15
|
+
export * from './special/response-apply-patch-toon-decode.js';
|
|
15
16
|
export * from './special/response-tool-arguments-blacklist.js';
|
|
16
17
|
export * from './special/response-tool-arguments-schema-converge.js';
|
|
17
18
|
export * from './special/response-tool-arguments-whitelist.js';
|
package/dist/filters/index.js
CHANGED
|
@@ -15,6 +15,7 @@ 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
17
|
export * from './special/response-tool-arguments-toon-decode.js';
|
|
18
|
+
export * from './special/response-apply-patch-toon-decode.js';
|
|
18
19
|
// Arguments policy filters (synced)
|
|
19
20
|
export * from './special/response-tool-arguments-blacklist.js';
|
|
20
21
|
export * from './special/response-tool-arguments-schema-converge.js';
|