@openrouter/agent 0.1.0

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.
Files changed (98) hide show
  1. package/README.md +367 -0
  2. package/esm/api-shape-helpers/claude-message.d.ts +218 -0
  3. package/esm/api-shape-helpers/claude-message.d.ts.map +1 -0
  4. package/esm/api-shape-helpers/claude-message.js +6 -0
  5. package/esm/api-shape-helpers/claude-message.js.map +1 -0
  6. package/esm/index.d.ts +22 -0
  7. package/esm/index.d.ts.map +1 -0
  8. package/esm/index.js +27 -0
  9. package/esm/index.js.map +1 -0
  10. package/esm/inner-loop/call-model.d.ts +67 -0
  11. package/esm/inner-loop/call-model.d.ts.map +1 -0
  12. package/esm/inner-loop/call-model.js +116 -0
  13. package/esm/inner-loop/call-model.js.map +1 -0
  14. package/esm/lib/anthropic-compat.d.ts +51 -0
  15. package/esm/lib/anthropic-compat.d.ts.map +1 -0
  16. package/esm/lib/anthropic-compat.js +216 -0
  17. package/esm/lib/anthropic-compat.js.map +1 -0
  18. package/esm/lib/anthropic-compat.test.d.ts +2 -0
  19. package/esm/lib/anthropic-compat.test.d.ts.map +1 -0
  20. package/esm/lib/anthropic-compat.test.js +668 -0
  21. package/esm/lib/anthropic-compat.test.js.map +1 -0
  22. package/esm/lib/async-params.d.ts +107 -0
  23. package/esm/lib/async-params.d.ts.map +1 -0
  24. package/esm/lib/async-params.js +94 -0
  25. package/esm/lib/async-params.js.map +1 -0
  26. package/esm/lib/chat-compat.d.ts +46 -0
  27. package/esm/lib/chat-compat.d.ts.map +1 -0
  28. package/esm/lib/chat-compat.js +111 -0
  29. package/esm/lib/chat-compat.js.map +1 -0
  30. package/esm/lib/chat-compat.test.d.ts +2 -0
  31. package/esm/lib/chat-compat.test.d.ts.map +1 -0
  32. package/esm/lib/chat-compat.test.js +405 -0
  33. package/esm/lib/chat-compat.test.js.map +1 -0
  34. package/esm/lib/claude-constants.d.ts +22 -0
  35. package/esm/lib/claude-constants.d.ts.map +1 -0
  36. package/esm/lib/claude-constants.js +20 -0
  37. package/esm/lib/claude-constants.js.map +1 -0
  38. package/esm/lib/claude-type-guards.d.ts +10 -0
  39. package/esm/lib/claude-type-guards.d.ts.map +1 -0
  40. package/esm/lib/claude-type-guards.js +68 -0
  41. package/esm/lib/claude-type-guards.js.map +1 -0
  42. package/esm/lib/conversation-state.d.ts +61 -0
  43. package/esm/lib/conversation-state.d.ts.map +1 -0
  44. package/esm/lib/conversation-state.js +230 -0
  45. package/esm/lib/conversation-state.js.map +1 -0
  46. package/esm/lib/model-result.d.ts +370 -0
  47. package/esm/lib/model-result.d.ts.map +1 -0
  48. package/esm/lib/model-result.js +1483 -0
  49. package/esm/lib/model-result.js.map +1 -0
  50. package/esm/lib/next-turn-params.d.ts +30 -0
  51. package/esm/lib/next-turn-params.d.ts.map +1 -0
  52. package/esm/lib/next-turn-params.js +129 -0
  53. package/esm/lib/next-turn-params.js.map +1 -0
  54. package/esm/lib/reusable-stream.d.ts +39 -0
  55. package/esm/lib/reusable-stream.d.ts.map +1 -0
  56. package/esm/lib/reusable-stream.js +192 -0
  57. package/esm/lib/reusable-stream.js.map +1 -0
  58. package/esm/lib/stop-conditions.d.ts +80 -0
  59. package/esm/lib/stop-conditions.d.ts.map +1 -0
  60. package/esm/lib/stop-conditions.js +104 -0
  61. package/esm/lib/stop-conditions.js.map +1 -0
  62. package/esm/lib/stream-transformers.d.ts +109 -0
  63. package/esm/lib/stream-transformers.d.ts.map +1 -0
  64. package/esm/lib/stream-transformers.js +856 -0
  65. package/esm/lib/stream-transformers.js.map +1 -0
  66. package/esm/lib/stream-type-guards.d.ts +29 -0
  67. package/esm/lib/stream-type-guards.d.ts.map +1 -0
  68. package/esm/lib/stream-type-guards.js +85 -0
  69. package/esm/lib/stream-type-guards.js.map +1 -0
  70. package/esm/lib/tool-context.d.ts +68 -0
  71. package/esm/lib/tool-context.d.ts.map +1 -0
  72. package/esm/lib/tool-context.js +188 -0
  73. package/esm/lib/tool-context.js.map +1 -0
  74. package/esm/lib/tool-event-broadcaster.d.ts +44 -0
  75. package/esm/lib/tool-event-broadcaster.d.ts.map +1 -0
  76. package/esm/lib/tool-event-broadcaster.js +162 -0
  77. package/esm/lib/tool-event-broadcaster.js.map +1 -0
  78. package/esm/lib/tool-executor.d.ts +73 -0
  79. package/esm/lib/tool-executor.d.ts.map +1 -0
  80. package/esm/lib/tool-executor.js +267 -0
  81. package/esm/lib/tool-executor.js.map +1 -0
  82. package/esm/lib/tool-orchestrator.d.ts +50 -0
  83. package/esm/lib/tool-orchestrator.d.ts.map +1 -0
  84. package/esm/lib/tool-orchestrator.js +180 -0
  85. package/esm/lib/tool-orchestrator.js.map +1 -0
  86. package/esm/lib/tool-types.d.ts +572 -0
  87. package/esm/lib/tool-types.d.ts.map +1 -0
  88. package/esm/lib/tool-types.js +80 -0
  89. package/esm/lib/tool-types.js.map +1 -0
  90. package/esm/lib/tool.d.ts +108 -0
  91. package/esm/lib/tool.d.ts.map +1 -0
  92. package/esm/lib/tool.js +84 -0
  93. package/esm/lib/tool.js.map +1 -0
  94. package/esm/lib/turn-context.d.ts +50 -0
  95. package/esm/lib/turn-context.d.ts.map +1 -0
  96. package/esm/lib/turn-context.js +61 -0
  97. package/esm/lib/turn-context.js.map +1 -0
  98. package/package.json +125 -0
