prior-cli 1.3.4 → 1.3.5
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/lib/agent.js +52 -3
- package/package.json +1 -1
package/lib/agent.js
CHANGED
|
@@ -77,6 +77,22 @@ function parseToolCalls(text) {
|
|
|
77
77
|
} catch { /* skip */ }
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
// Primary variant: <tool name="X">{"args"}</tool> (name as attribute — used by some models)
|
|
81
|
+
const reAttr = /<tool\s+name="([^"]+)"[^>]*>([\s\S]*?)<\/tool>/g;
|
|
82
|
+
while ((m = reAttr.exec(text)) !== null) {
|
|
83
|
+
const alreadyCaptured = calls.some(c => m.index >= c.offset && m.index < c.offset + c.raw.length);
|
|
84
|
+
if (alreadyCaptured) continue;
|
|
85
|
+
const toolName = m[1];
|
|
86
|
+
try {
|
|
87
|
+
const body = m[2].trim();
|
|
88
|
+
const parsed = body ? JSON.parse(fixJsonLiterals(body)) : {};
|
|
89
|
+
const { args, ...rest } = parsed;
|
|
90
|
+
calls.push({ raw: m[0], offset: m.index, name: toolName, args: args || (Object.keys(rest).length > 0 ? rest : {}) });
|
|
91
|
+
} catch {
|
|
92
|
+
calls.push({ raw: m[0], offset: m.index, name: toolName, args: {} });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
80
96
|
// Fallback 1: <tool_name>{...}</tool_name>
|
|
81
97
|
for (const name of TOOL_NAMES) {
|
|
82
98
|
const fbRe = new RegExp(`<${name}>([\\s\\S]*?)<\\/${name}>`, 'g');
|
|
@@ -109,6 +125,21 @@ function parseToolCalls(text) {
|
|
|
109
125
|
}
|
|
110
126
|
}
|
|
111
127
|
|
|
128
|
+
// Fallback 3: <tool_name key="val" ...>...</tool_name> (HTML attribute style — used by dolphin)
|
|
129
|
+
for (const name of TOOL_NAMES) {
|
|
130
|
+
const fbRe = new RegExp(`<${name}((?:\\s+[a-zA-Z_]\\w*="[^"]*")+)\\s*(?:>[\\s\\S]*?<\\/${name}>|/>)`, 'g');
|
|
131
|
+
let fm;
|
|
132
|
+
while ((fm = fbRe.exec(text)) !== null) {
|
|
133
|
+
const alreadyCaptured = calls.some(c => fm.index >= c.offset && fm.index < c.offset + c.raw.length);
|
|
134
|
+
if (alreadyCaptured) continue;
|
|
135
|
+
const args = {};
|
|
136
|
+
const attrRe = /([a-zA-Z_]\w*)="([^"]*)"/g;
|
|
137
|
+
let am;
|
|
138
|
+
while ((am = attrRe.exec(fm[1])) !== null) args[am[1]] = am[2];
|
|
139
|
+
calls.push({ raw: fm[0], offset: fm.index, name, args });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
112
143
|
return calls;
|
|
113
144
|
}
|
|
114
145
|
|
|
@@ -137,13 +168,31 @@ function stripThink(text) {
|
|
|
137
168
|
return text.replace(/<think>[\s\S]*?<\/think>/gi, '').trim();
|
|
138
169
|
}
|
|
139
170
|
|
|
171
|
+
// Some models (dolphin) hallucinate fake conversation turns after their response.
|
|
172
|
+
// Truncate at common boundary markers to prevent those being parsed as real tool calls.
|
|
173
|
+
function truncateAtFakeTurn(text) {
|
|
174
|
+
const MARKERS = [
|
|
175
|
+
/\n[-─]{3,}\s*\n+(?:user|human)\b/i,
|
|
176
|
+
/\n+(?:user|human)\s*:\s/i,
|
|
177
|
+
/\n+(?:assistant|prior)\s*:\s/i,
|
|
178
|
+
];
|
|
179
|
+
let cut = text.length;
|
|
180
|
+
for (const re of MARKERS) {
|
|
181
|
+
const m = re.exec(text);
|
|
182
|
+
if (m && m.index < cut) cut = m.index;
|
|
183
|
+
}
|
|
184
|
+
return text.slice(0, cut);
|
|
185
|
+
}
|
|
186
|
+
|
|
140
187
|
// Strip any residual tool-call tags the model echoes in its text output
|
|
141
188
|
function stripToolTags(text) {
|
|
142
189
|
// <tool>...</tool>
|
|
143
190
|
let out = text.replace(/<tool>[\s\S]*?<\/tool>/gi, '');
|
|
144
|
-
// <tool_name {...}>...</tool_name> AND <tool_name>...</tool_name>
|
|
145
191
|
const namesPattern = TOOL_NAMES.join('|');
|
|
192
|
+
// <tool_name ...>...</tool_name> (with or without attributes/JSON)
|
|
146
193
|
out = out.replace(new RegExp(`<(?:${namesPattern})[^>]*>[\\s\\S]*?<\\/(?:${namesPattern})>`, 'gi'), '');
|
|
194
|
+
// Self-closing or unclosed: <tool_name attr="val" /> or <tool_name attr="val">
|
|
195
|
+
out = out.replace(new RegExp(`<(?:${namesPattern})(?:\\s[^>]*)?\\s*/?>`, 'gi'), '');
|
|
147
196
|
return out.trim();
|
|
148
197
|
}
|
|
149
198
|
|
|
@@ -187,8 +236,8 @@ async function runAgent({ messages, model, uncensored, cwd, projectContext, send
|
|
|
187
236
|
totalCompletionTokens += result.completionTokens || 0;
|
|
188
237
|
|
|
189
238
|
const raw = result.content;
|
|
190
|
-
const cleaned = stripThink(raw)
|
|
191
|
-
.replace(/<tool_result[\s\S]*?<\/tool_result>/gi, '')
|
|
239
|
+
const cleaned = truncateAtFakeTurn(stripThink(raw)
|
|
240
|
+
.replace(/<tool_result[\s\S]*?<\/tool_result>/gi, ''))
|
|
192
241
|
.trim();
|
|
193
242
|
|
|
194
243
|
const calls = [
|