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.
@@ -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: 'The conversation history appears to be corrupted and could not be automatically repaired. Try /clear to start fresh.',
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' });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "protoagent",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",