@@ -0,0 +1,856 @@
1
+ import { isFileCitationAnnotation, isFilePathAnnotation, isFileSearchCallOutputItem, isFunctionCallArgumentsDeltaEvent, isFunctionCallArgumentsDoneEvent, isFunctionCallItem, isImageGenerationCallOutputItem, isOutputItemAddedEvent, isOutputItemDoneEvent, isOutputMessage, isOutputTextDeltaEvent, isOutputTextPart, isReasoningDeltaEvent, isReasoningOutputItem, isRefusalPart, isResponseCompletedEvent, isResponseFailedEvent, isResponseIncompleteEvent, isURLCitationAnnotation, isWebSearchCallOutputItem, } from './stream-type-guards.js';
2
+ /**
3
+ * Extract text deltas from responses stream events
4
+ */
5
+ export async function* extractTextDeltas(stream) {
6
+ const consumer = stream.createConsumer();
7
+ for await (const event of consumer) {
8
+ if (isOutputTextDeltaEvent(event)) {
9
+ if (event.delta) {
10
+ yield event.delta;
11
+ }
12
+ }
13
+ }
14
+ }
15
+ /**
16
+ * Extract reasoning deltas from responses stream events
17
+ */
18
+ export async function* extractReasoningDeltas(stream) {
19
+ const consumer = stream.createConsumer();
20
+ for await (const event of consumer) {
21
+ if (isReasoningDeltaEvent(event)) {
22
+ if (event.delta) {
23
+ yield event.delta;
24
+ }
25
+ }
26
+ }
27
+ }
28
+ /**
29
+ * Extract tool call argument deltas from responses stream events
30
+ */
31
+ export async function* extractToolDeltas(stream) {
32
+ const consumer = stream.createConsumer();
33
+ for await (const event of consumer) {
34
+ if (isFunctionCallArgumentsDeltaEvent(event)) {
35
+ if (event.delta) {
36
+ yield event.delta;
37
+ }
38
+ }
39
+ }
40
+ }
41
+ /**
42
+ * Core message stream builder - shared logic for both formats
43
+ * Accumulates text deltas and yields updates
44
+ */
45
+ async function* buildMessageStreamCore(stream) {
46
+ const consumer = stream.createConsumer();
47
+ // Track the accumulated text and message info
48
+ let currentText = '';
49
+ let currentId = '';
50
+ let hasStarted = false;
51
+ for await (const event of consumer) {
52
+ if (!('type' in event)) {
53
+ continue;
54
+ }
55
+ switch (event.type) {
56
+ case 'response.output_item.added': {
57
+ if (isOutputItemAddedEvent(event)) {
58
+ if (event.item && isOutputMessage(event.item)) {
59
+ hasStarted = true;
60
+ currentText = '';
61
+ currentId = event.item.id;
62
+ }
63
+ }
64
+ break;
65
+ }
66
+ case 'response.output_text.delta': {
67
+ if (isOutputTextDeltaEvent(event)) {
68
+ if (hasStarted && event.delta) {
69
+ currentText += event.delta;
70
+ yield {
71
+ type: 'delta',
72
+ text: currentText,
73
+ messageId: currentId,
74
+ };
75
+ }
76
+ }
77
+ break;
78
+ }
79
+ case 'response.output_item.done': {
80
+ if (isOutputItemDoneEvent(event)) {
81
+ if (event.item && isOutputMessage(event.item)) {
82
+ yield {
83
+ type: 'complete',
84
+ completeMessage: event.item,
85
+ };
86
+ }
87
+ }
88
+ break;
89
+ }
90
+ case 'response.completed':
91
+ case 'response.failed':
92
+ case 'response.incomplete':
93
+ // Stream is complete, stop consuming
94
+ return;
95
+ default:
96
+ // Ignore other event types - this is intentionally not exhaustive
97
+ // as we only care about specific events for message building
98
+ break;
99
+ }
100
+ }
101
+ }
102
+ /**
103
+ * Build incremental message updates from responses stream events
104
+ * Returns OutputMessage (assistant/responses format)
105
+ */
106
+ export async function* buildResponsesMessageStream(stream) {
107
+ for await (const update of buildMessageStreamCore(stream)) {
108
+ if (update.type === 'delta' && update.text !== undefined && update.messageId !== undefined) {
109
+ // Yield incremental update in OutputMessage format
110
+ yield {
111
+ id: update.messageId,
112
+ type: 'message',
113
+ role: 'assistant',
114
+ status: 'in_progress',
115
+ content: [
116
+ {
117
+ type: 'output_text',
118
+ text: update.text,
119
+ annotations: [],
120
+ },
121
+ ],
122
+ };
123
+ }
124
+ else if (update.type === 'complete' && update.completeMessage) {
125
+ // Yield final complete message
126
+ yield update.completeMessage;
127
+ }
128
+ }
129
+ }
130
+ /**
131
+ * Handle output_item.added event - Initialize tracking for new items
132
+ */
133
+ function handleOutputItemAdded(event, itemsInProgress) {
134
+ if (!isOutputItemAddedEvent(event) || !event.item) {
135
+ return undefined;
136
+ }
137
+ const item = event.item;
138
+ if (isOutputMessage(item)) {
139
+ itemsInProgress.set(item.id, {
140
+ type: 'message',
141
+ id: item.id,
142
+ textContent: '',
143
+ });
144
+ return {
145
+ id: item.id,
146
+ type: 'message',
147
+ role: 'assistant',
148
+ status: 'in_progress',
149
+ content: [],
150
+ };
151
+ }
152
+ if (isFunctionCallItem(item)) {
153
+ // Use item.id if available (matches itemId in delta events), fall back to callId
154
+ const itemKey = item.id ?? item.callId;
155
+ itemsInProgress.set(itemKey, {
156
+ type: 'function_call',
157
+ id: itemKey,
158
+ name: item.name,
159
+ callId: item.callId,
160
+ argumentsAccumulated: '',
161
+ });
162
+ return {
163
+ type: 'function_call',
164
+ id: item.id,
165
+ callId: item.callId,
166
+ name: item.name,
167
+ arguments: '',
168
+ status: 'in_progress',
169
+ };
170
+ }
171
+ if (isReasoningOutputItem(item)) {
172
+ itemsInProgress.set(item.id, {
173
+ type: 'reasoning',
174
+ id: item.id,
175
+ reasoningContent: '',
176
+ });
177
+ return {
178
+ type: 'reasoning',
179
+ id: item.id,
180
+ status: 'in_progress',
181
+ summary: [],
182
+ };
183
+ }
184
+ if (isWebSearchCallOutputItem(item)) {
185
+ return item;
186
+ }
187
+ if (isFileSearchCallOutputItem(item)) {
188
+ return item;
189
+ }
190
+ if (isImageGenerationCallOutputItem(item)) {
191
+ return item;
192
+ }
193
+ return undefined;
194
+ }
195
+ /**
196
+ * Handle text delta event for messages
197
+ */
198
+ function handleTextDelta(event, itemsInProgress) {
199
+ if (!isOutputTextDeltaEvent(event) || !event.delta) {
200
+ return undefined;
201
+ }
202
+ const item = itemsInProgress.get(event.itemId);
203
+ if (item?.type === 'message') {
204
+ item.textContent += event.delta;
205
+ return {
206
+ id: item.id,
207
+ type: 'message',
208
+ role: 'assistant',
209
+ status: 'in_progress',
210
+ content: [
211
+ {
212
+ type: 'output_text',
213
+ text: item.textContent,
214
+ annotations: [],
215
+ },
216
+ ],
217
+ };
218
+ }
219
+ return undefined;
220
+ }
221
+ /**
222
+ * Handle function call argument delta event
223
+ */
224
+ function handleFunctionCallDelta(event, itemsInProgress) {
225
+ if (!isFunctionCallArgumentsDeltaEvent(event) || !event.delta) {
226
+ return undefined;
227
+ }
228
+ const item = itemsInProgress.get(event.itemId);
229
+ if (item?.type === 'function_call') {
230
+ item.argumentsAccumulated += event.delta;
231
+ return {
232
+ type: 'function_call',
233
+ // Include id if it differs from callId (means API provided an id)
234
+ id: item.id !== item.callId ? item.id : undefined,
235
+ callId: item.callId,
236
+ name: item.name,
237
+ arguments: item.argumentsAccumulated,
238
+ status: 'in_progress',
239
+ };
240
+ }
241
+ return undefined;
242
+ }
243
+ /**
244
+ * Handle reasoning text delta event
245
+ */
246
+ function handleReasoningDelta(event, itemsInProgress) {
247
+ if (!isReasoningDeltaEvent(event) || !event.delta) {
248
+ return undefined;
249
+ }
250
+ const item = itemsInProgress.get(event.itemId);
251
+ if (item?.type === 'reasoning') {
252
+ item.reasoningContent += event.delta;
253
+ return {
254
+ type: 'reasoning',
255
+ id: item.id,
256
+ status: 'in_progress',
257
+ summary: [
258
+ {
259
+ type: 'summary_text',
260
+ text: item.reasoningContent,
261
+ },
262
+ ],
263
+ };
264
+ }
265
+ return undefined;
266
+ }
267
+ /**
268
+ * Handle output_item.done event - Yield final complete item
269
+ */
270
+ function handleOutputItemDone(event, itemsInProgress) {
271
+ if (!isOutputItemDoneEvent(event) || !event.item) {
272
+ return undefined;
273
+ }
274
+ const item = event.item;
275
+ if (isOutputMessage(item)) {
276
+ itemsInProgress.delete(item.id);
277
+ return item;
278
+ }
279
+ if (isFunctionCallItem(item)) {
280
+ // Use item.id if available (matches itemId in delta events), fall back to callId
281
+ itemsInProgress.delete(item.id ?? item.callId);
282
+ return item;
283
+ }
284
+ if (isReasoningOutputItem(item)) {
285
+ itemsInProgress.delete(item.id);
286
+ return item;
287
+ }
288
+ if (isWebSearchCallOutputItem(item)) {
289
+ return item;
290
+ }
291
+ if (isFileSearchCallOutputItem(item)) {
292
+ return item;
293
+ }
294
+ if (isImageGenerationCallOutputItem(item)) {
295
+ return item;
296
+ }
297
+ return undefined;
298
+ }
299
+ export const itemsStreamHandlers = {
300
+ 'response.output_item.added': handleOutputItemAdded,
301
+ 'response.output_text.delta': handleTextDelta,
302
+ 'response.function_call_arguments.delta': handleFunctionCallDelta,
303
+ 'response.reasoning_text.delta': handleReasoningDelta,
304
+ 'response.output_item.done': handleOutputItemDone,
305
+ };
306
+ export const streamTerminationEvents = new Set([
307
+ 'response.completed',
308
+ 'response.failed',
309
+ 'response.incomplete',
310
+ ]);
311
+ //#endregion
312
+ /**
313
+ * Build incremental output item updates from responses stream events.
314
+ * Yields all item types cumulatively - same item may be emitted multiple times
315
+ * with the same ID but progressively updated content as streaming progresses.
316
+ */
317
+ export async function* buildItemsStream(stream) {
318
+ const consumer = stream.createConsumer();
319
+ const itemsInProgress = new Map();
320
+ for await (const event of consumer) {
321
+ if (!('type' in event)) {
322
+ continue;
323
+ }
324
+ if (streamTerminationEvents.has(event.type)) {
325
+ return;
326
+ }
327
+ const handler = itemsStreamHandlers[event.type];
328
+ if (handler) {
329
+ const result = handler(event, itemsInProgress);
330
+ if (result) {
331
+ yield result;
332
+ }
333
+ }
334
+ }
335
+ }
336
+ /**
337
+ * Build incremental message updates from responses stream events
338
+ * Returns ChatAssistantMessage (chat format) instead of OutputMessage
339
+ */
340
+ export async function* buildMessageStream(stream) {
341
+ for await (const update of buildMessageStreamCore(stream)) {
342
+ if (update.type === 'delta' && update.text !== undefined) {
343
+ // Yield incremental update in chat format
344
+ yield {
345
+ role: 'assistant',
346
+ content: update.text,
347
+ };
348
+ }
349
+ else if (update.type === 'complete' && update.completeMessage) {
350
+ // Yield final complete message converted to chat format
351
+ yield convertToAssistantMessage(update.completeMessage);
352
+ }
353
+ }
354
+ }
355
+ /**
356
+ * Consume stream until completion and return the complete response
357
+ */
358
+ export async function consumeStreamForCompletion(stream) {
359
+ const consumer = stream.createConsumer();
360
+ for await (const event of consumer) {
361
+ if (!('type' in event)) {
362
+ continue;
363
+ }
364
+ if (isResponseCompletedEvent(event)) {
365
+ return event.response;
366
+ }
367
+ if (isResponseFailedEvent(event)) {
368
+ // The failed event contains the full response with error information
369
+ throw new Error(`Response failed: ${JSON.stringify(event.response.error)}`);
370
+ }
371
+ if (isResponseIncompleteEvent(event)) {
372
+ // Return the incomplete response
373
+ return event.response;
374
+ }
375
+ }
376
+ throw new Error('Stream ended without completion event');
377
+ }
378
+ /**
379
+ * Convert OutputMessage to ChatAssistantMessage (chat format)
380
+ */
381
+ function convertToAssistantMessage(outputMessage) {
382
+ // Extract text content
383
+ const textContent = outputMessage.content
384
+ .filter((part) => 'type' in part && part.type === 'output_text')
385
+ .map((part) => part.text)
386
+ .join('');
387
+ return {
388
+ role: 'assistant',
389
+ content: textContent || null,
390
+ };
391
+ }
392
+ /**
393
+ * Extract the first message from a completed response (chat format)
394
+ */
395
+ export function extractMessageFromResponse(response) {
396
+ const messageItem = response.output.find((item) => 'type' in item && item.type === 'message');
397
+ if (!messageItem) {
398
+ throw new Error('No message found in response output');
399
+ }
400
+ return convertToAssistantMessage(messageItem);
401
+ }
402
+ /**
403
+ * Extract the first message from a completed response (responses format)
404
+ */
405
+ export function extractResponsesMessageFromResponse(response) {
406
+ const messageItem = response.output.find((item) => 'type' in item && item.type === 'message');
407
+ if (!messageItem) {
408
+ throw new Error('No message found in response output');
409
+ }
410
+ return messageItem;
411
+ }
412
+ /**
413
+ * Extract text from a response, either from outputText or by concatenating message content
414
+ */
415
+ export function extractTextFromResponse(response) {
416
+ // Use pre-concatenated outputText if available
417
+ if (response.outputText) {
418
+ return response.outputText;
419
+ }
420
+ // Check if there's a message in the output
421
+ const hasMessage = response.output.some((item) => 'type' in item && item.type === 'message');
422
+ if (!hasMessage) {
423
+ // No message in response (e.g., only function calls)
424
+ return '';
425
+ }
426
+ // Otherwise, extract from the first message (convert to ChatAssistantMessage which has string content)
427
+ const message = extractMessageFromResponse(response);
428
+ // ChatAssistantMessage.content is string | Array | null | undefined
429
+ if (typeof message.content === 'string') {
430
+ return message.content;
431
+ }
432
+ return '';
433
+ }
434
+ /**
435
+ * Extract all tool calls from a completed response
436
+ * Returns parsed tool calls with arguments as objects (not JSON strings)
437
+ */
438
+ export function extractToolCallsFromResponse(response) {
439
+ const toolCalls = [];
440
+ for (const item of response.output) {
441
+ if (isFunctionCallItem(item)) {
442
+ try {
443
+ const trimmedArgs = item.arguments.trim();
444
+ const parsedArguments = trimmedArgs ? JSON.parse(trimmedArgs) : {};
445
+ toolCalls.push({
446
+ id: item.callId,
447
+ name: item.name,
448
+ arguments: parsedArguments,
449
+ });
450
+ }
451
+ catch (error) {
452
+ console.warn(`Failed to parse tool call arguments for ${item.name}:`, error instanceof Error ? error.message : String(error), `\nArguments: ${item.arguments.substring(0, 100)}${item.arguments.length > 100 ? '...' : ''}`);
453
+ // Include the tool call with unparsed arguments
454
+ toolCalls.push({
455
+ id: item.callId,
456
+ name: item.name,
457
+ arguments: item.arguments, // Keep as string if parsing fails
458
+ });
459
+ }
460
+ }
461
+ }
462
+ return toolCalls;
463
+ }
464
+ /**
465
+ * Build incremental tool call updates from responses stream events
466
+ * Yields structured tool call objects as they're built from deltas
467
+ */
468
+ export async function* buildToolCallStream(stream) {
469
+ const consumer = stream.createConsumer();
470
+ // Track tool calls being built
471
+ const toolCallsInProgress = new Map();
472
+ for await (const event of consumer) {
473
+ if (!('type' in event)) {
474
+ continue;
475
+ }
476
+ switch (event.type) {
477
+ case 'response.output_item.added': {
478
+ if (isOutputItemAddedEvent(event) && event.item && isFunctionCallItem(event.item)) {
479
+ // Use item.id if available (matches itemId in delta events), fall back to callId
480
+ const itemKey = event.item.id ?? event.item.callId;
481
+ toolCallsInProgress.set(itemKey, {
482
+ id: event.item.callId,
483
+ name: event.item.name,
484
+ argumentsAccumulated: '',
485
+ });
486
+ }
487
+ break;
488
+ }
489
+ case 'response.function_call_arguments.delta': {
490
+ if (isFunctionCallArgumentsDeltaEvent(event)) {
491
+ const toolCall = toolCallsInProgress.get(event.itemId);
492
+ if (toolCall && event.delta) {
493
+ toolCall.argumentsAccumulated += event.delta;
494
+ }
495
+ }
496
+ break;
497
+ }
498
+ case 'response.function_call_arguments.done': {
499
+ if (isFunctionCallArgumentsDoneEvent(event)) {
500
+ const toolCall = toolCallsInProgress.get(event.itemId);
501
+ if (toolCall) {
502
+ // Parse complete arguments (empty string → empty object for no-param tools)
503
+ try {
504
+ const trimmedArgs = event.arguments.trim();
505
+ const parsedArguments = trimmedArgs ? JSON.parse(trimmedArgs) : {};
506
+ yield {
507
+ id: toolCall.id,
508
+ name: event.name,
509
+ arguments: parsedArguments,
510
+ };
511
+ }
512
+ catch (error) {
513
+ console.warn(`Failed to parse tool call arguments for ${event.name}:`, error instanceof Error ? error.message : String(error), `\nArguments: ${event.arguments.substring(0, 100)}${event.arguments.length > 100 ? '...' : ''}`);
514
+ // Yield with unparsed arguments if parsing fails
515
+ yield {
516
+ id: toolCall.id,
517
+ name: event.name,
518
+ arguments: event.arguments,
519
+ };
520
+ }
521
+ // Clean up
522
+ toolCallsInProgress.delete(event.itemId);
523
+ }
524
+ }
525
+ break;
526
+ }
527
+ case 'response.output_item.done': {
528
+ if (isOutputItemDoneEvent(event) && event.item && isFunctionCallItem(event.item)) {
529
+ // Use item.id if available (matches itemId in delta events), fall back to callId
530
+ const itemKey = event.item.id ?? event.item.callId;
531
+ // Yield final tool call if we haven't already
532
+ if (toolCallsInProgress.has(itemKey)) {
533
+ try {
534
+ const trimmedArgs = event.item.arguments.trim();
535
+ const parsedArguments = trimmedArgs ? JSON.parse(trimmedArgs) : {};
536
+ yield {
537
+ id: event.item.callId,
538
+ name: event.item.name,
539
+ arguments: parsedArguments,
540
+ };
541
+ }
542
+ catch (_error) {
543
+ yield {
544
+ id: event.item.callId,
545
+ name: event.item.name,
546
+ arguments: event.item.arguments,
547
+ };
548
+ }
549
+ toolCallsInProgress.delete(itemKey);
550
+ }
551
+ }
552
+ break;
553
+ }
554
+ }
555
+ }
556
+ }
557
+ /**
558
+ * Check if a response contains any tool calls
559
+ */
560
+ export function responseHasToolCalls(response) {
561
+ return response.output.some((item) => 'type' in item && item.type === 'function_call');
562
+ }
563
+ /**
564
+ * Convert OpenRouter annotations to Claude citations
565
+ */
566
+ function mapAnnotationsToCitations(annotations) {
567
+ if (!annotations || annotations.length === 0) {
568
+ return undefined;
569
+ }
570
+ const citations = [];
571
+ for (const annotation of annotations) {
572
+ if (!('type' in annotation)) {
573
+ continue;
574
+ }
575
+ switch (annotation.type) {
576
+ case 'file_citation': {
577
+ if (isFileCitationAnnotation(annotation)) {
578
+ citations.push({
579
+ type: 'char_location',
580
+ cited_text: '',
581
+ document_index: annotation.index,
582
+ document_title: annotation.filename,
583
+ file_id: annotation.fileId,
584
+ start_char_index: 0,
585
+ end_char_index: 0,
586
+ });
587
+ }
588
+ break;
589
+ }
590
+ case 'url_citation': {
591
+ if (isURLCitationAnnotation(annotation)) {
592
+ citations.push({
593
+ type: 'web_search_result_location',
594
+ cited_text: '',
595
+ title: annotation.title,
596
+ url: annotation.url,
597
+ encrypted_index: '',
598
+ });
599
+ }
600
+ break;
601
+ }
602
+ case 'file_path': {
603
+ if (isFilePathAnnotation(annotation)) {
604
+ citations.push({
605
+ type: 'char_location',
606
+ cited_text: '',
607
+ document_index: annotation.index,
608
+ document_title: '',
609
+ file_id: annotation.fileId,
610
+ start_char_index: 0,
611
+ end_char_index: 0,
612
+ });
613
+ }
614
+ break;
615
+ }
616
+ default:
617
+ // Unknown annotation types are skipped for forward compatibility.
618
+ break;
619
+ }
620
+ }
621
+ return citations.length > 0 ? citations : undefined;
622
+ }
623
+ /**
624
+ * Map OpenResponses status to Claude stop reason
625
+ */
626
+ function mapStopReason(response) {
627
+ // Check if any tool calls exist in the response
628
+ const hasToolCalls = response.output.some((item) => 'type' in item && item.type === 'function_call');
629
+ if (hasToolCalls) {
630
+ return 'tool_use';
631
+ }
632
+ // Check the response status
633
+ if (response.status === 'completed') {
634
+ return 'end_turn';
635
+ }
636
+ if (response.status === 'incomplete') {
637
+ // Check incomplete reason if available
638
+ const incompleteReason = response.incompleteDetails?.reason;
639
+ if (incompleteReason === 'max_output_tokens') {
640
+ return 'max_tokens';
641
+ }
642
+ return 'end_turn';
643
+ }
644
+ return 'end_turn';
645
+ }
646
+ /**
647
+ * Convert OpenResponsesResult to ClaudeMessage format
648
+ * Compatible with the Anthropic SDK BetaMessage type
649
+ */
650
+ export function convertToClaudeMessage(response) {
651
+ const content = [];
652
+ const unsupportedContent = [];
653
+ for (const item of response.output) {
654
+ if (!('type' in item)) {
655
+ // Handle items without type field
656
+ // Convert unknown item to a record format for storage
657
+ const itemData = typeof item === 'object' && item !== null
658
+ ? item
659
+ : {
660
+ value: item,
661
+ };
662
+ unsupportedContent.push({
663
+ original_type: 'unknown',
664
+ data: itemData,
665
+ reason: 'Output item missing type field',
666
+ });
667
+ continue;
668
+ }
669
+ switch (item.type) {
670
+ case 'message': {
671
+ if (isOutputMessage(item)) {
672
+ for (const part of item.content) {
673
+ if (!('type' in part)) {
674
+ // Convert unknown part to a record format for storage
675
+ const partData = typeof part === 'object' && part !== null
676
+ ? part
677
+ : {
678
+ value: part,
679
+ };
680
+ unsupportedContent.push({
681
+ original_type: 'unknown_message_part',
682
+ data: partData,
683
+ reason: 'Message content part missing type field',
684
+ });
685
+ continue;
686
+ }
687
+ if (isOutputTextPart(part)) {
688
+ const citations = mapAnnotationsToCitations(part.annotations);
689
+ content.push({
690
+ type: 'text',
691
+ text: part.text,
692
+ ...(citations && {
693
+ citations,
694
+ }),
695
+ });
696
+ }
697
+ else if (isRefusalPart(part)) {
698
+ unsupportedContent.push({
699
+ original_type: 'refusal',
700
+ data: {
701
+ refusal: part.refusal,
702
+ },
703
+ reason: 'Claude does not have a native refusal content type',
704
+ });
705
+ }
706
+ else {
707
+ // Unknown content types are skipped for forward compatibility.
708
+ }
709
+ }
710
+ }
711
+ break;
712
+ }
713
+ case 'function_call': {
714
+ if (isFunctionCallItem(item)) {
715
+ let parsedInput;
716
+ try {
717
+ const trimmedArgs = item.arguments.trim();
718
+ parsedInput = trimmedArgs ? JSON.parse(trimmedArgs) : {};
719
+ }
720
+ catch (error) {
721
+ console.warn(`Failed to parse tool call arguments for ${item.name}:`, error instanceof Error ? error.message : String(error), `\nArguments: ${item.arguments.substring(0, 100)}${item.arguments.length > 100 ? '...' : ''}`);
722
+ // Preserve raw arguments if JSON parsing fails
723
+ parsedInput = {
724
+ _raw_arguments: item.arguments,
725
+ };
726
+ }
727
+ content.push({
728
+ type: 'tool_use',
729
+ id: item.callId,
730
+ name: item.name,
731
+ input: parsedInput,
732
+ });
733
+ }
734
+ break;
735
+ }
736
+ case 'reasoning': {
737
+ if (isReasoningOutputItem(item)) {
738
+ if (item.summary && item.summary.length > 0) {
739
+ for (const summaryItem of item.summary) {
740
+ if (summaryItem.type === 'summary_text' && summaryItem.text) {
741
+ content.push({
742
+ type: 'thinking',
743
+ thinking: summaryItem.text,
744
+ signature: '',
745
+ });
746
+ }
747
+ }
748
+ }
749
+ if (item.encryptedContent) {
750
+ unsupportedContent.push({
751
+ original_type: 'reasoning_encrypted',
752
+ data: {
753
+ id: item.id,
754
+ encrypted_content: item.encryptedContent,
755
+ },
756
+ reason: 'Encrypted reasoning content preserved for round-trip',
757
+ });
758
+ }
759
+ }
760
+ break;
761
+ }
762
+ case 'web_search_call': {
763
+ if (isWebSearchCallOutputItem(item)) {
764
+ content.push({
765
+ type: 'server_tool_use',
766
+ id: item.id,
767
+ name: 'web_search',
768
+ input: {
769
+ status: item.status,
770
+ },
771
+ });
772
+ }
773
+ break;
774
+ }
775
+ case 'file_search_call': {
776
+ if (isFileSearchCallOutputItem(item)) {
777
+ content.push({
778
+ type: 'tool_use',
779
+ id: item.id,
780
+ name: 'file_search',
781
+ input: {
782
+ queries: item.queries,
783
+ status: item.status,
784
+ },
785
+ });
786
+ }
787
+ break;
788
+ }
789
+ case 'image_generation_call': {
790
+ if (isImageGenerationCallOutputItem(item)) {
791
+ unsupportedContent.push({
792
+ original_type: 'image_generation_call',
793
+ data: {
794
+ id: item.id,
795
+ result: item.result,
796
+ status: item.status,
797
+ },
798
+ reason: 'Claude does not support image outputs in assistant messages',
799
+ });
800
+ }
801
+ break;
802
+ }
803
+ default:
804
+ // Unknown output types (e.g. new server tools) are skipped during Claude format
805
+ // conversion — they round-trip natively via the Responses API input union.
806
+ break;
807
+ }
808
+ }
809
+ return {
810
+ id: response.id,
811
+ type: 'message',
812
+ role: 'assistant',
813
+ model: response.model ?? 'unknown',
814
+ content,
815
+ stop_reason: mapStopReason(response),
816
+ stop_sequence: null,
817
+ usage: {
818
+ input_tokens: response.usage?.inputTokens ?? 0,
819
+ output_tokens: response.usage?.outputTokens ?? 0,
820
+ cache_creation_input_tokens: response.usage?.inputTokensDetails?.cachedTokens ?? 0,
821
+ cache_read_input_tokens: 0,
822
+ },
823
+ ...(unsupportedContent.length > 0 && {
824
+ unsupported_content: unsupportedContent,
825
+ }),
826
+ };
827
+ }
828
+ /**
829
+ * Extract unsupported content by original type
830
+ */
831
+ export function extractUnsupportedContent(message, originalType) {
832
+ if (!message.unsupported_content) {
833
+ return [];
834
+ }
835
+ return message.unsupported_content.filter((item) => item.original_type === originalType);
836
+ }
837
+ /**
838
+ * Check if message has any unsupported content
839
+ */
840
+ export function hasUnsupportedContent(message) {
841
+ return !!(message.unsupported_content && message.unsupported_content.length > 0);
842
+ }
843
+ /**
844
+ * Get summary of unsupported content types
845
+ */
846
+ export function getUnsupportedContentSummary(message) {
847
+ if (!message.unsupported_content) {
848
+ return {};
849
+ }
850
+ const summary = {};
851
+ for (const item of message.unsupported_content) {
852
+ summary[item.original_type] = (summary[item.original_type] || 0) + 1;
853
+ }
854
+ return summary;
855
+ }
856
+ //# sourceMappingURL=stream-transformers.js.map