@f5-sales-demo/pi-ai 19.51.2

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 (128) hide show
  1. package/CHANGELOG.md +1997 -0
  2. package/README.md +1160 -0
  3. package/package.json +135 -0
  4. package/src/api-registry.ts +95 -0
  5. package/src/auth-storage.ts +2694 -0
  6. package/src/cli.ts +493 -0
  7. package/src/index.ts +42 -0
  8. package/src/model-cache.ts +97 -0
  9. package/src/model-manager.ts +349 -0
  10. package/src/model-thinking.ts +561 -0
  11. package/src/models.json +49439 -0
  12. package/src/models.json.d.ts +9 -0
  13. package/src/models.ts +56 -0
  14. package/src/prompts/turn-aborted-guidance.md +4 -0
  15. package/src/provider-details.ts +81 -0
  16. package/src/provider-models/descriptors.ts +285 -0
  17. package/src/provider-models/google.ts +90 -0
  18. package/src/provider-models/index.ts +4 -0
  19. package/src/provider-models/openai-compat.ts +2074 -0
  20. package/src/provider-models/special.ts +106 -0
  21. package/src/providers/amazon-bedrock.ts +706 -0
  22. package/src/providers/anthropic.ts +1682 -0
  23. package/src/providers/azure-openai-responses.ts +391 -0
  24. package/src/providers/cursor/gen/agent_pb.ts +15274 -0
  25. package/src/providers/cursor/proto/agent.proto +3526 -0
  26. package/src/providers/cursor/proto/buf.gen.yaml +6 -0
  27. package/src/providers/cursor/proto/buf.yaml +17 -0
  28. package/src/providers/cursor.ts +2218 -0
  29. package/src/providers/github-copilot-headers.ts +140 -0
  30. package/src/providers/gitlab-duo.ts +381 -0
  31. package/src/providers/google-gemini-cli.ts +1133 -0
  32. package/src/providers/google-shared.ts +354 -0
  33. package/src/providers/google-vertex.ts +436 -0
  34. package/src/providers/google.ts +381 -0
  35. package/src/providers/kimi.ts +151 -0
  36. package/src/providers/openai-codex/constants.ts +43 -0
  37. package/src/providers/openai-codex/request-transformer.ts +158 -0
  38. package/src/providers/openai-codex/response-handler.ts +81 -0
  39. package/src/providers/openai-codex-responses.ts +2345 -0
  40. package/src/providers/openai-completions-compat.ts +159 -0
  41. package/src/providers/openai-completions.ts +1290 -0
  42. package/src/providers/openai-responses-shared.ts +452 -0
  43. package/src/providers/openai-responses.ts +519 -0
  44. package/src/providers/register-builtins.ts +329 -0
  45. package/src/providers/synthetic.ts +154 -0
  46. package/src/providers/transform-messages.ts +234 -0
  47. package/src/rate-limit-utils.ts +84 -0
  48. package/src/stream.ts +728 -0
  49. package/src/types.ts +546 -0
  50. package/src/usage/claude.ts +337 -0
  51. package/src/usage/gemini.ts +248 -0
  52. package/src/usage/github-copilot.ts +421 -0
  53. package/src/usage/google-antigravity.ts +200 -0
  54. package/src/usage/kimi.ts +286 -0
  55. package/src/usage/minimax-code.ts +31 -0
  56. package/src/usage/openai-codex.ts +387 -0
  57. package/src/usage/zai.ts +247 -0
  58. package/src/usage.ts +130 -0
  59. package/src/utils/abort.ts +36 -0
  60. package/src/utils/anthropic-auth.ts +293 -0
  61. package/src/utils/discovery/antigravity.ts +261 -0
  62. package/src/utils/discovery/codex.ts +371 -0
  63. package/src/utils/discovery/cursor.ts +306 -0
  64. package/src/utils/discovery/gemini.ts +248 -0
  65. package/src/utils/discovery/index.ts +5 -0
  66. package/src/utils/discovery/openai-compatible.ts +224 -0
  67. package/src/utils/event-stream.ts +209 -0
  68. package/src/utils/http-inspector.ts +165 -0
  69. package/src/utils/idle-iterator.ts +176 -0
  70. package/src/utils/json-parse.ts +28 -0
  71. package/src/utils/oauth/alibaba-coding-plan.ts +59 -0
  72. package/src/utils/oauth/anthropic.ts +134 -0
  73. package/src/utils/oauth/api-key-validation.ts +92 -0
  74. package/src/utils/oauth/callback-server.ts +276 -0
  75. package/src/utils/oauth/cerebras.ts +59 -0
  76. package/src/utils/oauth/cloudflare-ai-gateway.ts +48 -0
  77. package/src/utils/oauth/cursor.ts +157 -0
  78. package/src/utils/oauth/github-copilot.ts +358 -0
  79. package/src/utils/oauth/gitlab-duo.ts +123 -0
  80. package/src/utils/oauth/google-antigravity.ts +275 -0
  81. package/src/utils/oauth/google-gemini-cli.ts +334 -0
  82. package/src/utils/oauth/huggingface.ts +62 -0
  83. package/src/utils/oauth/index.ts +512 -0
  84. package/src/utils/oauth/kagi.ts +47 -0
  85. package/src/utils/oauth/kilo.ts +87 -0
  86. package/src/utils/oauth/kimi.ts +251 -0
  87. package/src/utils/oauth/litellm.ts +81 -0
  88. package/src/utils/oauth/lm-studio.ts +40 -0
  89. package/src/utils/oauth/minimax-code.ts +78 -0
  90. package/src/utils/oauth/moonshot.ts +59 -0
  91. package/src/utils/oauth/nanogpt.ts +51 -0
  92. package/src/utils/oauth/nvidia.ts +70 -0
  93. package/src/utils/oauth/oauth.html +199 -0
  94. package/src/utils/oauth/ollama.ts +47 -0
  95. package/src/utils/oauth/openai-codex.ts +190 -0
  96. package/src/utils/oauth/opencode.ts +49 -0
  97. package/src/utils/oauth/parallel.ts +46 -0
  98. package/src/utils/oauth/perplexity.ts +200 -0
  99. package/src/utils/oauth/pkce.ts +18 -0
  100. package/src/utils/oauth/qianfan.ts +58 -0
  101. package/src/utils/oauth/qwen-portal.ts +60 -0
  102. package/src/utils/oauth/synthetic.ts +60 -0
  103. package/src/utils/oauth/tavily.ts +46 -0
  104. package/src/utils/oauth/together.ts +59 -0
  105. package/src/utils/oauth/types.ts +89 -0
  106. package/src/utils/oauth/venice.ts +59 -0
  107. package/src/utils/oauth/vercel-ai-gateway.ts +47 -0
  108. package/src/utils/oauth/vllm.ts +40 -0
  109. package/src/utils/oauth/xiaomi.ts +88 -0
  110. package/src/utils/oauth/zai.ts +60 -0
  111. package/src/utils/oauth/zenmux.ts +51 -0
  112. package/src/utils/overflow.ts +134 -0
  113. package/src/utils/retry-after.ts +110 -0
  114. package/src/utils/retry.ts +93 -0
  115. package/src/utils/schema/CONSTRAINTS.md +160 -0
  116. package/src/utils/schema/adapt.ts +20 -0
  117. package/src/utils/schema/compatibility.ts +397 -0
  118. package/src/utils/schema/dereference.ts +93 -0
  119. package/src/utils/schema/equality.ts +93 -0
  120. package/src/utils/schema/fields.ts +147 -0
  121. package/src/utils/schema/index.ts +9 -0
  122. package/src/utils/schema/normalize-cca.ts +479 -0
  123. package/src/utils/schema/sanitize-google.ts +212 -0
  124. package/src/utils/schema/strict-mode.ts +385 -0
  125. package/src/utils/schema/types.ts +5 -0
  126. package/src/utils/tool-choice.ts +81 -0
  127. package/src/utils/validation.ts +664 -0
  128. package/src/utils.ts +147 -0
