@howaboua/pi-codex-conversion 1.5.4-dev.20.3249745 → 1.5.5-dev.22.90056d4

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.5.5
4
+
5
+ - Avoid registering disabled native `web_search` and `image_generation` tools so other extensions can own those names.
6
+ - Preserve other extensions' `web_search` and `image_generation` tools when the matching Codex feature is off.
7
+ - Added a `/codex status` toggle and settings UI option for hiding the Codex footer/statusline.
8
+
3
9
  ## 1.5.4
4
10
 
5
11
  - Added `/codex` settings UI.
package/README.md CHANGED
@@ -42,6 +42,7 @@ Notably:
42
42
  Use `/codex` to change adapter settings.
43
43
 
44
44
  - `/codex all` — use the Codex tool and prompt adapter on every model
45
+ - `/codex status` — toggle the footer/statusline entry
45
46
  - `/codex fast` — toggle priority service tier for the OpenAI Codex provider
46
47
  - `/codex search` — toggle native Codex web search
47
48
  - `/codex image` — toggle native Codex image generation
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@howaboua/pi-codex-conversion",
3
- "version": "1.5.4-dev.20.3249745",
3
+ "version": "1.5.5-dev.22.90056d4",
4
4
  "description": "Codex-oriented tool and prompt adapter for pi coding agent",
5
5
  "type": "module",
