@fleetagent/pi-ai 0.1.8 → 0.2.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 (148) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +29 -24
  3. package/dist/cli.js.map +1 -1
  4. package/dist/env-api-keys.d.ts.map +1 -1
  5. package/dist/env-api-keys.js +1 -1
  6. package/dist/env-api-keys.js.map +1 -1
  7. package/dist/image-models.generated.d.ts +225 -0
  8. package/dist/image-models.generated.d.ts.map +1 -1
  9. package/dist/image-models.generated.js +227 -2
  10. package/dist/image-models.generated.js.map +1 -1
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/models.d.ts +37 -2
  15. package/dist/models.d.ts.map +1 -1
  16. package/dist/models.generated.d.ts +6120 -2593
  17. package/dist/models.generated.d.ts.map +1 -1
  18. package/dist/models.generated.js +5347 -2110
  19. package/dist/models.generated.js.map +1 -1
  20. package/dist/models.js +259 -9
  21. package/dist/models.js.map +1 -1
  22. package/dist/oauth.d.ts +6 -1
  23. package/dist/oauth.d.ts.map +1 -1
  24. package/dist/oauth.js +4 -1
  25. package/dist/oauth.js.map +1 -1
  26. package/dist/providers/amazon-bedrock.d.ts +5 -4
  27. package/dist/providers/amazon-bedrock.d.ts.map +1 -1
  28. package/dist/providers/amazon-bedrock.js +303 -330
  29. package/dist/providers/amazon-bedrock.js.map +1 -1
  30. package/dist/providers/anthropic.d.ts +5 -4
  31. package/dist/providers/anthropic.d.ts.map +1 -1
  32. package/dist/providers/anthropic.js +492 -499
  33. package/dist/providers/anthropic.js.map +1 -1
  34. package/dist/providers/azure-openai-responses.d.ts +4 -2
  35. package/dist/providers/azure-openai-responses.d.ts.map +1 -1
  36. package/dist/providers/azure-openai-responses.js +52 -35
  37. package/dist/providers/azure-openai-responses.js.map +1 -1
  38. package/dist/providers/faux.d.ts +19 -22
  39. package/dist/providers/faux.d.ts.map +1 -1
  40. package/dist/providers/faux.js +106 -70
  41. package/dist/providers/faux.js.map +1 -1
  42. package/dist/providers/github-copilot-headers.d.ts +7 -4
  43. package/dist/providers/github-copilot-headers.d.ts.map +1 -1
  44. package/dist/providers/github-copilot-headers.js.map +1 -1
  45. package/dist/providers/google-shared.d.ts +24 -5
  46. package/dist/providers/google-shared.d.ts.map +1 -1
  47. package/dist/providers/google-shared.js +139 -140
  48. package/dist/providers/google-shared.js.map +1 -1
  49. package/dist/providers/google-vertex.d.ts +3 -7
  50. package/dist/providers/google-vertex.d.ts.map +1 -1
  51. package/dist/providers/google-vertex.js +216 -219
  52. package/dist/providers/google-vertex.js.map +1 -1
  53. package/dist/providers/google.d.ts +4 -7
  54. package/dist/providers/google.d.ts.map +1 -1
  55. package/dist/providers/google.js +214 -218
  56. package/dist/providers/google.js.map +1 -1
  57. package/dist/providers/images/openrouter.d.ts.map +1 -1
  58. package/dist/providers/images/openrouter.js +37 -27
  59. package/dist/providers/images/openrouter.js.map +1 -1
  60. package/dist/providers/mistral.d.ts +7 -6
  61. package/dist/providers/mistral.d.ts.map +1 -1
  62. package/dist/providers/mistral.js +287 -265
  63. package/dist/providers/mistral.js.map +1 -1
  64. package/dist/providers/openai-codex-responses.d.ts +8 -5
  65. package/dist/providers/openai-codex-responses.d.ts.map +1 -1
  66. package/dist/providers/openai-codex-responses.js +493 -391
  67. package/dist/providers/openai-codex-responses.js.map +1 -1
  68. package/dist/providers/openai-completions.d.ts +7 -10
  69. package/dist/providers/openai-completions.d.ts.map +1 -1
  70. package/dist/providers/openai-completions.js +670 -664
  71. package/dist/providers/openai-completions.js.map +1 -1
  72. package/dist/providers/openai-responses-shared.d.ts +5 -4
  73. package/dist/providers/openai-responses-shared.d.ts.map +1 -1
  74. package/dist/providers/openai-responses-shared.js +390 -360
  75. package/dist/providers/openai-responses-shared.js.map +1 -1
  76. package/dist/providers/openai-responses.d.ts +5 -5
  77. package/dist/providers/openai-responses.d.ts.map +1 -1
  78. package/dist/providers/openai-responses.js +134 -109
  79. package/dist/providers/openai-responses.js.map +1 -1
  80. package/dist/providers/simple-options.d.ts +4 -2
  81. package/dist/providers/simple-options.d.ts.map +1 -1
  82. package/dist/providers/simple-options.js.map +1 -1
  83. package/dist/providers/transform-messages.d.ts.map +1 -1
  84. package/dist/providers/transform-messages.js +112 -137
  85. package/dist/providers/transform-messages.js.map +1 -1
  86. package/dist/stream.d.ts +0 -1
  87. package/dist/stream.d.ts.map +1 -1
  88. package/dist/stream.js +0 -1
  89. package/dist/stream.js.map +1 -1
  90. package/dist/types.d.ts +91 -63
  91. package/dist/types.d.ts.map +1 -1
  92. package/dist/types.js.map +1 -1
  93. package/dist/utils/error-body.d.ts.map +1 -1
  94. package/dist/utils/error-body.js +15 -4
  95. package/dist/utils/error-body.js.map +1 -1
  96. package/dist/utils/estimate.d.ts +4 -0
  97. package/dist/utils/estimate.d.ts.map +1 -1
  98. package/dist/utils/estimate.js +3 -1
  99. package/dist/utils/estimate.js.map +1 -1
  100. package/dist/utils/headers.d.ts +3 -0
  101. package/dist/utils/headers.d.ts.map +1 -1
  102. package/dist/utils/headers.js +21 -0
  103. package/dist/utils/headers.js.map +1 -1
  104. package/dist/utils/json-parse.d.ts.map +1 -1
  105. package/dist/utils/json-parse.js +17 -19
  106. package/dist/utils/json-parse.js.map +1 -1
  107. package/dist/utils/oauth/anthropic.d.ts +4 -11
  108. package/dist/utils/oauth/anthropic.d.ts.map +1 -1
  109. package/dist/utils/oauth/anthropic.js +100 -117
  110. package/dist/utils/oauth/anthropic.js.map +1 -1
  111. package/dist/utils/oauth/device-code.d.ts.map +1 -1
  112. package/dist/utils/oauth/device-code.js +51 -38
  113. package/dist/utils/oauth/device-code.js.map +1 -1
  114. package/dist/utils/oauth/github-copilot.d.ts +6 -9
  115. package/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  116. package/dist/utils/oauth/github-copilot.js +47 -42
  117. package/dist/utils/oauth/github-copilot.js.map +1 -1
  118. package/dist/utils/oauth/index.d.ts +8 -17
  119. package/dist/utils/oauth/index.d.ts.map +1 -1
  120. package/dist/utils/oauth/index.js +27 -23
  121. package/dist/utils/oauth/index.js.map +1 -1
  122. package/dist/utils/oauth/oauth-page.d.ts.map +1 -1
  123. package/dist/utils/oauth/oauth-page.js.map +1 -1
  124. package/dist/utils/oauth/openai-codex.d.ts +7 -12
  125. package/dist/utils/oauth/openai-codex.d.ts.map +1 -1
  126. package/dist/utils/oauth/openai-codex.js +72 -89
  127. package/dist/utils/oauth/openai-codex.js.map +1 -1
  128. package/dist/utils/oauth/pkce.d.ts +5 -4
  129. package/dist/utils/oauth/pkce.d.ts.map +1 -1
  130. package/dist/utils/oauth/pkce.js.map +1 -1
  131. package/dist/utils/oauth/types.d.ts +7 -2
  132. package/dist/utils/oauth/types.d.ts.map +1 -1
  133. package/dist/utils/oauth/types.js.map +1 -1
  134. package/dist/utils/provider-retry.d.ts +16 -0
  135. package/dist/utils/provider-retry.d.ts.map +1 -0
  136. package/dist/utils/provider-retry.js +94 -0
  137. package/dist/utils/provider-retry.js.map +1 -0
  138. package/dist/utils/retry.d.ts +14 -0
  139. package/dist/utils/retry.d.ts.map +1 -1
  140. package/dist/utils/retry.js +84 -0
  141. package/dist/utils/retry.js.map +1 -1
  142. package/dist/utils/typebox-helpers.d.ts +5 -4
  143. package/dist/utils/typebox-helpers.d.ts.map +1 -1
  144. package/dist/utils/typebox-helpers.js.map +1 -1
  145. package/dist/utils/validation.d.ts.map +1 -1
  146. package/dist/utils/validation.js +102 -99
  147. package/dist/utils/validation.js.map +1 -1
  148. package/package.json +2 -2
