@oai404iao/pi-codex-minimal-tools 1.4.0 → 2.0.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 (99) hide show
  1. package/README.md +44 -17
  2. package/THIRD_PARTY_NOTICES.md +20 -0
  3. package/config.schema.json +1 -2
  4. package/index.ts +2 -0
  5. package/package.json +14 -13
  6. package/provenance/openai-codex-ddea03ad-astra.json +54 -0
  7. package/src/activation.ts +1 -56
  8. package/src/adapter/compaction/checkpoint.ts +1 -0
  9. package/src/adapter/compaction/collect.ts +1 -0
  10. package/src/adapter/compaction/http.ts +1 -0
  11. package/src/adapter/compaction/request.ts +1 -0
  12. package/src/adapter/compaction/transport.ts +1 -0
  13. package/src/adapter/compaction/websocket.ts +1 -0
  14. package/src/background-image-generation.ts +1 -574
  15. package/src/capabilities.ts +1 -146
  16. package/src/codex-http.ts +1 -133
  17. package/src/codex-identity-extension.ts +1 -339
  18. package/src/codex-request-profile.ts +1 -45
  19. package/src/codex-reserved-tools.ts +1 -323
  20. package/src/codex-wire-identity.ts +1 -596
  21. package/src/extension/prewarm-snapshot.ts +1 -0
  22. package/src/extension/provider-presentation.ts +1 -0
  23. package/src/extension/provider-runtime.ts +1 -0
  24. package/src/extension/register.ts +36 -0
  25. package/src/extension/startup-prewarm.ts +1 -0
  26. package/src/fast-mode.ts +1 -124
  27. package/src/glyphs.ts +1 -70
  28. package/src/index.ts +8 -331
  29. package/src/model-catalog/catalog.ts +1 -636
  30. package/src/model-catalog/default-models.json +8 -0
  31. package/src/model-catalog/runtime.ts +1 -113
  32. package/src/model-catalog/types.ts +1 -95
  33. package/src/native-compaction.ts +1 -393
  34. package/src/patch/apply.ts +1 -338
  35. package/src/patch/parser.ts +1 -224
  36. package/src/patch/render.ts +1 -201
  37. package/src/provider-headers.ts +1 -54
  38. package/src/provider-native-tools.ts +1 -71
  39. package/src/provider-shim.ts +20 -4511
  40. package/src/providers/codex-apply-patch-tool.ts +1 -23
  41. package/src/providers/openai-codex/cache-key.ts +1 -0
  42. package/src/providers/openai-codex/captured-stream.ts +1 -0
  43. package/src/providers/openai-codex/constants.ts +1 -0
  44. package/src/providers/openai-codex/continuation.ts +1 -0
  45. package/src/providers/openai-codex/errors.ts +1 -0
  46. package/src/providers/openai-codex/events.ts +1 -0
  47. package/src/providers/openai-codex/headers.ts +1 -0
  48. package/src/providers/openai-codex/lite.ts +1 -0
  49. package/src/providers/openai-codex/message.ts +1 -0
  50. package/src/providers/openai-codex/prewarm.ts +1 -0
  51. package/src/providers/openai-codex/proxy.ts +1 -0
  52. package/src/providers/openai-codex/reasoning.ts +1 -0
  53. package/src/providers/openai-codex/request-body.ts +1 -0
  54. package/src/providers/openai-codex/request-context.ts +1 -0
  55. package/src/providers/openai-codex/request-metadata.ts +1 -0
  56. package/src/providers/openai-codex/retry.ts +1 -0
  57. package/src/providers/openai-codex/runtime.ts +1 -0
  58. package/src/providers/openai-codex/sse.ts +1 -0
  59. package/src/providers/openai-codex/stream-effects.ts +1 -0
  60. package/src/providers/openai-codex/stream.ts +1 -0
  61. package/src/providers/openai-codex/types.ts +1 -0
  62. package/src/providers/openai-codex/urls.ts +1 -0
  63. package/src/providers/openai-codex/usage.ts +1 -0
  64. package/src/providers/openai-codex/websocket-connection.ts +1 -0
  65. package/src/providers/openai-codex/websocket-events.ts +1 -0
  66. package/src/providers/openai-codex/websocket-session.ts +1 -0
  67. package/src/providers/openai-codex/websocket-socket.ts +1 -0
  68. package/src/providers/openai-codex/websocket-stream.ts +1 -0
  69. package/src/providers/openai-responses-shared.ts +7 -1463
  70. package/src/providers/responses/citations.ts +1 -0
  71. package/src/providers/responses/history.ts +1 -0
  72. package/src/providers/responses/items.ts +1 -0
  73. package/src/providers/responses/markdown.ts +1 -0
  74. package/src/providers/responses/messages.ts +1 -0
  75. package/src/providers/responses/signatures.ts +1 -0
  76. package/src/providers/responses/stream-state.ts +1 -0
  77. package/src/providers/responses/stream.ts +1 -0
  78. package/src/providers/responses/text-renderer.ts +1 -0
  79. package/src/providers/responses/text.ts +1 -0
  80. package/src/providers/responses/tool-identity.ts +1 -0
  81. package/src/providers/responses/tools.ts +1 -0
  82. package/src/providers/responses/types.ts +1 -0
  83. package/src/providers/responses/usage.ts +1 -0
  84. package/src/settings.ts +1 -247
  85. package/src/subagent-inline.ts +1 -8
  86. package/src/tools/apply-patch.ts +1 -84
  87. package/src/tools/image-generation/capture.ts +1 -0
  88. package/src/tools/image-generation/display.ts +1 -0
  89. package/src/tools/image-generation/preview.ts +1 -0
  90. package/src/tools/image-generation/storage.ts +1 -0
  91. package/src/tools/image-generation/types.ts +1 -0
  92. package/src/tools/image-generation.ts +1 -274
  93. package/src/tools/view-image.ts +1 -98
  94. package/src/tools/web-search/activity.ts +1 -0
  95. package/src/tools/web-search/capture.ts +1 -0
  96. package/src/tools/web-search/render.ts +1 -0
  97. package/src/tools/web-search.ts +1 -554
  98. package/src/utils/images.ts +1 -73
  99. package/src/utils/theme.ts +1 -0
