protoagent 0.1.12 → 0.1.14
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/agentic-loop.js +62 -1
- package/package.json +1 -1
package/dist/agentic-loop.js
CHANGED
|
@@ -235,6 +235,41 @@ function sanitizeMessagesForRetry(messages, validToolNames) {
|
|
|
235
235
|
});
|
|
236
236
|
return { messages: sanitizedMessages, changed };
|
|
237
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Remove orphaned tool result messages that don't have a matching tool_call_id
|
|
240
|
+
* in any assistant message. This happens when messages are truncated and the
|
|
241
|
+
* assistant's tool_calls are removed but the tool results remain.
|
|
242
|
+
*/
|
|
243
|
+
function removeOrphanedToolResults(messages) {
|
|
244
|
+
// Collect all valid tool_call_ids from assistant messages
|
|
245
|
+
const validToolCallIds = new Set();
|
|
246
|
+
for (const message of messages) {
|
|
247
|
+
const msgAny = message;
|
|
248
|
+
if (message.role === 'assistant' && Array.isArray(msgAny.tool_calls)) {
|
|
249
|
+
for (const tc of msgAny.tool_calls) {
|
|
250
|
+
if (tc.id) {
|
|
251
|
+
validToolCallIds.add(tc.id);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// Filter out tool messages with orphaned tool_call_ids
|
|
257
|
+
const filteredMessages = messages.filter((message) => {
|
|
258
|
+
const msgAny = message;
|
|
259
|
+
if (message.role === 'tool' && msgAny.tool_call_id) {
|
|
260
|
+
const isOrphaned = !validToolCallIds.has(msgAny.tool_call_id);
|
|
261
|
+
if (isOrphaned) {
|
|
262
|
+
logger.warn('Removing orphaned tool result', {
|
|
263
|
+
tool_call_id: msgAny.tool_call_id,
|
|
264
|
+
contentPreview: msgAny.content?.slice(0, 100),
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
return !isOrphaned;
|
|
268
|
+
}
|
|
269
|
+
return true;
|
|
270
|
+
});
|
|
271
|
+
return { messages: filteredMessages, changed: filteredMessages.length !== messages.length };
|
|
272
|
+
}
|
|
238
273
|
function getValidToolNames() {
|
|
239
274
|
return new Set([...getAllTools(), subAgentTool]
|
|
240
275
|
.map((tool) => tool.function?.name)
|
|
@@ -281,8 +316,10 @@ export async function runAgenticLoop(client, model, messages, userInput, onEvent
|
|
|
281
316
|
let contextRetryCount = 0;
|
|
282
317
|
let retriggerCount = 0;
|
|
283
318
|
let truncateRetryCount = 0;
|
|
319
|
+
let continueRetryCount = 0;
|
|
284
320
|
const MAX_RETRIGGERS = 3;
|
|
285
321
|
const MAX_TRUNCATE_RETRIES = 5;
|
|
322
|
+
const MAX_CONTINUE_RETRIES = 1;
|
|
286
323
|
const validToolNames = getValidToolNames();
|
|
287
324
|
while (iterationCount < maxIterations) {
|
|
288
325
|
// Check if abort was requested
|
|
@@ -693,6 +730,15 @@ export async function runAgenticLoop(client, model, messages, userInput, onEvent
|
|
|
693
730
|
continue;
|
|
694
731
|
}
|
|
695
732
|
}
|
|
733
|
+
// Try removing orphaned tool results
|
|
734
|
+
const orphanedRemoved = removeOrphanedToolResults(updatedMessages);
|
|
735
|
+
if (orphanedRemoved.changed) {
|
|
736
|
+
updatedMessages.length = 0;
|
|
737
|
+
updatedMessages.push(...orphanedRemoved.messages);
|
|
738
|
+
logger.warn('400 response after orphaned tool results; retrying with cleaned messages');
|
|
739
|
+
// Silently retry without showing error to user
|
|
740
|
+
continue;
|
|
741
|
+
}
|
|
696
742
|
// If sanitization didn't help, try removing messages one at a time (up to 5)
|
|
697
743
|
if (truncateRetryCount < MAX_TRUNCATE_RETRIES) {
|
|
698
744
|
truncateRetryCount++;
|
|
@@ -714,6 +760,21 @@ export async function runAgenticLoop(client, model, messages, userInput, onEvent
|
|
|
714
760
|
continue;
|
|
715
761
|
}
|
|
716
762
|
}
|
|
763
|
+
// After truncation retries exhausted, try adding a "continue" message
|
|
764
|
+
if (continueRetryCount < MAX_CONTINUE_RETRIES) {
|
|
765
|
+
continueRetryCount++;
|
|
766
|
+
updatedMessages.push({ role: 'user', content: 'continue' });
|
|
767
|
+
logger.warn('400 error: adding "continue" message to retry', {
|
|
768
|
+
continueRetryCount,
|
|
769
|
+
messageCount: updatedMessages.length,
|
|
770
|
+
});
|
|
771
|
+
onEvent({
|
|
772
|
+
type: 'error',
|
|
773
|
+
error: 'Request failed. Retrying with "continue"...',
|
|
774
|
+
transient: true,
|
|
775
|
+
});
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
717
778
|
}
|
|
718
779
|
// Handle context-window-exceeded (prompt too long) — attempt forced compaction
|
|
719
780
|
// This fires when our token estimate was too low (e.g. base64 images from MCP tools)
|
|
@@ -778,7 +839,7 @@ export async function runAgenticLoop(client, model, messages, userInput, onEvent
|
|
|
778
839
|
if (apiError?.status === 400) {
|
|
779
840
|
onEvent({
|
|
780
841
|
type: 'error',
|
|
781
|
-
error:
|
|
842
|
+
error: `Request failed: ${errMsg}\n\nThe conversation history could not be automatically repaired. Try /clear to start fresh.`,
|
|
782
843
|
transient: false,
|
|
783
844
|
});
|
|
784
845
|
onEvent({ type: 'done' });
|