@@ -3,9 +3,6 @@ import { shortHash } from "../utils/hash.js";
3
3
  import { parseStreamingJson } from "../utils/json-parse.js";
4
4
  import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
5
5
  import { transformMessages } from "./transform-messages.js";
6
- // =============================================================================
7
- // Utilities
8
- // =============================================================================
9
6
  function encodeTextSignatureV1(id, phase) {
10
7
  const payload = { v: 1, id };
11
8
  if (phase)
@@ -34,169 +31,162 @@ function parseTextSignature(signature) {
34
31
  // =============================================================================
35
32
  // Message conversion
36
33
  // =============================================================================
37
- export function convertResponsesMessages(model, context, allowedToolCallProviders, options) {
38
- const messages = [];
39
- const normalizeIdPart = (part) => {
40
- const sanitized = part.replace(/[^a-zA-Z0-9_-]/g, "_");
41
- const normalized = sanitized.length > 64 ? sanitized.slice(0, 64) : sanitized;
42
- return normalized.replace(/_+$/, "");
34
+ function normalizeResponsesIdPart(part) {
35
+ const sanitized = part.replace(/[^a-zA-Z0-9_-]/g, "_");
36
+ const normalized = sanitized.length > 64 ? sanitized.slice(0, 64) : sanitized;
37
+ return normalized.replace(/_+$/, "");
38
+ }
39
+ function buildForeignResponsesItemId(itemId) {
40
+ const normalized = `fc_${shortHash(itemId)}`;
41
+ return normalized.length > 64 ? normalized.slice(0, 64) : normalized;
42
+ }
43
+ function normalizeResponsesToolCallId(id, model, source, allowedToolCallProviders) {
44
+ if (!allowedToolCallProviders.has(model.provider))
45
+ return normalizeResponsesIdPart(id);
46
+ if (!id.includes("|"))
47
+ return normalizeResponsesIdPart(id);
48
+ const [callId, itemId] = id.split("|");
49
+ const normalizedCallId = normalizeResponsesIdPart(callId);
50
+ const isForeignToolCall = source.provider !== model.provider || source.api !== model.api;
51
+ let normalizedItemId = isForeignToolCall ? buildForeignResponsesItemId(itemId) : normalizeResponsesIdPart(itemId);
52
+ // OpenAI Responses API requires item IDs to start with "fc".
53
+ if (!normalizedItemId.startsWith("fc_"))
54
+ normalizedItemId = normalizeResponsesIdPart(`fc_${normalizedItemId}`);
55
+ return `${normalizedCallId}|${normalizedItemId}`;
56
+ }
57
+ function convertResponsesUserMessage(message) {
58
+ if (typeof message.content === "string") {
59
+ return [
60
+ {
61
+ role: "user",
62
+ content: [{ type: "input_text", text: sanitizeSurrogates(message.content) }],
63
+ },
64
+ ];
65
+ }
66
+ const content = message.content.map((item) => {
67
+ if (item.type === "text") {
68
+ return {
69
+ type: "input_text",
70
+ text: sanitizeSurrogates(item.text),
71
+ };
72
+ }
73
+ return {
74
+ type: "input_image",
75
+ detail: "auto",
76
+ image_url: `data:${item.mimeType};base64,${item.data}`,
77
+ };
78
+ });
79
+ return content.length === 0 ? [] : [{ role: "user", content }];
80
+ }
81
+ function resolveResponsesMessageId(textBlock, messageIndex, textBlockIndex) {
82
+ const parsedSignature = parseTextSignature(textBlock.textSignature);
83
+ const fallbackId = textBlockIndex === 0 ? `msg_pi_${messageIndex}` : `msg_pi_${messageIndex}_${textBlockIndex}`;
84
+ if (!parsedSignature?.id)
85
+ return { id: fallbackId };
86
+ return {
87
+ ...parsedSignature,
88
+ id: parsedSignature.id.length > 64 ? `msg_${shortHash(parsedSignature.id)}` : parsedSignature.id,
43
89
  };
44
- const buildForeignResponsesItemId = (itemId) => {
45
- const normalized = `fc_${shortHash(itemId)}`;
46
- return normalized.length > 64 ? normalized.slice(0, 64) : normalized;
90
+ }
91
+ function convertResponsesTextBlock(block, messageIndex, textBlockIndex) {
92
+ const signature = resolveResponsesMessageId(block, messageIndex, textBlockIndex);
93
+ return {
94
+ type: "message",
95
+ role: "assistant",
96
+ content: [{ type: "output_text", text: sanitizeSurrogates(block.text), annotations: [] }],
97
+ status: "completed",
98
+ id: signature.id,
99
+ phase: signature.phase,
47
100
  };
48
- const normalizeToolCallId = (id, _targetModel, source) => {
49
- if (!allowedToolCallProviders.has(model.provider))
50
- return normalizeIdPart(id);
51
- if (!id.includes("|"))
52
- return normalizeIdPart(id);
53
- const [callId, itemId] = id.split("|");
54
- const normalizedCallId = normalizeIdPart(callId);
55
- const isForeignToolCall = source.provider !== model.provider || source.api !== model.api;
56
- let normalizedItemId = isForeignToolCall ? buildForeignResponsesItemId(itemId) : normalizeIdPart(itemId);
57
- // OpenAI Responses API requires item id to start with "fc"
58
- if (!normalizedItemId.startsWith("fc_")) {
59
- normalizedItemId = normalizeIdPart(`fc_${normalizedItemId}`);
60
- }
61
- return `${normalizedCallId}|${normalizedItemId}`;
101
+ }
102
+ function convertResponsesToolCall(block, isDifferentModel) {
103
+ const [callId, rawItemId] = block.id.split("|");
104
+ const itemId = isDifferentModel && rawItemId?.startsWith("fc_") ? undefined : rawItemId;
105
+ return {
106
+ type: "function_call",
107
+ id: itemId,
108
+ call_id: callId,
109
+ name: block.name,
110
+ arguments: JSON.stringify(block.arguments),
62
111
  };
112
+ }
113
+ function convertResponsesAssistantMessage(message, model, messageIndex) {
114
+ const output = [];
115
+ const isDifferentModel = message.model !== model.id && message.provider === model.provider && message.api === model.api;
116
+ let textBlockIndex = 0;
117
+ for (const block of message.content) {
118
+ if (block.type === "thinking") {
119
+ if (block.thinkingSignature)
120
+ output.push(JSON.parse(block.thinkingSignature));
121
+ continue;
122
+ }
123
+ if (block.type === "text") {
124
+ output.push(convertResponsesTextBlock(block, messageIndex, textBlockIndex));
125
+ textBlockIndex++;
126
+ continue;
127
+ }
128
+ if (block.type === "toolCall")
129
+ output.push(convertResponsesToolCall(block, isDifferentModel));
130
+ }
131
+ return output;
132
+ }
133
+ function buildResponsesToolOutput(message, supportsImages) {
134
+ const textResult = message.content
135
+ .filter((content) => content.type === "text")
136
+ .map((content) => content.text)
137
+ .join("\n");
138
+ const hasImages = message.content.some((content) => content.type === "image");
139
+ const hasText = textResult.length > 0;
140
+ if (!hasImages || !supportsImages) {
141
+ return sanitizeSurrogates(hasText ? textResult : hasImages ? "(see attached image)" : "(no tool output)");
142
+ }
143
+ const contentParts = [];
144
+ if (hasText)
145
+ contentParts.push({ type: "input_text", text: sanitizeSurrogates(textResult) });
146
+ for (const block of message.content) {
147
+ if (block.type !== "image")
148
+ continue;
149
+ contentParts.push({
150
+ type: "input_image",
151
+ detail: "auto",
152
+ image_url: `data:${block.mimeType};base64,${block.data}`,
153
+ });
154
+ }
155
+ return contentParts;
156
+ }
157
+ function convertResponsesToolResult(message, model) {
158
+ const [callId] = message.toolCallId.split("|");
159
+ return [
160
+ {
161
+ type: "function_call_output",
162
+ call_id: callId,
163
+ output: buildResponsesToolOutput(message, model.input.includes("image")),
164
+ },
165
+ ];
166
+ }
167
+ export function convertResponsesMessages(model, context, allowedToolCallProviders, options) {
168
+ const messages = [];
169
+ const normalizeToolCallId = (id, _targetModel, source) => normalizeResponsesToolCallId(id, model, source, allowedToolCallProviders);
63
170
  const transformedMessages = transformMessages(context.messages, model, normalizeToolCallId);
64
- const includeSystemPrompt = options?.includeSystemPrompt ?? true;
65
- if (includeSystemPrompt && context.systemPrompt) {
66
- const role = model.reasoning ? "developer" : "system";
171
+ if ((options?.includeSystemPrompt ?? true) && context.systemPrompt) {
67
172
  messages.push({
68
- role,
173
+ role: model.reasoning ? "developer" : "system",
69
174
  content: sanitizeSurrogates(context.systemPrompt),
70
175
  });
71
176
  }
72
- let msgIndex = 0;
73
- for (const msg of transformedMessages) {
74
- if (msg.role === "user") {
75
- if (typeof msg.content === "string") {
76
- messages.push({
77
- role: "user",
78
- content: [{ type: "input_text", text: sanitizeSurrogates(msg.content) }],
79
- });
80
- }
81
- else {
82
- const content = msg.content.map((item) => {
83
- if (item.type === "text") {
84
- return {
85
- type: "input_text",
86
- text: sanitizeSurrogates(item.text),
87
- };
88
- }
89
- return {
90
- type: "input_image",
91
- detail: "auto",
92
- image_url: `data:${item.mimeType};base64,${item.data}`,
93
- };
94
- });
95
- if (content.length === 0)
96
- continue;
97
- messages.push({
98
- role: "user",
99
- content,
100
- });
101
- }
102
- }
103
- else if (msg.role === "assistant") {
104
- const output = [];
105
- const assistantMsg = msg;
106
- const isDifferentModel = assistantMsg.model !== model.id &&
107
- assistantMsg.provider === model.provider &&
108
- assistantMsg.api === model.api;
109
- let textBlockIndex = 0;
110
- for (const block of msg.content) {
111
- if (block.type === "thinking") {
112
- if (block.thinkingSignature) {
113
- const reasoningItem = JSON.parse(block.thinkingSignature);
114
- output.push(reasoningItem);
115
- }
116
- }
117
- else if (block.type === "text") {
118
- const textBlock = block;
119
- const parsedSignature = parseTextSignature(textBlock.textSignature);
120
- const fallbackMessageId = textBlockIndex === 0 ? `msg_pi_${msgIndex}` : `msg_pi_${msgIndex}_${textBlockIndex}`;
121
- textBlockIndex++;
122
- // OpenAI requires id to be max 64 characters
123
- let msgId = parsedSignature?.id;
124
- if (!msgId) {
125
- msgId = fallbackMessageId;
126
- }
127
- else if (msgId.length > 64) {
128
- msgId = `msg_${shortHash(msgId)}`;
129
- }
130
- output.push({
131
- type: "message",
132
- role: "assistant",
133
- content: [{ type: "output_text", text: sanitizeSurrogates(textBlock.text), annotations: [] }],
134
- status: "completed",
135
- id: msgId,
136
- phase: parsedSignature?.phase,
137
- });
138
- }
139
- else if (block.type === "toolCall") {
140
- const toolCall = block;
141
- const [callId, itemIdRaw] = toolCall.id.split("|");
142
- let itemId = itemIdRaw;
143
- // For different-model messages, set id to undefined to avoid pairing validation.
144
- // OpenAI tracks which fc_xxx IDs were paired with rs_xxx reasoning items.
145
- // By omitting the id, we avoid triggering that validation (like cross-provider does).
146
- if (isDifferentModel && itemId?.startsWith("fc_")) {
147
- itemId = undefined;
148
- }
149
- output.push({
150
- type: "function_call",
151
- id: itemId,
152
- call_id: callId,
153
- name: toolCall.name,
154
- arguments: JSON.stringify(toolCall.arguments),
155
- });
156
- }
157
- }
158
- if (output.length === 0)
159
- continue;
160
- messages.push(...output);
161
- }
162
- else if (msg.role === "toolResult") {
163
- const textResult = msg.content
164
- .filter((c) => c.type === "text")
165
- .map((c) => c.text)
166
- .join("\n");
167
- const hasImages = msg.content.some((c) => c.type === "image");
168
- const hasText = textResult.length > 0;
169
- const [callId] = msg.toolCallId.split("|");
170
- let output;
171
- if (hasImages && model.input.includes("image")) {
172
- const contentParts = [];
173
- if (hasText) {
174
- contentParts.push({
175
- type: "input_text",
176
- text: sanitizeSurrogates(textResult),
177
- });
178
- }
179
- for (const block of msg.content) {
180
- if (block.type === "image") {
181
- contentParts.push({
182
- type: "input_image",
183
- detail: "auto",
184
- image_url: `data:${block.mimeType};base64,${block.data}`,
185
- });
186
- }
187
- }
188
- output = contentParts;
189
- }
190
- else {
191
- output = sanitizeSurrogates(hasText ? textResult : "(see attached image)");
192
- }
193
- messages.push({
194
- type: "function_call_output",
195
- call_id: callId,
196
- output,
197
- });
198
- }
199
- msgIndex++;
177
+ let messageIndex = 0;
178
+ for (const message of transformedMessages) {
179
+ let converted;
180
+ if (message.role === "user")
181
+ converted = convertResponsesUserMessage(message);
182
+ else if (message.role === "assistant")
183
+ converted = convertResponsesAssistantMessage(message, model, messageIndex);
184
+ else
185
+ converted = convertResponsesToolResult(message, model);
186
+ if (converted.length === 0)
187
+ continue;
188
+ messages.push(...converted);
189
+ messageIndex++;
200
190
  }
201
191
  return messages;
202
192
  }
@@ -213,34 +203,50 @@ export function convertResponsesTools(tools, options) {
213
203
  strict,
214
204
  }));
215
205
  }
216
- export async function processResponsesStream(openaiStream, output, stream, model, options) {
217
- const outputSlots = new Map();
218
- const getSlot = (outputIndex, type) => {
219
- const slot = outputSlots.get(outputIndex);
220
- return slot?.type === type ? slot : undefined;
221
- };
222
- const createSlot = (outputIndex, item) => {
223
- if (item.type === "reasoning") {
206
+ function applyResponsesMessagePhaseStopReason(output, item) {
207
+ if (item.type === "message" && item.phase === "final_answer")
208
+ output.stopReason = "stop";
209
+ }
210
+ function getResponsesThinkingSlot(state, outputIndex) {
211
+ const slot = state.outputSlots.get(outputIndex);
212
+ return slot?.type === "thinking" ? slot : undefined;
213
+ }
214
+ function getResponsesTextSlot(state, outputIndex) {
215
+ const slot = state.outputSlots.get(outputIndex);
216
+ return slot?.type === "text" ? slot : undefined;
217
+ }
218
+ function getResponsesToolCallSlot(state, outputIndex) {
219
+ const slot = state.outputSlots.get(outputIndex);
220
+ return slot?.type === "toolCall" ? slot : undefined;
221
+ }
222
+ function createResponsesOutputSlot(state, outputIndex, item) {
223
+ switch (item.type) {
224
+ case "reasoning": {
224
225
  const block = { type: "thinking", thinking: "" };
225
- output.content.push(block);
226
+ state.output.content.push(block);
226
227
  const slot = {
227
228
  type: "thinking",
228
229
  block,
229
- contentIndex: output.content.length - 1,
230
+ contentIndex: state.output.content.length - 1,
230
231
  };
231
- outputSlots.set(outputIndex, slot);
232
- stream.push({ type: "thinking_start", contentIndex: slot.contentIndex, partial: output });
232
+ state.outputSlots.set(outputIndex, slot);
233
+ state.stream.push({ type: "thinking_start", contentIndex: slot.contentIndex, partial: state.output });
233
234
  return slot;
234
235
  }
235
- if (item.type === "message") {
236
+ case "message": {
237
+ applyResponsesMessagePhaseStopReason(state.output, item);
236
238
  const block = { type: "text", text: "" };
237
- output.content.push(block);
238
- const slot = { type: "text", block, contentIndex: output.content.length - 1 };
239
- outputSlots.set(outputIndex, slot);
240
- stream.push({ type: "text_start", contentIndex: slot.contentIndex, partial: output });
239
+ state.output.content.push(block);
240
+ const slot = {
241
+ type: "text",
242
+ block,
243
+ contentIndex: state.output.content.length - 1,
244
+ };
245
+ state.outputSlots.set(outputIndex, slot);
246
+ state.stream.push({ type: "text_start", contentIndex: slot.contentIndex, partial: state.output });
241
247
  return slot;
242
248
  }
243
- if (item.type === "function_call") {
249
+ case "function_call": {
244
250
  const block = {
245
251
  type: "toolCall",
246
252
  id: `${item.call_id}|${item.id}`,
@@ -248,203 +254,227 @@ export async function processResponsesStream(openaiStream, output, stream, model
248
254
  arguments: {},
249
255
  partialJson: item.arguments || "",
250
256
  };
251
- output.content.push(block);
257
+ state.output.content.push(block);
252
258
  const slot = {
253
259
  type: "toolCall",
254
260
  block,
255
- contentIndex: output.content.length - 1,
261
+ contentIndex: state.output.content.length - 1,
256
262
  };
257
- outputSlots.set(outputIndex, slot);
258
- stream.push({ type: "toolcall_start", contentIndex: slot.contentIndex, partial: output });
263
+ state.outputSlots.set(outputIndex, slot);
264
+ state.stream.push({ type: "toolcall_start", contentIndex: slot.contentIndex, partial: state.output });
259
265
  return slot;
260
266
  }
261
- return undefined;
262
- };
263
- const getOrCreateSlot = (outputIndex, item) => {
264
- return outputSlots.get(outputIndex) ?? createSlot(outputIndex, item);
265
- };
266
- for await (const event of openaiStream) {
267
- if (event.type === "response.created") {
268
- output.responseId = event.response.id;
269
- }
270
- else if (event.type === "response.output_item.added") {
271
- createSlot(event.output_index, event.item);
272
- }
273
- else if (event.type === "response.reasoning_summary_text.delta") {
274
- const slot = getSlot(event.output_index, "thinking");
275
- if (!slot)
276
- continue;
277
- slot.block.thinking += event.delta;
278
- stream.push({
279
- type: "thinking_delta",
280
- contentIndex: slot.contentIndex,
281
- delta: event.delta,
282
- partial: output,
283
- });
284
- }
285
- else if (event.type === "response.reasoning_summary_part.done") {
286
- const slot = getSlot(event.output_index, "thinking");
287
- if (!slot)
288
- continue;
289
- slot.block.thinking += "\n\n";
290
- stream.push({
291
- type: "thinking_delta",
292
- contentIndex: slot.contentIndex,
293
- delta: "\n\n",
294
- partial: output,
295
- });
296
- }
297
- else if (event.type === "response.reasoning_text.delta") {
298
- const slot = getSlot(event.output_index, "thinking");
299
- if (!slot)
300
- continue;
301
- slot.block.thinking += event.delta;
302
- stream.push({
303
- type: "thinking_delta",
304
- contentIndex: slot.contentIndex,
305
- delta: event.delta,
306
- partial: output,
307
- });
308
- }
309
- else if (event.type === "response.output_text.delta") {
310
- const slot = getSlot(event.output_index, "text");
311
- if (!slot)
312
- continue;
313
- slot.block.text += event.delta;
314
- stream.push({
315
- type: "text_delta",
267
+ default:
268
+ return undefined;
269
+ }
270
+ }
271
+ function getOrCreateResponsesOutputSlot(state, outputIndex, item) {
272
+ return state.outputSlots.get(outputIndex) ?? createResponsesOutputSlot(state, outputIndex, item);
273
+ }
274
+ function backfillResponsesReasoningSignatures(state, responseOutput) {
275
+ for (const item of responseOutput) {
276
+ if (item.type !== "reasoning" || !item.encrypted_content)
277
+ continue;
278
+ const block = state.reasoningBlocksById.get(item.id);
279
+ if (!block?.thinkingSignature)
280
+ continue;
281
+ const storedItem = JSON.parse(block.thinkingSignature);
282
+ if (storedItem.encrypted_content)
283
+ continue;
284
+ block.thinkingSignature = JSON.stringify({ ...storedItem, encrypted_content: item.encrypted_content });
285
+ }
286
+ }
287
+ function finalizeResponsesResponse(state, response) {
288
+ state.sawTerminalResponseEvent = true;
289
+ backfillResponsesReasoningSignatures(state, response.output ?? []);
290
+ if (response.id)
291
+ state.output.responseId = response.id;
292
+ if (response.usage) {
293
+ const inputDetails = response.usage.input_tokens_details;
294
+ const cachedTokens = inputDetails?.cached_tokens || 0;
295
+ const cacheWriteTokens = inputDetails?.cache_write_tokens || 0;
296
+ state.output.usage = {
297
+ input: Math.max(0, (response.usage.input_tokens || 0) - cachedTokens - cacheWriteTokens),
298
+ output: response.usage.output_tokens || 0,
299
+ cacheRead: cachedTokens,
300
+ cacheWrite: cacheWriteTokens,
301
+ reasoning: response.usage.output_tokens_details?.reasoning_tokens || 0,
302
+ totalTokens: response.usage.total_tokens || 0,
303
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
304
+ };
305
+ }
306
+ calculateCost(state.model, state.output.usage);
307
+ if (state.options?.applyServiceTierPricing) {
308
+ const serviceTier = state.options.resolveServiceTier
309
+ ? state.options.resolveServiceTier(response.service_tier, state.options.serviceTier)
310
+ : (response.service_tier ?? state.options.serviceTier);
311
+ state.options.applyServiceTierPricing(state.output.usage, serviceTier);
312
+ }
313
+ state.output.rawStopReason = response.status;
314
+ state.output.stopReason = mapStopReason(response.status);
315
+ if (state.output.content.some((block) => block.type === "toolCall") && state.output.stopReason === "stop") {
316
+ state.output.stopReason = "toolUse";
317
+ }
318
+ }
319
+ function appendResponsesThinkingDelta(state, outputIndex, delta) {
320
+ const slot = getResponsesThinkingSlot(state, outputIndex);
321
+ if (!slot)
322
+ return;
323
+ slot.block.thinking += delta;
324
+ state.stream.push({
325
+ type: "thinking_delta",
326
+ contentIndex: slot.contentIndex,
327
+ delta,
328
+ partial: state.output,
329
+ });
330
+ }
331
+ function appendResponsesTextDelta(state, outputIndex, delta) {
332
+ const slot = getResponsesTextSlot(state, outputIndex);
333
+ if (!slot)
334
+ return;
335
+ slot.block.text += delta;
336
+ state.stream.push({ type: "text_delta", contentIndex: slot.contentIndex, delta, partial: state.output });
337
+ }
338
+ function appendResponsesToolCallDelta(state, outputIndex, delta) {
339
+ const slot = getResponsesToolCallSlot(state, outputIndex);
340
+ if (!slot)
341
+ return;
342
+ slot.block.partialJson += delta;
343
+ slot.block.arguments = parseStreamingJson(slot.block.partialJson);
344
+ state.stream.push({ type: "toolcall_delta", contentIndex: slot.contentIndex, delta, partial: state.output });
345
+ }
346
+ function completeResponsesToolCallArguments(state, outputIndex, argumentsJson) {
347
+ const slot = getResponsesToolCallSlot(state, outputIndex);
348
+ if (!slot)
349
+ return;
350
+ const previousPartialJson = slot.block.partialJson;
351
+ slot.block.partialJson = argumentsJson;
352
+ slot.block.arguments = parseStreamingJson(slot.block.partialJson);
353
+ if (!argumentsJson.startsWith(previousPartialJson))
354
+ return;
355
+ const delta = argumentsJson.slice(previousPartialJson.length);
356
+ if (delta.length > 0) {
357
+ state.stream.push({ type: "toolcall_delta", contentIndex: slot.contentIndex, delta, partial: state.output });
358
+ }
359
+ }
360
+ function finalizeResponsesOutputItem(state, outputIndex, item) {
361
+ applyResponsesMessagePhaseStopReason(state.output, item);
362
+ const slot = getOrCreateResponsesOutputSlot(state, outputIndex, item);
363
+ switch (item.type) {
364
+ case "reasoning":
365
+ if (slot?.type !== "thinking")
366
+ return;
367
+ slot.block.thinking =
368
+ item.summary?.map((summary) => summary.text).join("\n\n") ||
369
+ item.content?.map((content) => content.text).join("\n\n") ||
370
+ slot.block.thinking;
371
+ slot.block.thinkingSignature = JSON.stringify(item);
372
+ state.reasoningBlocksById.set(item.id, slot.block);
373
+ state.stream.push({
374
+ type: "thinking_end",
316
375
  contentIndex: slot.contentIndex,
317
- delta: event.delta,
318
- partial: output,
376
+ content: slot.block.thinking,
377
+ partial: state.output,
319
378
  });
320
- }
321
- else if (event.type === "response.refusal.delta") {
322
- const slot = getSlot(event.output_index, "text");
323
- if (!slot)
324
- continue;
325
- slot.block.text += event.delta;
326
- stream.push({
327
- type: "text_delta",
379
+ state.outputSlots.delete(outputIndex);
380
+ return;
381
+ case "message":
382
+ if (slot?.type !== "text")
383
+ return;
384
+ slot.block.text =
385
+ item.content
386
+ ?.map((content) => (content.type === "output_text" ? content.text : content.refusal))
387
+ .join("") || "";
388
+ slot.block.textSignature = encodeTextSignatureV1(item.id, item.phase ?? undefined);
389
+ state.stream.push({
390
+ type: "text_end",
328
391
  contentIndex: slot.contentIndex,
329
- delta: event.delta,
330
- partial: output,
392
+ content: slot.block.text,
393
+ partial: state.output,
331
394
  });
332
- }
333
- else if (event.type === "response.function_call_arguments.delta") {
334
- const slot = getSlot(event.output_index, "toolCall");
335
- if (!slot)
336
- continue;
337
- slot.block.partialJson += event.delta;
338
- slot.block.arguments = parseStreamingJson(slot.block.partialJson);
339
- stream.push({
340
- type: "toolcall_delta",
395
+ state.outputSlots.delete(outputIndex);
396
+ return;
397
+ case "function_call":
398
+ if (slot?.type !== "toolCall")
399
+ return;
400
+ slot.block.arguments = parseStreamingJson(item.arguments || slot.block.partialJson || "{}");
401
+ delete slot.block.partialJson;
402
+ state.stream.push({
403
+ type: "toolcall_end",
341
404
  contentIndex: slot.contentIndex,
342
- delta: event.delta,
343
- partial: output,
405
+ toolCall: slot.block,
406
+ partial: state.output,
344
407
  });
345
- }
346
- else if (event.type === "response.function_call_arguments.done") {
347
- const slot = getSlot(event.output_index, "toolCall");
348
- if (!slot)
349
- continue;
350
- const previousPartialJson = slot.block.partialJson;
351
- slot.block.partialJson = event.arguments;
352
- slot.block.arguments = parseStreamingJson(slot.block.partialJson);
353
- if (event.arguments.startsWith(previousPartialJson)) {
354
- const delta = event.arguments.slice(previousPartialJson.length);
355
- if (delta.length > 0) {
356
- stream.push({
357
- type: "toolcall_delta",
358
- contentIndex: slot.contentIndex,
359
- delta,
360
- partial: output,
361
- });
362
- }
363
- }
364
- }
365
- else if (event.type === "response.output_item.done") {
366
- const item = event.item;
367
- const slot = getOrCreateSlot(event.output_index, item);
368
- if (item.type === "reasoning" && slot?.type === "thinking") {
369
- const summaryText = item.summary?.map((summary) => summary.text).join("\n\n") || "";
370
- const contentText = item.content?.map((content) => content.text).join("\n\n") || "";
371
- slot.block.thinking = summaryText || contentText || slot.block.thinking;
372
- slot.block.thinkingSignature = JSON.stringify(item);
373
- stream.push({
374
- type: "thinking_end",
375
- contentIndex: slot.contentIndex,
376
- content: slot.block.thinking,
377
- partial: output,
378
- });
379
- outputSlots.delete(event.output_index);
380
- }
381
- else if (item.type === "message" && slot?.type === "text") {
382
- slot.block.text =
383
- item.content
384
- ?.map((content) => (content.type === "output_text" ? content.text : content.refusal))
385
- .join("") || "";
386
- slot.block.textSignature = encodeTextSignatureV1(item.id, item.phase ?? undefined);
387
- stream.push({
388
- type: "text_end",
389
- contentIndex: slot.contentIndex,
390
- content: slot.block.text,
391
- partial: output,
392
- });
393
- outputSlots.delete(event.output_index);
394
- }
395
- else if (item.type === "function_call" && slot?.type === "toolCall") {
396
- slot.block.arguments = parseStreamingJson(item.arguments || slot.block.partialJson || "{}");
397
- delete slot.block.partialJson;
398
- stream.push({
399
- type: "toolcall_end",
400
- contentIndex: slot.contentIndex,
401
- toolCall: slot.block,
402
- partial: output,
403
- });
404
- outputSlots.delete(event.output_index);
405
- }
406
- }
407
- else if (event.type === "response.completed") {
408
- const response = event.response;
409
- if (response?.id) {
410
- output.responseId = response.id;
411
- }
412
- if (response?.usage) {
413
- const cachedTokens = response.usage.input_tokens_details?.cached_tokens || 0;
414
- output.usage = {
415
- input: (response.usage.input_tokens || 0) - cachedTokens,
416
- output: response.usage.output_tokens || 0,
417
- cacheRead: cachedTokens,
418
- cacheWrite: 0,
419
- totalTokens: response.usage.total_tokens || 0,
420
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
421
- };
422
- }
423
- calculateCost(model, output.usage);
424
- if (options?.applyServiceTierPricing) {
425
- const serviceTier = options.resolveServiceTier
426
- ? options.resolveServiceTier(response?.service_tier, options.serviceTier)
427
- : (response?.service_tier ?? options.serviceTier);
428
- options.applyServiceTierPricing(output.usage, serviceTier);
429
- }
430
- output.stopReason = mapStopReason(response?.status);
431
- if (output.content.some((block) => block.type === "toolCall") && output.stopReason === "stop") {
432
- output.stopReason = "toolUse";
433
- }
434
- }
435
- else if (event.type === "error") {
408
+ state.outputSlots.delete(outputIndex);
409
+ return;
410
+ default:
411
+ return;
412
+ }
413
+ }
414
+ function failResponsesStream(state, response) {
415
+ state.sawTerminalResponseEvent = true;
416
+ state.output.rawStopReason = response?.status;
417
+ const error = response?.error;
418
+ const details = response?.incomplete_details;
419
+ const message = error
420
+ ? `${error.code || "unknown"}: ${error.message || "no message"}`
421
+ : details?.reason
422
+ ? `incomplete: ${details.reason}`
423
+ : "Unknown error (no error details in response)";
424
+ throw new Error(message);
425
+ }
426
+ function processResponsesStreamEvent(state, event) {
427
+ switch (event.type) {
428
+ case "response.created":
429
+ state.output.responseId = event.response.id;
430
+ break;
431
+ case "response.output_item.added":
432
+ createResponsesOutputSlot(state, event.output_index, event.item);
433
+ break;
434
+ case "response.reasoning_summary_text.delta":
435
+ case "response.reasoning_text.delta":
436
+ appendResponsesThinkingDelta(state, event.output_index, event.delta);
437
+ break;
438
+ case "response.reasoning_summary_part.done":
439
+ appendResponsesThinkingDelta(state, event.output_index, "\n\n");
440
+ break;
441
+ case "response.output_text.delta":
442
+ case "response.refusal.delta":
443
+ appendResponsesTextDelta(state, event.output_index, event.delta);
444
+ break;
445
+ case "response.function_call_arguments.delta":
446
+ appendResponsesToolCallDelta(state, event.output_index, event.delta);
447
+ break;
448
+ case "response.function_call_arguments.done":
449
+ completeResponsesToolCallArguments(state, event.output_index, event.arguments);
450
+ break;
451
+ case "response.output_item.done":
452
+ finalizeResponsesOutputItem(state, event.output_index, event.item);
453
+ break;
454
+ case "response.completed":
455
+ case "response.incomplete":
456
+ finalizeResponsesResponse(state, event.response);
457
+ break;
458
+ case "error":
436
459
  throw new Error(`Error Code ${event.code}: ${event.message}` || "Unknown error");
437
- }
438
- else if (event.type === "response.failed") {
439
- const error = event.response?.error;
440
- const details = event.response?.incomplete_details;
441
- const message = error
442
- ? `${error.code || "unknown"}: ${error.message || "no message"}`
443
- : details?.reason
444
- ? `incomplete: ${details.reason}`
445
- : "Unknown error (no error details in response)";
446
- throw new Error(message);
447
- }
460
+ case "response.failed":
461
+ failResponsesStream(state, event.response);
462
+ }
463
+ }
464
+ export async function processResponsesStream(openaiStream, output, stream, model, options) {
465
+ const state = {
466
+ output,
467
+ stream,
468
+ model,
469
+ options,
470
+ sawTerminalResponseEvent: false,
471
+ outputSlots: new Map(),
472
+ reasoningBlocksById: new Map(),
473
+ };
474
+ for await (const event of openaiStream)
475
+ processResponsesStreamEvent(state, event);
476
+ if (!state.sawTerminalResponseEvent) {
477
+ throw new Error("OpenAI Responses stream ended before a terminal response event");
448
478
  }
449
479
  }
450
480
  function mapStopReason(status) {