@@ -0,0 +1,452 @@
1
+ import { structuredCloneJSON } from "@f5xc-salesdemos/pi-utils";
2
+ import type OpenAI from "openai";
3
+ import type {
4
+ ResponseFunctionToolCall,
5
+ ResponseInput,
6
+ ResponseInputContent,
7
+ ResponseInputImage,
8
+ ResponseInputText,
9
+ ResponseOutputItem,
10
+ ResponseOutputMessage,
11
+ ResponseReasoningItem,
12
+ } from "openai/resources/responses/responses";
13
+ import { calculateCost } from "../models";
14
+ import type {
15
+ Api,
16
+ AssistantMessage,
17
+ ImageContent,
18
+ Model,
19
+ StopReason,
20
+ TextContent,
21
+ TextSignatureV1,
22
+ ThinkingContent,
23
+ ToolCall,
24
+ ToolResultMessage,
25
+ } from "../types";
26
+ import { normalizeResponsesToolCallId } from "../utils";
27
+ import type { AssistantMessageEventStream } from "../utils/event-stream";
28
+ import { parseStreamingJson } from "../utils/json-parse";
29
+
30
+ export function encodeTextSignatureV1(id: string, phase?: TextSignatureV1["phase"]): string {
31
+ const payload: TextSignatureV1 = { v: 1, id };
32
+ if (phase) payload.phase = phase;
33
+ return JSON.stringify(payload);
34
+ }
35
+
36
+ export function parseTextSignature(
37
+ signature: string | undefined,
38
+ ): { id: string; phase?: TextSignatureV1["phase"] } | undefined {
39
+ if (!signature) return undefined;
40
+ if (signature.startsWith("{")) {
41
+ try {
42
+ const parsed = JSON.parse(signature) as Partial<TextSignatureV1>;
43
+ if (parsed.v === 1 && typeof parsed.id === "string") {
44
+ if (parsed.phase === "commentary" || parsed.phase === "final_answer") {
45
+ return { id: parsed.id, phase: parsed.phase };
46
+ }
47
+ return { id: parsed.id };
48
+ }
49
+ } catch {
50
+ // Fall through to legacy plain-string handling.
51
+ }
52
+ }
53
+ return { id: signature };
54
+ }
55
+
56
+ export function normalizeResponsesToolCallIdForTransform(
57
+ id: string,
58
+ model?: Model<Api>,
59
+ source?: AssistantMessage,
60
+ ): string {
61
+ if (!id.includes("|")) return id;
62
+ const isForeignToolCall =
63
+ source != null && model != null && (source.provider !== model.provider || source.api !== model.api);
64
+ if (isForeignToolCall) {
65
+ const [callId, itemId] = id.split("|");
66
+ const normalizeIdPart = (part: string): string => {
67
+ const sanitized = part.replace(/[^a-zA-Z0-9_-]/g, "_");
68
+ const truncated = sanitized.length > 64 ? sanitized.slice(0, 64) : sanitized;
69
+ return truncated.replace(/_+$/, "");
70
+ };
71
+ const normalizedCallId = normalizeIdPart(callId);
72
+ let normalizedItemId = `fc_${Bun.hash(itemId).toString(36)}`;
73
+ if (normalizedItemId.length > 64) normalizedItemId = normalizedItemId.slice(0, 64);
74
+ return `${normalizedCallId}|${normalizedItemId}`;
75
+ }
76
+ const normalized = normalizeResponsesToolCallId(id);
77
+ return `${normalized.callId}|${normalized.itemId}`;
78
+ }
79
+
80
+ export function collectKnownCallIds(messages: ResponseInput): Set<string> {
81
+ const knownCallIds = new Set<string>();
82
+ for (const item of messages) {
83
+ if (item.type === "function_call" && typeof item.call_id === "string") {
84
+ knownCallIds.add(item.call_id);
85
+ }
86
+ }
87
+ return knownCallIds;
88
+ }
89
+
90
+ export function convertResponsesInputContent(
91
+ content: string | Array<TextContent | ImageContent>,
92
+ supportsImages: boolean,
93
+ ): ResponseInputContent[] | undefined {
94
+ if (typeof content === "string") {
95
+ if (content.trim().length === 0) return undefined;
96
+ return [{ type: "input_text", text: content.toWellFormed() } satisfies ResponseInputText];
97
+ }
98
+
99
+ const normalizedContent = content
100
+ .map((item): ResponseInputContent => {
101
+ if (item.type === "text") {
102
+ return {
103
+ type: "input_text",
104
+ text: item.text.toWellFormed(),
105
+ } satisfies ResponseInputText;
106
+ }
107
+ return {
108
+ type: "input_image",
109
+ detail: "auto",
110
+ image_url: `data:${item.mimeType};base64,${item.data}`,
111
+ } satisfies ResponseInputImage;
112
+ })
113
+ .filter(item => supportsImages || item.type !== "input_image")
114
+ .filter(item => item.type !== "input_text" || item.text.trim().length > 0);
115
+
116
+ return normalizedContent.length > 0 ? normalizedContent : undefined;
117
+ }
118
+
119
+ export function convertResponsesAssistantMessage<TApi extends Api>(
120
+ assistantMsg: AssistantMessage,
121
+ model: Model<TApi>,
122
+ msgIndex: number,
123
+ knownCallIds: Set<string>,
124
+ includeThinkingSignatures = true,
125
+ ): ResponseInput {
126
+ const outputItems: ResponseInput = [];
127
+ const isDifferentModel =
128
+ assistantMsg.model !== model.id && assistantMsg.provider === model.provider && assistantMsg.api === model.api;
129
+
130
+ for (const block of assistantMsg.content) {
131
+ if (block.type === "thinking" && assistantMsg.stopReason !== "error") {
132
+ if (!includeThinkingSignatures) {
133
+ continue;
134
+ }
135
+ if (block.thinkingSignature) {
136
+ outputItems.push(JSON.parse(block.thinkingSignature) as ResponseReasoningItem);
137
+ }
138
+ continue;
139
+ }
140
+
141
+ if (block.type === "text") {
142
+ const parsedSignature = parseTextSignature(block.textSignature);
143
+ let msgId = parsedSignature?.id;
144
+ if (!msgId) {
145
+ msgId = `msg_${msgIndex}`;
146
+ } else if (msgId.length > 64) {
147
+ msgId = `msg_${Bun.hash(msgId).toString(36)}`;
148
+ }
149
+ outputItems.push({
150
+ type: "message",
151
+ role: "assistant",
152
+ content: [{ type: "output_text", text: block.text.toWellFormed(), annotations: [] }],
153
+ status: "completed",
154
+ id: msgId,
155
+ phase: parsedSignature?.phase,
156
+ } satisfies ResponseOutputMessage);
157
+ continue;
158
+ }
159
+
160
+ if (block.type !== "toolCall") {
161
+ continue;
162
+ }
163
+
164
+ const normalized = normalizeResponsesToolCallId(block.id);
165
+ let itemId: string | undefined = normalized.itemId;
166
+ if (isDifferentModel && (itemId?.startsWith("fc_") || itemId?.startsWith("fcr_"))) {
167
+ itemId = undefined;
168
+ }
169
+ knownCallIds.add(normalized.callId);
170
+ outputItems.push({
171
+ type: "function_call",
172
+ id: itemId,
173
+ call_id: normalized.callId,
174
+ name: block.name,
175
+ arguments: JSON.stringify(block.arguments),
176
+ });
177
+ }
178
+
179
+ return outputItems;
180
+ }
181
+
182
+ export function appendResponsesToolResultMessages<TApi extends Api>(
183
+ messages: ResponseInput,
184
+ toolResult: ToolResultMessage,
185
+ model: Model<TApi>,
186
+ strictResponsesPairing: boolean,
187
+ knownCallIds: ReadonlySet<string>,
188
+ ): void {
189
+ const textResult = toolResult.content
190
+ .filter((block): block is TextContent => block.type === "text")
191
+ .map(block => block.text)
192
+ .join("\n");
193
+ const hasImages = toolResult.content.some((block): block is ImageContent => block.type === "image");
194
+ const normalized = normalizeResponsesToolCallId(toolResult.toolCallId);
195
+ if (strictResponsesPairing && !knownCallIds.has(normalized.callId)) {
196
+ return;
197
+ }
198
+
199
+ messages.push({
200
+ type: "function_call_output",
201
+ call_id: normalized.callId,
202
+ output: (textResult.length > 0 ? textResult : "(see attached image)").toWellFormed(),
203
+ });
204
+
205
+ if (!hasImages || !model.input.includes("image")) {
206
+ return;
207
+ }
208
+
209
+ const contentParts: ResponseInputContent[] = [
210
+ { type: "input_text", text: "Attached image(s) from tool result:" } satisfies ResponseInputText,
211
+ ];
212
+ for (const block of toolResult.content) {
213
+ if (block.type === "image") {
214
+ contentParts.push({
215
+ type: "input_image",
216
+ detail: "auto",
217
+ image_url: `data:${block.mimeType};base64,${block.data}`,
218
+ } satisfies ResponseInputImage);
219
+ }
220
+ }
221
+ messages.push({ role: "user", content: contentParts });
222
+ }
223
+
224
+ export interface ProcessResponsesStreamOptions {
225
+ onFirstToken?: () => void;
226
+ onOutputItemDone?: (item: ResponseOutputItem) => void;
227
+ }
228
+
229
+ export async function processResponsesStream<TApi extends Api>(
230
+ openaiStream: AsyncIterable<OpenAI.Responses.ResponseStreamEvent>,
231
+ output: AssistantMessage,
232
+ stream: AssistantMessageEventStream,
233
+ model: Model<TApi>,
234
+ options?: ProcessResponsesStreamOptions,
235
+ ): Promise<void> {
236
+ let currentItem: ResponseReasoningItem | ResponseOutputMessage | ResponseFunctionToolCall | null = null;
237
+ let currentBlock: ThinkingContent | TextContent | (ToolCall & { partialJson: string }) | null = null;
238
+ const blocks = output.content;
239
+ const blockIndex = () => blocks.length - 1;
240
+ let sawFirstToken = false;
241
+
242
+ for await (const event of openaiStream) {
243
+ if (event.type === "response.created") {
244
+ output.responseId = event.response.id;
245
+ } else if (event.type === "response.output_item.added") {
246
+ if (!sawFirstToken) {
247
+ sawFirstToken = true;
248
+ options?.onFirstToken?.();
249
+ }
250
+ const item = event.item;
251
+ if (item.type === "reasoning") {
252
+ currentItem = item;
253
+ currentBlock = { type: "thinking", thinking: "" };
254
+ output.content.push(currentBlock);
255
+ stream.push({ type: "thinking_start", contentIndex: blockIndex(), partial: output });
256
+ } else if (item.type === "message") {
257
+ currentItem = item;
258
+ currentBlock = { type: "text", text: "" };
259
+ output.content.push(currentBlock);
260
+ stream.push({ type: "text_start", contentIndex: blockIndex(), partial: output });
261
+ } else if (item.type === "function_call") {
262
+ currentItem = item;
263
+ currentBlock = {
264
+ type: "toolCall",
265
+ id: `${item.call_id}|${item.id}`,
266
+ name: item.name,
267
+ arguments: {},
268
+ partialJson: item.arguments || "",
269
+ };
270
+ output.content.push(currentBlock);
271
+ stream.push({ type: "toolcall_start", contentIndex: blockIndex(), partial: output });
272
+ }
273
+ } else if (event.type === "response.reasoning_summary_part.added") {
274
+ if (currentItem?.type === "reasoning") {
275
+ currentItem.summary = currentItem.summary || [];
276
+ currentItem.summary.push(event.part);
277
+ }
278
+ } else if (event.type === "response.reasoning_summary_text.delta") {
279
+ if (currentItem?.type === "reasoning" && currentBlock?.type === "thinking") {
280
+ currentItem.summary = currentItem.summary || [];
281
+ const lastPart = currentItem.summary[currentItem.summary.length - 1];
282
+ if (lastPart) {
283
+ currentBlock.thinking += event.delta;
284
+ lastPart.text += event.delta;
285
+ stream.push({
286
+ type: "thinking_delta",
287
+ contentIndex: blockIndex(),
288
+ delta: event.delta,
289
+ partial: output,
290
+ });
291
+ }
292
+ }
293
+ } else if (event.type === "response.reasoning_summary_part.done") {
294
+ if (currentItem?.type === "reasoning" && currentBlock?.type === "thinking") {
295
+ currentItem.summary = currentItem.summary || [];
296
+ const lastPart = currentItem.summary[currentItem.summary.length - 1];
297
+ if (lastPart) {
298
+ currentBlock.thinking += "\n\n";
299
+ lastPart.text += "\n\n";
300
+ stream.push({
301
+ type: "thinking_delta",
302
+ contentIndex: blockIndex(),
303
+ delta: "\n\n",
304
+ partial: output,
305
+ });
306
+ }
307
+ }
308
+ } else if (event.type === "response.content_part.added") {
309
+ if (currentItem?.type === "message") {
310
+ currentItem.content = currentItem.content || [];
311
+ if (event.part.type === "output_text" || event.part.type === "refusal") {
312
+ currentItem.content.push(event.part);
313
+ }
314
+ }
315
+ } else if (event.type === "response.output_text.delta") {
316
+ if (currentItem?.type === "message" && currentBlock?.type === "text") {
317
+ const lastPart = currentItem.content?.[currentItem.content.length - 1];
318
+ if (lastPart?.type === "output_text") {
319
+ currentBlock.text += event.delta;
320
+ lastPart.text += event.delta;
321
+ stream.push({
322
+ type: "text_delta",
323
+ contentIndex: blockIndex(),
324
+ delta: event.delta,
325
+ partial: output,
326
+ });
327
+ }
328
+ }
329
+ } else if (event.type === "response.refusal.delta") {
330
+ if (currentItem?.type === "message" && currentBlock?.type === "text") {
331
+ const lastPart = currentItem.content?.[currentItem.content.length - 1];
332
+ if (lastPart?.type === "refusal") {
333
+ currentBlock.text += event.delta;
334
+ lastPart.refusal += event.delta;
335
+ stream.push({
336
+ type: "text_delta",
337
+ contentIndex: blockIndex(),
338
+ delta: event.delta,
339
+ partial: output,
340
+ });
341
+ }
342
+ }
343
+ } else if (event.type === "response.function_call_arguments.delta") {
344
+ if (currentItem?.type === "function_call" && currentBlock?.type === "toolCall") {
345
+ currentBlock.partialJson += event.delta;
346
+ currentBlock.arguments = parseStreamingJson(currentBlock.partialJson);
347
+ stream.push({
348
+ type: "toolcall_delta",
349
+ contentIndex: blockIndex(),
350
+ delta: event.delta,
351
+ partial: output,
352
+ });
353
+ }
354
+ } else if (event.type === "response.function_call_arguments.done") {
355
+ if (currentItem?.type === "function_call" && currentBlock?.type === "toolCall") {
356
+ currentBlock.partialJson = event.arguments;
357
+ currentBlock.arguments = parseStreamingJson(currentBlock.partialJson);
358
+ }
359
+ } else if (event.type === "response.output_item.done") {
360
+ const item = structuredCloneJSON(event.item);
361
+ options?.onOutputItemDone?.(item);
362
+ if (item.type === "reasoning" && currentBlock?.type === "thinking") {
363
+ currentBlock.thinking = item.summary?.map(part => part.text).join("\n\n") || "";
364
+ currentBlock.thinkingSignature = JSON.stringify(item);
365
+ stream.push({
366
+ type: "thinking_end",
367
+ contentIndex: blockIndex(),
368
+ content: currentBlock.thinking,
369
+ partial: output,
370
+ });
371
+ currentBlock = null;
372
+ } else if (item.type === "message" && currentBlock?.type === "text") {
373
+ currentBlock.text = item.content
374
+ .map(part => (part.type === "output_text" ? (part.text ?? "") : (part.refusal ?? "")))
375
+ .join("");
376
+ currentBlock.textSignature = encodeTextSignatureV1(item.id, item.phase ?? undefined);
377
+ stream.push({
378
+ type: "text_end",
379
+ contentIndex: blockIndex(),
380
+ content: currentBlock.text,
381
+ partial: output,
382
+ });
383
+ currentBlock = null;
384
+ } else if (item.type === "function_call") {
385
+ const args =
386
+ currentBlock?.type === "toolCall" && currentBlock.partialJson
387
+ ? parseStreamingJson(currentBlock.partialJson)
388
+ : parseStreamingJson(item.arguments || "{}");
389
+ const toolCall: ToolCall = {
390
+ type: "toolCall",
391
+ id: `${item.call_id}|${item.id}`,
392
+ name: item.name,
393
+ arguments: args,
394
+ };
395
+ currentBlock = null;
396
+ stream.push({ type: "toolcall_end", contentIndex: blockIndex(), toolCall, partial: output });
397
+ }
398
+ } else if (event.type === "response.completed") {
399
+ const response = event.response;
400
+ if (response?.id) {
401
+ output.responseId = response.id;
402
+ }
403
+ if (response?.usage) {
404
+ const cachedTokens = response.usage.input_tokens_details?.cached_tokens || 0;
405
+ output.usage = {
406
+ input: (response.usage.input_tokens || 0) - cachedTokens,
407
+ output: response.usage.output_tokens || 0,
408
+ cacheRead: cachedTokens,
409
+ cacheWrite: 0,
410
+ totalTokens: response.usage.total_tokens || 0,
411
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
412
+ };
413
+ }
414
+ calculateCost(model, output.usage);
415
+ output.stopReason = mapOpenAIResponsesStopReason(response?.status);
416
+ if (output.content.some(block => block.type === "toolCall") && output.stopReason === "stop") {
417
+ output.stopReason = "toolUse";
418
+ }
419
+ } else if (event.type === "error") {
420
+ throw new Error(`Error Code ${event.code}: ${event.message}` || "Unknown error");
421
+ } else if (event.type === "response.failed") {
422
+ const error = event.response?.error;
423
+ const details = event.response?.incomplete_details;
424
+ const message = error
425
+ ? `${error.code || "unknown"}: ${error.message || "no message"}`
426
+ : details?.reason
427
+ ? `incomplete: ${details.reason}`
428
+ : "Unknown error (no error details in response)";
429
+ throw new Error(message);
430
+ }
431
+ }
432
+ }
433
+
434
+ export function mapOpenAIResponsesStopReason(status: OpenAI.Responses.ResponseStatus | undefined): StopReason {
435
+ if (!status) return "stop";
436
+ switch (status) {
437
+ case "completed":
438
+ return "stop";
439
+ case "incomplete":
440
+ return "length";
441
+ case "failed":
442
+ case "cancelled":
443
+ return "error";
444
+ case "in_progress":
445
+ case "queued":
446
+ return "stop";
447
+ default: {
448
+ const exhaustive: never = status;
449
+ throw new Error(`Unhandled stop reason: ${exhaustive}`);
450
+ }
451
+ }
452
+ }