@oai404iao/pi-codex-runtime 0.1.0-alpha.1

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 (49) hide show
  1. package/LICENSE +28 -0
  2. package/LICENSES/Apache-2.0.txt +201 -0
  3. package/LICENSES/OpenAI-Codex-NOTICE.txt +6 -0
  4. package/README.md +26 -0
  5. package/THIRD_PARTY_NOTICES.md +19 -0
  6. package/config.schema.json +174 -0
  7. package/models.schema.json +217 -0
  8. package/package.json +68 -0
  9. package/provenance/openai-codex-eb9dceba-reserved-tools.json +140 -0
  10. package/src/activation.ts +56 -0
  11. package/src/broker.ts +133 -0
  12. package/src/capabilities.ts +146 -0
  13. package/src/codex-http.ts +133 -0
  14. package/src/codex-identity-extension.ts +341 -0
  15. package/src/codex-request-profile.ts +45 -0
  16. package/src/codex-reserved-tools.ts +33 -0
  17. package/src/codex-wire-identity.ts +596 -0
  18. package/src/extension/provider-presentation.ts +11 -0
  19. package/src/glyphs.ts +70 -0
  20. package/src/index.ts +4 -0
  21. package/src/model-catalog/catalog.ts +636 -0
  22. package/src/model-catalog/default-models.json +252 -0
  23. package/src/model-catalog/runtime.ts +113 -0
  24. package/src/model-catalog/types.ts +95 -0
  25. package/src/provider-headers.ts +54 -0
  26. package/src/providers/openai-codex/stream-effects.ts +21 -0
  27. package/src/providers/openai-codex/types.ts +155 -0
  28. package/src/providers/responses/citations.ts +105 -0
  29. package/src/providers/responses/history.ts +144 -0
  30. package/src/providers/responses/items.ts +87 -0
  31. package/src/providers/responses/markdown.ts +92 -0
  32. package/src/providers/responses/messages.ts +169 -0
  33. package/src/providers/responses/signatures.ts +102 -0
  34. package/src/providers/responses/stream-state.ts +99 -0
  35. package/src/providers/responses/stream.ts +386 -0
  36. package/src/providers/responses/text-renderer.ts +147 -0
  37. package/src/providers/responses/text.ts +32 -0
  38. package/src/providers/responses/tool-identity.ts +25 -0
  39. package/src/providers/responses/tools.ts +14 -0
  40. package/src/providers/responses/types.ts +113 -0
  41. package/src/providers/responses/usage.ts +53 -0
  42. package/src/reserved-tools/image-generation.ts +53 -0
  43. package/src/reserved-tools/types.ts +32 -0
  44. package/src/reserved-tools/web-search.ts +269 -0
  45. package/src/session-claims.ts +19 -0
  46. package/src/settings.ts +247 -0
  47. package/src/subagent-inline.ts +8 -0
  48. package/src/tool-activation.ts +89 -0
  49. package/src/utils/theme.ts +7 -0
