@howaboua/pi-codex-conversion 1.0.4 → 1.0.6
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/README.md +4 -0
- package/package.json +1 -1
- package/src/index.ts +27 -32
- package/src/prompt/build-system-prompt.ts +1 -13
- package/src/tools/codex-rendering.ts +0 -8
- package/src/tools/web-search-tool.ts +41 -28
package/README.md
CHANGED
|
@@ -10,6 +10,9 @@ This package replaces Pi's default Codex/GPT experience with a narrower Codex-li
|
|
|
10
10
|
|
|
11
11
|

|
|
12
12
|
|
|
13
|
+
> [!NOTE]
|
|
14
|
+
> Native OpenAI Codex Responses web search runs silently. Pi does not expose native web-search usage events to extensions, so the adapter shows a one-time session notice instead of per-search tool-call history.
|
|
15
|
+
|
|
13
16
|
## Active tools in adapter mode
|
|
14
17
|
|
|
15
18
|
When the adapter is active, the LLM sees these tools:
|
|
@@ -55,6 +58,7 @@ npm run check
|
|
|
55
58
|
- `write_stdin({ session_id, chars: "y\\n" })` renders like `Interacted with background terminal`
|
|
56
59
|
- `view_image({ path: "/absolute/path/to/screenshot.png" })` is available on image-capable models
|
|
57
60
|
- `web_search` is surfaced only on `openai-codex`, and the adapter rewrites it into the native OpenAI Responses `type: "web_search"` payload instead of executing a local function tool
|
|
61
|
+
- when native web search is available, the adapter shows a one-time session notice; individual searches are not surfaced because Pi does not expose native web-search execution events to extensions
|
|
58
62
|
|
|
59
63
|
Raw command output is still available by expanding the tool result.
|
|
60
64
|
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -8,12 +8,13 @@ import { createExecSessionManager } from "./tools/exec-session-manager.ts";
|
|
|
8
8
|
import { buildCodexSystemPrompt, extractPiPromptSkills, type PromptSkill } from "./prompt/build-system-prompt.ts";
|
|
9
9
|
import { registerViewImageTool, supportsOriginalImageDetail } from "./tools/view-image-tool.ts";
|
|
10
10
|
import {
|
|
11
|
-
WEB_SEARCH_ACTIVITY_MESSAGE_TYPE,
|
|
12
|
-
payloadContainsWebSearchTool,
|
|
13
|
-
registerWebSearchMessageRenderer,
|
|
14
11
|
registerWebSearchTool,
|
|
12
|
+
registerWebSearchSessionNoteRenderer,
|
|
15
13
|
rewriteNativeWebSearchTool,
|
|
14
|
+
shouldShowWebSearchSessionNote,
|
|
16
15
|
supportsNativeWebSearch,
|
|
16
|
+
WEB_SEARCH_SESSION_NOTE_TEXT,
|
|
17
|
+
WEB_SEARCH_SESSION_NOTE_TYPE,
|
|
17
18
|
} from "./tools/web-search-tool.ts";
|
|
18
19
|
import { registerWriteStdinTool } from "./tools/write-stdin-tool.ts";
|
|
19
20
|
|
|
@@ -21,7 +22,7 @@ interface AdapterState {
|
|
|
21
22
|
enabled: boolean;
|
|
22
23
|
previousToolNames?: string[];
|
|
23
24
|
promptSkills: PromptSkill[];
|
|
24
|
-
|
|
25
|
+
webSearchNoticeShown: boolean;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
const ADAPTER_TOOL_NAMES = [...CORE_ADAPTER_TOOL_NAMES, VIEW_IMAGE_TOOL_NAME, WEB_SEARCH_TOOL_NAME];
|
|
@@ -35,20 +36,21 @@ function getCommandArg(args: unknown): string | undefined {
|
|
|
35
36
|
|
|
36
37
|
export default function codexConversion(pi: ExtensionAPI) {
|
|
37
38
|
const tracker = createExecCommandTracker();
|
|
38
|
-
const state: AdapterState = { enabled: false, promptSkills: [],
|
|
39
|
+
const state: AdapterState = { enabled: false, promptSkills: [], webSearchNoticeShown: false };
|
|
39
40
|
const sessions = createExecSessionManager();
|
|
40
41
|
|
|
41
42
|
registerApplyPatchTool(pi);
|
|
42
43
|
registerExecCommandTool(pi, tracker, sessions);
|
|
43
44
|
registerWriteStdinTool(pi, sessions);
|
|
44
45
|
registerWebSearchTool(pi);
|
|
45
|
-
|
|
46
|
+
registerWebSearchSessionNoteRenderer(pi);
|
|
46
47
|
|
|
47
48
|
sessions.onSessionExit((_sessionId, command) => {
|
|
48
49
|
tracker.recordCommandFinished(command);
|
|
49
50
|
});
|
|
50
51
|
|
|
51
52
|
pi.on("session_start", async (_event, ctx) => {
|
|
53
|
+
state.webSearchNoticeShown = false;
|
|
52
54
|
syncAdapter(pi, ctx, state);
|
|
53
55
|
});
|
|
54
56
|
|
|
@@ -84,39 +86,19 @@ export default function codexConversion(pi: ExtensionAPI) {
|
|
|
84
86
|
};
|
|
85
87
|
});
|
|
86
88
|
|
|
87
|
-
pi.on("context", async (event) => {
|
|
88
|
-
return {
|
|
89
|
-
messages: event.messages.filter(
|
|
90
|
-
(message) => !(message.role === "custom" && message.customType === WEB_SEARCH_ACTIVITY_MESSAGE_TYPE),
|
|
91
|
-
),
|
|
92
|
-
};
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
pi.on("turn_start", async () => {
|
|
96
|
-
state.pendingWebSearchCount = 0;
|
|
97
|
-
});
|
|
98
|
-
|
|
99
89
|
pi.on("before_provider_request", async (event, ctx) => {
|
|
100
90
|
if (!isOpenAICodexContext(ctx)) {
|
|
101
91
|
return undefined;
|
|
102
92
|
}
|
|
103
|
-
if (payloadContainsWebSearchTool(event.payload)) {
|
|
104
|
-
state.pendingWebSearchCount += 1;
|
|
105
|
-
}
|
|
106
93
|
return rewriteNativeWebSearchTool(event.payload, ctx.model);
|
|
107
94
|
});
|
|
108
95
|
|
|
109
|
-
pi.on("
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
content: "",
|
|
116
|
-
display: true,
|
|
117
|
-
details: { count: state.pendingWebSearchCount },
|
|
118
|
-
});
|
|
119
|
-
state.pendingWebSearchCount = 0;
|
|
96
|
+
pi.on("context", async (event) => {
|
|
97
|
+
return {
|
|
98
|
+
messages: event.messages.filter(
|
|
99
|
+
(message) => !(message.role === "custom" && message.customType === WEB_SEARCH_SESSION_NOTE_TYPE),
|
|
100
|
+
),
|
|
101
|
+
};
|
|
120
102
|
});
|
|
121
103
|
}
|
|
122
104
|
|
|
@@ -124,6 +106,7 @@ function syncAdapter(pi: ExtensionAPI, ctx: ExtensionContext, state: AdapterStat
|
|
|
124
106
|
state.promptSkills = extractPiPromptSkills(ctx.getSystemPrompt());
|
|
125
107
|
|
|
126
108
|
registerViewImageTool(pi, { allowOriginalDetail: supportsOriginalImageDetail(ctx.model) });
|
|
109
|
+
maybeShowWebSearchSessionNote(pi, ctx, state);
|
|
127
110
|
|
|
128
111
|
if (isCodexLikeContext(ctx)) {
|
|
129
112
|
enableAdapter(pi, ctx, state);
|
|
@@ -190,3 +173,15 @@ export function restoreTools(previousTools: string[], activeTools: string[]): st
|
|
|
190
173
|
function hasAdapterTools(activeTools: string[]): boolean {
|
|
191
174
|
return activeTools.some((toolName) => ADAPTER_TOOL_NAMES.includes(toolName));
|
|
192
175
|
}
|
|
176
|
+
|
|
177
|
+
function maybeShowWebSearchSessionNote(pi: ExtensionAPI, ctx: ExtensionContext, state: AdapterState): void {
|
|
178
|
+
if (!shouldShowWebSearchSessionNote(ctx.model, ctx.hasUI, state.webSearchNoticeShown)) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
pi.sendMessage({
|
|
182
|
+
customType: WEB_SEARCH_SESSION_NOTE_TYPE,
|
|
183
|
+
content: WEB_SEARCH_SESSION_NOTE_TEXT,
|
|
184
|
+
display: true,
|
|
185
|
+
});
|
|
186
|
+
state.webSearchNoticeShown = true;
|
|
187
|
+
}
|
|
@@ -4,24 +4,12 @@ export interface PromptSkill {
|
|
|
4
4
|
filePath: string;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
const PI_INTRO =
|
|
8
|
-
"You are an expert coding assistant operating inside pi, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.";
|
|
9
|
-
const CODEX_INTRO =
|
|
10
|
-
"You are Codex running inside pi, a coding agent harness. Work directly in the user's workspace and finish the task end-to-end when feasible.";
|
|
11
|
-
|
|
12
7
|
const CODEX_GUIDELINES = [
|
|
13
8
|
"Use `parallel` only when tool calls are independent and can safely run at the same time.",
|
|
14
9
|
"Use `write_stdin` when an exec session returns `session_id`, and continue until `exit_code` is present.",
|
|
15
10
|
"Do not request `tty` unless interactive terminal behavior is required.",
|
|
16
11
|
];
|
|
17
12
|
|
|
18
|
-
function rewriteIntro(prompt: string): string {
|
|
19
|
-
if (!prompt.startsWith(PI_INTRO)) {
|
|
20
|
-
return prompt;
|
|
21
|
-
}
|
|
22
|
-
return `${CODEX_INTRO}${prompt.slice(PI_INTRO.length)}`;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
13
|
function insertBeforeTrailingContext(prompt: string, section: string): string {
|
|
26
14
|
const currentDateIndex = prompt.lastIndexOf("\nCurrent date:");
|
|
27
15
|
if (currentDateIndex !== -1) {
|
|
@@ -114,5 +102,5 @@ function injectGuidelines(prompt: string): string {
|
|
|
114
102
|
}
|
|
115
103
|
|
|
116
104
|
export function buildCodexSystemPrompt(basePrompt: string, options: { skills?: PromptSkill[]; shell?: string } = {}): string {
|
|
117
|
-
return injectShell(injectSkills(injectGuidelines(
|
|
105
|
+
return injectShell(injectSkills(injectGuidelines(basePrompt), options.skills ?? []), options.shell);
|
|
118
106
|
}
|
|
@@ -32,14 +32,6 @@ export function renderWriteStdinCall(
|
|
|
32
32
|
return text;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
export function renderWebSearchActivity(count: number, theme: RenderTheme, expanded = false): string {
|
|
36
|
-
let text = `${theme.fg("dim", "•")} ${theme.bold("Searched the web")}`;
|
|
37
|
-
if (expanded && count > 1) {
|
|
38
|
-
text += `\n${theme.fg("dim", " └ ")}${theme.fg("muted", `${count} web searches in this turn`)}`;
|
|
39
|
-
}
|
|
40
|
-
return text;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
35
|
function renderExplorationText(actions: ShellAction[], state: ExecCommandStatus, theme: RenderTheme): string {
|
|
44
36
|
const header = state === "running" ? "Exploring" : "Explored";
|
|
45
37
|
let text = `${theme.fg("dim", "•")} ${theme.bold(header)}`;
|
|
@@ -2,14 +2,19 @@ import type { ExtensionAPI, ExtensionContext, ToolDefinition } from "@mariozechn
|
|
|
2
2
|
import { Type } from "@sinclair/typebox";
|
|
3
3
|
import { Box, Text } from "@mariozechner/pi-tui";
|
|
4
4
|
import { isOpenAICodexModel } from "../adapter/codex-model.ts";
|
|
5
|
-
import { renderWebSearchActivity } from "./codex-rendering.ts";
|
|
6
5
|
|
|
7
6
|
export const WEB_SEARCH_UNSUPPORTED_MESSAGE = "web_search is only available with the openai-codex provider";
|
|
8
7
|
const WEB_SEARCH_LOCAL_EXECUTION_MESSAGE =
|
|
9
8
|
"web_search is a native openai-codex provider tool and should not execute locally";
|
|
10
|
-
export const
|
|
9
|
+
export const WEB_SEARCH_SESSION_NOTE_TYPE = "codex-web-search-session-note";
|
|
10
|
+
export const WEB_SEARCH_SESSION_NOTE_TEXT =
|
|
11
|
+
"Native OpenAI Codex web search is enabled for this session. Search runs silently and is not surfaced as a separate tool call.";
|
|
12
|
+
const WEB_SEARCH_MULTIMODAL_CONTENT_TYPES = ["text", "image"] as const;
|
|
11
13
|
|
|
12
|
-
const WEB_SEARCH_PARAMETERS = Type.
|
|
14
|
+
const WEB_SEARCH_PARAMETERS = Type.Unsafe<Record<string, never>>({
|
|
15
|
+
type: "object",
|
|
16
|
+
additionalProperties: false,
|
|
17
|
+
});
|
|
13
18
|
|
|
14
19
|
interface FunctionToolPayload {
|
|
15
20
|
type?: unknown;
|
|
@@ -21,14 +26,32 @@ interface ResponsesPayload {
|
|
|
21
26
|
[key: string]: unknown;
|
|
22
27
|
}
|
|
23
28
|
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
interface ResponsesWebSearchTool {
|
|
30
|
+
type: "web_search";
|
|
31
|
+
external_web_access: true;
|
|
32
|
+
search_content_types?: string[];
|
|
26
33
|
}
|
|
27
34
|
|
|
28
35
|
export function supportsNativeWebSearch(model: ExtensionContext["model"]): boolean {
|
|
29
36
|
return isOpenAICodexModel(model);
|
|
30
37
|
}
|
|
31
38
|
|
|
39
|
+
export function shouldShowWebSearchSessionNote(
|
|
40
|
+
model: ExtensionContext["model"],
|
|
41
|
+
hasUI: boolean,
|
|
42
|
+
alreadyShown: boolean,
|
|
43
|
+
): boolean {
|
|
44
|
+
return hasUI && !alreadyShown && supportsNativeWebSearch(model);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function supportsMultimodalNativeWebSearch(model: ExtensionContext["model"]): boolean {
|
|
48
|
+
if (!supportsNativeWebSearch(model)) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
const id = (model?.id ?? "").toLowerCase();
|
|
52
|
+
return !id.includes("spark");
|
|
53
|
+
}
|
|
54
|
+
|
|
32
55
|
function isWebSearchFunctionTool(tool: unknown): tool is FunctionToolPayload {
|
|
33
56
|
return !!tool && typeof tool === "object" && (tool as FunctionToolPayload).type === "function" && (tool as FunctionToolPayload).name === "web_search";
|
|
34
57
|
}
|
|
@@ -50,10 +73,14 @@ export function rewriteNativeWebSearchTool(payload: unknown, model: ExtensionCon
|
|
|
50
73
|
}
|
|
51
74
|
rewritten = true;
|
|
52
75
|
// Match Codex's native tool shape rather than exposing a synthetic function tool.
|
|
53
|
-
|
|
76
|
+
const nativeTool: ResponsesWebSearchTool = {
|
|
54
77
|
type: "web_search",
|
|
55
78
|
external_web_access: true,
|
|
56
79
|
};
|
|
80
|
+
if (supportsMultimodalNativeWebSearch(model)) {
|
|
81
|
+
nativeTool.search_content_types = [...WEB_SEARCH_MULTIMODAL_CONTENT_TYPES];
|
|
82
|
+
}
|
|
83
|
+
return nativeTool;
|
|
57
84
|
});
|
|
58
85
|
|
|
59
86
|
if (!rewritten) {
|
|
@@ -66,28 +93,14 @@ export function rewriteNativeWebSearchTool(payload: unknown, model: ExtensionCon
|
|
|
66
93
|
};
|
|
67
94
|
}
|
|
68
95
|
|
|
69
|
-
export function payloadContainsWebSearchTool(payload: unknown): boolean {
|
|
70
|
-
if (!payload || typeof payload !== "object") {
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
const tools = (payload as ResponsesPayload).tools;
|
|
74
|
-
if (!Array.isArray(tools)) {
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
return tools.some(
|
|
78
|
-
(tool) =>
|
|
79
|
-
!!tool &&
|
|
80
|
-
typeof tool === "object" &&
|
|
81
|
-
(("type" in tool && (tool as { type?: unknown }).type === "web_search") || isWebSearchFunctionTool(tool)),
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
96
|
export function createWebSearchTool(): ToolDefinition<typeof WEB_SEARCH_PARAMETERS> {
|
|
86
97
|
return {
|
|
87
98
|
name: "web_search",
|
|
88
99
|
label: "web_search",
|
|
89
|
-
description:
|
|
90
|
-
|
|
100
|
+
description:
|
|
101
|
+
"Search the web for sources relevant to the current task. Use it when you need up-to-date information, external references, or broader context beyond the workspace.",
|
|
102
|
+
promptSnippet:
|
|
103
|
+
"Search the web for sources relevant to the current task. Use it when you need up-to-date information, external references, or broader context beyond the workspace.",
|
|
91
104
|
parameters: WEB_SEARCH_PARAMETERS,
|
|
92
105
|
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
93
106
|
if (!supportsNativeWebSearch(ctx.model)) {
|
|
@@ -113,11 +126,11 @@ export function registerWebSearchTool(pi: ExtensionAPI): void {
|
|
|
113
126
|
pi.registerTool(createWebSearchTool());
|
|
114
127
|
}
|
|
115
128
|
|
|
116
|
-
export function
|
|
117
|
-
pi.registerMessageRenderer
|
|
118
|
-
const count = typeof message.details?.count === "number" ? message.details.count : 1;
|
|
129
|
+
export function registerWebSearchSessionNoteRenderer(pi: ExtensionAPI): void {
|
|
130
|
+
pi.registerMessageRenderer(WEB_SEARCH_SESSION_NOTE_TYPE, (_message, _options, theme) => {
|
|
119
131
|
const box = new Box(1, 1, (text) => theme.bg("toolSuccessBg", text));
|
|
120
|
-
box.addChild(new Text(
|
|
132
|
+
box.addChild(new Text(theme.bold("Web search enabled"), 0, 0));
|
|
133
|
+
box.addChild(new Text(`\n${theme.fg("dim", WEB_SEARCH_SESSION_NOTE_TEXT)}`, 0, 0));
|
|
121
134
|
return box;
|
|
122
135
|
});
|
|
123
136
|
}
|