@lazyingart/agintiflow 0.20.50 → 0.20.51

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.50",
3
+ "version": "0.20.51",
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",
@@ -85,6 +85,52 @@ try {
85
85
  !staleDeepSeekState.messages.some((message) => message.role === "tool" && message.tool_call_id === "stale-call"),
86
86
  "repaired DeepSeek history retained an orphan stale tool message"
87
87
  );
88
+ const interruptedDeepSeekState = {
89
+ messages: [
90
+ { role: "system", content: "system" },
91
+ { role: "user", content: "old request" },
92
+ {
93
+ role: "assistant",
94
+ content: "Running checks.",
95
+ reasoning_content: "Need shell evidence.",
96
+ tool_calls: [
97
+ { id: "call-a", type: "function", function: { name: "run_command", arguments: "{}" } },
98
+ { id: "call-b", type: "function", function: { name: "run_command", arguments: "{}" } },
99
+ ],
100
+ },
101
+ { role: "tool", tool_call_id: "call-a", content: "{\"ok\":true}" },
102
+ { role: "user", content: "Continue with this new request: /review" },
103
+ ],
104
+ };
105
+ const interruptedRepair = repairModelMessageHistory(interruptedDeepSeekState, { provider: "deepseek" });
106
+ assert(interruptedRepair.changed, "interrupted tool-call history was not repaired");
107
+ assert(interruptedRepair.incompleteToolCallMessages === 1, "interrupted repair did not count the incomplete tool call");
108
+ assert(
109
+ !interruptedDeepSeekState.messages.some((message) => Array.isArray(message.tool_calls) && message.tool_calls.length > 0),
110
+ "interrupted repair retained incomplete assistant tool calls"
111
+ );
112
+ assert(
113
+ !interruptedDeepSeekState.messages.some((message) => message.role === "tool"),
114
+ "interrupted repair retained orphan partial tool result"
115
+ );
116
+ assert(
117
+ interruptedDeepSeekState.messages.at(-1)?.content === "Continue with this new request: /review",
118
+ "interrupted repair dropped the new user request"
119
+ );
120
+ const completeToolState = {
121
+ messages: [
122
+ { role: "system", content: "system" },
123
+ {
124
+ role: "assistant",
125
+ content: "Using a tool.",
126
+ tool_calls: [{ id: "call-ok", type: "function", function: { name: "list_files", arguments: "{}" } }],
127
+ },
128
+ { role: "tool", tool_call_id: "call-ok", content: "{\"ok\":true}" },
129
+ { role: "user", content: "next" },
130
+ ],
131
+ };
132
+ const completeRepair = repairModelMessageHistory(completeToolState, { provider: "openai" });
133
+ assert(!completeRepair.changed, "complete OpenAI-format tool history should not be modified");
88
134
  const patchRoute = selectModelRoute({
89
135
  routingMode: "smart",
90
136
  provider: "deepseek",
@@ -190,32 +190,68 @@ function preserveAssistantMessage(message) {
190
190
  }
191
191
 
192
192
  export function repairModelMessageHistory(state, config = {}) {
193
- if (config.provider !== "deepseek" || !Array.isArray(state?.messages)) {
194
- return { changed: false, droppedAssistantMessages: 0, convertedAssistantMessages: 0, droppedToolMessages: 0 };
193
+ if (!Array.isArray(state?.messages)) {
194
+ return {
195
+ changed: false,
196
+ droppedAssistantMessages: 0,
197
+ convertedAssistantMessages: 0,
198
+ droppedToolMessages: 0,
199
+ incompleteToolCallMessages: 0,
200
+ };
195
201
  }
196
202
 
197
203
  const repaired = [];
198
- const droppedToolCallIds = new Set();
199
204
  let droppedAssistantMessages = 0;
200
205
  let convertedAssistantMessages = 0;
201
206
  let droppedToolMessages = 0;
207
+ let incompleteToolCallMessages = 0;
202
208
 
203
- for (const message of state.messages) {
209
+ for (let index = 0; index < state.messages.length; index += 1) {
210
+ const message = state.messages[index];
204
211
  if (message.role === "assistant") {
205
212
  const hasReasoningContent = Boolean(message.reasoning_content || message.reasoningContent);
206
213
  const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
207
214
  const content = String(message.content || "");
215
+ const requiresDeepSeekReasoning = config.provider === "deepseek" && !hasReasoningContent;
216
+
217
+ if (toolCalls.length > 0) {
218
+ const expectedIds = toolCalls.map((call) => String(call?.id || "")).filter(Boolean);
219
+ const expected = new Set(expectedIds);
220
+ const followingToolMessages = [];
221
+ const duplicateOrUnexpectedToolMessages = [];
222
+ const seen = new Set();
223
+ let cursor = index + 1;
224
+
225
+ while (cursor < state.messages.length && state.messages[cursor]?.role === "tool") {
226
+ const toolMessage = state.messages[cursor];
227
+ const toolCallId = String(toolMessage.tool_call_id || "");
228
+ if (expected.has(toolCallId) && !seen.has(toolCallId)) {
229
+ followingToolMessages.push(toolMessage);
230
+ seen.add(toolCallId);
231
+ } else {
232
+ duplicateOrUnexpectedToolMessages.push(toolMessage);
233
+ }
234
+ cursor += 1;
235
+ }
208
236
 
209
- if (!hasReasoningContent) {
210
- if (content.startsWith("Execution plan:")) {
237
+ const completeToolResults = expectedIds.length > 0 && expectedIds.every((id) => seen.has(id));
238
+ if (requiresDeepSeekReasoning || !completeToolResults) {
211
239
  droppedAssistantMessages += 1;
240
+ droppedToolMessages += followingToolMessages.length + duplicateOrUnexpectedToolMessages.length;
241
+ if (!completeToolResults) incompleteToolCallMessages += 1;
242
+ index = cursor - 1;
212
243
  continue;
213
244
  }
214
245
 
215
- if (toolCalls.length > 0) {
216
- for (const call of toolCalls) {
217
- if (call?.id) droppedToolCallIds.add(call.id);
218
- }
246
+ repaired.push(preserveAssistantMessage(message));
247
+ repaired.push(...followingToolMessages);
248
+ droppedToolMessages += duplicateOrUnexpectedToolMessages.length;
249
+ index = cursor - 1;
250
+ continue;
251
+ }
252
+
253
+ if (requiresDeepSeekReasoning) {
254
+ if (content.startsWith("Execution plan:")) {
219
255
  droppedAssistantMessages += 1;
220
256
  continue;
221
257
  }
@@ -236,7 +272,7 @@ export function repairModelMessageHistory(state, config = {}) {
236
272
  continue;
237
273
  }
238
274
 
239
- if (message.role === "tool" && droppedToolCallIds.has(message.tool_call_id)) {
275
+ if (message.role === "tool") {
240
276
  droppedToolMessages += 1;
241
277
  continue;
242
278
  }
@@ -259,6 +295,7 @@ export function repairModelMessageHistory(state, config = {}) {
259
295
  droppedAssistantMessages,
260
296
  convertedAssistantMessages,
261
297
  droppedToolMessages,
298
+ incompleteToolCallMessages,
262
299
  };
263
300
  }
264
301
 
@@ -1314,6 +1351,13 @@ export async function runAgent(config) {
1314
1351
 
1315
1352
  ensureChatState(state);
1316
1353
 
1354
+ const initialRepair = repairModelMessageHistory(state, config);
1355
+ if (initialRepair.changed) {
1356
+ await store.appendEvent("history.repaired", initialRepair);
1357
+ observers.event("history.repaired", initialRepair);
1358
+ await store.saveState(state);
1359
+ }
1360
+
1317
1361
  observers.log("session.ready", {
1318
1362
  sessionId,
1319
1363
  provider: config.provider,