6
6
  "repository": {
@@ -15,6 +15,7 @@ import { supportsNativeImageGeneration } from "../tools/image-generation-tool.ts
15
15
  import { supportsNativeWebSearch } from "../tools/web-search-tool.ts";
16
16
 
17
17
  const ADAPTER_TOOL_NAMES = [...CORE_ADAPTER_TOOL_NAMES, WEB_SEARCH_TOOL_NAME, IMAGE_GENERATION_TOOL_NAME, VIEW_IMAGE_TOOL_NAME];
18
+ const ALWAYS_OWNED_ADAPTER_TOOL_NAMES = [...CORE_ADAPTER_TOOL_NAMES, VIEW_IMAGE_TOOL_NAME];
18
19
 
19
20
  export function syncAdapter(pi: ExtensionAPI, ctx: ExtensionContext, state: AdapterState): void {
20
21
  if (shouldUseCodexAdapter(ctx, state.config)) {
@@ -29,32 +30,41 @@ export function shouldUseCodexAdapter(ctx: ExtensionContext, config: CodexConver
29
30
  }
30
31
 
31
32
  function enableAdapter(pi: ExtensionAPI, ctx: ExtensionContext, state: AdapterState): void {
32
- const toolNames = mergeAdapterTools(pi.getActiveTools(), getAdapterToolNames(ctx, state.config));
33
+ const currentAdapterOwnedTools = getAdapterOwnedToolNames(state.config);
34
+ const adapterOwnedTools = state.enabled ? mergeToolNames(state.adapterOwnedToolNames ?? currentAdapterOwnedTools, currentAdapterOwnedTools) : currentAdapterOwnedTools;
35
+ const toolNames = mergeAdapterTools(pi.getActiveTools(), getAdapterToolNames(ctx, state.config), adapterOwnedTools);
33
36
  if (!state.enabled) {
34
37
  // Preserve the previous active set once so switching away from Codex-like
35
38
  // models restores the user's existing Pi tool configuration. Strip adapter
36
39
  // tools in case a fresh session starts from persisted/mixed active tools.
37
- state.previousToolNames = stripAdapterTools(pi.getActiveTools());
40
+ state.previousToolNames = stripAdapterTools(pi.getActiveTools(), adapterOwnedTools);
38
41
  state.enabled = true;
39
42
  }
43
+ state.adapterOwnedToolNames = currentAdapterOwnedTools;
40
44
  pi.setActiveTools(toolNames);
41
45
  setStatus(ctx, true, state.config);
42
46
  }
43
47
 
44
48
  function disableAdapter(pi: ExtensionAPI, ctx: ExtensionContext, state: AdapterState): void {
45
49
  const previousToolNames = state.previousToolNames && state.previousToolNames.length > 0 ? state.previousToolNames : DEFAULT_TOOL_NAMES;
46
- const restoredTools = restoreTools(previousToolNames, pi.getActiveTools());
47
- if (state.enabled || hasAdapterTools(pi.getActiveTools())) {
50
+ const adapterOwnedTools = state.adapterOwnedToolNames ?? getAdapterOwnedToolNames(state.config);
51
+ const restoredTools = restoreTools(previousToolNames, pi.getActiveTools(), adapterOwnedTools);
52
+ if (state.enabled || hasAdapterTools(pi.getActiveTools(), adapterOwnedTools)) {
48
53
  pi.setActiveTools(restoredTools);
49
54
  }
50
55
  if (state.enabled) {
51
56
  state.enabled = false;
57
+ state.adapterOwnedToolNames = undefined;
52
58
  }
53
59
  setStatus(ctx, false, state.config);
54
60
  }
55
61
 
56
62
  function setStatus(ctx: ExtensionContext, enabled: boolean, config: CodexConversionConfig): void {
57
63
  if (!ctx.hasUI) return;
64
+ if (!config.statusLine) {
65
+ ctx.ui.setStatus(STATUS_KEY, undefined);
66
+ return;
67
+ }
58
68
  const statusConfig = getStatusConfig(ctx, config);
59
69
  ctx.ui.setStatus(STATUS_KEY, enabled ? buildStatusText(statusConfig) : undefined);
60
70
  }
@@ -85,25 +95,38 @@ function getAdapterToolNames(ctx: ExtensionContext, config: CodexConversionConfi
85
95
  return toolNames;
86
96
  }
87
97
 
88
- export function mergeAdapterTools(activeTools: string[], adapterTools: string[]): string[] {
89
- const preservedTools = activeTools.filter((toolName) => !DEFAULT_TOOL_NAMES.includes(toolName) && !ADAPTER_TOOL_NAMES.includes(toolName));
98
+ function getAdapterOwnedToolNames(config: CodexConversionConfig): string[] {
99
+ return [
100
+ ...ALWAYS_OWNED_ADAPTER_TOOL_NAMES,
101
+ ...(config.webSearch ? [WEB_SEARCH_TOOL_NAME] : []),
102
+ ...(config.imageGeneration ? [IMAGE_GENERATION_TOOL_NAME] : []),
103
+ ];
104
+ }
105
+
106
+ function mergeToolNames(...toolNameGroups: string[][]): string[] {
107
+ return [...new Set(toolNameGroups.flat())];
108
+ }
109
+
110
+ export function mergeAdapterTools(activeTools: string[], adapterTools: string[], adapterOwnedTools: string[] = adapterTools): string[] {
111
+ const ownedTools = new Set([...ALWAYS_OWNED_ADAPTER_TOOL_NAMES, ...adapterTools, ...adapterOwnedTools]);
112
+ const preservedTools = activeTools.filter((toolName) => !DEFAULT_TOOL_NAMES.includes(toolName) && !ownedTools.has(toolName));
90
113
  return [...adapterTools, ...preservedTools];
91
114
  }
92
115
 
93
- export function restoreTools(previousTools: string[], activeTools: string[]): string[] {
94
- const restored = stripAdapterTools(previousTools);
116
+ export function restoreTools(previousTools: string[], activeTools: string[], adapterOwnedTools: string[] = ADAPTER_TOOL_NAMES): string[] {
117
+ const restored = stripAdapterTools(previousTools, adapterOwnedTools);
95
118
  for (const toolName of activeTools) {
96
- if (!ADAPTER_TOOL_NAMES.includes(toolName) && !restored.includes(toolName)) {
119
+ if (!adapterOwnedTools.includes(toolName) && !restored.includes(toolName)) {
97
120
  restored.push(toolName);
98
121
  }
99
122
  }
100
123
  return restored;
101
124
  }
102
125
 
103
- export function stripAdapterTools(toolNames: string[]): string[] {
104
- return toolNames.filter((toolName) => !ADAPTER_TOOL_NAMES.includes(toolName));
126
+ export function stripAdapterTools(toolNames: string[], adapterOwnedTools: string[] = ADAPTER_TOOL_NAMES): string[] {
127
+ return toolNames.filter((toolName) => !adapterOwnedTools.includes(toolName));
105
128
  }
106
129
 
107
- function hasAdapterTools(activeTools: string[]): boolean {
108
- return activeTools.some((toolName) => ADAPTER_TOOL_NAMES.includes(toolName));
130
+ function hasAdapterTools(activeTools: string[], adapterOwnedTools: string[]): boolean {
131
+ return activeTools.some((toolName) => adapterOwnedTools.includes(toolName));
109
132
  }
@@ -7,6 +7,7 @@ export type CodexVerbosity = "low" | "medium" | "high";
7
7
  export interface CodexConversionConfig {
8
8
  fast: boolean;
9
9
  imageGeneration: boolean;
10
+ statusLine: boolean;
10
11
  useOnAllModels: boolean;
11
12
  webSearch: boolean;
12
13
  verbosity: CodexVerbosity;
@@ -16,6 +17,7 @@ export const CODEX_CONVERSION_CONFIG_BASENAME = "pi-codex-conversion.json";
16
17
  export const DEFAULT_CODEX_CONVERSION_CONFIG: CodexConversionConfig = {
17
18
  fast: false,
18
19
  imageGeneration: true,
20
+ statusLine: true,
19
21
  useOnAllModels: false,
20
22
  webSearch: true,
21
23
  verbosity: "low",
@@ -47,6 +49,7 @@ export function readCodexConversionConfig(configPath: string = getCodexConversio
47
49
  return {
48
50
  fast: typeof parsed.fast === "boolean" ? parsed.fast : DEFAULT_CODEX_CONVERSION_CONFIG.fast,
49
51
  imageGeneration: typeof parsed.imageGeneration === "boolean" ? parsed.imageGeneration : DEFAULT_CODEX_CONVERSION_CONFIG.imageGeneration,
52
+ statusLine: typeof parsed.statusLine === "boolean" ? parsed.statusLine : DEFAULT_CODEX_CONVERSION_CONFIG.statusLine,
50
53
  useOnAllModels: typeof parsed.useOnAllModels === "boolean" ? parsed.useOnAllModels : DEFAULT_CODEX_CONVERSION_CONFIG.useOnAllModels,
51
54
  webSearch: typeof parsed.webSearch === "boolean" ? parsed.webSearch : DEFAULT_CODEX_CONVERSION_CONFIG.webSearch,
52
55
  verbosity: normalizeCodexVerbosity(parsed.verbosity) ?? DEFAULT_CODEX_CONVERSION_CONFIG.verbosity,
@@ -4,6 +4,7 @@ import type { CodexConversionConfig } from "./config.ts";
4
4
  export interface AdapterState {
5
5
  enabled: boolean;
6
6
  cwd: string;
7
+ adapterOwnedToolNames?: string[];
7
8
  previousToolNames?: string[];
8
9
  promptSkills: PromptSkill[];
9
10
  config: CodexConversionConfig;
@@ -9,9 +9,9 @@ import { syncAdapter } from "../adapter/activation.ts";
9
9
  import type { AdapterState } from "../adapter/state.ts";
10
10
  import { openCodexSettingsScreen } from "./ui.ts";
11
11
 
12
- const CODEX_COMMAND_COMPLETIONS = ["all", "fast", "search", "image", "low", "medium", "high"] as const;
12
+ const CODEX_COMMAND_COMPLETIONS = ["all", "status", "fast", "search", "image", "low", "medium", "high"] as const;
13
13
 
14
- export function registerCodexCommand(pi: ExtensionAPI, state: AdapterState): void {
14
+ export function registerCodexCommand(pi: ExtensionAPI, state: AdapterState, onConfigApplied?: (config: CodexConversionConfig) => void): void {
15
15
  function saveAndApply(ctx: ExtensionContext, nextConfig: CodexConversionConfig): boolean {
16
16
  const writeResult = writeCodexConversionConfig(nextConfig);
17
17
  if (!writeResult.ok) {
@@ -19,6 +19,7 @@ export function registerCodexCommand(pi: ExtensionAPI, state: AdapterState): voi
19
19
  return false;
20
20
  }
21
21
  state.config = nextConfig;
22
+ onConfigApplied?.(nextConfig);
22
23
  syncAdapter(pi, ctx, state);
23
24
  return true;
24
25
  }
@@ -37,7 +38,7 @@ export function registerCodexCommand(pi: ExtensionAPI, state: AdapterState): voi
37
38
  }
38
39
 
39
40
  if (arg) {
40
- ctx.ui.notify("Usage: /codex, /codex all, /codex fast, /codex search, /codex image, /codex low|medium|high", "warning");
41
+ ctx.ui.notify("Usage: /codex, /codex all, /codex status, /codex fast, /codex search, /codex image, /codex low|medium|high", "warning");
41
42
  return;
42
43
  }
43
44
 
@@ -57,6 +58,7 @@ export function registerCodexCommand(pi: ExtensionAPI, state: AdapterState): voi
57
58
  function getCommandConfigUpdate(arg: string, config: CodexConversionConfig): CodexConversionConfig | undefined {
58
59
  if (arg === "fast") return { ...config, fast: !config.fast };
59
60
  if (arg === "all") return { ...config, useOnAllModels: !config.useOnAllModels };
61
+ if (arg === "status") return { ...config, statusLine: !config.statusLine };
60
62
  if (arg === "search") return { ...config, webSearch: !config.webSearch };
61
63
  if (arg === "image") return { ...config, imageGeneration: !config.imageGeneration };
62
64
  const verbosity = normalizeCodexVerbosity(arg);
@@ -64,5 +66,5 @@ function getCommandConfigUpdate(arg: string, config: CodexConversionConfig): Cod
64
66
  }
65
67
 
66
68
  function formatCodexSettings(config: CodexConversionConfig): string {
67
- return `Codex settings: all models ${config.useOnAllModels ? "on" : "off"}, fast ${config.fast ? "on" : "off"}, web search ${config.webSearch ? "on" : "off"}, image generation ${config.imageGeneration ? "on" : "off"}, verbosity ${config.verbosity}`;
69
+ return `Codex settings: all models ${config.useOnAllModels ? "on" : "off"}, statusline ${config.statusLine ? "on" : "off"}, fast ${config.fast ? "on" : "off"}, web search ${config.webSearch ? "on" : "off"}, image generation ${config.imageGeneration ? "on" : "off"}, verbosity ${config.verbosity}`;
68
70
  }
@@ -13,6 +13,7 @@ export async function openCodexSettingsScreen(ctx: ExtensionContext, options: Co
13
13
  await ctx.ui.custom<void>((tui, theme, _kb, done) => {
14
14
  const buildItems = (): SettingItem[] => [
15
15
  { id: "useOnAllModels", label: "Use on all models", currentValue: draft.useOnAllModels ? "on" : "off", values: ["off", "on"] },
16
+ { id: "statusLine", label: "Statusline", currentValue: draft.statusLine ? "on" : "off", values: ["off", "on"] },
16
17
  { id: "fast", label: "Fast mode", currentValue: draft.fast ? "on" : "off", values: ["off", "on"] },
17
18
  { id: "webSearch", label: "Web search", currentValue: draft.webSearch ? "on" : "off", values: ["off", "on"] },
18
19
  { id: "imageGeneration", label: "Image generation", currentValue: draft.imageGeneration ? "on" : "off", values: ["off", "on"] },
@@ -27,6 +28,7 @@ export async function openCodexSettingsScreen(ctx: ExtensionContext, options: Co
27
28
  const nextDraft = { ...draft };
28
29
  const previousValue = buildItems().find((item) => item.id === id)?.currentValue;
29
30
  if (id === "useOnAllModels") nextDraft.useOnAllModels = value === "on";
31
+ if (id === "statusLine") nextDraft.statusLine = value === "on";
30
32
  if (id === "fast") nextDraft.fast = value === "on";
31
33
  if (id === "webSearch") nextDraft.webSearch = value === "on";
32
34
  if (id === "imageGeneration") nextDraft.imageGeneration = value === "on";
package/src/index.ts CHANGED
@@ -44,6 +44,19 @@ export default function codexConversion(pi: ExtensionAPI) {
44
44
  const tracker = createExecCommandTracker();
45
45
  const state: AdapterState = { enabled: false, cwd: process.cwd(), promptSkills: [], config: readCodexConversionConfig() };
46
46
  const sessions = createExecSessionManager();
47
+ let nativeWebSearchRegistered = false;
48
+ let nativeImageGenerationRegistered = false;
49
+
50
+ function ensureOptionalNativeToolsRegistered(config = state.config): void {
51
+ if (config.webSearch && !nativeWebSearchRegistered) {
52
+ registerWebSearchTool(pi);
53
+ nativeWebSearchRegistered = true;
54
+ }
55
+ if (config.imageGeneration && !nativeImageGenerationRegistered) {
56
+ registerImageGenerationTool(pi);
57
+ nativeImageGenerationRegistered = true;
58
+ }
59
+ }
47
60
 
48
61
  registerOpenAICodexCustomProvider(pi, {
49
62
  getCurrentCwd: () => state.cwd,
@@ -51,9 +64,8 @@ export default function codexConversion(pi: ExtensionAPI) {
51
64
  registerApplyPatchTool(pi);
52
65
  registerExecCommandTool(pi, tracker, sessions);
53
66
  registerWriteStdinTool(pi, sessions);
54
- registerImageGenerationTool(pi);
55
- registerWebSearchTool(pi);
56
- registerCodexCommand(pi, state);
67
+ ensureOptionalNativeToolsRegistered();
68
+ registerCodexCommand(pi, state, ensureOptionalNativeToolsRegistered);
57
69
 
58
70
  sessions.onSessionExit((sessionId) => {
59
71
  tracker.recordSessionFinished(sessionId);
@@ -62,6 +74,7 @@ export default function codexConversion(pi: ExtensionAPI) {
62
74
  pi.on("session_start", async (_event, ctx) => {
63
75
  state.cwd = ctx.cwd;
64
76
  state.config = readCodexConversionConfig();
77
+ ensureOptionalNativeToolsRegistered();
65
78
  state.promptSkills = extractPiPromptSkills(ctx.getSystemPrompt());
66
79
  registerViewImageTool(pi, { allowOriginalDetail: supportsOriginalImageDetail(ctx.model) });
67
80
  clearApplyPatchRenderState();