@howaboua/pi-codex-conversion 1.0.4 → 1.0.5

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 CHANGED
@@ -10,6 +10,9 @@ This package replaces Pi's default Codex/GPT experience with a narrower Codex-li
10
10
 
11
11
  ![Available tools](./available-tools.png)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@howaboua/pi-codex-conversion",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Codex-oriented tool and prompt adapter for pi coding agent",
5
5
  "type": "module",
6
6
  "repository": {
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
- pendingWebSearchCount: number;
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: [], pendingWebSearchCount: 0 };
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
- registerWebSearchMessageRenderer(pi);
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("agent_end", async () => {
110
- if (state.pendingWebSearchCount <= 0) {
111
- return;
112
- }
113
- pi.sendMessage({
114
- customType: WEB_SEARCH_ACTIVITY_MESSAGE_TYPE,
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
+ }
@@ -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,12 +2,13 @@ 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 WEB_SEARCH_ACTIVITY_MESSAGE_TYPE = "codex-web-search";
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.";
11
12
 
12
13
  const WEB_SEARCH_PARAMETERS = Type.Object({}, { additionalProperties: false });
13
14
 
@@ -21,14 +22,18 @@ interface ResponsesPayload {
21
22
  [key: string]: unknown;
22
23
  }
23
24
 
24
- export interface WebSearchActivityDetails {
25
- count: number;
26
- }
27
-
28
25
  export function supportsNativeWebSearch(model: ExtensionContext["model"]): boolean {
29
26
  return isOpenAICodexModel(model);
30
27
  }
31
28
 
29
+ export function shouldShowWebSearchSessionNote(
30
+ model: ExtensionContext["model"],
31
+ hasUI: boolean,
32
+ alreadyShown: boolean,
33
+ ): boolean {
34
+ return hasUI && !alreadyShown && supportsNativeWebSearch(model);
35
+ }
36
+
32
37
  function isWebSearchFunctionTool(tool: unknown): tool is FunctionToolPayload {
33
38
  return !!tool && typeof tool === "object" && (tool as FunctionToolPayload).type === "function" && (tool as FunctionToolPayload).name === "web_search";
34
39
  }
@@ -66,22 +71,6 @@ export function rewriteNativeWebSearchTool(payload: unknown, model: ExtensionCon
66
71
  };
67
72
  }
68
73
 
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
74
  export function createWebSearchTool(): ToolDefinition<typeof WEB_SEARCH_PARAMETERS> {
86
75
  return {
87
76
  name: "web_search",
@@ -113,11 +102,11 @@ export function registerWebSearchTool(pi: ExtensionAPI): void {
113
102
  pi.registerTool(createWebSearchTool());
114
103
  }
115
104
 
116
- export function registerWebSearchMessageRenderer(pi: ExtensionAPI): void {
117
- pi.registerMessageRenderer<WebSearchActivityDetails>(WEB_SEARCH_ACTIVITY_MESSAGE_TYPE, (message, { expanded }, theme) => {
118
- const count = typeof message.details?.count === "number" ? message.details.count : 1;
105
+ export function registerWebSearchSessionNoteRenderer(pi: ExtensionAPI): void {
106
+ pi.registerMessageRenderer(WEB_SEARCH_SESSION_NOTE_TYPE, (_message, _options, theme) => {
119
107
  const box = new Box(1, 1, (text) => theme.bg("toolSuccessBg", text));
120
- box.addChild(new Text(renderWebSearchActivity(count, theme, expanded), 0, 0));
108
+ box.addChild(new Text(theme.bold("Web search enabled"), 0, 0));
109
+ box.addChild(new Text(`\n${theme.fg("dim", WEB_SEARCH_SESSION_NOTE_TEXT)}`, 0, 0));
121
110
  return box;
122
111
  });
123
112
  }