pi-openai-codex-compat 0.0.2 → 0.0.4
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/CHANGELOG.md +86 -0
- package/README.md +86 -38
- package/extensions/openai-codex-compat/apply-patch-diff-render.ts +6 -2
- package/extensions/openai-codex-compat/apply-patch-engine.ts +89 -5
- package/extensions/openai-codex-compat/apply-patch.ts +4 -5
- package/extensions/openai-codex-compat/codex-cache-diagnostics.ts +97 -0
- package/extensions/openai-codex-compat/codex-cache-key.ts +9 -0
- package/extensions/openai-codex-compat/codex-installation.ts +51 -0
- package/extensions/openai-codex-compat/codex-metadata.ts +139 -0
- package/extensions/openai-codex-compat/codex-protocol.ts +4 -2
- package/extensions/openai-codex-compat/codex-provider.ts +708 -128
- package/extensions/openai-codex-compat/codex-stream.ts +137 -40
- package/extensions/openai-codex-compat/codex-thread-lineage.ts +156 -0
- package/extensions/openai-codex-compat/codex-transport.ts +1795 -199
- package/extensions/openai-codex-compat/compaction-checkpoint.ts +2 -2
- package/extensions/openai-codex-compat/config.ts +15 -2
- package/extensions/openai-codex-compat/image-generation-schema.ts +37 -0
- package/extensions/openai-codex-compat/image-generation.ts +25 -48
- package/extensions/openai-codex-compat/index.ts +13 -0
- package/extensions/openai-codex-compat/namespaced-tools.ts +2 -0
- package/extensions/openai-codex-compat/output-limit-continuation.ts +151 -0
- package/extensions/openai-codex-compat/provider-error.ts +79 -0
- package/extensions/openai-codex-compat/remote-compaction.ts +13 -0
- package/extensions/openai-codex-compat/request-options.ts +2 -2
- package/extensions/openai-codex-compat/responses-lite.ts +147 -0
- package/extensions/openai-codex-compat/responses-replay.ts +0 -7
- package/extensions/openai-codex-compat/settings-pane.ts +11 -0
- package/extensions/openai-codex-compat/web-run.ts +7 -0
- package/package.json +2 -1
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { isObject, type JsonRecord } from "./codex-protocol.ts";
|
|
2
|
+
import { DEFAULT_FUNCTION_NAMESPACE } from "./namespaced-tools.ts";
|
|
3
|
+
|
|
4
|
+
export const RESPONSES_LITE_HEADER = "x-openai-internal-codex-responses-lite";
|
|
5
|
+
export const RESPONSES_LITE_WS_METADATA_KEY =
|
|
6
|
+
"ws_request_header_x_openai_internal_codex_responses_lite";
|
|
7
|
+
const RESPONSES_LITE_MODELS: ReadonlySet<string> = new Set([
|
|
8
|
+
"gpt-5.6-sol",
|
|
9
|
+
"gpt-5.6-terra",
|
|
10
|
+
"gpt-5.6-luna",
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
function isHostedTool(tool: JsonRecord): boolean {
|
|
14
|
+
return (
|
|
15
|
+
tool.type === "web_search" ||
|
|
16
|
+
tool.type === "web_search_preview" ||
|
|
17
|
+
tool.type === "image_generation"
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function responsesLiteTools(value: unknown): JsonRecord[] {
|
|
22
|
+
const functions: JsonRecord = {
|
|
23
|
+
type: "namespace",
|
|
24
|
+
name: DEFAULT_FUNCTION_NAMESPACE,
|
|
25
|
+
description: "",
|
|
26
|
+
tools: [],
|
|
27
|
+
};
|
|
28
|
+
const functionTools = functions["tools"] as JsonRecord[];
|
|
29
|
+
const result: JsonRecord[] = [];
|
|
30
|
+
let functionsIndex: number | undefined;
|
|
31
|
+
|
|
32
|
+
for (const candidate of Array.isArray(value) ? value : []) {
|
|
33
|
+
if (!isObject(candidate)) throw new Error("Responses Lite received an invalid tool.");
|
|
34
|
+
const tool = structuredClone(candidate);
|
|
35
|
+
if (tool.type === "function" || tool.type === "custom") {
|
|
36
|
+
if (typeof tool.name !== "string") {
|
|
37
|
+
throw new Error("Responses Lite received an invalid default-namespace tool.");
|
|
38
|
+
}
|
|
39
|
+
functionsIndex ??= result.length;
|
|
40
|
+
functionTools.push(tool);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (tool.type === "namespace" && tool.name === DEFAULT_FUNCTION_NAMESPACE) {
|
|
44
|
+
functionsIndex ??= result.length;
|
|
45
|
+
if (typeof tool["description"] === "string" && tool["description"].trim()) {
|
|
46
|
+
functions["description"] = tool["description"];
|
|
47
|
+
}
|
|
48
|
+
if (!Array.isArray(tool.tools)) {
|
|
49
|
+
throw new Error("Responses Lite received an invalid functions namespace.");
|
|
50
|
+
}
|
|
51
|
+
for (const child of tool.tools) {
|
|
52
|
+
if (
|
|
53
|
+
!isObject(child) ||
|
|
54
|
+
(child.type !== "function" && child.type !== "custom") ||
|
|
55
|
+
typeof child.name !== "string"
|
|
56
|
+
) {
|
|
57
|
+
throw new Error("Responses Lite received an invalid functions namespace tool.");
|
|
58
|
+
}
|
|
59
|
+
const cloned = structuredClone(child);
|
|
60
|
+
functionTools.push(cloned);
|
|
61
|
+
}
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (!isHostedTool(tool)) result.push(tool);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (functionsIndex !== undefined && functionTools.length > 0) {
|
|
68
|
+
result.splice(functionsIndex, 0, functions);
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function prepareInputItem(value: unknown): unknown {
|
|
74
|
+
if (Array.isArray(value)) {
|
|
75
|
+
return value.map((item) => prepareInputItem(item));
|
|
76
|
+
}
|
|
77
|
+
if (!isObject(value)) return value;
|
|
78
|
+
|
|
79
|
+
const result: JsonRecord = {};
|
|
80
|
+
for (const [key, child] of Object.entries(value)) {
|
|
81
|
+
if (key === "detail" && value.type === "input_image") continue;
|
|
82
|
+
result[key] = prepareInputItem(child);
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function usesResponsesLite(modelId: string, enabled = true): boolean {
|
|
88
|
+
return enabled && RESPONSES_LITE_MODELS.has(modelId);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function applyResponsesLite(
|
|
92
|
+
payload: JsonRecord,
|
|
93
|
+
modelId: string,
|
|
94
|
+
enabled = true,
|
|
95
|
+
): JsonRecord {
|
|
96
|
+
if (!usesResponsesLite(modelId, enabled)) return payload;
|
|
97
|
+
if (!Array.isArray(payload.input)) {
|
|
98
|
+
throw new Error("Responses Lite requires array input.");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const requestInput = payload.input;
|
|
102
|
+
const result = structuredClone(payload);
|
|
103
|
+
const tools = responsesLiteTools(result.tools);
|
|
104
|
+
const input = requestInput.map((item) => prepareInputItem(item));
|
|
105
|
+
const prefix: JsonRecord[] = [{ type: "additional_tools", role: "developer", tools }];
|
|
106
|
+
if (typeof result.instructions === "string" && result.instructions.length > 0) {
|
|
107
|
+
prefix.push({
|
|
108
|
+
type: "message",
|
|
109
|
+
role: "developer",
|
|
110
|
+
content: [{ type: "input_text", text: result.instructions }],
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
result.input = [...prefix, ...input];
|
|
114
|
+
delete result.instructions;
|
|
115
|
+
delete result.tools;
|
|
116
|
+
result.parallel_tool_calls = false;
|
|
117
|
+
result["reasoning"] = {
|
|
118
|
+
...(isObject(result["reasoning"]) ? result["reasoning"] : {}),
|
|
119
|
+
context: "all_turns",
|
|
120
|
+
};
|
|
121
|
+
result.client_metadata = {
|
|
122
|
+
...(isObject(result.client_metadata) ? result.client_metadata : {}),
|
|
123
|
+
[RESPONSES_LITE_WS_METADATA_KEY]: "true",
|
|
124
|
+
};
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function applyResponsesLiteHeaders(headers: Headers, payload: JsonRecord): void {
|
|
129
|
+
const metadata = payload.client_metadata;
|
|
130
|
+
if (isObject(metadata) && metadata[RESPONSES_LITE_WS_METADATA_KEY] === "true") {
|
|
131
|
+
headers.set(RESPONSES_LITE_HEADER, "true");
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Keep the WebSocket-only Lite marker out of HTTP/SSE request bodies. */
|
|
136
|
+
export function responsesLiteSsePayload(payload: JsonRecord): JsonRecord {
|
|
137
|
+
const metadata = payload.client_metadata;
|
|
138
|
+
if (!isObject(metadata) || metadata[RESPONSES_LITE_WS_METADATA_KEY] === undefined) {
|
|
139
|
+
return payload;
|
|
140
|
+
}
|
|
141
|
+
const clientMetadata = { ...metadata };
|
|
142
|
+
delete clientMetadata[RESPONSES_LITE_WS_METADATA_KEY];
|
|
143
|
+
return {
|
|
144
|
+
...payload,
|
|
145
|
+
client_metadata: clientMetadata,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
@@ -24,10 +24,3 @@ export function normalizeReplayItem(item: JsonRecord): JsonRecord {
|
|
|
24
24
|
}
|
|
25
25
|
return normalized;
|
|
26
26
|
}
|
|
27
|
-
|
|
28
|
-
export function replayItemsEqual(
|
|
29
|
-
left: readonly JsonRecord[] | undefined,
|
|
30
|
-
right: readonly JsonRecord[] | undefined,
|
|
31
|
-
): boolean {
|
|
32
|
-
return stableResponsesJson(left ?? []) === stableResponsesJson(right ?? []);
|
|
33
|
-
}
|
|
@@ -28,6 +28,7 @@ const COMMAND_NAME = "codex-settings";
|
|
|
28
28
|
|
|
29
29
|
type SettingId =
|
|
30
30
|
| "fastMode"
|
|
31
|
+
| "responsesLite"
|
|
31
32
|
| "textVerbosity"
|
|
32
33
|
| "reasoningSummary"
|
|
33
34
|
| "reasoningMode"
|
|
@@ -64,6 +65,13 @@ export function settingItems(
|
|
|
64
65
|
currentValue: toggleValue(config.fastMode),
|
|
65
66
|
values: ["off", "on"],
|
|
66
67
|
},
|
|
68
|
+
{
|
|
69
|
+
id: "responsesLite",
|
|
70
|
+
label: "Responses Lite",
|
|
71
|
+
description: "Use Codex's Responses Lite envelope on supported GPT-5.6 models.",
|
|
72
|
+
currentValue: toggleValue(config.responsesLite),
|
|
73
|
+
values: ["off", "on"],
|
|
74
|
+
},
|
|
67
75
|
{
|
|
68
76
|
id: "textVerbosity",
|
|
69
77
|
label: "Text verbosity",
|
|
@@ -155,6 +163,8 @@ export function settingPatch(id: string, value: string): ConfigLayer | undefined
|
|
|
155
163
|
switch (id as SettingId) {
|
|
156
164
|
case "fastMode":
|
|
157
165
|
return value === "on" || value === "off" ? { fastMode: value === "on" } : undefined;
|
|
166
|
+
case "responsesLite":
|
|
167
|
+
return value === "on" || value === "off" ? { responsesLite: value === "on" } : undefined;
|
|
158
168
|
case "textVerbosity":
|
|
159
169
|
if (value === "low" || value === "medium" || value === "high") {
|
|
160
170
|
return { textVerbosity: value };
|
|
@@ -202,6 +212,7 @@ export function settingPatch(id: string, value: string): ConfigLayer | undefined
|
|
|
202
212
|
function applySettingPatch(config: CodexCompatConfig, patch: ConfigLayer): CodexCompatConfig {
|
|
203
213
|
const next: CodexCompatConfig = { ...config };
|
|
204
214
|
if (typeof patch.fastMode === "boolean") next.fastMode = patch.fastMode;
|
|
215
|
+
if (typeof patch.responsesLite === "boolean") next.responsesLite = patch.responsesLite;
|
|
205
216
|
if (typeof patch.applyPatch === "boolean") next.applyPatch = patch.applyPatch;
|
|
206
217
|
if (patch.toolBackground) next.toolBackground = patch.toolBackground;
|
|
207
218
|
if (typeof patch.imageGeneration === "boolean") {
|
|
@@ -116,6 +116,13 @@ export default function registerWebRun(
|
|
|
116
116
|
name: WEB_RUN_TOOL_NAME,
|
|
117
117
|
label: WEB_RUN_TOOL_NAME,
|
|
118
118
|
description: WEB_RUN_DESCRIPTION,
|
|
119
|
+
promptSnippet: "Search and browse the internet",
|
|
120
|
+
promptGuidelines: [
|
|
121
|
+
"Use `web.run` when the user explicitly asks to browse or when answering requires current, niche, high-stakes, or precisely sourced information, including recommendations that may change over time.",
|
|
122
|
+
'Batch independent `web.run` operations in one call, pass only required parameters, and keep `search_query` to at most four queries; use `response_length: "medium"` or `"long"` when sending four.',
|
|
123
|
+
"For technical research, prefer primary sources; for OpenAI product questions, inspect local code first and restrict fallback browsing to official OpenAI sites.",
|
|
124
|
+
"Cite supported claims with direct Markdown links near the relevant text, never expose internal reference IDs, and respect the description's quotation and source word limits.",
|
|
125
|
+
],
|
|
119
126
|
parameters: WEB_RUN_PARAMETERS,
|
|
120
127
|
executionMode: "parallel",
|
|
121
128
|
renderShell: "self",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-openai-codex-compat",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "OpenAI Codex compatibility for Pi with native compaction, fast mode, and Codex-optimized capabilities",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package"
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"scripts": {
|
|
32
32
|
"check": "hk check --all --check",
|
|
33
33
|
"test": "node --test --test-concurrency=1 test/*.test.ts",
|
|
34
|
+
"test:live:codex": "PI_CODEX_LIVE_TEST=1 node --test --test-concurrency=1 test/codex-host.live.test.ts",
|
|
34
35
|
"fmt": "oxfmt",
|
|
35
36
|
"lint": "oxlint",
|
|
36
37
|
"lint:fix": "oxlint --fix"
|