@lazyingart/agintiflow 0.20.5 → 0.20.6

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.
@@ -52,7 +52,7 @@ In the interactive CLI, `/provider`, `/route`, `/model`, `/spare`, and `/auxilia
52
52
 
53
53
  ```text
54
54
  venice/venice-uncensored-1-2
55
- venice/e2ee-venice-uncensored-24b-p
55
+ venice/venice-uncensored
56
56
  venice/gemma-4-uncensored
57
57
  ```
58
58
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.5",
3
+ "version": "0.20.6",
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",
@@ -53,7 +53,7 @@ Interactive equivalents:
53
53
  /auxiliary model grsai/nano-banana-2
54
54
  ```
55
55
 
56
- `/venice` opens a route/main selector for Venice text models. The selector includes `venice/venice-uncensored-1-2` (Venice 1.2), `venice/e2ee-venice-uncensored-24b-p` (Venice 1.1), and `venice/gemma-4-uncensored` (Gemma 4). In non-interactive shells, `/venice` keeps script compatibility by selecting `venice/venice-uncensored-1-2` for both roles. `/venice 1.2 gemma` sets route to Venice 1.2 and main to Gemma 4; `/venice off` restores `deepseek/deepseek-v4-flash` for route and `deepseek/deepseek-v4-pro` for main.
56
+ `/venice` opens a route/main selector for Venice text models. The selector includes `venice/venice-uncensored-1-2` (Venice 1.2), `venice/venice-uncensored` (Venice 1.1), and `venice/gemma-4-uncensored` (Gemma 4). In non-interactive shells, `/venice` keeps script compatibility by selecting `venice/venice-uncensored-1-2` for both roles. `/venice 1.2 gemma` sets route to Venice 1.2 and main to Gemma 4; `/venice off` restores `deepseek/deepseek-v4-flash` for route and `deepseek/deepseek-v4-pro` for main.
57
57
 
58
58
  The web UI should expose model names as dropdowns, not free-text fields. The left panel should stay focused on common daily controls, while model-role editing and less-used switches live in an Advanced settings modal. The terminal-like capability panels belong after the runtime log so the left control panel remains short.
59
59
 
@@ -74,7 +74,7 @@ Codex wrapper defaults stay separate from native OpenAI API settings: GPT-5.5 me
74
74
 
75
75
  | UI bucket | Concrete default | Notes |
76
76
  |---|---|---|
77
- | `venice-uncensored` | `venice-uncensored-1-2` | Venice-native text; `/venice` also exposes `e2ee-venice-uncensored-24b-p` as Venice 1.1 |
77
+ | `venice-uncensored` | `venice-uncensored-1-2` | Venice-native text; `/venice` also exposes `venice-uncensored` as Venice 1.1 |
78
78
  | `venice-qwen` | `qwen3-6-27b` | Qwen-family text/code |
79
79
  | `venice-gpt` | `openai-gpt-55` | OpenAI-family through Venice |
80
80
  | `venice-claude` | `claude-sonnet-4-6` | Claude-family through Venice |
@@ -52,7 +52,7 @@ Store `VENICE_API_KEY` only in ignored local files such as `.aginti/.env` or a s
52
52
  | `openai-gpt-52` | 256K | GPT-5.2 |
53
53
  | `venice-uncensored` | 32K | Venice Uncensored legacy / deprecated |
54
54
 
55
- AgInTiFlow exposes the primary Venice text choices through `/venice`: Venice 1.2 (`venice-uncensored-1-2`), Venice 1.1 (`e2ee-venice-uncensored-24b-p`), and Gemma 4 (`gemma-4-uncensored`). The command can select route and main independently, so a fast Venice route can be paired with a larger Gemma main model when useful. The older `venice-uncensored` ID is retained only as a legacy alias.
55
+ AgInTiFlow exposes the primary Venice text choices through `/venice`: Venice 1.2 (`venice-uncensored-1-2`), Venice 1.1 (`venice-uncensored`), and Gemma 4 (`gemma-4-uncensored`). The command can select route and main independently, so a fast Venice route can be paired with a larger Gemma main model when useful. The E2EE 1.1 ID (`e2ee-venice-uncensored-24b-p`) is documented for reference, but the selector uses the working non-E2EE 1.1 route because the E2EE route currently returns an upstream provider error in live tests.
56
56
 
57
57
  ## Image And Edit Models
58
58
 
@@ -9,7 +9,7 @@ import {
9
9
  modelsForProviderGroup,
10
10
  selectModelRoute,
11
11
  } from "../src/model-routing.js";
