@howaboua/pi-codex-conversion 1.0.5 → 1.0.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@howaboua/pi-codex-conversion",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "Codex-oriented tool and prompt adapter for pi coding agent",
5
5
  "type": "module",
6
6
  "repository": {
@@ -8,20 +8,17 @@ export function normalizePatchPath({ path }: { path: string }): string {
8
8
  return withoutAt.replace(/^['"]|['"]$/g, "");
9
9
  }
10
10
 
11
- // Patch paths are intentionally confined to ctx.cwd so the adapter cannot write
12
- // outside the active workspace even if the incoming patch text is malicious.
11
+ // Relative patch paths stay anchored to ctx.cwd. Absolute patch paths are
12
+ // accepted as-is so the adapter can match Codex-style path usage.
13
13
  export function resolvePatchPath({ cwd, patchPath }: { cwd: string; patchPath: string }): string {
14
14
  const normalized = normalizePatchPath({ path: patchPath });
15
15
  if (!normalized) {
16
16
  throw new DiffError("Patch path cannot be empty");
17
17
  }
18
- if (isAbsolute(normalized)) {
19
- throw new DiffError("We do not support absolute paths.");
20
- }
21
18
 
22
- const absolutePath = resolve(cwd, normalized);
19
+ const absolutePath = isAbsolute(normalized) ? normalized : resolve(cwd, normalized);
23
20
  const rel = relative(cwd, absolutePath);
24
- if (rel.startsWith("..") || isAbsolute(rel)) {
21
+ if (!isAbsolute(normalized) && (rel.startsWith("..") || isAbsolute(rel))) {
25
22
  throw new DiffError(`Path escapes working directory: ${normalized}`);
26
23
  }
27
24
  return absolutePath;
@@ -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(rewriteIntro(basePrompt)), options.skills ?? []), options.shell);
105
+ return injectShell(injectSkills(injectGuidelines(basePrompt), options.skills ?? []), options.shell);
118
106
  }
@@ -65,7 +65,7 @@ export interface ExecSessionManager {
65
65
  }
66
66
 
67
67
  const DEFAULT_EXEC_YIELD_TIME_MS = 10_000;
68
- const DEFAULT_WRITE_YIELD_TIME_MS = 250;
68
+ const DEFAULT_WRITE_YIELD_TIME_MS = 10_000;
69
69
  const DEFAULT_MAX_OUTPUT_TOKENS = 10_000;
70
70
  const MIN_YIELD_TIME_MS = 250;
71
71
  const MAX_YIELD_TIME_MS = 30_000;
@@ -292,18 +292,24 @@ export function createExecSessionManager(): ExecSessionManager {
292
292
  notify(session);
293
293
  }
294
294
 
295
- function waitForActivity(session: ExecSession, yieldTimeMs: number): Promise<number> {
296
- if (session.buffer !== session.emittedBuffer || session.exitCode !== undefined && session.exitCode !== null) {
295
+ function waitForExitOrTimeout(session: ExecSession, yieldTimeMs: number): Promise<number> {
296
+ if (session.exitCode !== undefined && session.exitCode !== null) {
297
297
  return Promise.resolve(0);
298
298
  }
299
299
 
300
300
  const startedAt = Date.now();
301
301
  return new Promise((resolvePromise) => {
302
302
  const onWake = () => {
303
+ if (session.exitCode === undefined || session.exitCode === null) {
304
+ return;
305
+ }
303
306
  cleanup();
304
307
  resolvePromise(Date.now() - startedAt);
305
308
  };
306
- const timeout = setTimeout(onWake, yieldTimeMs);
309
+ const timeout = setTimeout(() => {
310
+ cleanup();
311
+ resolvePromise(Date.now() - startedAt);
312
+ }, yieldTimeMs);
307
313
  const cleanup = () => {
308
314
  clearTimeout(timeout);
309
315
  session.listeners.delete(onWake);
@@ -432,7 +438,7 @@ export function createExecSessionManager(): ExecSessionManager {
432
438
  sessions.set(session.id, session);
433
439
  rememberCommand(session.id, session.command);
434
440
 
435
- const waitedMs = await waitForActivity(session, clampYieldTime(input.yield_time_ms, DEFAULT_EXEC_YIELD_TIME_MS));
441
+ const waitedMs = await waitForExitOrTimeout(session, clampYieldTime(input.yield_time_ms, DEFAULT_EXEC_YIELD_TIME_MS));
436
442
  return makeResult(session, waitedMs, input.max_output_tokens);
437
443
  },
438
444
  write: async (input) => {
@@ -450,7 +456,7 @@ export function createExecSessionManager(): ExecSessionManager {
450
456
  }
451
457
  const waitedMs =
452
458
  session.exitCode === undefined
453
- ? await waitForActivity(session, clampYieldTime(input.yield_time_ms, DEFAULT_WRITE_YIELD_TIME_MS))
459
+ ? await waitForExitOrTimeout(session, clampYieldTime(input.yield_time_ms, DEFAULT_WRITE_YIELD_TIME_MS))
454
460
  : 0;
455
461
  return makeResult(session, waitedMs, input.max_output_tokens);
456
462
  },
@@ -9,8 +9,12 @@ const WEB_SEARCH_LOCAL_EXECUTION_MESSAGE =
9
9
  export const WEB_SEARCH_SESSION_NOTE_TYPE = "codex-web-search-session-note";
10
10
  export const WEB_SEARCH_SESSION_NOTE_TEXT =
11
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;
12
13
 
13
- const WEB_SEARCH_PARAMETERS = Type.Object({}, { additionalProperties: false });
14
+ const WEB_SEARCH_PARAMETERS = Type.Unsafe<Record<string, never>>({
15
+ type: "object",
16
+ additionalProperties: false,
17
+ });
14
18
 
15
19
  interface FunctionToolPayload {
16
20
  type?: unknown;
@@ -22,6 +26,12 @@ interface ResponsesPayload {
22
26
  [key: string]: unknown;
23
27
  }
24
28
 
29
+ interface ResponsesWebSearchTool {
30
+ type: "web_search";
31
+ external_web_access: true;
32
+ search_content_types?: string[];
33
+ }
34
+
25
35
  export function supportsNativeWebSearch(model: ExtensionContext["model"]): boolean {
26
36
  return isOpenAICodexModel(model);
27
37
  }
@@ -34,6 +44,14 @@ export function shouldShowWebSearchSessionNote(
34
44
  return hasUI && !alreadyShown && supportsNativeWebSearch(model);
35
45
  }
36
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
+
37
55
  function isWebSearchFunctionTool(tool: unknown): tool is FunctionToolPayload {
38
56
  return !!tool && typeof tool === "object" && (tool as FunctionToolPayload).type === "function" && (tool as FunctionToolPayload).name === "web_search";
39
57
  }
@@ -55,10 +73,14 @@ export function rewriteNativeWebSearchTool(payload: unknown, model: ExtensionCon
55
73
  }
56
74
  rewritten = true;
57
75
  // Match Codex's native tool shape rather than exposing a synthetic function tool.
58
- return {
76
+ const nativeTool: ResponsesWebSearchTool = {
59
77
  type: "web_search",
60
78
  external_web_access: true,
61
79
  };
80
+ if (supportsMultimodalNativeWebSearch(model)) {
81
+ nativeTool.search_content_types = [...WEB_SEARCH_MULTIMODAL_CONTENT_TYPES];
82
+ }
83
+ return nativeTool;
62
84
  });
63
85
 
64
86
  if (!rewritten) {
@@ -75,8 +97,10 @@ export function createWebSearchTool(): ToolDefinition<typeof WEB_SEARCH_PARAMETE
75
97
  return {
76
98
  name: "web_search",
77
99
  label: "web_search",
78
- description: "Search the internet for sources related to the prompt.",
79
- promptSnippet: "Search the internet for sources related to the prompt.",
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.",
80
104
  parameters: WEB_SEARCH_PARAMETERS,
81
105
  async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
82
106
  if (!supportsNativeWebSearch(ctx.model)) {