@openrouter/sdk 0.3.7 → 0.3.10
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/.zed/settings.json +10 -0
- package/_speakeasy/.github/action-inputs-config.json +53 -0
- package/_speakeasy/.github/action-security-config.json +88 -0
- package/esm/funcs/call-model.d.ts +94 -9
- package/esm/funcs/call-model.js +102 -120
- package/esm/index.d.ts +20 -8
- package/esm/index.js +20 -7
- package/esm/lib/anthropic-compat.d.ts +6 -2
- package/esm/lib/anthropic-compat.js +117 -98
- package/esm/lib/async-params.d.ts +53 -0
- package/esm/lib/async-params.js +76 -0
- package/esm/lib/chat-compat.js +4 -0
- package/esm/lib/claude-constants.d.ts +22 -0
- package/esm/lib/claude-constants.js +20 -0
- package/esm/lib/claude-type-guards.d.ts +10 -0
- package/esm/lib/claude-type-guards.js +70 -0
- package/esm/lib/config.d.ts +2 -2
- package/esm/lib/config.js +2 -2
- package/esm/lib/model-result.d.ts +18 -25
- package/esm/lib/model-result.js +137 -176
- package/esm/lib/next-turn-params.d.ts +30 -0
- package/esm/lib/next-turn-params.js +129 -0
- package/esm/lib/reusable-stream.js +10 -10
- package/esm/lib/stop-conditions.d.ts +80 -0
- package/esm/lib/stop-conditions.js +104 -0
- package/esm/lib/stream-transformers.d.ts +3 -3
- package/esm/lib/stream-transformers.js +311 -260
- package/esm/lib/stream-type-guards.d.ts +29 -0
- package/esm/lib/stream-type-guards.js +109 -0
- package/esm/lib/tool-executor.d.ts +7 -6
- package/esm/lib/tool-executor.js +4 -0
- package/esm/lib/tool-orchestrator.d.ts +7 -7
- package/esm/lib/tool-orchestrator.js +38 -10
- package/esm/lib/tool-types.d.ts +162 -28
- package/esm/lib/tool-types.js +6 -0
- package/esm/lib/tool.d.ts +99 -0
- package/esm/lib/tool.js +71 -0
- package/esm/lib/turn-context.d.ts +50 -0
- package/esm/lib/turn-context.js +59 -0
- package/esm/sdk/sdk.d.ts +3 -9
- package/jsr.json +1 -1
- package/package.json +6 -3
|
@@ -1,13 +1,13 @@
|
|
|
1
|
+
import { isOutputTextDeltaEvent, isReasoningDeltaEvent, isFunctionCallArgumentsDeltaEvent, isOutputItemAddedEvent, isOutputItemDoneEvent, isResponseCompletedEvent, isResponseFailedEvent, isResponseIncompleteEvent, isFunctionCallArgumentsDoneEvent, isOutputMessage, isFunctionCallOutputItem, isReasoningOutputItem, isWebSearchCallOutputItem, isFileSearchCallOutputItem, isImageGenerationCallOutputItem, isOutputTextPart, isRefusalPart, isFileCitationAnnotation, isURLCitationAnnotation, isFilePathAnnotation, } from './stream-type-guards.js';
|
|
1
2
|
/**
|
|
2
3
|
* Extract text deltas from responses stream events
|
|
3
4
|
*/
|
|
4
5
|
export async function* extractTextDeltas(stream) {
|
|
5
6
|
const consumer = stream.createConsumer();
|
|
6
7
|
for await (const event of consumer) {
|
|
7
|
-
if (
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
yield deltaEvent.delta;
|
|
8
|
+
if (isOutputTextDeltaEvent(event)) {
|
|
9
|
+
if (event.delta) {
|
|
10
|
+
yield event.delta;
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
}
|
|
@@ -18,10 +18,9 @@ export async function* extractTextDeltas(stream) {
|
|
|
18
18
|
export async function* extractReasoningDeltas(stream) {
|
|
19
19
|
const consumer = stream.createConsumer();
|
|
20
20
|
for await (const event of consumer) {
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
yield deltaEvent.delta;
|
|
21
|
+
if (isReasoningDeltaEvent(event)) {
|
|
22
|
+
if (event.delta) {
|
|
23
|
+
yield event.delta;
|
|
25
24
|
}
|
|
26
25
|
}
|
|
27
26
|
}
|
|
@@ -32,19 +31,18 @@ export async function* extractReasoningDeltas(stream) {
|
|
|
32
31
|
export async function* extractToolDeltas(stream) {
|
|
33
32
|
const consumer = stream.createConsumer();
|
|
34
33
|
for await (const event of consumer) {
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
yield deltaEvent.delta;
|
|
34
|
+
if (isFunctionCallArgumentsDeltaEvent(event)) {
|
|
35
|
+
if (event.delta) {
|
|
36
|
+
yield event.delta;
|
|
39
37
|
}
|
|
40
38
|
}
|
|
41
39
|
}
|
|
42
40
|
}
|
|
43
41
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
42
|
+
* Core message stream builder - shared logic for both formats
|
|
43
|
+
* Accumulates text deltas and yields updates
|
|
46
44
|
*/
|
|
47
|
-
|
|
45
|
+
async function* buildMessageStreamCore(stream) {
|
|
48
46
|
const consumer = stream.createConsumer();
|
|
49
47
|
// Track the accumulated text and message info
|
|
50
48
|
let currentText = '';
|
|
@@ -56,47 +54,76 @@ export async function* buildResponsesMessageStream(stream) {
|
|
|
56
54
|
}
|
|
57
55
|
switch (event.type) {
|
|
58
56
|
case 'response.output_item.added': {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
if (isOutputItemAddedEvent(event)) {
|
|
58
|
+
if (event.item && isOutputMessage(event.item)) {
|
|
59
|
+
hasStarted = true;
|
|
60
|
+
currentText = '';
|
|
61
|
+
currentId = event.item.id;
|
|
62
|
+
}
|
|
65
63
|
}
|
|
66
64
|
break;
|
|
67
65
|
}
|
|
68
66
|
case 'response.output_text.delta': {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
content: [
|
|
79
|
-
{
|
|
80
|
-
type: 'output_text',
|
|
81
|
-
text: currentText,
|
|
82
|
-
annotations: [],
|
|
83
|
-
},
|
|
84
|
-
],
|
|
85
|
-
};
|
|
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
|
+
}
|
|
86
76
|
}
|
|
87
77
|
break;
|
|
88
78
|
}
|
|
89
79
|
case 'response.output_item.done': {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
80
|
+
if (isOutputItemDoneEvent(event)) {
|
|
81
|
+
if (event.item && isOutputMessage(event.item)) {
|
|
82
|
+
yield {
|
|
83
|
+
type: 'complete',
|
|
84
|
+
completeMessage: event.item,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
97
87
|
}
|
|
98
88
|
break;
|
|
99
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 ResponsesOutputMessage (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 ResponsesOutputMessage 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;
|
|
100
127
|
}
|
|
101
128
|
}
|
|
102
129
|
}
|
|
@@ -105,46 +132,17 @@ export async function* buildResponsesMessageStream(stream) {
|
|
|
105
132
|
* Returns AssistantMessage (chat format) instead of ResponsesOutputMessage
|
|
106
133
|
*/
|
|
107
134
|
export async function* buildMessageStream(stream) {
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
135
|
+
for await (const update of buildMessageStreamCore(stream)) {
|
|
136
|
+
if (update.type === 'delta' && update.text !== undefined) {
|
|
137
|
+
// Yield incremental update in chat format
|
|
138
|
+
yield {
|
|
139
|
+
role: 'assistant',
|
|
140
|
+
content: update.text,
|
|
141
|
+
};
|
|
115
142
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (itemEvent.item && 'type' in itemEvent.item && itemEvent.item.type === 'message') {
|
|
120
|
-
hasStarted = true;
|
|
121
|
-
currentText = '';
|
|
122
|
-
}
|
|
123
|
-
break;
|
|
124
|
-
}
|
|
125
|
-
case 'response.output_text.delta': {
|
|
126
|
-
const deltaEvent = event;
|
|
127
|
-
if (hasStarted && deltaEvent.delta) {
|
|
128
|
-
currentText += deltaEvent.delta;
|
|
129
|
-
// Yield updated message
|
|
130
|
-
yield {
|
|
131
|
-
role: 'assistant',
|
|
132
|
-
content: currentText,
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
break;
|
|
136
|
-
}
|
|
137
|
-
case 'response.output_item.done': {
|
|
138
|
-
const itemDoneEvent = event;
|
|
139
|
-
if (itemDoneEvent.item &&
|
|
140
|
-
'type' in itemDoneEvent.item &&
|
|
141
|
-
itemDoneEvent.item.type === 'message') {
|
|
142
|
-
// Yield final complete message
|
|
143
|
-
const outputMessage = itemDoneEvent.item;
|
|
144
|
-
yield convertToAssistantMessage(outputMessage);
|
|
145
|
-
}
|
|
146
|
-
break;
|
|
147
|
-
}
|
|
143
|
+
else if (update.type === 'complete' && update.completeMessage) {
|
|
144
|
+
// Yield final complete message converted to chat format
|
|
145
|
+
yield convertToAssistantMessage(update.completeMessage);
|
|
148
146
|
}
|
|
149
147
|
}
|
|
150
148
|
}
|
|
@@ -157,19 +155,16 @@ export async function consumeStreamForCompletion(stream) {
|
|
|
157
155
|
if (!('type' in event)) {
|
|
158
156
|
continue;
|
|
159
157
|
}
|
|
160
|
-
if (event
|
|
161
|
-
|
|
162
|
-
return completedEvent.response;
|
|
158
|
+
if (isResponseCompletedEvent(event)) {
|
|
159
|
+
return event.response;
|
|
163
160
|
}
|
|
164
|
-
if (event
|
|
165
|
-
const failedEvent = event;
|
|
161
|
+
if (isResponseFailedEvent(event)) {
|
|
166
162
|
// The failed event contains the full response with error information
|
|
167
|
-
throw new Error(`Response failed: ${JSON.stringify(
|
|
163
|
+
throw new Error(`Response failed: ${JSON.stringify(event.response.error)}`);
|
|
168
164
|
}
|
|
169
|
-
if (event
|
|
170
|
-
const incompleteEvent = event;
|
|
165
|
+
if (isResponseIncompleteEvent(event)) {
|
|
171
166
|
// Return the incomplete response
|
|
172
|
-
return
|
|
167
|
+
return event.response;
|
|
173
168
|
}
|
|
174
169
|
}
|
|
175
170
|
throw new Error('Stream ended without completion event');
|
|
@@ -216,6 +211,12 @@ export function extractTextFromResponse(response) {
|
|
|
216
211
|
if (response.outputText) {
|
|
217
212
|
return response.outputText;
|
|
218
213
|
}
|
|
214
|
+
// Check if there's a message in the output
|
|
215
|
+
const hasMessage = response.output.some((item) => 'type' in item && item.type === 'message');
|
|
216
|
+
if (!hasMessage) {
|
|
217
|
+
// No message in response (e.g., only function calls)
|
|
218
|
+
return '';
|
|
219
|
+
}
|
|
219
220
|
// Otherwise, extract from the first message (convert to AssistantMessage which has string content)
|
|
220
221
|
const message = extractMessageFromResponse(response);
|
|
221
222
|
// AssistantMessage.content is string | Array | null | undefined
|
|
@@ -231,22 +232,22 @@ export function extractTextFromResponse(response) {
|
|
|
231
232
|
export function extractToolCallsFromResponse(response) {
|
|
232
233
|
const toolCalls = [];
|
|
233
234
|
for (const item of response.output) {
|
|
234
|
-
if (
|
|
235
|
-
const functionCallItem = item;
|
|
235
|
+
if (isFunctionCallOutputItem(item)) {
|
|
236
236
|
try {
|
|
237
|
-
const parsedArguments = JSON.parse(
|
|
237
|
+
const parsedArguments = JSON.parse(item.arguments);
|
|
238
238
|
toolCalls.push({
|
|
239
|
-
id:
|
|
240
|
-
name:
|
|
239
|
+
id: item.callId,
|
|
240
|
+
name: item.name,
|
|
241
241
|
arguments: parsedArguments,
|
|
242
242
|
});
|
|
243
243
|
}
|
|
244
|
-
catch (
|
|
244
|
+
catch (error) {
|
|
245
|
+
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 ? '...' : ''}`);
|
|
245
246
|
// Include the tool call with unparsed arguments
|
|
246
247
|
toolCalls.push({
|
|
247
|
-
id:
|
|
248
|
-
name:
|
|
249
|
-
arguments:
|
|
248
|
+
id: item.callId,
|
|
249
|
+
name: item.name,
|
|
250
|
+
arguments: item.arguments, // Keep as string if parsing fails
|
|
250
251
|
});
|
|
251
252
|
}
|
|
252
253
|
}
|
|
@@ -267,75 +268,72 @@ export async function* buildToolCallStream(stream) {
|
|
|
267
268
|
}
|
|
268
269
|
switch (event.type) {
|
|
269
270
|
case 'response.output_item.added': {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
id: functionCallItem.callId,
|
|
275
|
-
name: functionCallItem.name,
|
|
271
|
+
if (isOutputItemAddedEvent(event) && event.item && isFunctionCallOutputItem(event.item)) {
|
|
272
|
+
toolCallsInProgress.set(event.item.callId, {
|
|
273
|
+
id: event.item.callId,
|
|
274
|
+
name: event.item.name,
|
|
276
275
|
argumentsAccumulated: '',
|
|
277
276
|
});
|
|
278
277
|
}
|
|
279
278
|
break;
|
|
280
279
|
}
|
|
281
280
|
case 'response.function_call_arguments.delta': {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
281
|
+
if (isFunctionCallArgumentsDeltaEvent(event)) {
|
|
282
|
+
const toolCall = toolCallsInProgress.get(event.itemId);
|
|
283
|
+
if (toolCall && event.delta) {
|
|
284
|
+
toolCall.argumentsAccumulated += event.delta;
|
|
285
|
+
}
|
|
286
286
|
}
|
|
287
287
|
break;
|
|
288
288
|
}
|
|
289
289
|
case 'response.function_call_arguments.done': {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
290
|
+
if (isFunctionCallArgumentsDoneEvent(event)) {
|
|
291
|
+
const toolCall = toolCallsInProgress.get(event.itemId);
|
|
292
|
+
if (toolCall) {
|
|
293
|
+
// Parse complete arguments
|
|
294
|
+
try {
|
|
295
|
+
const parsedArguments = JSON.parse(event.arguments);
|
|
296
|
+
yield {
|
|
297
|
+
id: toolCall.id,
|
|
298
|
+
name: event.name,
|
|
299
|
+
arguments: parsedArguments,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
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 ? '...' : ''}`);
|
|
304
|
+
// Yield with unparsed arguments if parsing fails
|
|
305
|
+
yield {
|
|
306
|
+
id: toolCall.id,
|
|
307
|
+
name: event.name,
|
|
308
|
+
arguments: event.arguments,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
// Clean up
|
|
312
|
+
toolCallsInProgress.delete(event.itemId);
|
|
309
313
|
}
|
|
310
|
-
// Clean up
|
|
311
|
-
toolCallsInProgress.delete(doneEvent.itemId);
|
|
312
314
|
}
|
|
313
315
|
break;
|
|
314
316
|
}
|
|
315
317
|
case 'response.output_item.done': {
|
|
316
|
-
|
|
317
|
-
if (itemDoneEvent.item &&
|
|
318
|
-
'type' in itemDoneEvent.item &&
|
|
319
|
-
itemDoneEvent.item.type === 'function_call') {
|
|
320
|
-
const functionCallItem = itemDoneEvent.item;
|
|
318
|
+
if (isOutputItemDoneEvent(event) && event.item && isFunctionCallOutputItem(event.item)) {
|
|
321
319
|
// Yield final tool call if we haven't already
|
|
322
|
-
if (toolCallsInProgress.has(
|
|
320
|
+
if (toolCallsInProgress.has(event.item.callId)) {
|
|
323
321
|
try {
|
|
324
|
-
const parsedArguments = JSON.parse(
|
|
322
|
+
const parsedArguments = JSON.parse(event.item.arguments);
|
|
325
323
|
yield {
|
|
326
|
-
id:
|
|
327
|
-
name:
|
|
324
|
+
id: event.item.callId,
|
|
325
|
+
name: event.item.name,
|
|
328
326
|
arguments: parsedArguments,
|
|
329
327
|
};
|
|
330
328
|
}
|
|
331
329
|
catch (_error) {
|
|
332
330
|
yield {
|
|
333
|
-
id:
|
|
334
|
-
name:
|
|
335
|
-
arguments:
|
|
331
|
+
id: event.item.callId,
|
|
332
|
+
name: event.item.name,
|
|
333
|
+
arguments: event.item.arguments,
|
|
336
334
|
};
|
|
337
335
|
}
|
|
338
|
-
toolCallsInProgress.delete(
|
|
336
|
+
toolCallsInProgress.delete(event.item.callId);
|
|
339
337
|
}
|
|
340
338
|
}
|
|
341
339
|
break;
|
|
@@ -363,45 +361,52 @@ function mapAnnotationsToCitations(annotations) {
|
|
|
363
361
|
}
|
|
364
362
|
switch (annotation.type) {
|
|
365
363
|
case 'file_citation': {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
364
|
+
if (isFileCitationAnnotation(annotation)) {
|
|
365
|
+
citations.push({
|
|
366
|
+
type: 'char_location',
|
|
367
|
+
cited_text: '',
|
|
368
|
+
document_index: annotation.index,
|
|
369
|
+
document_title: annotation.filename,
|
|
370
|
+
file_id: annotation.fileId,
|
|
371
|
+
start_char_index: 0,
|
|
372
|
+
end_char_index: 0,
|
|
373
|
+
});
|
|
374
|
+
}
|
|
376
375
|
break;
|
|
377
376
|
}
|
|
378
377
|
case 'url_citation': {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
378
|
+
if (isURLCitationAnnotation(annotation)) {
|
|
379
|
+
citations.push({
|
|
380
|
+
type: 'web_search_result_location',
|
|
381
|
+
cited_text: '',
|
|
382
|
+
title: annotation.title,
|
|
383
|
+
url: annotation.url,
|
|
384
|
+
encrypted_index: '',
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
387
|
break;
|
|
388
388
|
}
|
|
389
389
|
case 'file_path': {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
390
|
+
if (isFilePathAnnotation(annotation)) {
|
|
391
|
+
citations.push({
|
|
392
|
+
type: 'char_location',
|
|
393
|
+
cited_text: '',
|
|
394
|
+
document_index: annotation.index,
|
|
395
|
+
document_title: '',
|
|
396
|
+
file_id: annotation.fileId,
|
|
397
|
+
start_char_index: 0,
|
|
398
|
+
end_char_index: 0,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
400
401
|
break;
|
|
401
402
|
}
|
|
402
403
|
default: {
|
|
403
|
-
|
|
404
|
-
|
|
404
|
+
// Exhaustiveness check - TypeScript will error if we don't handle all annotation types
|
|
405
|
+
const exhaustiveCheck = annotation;
|
|
406
|
+
// Cast to unknown for runtime debugging if type system bypassed
|
|
407
|
+
// This should never execute - throw with JSON of the unhandled value
|
|
408
|
+
throw new Error(`Unhandled annotation type. This indicates a new annotation type was added. ` +
|
|
409
|
+
`Annotation: ${JSON.stringify(exhaustiveCheck)}`);
|
|
405
410
|
}
|
|
406
411
|
}
|
|
407
412
|
}
|
|
@@ -439,116 +444,160 @@ export function convertToClaudeMessage(response) {
|
|
|
439
444
|
const unsupportedContent = [];
|
|
440
445
|
for (const item of response.output) {
|
|
441
446
|
if (!('type' in item)) {
|
|
447
|
+
// Handle items without type field
|
|
448
|
+
// Convert unknown item to a record format for storage
|
|
449
|
+
const itemData = typeof item === 'object' && item !== null
|
|
450
|
+
? item
|
|
451
|
+
: { value: item };
|
|
452
|
+
unsupportedContent.push({
|
|
453
|
+
original_type: 'unknown',
|
|
454
|
+
data: itemData,
|
|
455
|
+
reason: 'Output item missing type field',
|
|
456
|
+
});
|
|
442
457
|
continue;
|
|
443
458
|
}
|
|
444
459
|
switch (item.type) {
|
|
445
460
|
case 'message': {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
461
|
+
if (isOutputMessage(item)) {
|
|
462
|
+
for (const part of item.content) {
|
|
463
|
+
if (!('type' in part)) {
|
|
464
|
+
// Convert unknown part to a record format for storage
|
|
465
|
+
const partData = typeof part === 'object' && part !== null
|
|
466
|
+
? part
|
|
467
|
+
: { value: part };
|
|
468
|
+
unsupportedContent.push({
|
|
469
|
+
original_type: 'unknown_message_part',
|
|
470
|
+
data: partData,
|
|
471
|
+
reason: 'Message content part missing type field',
|
|
472
|
+
});
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
if (isOutputTextPart(part)) {
|
|
476
|
+
const citations = mapAnnotationsToCitations(part.annotations);
|
|
477
|
+
content.push({
|
|
478
|
+
type: 'text',
|
|
479
|
+
text: part.text,
|
|
480
|
+
...(citations && {
|
|
481
|
+
citations,
|
|
482
|
+
}),
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
else if (isRefusalPart(part)) {
|
|
486
|
+
unsupportedContent.push({
|
|
487
|
+
original_type: 'refusal',
|
|
488
|
+
data: {
|
|
489
|
+
refusal: part.refusal,
|
|
490
|
+
},
|
|
491
|
+
reason: 'Claude does not have a native refusal content type',
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
// Exhaustiveness check - TypeScript will error if we don't handle all part types
|
|
496
|
+
const exhaustiveCheck = part;
|
|
497
|
+
// This should never execute - new content type was added
|
|
498
|
+
throw new Error(`Unhandled message content type. This indicates a new content type was added. ` +
|
|
499
|
+
`Part: ${JSON.stringify(exhaustiveCheck)}`);
|
|
500
|
+
}
|
|
467
501
|
}
|
|
468
502
|
}
|
|
469
503
|
break;
|
|
470
504
|
}
|
|
471
505
|
case 'function_call': {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
506
|
+
if (isFunctionCallOutputItem(item)) {
|
|
507
|
+
let parsedInput;
|
|
508
|
+
try {
|
|
509
|
+
parsedInput = JSON.parse(item.arguments);
|
|
510
|
+
}
|
|
511
|
+
catch (error) {
|
|
512
|
+
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 ? '...' : ''}`);
|
|
513
|
+
// Preserve raw arguments if JSON parsing fails
|
|
514
|
+
parsedInput = {
|
|
515
|
+
_raw_arguments: item.arguments,
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
content.push({
|
|
519
|
+
type: 'tool_use',
|
|
520
|
+
id: item.callId,
|
|
521
|
+
name: item.name,
|
|
522
|
+
input: parsedInput,
|
|
523
|
+
});
|
|
479
524
|
}
|
|
480
|
-
content.push({
|
|
481
|
-
type: 'tool_use',
|
|
482
|
-
id: fnCall.callId,
|
|
483
|
-
name: fnCall.name,
|
|
484
|
-
input: parsedInput,
|
|
485
|
-
});
|
|
486
525
|
break;
|
|
487
526
|
}
|
|
488
527
|
case 'reasoning': {
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
528
|
+
if (isReasoningOutputItem(item)) {
|
|
529
|
+
if (item.summary && item.summary.length > 0) {
|
|
530
|
+
for (const summaryItem of item.summary) {
|
|
531
|
+
if (summaryItem.type === 'summary_text' && summaryItem.text) {
|
|
532
|
+
content.push({
|
|
533
|
+
type: 'thinking',
|
|
534
|
+
thinking: summaryItem.text,
|
|
535
|
+
signature: '',
|
|
536
|
+
});
|
|
537
|
+
}
|
|
498
538
|
}
|
|
499
539
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
}
|
|
540
|
+
if (item.encryptedContent) {
|
|
541
|
+
unsupportedContent.push({
|
|
542
|
+
original_type: 'reasoning_encrypted',
|
|
543
|
+
data: {
|
|
544
|
+
id: item.id,
|
|
545
|
+
encrypted_content: item.encryptedContent,
|
|
546
|
+
},
|
|
547
|
+
reason: 'Encrypted reasoning content preserved for round-trip',
|
|
548
|
+
});
|
|
549
|
+
}
|
|
510
550
|
}
|
|
511
551
|
break;
|
|
512
552
|
}
|
|
513
553
|
case 'web_search_call': {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
554
|
+
if (isWebSearchCallOutputItem(item)) {
|
|
555
|
+
content.push({
|
|
556
|
+
type: 'server_tool_use',
|
|
557
|
+
id: item.id,
|
|
558
|
+
name: 'web_search',
|
|
559
|
+
input: {
|
|
560
|
+
status: item.status,
|
|
561
|
+
},
|
|
562
|
+
});
|
|
563
|
+
}
|
|
521
564
|
break;
|
|
522
565
|
}
|
|
523
566
|
case 'file_search_call': {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
567
|
+
if (isFileSearchCallOutputItem(item)) {
|
|
568
|
+
content.push({
|
|
569
|
+
type: 'tool_use',
|
|
570
|
+
id: item.id,
|
|
571
|
+
name: 'file_search',
|
|
572
|
+
input: {
|
|
573
|
+
queries: item.queries,
|
|
574
|
+
status: item.status,
|
|
575
|
+
},
|
|
576
|
+
});
|
|
577
|
+
}
|
|
534
578
|
break;
|
|
535
579
|
}
|
|
536
580
|
case 'image_generation_call': {
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
581
|
+
if (isImageGenerationCallOutputItem(item)) {
|
|
582
|
+
unsupportedContent.push({
|
|
583
|
+
original_type: 'image_generation_call',
|
|
584
|
+
data: {
|
|
585
|
+
id: item.id,
|
|
586
|
+
result: item.result,
|
|
587
|
+
status: item.status,
|
|
588
|
+
},
|
|
589
|
+
reason: 'Claude does not support image outputs in assistant messages',
|
|
590
|
+
});
|
|
591
|
+
}
|
|
547
592
|
break;
|
|
548
593
|
}
|
|
549
594
|
default: {
|
|
550
|
-
|
|
551
|
-
|
|
595
|
+
// Exhaustiveness check - if a new output type is added, TypeScript will error here
|
|
596
|
+
const exhaustiveCheck = item;
|
|
597
|
+
// This line should never execute - it means a new type was added to the union
|
|
598
|
+
// Throw an error instead of silently continuing to ensure we catch new types
|
|
599
|
+
throw new Error(`Unhandled output item type. This indicates a new output type was added to the API. ` +
|
|
600
|
+
`Item: ${JSON.stringify(exhaustiveCheck)}`);
|
|
552
601
|
}
|
|
553
602
|
}
|
|
554
603
|
}
|
|
@@ -566,7 +615,9 @@ export function convertToClaudeMessage(response) {
|
|
|
566
615
|
cache_creation_input_tokens: response.usage?.inputTokensDetails?.cachedTokens ?? 0,
|
|
567
616
|
cache_read_input_tokens: 0,
|
|
568
617
|
},
|
|
569
|
-
...(unsupportedContent.length > 0 && {
|
|
618
|
+
...(unsupportedContent.length > 0 && {
|
|
619
|
+
unsupported_content: unsupportedContent,
|
|
620
|
+
}),
|
|
570
621
|
};
|
|
571
622
|
}
|
|
572
623
|
/**
|
|
@@ -576,7 +627,7 @@ export function extractUnsupportedContent(message, originalType) {
|
|
|
576
627
|
if (!message.unsupported_content) {
|
|
577
628
|
return [];
|
|
578
629
|
}
|
|
579
|
-
return message.unsupported_content.filter(item => item.original_type === originalType);
|
|
630
|
+
return message.unsupported_content.filter((item) => item.original_type === originalType);
|
|
580
631
|
}
|
|
581
632
|
/**
|
|
582
633
|
* Check if message has any unsupported content
|