@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.
- package/LICENSE +28 -0
- package/LICENSES/Apache-2.0.txt +201 -0
- package/LICENSES/OpenAI-Codex-NOTICE.txt +6 -0
- package/README.md +26 -0
- package/THIRD_PARTY_NOTICES.md +19 -0
- package/config.schema.json +174 -0
- package/models.schema.json +217 -0
- package/package.json +68 -0
- package/provenance/openai-codex-eb9dceba-reserved-tools.json +140 -0
- package/src/activation.ts +56 -0
- package/src/broker.ts +133 -0
- package/src/capabilities.ts +146 -0
- package/src/codex-http.ts +133 -0
- package/src/codex-identity-extension.ts +341 -0
- package/src/codex-request-profile.ts +45 -0
- package/src/codex-reserved-tools.ts +33 -0
- package/src/codex-wire-identity.ts +596 -0
- package/src/extension/provider-presentation.ts +11 -0
- package/src/glyphs.ts +70 -0
- package/src/index.ts +4 -0
- package/src/model-catalog/catalog.ts +636 -0
- package/src/model-catalog/default-models.json +252 -0
- package/src/model-catalog/runtime.ts +113 -0
- package/src/model-catalog/types.ts +95 -0
- package/src/provider-headers.ts +54 -0
- package/src/providers/openai-codex/stream-effects.ts +21 -0
- package/src/providers/openai-codex/types.ts +155 -0
- package/src/providers/responses/citations.ts +105 -0
- package/src/providers/responses/history.ts +144 -0
- package/src/providers/responses/items.ts +87 -0
- package/src/providers/responses/markdown.ts +92 -0
- package/src/providers/responses/messages.ts +169 -0
- package/src/providers/responses/signatures.ts +102 -0
- package/src/providers/responses/stream-state.ts +99 -0
- package/src/providers/responses/stream.ts +386 -0
- package/src/providers/responses/text-renderer.ts +147 -0
- package/src/providers/responses/text.ts +32 -0
- package/src/providers/responses/tool-identity.ts +25 -0
- package/src/providers/responses/tools.ts +14 -0
- package/src/providers/responses/types.ts +113 -0
- package/src/providers/responses/usage.ts +53 -0
- package/src/reserved-tools/image-generation.ts +53 -0
- package/src/reserved-tools/types.ts +32 -0
- package/src/reserved-tools/web-search.ts +269 -0
- package/src/session-claims.ts +19 -0
- package/src/settings.ts +247 -0
- package/src/subagent-inline.ts +8 -0
- package/src/tool-activation.ts +89 -0
- package/src/utils/theme.ts +7 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { INTERNAL_CITATION_MARKER } from "./citations.js";
|
|
2
|
+
import { isInsideMarkdownCode, markdownCodeRanges, trailingCitationFragmentStart, trailingIndexedSourceFragmentStart } from "./markdown.js";
|
|
3
|
+
import type { CitationSource, MessagePartState, WebSearchCitationSource } from "./types.js";
|
|
4
|
+
|
|
5
|
+
export function createResponseTextRenderer(
|
|
6
|
+
webSearchCitationSources: ReadonlyMap<string, WebSearchCitationSource>,
|
|
7
|
+
historicalCitationSources: ReadonlyArray<CitationSource>,
|
|
8
|
+
) {
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
const annotationUrl = (annotation: unknown): string | undefined => {
|
|
12
|
+
const candidate = annotation && typeof annotation === "object" ? annotation as Record<string, unknown> : undefined;
|
|
13
|
+
return candidate?.type === "url_citation" && typeof candidate.url === "string" && candidate.url.trim() ? candidate.url.trim() : undefined;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const annotationTitle = (annotation: unknown): string | undefined => {
|
|
17
|
+
const candidate = annotation && typeof annotation === "object" ? annotation as Record<string, unknown> : undefined;
|
|
18
|
+
return typeof candidate?.title === "string" && candidate.title.trim() ? candidate.title.trim() : undefined;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const markdownLinkText = (value: string): string => value.replace(/\\/g, "\\\\").replace(/]/g, "\\]").replace(/\n+/g, " ");
|
|
22
|
+
|
|
23
|
+
const markdownLinkUrl = (value: string): string => value.replace(/[)\s]/g, (char) => encodeURIComponent(char));
|
|
24
|
+
|
|
25
|
+
const containsMarkdownLink = (value: string): boolean => /\[[^\]\n]+\]\([^) \n]+\)/.test(value);
|
|
26
|
+
|
|
27
|
+
const citationSourceLabel = (source: CitationSource): string => {
|
|
28
|
+
try {
|
|
29
|
+
const hostname = new URL(source.url).hostname.replace(/^www\./, "");
|
|
30
|
+
if (hostname) return hostname;
|
|
31
|
+
} catch {
|
|
32
|
+
// Fall back to the result title or URL.
|
|
33
|
+
}
|
|
34
|
+
return source.title ?? source.url;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const citationSourceMarkdown = (source: CitationSource): string =>
|
|
38
|
+
`[${markdownLinkText(citationSourceLabel(source))}](${markdownLinkUrl(source.url)})`;
|
|
39
|
+
|
|
40
|
+
const renderInternalCitationMarkers = (text: string): string => {
|
|
41
|
+
const initialCodeRanges = markdownCodeRanges(text);
|
|
42
|
+
const fragmentStart = trailingCitationFragmentStart(text, initialCodeRanges);
|
|
43
|
+
const visibleText = fragmentStart === undefined ? text : text.slice(0, fragmentStart);
|
|
44
|
+
const codeRanges = fragmentStart === undefined ? initialCodeRanges : markdownCodeRanges(visibleText);
|
|
45
|
+
const markerPattern = new RegExp(INTERNAL_CITATION_MARKER.source, "g");
|
|
46
|
+
let cursor = 0;
|
|
47
|
+
let rendered = "";
|
|
48
|
+
for (const match of visibleText.matchAll(markerPattern)) {
|
|
49
|
+
const start = match.index ?? 0;
|
|
50
|
+
if (isInsideMarkdownCode(start, codeRanges)) continue;
|
|
51
|
+
rendered += visibleText.slice(cursor, start);
|
|
52
|
+
const seenUrls = new Set<string>();
|
|
53
|
+
const links: string[] = [];
|
|
54
|
+
for (const rawRef of match[1]?.split("") ?? []) {
|
|
55
|
+
const source = webSearchCitationSources.get(rawRef.trim());
|
|
56
|
+
if (!source || seenUrls.has(source.url)) continue;
|
|
57
|
+
seenUrls.add(source.url);
|
|
58
|
+
links.push(citationSourceMarkdown(source));
|
|
59
|
+
}
|
|
60
|
+
rendered += links.length > 0 ? `(${links.join(", ")})` : "";
|
|
61
|
+
cursor = start + match[0].length;
|
|
62
|
+
}
|
|
63
|
+
if (cursor === 0) return visibleText;
|
|
64
|
+
return rendered + visibleText.slice(cursor);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const renderIndexedSourceMarkers = (text: string): string => {
|
|
68
|
+
const initialCodeRanges = markdownCodeRanges(text);
|
|
69
|
+
const fragmentStart = trailingIndexedSourceFragmentStart(text, initialCodeRanges);
|
|
70
|
+
const visibleText = fragmentStart === undefined ? text : text.slice(0, fragmentStart);
|
|
71
|
+
const codeRanges = fragmentStart === undefined ? initialCodeRanges : markdownCodeRanges(visibleText);
|
|
72
|
+
const markerPattern = /【(\d+)†source】/gi;
|
|
73
|
+
let cursor = 0;
|
|
74
|
+
let rendered = "";
|
|
75
|
+
for (const match of visibleText.matchAll(markerPattern)) {
|
|
76
|
+
const start = match.index ?? 0;
|
|
77
|
+
if (isInsideMarkdownCode(start, codeRanges)) continue;
|
|
78
|
+
rendered += visibleText.slice(cursor, start);
|
|
79
|
+
const followingText = visibleText.slice(start + match[0].length);
|
|
80
|
+
const alreadyLinked = /^\s*(?:\(\s*)?\[[^\]\n]+\]\(https?:\/\/[^)\s]+\)/.test(followingText);
|
|
81
|
+
const sourceIndex = Number.parseInt(match[1] ?? "", 10);
|
|
82
|
+
const source = Number.isInteger(sourceIndex) ? historicalCitationSources[sourceIndex] : undefined;
|
|
83
|
+
rendered += alreadyLinked || !source ? "" : `(${citationSourceMarkdown(source)})`;
|
|
84
|
+
cursor = start + match[0].length;
|
|
85
|
+
}
|
|
86
|
+
if (cursor === 0) return visibleText;
|
|
87
|
+
return rendered + visibleText.slice(cursor);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const sourceMarkdown = (annotations: unknown[], citedUrls: Set<string>): string => {
|
|
91
|
+
const seen = new Set<string>();
|
|
92
|
+
const links: string[] = [];
|
|
93
|
+
for (const annotation of annotations) {
|
|
94
|
+
const url = annotationUrl(annotation);
|
|
95
|
+
if (!url || citedUrls.has(url) || seen.has(url)) continue;
|
|
96
|
+
seen.add(url);
|
|
97
|
+
links.push(`[${markdownLinkText(annotationTitle(annotation) ?? url)}](${markdownLinkUrl(url)})`);
|
|
98
|
+
}
|
|
99
|
+
return links.length > 0 ? `\n\nSources: ${links.join(", ")}` : "";
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const renderOutputTextPart = (part: MessagePartState): string => {
|
|
103
|
+
const annotations = part.annotations ?? [];
|
|
104
|
+
if (part.type !== "output_text" || annotations.length === 0) return part.text;
|
|
105
|
+
const spans = annotations
|
|
106
|
+
.map((annotation) => {
|
|
107
|
+
const candidate = annotation && typeof annotation === "object" ? annotation as Record<string, unknown> : undefined;
|
|
108
|
+
const url = annotationUrl(annotation);
|
|
109
|
+
const start = candidate?.start_index;
|
|
110
|
+
const end = candidate?.end_index;
|
|
111
|
+
return typeof start === "number" && typeof end === "number" && url && Number.isInteger(start) && Number.isInteger(end) && start >= 0 && end > start && end <= part.text.length
|
|
112
|
+
? { start, end, url }
|
|
113
|
+
: undefined;
|
|
114
|
+
})
|
|
115
|
+
.filter((span): span is { start: number; end: number; url: string } => !!span)
|
|
116
|
+
.sort((a, b) => a.start - b.start || a.end - b.end);
|
|
117
|
+
const citedUrls = new Set<string>();
|
|
118
|
+
let cursor = 0;
|
|
119
|
+
let rendered = "";
|
|
120
|
+
for (const span of spans) {
|
|
121
|
+
if (span.start < cursor) continue;
|
|
122
|
+
rendered += part.text.slice(cursor, span.start);
|
|
123
|
+
const label = part.text.slice(span.start, span.end);
|
|
124
|
+
rendered += containsMarkdownLink(label)
|
|
125
|
+
? label
|
|
126
|
+
: `[${markdownLinkText(label)}](${markdownLinkUrl(span.url)})`;
|
|
127
|
+
citedUrls.add(span.url);
|
|
128
|
+
cursor = span.end;
|
|
129
|
+
}
|
|
130
|
+
if (cursor === 0) return part.text + sourceMarkdown(annotations, citedUrls);
|
|
131
|
+
rendered += part.text.slice(cursor);
|
|
132
|
+
return rendered + sourceMarkdown(annotations, citedUrls);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const renderMessageText = (parts: Map<number, MessagePartState>, citations = false): string =>
|
|
136
|
+
Array.from(parts.entries())
|
|
137
|
+
.sort(([a], [b]) => a - b)
|
|
138
|
+
.map(([, part]) => {
|
|
139
|
+
const text = citations ? renderOutputTextPart(part) : part.text;
|
|
140
|
+
return part.type === "output_text"
|
|
141
|
+
? renderIndexedSourceMarkers(renderInternalCitationMarkers(text))
|
|
142
|
+
: text;
|
|
143
|
+
})
|
|
144
|
+
.join("");
|
|
145
|
+
|
|
146
|
+
return { renderMessageText };
|
|
147
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export function shortHash(str: string): string {
|
|
2
|
+
let h1 = 0xdeadbeef;
|
|
3
|
+
let h2 = 0x41c6ce57;
|
|
4
|
+
for (let i = 0; i < str.length; i++) {
|
|
5
|
+
const ch = str.charCodeAt(i);
|
|
6
|
+
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
7
|
+
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
8
|
+
}
|
|
9
|
+
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
|
10
|
+
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
|
11
|
+
return (h2 >>> 0).toString(36) + (h1 >>> 0).toString(36);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function parseStreamingJson(partialJson: string): Record<string, unknown> {
|
|
15
|
+
if (!partialJson || partialJson.trim() === "") return {};
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(partialJson) as Record<string, unknown>;
|
|
18
|
+
} catch {
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function sanitizeSurrogates(text: string): string {
|
|
24
|
+
return text.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function localToolName(namespace: unknown, name: string): string {
|
|
28
|
+
if (typeof namespace !== "string" || namespace === "" || namespace === "functions") return name;
|
|
29
|
+
if (namespace === "web" && name === "run") return "web_search";
|
|
30
|
+
if (namespace === "image_gen" && name === "imagegen") return "image_generation";
|
|
31
|
+
return name;
|
|
32
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const TOOL_NAMESPACE_SIGNATURE_PREFIX = "pi:codex-tool-namespace:";
|
|
2
|
+
|
|
3
|
+
export function encodeToolNamespaceSignature(namespace: unknown, name: string): string | undefined {
|
|
4
|
+
if (typeof namespace !== "string" || !namespace) return undefined;
|
|
5
|
+
return `${TOOL_NAMESPACE_SIGNATURE_PREFIX}${JSON.stringify({ namespace, name })}`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function wireToolIdentity(
|
|
9
|
+
name: string,
|
|
10
|
+
thoughtSignature: string | undefined,
|
|
11
|
+
): { name: string; namespace?: string } {
|
|
12
|
+
if (!thoughtSignature?.startsWith(TOOL_NAMESPACE_SIGNATURE_PREFIX)) return { name };
|
|
13
|
+
try {
|
|
14
|
+
const value = JSON.parse(thoughtSignature.slice(TOOL_NAMESPACE_SIGNATURE_PREFIX.length)) as {
|
|
15
|
+
namespace?: unknown;
|
|
16
|
+
name?: unknown;
|
|
17
|
+
};
|
|
18
|
+
if (typeof value.namespace === "string" && value.namespace && typeof value.name === "string" && value.name) {
|
|
19
|
+
return { namespace: value.namespace, name: value.name };
|
|
20
|
+
}
|
|
21
|
+
} catch {
|
|
22
|
+
// Ignore malformed local metadata and replay the Pi-visible tool name.
|
|
23
|
+
}
|
|
24
|
+
return { name };
|
|
25
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Tool } from "@earendil-works/pi-ai";
|
|
2
|
+
import { type Tool as OpenAITool } from "openai/resources/responses/responses.js";
|
|
3
|
+
import { type ConvertResponsesToolsOptions } from "./types.js";
|
|
4
|
+
|
|
5
|
+
export function convertResponsesTools(tools: Tool[], options?: ConvertResponsesToolsOptions): OpenAITool[] {
|
|
6
|
+
const strict = options?.strict === undefined ? false : options.strict;
|
|
7
|
+
return tools.map((tool) => ({
|
|
8
|
+
type: "function",
|
|
9
|
+
name: tool.name,
|
|
10
|
+
description: tool.description,
|
|
11
|
+
parameters: tool.parameters as unknown as Record<string, unknown>,
|
|
12
|
+
strict,
|
|
13
|
+
}));
|
|
14
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
2
|
+
import { type Context, type Usage } from "@earendil-works/pi-ai";
|
|
3
|
+
import { type ResponseCreateParamsStreaming } from "openai/resources/responses/responses.js";
|
|
4
|
+
|
|
5
|
+
type MessageRole = Context["messages"][number]["role"];
|
|
6
|
+
|
|
7
|
+
export type Message = Context["messages"][number];
|
|
8
|
+
|
|
9
|
+
export interface ImageGenerationCallItem {
|
|
10
|
+
type: "image_generation_call";
|
|
11
|
+
id: string;
|
|
12
|
+
status: string;
|
|
13
|
+
result: string | null;
|
|
14
|
+
revised_prompt?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ImageGenerationCallBlock {
|
|
18
|
+
type: "image_generation_call";
|
|
19
|
+
item: ImageGenerationCallItem;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ReplayableWebSearchCallItem {
|
|
23
|
+
type: "web_search_call";
|
|
24
|
+
id: string;
|
|
25
|
+
status: "completed";
|
|
26
|
+
action: Record<string, unknown>;
|
|
27
|
+
results?: Array<Record<string, unknown>>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface CitationSource {
|
|
31
|
+
url: string;
|
|
32
|
+
title?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface WebSearchCitationSource extends CitationSource {
|
|
36
|
+
refId: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type ReplayableResponseMessageContent =
|
|
40
|
+
| { type: "output_text"; text: string; annotations: Array<Record<string, unknown>> }
|
|
41
|
+
| { type: "refusal"; refusal: string };
|
|
42
|
+
|
|
43
|
+
export interface ReplayableResponseMessageItem {
|
|
44
|
+
type: "message";
|
|
45
|
+
id: string;
|
|
46
|
+
role: "assistant";
|
|
47
|
+
status: "completed";
|
|
48
|
+
content: ReplayableResponseMessageContent[];
|
|
49
|
+
phase?: TextSignaturePhase;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type InternalAssistantContent = Extract<Message, { role: "assistant" }>["content"][number] | ImageGenerationCallBlock;
|
|
53
|
+
|
|
54
|
+
export interface OpenAIResponsesStreamOptions {
|
|
55
|
+
serviceTier?: ResponseCreateParamsStreaming["service_tier"];
|
|
56
|
+
resolveServiceTier?: (
|
|
57
|
+
responseServiceTier: ResponseCreateParamsStreaming["service_tier"] | undefined,
|
|
58
|
+
requestServiceTier: ResponseCreateParamsStreaming["service_tier"] | undefined,
|
|
59
|
+
) => ResponseCreateParamsStreaming["service_tier"] | undefined;
|
|
60
|
+
applyServiceTierPricing?: (usage: Usage, serviceTier: ResponseCreateParamsStreaming["service_tier"] | undefined) => void;
|
|
61
|
+
webSearchCitationSources?: ReadonlyArray<WebSearchCitationSource>;
|
|
62
|
+
historicalCitationSources?: ReadonlyArray<CitationSource>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type TextSignaturePhase = "commentary" | "final_answer";
|
|
66
|
+
|
|
67
|
+
export interface ConvertResponsesMessagesOptions {
|
|
68
|
+
includeSystemPrompt?: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ConvertResponsesToolsOptions {
|
|
72
|
+
strict?: boolean | null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type ThinkingBlock = Extract<AssistantMessage["content"][number], { type: "thinking" }>;
|
|
76
|
+
|
|
77
|
+
export type TextBlock = Extract<AssistantMessage["content"][number], { type: "text" }>;
|
|
78
|
+
|
|
79
|
+
export type ToolCallBlock = Extract<AssistantMessage["content"][number], { type: "toolCall" }> & { partialJson?: string; partialInput?: string };
|
|
80
|
+
|
|
81
|
+
export type ReasoningState = {
|
|
82
|
+
kind: "reasoning";
|
|
83
|
+
blockIndex: number;
|
|
84
|
+
block: ThinkingBlock;
|
|
85
|
+
summaryParts: Map<number, { text: string }>;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export type MessagePartState = { type: "output_text" | "refusal"; text: string; annotations?: unknown[] };
|
|
89
|
+
|
|
90
|
+
export type MessageState = {
|
|
91
|
+
kind: "message";
|
|
92
|
+
blockIndex: number;
|
|
93
|
+
block: TextBlock;
|
|
94
|
+
parts: Map<number, MessagePartState>;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export type FunctionCallState = {
|
|
98
|
+
kind: "function_call";
|
|
99
|
+
blockIndex: number;
|
|
100
|
+
block: ToolCallBlock;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export type CustomToolCallState = {
|
|
104
|
+
kind: "custom_tool_call";
|
|
105
|
+
blockIndex: number;
|
|
106
|
+
block: ToolCallBlock;
|
|
107
|
+
itemId: string;
|
|
108
|
+
sourceItemId?: string;
|
|
109
|
+
callId: string;
|
|
110
|
+
input: string;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export type OutputState = ReasoningState | MessageState | FunctionCallState | CustomToolCallState;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { calculateCost, type Api, type AssistantMessage, type Model, type Usage } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { ResponseStreamEvent } from "openai/resources/responses/responses.js";
|
|
3
|
+
import type { OpenAIResponsesStreamOptions } from "./types.js";
|
|
4
|
+
|
|
5
|
+
export function finalizeResponseUsage(
|
|
6
|
+
response: Extract<ResponseStreamEvent, { type: "response.completed" }>["response"],
|
|
7
|
+
model: Model<Api>,
|
|
8
|
+
output: AssistantMessage,
|
|
9
|
+
options?: OpenAIResponsesStreamOptions,
|
|
10
|
+
): void {
|
|
11
|
+
if (response?.usage) {
|
|
12
|
+
const cachedTokens = response.usage.input_tokens_details?.cached_tokens || 0;
|
|
13
|
+
const reasoningTokens = (response.usage as { output_tokens_details?: { reasoning_tokens?: number } }).output_tokens_details?.reasoning_tokens || 0;
|
|
14
|
+
output.usage = {
|
|
15
|
+
input: (response.usage.input_tokens || 0) - cachedTokens,
|
|
16
|
+
output: response.usage.output_tokens || 0,
|
|
17
|
+
cacheRead: cachedTokens,
|
|
18
|
+
cacheWrite: 0,
|
|
19
|
+
totalTokens: response.usage.total_tokens || 0,
|
|
20
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
21
|
+
};
|
|
22
|
+
(output.usage as Usage & { reasoning?: number }).reasoning = reasoningTokens;
|
|
23
|
+
}
|
|
24
|
+
calculateCost(model, output.usage);
|
|
25
|
+
if (options?.applyServiceTierPricing) {
|
|
26
|
+
const serviceTier = options.resolveServiceTier
|
|
27
|
+
? options.resolveServiceTier(response?.service_tier, options.serviceTier)
|
|
28
|
+
: (response?.service_tier ?? options.serviceTier);
|
|
29
|
+
options.applyServiceTierPricing(output.usage, serviceTier);
|
|
30
|
+
}
|
|
31
|
+
output.stopReason = mapStopReason(response?.status);
|
|
32
|
+
if (output.content.some((block) => block.type === "toolCall") && output.stopReason === "stop") {
|
|
33
|
+
output.stopReason = "toolUse";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function mapStopReason(status: string | undefined): AssistantMessage["stopReason"] {
|
|
38
|
+
if (!status) return "stop";
|
|
39
|
+
switch (status) {
|
|
40
|
+
case "completed":
|
|
41
|
+
return "stop";
|
|
42
|
+
case "incomplete":
|
|
43
|
+
return "length";
|
|
44
|
+
case "failed":
|
|
45
|
+
case "cancelled":
|
|
46
|
+
return "error";
|
|
47
|
+
case "in_progress":
|
|
48
|
+
case "queued":
|
|
49
|
+
return "stop";
|
|
50
|
+
default:
|
|
51
|
+
throw new Error(`Unhandled stop reason: ${status}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2025 OpenAI
|
|
3
|
+
* SPDX-FileCopyrightText: 2026 oai404iao
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*
|
|
6
|
+
* Modified TypeScript compatibility serialization derived from the namespace
|
|
7
|
+
* tool construction in OpenAI Codex at revision
|
|
8
|
+
* eb9dceba1a2e658142a456c5898836774835616b.
|
|
9
|
+
*
|
|
10
|
+
* This preserves the reviewed `web.run` and `image_gen.imagegen` declaration
|
|
11
|
+
* shapes for this package's internal Responses Lite compatibility path. It is
|
|
12
|
+
* not an OpenAI-supported public API contract. Immutable upstream blob IDs,
|
|
13
|
+
* source hashes, and local compatibility fingerprints are recorded in
|
|
14
|
+
* provenance/openai-codex-eb9dceba-reserved-tools.json.
|
|
15
|
+
*
|
|
16
|
+
* See THIRD_PARTY_NOTICES.md and LICENSES/Apache-2.0.txt.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { CodexReservedNamespaceTool } from "./types.js";
|
|
20
|
+
|
|
21
|
+
export const IMAGE_GENERATION_NAMESPACE: CodexReservedNamespaceTool = {
|
|
22
|
+
type: "namespace",
|
|
23
|
+
name: "image_gen",
|
|
24
|
+
description: "Tools in the image_gen namespace.",
|
|
25
|
+
tools: [
|
|
26
|
+
{
|
|
27
|
+
type: "function",
|
|
28
|
+
name: "imagegen",
|
|
29
|
+
description: "The `image_gen.imagegen` tool enables image generation from descriptions and editing of existing images based on specific instructions. Use it when:\n\n- The user requests an image based on a scene description, such as a diagram, portrait, comic, meme, or any other visual.\n- The user wants to modify an attached or previously generated image with specific changes, including adding or removing elements, altering colors, improving quality/resolution, or transforming the style (e.g., cartoon, oil painting).\n\nGuidelines:\n- imagegen needs a few minutes to finish. In code-mode, use the first-line @exec directive to give the initial call 120 seconds and the same yield for any waits that follow. Once it finishes, return the image with generatedImage(result).\n- Omit both `referenced_image_paths` and `num_last_images_to_include` when generating a brand new image.\n- For edits, use `referenced_image_paths` when every target image has a local file path.\n- If you have not seen a local image yet, use `view_image` to inspect it before editing.\n- Use `num_last_images_to_include` only when at least one target image has no local file path.\n- Set `num_last_images_to_include` to the smallest number of recent conversation images that includes every target image, up to 5.\n- Never provide both `referenced_image_paths` and `num_last_images_to_include`.\n- If neither mechanism can include every target image, ask the user to attach the missing images again.\n- Directly generate the image without reconfirmation or clarification unless required images must be attached again.\n- Always use this tool for image editing unless the user explicitly requests otherwise. Do not use the `python` tool for image editing unless specifically instructed.\n",
|
|
30
|
+
strict: false,
|
|
31
|
+
parameters: {
|
|
32
|
+
type: "object",
|
|
33
|
+
properties: {
|
|
34
|
+
num_last_images_to_include: {
|
|
35
|
+
type: ["integer", "null"],
|
|
36
|
+
},
|
|
37
|
+
prompt: {
|
|
38
|
+
type: "string",
|
|
39
|
+
},
|
|
40
|
+
referenced_image_paths: {
|
|
41
|
+
type: ["array", "null"],
|
|
42
|
+
items: {
|
|
43
|
+
type: "string",
|
|
44
|
+
description: "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
required: ["prompt"],
|
|
49
|
+
additionalProperties: false,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2025 OpenAI
|
|
3
|
+
* SPDX-FileCopyrightText: 2026 oai404iao
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*
|
|
6
|
+
* Modified TypeScript compatibility serialization derived from the namespace
|
|
7
|
+
* tool construction in OpenAI Codex at revision
|
|
8
|
+
* eb9dceba1a2e658142a456c5898836774835616b.
|
|
9
|
+
*
|
|
10
|
+
* This preserves the reviewed `web.run` and `image_gen.imagegen` declaration
|
|
11
|
+
* shapes for this package's internal Responses Lite compatibility path. It is
|
|
12
|
+
* not an OpenAI-supported public API contract. Immutable upstream blob IDs,
|
|
13
|
+
* source hashes, and local compatibility fingerprints are recorded in
|
|
14
|
+
* provenance/openai-codex-eb9dceba-reserved-tools.json.
|
|
15
|
+
*
|
|
16
|
+
* See THIRD_PARTY_NOTICES.md and LICENSES/Apache-2.0.txt.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export type CodexReservedToolName = "web_search" | "image_generation";
|
|
20
|
+
|
|
21
|
+
export interface CodexReservedNamespaceTool {
|
|
22
|
+
type: "namespace";
|
|
23
|
+
name: "web" | "image_gen";
|
|
24
|
+
description: string;
|
|
25
|
+
tools: Array<{
|
|
26
|
+
type: "function";
|
|
27
|
+
name: "run" | "imagegen";
|
|
28
|
+
description: string;
|
|
29
|
+
strict: false;
|
|
30
|
+
parameters: Record<string, unknown>;
|
|
31
|
+
}>;
|
|
32
|
+
}
|