@@ -1,1463 +1,7 @@
1
- import { calculateCost, type Api, type AssistantMessage, type Context, type Model, type Tool, type Usage } from "@earendil-works/pi-ai";
2
- import type { ResponseCreateParamsStreaming, ResponseInput, ResponseStreamEvent, Tool as OpenAITool } from "openai/resources/responses/responses.js";
3
- import type { AssistantMessageEventStream } from "@earendil-works/pi-ai";
4
-
5
- type MessageRole = Context["messages"][number]["role"];
6
- type Message = Context["messages"][number];
7
-
8
- interface ImageGenerationCallItem {
9
- type: "image_generation_call";
10
- id: string;
11
- status: string;
12
- result: string | null;
13
- revised_prompt?: string;
14
- }
15
-
16
- interface ImageGenerationCallBlock {
17
- type: "image_generation_call";
18
- item: ImageGenerationCallItem;
19
- }
20
-
21
- export interface ReplayableWebSearchCallItem {
22
- type: "web_search_call";
23
- id: string;
24
- status: "completed";
25
- action: Record<string, unknown>;
26
- results?: Array<Record<string, unknown>>;
27
- }
28
-
29
- export interface CitationSource {
30
- url: string;
31
- title?: string;
32
- }
33
-
34
- export interface WebSearchCitationSource extends CitationSource {
35
- refId: string;
36
- }
37
-
38
- type ReplayableResponseMessageContent =
39
- | { type: "output_text"; text: string; annotations: Array<Record<string, unknown>> }
40
- | { type: "refusal"; refusal: string };
41
-
42
- interface ReplayableResponseMessageItem {
43
- type: "message";
44
- id: string;
45
- role: "assistant";
46
- status: "completed";
47
- content: ReplayableResponseMessageContent[];
48
- phase?: TextSignaturePhase;
49
- }
50
-
51
- type InternalAssistantContent = Extract<Message, { role: "assistant" }>["content"][number] | ImageGenerationCallBlock;
52
-
53
- export interface OpenAIResponsesStreamOptions {
54
- serviceTier?: ResponseCreateParamsStreaming["service_tier"];
55
- resolveServiceTier?: (
56
- responseServiceTier: ResponseCreateParamsStreaming["service_tier"] | undefined,
57
- requestServiceTier: ResponseCreateParamsStreaming["service_tier"] | undefined,
58
- ) => ResponseCreateParamsStreaming["service_tier"] | undefined;
59
- applyServiceTierPricing?: (usage: Usage, serviceTier: ResponseCreateParamsStreaming["service_tier"] | undefined) => void;
60
- webSearchCitationSources?: ReadonlyArray<WebSearchCitationSource>;
61
- historicalCitationSources?: ReadonlyArray<CitationSource>;
62
- }
63
-
64
- type TextSignaturePhase = "commentary" | "final_answer";
65
- export const WEB_SEARCH_ACTIVITY_TEXT_SIGNATURE_PREFIX = "pi:web-search-activity:";
66
- const LEAKED_CITATION_PROTOCOL = /cite[^]+|【\d+†source】/i;
67
-
68
- interface ConvertResponsesMessagesOptions {
69
- includeSystemPrompt?: boolean;
70
- }
71
-
72
- interface ConvertResponsesToolsOptions {
73
- strict?: boolean | null;
74
- }
75
-
76
- function shortHash(str: string): string {
77
- let h1 = 0xdeadbeef;
78
- let h2 = 0x41c6ce57;
79
- for (let i = 0; i < str.length; i++) {
80
- const ch = str.charCodeAt(i);
81
- h1 = Math.imul(h1 ^ ch, 2654435761);
82
- h2 = Math.imul(h2 ^ ch, 1597334677);
83
- }
84
- h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
85
- h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
86
- return (h2 >>> 0).toString(36) + (h1 >>> 0).toString(36);
87
- }
88
-
89
- function parseStreamingJson(partialJson: string): Record<string, unknown> {
90
- if (!partialJson || partialJson.trim() === "") return {};
91
- try {
92
- return JSON.parse(partialJson) as Record<string, unknown>;
93
- } catch {
94
- return {};
95
- }
96
- }
97
-
98
- function sanitizeSurrogates(text: string): string {
99
- return text.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "");
100
- }
101
-
102
- function localToolName(namespace: unknown, name: string): string {
103
- if (typeof namespace !== "string" || namespace === "" || namespace === "functions") return name;
104
- if (namespace === "web" && name === "run") return "web_search";
105
- if (namespace === "image_gen" && name === "imagegen") return "image_generation";
106
- return name;
107
- }
108
-
109
- const TOOL_NAMESPACE_SIGNATURE_PREFIX = "pi:codex-tool-namespace:";
110
-
111
- function encodeToolNamespaceSignature(namespace: unknown, name: string): string | undefined {
112
- if (typeof namespace !== "string" || !namespace) return undefined;
113
- return `${TOOL_NAMESPACE_SIGNATURE_PREFIX}${JSON.stringify({ namespace, name })}`;
114
- }
115
-
116
- function wireToolIdentity(
117
- name: string,
118
- thoughtSignature: string | undefined,
119
- ): { name: string; namespace?: string } {
120
- if (!thoughtSignature?.startsWith(TOOL_NAMESPACE_SIGNATURE_PREFIX)) return { name };
121
- try {
122
- const value = JSON.parse(thoughtSignature.slice(TOOL_NAMESPACE_SIGNATURE_PREFIX.length)) as {
123
- namespace?: unknown;
124
- name?: unknown;
125
- };
126
- if (typeof value.namespace === "string" && value.namespace && typeof value.name === "string" && value.name) {
127
- return { namespace: value.namespace, name: value.name };
128
- }
129
- } catch {
130
- // Ignore malformed local metadata and replay the Pi-visible tool name.
131
- }
132
- return { name };
133
- }
134
-
135
- function cloneJsonRecord(value: unknown): Record<string, unknown> | undefined {
136
- if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
137
- try {
138
- const cloned = JSON.parse(JSON.stringify(value)) as unknown;
139
- return cloned && typeof cloned === "object" && !Array.isArray(cloned)
140
- ? cloned as Record<string, unknown>
141
- : undefined;
142
- } catch {
143
- return undefined;
144
- }
145
- }
146
-
147
- function sanitizeWebSearchCallItem(item: unknown): ReplayableWebSearchCallItem | undefined {
148
- if (!item || typeof item !== "object") return undefined;
149
- const candidate = item as Record<string, unknown>;
150
- if (candidate.type !== "web_search_call") return undefined;
151
- if (typeof candidate.id !== "string" || candidate.id === "") return undefined;
152
- if (candidate.status !== "completed") return undefined;
153
- const action = cloneJsonRecord(candidate.action);
154
- if (!action) return undefined;
155
- const results = Array.isArray(candidate.results)
156
- ? candidate.results.map(cloneJsonRecord).filter((result): result is Record<string, unknown> => !!result)
157
- : undefined;
158
-
159
- return {
160
- type: "web_search_call",
161
- id: candidate.id,
162
- status: "completed",
163
- action,
164
- ...(results ? { results } : {}),
165
- };
166
- }
167
-
168
- function sanitizeResponseMessageItem(item: unknown): ReplayableResponseMessageItem | undefined {
169
- if (!item || typeof item !== "object") return undefined;
170
- const candidate = item as Record<string, unknown>;
171
- if (candidate.type !== "message") return undefined;
172
- if (typeof candidate.id !== "string" || candidate.id === "") return undefined;
173
- const rawContent = Array.isArray(candidate.content) ? candidate.content : [];
174
- const content: ReplayableResponseMessageContent[] = [];
175
- for (const rawPart of rawContent) {
176
- if (!rawPart || typeof rawPart !== "object") continue;
177
- const part = rawPart as Record<string, unknown>;
178
- if (part.type === "output_text" && typeof part.text === "string") {
179
- const annotations = Array.isArray(part.annotations)
180
- ? part.annotations.map(cloneJsonRecord).filter((annotation): annotation is Record<string, unknown> => !!annotation)
181
- : [];
182
- content.push({ type: "output_text", text: part.text, annotations });
183
- } else if (part.type === "refusal" && typeof part.refusal === "string") {
184
- content.push({ type: "refusal", refusal: part.refusal });
185
- }
186
- }
187
- const phase = candidate.phase === "commentary" || candidate.phase === "final_answer"
188
- ? candidate.phase
189
- : undefined;
190
- return {
191
- type: "message",
192
- id: candidate.id,
193
- role: "assistant",
194
- status: "completed",
195
- content,
196
- ...(phase ? { phase } : {}),
197
- };
198
- }
199
-
200
- function isImageGenerationCallBlock(block: InternalAssistantContent): block is ImageGenerationCallBlock {
201
- return block.type === "image_generation_call" && block.item?.type === "image_generation_call";
202
- }
203
-
204
- function sanitizeImageGenerationCallItem(item: unknown): ImageGenerationCallItem | undefined {
205
- if (!item || typeof item !== "object") return undefined;
206
- const candidate = item as Record<string, unknown>;
207
- if (candidate.type !== "image_generation_call") return undefined;
208
- if (typeof candidate.id !== "string" || candidate.id === "") return undefined;
209
- if (typeof candidate.status !== "string" || candidate.status === "") return undefined;
210
- if (!(typeof candidate.result === "string" || candidate.result === null)) return undefined;
211
-
212
- return {
213
- type: "image_generation_call",
214
- id: candidate.id,
215
- status: candidate.status,
216
- result: candidate.result,
217
- ...(typeof candidate.revised_prompt === "string" ? { revised_prompt: candidate.revised_prompt } : {}),
218
- };
219
- }
220
-
221
- const NON_VISION_USER_IMAGE_PLACEHOLDER = "(image omitted: model does not support images)";
222
- const NON_VISION_TOOL_IMAGE_PLACEHOLDER = "(tool image omitted: model does not support images)";
223
-
224
- function replaceImagesWithPlaceholder(
225
- content: Extract<Message, { role: "user" }> extends { content: infer T } ? Exclude<T, string> : never,
226
- placeholder: string,
227
- ) {
228
- const result: Array<{ type: "text"; text: string } | { type: "image"; data: string; mimeType: string }> = [];
229
- let previousWasPlaceholder = false;
230
- for (const block of content) {
231
- if (block.type === "image") {
232
- if (!previousWasPlaceholder) {
233
- result.push({ type: "text", text: placeholder });
234
- }
235
- previousWasPlaceholder = true;
236
- continue;
237
- }
238
- result.push(block);
239
- previousWasPlaceholder = block.text === placeholder;
240
- }
241
- return result;
242
- }
243
-
244
- function downgradeUnsupportedImages(messages: Context["messages"], model: Model<Api>): Context["messages"] {
245
- if (model.input.includes("image")) return messages;
246
- return messages.map((msg) => {
247
- if (msg.role === "user" && Array.isArray(msg.content)) {
248
- return { ...msg, content: replaceImagesWithPlaceholder(msg.content, NON_VISION_USER_IMAGE_PLACEHOLDER) };
249
- }
250
- if (msg.role === "toolResult") {
251
- return { ...msg, content: replaceImagesWithPlaceholder(msg.content, NON_VISION_TOOL_IMAGE_PLACEHOLDER) };
252
- }
253
- return msg;
254
- });
255
- }
256
-
257
- function transformMessages(
258
- messages: Context["messages"],
259
- model: Model<Api>,
260
- normalizeToolCallId?: (id: string, targetModel: Model<Api>, source: Extract<Message, { role: "assistant" }>) => string,
261
- ): Context["messages"] {
262
- const toolCallIdMap = new Map<string, string>();
263
- const imageAwareMessages = downgradeUnsupportedImages(messages, model);
264
- const transformed = imageAwareMessages.map((msg) => {
265
- if (msg.role === "user") return msg;
266
- if (msg.role === "toolResult") {
267
- const normalizedId = toolCallIdMap.get(msg.toolCallId);
268
- return normalizedId && normalizedId !== msg.toolCallId ? { ...msg, toolCallId: normalizedId } : msg;
269
- }
270
- if (msg.role === "assistant") {
271
- const assistantMsg = msg;
272
- const isSameModel =
273
- assistantMsg.provider === model.provider && assistantMsg.api === model.api && assistantMsg.model === model.id;
274
- const transformedContent = (assistantMsg.content as InternalAssistantContent[]).flatMap((block) => {
275
- if (isImageGenerationCallBlock(block)) return block;
276
- if (block.type === "thinking") {
277
- if (block.redacted) return isSameModel ? block : [];
278
- if (isSameModel && block.thinkingSignature) return block;
279
- if (!block.thinking || block.thinking.trim() === "") return [];
280
- return isSameModel ? block : { type: "text" as const, text: block.thinking };
281
- }
282
- if (block.type === "text") {
283
- if (isWebSearchActivityTextSignature(block.textSignature)) return block;
284
- return isSameModel ? block : { type: "text" as const, text: block.text };
285
- }
286
- if (block.type === "toolCall") {
287
- let normalizedToolCall = block;
288
- if (!isSameModel && block.thoughtSignature) {
289
- normalizedToolCall = { ...block };
290
- delete normalizedToolCall.thoughtSignature;
291
- }
292
- if (!isSameModel && normalizeToolCallId) {
293
- const normalizedId = normalizeToolCallId(block.id, model, assistantMsg);
294
- if (normalizedId !== block.id) {
295
- toolCallIdMap.set(block.id, normalizedId);
296
- normalizedToolCall = { ...normalizedToolCall, id: normalizedId };
297
- }
298
- }
299
- return normalizedToolCall;
300
- }
301
- return block;
302
- });
303
- return { ...assistantMsg, content: transformedContent as Extract<Message, { role: "assistant" }>["content"] };
304
- }
305
- return msg;
306
- });
307
-
308
- const result: Context["messages"] = [];
309
- let pendingToolCalls: Array<Extract<Extract<Message, { role: "assistant" }>["content"][number], { type: "toolCall" }>> = [];
310
- let existingToolResultIds = new Set<string>();
311
-
312
- const insertSyntheticToolResults = () => {
313
- if (pendingToolCalls.length === 0) return;
314
- for (const toolCall of pendingToolCalls) {
315
- if (!existingToolResultIds.has(toolCall.id)) {
316
- result.push({
317
- role: "toolResult",
318
- toolCallId: toolCall.id,
319
- toolName: toolCall.name,
320
- content: [{ type: "text", text: "No result provided" }],
321
- isError: true,
322
- timestamp: Date.now(),
323
- });
324
- }
325
- }
326
- pendingToolCalls = [];
327
- existingToolResultIds = new Set();
328
- };
329
-
330
- for (const msg of transformed) {
331
- if (msg.role === "assistant") {
332
- insertSyntheticToolResults();
333
- if (msg.stopReason === "error" || msg.stopReason === "aborted") continue;
334
- const toolCalls = msg.content.filter((block) => block.type === "toolCall");
335
- if (toolCalls.length > 0) {
336
- pendingToolCalls = toolCalls;
337
- existingToolResultIds = new Set();
338
- }
339
- result.push(msg);
340
- continue;
341
- }
342
- if (msg.role === "toolResult") {
343
- existingToolResultIds.add(msg.toolCallId);
344
- result.push(msg);
345
- continue;
346
- }
347
- if (msg.role === "user") {
348
- insertSyntheticToolResults();
349
- result.push(msg);
350
- continue;
351
- }
352
- result.push(msg);
353
- }
354
-
355
- insertSyntheticToolResults();
356
-
357
- return result;
358
- }
359
-
360
- function encodeTextSignatureV1(id: string, phase?: string): string {
361
- const payload: { v: 1; id: string; phase?: string } = { v: 1, id };
362
- if (phase) payload.phase = phase;
363
- return JSON.stringify(payload);
364
- }
365
-
366
- function encodeTextSignature(item: unknown, renderedText?: string): string {
367
- let replayItem = sanitizeResponseMessageItem(item);
368
- if (!replayItem) {
369
- const candidate = item && typeof item === "object" ? item as Record<string, unknown> : undefined;
370
- return encodeTextSignatureV1(
371
- typeof candidate?.id === "string" ? candidate.id : "msg_unknown",
372
- typeof candidate?.phase === "string" ? candidate.phase : undefined,
373
- );
374
- }
375
- const rawText = replayItem.content
376
- .map((part) => part.type === "output_text" ? part.text : part.refusal)
377
- .join("");
378
- if (
379
- renderedText !== undefined
380
- && renderedText !== rawText
381
- && replayItem.content.every((part) => part.type === "output_text")
382
- && LEAKED_CITATION_PROTOCOL.test(rawText)
383
- ) {
384
- replayItem = {
385
- ...replayItem,
386
- content: [{ type: "output_text", text: renderedText, annotations: [] }],
387
- };
388
- }
389
- return JSON.stringify({ v: 2, item: replayItem });
390
- }
391
-
392
- function parseTextSignature(signature: string | undefined): {
393
- id: string;
394
- phase?: TextSignaturePhase;
395
- item?: ReplayableResponseMessageItem;
396
- } | undefined {
397
- if (!signature) return undefined;
398
- if (signature.startsWith("{")) {
399
- try {
400
- const parsed = JSON.parse(signature) as {
401
- v?: number;
402
- id?: string;
403
- phase?: TextSignaturePhase | string;
404
- item?: unknown;
405
- };
406
- if (parsed.v === 2) {
407
- const item = sanitizeResponseMessageItem(parsed.item);
408
- if (item) {
409
- return {
410
- id: item.id,
411
- ...(item.phase ? { phase: item.phase } : {}),
412
- item,
413
- };
414
- }
415
- }
416
- if (parsed.v === 1 && typeof parsed.id === "string") {
417
- return parsed.phase === "commentary" || parsed.phase === "final_answer"
418
- ? { id: parsed.id, phase: parsed.phase }
419
- : { id: parsed.id };
420
- }
421
- } catch {
422
- // Fall through to legacy plain-string handling.
423
- }
424
- }
425
- return { id: signature };
426
- }
427
-
428
- export function isWebSearchActivityTextSignature(signature: string | undefined): boolean {
429
- return signature?.startsWith(WEB_SEARCH_ACTIVITY_TEXT_SIGNATURE_PREFIX) === true;
430
- }
431
-
432
- export function encodeWebSearchActivityTextSignature(callId: string, item?: unknown): string {
433
- const replayItem = sanitizeWebSearchCallItem(item);
434
- if (!replayItem || replayItem.id !== callId) {
435
- return `${WEB_SEARCH_ACTIVITY_TEXT_SIGNATURE_PREFIX}${callId}`;
436
- }
437
- return `${WEB_SEARCH_ACTIVITY_TEXT_SIGNATURE_PREFIX}${callId}:${JSON.stringify({ v: 2, item: replayItem })}`;
438
- }
439
-
440
- export function decodeWebSearchActivityTextSignature(signature: string | undefined): ReplayableWebSearchCallItem | undefined {
441
- if (!isWebSearchActivityTextSignature(signature)) return undefined;
442
- const payload = signature!.slice(WEB_SEARCH_ACTIVITY_TEXT_SIGNATURE_PREFIX.length);
443
- const separator = payload.indexOf(":");
444
- if (separator < 1) return undefined;
445
- const callId = payload.slice(0, separator);
446
- try {
447
- const parsed = JSON.parse(payload.slice(separator + 1)) as { v?: number; item?: unknown };
448
- if (parsed.v !== 2) return undefined;
449
- const item = sanitizeWebSearchCallItem(parsed.item);
450
- return item?.id === callId ? item : undefined;
451
- } catch {
452
- return undefined;
453
- }
454
- }
455
-
456
- const INTERNAL_CITATION_MARKER = /cite([^]+)/;
457
- const INTERNAL_CITATION_REF = /^turn\d+[a-z][a-z0-9_-]*\d+$/i;
458
-
459
- function citationRefsFromResult(result: Record<string, unknown>): string[] {
460
- const refs = new Set<string>();
461
- for (const key of ["ref_id", "reference_id", "id"]) {
462
- const value = result[key];
463
- if (typeof value === "string" && INTERNAL_CITATION_REF.test(value)) refs.add(value);
464
- }
465
- if (typeof result.snippet === "string") {
466
- const marker = INTERNAL_CITATION_MARKER.exec(result.snippet.slice(0, 2048));
467
- for (const ref of marker?.[1]?.split("") ?? []) {
468
- const trimmed = ref.trim();
469
- if (INTERNAL_CITATION_REF.test(trimmed)) refs.add(trimmed);
470
- }
471
- }
472
- return [...refs];
473
- }
474
-
475
- export function extractWebSearchCitationSources(item: unknown): WebSearchCitationSource[] {
476
- const replayItem = sanitizeWebSearchCallItem(item);
477
- if (!replayItem) return [];
478
- const actionResults = Array.isArray(replayItem.action.results)
479
- ? replayItem.action.results.map(cloneJsonRecord).filter((result): result is Record<string, unknown> => !!result)
480
- : [];
481
- const results = [...(replayItem.results ?? []), ...actionResults];
482
- const sources: WebSearchCitationSource[] = [];
483
- const seen = new Set<string>();
484
- for (const result of results) {
485
- const url = typeof result.url === "string" && result.url.trim() ? result.url.trim() : undefined;
486
- if (!url) continue;
487
- const title = typeof result.title === "string" && result.title.trim() ? result.title.trim() : undefined;
488
- for (const refId of citationRefsFromResult(result)) {
489
- const key = `${refId}\n${url}`;
490
- if (seen.has(key)) continue;
491
- seen.add(key);
492
- sources.push({ refId, url, ...(title ? { title } : {}) });
493
- }
494
- }
495
- return sources;
496
- }
497
-
498
- export function collectWebSearchCitationSources<TApi extends Api>(
499
- model: Model<TApi>,
500
- context: Context,
501
- ): WebSearchCitationSource[] {
502
- const byRef = new Map<string, WebSearchCitationSource>();
503
- for (const message of context.messages) {
504
- if (message.role !== "assistant") continue;
505
- if (message.provider !== model.provider || message.api !== model.api || message.model !== model.id) continue;
506
- for (const block of message.content as InternalAssistantContent[]) {
507
- if (block.type !== "text") continue;
508
- const item = decodeWebSearchActivityTextSignature(block.textSignature);
509
- if (!item) continue;
510
- for (const source of extractWebSearchCitationSources(item)) byRef.set(source.refId, source);
511
- }
512
- }
513
- return [...byRef.values()];
514
- }
515
-
516
- export function collectHistoricalCitationSources<TApi extends Api>(
517
- model: Model<TApi>,
518
- context: Context,
519
- ): CitationSource[] {
520
- let latestSources: CitationSource[] = [];
521
- for (const message of context.messages) {
522
- if (message.role !== "assistant") continue;
523
- if (message.provider !== model.provider || message.api !== model.api || message.model !== model.id) continue;
524
- const messageSources: CitationSource[] = [];
525
- const seenUrls = new Set<string>();
526
- const pushSource = (source: CitationSource): void => {
527
- if (!source.url || seenUrls.has(source.url)) return;
528
- seenUrls.add(source.url);
529
- messageSources.push(source);
530
- };
531
- for (const block of message.content as InternalAssistantContent[]) {
532
- if (block.type !== "text" || isWebSearchActivityTextSignature(block.textSignature)) continue;
533
- const markdownLinkPattern = /\[([^\]\n]+)\]\((https?:\/\/[^)\s]+)\)/g;
534
- for (const match of block.text.matchAll(markdownLinkPattern)) {
535
- pushSource({ title: match[1], url: match[2] });
536
- }
537
- const replayItem = parseTextSignature(block.textSignature)?.item;
538
- for (const part of replayItem?.content ?? []) {
539
- if (part.type !== "output_text") continue;
540
- for (const annotation of part.annotations) {
541
- const url = annotation.type === "url_citation" && typeof annotation.url === "string"
542
- ? annotation.url.trim()
543
- : "";
544
- const title = typeof annotation.title === "string" && annotation.title.trim()
545
- ? annotation.title.trim()
546
- : undefined;
547
- if (url) pushSource({ url, ...(title ? { title } : {}) });
548
- }
549
- }
550
- }
551
- if (messageSources.length > 0) latestSources = messageSources;
552
- }
553
- return latestSources;
554
- }
555
-
556
- export function convertResponsesMessages<TApi extends Api>(
557
- model: Model<TApi>,
558
- context: Context,
559
- allowedToolCallProviders: ReadonlySet<string>,
560
- options?: ConvertResponsesMessagesOptions,
561
- ): ResponseInput {
562
- const messages: ResponseInput = [];
563
- const normalizeIdPart = (part: string) => {
564
- const sanitized = part.replace(/[^a-zA-Z0-9_-]/g, "_");
565
- const normalized = sanitized.length > 64 ? sanitized.slice(0, 64) : sanitized;
566
- return normalized.replace(/_+$/, "");
567
- };
568
- const buildForeignResponsesItemId = (itemId: string, prefix: "fc_" | "ctc_") => {
569
- const normalized = `${prefix}${shortHash(itemId)}`;
570
- return normalized.length > 64 ? normalized.slice(0, 64) : normalized;
571
- };
572
- const normalizeToolCallId = (id: string, _targetModel: Model<TApi>, source: Extract<Message, { role: "assistant" }>) => {
573
- if (!allowedToolCallProviders.has(model.provider)) return normalizeIdPart(id);
574
- if (!id.includes("|")) return normalizeIdPart(id);
575
- const [callId, itemId] = id.split("|");
576
- const normalizedCallId = normalizeIdPart(callId);
577
- const isForeignToolCall = source.provider !== model.provider || source.api !== model.api;
578
- const itemPrefix = itemId?.startsWith("ctc_") ? "ctc_" : "fc_";
579
- let normalizedItemId = isForeignToolCall ? buildForeignResponsesItemId(itemId ?? "", itemPrefix) : normalizeIdPart(itemId ?? "");
580
- if (!normalizedItemId.startsWith(itemPrefix)) normalizedItemId = normalizeIdPart(`${itemPrefix}${normalizedItemId}`);
581
- return `${normalizedCallId}|${normalizedItemId}`;
582
- };
583
-
584
- const transformedMessages = transformMessages(context.messages, model as Model<Api>, normalizeToolCallId as never);
585
- const includeSystemPrompt = options?.includeSystemPrompt ?? true;
586
- if (includeSystemPrompt && context.systemPrompt) {
587
- messages.push({ role: model.reasoning ? "developer" : "system", content: sanitizeSurrogates(context.systemPrompt) });
588
- }
589
-
590
- let msgIndex = 0;
591
- for (const msg of transformedMessages) {
592
- if (msg.role === "user") {
593
- if (typeof msg.content === "string") {
594
- messages.push({ role: "user", content: [{ type: "input_text", text: sanitizeSurrogates(msg.content) }] });
595
- } else {
596
- const content = msg.content.map((item) =>
597
- item.type === "text"
598
- ? { type: "input_text" as const, text: sanitizeSurrogates(item.text) }
599
- : { type: "input_image" as const, detail: "auto" as const, image_url: `data:${item.mimeType};base64,${item.data}` },
600
- );
601
- if (content.length > 0) messages.push({ role: "user", content });
602
- }
603
- } else if (msg.role === "assistant") {
604
- const output: ResponseInput = [];
605
- const isSameModel = msg.model === model.id && msg.provider === model.provider && msg.api === model.api;
606
- const isDifferentModel = msg.model !== model.id && msg.provider === model.provider && msg.api === model.api;
607
- let assistantBlockIndex = 0;
608
- for (const block of msg.content as InternalAssistantContent[]) {
609
- if (isImageGenerationCallBlock(block)) {
610
- const imageGenerationCall = sanitizeImageGenerationCallItem(block.item);
611
- if (imageGenerationCall) output.push(imageGenerationCall as ResponseInput[number]);
612
- } else if (block.type === "thinking") {
613
- if (block.thinkingSignature) output.push(JSON.parse(block.thinkingSignature));
614
- } else if (block.type === "text") {
615
- if (isWebSearchActivityTextSignature(block.textSignature)) {
616
- const webSearchItem = isSameModel
617
- ? decodeWebSearchActivityTextSignature(block.textSignature)
618
- : undefined;
619
- if (webSearchItem) output.push(webSearchItem as unknown as ResponseInput[number]);
620
- continue;
621
- }
622
- const parsedSignature = parseTextSignature(block.textSignature);
623
- if (isSameModel && parsedSignature?.item) {
624
- output.push(parsedSignature.item as ResponseInput[number]);
625
- assistantBlockIndex++;
626
- continue;
627
- }
628
- let msgId = parsedSignature?.id ?? `msg_${msgIndex}_${assistantBlockIndex}`;
629
- if (msgId.length > 64) msgId = `msg_${shortHash(msgId)}`;
630
- output.push({
631
- type: "message",
632
- role: "assistant",
633
- content: [{ type: "output_text", text: sanitizeSurrogates(block.text), annotations: [] }],
634
- status: "completed",
635
- id: msgId,
636
- ...(parsedSignature?.phase ? { phase: parsedSignature.phase } : {}),
637
- });
638
- assistantBlockIndex++;
639
- } else if (block.type === "toolCall") {
640
- const [callId, itemIdRaw] = block.id.split("|");
641
- const custom = itemIdRaw?.startsWith("ctc_") === true;
642
- let itemId: string | undefined = itemIdRaw;
643
- if (isDifferentModel && (itemId?.startsWith("fc_") || itemId?.startsWith("ctc_"))) itemId = undefined;
644
- const wireIdentity = wireToolIdentity(block.name, block.thoughtSignature);
645
- if (custom) {
646
- output.push({
647
- type: "custom_tool_call",
648
- ...(itemId ? { id: itemId } : {}),
649
- call_id: callId,
650
- name: wireIdentity.name,
651
- ...(wireIdentity.namespace ? { namespace: wireIdentity.namespace } : {}),
652
- input: typeof block.arguments.input === "string" ? block.arguments.input : "",
653
- } as ResponseInput[number]);
654
- } else {
655
- output.push({
656
- type: "function_call",
657
- ...(itemId ? { id: itemId } : {}),
658
- call_id: callId,
659
- name: wireIdentity.name,
660
- ...(wireIdentity.namespace ? { namespace: wireIdentity.namespace } : {}),
661
- arguments: JSON.stringify(block.arguments),
662
- } as ResponseInput[number]);
663
- }
664
- }
665
- }
666
- if (output.length > 0) messages.push(...output);
667
- } else if (msg.role === "toolResult") {
668
- const textResult = msg.content.filter((c) => c.type === "text").map((c) => c.text).join("\n");
669
- const hasImages = msg.content.some((c) => c.type === "image");
670
- const hasText = textResult.length > 0;
671
- const [callId, itemId] = msg.toolCallId.split("|");
672
- const supportsToolResultImages = hasImages && model.input.includes("image");
673
- let originalCallUsesContentItems = false;
674
- for (let index = msgIndex - 1; index >= 0; index--) {
675
- const previous = context.messages[index];
676
- if (previous?.role !== "assistant") continue;
677
- const originalCall = previous.content.find((block) =>
678
- block.type === "toolCall" && block.id.split("|")[0] === callId);
679
- if (originalCall?.type === "toolCall") {
680
- originalCallUsesContentItems = wireToolIdentity(
681
- originalCall.name,
682
- originalCall.thoughtSignature,
683
- ).namespace === "web";
684
- break;
685
- }
686
- }
687
- const usesContentItems = supportsToolResultImages || originalCallUsesContentItems;
688
- const output = usesContentItems
689
- ? [
690
- ...(hasText ? [{ type: "input_text" as const, text: sanitizeSurrogates(textResult) }] : []),
691
- ...(supportsToolResultImages
692
- ? msg.content
693
- .filter((block) => block.type === "image")
694
- .map((block) => ({
695
- type: "input_image" as const,
696
- detail: "auto" as const,
697
- image_url: `data:${block.mimeType};base64,${block.data}`,
698
- }))
699
- : []),
700
- ...(!hasText && !supportsToolResultImages
701
- ? [{ type: "input_text" as const, text: "(see attached image)" }]
702
- : []),
703
- ]
704
- : sanitizeSurrogates(hasText ? textResult : "(see attached image)");
705
- messages.push({
706
- type: itemId?.startsWith("ctc_") ? "custom_tool_call_output" : "function_call_output",
707
- call_id: callId,
708
- output,
709
- } as ResponseInput[number]);
710
- }
711
- msgIndex++;
712
- }
713
-
714
- return messages;
715
- }
716
-
717
- export function convertResponsesTools(tools: Tool[], options?: ConvertResponsesToolsOptions): OpenAITool[] {
718
- const strict = options?.strict === undefined ? false : options.strict;
719
- return tools.map((tool) => ({
720
- type: "function",
721
- name: tool.name,
722
- description: tool.description,
723
- parameters: tool.parameters as unknown as Record<string, unknown>,
724
- strict,
725
- }));
726
- }
727
-
728
- interface MarkdownCodeRange {
729
- start: number;
730
- end: number;
731
- }
732
-
733
- function characterRunLength(text: string, index: number, character: string): number {
734
- let end = index;
735
- while (end < text.length && text[end] === character) end++;
736
- return end - index;
737
- }
738
-
739
- function isFencePosition(text: string, index: number): boolean {
740
- const lineStart = text.lastIndexOf("\n", index - 1) + 1;
741
- return /^ {0,3}$/.test(text.slice(lineStart, index));
742
- }
743
-
744
- function markdownCodeRanges(text: string): MarkdownCodeRange[] {
745
- const ranges: MarkdownCodeRange[] = [];
746
- let fence: { character: "`" | "~"; length: number; start: number } | undefined;
747
- let index = 0;
748
- while (index < text.length) {
749
- const character = text[index];
750
- if (fence) {
751
- if (character === fence.character && isFencePosition(text, index)) {
752
- const runLength = characterRunLength(text, index, character);
753
- if (runLength >= fence.length) {
754
- ranges.push({ start: fence.start, end: index + runLength });
755
- fence = undefined;
756
- index += runLength;
757
- continue;
758
- }
759
- }
760
- index++;
761
- continue;
762
- }
763
-
764
- if ((character === "`" || character === "~") && isFencePosition(text, index)) {
765
- const runLength = characterRunLength(text, index, character);
766
- if (runLength >= 3) {
767
- fence = { character, length: runLength, start: index };
768
- index += runLength;
769
- continue;
770
- }
771
- }
772
-
773
- if (character === "`") {
774
- const runLength = characterRunLength(text, index, character);
775
- const delimiter = character.repeat(runLength);
776
- const close = text.indexOf(delimiter, index + runLength);
777
- if (close < 0) {
778
- ranges.push({ start: index, end: text.length });
779
- break;
780
- }
781
- ranges.push({ start: index, end: close + runLength });
782
- index = close + runLength;
783
- continue;
784
- }
785
- index++;
786
- }
787
- if (fence) ranges.push({ start: fence.start, end: text.length });
788
- return ranges;
789
- }
790
-
791
- function isInsideMarkdownCode(index: number, ranges: MarkdownCodeRange[]): boolean {
792
- return ranges.some((range) => index >= range.start && index < range.end);
793
- }
794
-
795
- function trailingCitationFragmentStart(text: string, ranges: MarkdownCodeRange[]): number | undefined {
796
- const opener = "cite";
797
- const openIndex = text.lastIndexOf("cite");
798
- if (openIndex >= 0 && text.indexOf("", openIndex) < 0 && !isInsideMarkdownCode(openIndex, ranges)) {
799
- return openIndex;
800
- }
801
- for (let length = Math.min(opener.length - 1, text.length); length > 0; length--) {
802
- if (!text.endsWith(opener.slice(0, length))) continue;
803
- const start = text.length - length;
804
- if (!isInsideMarkdownCode(start, ranges)) return start;
805
- }
806
- return undefined;
807
- }
808
-
809
- function trailingIndexedSourceFragmentStart(text: string, ranges: MarkdownCodeRange[]): number | undefined {
810
- const openIndex = text.lastIndexOf("【");
811
- if (openIndex < 0 || text.indexOf("】", openIndex) >= 0 || isInsideMarkdownCode(openIndex, ranges)) {
812
- return undefined;
813
- }
814
- const fragment = text.slice(openIndex);
815
- const match = /^【\d*(?:†([a-z]*))?$/i.exec(fragment);
816
- if (!match) return undefined;
817
- const sourceFragment = match[1]?.toLowerCase() ?? "";
818
- return "source".startsWith(sourceFragment) ? openIndex : undefined;
819
- }
820
-
821
- export async function processResponsesStream<TApi extends Api>(
822
- openaiStream: AsyncIterable<ResponseStreamEvent>,
823
- output: AssistantMessage,
824
- stream: AssistantMessageEventStream,
825
- model: Model<TApi>,
826
- options?: OpenAIResponsesStreamOptions,
827
- ): Promise<void> {
828
- const blocks = output.content;
829
- const blockIndex = () => blocks.length - 1;
830
- type ThinkingBlock = Extract<AssistantMessage["content"][number], { type: "thinking" }>;
831
- type TextBlock = Extract<AssistantMessage["content"][number], { type: "text" }>;
832
- type ToolCallBlock = Extract<AssistantMessage["content"][number], { type: "toolCall" }> & { partialJson?: string; partialInput?: string };
833
-
834
- type ReasoningState = {
835
- kind: "reasoning";
836
- blockIndex: number;
837
- block: ThinkingBlock;
838
- summaryParts: Map<number, { text: string }>;
839
- };
840
- type MessagePartState = { type: "output_text" | "refusal"; text: string; annotations?: unknown[] };
841
- type MessageState = {
842
- kind: "message";
843
- blockIndex: number;
844
- block: TextBlock;
845
- parts: Map<number, MessagePartState>;
846
- };
847
- type FunctionCallState = {
848
- kind: "function_call";
849
- blockIndex: number;
850
- block: ToolCallBlock;
851
- };
852
- type CustomToolCallState = {
853
- kind: "custom_tool_call";
854
- blockIndex: number;
855
- block: ToolCallBlock;
856
- itemId: string;
857
- sourceItemId?: string;
858
- callId: string;
859
- input: string;
860
- };
861
- type OutputState = ReasoningState | MessageState | FunctionCallState | CustomToolCallState;
862
-
863
- let sawTerminalResponseEvent = false;
864
- const outputStates = new Map<number, OutputState>();
865
- const imageGenerationCallIds = new Set<string>();
866
- const nativeCompactionSignatures = new Set<string>();
867
- const webSearchCitationSources = new Map<string, WebSearchCitationSource>();
868
- for (const source of options?.webSearchCitationSources ?? []) {
869
- if (source.refId && source.url) webSearchCitationSources.set(source.refId, source);
870
- }
871
- const registerWebSearchCitationSources = (item: unknown): void => {
872
- for (const source of extractWebSearchCitationSources(item)) {
873
- webSearchCitationSources.set(source.refId, source);
874
- }
875
- };
876
- const historicalCitationSources = [...(options?.historicalCitationSources ?? [])];
877
- const responseOutputIndexByBlock = new WeakMap<object, number>();
878
- const pushResponseBlock = <T extends InternalAssistantContent>(block: T, outputIndex: number): T => {
879
- (output.content as InternalAssistantContent[]).push(block);
880
- responseOutputIndexByBlock.set(block as object, outputIndex);
881
- return block;
882
- };
883
- const normalizeResponseBlockOrder = (): void => {
884
- const content = output.content as InternalAssistantContent[];
885
- const slots: number[] = [];
886
- const indexedBlocks: Array<{ block: InternalAssistantContent; outputIndex: number; sequence: number }> = [];
887
- for (let index = 0; index < content.length; index++) {
888
- const block = content[index]!;
889
- const outputIndex = responseOutputIndexByBlock.get(block as object);
890
- if (outputIndex === undefined) continue;
891
- slots.push(index);
892
- indexedBlocks.push({ block, outputIndex, sequence: indexedBlocks.length });
893
- }
894
- indexedBlocks.sort((a, b) => a.outputIndex - b.outputIndex || a.sequence - b.sequence);
895
- for (let index = 0; index < slots.length; index++) {
896
- content[slots[index]!] = indexedBlocks[index]!.block;
897
- }
898
- };
899
- const customItemId = (itemId: string | undefined, callId: string): string =>
900
- itemId?.startsWith("ctc_") ? itemId : `ctc_${shortHash(itemId || callId)}`;
901
- const findCustomToolCallState = (event: { output_index?: number; item_id?: string; call_id?: string }): CustomToolCallState | undefined => {
902
- const indexed = typeof event.output_index === "number" ? outputStates.get(event.output_index) : undefined;
903
- if (indexed?.kind === "custom_tool_call") return indexed;
904
- for (const state of outputStates.values()) {
905
- if (state.kind !== "custom_tool_call") continue;
906
- if (event.item_id && (event.item_id === state.itemId || event.item_id === state.sourceItemId)) return state;
907
- if (event.call_id && event.call_id === state.callId) return state;
908
- }
909
- return undefined;
910
- };
911
-
912
- const appendImageGenerationCall = (item: unknown, outputIndex: number): void => {
913
- const imageGenerationCall = sanitizeImageGenerationCallItem(item);
914
- if (!imageGenerationCall || imageGenerationCallIds.has(imageGenerationCall.id)) return;
915
- imageGenerationCallIds.add(imageGenerationCall.id);
916
- pushResponseBlock({
917
- type: "image_generation_call",
918
- item: imageGenerationCall,
919
- }, outputIndex);
920
- };
921
- const appendNativeCompaction = (item: unknown, outputIndex: number): void => {
922
- if (!item || typeof item !== "object") return;
923
- const type = (item as { type?: unknown }).type;
924
- if (type !== "compaction" && type !== "context_compaction") return;
925
- const signature = JSON.stringify(item);
926
- if (nativeCompactionSignatures.has(signature)) return;
927
- nativeCompactionSignatures.add(signature);
928
- pushResponseBlock({
929
- type: "thinking",
930
- thinking: "",
931
- thinkingSignature: signature,
932
- redacted: true,
933
- } as AssistantMessage["content"][number], outputIndex);
934
- };
935
-
936
- const renderReasoningSummary = (summaryParts: Map<number, { text: string }>): string =>
937
- Array.from(summaryParts.entries())
938
- .sort(([a], [b]) => a - b)
939
- .map(([, part]) => part.text)
940
- .join("\n\n");
941
-
942
- const annotationUrl = (annotation: unknown): string | undefined => {
943
- const candidate = annotation && typeof annotation === "object" ? annotation as Record<string, unknown> : undefined;
944
- return candidate?.type === "url_citation" && typeof candidate.url === "string" && candidate.url.trim() ? candidate.url.trim() : undefined;
945
- };
946
- const annotationTitle = (annotation: unknown): string | undefined => {
947
- const candidate = annotation && typeof annotation === "object" ? annotation as Record<string, unknown> : undefined;
948
- return typeof candidate?.title === "string" && candidate.title.trim() ? candidate.title.trim() : undefined;
949
- };
950
- const markdownLinkText = (value: string): string => value.replace(/\\/g, "\\\\").replace(/]/g, "\\]").replace(/\n+/g, " ");
951
- const markdownLinkUrl = (value: string): string => value.replace(/[)\s]/g, (char) => encodeURIComponent(char));
952
- const containsMarkdownLink = (value: string): boolean => /\[[^\]\n]+\]\([^) \n]+\)/.test(value);
953
- const citationSourceLabel = (source: CitationSource): string => {
954
- try {
955
- const hostname = new URL(source.url).hostname.replace(/^www\./, "");
956
- if (hostname) return hostname;
957
- } catch {
958
- // Fall back to the result title or URL.
959
- }
960
- return source.title ?? source.url;
961
- };
962
- const citationSourceMarkdown = (source: CitationSource): string =>
963
- `[${markdownLinkText(citationSourceLabel(source))}](${markdownLinkUrl(source.url)})`;
964
- const renderInternalCitationMarkers = (text: string): string => {
965
- const initialCodeRanges = markdownCodeRanges(text);
966
- const fragmentStart = trailingCitationFragmentStart(text, initialCodeRanges);
967
- const visibleText = fragmentStart === undefined ? text : text.slice(0, fragmentStart);
968
- const codeRanges = fragmentStart === undefined ? initialCodeRanges : markdownCodeRanges(visibleText);
969
- const markerPattern = new RegExp(INTERNAL_CITATION_MARKER.source, "g");
970
- let cursor = 0;
971
- let rendered = "";
972
- for (const match of visibleText.matchAll(markerPattern)) {
973
- const start = match.index ?? 0;
974
- if (isInsideMarkdownCode(start, codeRanges)) continue;
975
- rendered += visibleText.slice(cursor, start);
976
- const seenUrls = new Set<string>();
977
- const links: string[] = [];
978
- for (const rawRef of match[1]?.split("") ?? []) {
979
- const source = webSearchCitationSources.get(rawRef.trim());
980
- if (!source || seenUrls.has(source.url)) continue;
981
- seenUrls.add(source.url);
982
- links.push(citationSourceMarkdown(source));
983
- }
984
- rendered += links.length > 0 ? `(${links.join(", ")})` : "";
985
- cursor = start + match[0].length;
986
- }
987
- if (cursor === 0) return visibleText;
988
- return rendered + visibleText.slice(cursor);
989
- };
990
- const renderIndexedSourceMarkers = (text: string): string => {
991
- const initialCodeRanges = markdownCodeRanges(text);
992
- const fragmentStart = trailingIndexedSourceFragmentStart(text, initialCodeRanges);
993
- const visibleText = fragmentStart === undefined ? text : text.slice(0, fragmentStart);
994
- const codeRanges = fragmentStart === undefined ? initialCodeRanges : markdownCodeRanges(visibleText);
995
- const markerPattern = /【(\d+)†source】/gi;
996
- let cursor = 0;
997
- let rendered = "";
998
- for (const match of visibleText.matchAll(markerPattern)) {
999
- const start = match.index ?? 0;
1000
- if (isInsideMarkdownCode(start, codeRanges)) continue;
1001
- rendered += visibleText.slice(cursor, start);
1002
- const followingText = visibleText.slice(start + match[0].length);
1003
- const alreadyLinked = /^\s*(?:\(\s*)?\[[^\]\n]+\]\(https?:\/\/[^)\s]+\)/.test(followingText);
1004
- const sourceIndex = Number.parseInt(match[1] ?? "", 10);
1005
- const source = Number.isInteger(sourceIndex) ? historicalCitationSources[sourceIndex] : undefined;
1006
- rendered += alreadyLinked || !source ? "" : `(${citationSourceMarkdown(source)})`;
1007
- cursor = start + match[0].length;
1008
- }
1009
- if (cursor === 0) return visibleText;
1010
- return rendered + visibleText.slice(cursor);
1011
- };
1012
- const sourceMarkdown = (annotations: unknown[], citedUrls: Set<string>): string => {
1013
- const seen = new Set<string>();
1014
- const links: string[] = [];
1015
- for (const annotation of annotations) {
1016
- const url = annotationUrl(annotation);
1017
- if (!url || citedUrls.has(url) || seen.has(url)) continue;
1018
- seen.add(url);
1019
- links.push(`[${markdownLinkText(annotationTitle(annotation) ?? url)}](${markdownLinkUrl(url)})`);
1020
- }
1021
- return links.length > 0 ? `\n\nSources: ${links.join(", ")}` : "";
1022
- };
1023
- const renderOutputTextPart = (part: MessagePartState): string => {
1024
- const annotations = part.annotations ?? [];
1025
- if (part.type !== "output_text" || annotations.length === 0) return part.text;
1026
- const spans = annotations
1027
- .map((annotation) => {
1028
- const candidate = annotation && typeof annotation === "object" ? annotation as Record<string, unknown> : undefined;
1029
- const url = annotationUrl(annotation);
1030
- const start = candidate?.start_index;
1031
- const end = candidate?.end_index;
1032
- return typeof start === "number" && typeof end === "number" && url && Number.isInteger(start) && Number.isInteger(end) && start >= 0 && end > start && end <= part.text.length
1033
- ? { start, end, url }
1034
- : undefined;
1035
- })
1036
- .filter((span): span is { start: number; end: number; url: string } => !!span)
1037
- .sort((a, b) => a.start - b.start || a.end - b.end);
1038
- const citedUrls = new Set<string>();
1039
- let cursor = 0;
1040
- let rendered = "";
1041
- for (const span of spans) {
1042
- if (span.start < cursor) continue;
1043
- rendered += part.text.slice(cursor, span.start);
1044
- const label = part.text.slice(span.start, span.end);
1045
- rendered += containsMarkdownLink(label)
1046
- ? label
1047
- : `[${markdownLinkText(label)}](${markdownLinkUrl(span.url)})`;
1048
- citedUrls.add(span.url);
1049
- cursor = span.end;
1050
- }
1051
- if (cursor === 0) return part.text + sourceMarkdown(annotations, citedUrls);
1052
- rendered += part.text.slice(cursor);
1053
- return rendered + sourceMarkdown(annotations, citedUrls);
1054
- };
1055
- const renderMessageText = (parts: Map<number, MessagePartState>, citations = false): string =>
1056
- Array.from(parts.entries())
1057
- .sort(([a], [b]) => a - b)
1058
- .map(([, part]) => {
1059
- const text = citations ? renderOutputTextPart(part) : part.text;
1060
- return part.type === "output_text"
1061
- ? renderIndexedSourceMarkers(renderInternalCitationMarkers(text))
1062
- : text;
1063
- })
1064
- .join("");
1065
-
1066
- const emitAppendedDelta = (
1067
- eventType: "thinking_delta" | "text_delta",
1068
- contentIndex: number,
1069
- previous: string,
1070
- next: string,
1071
- ) => {
1072
- if (next.startsWith(previous)) {
1073
- const delta = next.slice(previous.length);
1074
- if (delta.length > 0) {
1075
- stream.push({ type: eventType, contentIndex, delta, partial: output });
1076
- }
1077
- }
1078
- };
1079
-
1080
- for await (const event of openaiStream) {
1081
- if (event.type === "response.created") {
1082
- output.responseId = event.response.id;
1083
- } else if (event.type === "response.output_item.added") {
1084
- const item = event.item;
1085
- const customItem = item as unknown as { type?: string; id?: string; call_id?: string; name?: string; namespace?: string; input?: string };
1086
- if (item.type === "reasoning") {
1087
- const currentBlock: ThinkingBlock = pushResponseBlock(
1088
- { type: "thinking", thinking: "" },
1089
- event.output_index,
1090
- );
1091
- outputStates.set(event.output_index, {
1092
- kind: "reasoning",
1093
- blockIndex: blockIndex(),
1094
- block: currentBlock,
1095
- summaryParts: new Map(),
1096
- });
1097
- stream.push({ type: "thinking_start", contentIndex: blockIndex(), partial: output });
1098
- } else if (item.type === "message") {
1099
- const currentBlock: TextBlock = pushResponseBlock(
1100
- { type: "text", text: "" },
1101
- event.output_index,
1102
- );
1103
- outputStates.set(event.output_index, {
1104
- kind: "message",
1105
- blockIndex: blockIndex(),
1106
- block: currentBlock,
1107
- parts: new Map(),
1108
- });
1109
- stream.push({ type: "text_start", contentIndex: blockIndex(), partial: output });
1110
- } else if (item.type === "function_call") {
1111
- const namespace = (item as { namespace?: unknown }).namespace;
1112
- const thoughtSignature = encodeToolNamespaceSignature(namespace, item.name);
1113
- const currentBlock: ToolCallBlock = {
1114
- type: "toolCall",
1115
- id: `${item.call_id}|${item.id}`,
1116
- name: localToolName(namespace, item.name),
1117
- arguments: {},
1118
- ...(thoughtSignature ? { thoughtSignature } : {}),
1119
- partialJson: item.arguments || "",
1120
- };
1121
- pushResponseBlock(currentBlock, event.output_index);
1122
- outputStates.set(event.output_index, {
1123
- kind: "function_call",
1124
- blockIndex: blockIndex(),
1125
- block: currentBlock,
1126
- });
1127
- stream.push({ type: "toolcall_start", contentIndex: blockIndex(), partial: output });
1128
- } else if (customItem.type === "custom_tool_call" && customItem.call_id && customItem.name) {
1129
- const itemId = customItemId(customItem.id, customItem.call_id);
1130
- const input = customItem.input ?? "";
1131
- const thoughtSignature = encodeToolNamespaceSignature(customItem.namespace, customItem.name);
1132
- const currentBlock: ToolCallBlock = {
1133
- type: "toolCall",
1134
- id: `${customItem.call_id}|${itemId}`,
1135
- name: localToolName(customItem.namespace, customItem.name),
1136
- arguments: { input },
1137
- ...(thoughtSignature ? { thoughtSignature } : {}),
1138
- partialInput: input,
1139
- };
1140
- pushResponseBlock(currentBlock, event.output_index);
1141
- outputStates.set(event.output_index, {
1142
- kind: "custom_tool_call",
1143
- blockIndex: blockIndex(),
1144
- block: currentBlock,
1145
- itemId,
1146
- sourceItemId: customItem.id,
1147
- callId: customItem.call_id,
1148
- input,
1149
- });
1150
- stream.push({ type: "toolcall_start", contentIndex: blockIndex(), partial: output });
1151
- }
1152
- } else if (event.type === "response.reasoning_summary_part.added") {
1153
- const state = outputStates.get(event.output_index);
1154
- if (state?.kind === "reasoning") {
1155
- state.summaryParts.set(event.summary_index, { text: event.part.text });
1156
- }
1157
- } else if (event.type === "response.reasoning_summary_text.delta") {
1158
- const state = outputStates.get(event.output_index);
1159
- if (state?.kind === "reasoning") {
1160
- const summaryPart = state.summaryParts.get(event.summary_index) ?? { text: "" };
1161
- summaryPart.text += event.delta;
1162
- state.summaryParts.set(event.summary_index, summaryPart);
1163
- const previousThinking = state.block.thinking;
1164
- const nextThinking = renderReasoningSummary(state.summaryParts);
1165
- state.block.thinking = nextThinking;
1166
- emitAppendedDelta("thinking_delta", state.blockIndex, previousThinking, nextThinking);
1167
- }
1168
- } else if (event.type === "response.reasoning_summary_part.done") {
1169
- const state = outputStates.get(event.output_index);
1170
- if (state?.kind === "reasoning") {
1171
- state.summaryParts.set(event.summary_index, { text: event.part.text });
1172
- state.block.thinking = renderReasoningSummary(state.summaryParts);
1173
- }
1174
- } else if (event.type === "response.reasoning_text.delta") {
1175
- const state = outputStates.get(event.output_index);
1176
- if (state?.kind === "reasoning") {
1177
- state.block.thinking += event.delta;
1178
- stream.push({ type: "thinking_delta", contentIndex: state.blockIndex, delta: event.delta, partial: output });
1179
- }
1180
- } else if (event.type === "response.content_part.added") {
1181
- const state = outputStates.get(event.output_index);
1182
- if (state?.kind === "message" && (event.part.type === "output_text" || event.part.type === "refusal")) {
1183
- const annotations = event.part.type === "output_text" && Array.isArray((event.part as { annotations?: unknown }).annotations)
1184
- ? (event.part as { annotations: unknown[] }).annotations
1185
- : undefined;
1186
- state.parts.set(event.content_index, {
1187
- type: event.part.type,
1188
- text: event.part.type === "output_text" ? event.part.text : event.part.refusal,
1189
- ...(annotations ? { annotations } : {}),
1190
- });
1191
- }
1192
- } else if ((event as { type?: string }).type === "response.output_text.annotation.added") {
1193
- const annotationEvent = event as unknown as { output_index: number; content_index: number; annotation?: unknown };
1194
- const state = outputStates.get(annotationEvent.output_index);
1195
- if (state?.kind === "message") {
1196
- const messagePart = state.parts.get(annotationEvent.content_index) ?? { type: "output_text" as const, text: "", annotations: [] };
1197
- if (messagePart.type === "output_text" && annotationEvent.annotation) {
1198
- messagePart.annotations = [...(messagePart.annotations ?? []), annotationEvent.annotation];
1199
- state.parts.set(annotationEvent.content_index, messagePart);
1200
- }
1201
- }
1202
- } else if (event.type === "response.output_text.delta") {
1203
- const state = outputStates.get(event.output_index);
1204
- if (state?.kind === "message") {
1205
- const messagePart = state.parts.get(event.content_index) ?? { type: "output_text" as const, text: "" };
1206
- if (messagePart.type === "output_text") {
1207
- messagePart.text += event.delta;
1208
- state.parts.set(event.content_index, messagePart);
1209
- const previousText = state.block.text;
1210
- const nextText = renderMessageText(state.parts);
1211
- state.block.text = nextText;
1212
- emitAppendedDelta("text_delta", state.blockIndex, previousText, nextText);
1213
- }
1214
- }
1215
- } else if (event.type === "response.refusal.delta") {
1216
- const state = outputStates.get(event.output_index);
1217
- if (state?.kind === "message") {
1218
- const messagePart = state.parts.get(event.content_index) ?? { type: "refusal" as const, text: "" };
1219
- if (messagePart.type === "refusal") {
1220
- messagePart.text += event.delta;
1221
- state.parts.set(event.content_index, messagePart);
1222
- const previousText = state.block.text;
1223
- const nextText = renderMessageText(state.parts);
1224
- state.block.text = nextText;
1225
- emitAppendedDelta("text_delta", state.blockIndex, previousText, nextText);
1226
- }
1227
- }
1228
- } else if ((event as { type?: string }).type === "response.custom_tool_call_input.delta") {
1229
- const customEvent = event as unknown as { output_index?: number; item_id?: string; call_id?: string; delta?: string };
1230
- const state = findCustomToolCallState(customEvent);
1231
- if (state && typeof customEvent.delta === "string") {
1232
- state.input += customEvent.delta;
1233
- state.block.partialInput = state.input;
1234
- state.block.arguments = { input: state.input };
1235
- stream.push({ type: "toolcall_delta", contentIndex: state.blockIndex, delta: customEvent.delta, partial: output });
1236
- }
1237
- } else if (event.type === "response.function_call_arguments.delta") {
1238
- const state = outputStates.get(event.output_index);
1239
- if (state?.kind === "function_call") {
1240
- state.block.partialJson = (state.block.partialJson ?? "") + event.delta;
1241
- state.block.arguments = parseStreamingJson(state.block.partialJson ?? "");
1242
- stream.push({ type: "toolcall_delta", contentIndex: state.blockIndex, delta: event.delta, partial: output });
1243
- }
1244
- } else if (event.type === "response.function_call_arguments.done") {
1245
- const state = outputStates.get(event.output_index);
1246
- if (state?.kind === "function_call") {
1247
- const previousPartialJson = state.block.partialJson ?? "";
1248
- state.block.partialJson = event.arguments;
1249
- state.block.arguments = parseStreamingJson(state.block.partialJson ?? "");
1250
- if (event.arguments.startsWith(previousPartialJson)) {
1251
- const delta = event.arguments.slice(previousPartialJson.length);
1252
- if (delta.length > 0) {
1253
- stream.push({ type: "toolcall_delta", contentIndex: state.blockIndex, delta, partial: output });
1254
- }
1255
- }
1256
- }
1257
- } else if (event.type === "response.output_item.done") {
1258
- const item = event.item;
1259
- const customItem = item as unknown as { type?: string; id?: string; call_id?: string; name?: string; namespace?: string; input?: string };
1260
- if (item.type === "reasoning") {
1261
- let state = outputStates.get(event.output_index);
1262
- if (!state || state.kind !== "reasoning") {
1263
- const currentBlock: ThinkingBlock = pushResponseBlock(
1264
- { type: "thinking", thinking: "" },
1265
- event.output_index,
1266
- );
1267
- state = { kind: "reasoning", blockIndex: blockIndex(), block: currentBlock, summaryParts: new Map() };
1268
- outputStates.set(event.output_index, state);
1269
- }
1270
- const summaryText = item.summary?.map((summary) => summary.text).join("\n\n") || "";
1271
- const contentText = (item as { content?: Array<{ text?: string }> }).content?.map((content) => content.text ?? "").filter(Boolean).join("\n\n") || "";
1272
- state.block.thinking = summaryText || contentText || state.block.thinking;
1273
- state.block.thinkingSignature = JSON.stringify(item);
1274
- stream.push({ type: "thinking_end", contentIndex: state.blockIndex, content: state.block.thinking, partial: output });
1275
- outputStates.delete(event.output_index);
1276
- } else if (item.type === "message") {
1277
- let state = outputStates.get(event.output_index);
1278
- if (!state || state.kind !== "message") {
1279
- const currentBlock: TextBlock = pushResponseBlock(
1280
- { type: "text", text: "" },
1281
- event.output_index,
1282
- );
1283
- state = { kind: "message", blockIndex: blockIndex(), block: currentBlock, parts: new Map() };
1284
- outputStates.set(event.output_index, state);
1285
- }
1286
- // Null-tolerant like the reasoning branch above: OpenAI-compatible streams
1287
- // (e.g. vLLM) can emit an empty message item with content: null before a
1288
- // function_call. Without the guard, item.content.map throws and the stream
1289
- // aborts, silently dropping the tool call (earendil-works/pi#5819).
1290
- const finalContent = item.content ?? [];
1291
- if (finalContent.length > 0) {
1292
- state.parts.clear();
1293
- for (let contentIndex = 0; contentIndex < finalContent.length; contentIndex++) {
1294
- const content = finalContent[contentIndex]!;
1295
- const annotations = content.type === "output_text" && Array.isArray((content as { annotations?: unknown }).annotations)
1296
- ? (content as { annotations: unknown[] }).annotations
1297
- : undefined;
1298
- state.parts.set(contentIndex, {
1299
- type: content.type === "output_text" ? "output_text" : "refusal",
1300
- text: content.type === "output_text" ? content.text : content.refusal,
1301
- ...(annotations ? { annotations } : {}),
1302
- });
1303
- }
1304
- }
1305
- state.block.text = renderMessageText(state.parts, true);
1306
- state.block.textSignature = encodeTextSignature(item, state.block.text);
1307
- stream.push({ type: "text_end", contentIndex: state.blockIndex, content: state.block.text, partial: output });
1308
- outputStates.delete(event.output_index);
1309
- } else if (item.type === "function_call") {
1310
- const state = outputStates.get(event.output_index);
1311
- const namespace = (item as { namespace?: unknown }).namespace;
1312
- const thoughtSignature = encodeToolNamespaceSignature(namespace, item.name);
1313
- const args = state?.kind === "function_call" && state.block.partialJson
1314
- ? parseStreamingJson(state.block.partialJson)
1315
- : parseStreamingJson(item.arguments || "{}");
1316
- const toolCall = state?.kind === "function_call"
1317
- ? (() => {
1318
- state.block.arguments = args;
1319
- state.block.name = localToolName(namespace, item.name);
1320
- if (thoughtSignature) state.block.thoughtSignature = thoughtSignature;
1321
- delete state.block.partialJson;
1322
- return state.block;
1323
- })()
1324
- : (() => {
1325
- const fallbackToolCall: ToolCallBlock = {
1326
- type: "toolCall",
1327
- id: `${item.call_id}|${item.id}`,
1328
- name: localToolName((item as { namespace?: unknown }).namespace, item.name),
1329
- arguments: args,
1330
- ...(thoughtSignature ? { thoughtSignature } : {}),
1331
- };
1332
- pushResponseBlock(fallbackToolCall, event.output_index);
1333
- return fallbackToolCall;
1334
- })();
1335
- const toolCallIndex = state?.kind === "function_call" ? state.blockIndex : blockIndex();
1336
- stream.push({ type: "toolcall_end", contentIndex: toolCallIndex, toolCall, partial: output });
1337
- outputStates.delete(event.output_index);
1338
- } else if (customItem.type === "custom_tool_call" && customItem.call_id && customItem.name) {
1339
- const state = findCustomToolCallState({
1340
- output_index: event.output_index,
1341
- item_id: customItem.id,
1342
- call_id: customItem.call_id,
1343
- });
1344
- const input = typeof customItem.input === "string" ? customItem.input : state?.input ?? "";
1345
- const thoughtSignature = encodeToolNamespaceSignature(customItem.namespace, customItem.name);
1346
- const toolCall = state
1347
- ? (() => {
1348
- state.input = input;
1349
- state.block.name = localToolName(customItem.namespace, customItem.name);
1350
- state.block.arguments = { input };
1351
- if (thoughtSignature) state.block.thoughtSignature = thoughtSignature;
1352
- delete state.block.partialInput;
1353
- return state.block;
1354
- })()
1355
- : (() => {
1356
- const fallbackToolCall: ToolCallBlock = {
1357
- type: "toolCall",
1358
- id: `${customItem.call_id}|${customItemId(customItem.id, customItem.call_id)}`,
1359
- name: localToolName(customItem.namespace, customItem.name),
1360
- arguments: { input },
1361
- ...(thoughtSignature ? { thoughtSignature } : {}),
1362
- };
1363
- pushResponseBlock(fallbackToolCall, event.output_index);
1364
- return fallbackToolCall;
1365
- })();
1366
- const toolCallIndex = state?.blockIndex ?? blockIndex();
1367
- stream.push({ type: "toolcall_end", contentIndex: toolCallIndex, toolCall, partial: output });
1368
- outputStates.delete(event.output_index);
1369
- } else if (item.type === "web_search_call") {
1370
- registerWebSearchCitationSources(item);
1371
- outputStates.delete(event.output_index);
1372
- } else if (item.type === "image_generation_call") {
1373
- appendImageGenerationCall(item, event.output_index);
1374
- outputStates.delete(event.output_index);
1375
- } else if (
1376
- (item as { type?: unknown }).type === "compaction"
1377
- || (item as { type?: unknown }).type === "context_compaction"
1378
- ) {
1379
- appendNativeCompaction(item, event.output_index);
1380
- outputStates.delete(event.output_index);
1381
- }
1382
- } else if (event.type === "response.completed" || event.type === "response.incomplete") {
1383
- sawTerminalResponseEvent = true;
1384
- const response = event.response;
1385
- const finalOutput = Array.isArray((response as { output?: unknown } | undefined)?.output)
1386
- ? ((response as unknown as { output: unknown[] }).output)
1387
- : [];
1388
- for (let outputIndex = 0; outputIndex < finalOutput.length; outputIndex++) {
1389
- const item = finalOutput[outputIndex];
1390
- if ((item as { type?: unknown } | undefined)?.type === "web_search_call") {
1391
- registerWebSearchCitationSources(item);
1392
- }
1393
- if ((item as { type?: unknown } | undefined)?.type === "image_generation_call") {
1394
- appendImageGenerationCall(item, outputIndex);
1395
- }
1396
- appendNativeCompaction(item, outputIndex);
1397
- }
1398
- // Some Responses-compatible transports only include compaction in the
1399
- // terminal output array. Preserve its authoritative output_index instead
1400
- // of appending it after an already-streamed tool call.
1401
- normalizeResponseBlockOrder();
1402
- if (response?.id) output.responseId = response.id;
1403
- if (response?.usage) {
1404
- const cachedTokens = response.usage.input_tokens_details?.cached_tokens || 0;
1405
- const reasoningTokens = (response.usage as { output_tokens_details?: { reasoning_tokens?: number } }).output_tokens_details?.reasoning_tokens || 0;
1406
- output.usage = {
1407
- input: (response.usage.input_tokens || 0) - cachedTokens,
1408
- output: response.usage.output_tokens || 0,
1409
- cacheRead: cachedTokens,
1410
- cacheWrite: 0,
1411
- totalTokens: response.usage.total_tokens || 0,
1412
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
1413
- };
1414
- (output.usage as Usage & { reasoning?: number }).reasoning = reasoningTokens;
1415
- }
1416
- calculateCost(model, output.usage);
1417
- if (options?.applyServiceTierPricing) {
1418
- const serviceTier = options.resolveServiceTier
1419
- ? options.resolveServiceTier(response?.service_tier, options.serviceTier)
1420
- : (response?.service_tier ?? options.serviceTier);
1421
- options.applyServiceTierPricing(output.usage, serviceTier);
1422
- }
1423
- output.stopReason = mapStopReason(response?.status);
1424
- if (output.content.some((block) => block.type === "toolCall") && output.stopReason === "stop") {
1425
- output.stopReason = "toolUse";
1426
- }
1427
- } else if (event.type === "error") {
1428
- const details = [event.code, event.message].filter(Boolean).join(": ");
1429
- throw new Error(details || "Unknown error");
1430
- } else if (event.type === "response.failed") {
1431
- sawTerminalResponseEvent = true;
1432
- const error = event.response?.error;
1433
- const details = (event.response as { incomplete_details?: { reason?: string } } | undefined)?.incomplete_details;
1434
- const msg = error
1435
- ? `${error.code || "unknown"}: ${error.message || "no message"}`
1436
- : details?.reason
1437
- ? `incomplete: ${details.reason}`
1438
- : "Unknown error (no error details in response)";
1439
- throw new Error(msg);
1440
- }
1441
- }
1442
- if (!sawTerminalResponseEvent) {
1443
- throw new Error("OpenAI Responses stream ended before a terminal response event");
1444
- }
1445
- }
1446
-
1447
- function mapStopReason(status: string | undefined): AssistantMessage["stopReason"] {
1448
- if (!status) return "stop";
1449
- switch (status) {
1450
- case "completed":
1451
- return "stop";
1452
- case "incomplete":
1453
- return "length";
1454
- case "failed":
1455
- case "cancelled":
1456
- return "error";
1457
- case "in_progress":
1458
- case "queued":
1459
- return "stop";
1460
- default:
1461
- throw new Error(`Unhandled stop reason: ${status}`);
1462
- }
1463
- }
1
+ // Compatibility facade. Implementations must import their owning modules directly.
2
+ export { collectHistoricalCitationSources, collectWebSearchCitationSources, extractWebSearchCitationSources } from "@oai404iao/pi-codex-runtime/internal/providers/responses/citations";
3
+ export { convertResponsesMessages } from "@oai404iao/pi-codex-runtime/internal/providers/responses/messages";
4
+ export { WEB_SEARCH_ACTIVITY_TEXT_SIGNATURE_PREFIX, decodeWebSearchActivityTextSignature, encodeWebSearchActivityTextSignature, isWebSearchActivityTextSignature } from "@oai404iao/pi-codex-runtime/internal/providers/responses/signatures";
5
+ export { processResponsesStream } from "@oai404iao/pi-codex-runtime/internal/providers/responses/stream";
6
+ export { convertResponsesTools } from "@oai404iao/pi-codex-runtime/internal/providers/responses/tools";
7
+ export type { CitationSource, OpenAIResponsesStreamOptions, ReplayableWebSearchCallItem, WebSearchCitationSource } from "@oai404iao/pi-codex-runtime/internal/providers/responses/types";