@@ -0,0 +1,105 @@
1
+ import { type Api, type Context, type Model } from "@earendil-works/pi-ai";
2
+ import { cloneJsonRecord, sanitizeWebSearchCallItem } from "./items.js";
3
+ import { decodeWebSearchActivityTextSignature, isWebSearchActivityTextSignature, parseTextSignature } from "./signatures.js";
4
+ import { type CitationSource, type InternalAssistantContent, type WebSearchCitationSource } from "./types.js";
5
+
6
+ export const INTERNAL_CITATION_MARKER = /cite([^]+)/;
7
+
8
+ const INTERNAL_CITATION_REF = /^turn\d+[a-z][a-z0-9_-]*\d+$/i;
9
+
10
+ function citationRefsFromResult(result: Record<string, unknown>): string[] {
11
+ const refs = new Set<string>();
12
+ for (const key of ["ref_id", "reference_id", "id"]) {
13
+ const value = result[key];
14
+ if (typeof value === "string" && INTERNAL_CITATION_REF.test(value)) refs.add(value);
15
+ }
16
+ if (typeof result.snippet === "string") {
17
+ const marker = INTERNAL_CITATION_MARKER.exec(result.snippet.slice(0, 2048));
18
+ for (const ref of marker?.[1]?.split("") ?? []) {
19
+ const trimmed = ref.trim();
20
+ if (INTERNAL_CITATION_REF.test(trimmed)) refs.add(trimmed);
21
+ }
22
+ }
23
+ return [...refs];
24
+ }
25
+
26
+ export function extractWebSearchCitationSources(item: unknown): WebSearchCitationSource[] {
27
+ const replayItem = sanitizeWebSearchCallItem(item);
28
+ if (!replayItem) return [];
29
+ const actionResults = Array.isArray(replayItem.action.results)
30
+ ? replayItem.action.results.map(cloneJsonRecord).filter((result): result is Record<string, unknown> => !!result)
31
+ : [];
32
+ const results = [...(replayItem.results ?? []), ...actionResults];
33
+ const sources: WebSearchCitationSource[] = [];
34
+ const seen = new Set<string>();
35
+ for (const result of results) {
36
+ const url = typeof result.url === "string" && result.url.trim() ? result.url.trim() : undefined;
37
+ if (!url) continue;
38
+ const title = typeof result.title === "string" && result.title.trim() ? result.title.trim() : undefined;
39
+ for (const refId of citationRefsFromResult(result)) {
40
+ const key = `${refId}\n${url}`;
41
+ if (seen.has(key)) continue;
42
+ seen.add(key);
43
+ sources.push({ refId, url, ...(title ? { title } : {}) });
44
+ }
45
+ }
46
+ return sources;
47
+ }
48
+
49
+ export function collectWebSearchCitationSources<TApi extends Api>(
50
+ model: Model<TApi>,
51
+ context: Context,
52
+ ): WebSearchCitationSource[] {
53
+ const byRef = new Map<string, WebSearchCitationSource>();
54
+ for (const message of context.messages) {
55
+ if (message.role !== "assistant") continue;
56
+ if (message.provider !== model.provider || message.api !== model.api || message.model !== model.id) continue;
57
+ for (const block of message.content as InternalAssistantContent[]) {
58
+ if (block.type !== "text") continue;
59
+ const item = decodeWebSearchActivityTextSignature(block.textSignature);
60
+ if (!item) continue;
61
+ for (const source of extractWebSearchCitationSources(item)) byRef.set(source.refId, source);
62
+ }
63
+ }
64
+ return [...byRef.values()];
65
+ }
66
+
67
+ export function collectHistoricalCitationSources<TApi extends Api>(
68
+ model: Model<TApi>,
69
+ context: Context,
70
+ ): CitationSource[] {
71
+ let latestSources: CitationSource[] = [];
72
+ for (const message of context.messages) {
73
+ if (message.role !== "assistant") continue;
74
+ if (message.provider !== model.provider || message.api !== model.api || message.model !== model.id) continue;
75
+ const messageSources: CitationSource[] = [];
76
+ const seenUrls = new Set<string>();
77
+ const pushSource = (source: CitationSource): void => {
78
+ if (!source.url || seenUrls.has(source.url)) return;
79
+ seenUrls.add(source.url);
80
+ messageSources.push(source);
81
+ };
82
+ for (const block of message.content as InternalAssistantContent[]) {
83
+ if (block.type !== "text" || isWebSearchActivityTextSignature(block.textSignature)) continue;
84
+ const markdownLinkPattern = /\[([^\]\n]+)\]\((https?:\/\/[^)\s]+)\)/g;
85
+ for (const match of block.text.matchAll(markdownLinkPattern)) {
86
+ pushSource({ title: match[1], url: match[2] });
87
+ }
88
+ const replayItem = parseTextSignature(block.textSignature)?.item;
89
+ for (const part of replayItem?.content ?? []) {
90
+ if (part.type !== "output_text") continue;
91
+ for (const annotation of part.annotations) {
92
+ const url = annotation.type === "url_citation" && typeof annotation.url === "string"
93
+ ? annotation.url.trim()
94
+ : "";
95
+ const title = typeof annotation.title === "string" && annotation.title.trim()
96
+ ? annotation.title.trim()
97
+ : undefined;
98
+ if (url) pushSource({ url, ...(title ? { title } : {}) });
99
+ }
100
+ }
101
+ }
102
+ if (messageSources.length > 0) latestSources = messageSources;
103
+ }
104
+ return latestSources;
105
+ }
@@ -0,0 +1,144 @@
1
+ import { type Api, type Context, type Model } from "@earendil-works/pi-ai";
2
+ import { isImageGenerationCallBlock } from "./items.js";
3
+ import { isWebSearchActivityTextSignature } from "./signatures.js";
4
+ import { type InternalAssistantContent, type Message } from "./types.js";
5
+
6
+ const NON_VISION_USER_IMAGE_PLACEHOLDER = "(image omitted: model does not support images)";
7
+
8
+ const NON_VISION_TOOL_IMAGE_PLACEHOLDER = "(tool image omitted: model does not support images)";
9
+
10
+ function replaceImagesWithPlaceholder(
11
+ content: Extract<Message, { role: "user" }> extends { content: infer T } ? Exclude<T, string> : never,
12
+ placeholder: string,
13
+ ) {
14
+ const result: Array<{ type: "text"; text: string } | { type: "image"; data: string; mimeType: string }> = [];
15
+ let previousWasPlaceholder = false;
16
+ for (const block of content) {
17
+ if (block.type === "image") {
18
+ if (!previousWasPlaceholder) {
19
+ result.push({ type: "text", text: placeholder });
20
+ }
21
+ previousWasPlaceholder = true;
22
+ continue;
23
+ }
24
+ result.push(block);
25
+ previousWasPlaceholder = block.text === placeholder;
26
+ }
27
+ return result;
28
+ }
29
+
30
+ function downgradeUnsupportedImages(messages: Context["messages"], model: Model<Api>): Context["messages"] {
31
+ if (model.input.includes("image")) return messages;
32
+ return messages.map((msg) => {
33
+ if (msg.role === "user" && Array.isArray(msg.content)) {
34
+ return { ...msg, content: replaceImagesWithPlaceholder(msg.content, NON_VISION_USER_IMAGE_PLACEHOLDER) };
35
+ }
36
+ if (msg.role === "toolResult") {
37
+ return { ...msg, content: replaceImagesWithPlaceholder(msg.content, NON_VISION_TOOL_IMAGE_PLACEHOLDER) };
38
+ }
39
+ return msg;
40
+ });
41
+ }
42
+
43
+ export function transformMessages(
44
+ messages: Context["messages"],
45
+ model: Model<Api>,
46
+ normalizeToolCallId?: (id: string, targetModel: Model<Api>, source: Extract<Message, { role: "assistant" }>) => string,
47
+ ): Context["messages"] {
48
+ const toolCallIdMap = new Map<string, string>();
49
+ const imageAwareMessages = downgradeUnsupportedImages(messages, model);
50
+ const transformed = imageAwareMessages.map((msg) => {
51
+ if (msg.role === "user") return msg;
52
+ if (msg.role === "toolResult") {
53
+ const normalizedId = toolCallIdMap.get(msg.toolCallId);
54
+ return normalizedId && normalizedId !== msg.toolCallId ? { ...msg, toolCallId: normalizedId } : msg;
55
+ }
56
+ if (msg.role === "assistant") {
57
+ const assistantMsg = msg;
58
+ const isSameModel =
59
+ assistantMsg.provider === model.provider && assistantMsg.api === model.api && assistantMsg.model === model.id;
60
+ const transformedContent = (assistantMsg.content as InternalAssistantContent[]).flatMap((block) => {
61
+ if (isImageGenerationCallBlock(block)) return block;
62
+ if (block.type === "thinking") {
63
+ if (block.redacted) return isSameModel ? block : [];
64
+ if (isSameModel && block.thinkingSignature) return block;
65
+ if (!block.thinking || block.thinking.trim() === "") return [];
66
+ return isSameModel ? block : { type: "text" as const, text: block.thinking };
67
+ }
68
+ if (block.type === "text") {
69
+ if (isWebSearchActivityTextSignature(block.textSignature)) return block;
70
+ return isSameModel ? block : { type: "text" as const, text: block.text };
71
+ }
72
+ if (block.type === "toolCall") {
73
+ let normalizedToolCall = block;
74
+ if (!isSameModel && block.thoughtSignature) {
75
+ normalizedToolCall = { ...block };
76
+ delete normalizedToolCall.thoughtSignature;
77
+ }
78
+ if (!isSameModel && normalizeToolCallId) {
79
+ const normalizedId = normalizeToolCallId(block.id, model, assistantMsg);
80
+ if (normalizedId !== block.id) {
81
+ toolCallIdMap.set(block.id, normalizedId);
82
+ normalizedToolCall = { ...normalizedToolCall, id: normalizedId };
83
+ }
84
+ }
85
+ return normalizedToolCall;
86
+ }
87
+ return block;
88
+ });
89
+ return { ...assistantMsg, content: transformedContent as Extract<Message, { role: "assistant" }>["content"] };
90
+ }
91
+ return msg;
92
+ });
93
+
94
+ const result: Context["messages"] = [];
95
+ let pendingToolCalls: Array<Extract<Extract<Message, { role: "assistant" }>["content"][number], { type: "toolCall" }>> = [];
96
+ let existingToolResultIds = new Set<string>();
97
+
98
+ const insertSyntheticToolResults = () => {
99
+ if (pendingToolCalls.length === 0) return;
100
+ for (const toolCall of pendingToolCalls) {
101
+ if (!existingToolResultIds.has(toolCall.id)) {
102
+ result.push({
103
+ role: "toolResult",
104
+ toolCallId: toolCall.id,
105
+ toolName: toolCall.name,
106
+ content: [{ type: "text", text: "No result provided" }],
107
+ isError: true,
108
+ timestamp: Date.now(),
109
+ });
110
+ }
111
+ }
112
+ pendingToolCalls = [];
113
+ existingToolResultIds = new Set();
114
+ };
115
+
116
+ for (const msg of transformed) {
117
+ if (msg.role === "assistant") {
118
+ insertSyntheticToolResults();
119
+ if (msg.stopReason === "error" || msg.stopReason === "aborted") continue;
120
+ const toolCalls = msg.content.filter((block) => block.type === "toolCall");
121
+ if (toolCalls.length > 0) {
122
+ pendingToolCalls = toolCalls;
123
+ existingToolResultIds = new Set();
124
+ }
125
+ result.push(msg);
126
+ continue;
127
+ }
128
+ if (msg.role === "toolResult") {
129
+ existingToolResultIds.add(msg.toolCallId);
130
+ result.push(msg);
131
+ continue;
132
+ }
133
+ if (msg.role === "user") {
134
+ insertSyntheticToolResults();
135
+ result.push(msg);
136
+ continue;
137
+ }
138
+ result.push(msg);
139
+ }
140
+
141
+ insertSyntheticToolResults();
142
+
143
+ return result;
144
+ }
@@ -0,0 +1,87 @@
1
+ import { type ImageGenerationCallBlock, type ImageGenerationCallItem, type InternalAssistantContent, type ReplayableResponseMessageContent, type ReplayableResponseMessageItem, type ReplayableWebSearchCallItem } from "./types.js";
2
+
3
+ export function cloneJsonRecord(value: unknown): Record<string, unknown> | undefined {
4
+ if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
5
+ try {
6
+ const cloned = JSON.parse(JSON.stringify(value)) as unknown;
7
+ return cloned && typeof cloned === "object" && !Array.isArray(cloned)
8
+ ? cloned as Record<string, unknown>
9
+ : undefined;
10
+ } catch {
11
+ return undefined;
12
+ }
13
+ }
14
+
15
+ export function sanitizeWebSearchCallItem(item: unknown): ReplayableWebSearchCallItem | undefined {
16
+ if (!item || typeof item !== "object") return undefined;
17
+ const candidate = item as Record<string, unknown>;
18
+ if (candidate.type !== "web_search_call") return undefined;
19
+ if (typeof candidate.id !== "string" || candidate.id === "") return undefined;
20
+ if (candidate.status !== "completed") return undefined;
21
+ const action = cloneJsonRecord(candidate.action);
22
+ if (!action) return undefined;
23
+ const results = Array.isArray(candidate.results)
24
+ ? candidate.results.map(cloneJsonRecord).filter((result): result is Record<string, unknown> => !!result)
25
+ : undefined;
26
+
27
+ return {
28
+ type: "web_search_call",
29
+ id: candidate.id,
30
+ status: "completed",
31
+ action,
32
+ ...(results ? { results } : {}),
33
+ };
34
+ }
35
+
36
+ export function sanitizeResponseMessageItem(item: unknown): ReplayableResponseMessageItem | undefined {
37
+ if (!item || typeof item !== "object") return undefined;
38
+ const candidate = item as Record<string, unknown>;
39
+ if (candidate.type !== "message") return undefined;
40
+ if (typeof candidate.id !== "string" || candidate.id === "") return undefined;
41
+ const rawContent = Array.isArray(candidate.content) ? candidate.content : [];
42
+ const content: ReplayableResponseMessageContent[] = [];
43
+ for (const rawPart of rawContent) {
44
+ if (!rawPart || typeof rawPart !== "object") continue;
45
+ const part = rawPart as Record<string, unknown>;
46
+ if (part.type === "output_text" && typeof part.text === "string") {
47
+ const annotations = Array.isArray(part.annotations)
48
+ ? part.annotations.map(cloneJsonRecord).filter((annotation): annotation is Record<string, unknown> => !!annotation)
49
+ : [];
50
+ content.push({ type: "output_text", text: part.text, annotations });
51
+ } else if (part.type === "refusal" && typeof part.refusal === "string") {
52
+ content.push({ type: "refusal", refusal: part.refusal });
53
+ }
54
+ }
55
+ const phase = candidate.phase === "commentary" || candidate.phase === "final_answer"
56
+ ? candidate.phase
57
+ : undefined;
58
+ return {
59
+ type: "message",
60
+ id: candidate.id,
61
+ role: "assistant",
62
+ status: "completed",
63
+ content,
64
+ ...(phase ? { phase } : {}),
65
+ };
66
+ }
67
+
68
+ export function isImageGenerationCallBlock(block: InternalAssistantContent): block is ImageGenerationCallBlock {
69
+ return block.type === "image_generation_call" && block.item?.type === "image_generation_call";
70
+ }
71
+
72
+ export function sanitizeImageGenerationCallItem(item: unknown): ImageGenerationCallItem | undefined {
73
+ if (!item || typeof item !== "object") return undefined;
74
+ const candidate = item as Record<string, unknown>;
75
+ if (candidate.type !== "image_generation_call") return undefined;
76
+ if (typeof candidate.id !== "string" || candidate.id === "") return undefined;
77
+ if (typeof candidate.status !== "string" || candidate.status === "") return undefined;
78
+ if (!(typeof candidate.result === "string" || candidate.result === null)) return undefined;
79
+
80
+ return {
81
+ type: "image_generation_call",
82
+ id: candidate.id,
83
+ status: candidate.status,
84
+ result: candidate.result,
85
+ ...(typeof candidate.revised_prompt === "string" ? { revised_prompt: candidate.revised_prompt } : {}),
86
+ };
87
+ }
@@ -0,0 +1,92 @@
1
+ interface MarkdownCodeRange {
2
+ start: number;
3
+ end: number;
4
+ }
5
+
6
+ function characterRunLength(text: string, index: number, character: string): number {
7
+ let end = index;
8
+ while (end < text.length && text[end] === character) end++;
9
+ return end - index;
10
+ }
11
+
12
+ function isFencePosition(text: string, index: number): boolean {
13
+ const lineStart = text.lastIndexOf("\n", index - 1) + 1;
14
+ return /^ {0,3}$/.test(text.slice(lineStart, index));
15
+ }
16
+
17
+ export function markdownCodeRanges(text: string): MarkdownCodeRange[] {
18
+ const ranges: MarkdownCodeRange[] = [];
19
+ let fence: { character: "`" | "~"; length: number; start: number } | undefined;
20
+ let index = 0;
21
+ while (index < text.length) {
22
+ const character = text[index];
23
+ if (fence) {
24
+ if (character === fence.character && isFencePosition(text, index)) {
25
+ const runLength = characterRunLength(text, index, character);
26
+ if (runLength >= fence.length) {
27
+ ranges.push({ start: fence.start, end: index + runLength });
28
+ fence = undefined;
29
+ index += runLength;
30
+ continue;
31
+ }
32
+ }
33
+ index++;
34
+ continue;
35
+ }
36
+
37
+ if ((character === "`" || character === "~") && isFencePosition(text, index)) {
38
+ const runLength = characterRunLength(text, index, character);
39
+ if (runLength >= 3) {
40
+ fence = { character, length: runLength, start: index };
41
+ index += runLength;
42
+ continue;
43
+ }
44
+ }
45
+
46
+ if (character === "`") {
47
+ const runLength = characterRunLength(text, index, character);
48
+ const delimiter = character.repeat(runLength);
49
+ const close = text.indexOf(delimiter, index + runLength);
50
+ if (close < 0) {
51
+ ranges.push({ start: index, end: text.length });
52
+ break;
53
+ }
54
+ ranges.push({ start: index, end: close + runLength });
55
+ index = close + runLength;
56
+ continue;
57
+ }
58
+ index++;
59
+ }
60
+ if (fence) ranges.push({ start: fence.start, end: text.length });
61
+ return ranges;
62
+ }
63
+
64
+ export function isInsideMarkdownCode(index: number, ranges: MarkdownCodeRange[]): boolean {
65
+ return ranges.some((range) => index >= range.start && index < range.end);
66
+ }
67
+
68
+ export function trailingCitationFragmentStart(text: string, ranges: MarkdownCodeRange[]): number | undefined {
69
+ const opener = "cite";
70
+ const openIndex = text.lastIndexOf("cite");
71
+ if (openIndex >= 0 && text.indexOf("", openIndex) < 0 && !isInsideMarkdownCode(openIndex, ranges)) {
72
+ return openIndex;
73
+ }
74
+ for (let length = Math.min(opener.length - 1, text.length); length > 0; length--) {
75
+ if (!text.endsWith(opener.slice(0, length))) continue;
76
+ const start = text.length - length;
77
+ if (!isInsideMarkdownCode(start, ranges)) return start;
78
+ }
79
+ return undefined;
80
+ }
81
+
82
+ export function trailingIndexedSourceFragmentStart(text: string, ranges: MarkdownCodeRange[]): number | undefined {
83
+ const openIndex = text.lastIndexOf("【");
84
+ if (openIndex < 0 || text.indexOf("】", openIndex) >= 0 || isInsideMarkdownCode(openIndex, ranges)) {
85
+ return undefined;
86
+ }
87
+ const fragment = text.slice(openIndex);
88
+ const match = /^【\d*(?:†([a-z]*))?$/i.exec(fragment);
89
+ if (!match) return undefined;
90
+ const sourceFragment = match[1]?.toLowerCase() ?? "";
91
+ return "source".startsWith(sourceFragment) ? openIndex : undefined;
92
+ }
@@ -0,0 +1,169 @@
1
+ import { type Api, type Context, type Model } from "@earendil-works/pi-ai";
2
+ import { type ResponseInput } from "openai/resources/responses/responses.js";
3
+ import { transformMessages } from "./history.js";
4
+ import { isImageGenerationCallBlock, sanitizeImageGenerationCallItem } from "./items.js";
5
+ import { decodeWebSearchActivityTextSignature, isWebSearchActivityTextSignature, parseTextSignature } from "./signatures.js";
6
+ import { sanitizeSurrogates, shortHash } from "./text.js";
7
+ import { wireToolIdentity } from "./tool-identity.js";
8
+ import { type ConvertResponsesMessagesOptions, type InternalAssistantContent, type Message } from "./types.js";
9
+
10
+ export function convertResponsesMessages<TApi extends Api>(
11
+ model: Model<TApi>,
12
+ context: Context,
13
+ allowedToolCallProviders: ReadonlySet<string>,
14
+ options?: ConvertResponsesMessagesOptions,
15
+ ): ResponseInput {
16
+ const messages: ResponseInput = [];
17
+ const normalizeIdPart = (part: string) => {
18
+ const sanitized = part.replace(/[^a-zA-Z0-9_-]/g, "_");
19
+ const normalized = sanitized.length > 64 ? sanitized.slice(0, 64) : sanitized;
20
+ return normalized.replace(/_+$/, "");
21
+ };
22
+ const buildForeignResponsesItemId = (itemId: string, prefix: "fc_" | "ctc_") => {
23
+ const normalized = `${prefix}${shortHash(itemId)}`;
24
+ return normalized.length > 64 ? normalized.slice(0, 64) : normalized;
25
+ };
26
+ const normalizeToolCallId = (id: string, _targetModel: Model<TApi>, source: Extract<Message, { role: "assistant" }>) => {
27
+ if (!allowedToolCallProviders.has(model.provider)) return normalizeIdPart(id);
28
+ if (!id.includes("|")) return normalizeIdPart(id);
29
+ const [callId, itemId] = id.split("|");
30
+ const normalizedCallId = normalizeIdPart(callId);
31
+ const isForeignToolCall = source.provider !== model.provider || source.api !== model.api;
32
+ const itemPrefix = itemId?.startsWith("ctc_") ? "ctc_" : "fc_";
33
+ let normalizedItemId = isForeignToolCall ? buildForeignResponsesItemId(itemId ?? "", itemPrefix) : normalizeIdPart(itemId ?? "");
34
+ if (!normalizedItemId.startsWith(itemPrefix)) normalizedItemId = normalizeIdPart(`${itemPrefix}${normalizedItemId}`);
35
+ return `${normalizedCallId}|${normalizedItemId}`;
36
+ };
37
+
38
+ const transformedMessages = transformMessages(context.messages, model as Model<Api>, normalizeToolCallId as never);
39
+ const includeSystemPrompt = options?.includeSystemPrompt ?? true;
40
+ if (includeSystemPrompt && context.systemPrompt) {
41
+ messages.push({ role: model.reasoning ? "developer" : "system", content: sanitizeSurrogates(context.systemPrompt) });
42
+ }
43
+
44
+ let msgIndex = 0;
45
+ for (const msg of transformedMessages) {
46
+ if (msg.role === "user") {
47
+ if (typeof msg.content === "string") {
48
+ messages.push({ role: "user", content: [{ type: "input_text", text: sanitizeSurrogates(msg.content) }] });
49
+ } else {
50
+ const content = msg.content.map((item) =>
51
+ item.type === "text"
52
+ ? { type: "input_text" as const, text: sanitizeSurrogates(item.text) }
53
+ : { type: "input_image" as const, detail: "auto" as const, image_url: `data:${item.mimeType};base64,${item.data}` },
54
+ );
55
+ if (content.length > 0) messages.push({ role: "user", content });
56
+ }
57
+ } else if (msg.role === "assistant") {
58
+ const output: ResponseInput = [];
59
+ const isSameModel = msg.model === model.id && msg.provider === model.provider && msg.api === model.api;
60
+ const isDifferentModel = msg.model !== model.id && msg.provider === model.provider && msg.api === model.api;
61
+ let assistantBlockIndex = 0;
62
+ for (const block of msg.content as InternalAssistantContent[]) {
63
+ if (isImageGenerationCallBlock(block)) {
64
+ const imageGenerationCall = sanitizeImageGenerationCallItem(block.item);
65
+ if (imageGenerationCall) output.push(imageGenerationCall as ResponseInput[number]);
66
+ } else if (block.type === "thinking") {
67
+ if (block.thinkingSignature) output.push(JSON.parse(block.thinkingSignature));
68
+ } else if (block.type === "text") {
69
+ if (isWebSearchActivityTextSignature(block.textSignature)) {
70
+ const webSearchItem = isSameModel
71
+ ? decodeWebSearchActivityTextSignature(block.textSignature)
72
+ : undefined;
73
+ if (webSearchItem) output.push(webSearchItem as unknown as ResponseInput[number]);
74
+ continue;
75
+ }
76
+ const parsedSignature = parseTextSignature(block.textSignature);
77
+ if (isSameModel && parsedSignature?.item) {
78
+ output.push(parsedSignature.item as ResponseInput[number]);
79
+ assistantBlockIndex++;
80
+ continue;
81
+ }
82
+ let msgId = parsedSignature?.id ?? `msg_${msgIndex}_${assistantBlockIndex}`;
83
+ if (msgId.length > 64) msgId = `msg_${shortHash(msgId)}`;
84
+ output.push({
85
+ type: "message",
86
+ role: "assistant",
87
+ content: [{ type: "output_text", text: sanitizeSurrogates(block.text), annotations: [] }],
88
+ status: "completed",
89
+ id: msgId,
90
+ ...(parsedSignature?.phase ? { phase: parsedSignature.phase } : {}),
91
+ });
92
+ assistantBlockIndex++;
93
+ } else if (block.type === "toolCall") {
94
+ const [callId, itemIdRaw] = block.id.split("|");
95
+ const custom = itemIdRaw?.startsWith("ctc_") === true;
96
+ let itemId: string | undefined = itemIdRaw;
97
+ if (isDifferentModel && (itemId?.startsWith("fc_") || itemId?.startsWith("ctc_"))) itemId = undefined;
98
+ const wireIdentity = wireToolIdentity(block.name, block.thoughtSignature);
99
+ if (custom) {
100
+ output.push({
101
+ type: "custom_tool_call",
102
+ ...(itemId ? { id: itemId } : {}),
103
+ call_id: callId,
104
+ name: wireIdentity.name,
105
+ ...(wireIdentity.namespace ? { namespace: wireIdentity.namespace } : {}),
106
+ input: typeof block.arguments.input === "string" ? block.arguments.input : "",
107
+ } as ResponseInput[number]);
108
+ } else {
109
+ output.push({
110
+ type: "function_call",
111
+ ...(itemId ? { id: itemId } : {}),
112
+ call_id: callId,
113
+ name: wireIdentity.name,
114
+ ...(wireIdentity.namespace ? { namespace: wireIdentity.namespace } : {}),
115
+ arguments: JSON.stringify(block.arguments),
116
+ } as ResponseInput[number]);
117
+ }
118
+ }
119
+ }
120
+ if (output.length > 0) messages.push(...output);
121
+ } else if (msg.role === "toolResult") {
122
+ const textResult = msg.content.filter((c) => c.type === "text").map((c) => c.text).join("\n");
123
+ const hasImages = msg.content.some((c) => c.type === "image");
124
+ const hasText = textResult.length > 0;
125
+ const [callId, itemId] = msg.toolCallId.split("|");
126
+ const supportsToolResultImages = hasImages && model.input.includes("image");
127
+ let originalCallUsesContentItems = false;
128
+ for (let index = msgIndex - 1; index >= 0; index--) {
129
+ const previous = context.messages[index];
130
+ if (previous?.role !== "assistant") continue;
131
+ const originalCall = previous.content.find((block) =>
132
+ block.type === "toolCall" && block.id.split("|")[0] === callId);
133
+ if (originalCall?.type === "toolCall") {
134
+ originalCallUsesContentItems = wireToolIdentity(
135
+ originalCall.name,
136
+ originalCall.thoughtSignature,
137
+ ).namespace === "web";
138
+ break;
139
+ }
140
+ }
141
+ const usesContentItems = supportsToolResultImages || originalCallUsesContentItems;
142
+ const output = usesContentItems
143
+ ? [
144
+ ...(hasText ? [{ type: "input_text" as const, text: sanitizeSurrogates(textResult) }] : []),
145
+ ...(supportsToolResultImages
146
+ ? msg.content
147
+ .filter((block) => block.type === "image")
148
+ .map((block) => ({
149
+ type: "input_image" as const,
150
+ detail: "auto" as const,
151
+ image_url: `data:${block.mimeType};base64,${block.data}`,
152
+ }))
153
+ : []),
154
+ ...(!hasText && !supportsToolResultImages
155
+ ? [{ type: "input_text" as const, text: "(see attached image)" }]
156
+ : []),
157
+ ]
158
+ : sanitizeSurrogates(hasText ? textResult : "(see attached image)");
159
+ messages.push({
160
+ type: itemId?.startsWith("ctc_") ? "custom_tool_call_output" : "function_call_output",
161
+ call_id: callId,
162
+ output,
163
+ } as ResponseInput[number]);
164
+ }
165
+ msgIndex++;
166
+ }
167
+
168
+ return messages;
169
+ }