@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
|