@lazyingart/agintiflow 0.20.12 → 0.20.13
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/package.json +1 -1
- package/scripts/smoke-model-roles.js +29 -1
- package/src/model-client.js +37 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AgInTiFlow is a web-first coding agent and CLI with DeepSeek routing, sandboxed tools, model providers, canvas artifacts, and optional wrappers.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
modelsForProviderGroup,
|
|
10
10
|
selectModelRoute,
|
|
11
11
|
} from "../src/model-routing.js";
|
|
12
|
-
import { parseTextToolCalls, usesTextToolProtocol } from "../src/model-client.js";
|
|
12
|
+
import { normalizeTextToolCallResponse, parseTextToolCalls, usesTextToolProtocol } from "../src/model-client.js";
|
|
13
13
|
import { modelRoleChoices } from "../src/interactive-cli.js";
|
|
14
14
|
|
|
15
15
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
@@ -161,6 +161,33 @@ const multipleRequestedToolCalls = parseTextToolCalls(
|
|
|
161
161
|
);
|
|
162
162
|
assert(multipleRequestedToolCalls.length === 2, "Requested tools parser did not detect multiple function-call texts");
|
|
163
163
|
assert(multipleRequestedToolCalls[1].function.name === "inspect_project", "Requested tools parser returned wrong second requested tool");
|
|
164
|
+
const malformedRequestedToolResponse = normalizeTextToolCallResponse({
|
|
165
|
+
choices: [
|
|
166
|
+
{
|
|
167
|
+
message: {
|
|
168
|
+
role: "assistant",
|
|
169
|
+
content: 'Requested tools: write_file({"path":"story.md","content":"unfinished',
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
});
|
|
174
|
+
const malformedMessage = malformedRequestedToolResponse.choices[0].message;
|
|
175
|
+
assert(malformedMessage.tool_calls?.[0]?.function?.name === "wait", "malformed requested tool text should trigger a safe retry tool");
|
|
176
|
+
assert(!malformedMessage.content.includes("write_file("), "malformed requested tool text should not be surfaced as assistant content");
|
|
177
|
+
const cleanedMalformedSuffix = normalizeTextToolCallResponse({
|
|
178
|
+
choices: [
|
|
179
|
+
{
|
|
180
|
+
message: {
|
|
181
|
+
role: "assistant",
|
|
182
|
+
content: 'Here is a normal answer. Requested tools: write_file({"path":"story.md","content":"unfinished',
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
});
|
|
187
|
+
assert(
|
|
188
|
+
cleanedMalformedSuffix.choices[0].message.content === "Here is a normal answer.",
|
|
189
|
+
"malformed tool suffix should be stripped from otherwise usable assistant content"
|
|
190
|
+
);
|
|
164
191
|
assert(usesTextToolProtocol({ provider: "venice", model: "gemma-4-uncensored" }), "Venice Gemma should use text tool protocol");
|
|
165
192
|
assert(usesTextToolProtocol({ provider: "venice", model: "e2ee-venice-uncensored-24b-p" }), "Venice 1.1 should use text tool protocol");
|
|
166
193
|
assert(usesTextToolProtocol({ provider: "venice", model: "venice-uncensored" }), "Venice legacy 1.1 should use text tool protocol");
|
|
@@ -192,6 +219,7 @@ console.log(
|
|
|
192
219
|
"shared-model-selectors",
|
|
193
220
|
"venice-text-tool-parser",
|
|
194
221
|
"requested-tools-parser",
|
|
222
|
+
"malformed-text-tool-retry",
|
|
195
223
|
"cli-models-command",
|
|
196
224
|
"venice-shortcut",
|
|
197
225
|
],
|
package/src/model-client.js
CHANGED
|
@@ -107,6 +107,7 @@ function textToolProtocolPrompt(tools = []) {
|
|
|
107
107
|
'A strict id form is also accepted: [TOOL_CALLS]tool_name[ARGS]call_short_id[ARGS]{"arg":"value"}',
|
|
108
108
|
'A JSON block form is accepted too: TOOL_CALLS: ```json [{"name":"tool_name","arguments":{"arg":"value"}}] ```',
|
|
109
109
|
"Return only one or more TOOL_CALLS blocks when calling tools; do not wrap them in markdown.",
|
|
110
|
+
"Keep tool-call JSON valid and complete. For long write_file content, prefer a concise complete file or smaller follow-up edits over emitting huge/truncated JSON.",
|
|
110
111
|
"If no tool is needed, answer normally.",
|
|
111
112
|
"Available text tools:",
|
|
112
113
|
...toolLines,
|
|
@@ -140,7 +141,7 @@ function messagesWithTextToolProtocol(config, messages, tools) {
|
|
|
140
141
|
|
|
141
142
|
export function parseTextToolCalls(content = "") {
|
|
142
143
|
const text = String(content || "");
|
|
143
|
-
if (!
|
|
144
|
+
if (!hasTextToolCallMarker(text)) return [];
|
|
144
145
|
|
|
145
146
|
const calls = [];
|
|
146
147
|
for (const call of parseRequestedToolCalls(text)) calls.push(call);
|
|
@@ -196,6 +197,11 @@ export function parseTextToolCalls(content = "") {
|
|
|
196
197
|
return calls;
|
|
197
198
|
}
|
|
198
199
|
|
|
200
|
+
function hasTextToolCallMarker(content = "") {
|
|
201
|
+
const text = String(content || "");
|
|
202
|
+
return text.includes("[TOOL_CALLS]") || /TOOL_CALLS\s*:/i.test(text) || /Requested tools?\s*:/i.test(text);
|
|
203
|
+
}
|
|
204
|
+
|
|
199
205
|
function findMatchingParen(text = "", openIndex = 0) {
|
|
200
206
|
let depth = 0;
|
|
201
207
|
let quote = "";
|
|
@@ -269,14 +275,30 @@ function textBeforeToolCallMarker(content = "") {
|
|
|
269
275
|
.trim();
|
|
270
276
|
}
|
|
271
277
|
|
|
272
|
-
function normalizeTextToolCallResponse(response) {
|
|
278
|
+
export function normalizeTextToolCallResponse(response) {
|
|
273
279
|
const message = response?.choices?.[0]?.message;
|
|
274
280
|
if (!message || Array.isArray(message.tool_calls) && message.tool_calls.length > 0) return response;
|
|
275
281
|
|
|
276
282
|
const calls = parseTextToolCalls(message.content || "");
|
|
277
283
|
if (calls.length === 0) {
|
|
278
284
|
const cleanedContent = textBeforeToolCallMarker(message.content || "");
|
|
279
|
-
if (!
|
|
285
|
+
if (!hasTextToolCallMarker(message.content || "")) return response;
|
|
286
|
+
if (cleanedContent) {
|
|
287
|
+
return {
|
|
288
|
+
...response,
|
|
289
|
+
choices: response.choices.map((choice, index) =>
|
|
290
|
+
index === 0
|
|
291
|
+
? {
|
|
292
|
+
...choice,
|
|
293
|
+
message: {
|
|
294
|
+
...message,
|
|
295
|
+
content: cleanedContent,
|
|
296
|
+
},
|
|
297
|
+
}
|
|
298
|
+
: choice
|
|
299
|
+
),
|
|
300
|
+
};
|
|
301
|
+
}
|
|
280
302
|
return {
|
|
281
303
|
...response,
|
|
282
304
|
choices: response.choices.map((choice, index) =>
|
|
@@ -285,7 +307,18 @@ function normalizeTextToolCallResponse(response) {
|
|
|
285
307
|
...choice,
|
|
286
308
|
message: {
|
|
287
309
|
...message,
|
|
288
|
-
content:
|
|
310
|
+
content:
|
|
311
|
+
"The previous text tool request was malformed or truncated and was not executed. Retry with one valid tool call using the configured tool interface. For long files, write a complete concise file or split the work into smaller valid edits; do not show raw tool-call JSON to the user.",
|
|
312
|
+
tool_calls: [
|
|
313
|
+
{
|
|
314
|
+
id: "text-tool-retry-1",
|
|
315
|
+
type: "function",
|
|
316
|
+
function: {
|
|
317
|
+
name: "wait",
|
|
318
|
+
arguments: JSON.stringify({ ms: 1 }),
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
],
|
|
289
322
|
},
|
|
290
323
|
}
|
|
291
324
|
: choice
|