12
- import { parseTextToolCalls } from "../src/model-client.js";
12
+ import { parseTextToolCalls, usesTextToolProtocol } from "../src/model-client.js";
13
13
 
14
14
  const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
15
15
 
@@ -115,6 +115,18 @@ const parsedTextToolCalls = parseTextToolCalls('[TOOL_CALLS]list_files[ARGS]call
115
115
  assert(parsedTextToolCalls.length === 1, "Venice text tool-call parser did not detect encoded tool call");
116
116
  assert(parsedTextToolCalls[0].function.name === "list_files", "Venice text tool-call parser returned wrong tool name");
117
117
  assert(parsedTextToolCalls[0].function.arguments.includes('"maxDepth":1'), "Venice text tool-call parser returned wrong arguments");
118
+ const looseTextToolCalls = parseTextToolCalls('[TOOL_CALLS]list_files[ARGS]{"path":"."}[TOOL_CALLS]inspect_project[ARGS]{"path":"."}');
119
+ assert(looseTextToolCalls.length === 2, "Venice loose text tool-call parser did not detect multiple calls");
120
+ assert(looseTextToolCalls[1].function.name === "inspect_project", "Venice loose text tool-call parser returned wrong second tool");
121
+ const nativeMarkerText = parseTextToolCalls('Done. <|tool_call>call:finish{result:<|"|>Done<|"|>}');
122
+ assert(nativeMarkerText.length === 0, "native marker text should not be treated as JSON text tool call");
123
+ const jsonBlockToolCalls = parseTextToolCalls('TOOL_CALLS:\n```json\n[{"name":"list_files","arguments":{"path":"/workspace"}}]\n```');
124
+ assert(jsonBlockToolCalls.length === 1, "Venice JSON text tool-call parser did not detect JSON block calls");
125
+ assert(jsonBlockToolCalls[0].function.arguments.includes("/workspace"), "Venice JSON text tool-call parser returned wrong arguments");
126
+ assert(usesTextToolProtocol({ provider: "venice", model: "gemma-4-uncensored" }), "Venice Gemma should use text tool protocol");
127
+ assert(usesTextToolProtocol({ provider: "venice", model: "e2ee-venice-uncensored-24b-p" }), "Venice 1.1 should use text tool protocol");
128
+ assert(usesTextToolProtocol({ provider: "venice", model: "venice-uncensored" }), "Venice legacy 1.1 should use text tool protocol");
129
+ assert(!usesTextToolProtocol({ provider: "venice", model: "venice-uncensored-1-2" }), "Venice 1.2 should keep native tool calls first");
118
130
 
119
131
  const output = await runCli(["models"]);
120
132
  assert(output.includes("/route") && output.includes("/spare") && output.includes("venice-gpt"), "aginti models output missing role details");
@@ -124,7 +136,7 @@ assert(interactiveOutput.includes("venice=on"), "/venice did not enable Venice r
124
136
  assert(interactiveOutput.includes("route=venice/venice-uncensored-1-2"), "/venice did not set Venice route role");
125
137
  assert(interactiveOutput.includes("main=venice/venice-uncensored-1-2"), "/venice did not set Venice main role");
126
138
  const interactiveGemmaOutput = await runInteractive("/venice 1.1 gemma\n");
127
- assert(interactiveGemmaOutput.includes("route=venice/e2ee-venice-uncensored-24b-p"), "/venice 1.1 did not set Venice 1.1 route role");
139
+ assert(interactiveGemmaOutput.includes("route=venice/venice-uncensored"), "/venice 1.1 did not set Venice 1.1 route role");
128
140
  assert(interactiveGemmaOutput.includes("main=venice/gemma-4-uncensored"), "/venice gemma did not set Gemma 4 main role");
129
141
  const interactiveOffOutput = await runInteractive("/venice off\n");
130
142
  assert(interactiveOffOutput.includes("venice=off"), "/venice off did not restore DeepSeek roles");
@@ -739,7 +739,7 @@ async function buildSnapshot(browserState, store, step, config) {
739
739
 
740
740
  async function injectQueuedUserMessages(store, state, observers) {
741
741
  const inbox = await store.drainInbox();
742
- if (inbox.length === 0) return;
742
+ if (inbox.length === 0) return 0;
743
743
 
744
744
  for (const item of inbox) {
745
745
  const content = String(item.content || "").trim();
@@ -762,6 +762,7 @@ async function injectQueuedUserMessages(store, state, observers) {
762
762
  priority: item.priority || "normal",
763
763
  });
764
764
  }
765
+ return inbox.length;
765
766
  }
766
767
 
767
768
  async function executeTool(browserState, toolCall, snapshot, config, store, observers, state) {
@@ -1374,6 +1375,16 @@ export async function runAgent(config) {
1374
1375
  });
1375
1376
 
1376
1377
  throwIfAborted(config);
1378
+ await store.appendEvent("model.requested", {
1379
+ step,
1380
+ provider: config.provider,
1381
+ model: config.model,
1382
+ });
1383
+ observers.event("model.requested", {
1384
+ step,
1385
+ provider: config.provider,
1386
+ model: config.model,
1387
+ });
1377
1388
  const response = await requestNextStep(client, config, state.messages);
1378
1389
  const assistantMessage = response.choices[0]?.message;
1379
1390
  if (!assistantMessage) {
@@ -1399,6 +1410,13 @@ export async function runAgent(config) {
1399
1410
  const toolCalls = assistantMessage.tool_calls || [];
1400
1411
 
1401
1412
  if (toolCalls.length === 0) {
1413
+ const queuedCount = await injectQueuedUserMessages(store, state, observers);
1414
+ if (queuedCount > 0) {
1415
+ state.stepsCompleted = step;
1416
+ state.updatedAt = new Date().toISOString();
1417
+ await store.saveState(state);
1418
+ continue;
1419
+ }
1402
1420
  const fallback = assistantMessage.content?.trim() || "No tool call returned.";
1403
1421
  appendChatEntry(state, "assistant", fallback);
1404
1422
  await store.appendEvent("session.finished", {
@@ -1419,6 +1437,7 @@ export async function runAgent(config) {
1419
1437
  };
1420
1438
  }
1421
1439
 
1440
+ let continueForQueuedInput = false;
1422
1441
  for (const toolCall of toolCalls) {
1423
1442
  throwIfAborted(config);
1424
1443
  const toolResult = await executeTool(browserState, toolCall, snapshot, config, store, observers, state);
@@ -1462,6 +1481,14 @@ export async function runAgent(config) {
1462
1481
  }
1463
1482
 
1464
1483
  if (toolResult.done) {
1484
+ const queuedCount = await injectQueuedUserMessages(store, state, observers);
1485
+ if (queuedCount > 0) {
1486
+ state.stepsCompleted = step;
1487
+ state.updatedAt = new Date().toISOString();
1488
+ await store.saveState(state);
1489
+ continueForQueuedInput = true;
1490
+ break;
1491
+ }
1465
1492
  state.stepsCompleted = step;
1466
1493
  state.updatedAt = new Date().toISOString();
1467
1494
  state.meta.lastUrl = browserState.page?.url() || state.meta.lastUrl;
@@ -1487,6 +1514,8 @@ export async function runAgent(config) {
1487
1514
  }
1488
1515
  }
1489
1516
 
1517
+ if (continueForQueuedInput) continue;
1518
+
1490
1519
  await injectQueuedUserMessages(store, state, observers);
1491
1520
 
1492
1521
  state.stepsCompleted = step;
@@ -1650,9 +1650,9 @@ function veniceTextModelChoices() {
1650
1650
  },
1651
1651
  {
1652
1652
  provider: "venice",
1653
- model: "e2ee-venice-uncensored-24b-p",
1653
+ model: "venice-uncensored",
1654
1654
  label: "Venice 1.1",
1655
- description: "E2EE Venice text model; 32K context",
1655
+ description: "legacy Venice text model; 32K context",
1656
1656
  },
1657
1657
  {
1658
1658
  provider: "venice",
@@ -1670,14 +1670,16 @@ function resolveVeniceTextModel(value = "") {
1670
1670
  }
1671
1671
  if (
1672
1672
  normalized === "1.1" ||
1673
- normalized === "venice-1.1" ||
1674
- normalized === "e2ee-venice-uncensored-24b-p"
1673
+ normalized === "venice-1.1"
1675
1674
  ) {
1676
- return "e2ee-venice-uncensored-24b-p";
1675
+ return "venice-uncensored";
1677
1676
  }
1678
1677
  if (normalized === "legacy" || normalized === "venice-uncensored") {
1679
1678
  return "venice-uncensored";
1680
1679
  }
1680
+ if (normalized === "e2ee" || normalized === "e2ee-venice-uncensored-24b-p") {
1681
+ return "e2ee-venice-uncensored-24b-p";
1682
+ }
1681
1683
  if (normalized === "gemma" || normalized === "gemma4" || normalized === "gemma-4" || normalized === "gemma-4-uncensored") {
1682
1684
  return "gemma-4-uncensored";
1683
1685
  }
@@ -1781,9 +1783,9 @@ function modelRoleChoices(role = "main") {
1781
1783
  },
1782
1784
  {
1783
1785
  provider: "venice",
1784
- model: "e2ee-venice-uncensored-24b-p",
1786
+ model: "venice-uncensored",
1785
1787
  label: "Venice 1.1",
1786
- description: "E2EE Venice text route; use /auth venice if missing",
1788
+ description: "legacy Venice text route; use /auth venice if missing",
1787
1789
  route: true,
1788
1790
  main: true,
1789
1791
  spare: true,
@@ -1930,8 +1932,11 @@ function clearSelector(lineCount) {
1930
1932
  }
1931
1933
  }
1932
1934
 
1935
+ function clearSelectorSequence(lineCount) {
1936
+ return Array.from({ length: Math.max(lineCount, 0) }, () => "\x1b[1A\r\x1b[2K").join("");
1937
+ }
1938
+
1933
1939
  function renderSelector({ title, subtitle, options, selectedIndex, lineCount = 0 }) {
1934
- if (lineCount > 0) clearSelector(lineCount);
1935
1940
  const width = Math.min(Math.max(terminalWidth() - 2, 60), 110);
1936
1941
  const bodyWidth = width - 4;
1937
1942
  const safeTitle = compactLine(title, bodyWidth);
@@ -1948,7 +1953,7 @@ function renderSelector({ title, subtitle, options, selectedIndex, lineCount = 0
1948
1953
  }),
1949
1954
  `╰${"─".repeat(width - 2)}╯`,
1950
1955
  ];
1951
- output.write(`${rows.join("\n")}\n`);
1956
+ output.write(`${lineCount > 0 ? clearSelectorSequence(lineCount) : ""}${rows.join("\n")}\n`);
1952
1957
  return rows.length;
1953
1958
  }
1954
1959
 
@@ -2639,6 +2644,7 @@ async function runPrompt(prompt, state, packageDir) {
2639
2644
  printSystemLine(`status=running workingOn=${state.activeGoal}`);
2640
2645
  }
2641
2646
  let result;
2647
+ let runError = null;
2642
2648
  let queuedAfterFinish = [];
2643
2649
  try {
2644
2650
  result = await runAgent({
@@ -2666,6 +2672,8 @@ async function runPrompt(prompt, state, packageDir) {
2666
2672
  onEvent: (type, data = {}) => {
2667
2673
  if (type === "plan.created") {
2668
2674
  printStatusEvent(state, "planned");
2675
+ } else if (type === "model.requested") {
2676
+ printStatusEvent(state, "model_wait", `${data.provider || "model"}/${data.model || ""}`);
2669
2677
  } else if (type === "tool.started") {
2670
2678
  printStatusEvent(state, "tool", data.toolName || "unknown");
2671
2679
  } else if (type === "tool.completed") {
@@ -2688,10 +2696,18 @@ async function runPrompt(prompt, state, packageDir) {
2688
2696
  }
2689
2697
  },
2690
2698
  });
2699
+ } catch (error) {
2700
+ runError = error;
2691
2701
  } finally {
2692
2702
  detachInterrupts();
2693
2703
  queuedAfterFinish = await liveInput.stop();
2694
2704
  }
2705
+ if (runError) {
2706
+ state.status = isAbortError(runError) ? "stopped" : "failed";
2707
+ state.activeGoal = "";
2708
+ printSystemLine(`status=${state.status} session=${state.sessionId}`);
2709
+ throw runError;
2710
+ }
2695
2711
  state.sessionId = result.sessionId || state.sessionId;
2696
2712
  state.status = result.stopped ? "stopped" : "idle";
2697
2713
  state.activeGoal = "";
@@ -73,16 +73,110 @@ function toolChoiceForProvider(config, messages = []) {
73
73
  return messages.some((message) => message.role === "tool") ? "auto" : "required";
74
74
  }
75
75
 
76
+ export function usesTextToolProtocol(config = {}) {
77
+ if (config.provider !== "venice") return false;
78
+ const model = String(config.model || "").toLowerCase();
79
+ return model === "gemma-4-uncensored" || model === "e2ee-venice-uncensored-24b-p" || model === "venice-uncensored";
80
+ }
81
+
82
+ function shouldRetryWithTextToolProtocol(error, config = {}) {
83
+ if (config.provider !== "venice") return false;
84
+ const message = [
85
+ error?.message,
86
+ error?.error?.message,
87
+ error?.response?.data?.error?.message,
88
+ error?.response?.data?.message,
89
+ ]
90
+ .filter(Boolean)
91
+ .join(" ");
92
+ return /invalid request parameters|tool_choice|parallel_tool_calls|tools/i.test(message);
93
+ }
94
+
95
+ function textToolProtocolPrompt(tools = []) {
96
+ const toolLines = tools.map((tool) => {
97
+ const fn = tool.function || {};
98
+ const properties = fn.parameters?.properties ? Object.keys(fn.parameters.properties).slice(0, 8) : [];
99
+ const required = Array.isArray(fn.parameters?.required) ? fn.parameters.required : [];
100
+ const args = properties.length > 0 ? ` args=${properties.join(",")}${required.length ? ` required=${required.join(",")}` : ""}` : "";
101
+ return `- ${fn.name}: ${String(fn.description || "").slice(0, 180)}${args}`;
102
+ });
103
+ return [
104
+ "This provider/model may not accept native OpenAI function-call parameters.",
105
+ "Use this text tool protocol when you need a tool:",
106
+ '[TOOL_CALLS]tool_name[ARGS]{"arg":"value"}',
107
+ 'A strict id form is also accepted: [TOOL_CALLS]tool_name[ARGS]call_short_id[ARGS]{"arg":"value"}',
108
+ 'A JSON block form is accepted too: TOOL_CALLS: ```json [{"name":"tool_name","arguments":{"arg":"value"}}] ```',
109
+ "Return only one or more TOOL_CALLS blocks when calling tools; do not wrap them in markdown.",
110
+ "If no tool is needed, answer normally.",
111
+ "Available text tools:",
112
+ ...toolLines,
113
+ ].join("\n");
114
+ }
115
+
116
+ function messagesWithTextToolProtocol(config, messages, tools) {
117
+ const prepared = prepareMessages(config, messages).map((message) => {
118
+ if (message.role === "tool") {
119
+ return {
120
+ role: "user",
121
+ content: `Tool result for ${message.tool_call_id || "previous tool"}:\n${message.content || ""}`,
122
+ };
123
+ }
124
+ if (message.role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.length > 0) {
125
+ return {
126
+ role: "assistant",
127
+ content:
128
+ message.content ||
129
+ `Requested tools: ${message.tool_calls
130
+ .map((call) => `${call.function?.name || "tool"}(${call.function?.arguments || "{}"})`)
131
+ .join("; ")}`,
132
+ };
133
+ }
134
+ return message;
135
+ });
136
+ const protocol = { role: "system", content: textToolProtocolPrompt(tools) };
137
+ if (prepared[0]?.role === "system") return [prepared[0], protocol, ...prepared.slice(1)];
138
+ return [protocol, ...prepared];
139
+ }
140
+
76
141
  export function parseTextToolCalls(content = "") {
77
142
  const text = String(content || "");
78
- if (!text.includes("[TOOL_CALLS]")) return [];
143
+ if (!text.includes("[TOOL_CALLS]") && !/TOOL_CALLS\s*:/i.test(text)) return [];
79
144
 
80
145
  const calls = [];
81
- const pattern = /\[TOOL_CALLS\]([A-Za-z0-9_-]+)\[ARGS\]([A-Za-z0-9_.:-]+)\[ARGS\]([\s\S]*?)(?=\[TOOL_CALLS\]|$)/g;
82
- for (const match of text.matchAll(pattern)) {
83
- const name = match[1]?.trim();
84
- const id = match[2]?.trim() || `text-tool-${calls.length + 1}`;
85
- const rawArgs = match[3]?.trim() || "{}";
146
+ const jsonBlock = text.match(/TOOL_CALLS\s*:\s*```(?:json)?\s*([\s\S]*?)```/i);
147
+ if (jsonBlock?.[1]) {
148
+ try {
149
+ const parsed = JSON.parse(jsonBlock[1].trim());
150
+ if (Array.isArray(parsed)) {
151
+ for (const item of parsed) {
152
+ const name = String(item?.name || item?.tool || "").trim();
153
+ if (!name) continue;
154
+ const args = item?.arguments && typeof item.arguments === "object" ? item.arguments : {};
155
+ calls.push({
156
+ id: String(item?.id || `text-tool-${calls.length + 1}`),
157
+ type: "function",
158
+ function: {
159
+ name,
160
+ arguments: JSON.stringify(args),
161
+ },
162
+ });
163
+ }
164
+ }
165
+ } catch {
166
+ // Fall through to bracket parser below.
167
+ }
168
+ }
169
+
170
+ for (const chunk of text.split("[TOOL_CALLS]").slice(1)) {
171
+ const match = chunk.match(/^([A-Za-z0-9_-]+)\[ARGS\]([\s\S]*?)$/);
172
+ const name = match?.[1]?.trim();
173
+ let rawArgs = match?.[2]?.trim() || "{}";
174
+ let id = `text-tool-${calls.length + 1}`;
175
+ const strictParts = rawArgs.split("[ARGS]");
176
+ if (strictParts.length >= 2 && !rawArgs.startsWith("{") && !rawArgs.startsWith("[")) {
177
+ id = strictParts.shift()?.trim() || id;
178
+ rawArgs = strictParts.join("[ARGS]").trim() || "{}";
179
+ }
86
180
  if (!name) continue;
87
181
  try {
88
182
  JSON.parse(rawArgs);
@@ -101,14 +195,39 @@ export function parseTextToolCalls(content = "") {
101
195
  return calls;
102
196
  }
103
197
 
198
+ function textBeforeToolCallMarker(content = "") {
199
+ return String(content || "")
200
+ .split("[TOOL_CALLS]")[0]
201
+ .split("TOOL_CALLS:")[0]
202
+ .split("<|tool_call>")[0]
203
+ .trim();
204
+ }
205
+
104
206
  function normalizeTextToolCallResponse(response) {
105
207
  const message = response?.choices?.[0]?.message;
106
208
  if (!message || Array.isArray(message.tool_calls) && message.tool_calls.length > 0) return response;
107
209
 
108
210
  const calls = parseTextToolCalls(message.content || "");
109
- if (calls.length === 0) return response;
211
+ if (calls.length === 0) {
212
+ const cleanedContent = textBeforeToolCallMarker(message.content || "");
213
+ if (!cleanedContent || cleanedContent === message.content) return response;
214
+ return {
215
+ ...response,
216
+ choices: response.choices.map((choice, index) =>
217
+ index === 0
218
+ ? {
219
+ ...choice,
220
+ message: {
221
+ ...message,
222
+ content: cleanedContent,
223
+ },
224
+ }
225
+ : choice
226
+ ),
227
+ };
228
+ }
110
229
 
111
- const content = String(message.content || "").split("[TOOL_CALLS]")[0].trim();
230
+ const content = textBeforeToolCallMarker(message.content || "");
112
231
  return {
113
232
  ...response,
114
233
  choices: response.choices.map((choice, index) =>
@@ -919,16 +1038,33 @@ export async function requestNextStep(client, config, messages) {
919
1038
  ]);
920
1039
  }
921
1040
 
922
- const response = await client.chat.completions.create(
923
- {
924
- model: config.model,
925
- temperature: 0,
926
- tool_choice: toolChoiceForProvider(config, messages),
927
- parallel_tool_calls: false,
928
- messages: prepareMessages(config, messages),
929
- tools,
930
- },
931
- requestOptions(config)
932
- );
1041
+ const textToolProtocol = usesTextToolProtocol(config);
1042
+ const nativePayload = {
1043
+ model: config.model,
1044
+ temperature: 0,
1045
+ tool_choice: toolChoiceForProvider(config, messages),
1046
+ parallel_tool_calls: false,
1047
+ messages: prepareMessages(config, messages),
1048
+ tools,
1049
+ };
1050
+ const textPayload = {
1051
+ model: config.model,
1052
+ temperature: 0,
1053
+ messages: messagesWithTextToolProtocol(config, messages, tools),
1054
+ };
1055
+
1056
+ let response;
1057
+ try {
1058
+ response = await client.chat.completions.create(
1059
+ textToolProtocol ? textPayload : nativePayload,
1060
+ requestOptions(config)
1061
+ );
1062
+ } catch (error) {
1063
+ if (!textToolProtocol && shouldRetryWithTextToolProtocol(error, config)) {
1064
+ response = await client.chat.completions.create(textPayload, requestOptions(config));
1065
+ } else {
1066
+ throw error;
1067
+ }
1068
+ }
933
1069
  return normalizeTextToolCallResponse(response);
934
1070
  }