@compilr-dev/agents 0.3.21 → 0.3.22

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.
@@ -169,6 +169,16 @@ export declare abstract class OpenAICompatibleProvider implements LLMProvider {
169
169
  * Convert library messages to OpenAI format
170
170
  */
171
171
  protected convertMessages(messages: Message[]): OpenAIMessage[];
172
+ /**
173
+ * Ensure every assistant tool_call has a matching tool response message.
174
+ *
175
+ * OpenAI strictly requires that each tool_call_id in an assistant message
176
+ * is followed by a tool-role response before the next non-tool message.
177
+ * This can break after ToolLoopError (partial results) or context
178
+ * compaction (message reordering). We add synthetic error responses
179
+ * for any orphaned tool_calls.
180
+ */
181
+ protected repairOpenAIToolPairing(messages: OpenAIMessage[]): OpenAIMessage[];
172
182
  /**
173
183
  * Map library role to OpenAI role
174
184
  */
@@ -271,7 +271,47 @@ export class OpenAICompatibleProvider {
271
271
  }
272
272
  }
273
273
  }
274
- return result;
274
+ return this.repairOpenAIToolPairing(result);
275
+ }
276
+ /**
277
+ * Ensure every assistant tool_call has a matching tool response message.
278
+ *
279
+ * OpenAI strictly requires that each tool_call_id in an assistant message
280
+ * is followed by a tool-role response before the next non-tool message.
281
+ * This can break after ToolLoopError (partial results) or context
282
+ * compaction (message reordering). We add synthetic error responses
283
+ * for any orphaned tool_calls.
284
+ */
285
+ repairOpenAIToolPairing(messages) {
286
+ const repaired = [];
287
+ for (let i = 0; i < messages.length; i++) {
288
+ const msg = messages[i];
289
+ repaired.push(msg);
290
+ // Only check assistant messages with tool_calls
291
+ if (msg.role !== 'assistant' || !msg.tool_calls || msg.tool_calls.length === 0) {
292
+ continue;
293
+ }
294
+ // Collect expected tool_call_ids
295
+ const expectedIds = new Set(msg.tool_calls.map((tc) => tc.id));
296
+ // Push all consecutive tool messages first, tracking which IDs are satisfied
297
+ while (i + 1 < messages.length && messages[i + 1].role === 'tool') {
298
+ i++;
299
+ const toolMsg = messages[i];
300
+ repaired.push(toolMsg);
301
+ if (toolMsg.tool_call_id) {
302
+ expectedIds.delete(toolMsg.tool_call_id);
303
+ }
304
+ }
305
+ // Add synthetic responses for any missing tool_call_ids
306
+ for (const missingId of expectedIds) {
307
+ repaired.push({
308
+ role: 'tool',
309
+ tool_call_id: missingId,
310
+ content: '[Error: Tool execution was interrupted]',
311
+ });
312
+ }
313
+ }
314
+ return repaired;
275
315
  }
276
316
  /**
277
317
  * Map library role to OpenAI role
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compilr-dev/agents",
3
- "version": "0.3.21",
3
+ "version": "0.3.22",
4
4
  "description": "Lightweight multi-LLM agent library for building CLI AI assistants",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",