pi-openai-codex-compat 0.0.10-alpha.5 → 0.0.10-alpha.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/CHANGELOG.md CHANGED
@@ -21,7 +21,10 @@
21
21
  - Show effective unified-exec yield durations beside accent-colored command
22
22
  tool titles, include explicit working directories, emphasize commands on
23
23
  their own separated rows, separate `apply_patch` results, and mute command
24
- output and protocol metadata.
24
+ output and protocol metadata. Show yielded `exec_command` session IDs on
25
+ their title line, and identify `write_stdin` polls in the title and with the
26
+ original command, working directory, and session ID without repeating that
27
+ context for writes.
25
28
  - Stream live `exec_command`, `write_stdin`, and `shell_command` output in the
26
29
  TUI while each tool call is running instead of hiding updates until it exits
27
30
  or yields.
@@ -6,12 +6,12 @@ import {
6
6
  } from "./codex-tool-surface.ts";
7
7
  import { DEFAULT_CONFIG } from "./config.ts";
8
8
 
9
- type CommandRenderContext = {
9
+ export type CommandRenderContext = {
10
10
  isError: boolean;
11
11
  isPartial: boolean;
12
12
  };
13
13
 
14
- type CommandCallRenderContext = CommandRenderContext & {
14
+ export type CommandCallRenderContext = CommandRenderContext & {
15
15
  executionStarted: boolean;
16
16
  };
17
17
 
@@ -19,6 +19,12 @@ type CommandRenderResult = {
19
19
  content: Array<{ type: string; text?: string }>;
20
20
  };
21
21
 
22
+ type CommandCallMetadata = {
23
+ sessionId?: number;
24
+ };
25
+
26
+ type CommandCallMetadataSource = CommandCallMetadata | (() => CommandCallMetadata);
27
+
22
28
  type CommandOutputPreviewLine = {
23
29
  text: string;
24
30
  sourceLine?: number;
@@ -145,6 +151,59 @@ class CommandResultComponent implements Component {
145
151
  }
146
152
  }
147
153
 
154
+ class CommandCallComponent implements Component {
155
+ private readonly text: Text;
156
+ private readonly title: string;
157
+ private readonly yieldLabel: string;
158
+ private readonly workdirLabel: string;
159
+ private readonly summary: string;
160
+ private readonly theme: RenderTheme;
161
+ private readonly metadata: CommandCallMetadataSource;
162
+ private renderedSessionId: number | undefined;
163
+
164
+ constructor(
165
+ title: string,
166
+ yieldLabel: string,
167
+ workdirLabel: string,
168
+ summary: string,
169
+ theme: RenderTheme,
170
+ metadata: CommandCallMetadataSource,
171
+ ) {
172
+ this.title = title;
173
+ this.yieldLabel = yieldLabel;
174
+ this.workdirLabel = workdirLabel;
175
+ this.summary = summary;
176
+ this.theme = theme;
177
+ this.metadata = metadata;
178
+ this.renderedSessionId = this.resolveSessionId();
179
+ this.text = new Text(this.renderText(this.renderedSessionId), 0, 0);
180
+ }
181
+
182
+ render(width: number): string[] {
183
+ const sessionId = this.resolveSessionId();
184
+ if (sessionId !== this.renderedSessionId) {
185
+ this.renderedSessionId = sessionId;
186
+ this.text.setText(this.renderText(sessionId));
187
+ }
188
+ return this.text.render(width);
189
+ }
190
+
191
+ invalidate(): void {
192
+ this.text.invalidate();
193
+ }
194
+
195
+ private resolveSessionId(): number | undefined {
196
+ const metadata = typeof this.metadata === "function" ? this.metadata() : this.metadata;
197
+ return metadata.sessionId;
198
+ }
199
+
200
+ private renderText(sessionId: number | undefined): string {
201
+ const sessionLabel =
202
+ sessionId === undefined ? "" : ` ${this.theme.fg("muted", `[session id: ${sessionId}]`)}`;
203
+ return `${this.title}${sessionLabel}${this.yieldLabel}${this.workdirLabel}\n\n${this.summary}`;
204
+ }
205
+ }
206
+
148
207
  export function renderCommandCall(
149
208
  toolName: string,
150
209
  command: string,
@@ -153,13 +212,14 @@ export function renderCommandCall(
153
212
  theme: RenderTheme,
154
213
  context: CommandCallRenderContext,
155
214
  resolveBackground: CodexToolBackgroundResolver = () => DEFAULT_CONFIG.toolBackground,
215
+ metadata: CommandCallMetadataSource = {},
156
216
  ): Component {
157
217
  const title = theme.fg("accent", theme.bold(toolName));
158
218
  const yieldLabel = yieldDuration ? ` ${theme.fg("muted", `[yield: ${yieldDuration}]`)}` : "";
159
219
  const workdirLabel = workdir ? ` ${theme.fg("muted", `[workdir: ${workdir}]`)}` : "";
160
220
  const summary = theme.fg("text", command || "…");
161
221
  return new CodexToolSurfaceComponent(
162
- new Text(`${title}${yieldLabel}${workdirLabel}\n\n${summary}`, 0, 0),
222
+ new CommandCallComponent(title, yieldLabel, workdirLabel, summary, theme, metadata),
163
223
  theme,
164
224
  {
165
225
  background: resolveBackground,
@@ -10,14 +10,19 @@ import {
10
10
  type WriteStdinRequest,
11
11
  } from "./command-runtime.ts";
12
12
  import type { CommandProcessSpawner } from "./command-process.ts";
13
+ import type { CommandOutputDetails } from "./command-output.ts";
13
14
  import {
14
15
  commandShellDisplayName,
15
16
  resolveCommandShellCatalog,
16
17
  type CommandShellCatalog,
17
18
  } from "./command-shell.ts";
18
- import type { CodexToolBackgroundResolver } from "./codex-tool-surface.ts";
19
+ import type { CodexToolBackgroundResolver, RenderTheme } from "./codex-tool-surface.ts";
19
20
  import { DEFAULT_CONFIG } from "./config.ts";
20
- import { renderCommandCall, renderCommandResult } from "./command-render.ts";
21
+ import {
22
+ renderCommandCall,
23
+ renderCommandResult,
24
+ type CommandCallRenderContext,
25
+ } from "./command-render.ts";
21
26
  import {
22
27
  EXEC_COMMAND_PARAMETERS,
23
28
  EXEC_COMMAND_TOOL_NAME,
@@ -57,6 +62,20 @@ export type CommandToolsController = {
57
62
  terminateUnifiedExecSessions: () => void;
58
63
  };
59
64
 
65
+ export type WriteStdinRenderProcess = {
66
+ sessionId: number;
67
+ command: string;
68
+ workdir: string;
69
+ };
70
+
71
+ type WriteStdinRenderState = {
72
+ polledProcess?: WriteStdinRenderProcess;
73
+ };
74
+
75
+ type ExecCommandRenderState = {
76
+ sessionId?: number;
77
+ };
78
+
60
79
  // Pi validates the prepared value against SHELL_COMMAND_PARAMETERS immediately
61
80
  // after this compatibility rewrite.
62
81
  export function prepareShellCommandArguments(
@@ -75,14 +94,59 @@ export function prepareShellCommandArguments(args: unknown): unknown {
75
94
  return prepared;
76
95
  }
77
96
 
78
- function writeStdinSummary(request: Partial<WriteStdinRequest>): string {
97
+ function writeStdinSummary(
98
+ request: Partial<WriteStdinRequest>,
99
+ polledProcess: WriteStdinRenderProcess | undefined,
100
+ ): string {
79
101
  const id = typeof request.session_id === "number" ? request.session_id : "?";
80
102
  const chars = typeof request.chars === "string" ? request.chars : "";
81
- if (!chars) return `poll session ${id}`;
103
+ if (!chars) return polledProcess?.command ?? `poll session ${id}`;
82
104
  const singleLine = chars.replaceAll("\n", "\\n").replaceAll("\r", "\\r");
83
105
  return `write ${JSON.stringify(singleLine)} to session ${id}`;
84
106
  }
85
107
 
108
+ export function renderWriteStdinCall(
109
+ request: Partial<WriteStdinRequest>,
110
+ polledProcess: WriteStdinRenderProcess | undefined,
111
+ yieldDuration: string | undefined,
112
+ theme: RenderTheme,
113
+ context: CommandCallRenderContext,
114
+ resolveBackground: CodexToolBackgroundResolver = () => DEFAULT_CONFIG.toolBackground,
115
+ ): ReturnType<typeof renderCommandCall> {
116
+ const polling = !(request.chars ?? "");
117
+ const process = polling ? polledProcess : undefined;
118
+ return renderCommandCall(
119
+ polling ? `${WRITE_STDIN_TOOL_NAME} (polling)` : WRITE_STDIN_TOOL_NAME,
120
+ writeStdinSummary(request, process),
121
+ yieldDuration,
122
+ process?.workdir,
123
+ theme,
124
+ context,
125
+ resolveBackground,
126
+ process ? { sessionId: process.sessionId } : {},
127
+ );
128
+ }
129
+
130
+ function resolvePolledProcess(
131
+ request: Partial<WriteStdinRequest>,
132
+ manager: UnifiedExecManager,
133
+ state: WriteStdinRenderState,
134
+ ): WriteStdinRenderState["polledProcess"] {
135
+ if ((request.chars ?? "") || typeof request.session_id !== "number") return undefined;
136
+ if (state.polledProcess?.sessionId === request.session_id) return state.polledProcess;
137
+ const process = manager.observeProcess(request.session_id)?.();
138
+ if (!process) {
139
+ Reflect.deleteProperty(state, "polledProcess");
140
+ return undefined;
141
+ }
142
+ state.polledProcess = {
143
+ sessionId: process.sessionId,
144
+ command: process.command,
145
+ workdir: process.cwd,
146
+ };
147
+ return state.polledProcess;
148
+ }
149
+
86
150
  function formatYieldDuration(milliseconds: number): string {
87
151
  return milliseconds >= 1_000 ? `${milliseconds / 1_000}s` : `${milliseconds}ms`;
88
152
  }
@@ -185,7 +249,7 @@ export default function registerCommandTools(
185
249
  },
186
250
  });
187
251
 
188
- pi.registerTool({
252
+ pi.registerTool<typeof EXEC_COMMAND_PARAMETERS, CommandOutputDetails, ExecCommandRenderState>({
189
253
  name: EXEC_COMMAND_TOOL_NAME,
190
254
  label: EXEC_COMMAND_TOOL_NAME,
191
255
  description: execCommandPrompt.description,
@@ -197,6 +261,7 @@ export default function registerCommandTools(
197
261
  return manager.execCommand(params satisfies ExecCommandRequest, ctx, signal, onUpdate);
198
262
  },
199
263
  renderCall(args, theme, context) {
264
+ const state = context.state;
200
265
  return renderCommandCall(
201
266
  EXEC_COMMAND_TOOL_NAME,
202
267
  typeof args.cmd === "string" ? args.cmd : "",
@@ -205,14 +270,22 @@ export default function registerCommandTools(
205
270
  theme,
206
271
  context,
207
272
  resolveToolBackground,
273
+ () => (state.sessionId === undefined ? {} : { sessionId: state.sessionId }),
208
274
  );
209
275
  },
210
276
  renderResult(result, options, theme, context) {
277
+ if (!options.isPartial) {
278
+ if (result.details.sessionId === undefined) {
279
+ Reflect.deleteProperty(context.state, "sessionId");
280
+ } else {
281
+ context.state.sessionId = result.details.sessionId;
282
+ }
283
+ }
211
284
  return renderCommandResult(result, options, theme, context, resolveToolBackground);
212
285
  },
213
286
  });
214
287
 
215
- pi.registerTool({
288
+ pi.registerTool<typeof WRITE_STDIN_PARAMETERS, CommandOutputDetails, WriteStdinRenderState>({
216
289
  name: WRITE_STDIN_TOOL_NAME,
217
290
  label: WRITE_STDIN_TOOL_NAME,
218
291
  description: writeStdinPrompt.description,
@@ -224,13 +297,13 @@ export default function registerCommandTools(
224
297
  return manager.writeStdin(params satisfies WriteStdinRequest, signal, onUpdate);
225
298
  },
226
299
  renderCall(args, theme, context) {
227
- return renderCommandCall(
228
- WRITE_STDIN_TOOL_NAME,
229
- writeStdinSummary(args),
300
+ const polledProcess = resolvePolledProcess(args, manager, context.state);
301
+ return renderWriteStdinCall(
302
+ args,
303
+ polledProcess,
230
304
  resolveYieldDuration(args.yield_time_ms, (value) =>
231
305
  effectiveWriteStdinYieldTimeMs(value, !(args.chars ?? "")),
232
306
  ),
233
- undefined,
234
307
  theme,
235
308
  context,
236
309
  resolveToolBackground,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-openai-codex-compat",
3
- "version": "0.0.10-alpha.5",
3
+ "version": "0.0.10-alpha.6",
4
4
  "description": "OpenAI Codex compatibility for Pi with native compaction, fast mode, and Codex-optimized capabilities",
5
5
  "keywords": [
6
6
  "pi-package"