@howaboua/pi-codex-conversion 1.5.1 → 1.5.2

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.5.1",
3
+ "version": "1.5.2",
4
4
  "description": "Codex-oriented tool and prompt adapter for pi coding agent",
5
5
  "type": "module",
6
6
  "repository": {
@@ -110,7 +110,7 @@ const renderExecCommandResultWithOptionalContext: any = (
110
110
  context: ExecCommandRenderContextLike | undefined,
111
111
  tracker: ExecCommandTracker,
112
112
  ) => {
113
- if (options.isPartial || !options.expanded) {
113
+ if (!options.expanded) {
114
114
  return createEmptyResultComponent();
115
115
  }
116
116
 
@@ -140,12 +140,16 @@ export function registerExecCommandTool(pi: ExtensionAPI, tracker: ExecCommandTr
140
140
  promptSnippet: "Run a command.",
141
141
  parameters: EXEC_COMMAND_PARAMETERS,
142
142
  prepareArguments: prepareExecCommandArguments,
143
- async execute(toolCallId, params, signal, _onUpdate, ctx) {
143
+ async execute(toolCallId, params, signal, onUpdate, ctx) {
144
144
  if (signal?.aborted) {
145
145
  throw new Error("exec_command aborted");
146
146
  }
147
147
  const typedParams = parseExecCommandParams(params);
148
- const result = await sessions.exec(typedParams, ctx.cwd, signal);
148
+ const toToolResult = (partial: UnifiedExecResult) => ({
149
+ content: [{ type: "text" as const, text: formatUnifiedExecResult(partial, typedParams.cmd) }],
150
+ details: partial,
151
+ });
152
+ const result = await sessions.exec(typedParams, ctx.cwd, signal, onUpdate ? (partial) => onUpdate(toToolResult(partial)) : undefined);
149
153
  if (result.session_id !== undefined) {
150
154
  tracker.recordPersistentSession(toolCallId, result.session_id);
151
155
  }
@@ -56,9 +56,11 @@ interface PtyExecSession extends BaseExecSession {
56
56
 
57
57
  type ExecSession = PipeExecSession | PtyExecSession;
58
58
 
59
+ export type ExecSessionUpdateCallback = (result: UnifiedExecResult) => void;
60
+
59
61
  export interface ExecSessionManager {
60
- exec(input: ExecCommandInput, cwd: string, signal?: AbortSignal): Promise<UnifiedExecResult>;
61
- write(input: WriteStdinInput): Promise<UnifiedExecResult>;
62
+ exec(input: ExecCommandInput, cwd: string, signal?: AbortSignal, onUpdate?: ExecSessionUpdateCallback): Promise<UnifiedExecResult>;
63
+ write(input: WriteStdinInput, onUpdate?: ExecSessionUpdateCallback): Promise<UnifiedExecResult>;
62
64
  hasSession(sessionId: number): boolean;
63
65
  getSessionCommand(sessionId: number): string | undefined;
64
66
  onSessionExit(listener: (sessionId: number, command: string) => void): () => void;
@@ -311,10 +313,7 @@ function generateChunkId(): string {
311
313
  return randomBytes(3).toString("hex");
312
314
  }
313
315
 
314
- function consumeOutput(session: ExecSession, maxOutputTokens?: number): { output: string; original_token_count?: number } {
315
- const text =
316
- session.kind === "pty" ? computePtyDelta(session.emittedBuffer, session.buffer) : session.buffer.slice(session.emittedBuffer.length);
317
- session.emittedBuffer = session.buffer;
316
+ function truncateOutput(text: string, maxOutputTokens?: number): { output: string; original_token_count?: number } {
318
317
  if (text.length === 0) {
319
318
  return { output: "" };
320
319
  }
@@ -331,6 +330,24 @@ function consumeOutput(session: ExecSession, maxOutputTokens?: number): { output
331
330
  };
332
331
  }
333
332
 
333
+ function consumeOutput(session: ExecSession, maxOutputTokens?: number): { output: string; original_token_count?: number } {
334
+ const text =
335
+ session.kind === "pty" ? computePtyDelta(session.emittedBuffer, session.buffer) : session.buffer.slice(session.emittedBuffer.length);
336
+ session.emittedBuffer = session.buffer;
337
+ return truncateOutput(text, maxOutputTokens);
338
+ }
339
+
340
+ function peekUnconsumedOutput(session: ExecSession, maxOutputTokens?: number): { output: string; original_token_count?: number } {
341
+ const text =
342
+ session.kind === "pty" ? computePtyDelta(session.emittedBuffer, session.buffer) : session.buffer.slice(session.emittedBuffer.length);
343
+ return truncateOutput(text, maxOutputTokens);
344
+ }
345
+
346
+ function peekOutputSince(session: ExecSession, baseline: string, maxOutputTokens?: number): { output: string; original_token_count?: number } {
347
+ const text = session.kind === "pty" ? computePtyDelta(baseline, session.buffer) : session.buffer.slice(baseline.length);
348
+ return truncateOutput(text, maxOutputTokens);
349
+ }
350
+
334
351
  function registerAbortHandler(signal: AbortSignal | undefined, onAbort: () => void): () => void {
335
352
  if (!signal) {
336
353
  return () => {};
@@ -398,17 +415,31 @@ export function createExecSessionManager(options: ExecSessionManagerOptions = {}
398
415
  notify(session);
399
416
  }
400
417
 
401
- function waitForExitOrTimeout(session: ExecSession, yieldTimeMs: number): Promise<number> {
418
+ function waitForExitOrTimeout(
419
+ session: ExecSession,
420
+ yieldTimeMs: number,
421
+ onUpdate?: (elapsedMs: number) => void,
422
+ ): Promise<number> {
402
423
  if (session.exitCode !== undefined && session.exitCode !== null) {
403
424
  return Promise.resolve(0);
404
425
  }
405
426
 
406
427
  const startedAt = Date.now();
428
+ let updateTimer: ReturnType<typeof setInterval> | undefined;
429
+ let lastUpdateAt = 0;
407
430
  return new Promise((resolvePromise) => {
431
+ const emitUpdate = (force = false) => {
432
+ const now = Date.now();
433
+ if (!force && now - lastUpdateAt < 250) return;
434
+ lastUpdateAt = now;
435
+ onUpdate?.(now - startedAt);
436
+ };
408
437
  const onWake = () => {
409
438
  if (session.exitCode === undefined || session.exitCode === null) {
439
+ emitUpdate();
410
440
  return;
411
441
  }
442
+ emitUpdate(true);
412
443
  cleanup();
413
444
  resolvePromise(Date.now() - startedAt);
414
445
  };
@@ -416,8 +447,12 @@ export function createExecSessionManager(options: ExecSessionManagerOptions = {}
416
447
  cleanup();
417
448
  resolvePromise(Date.now() - startedAt);
418
449
  }, yieldTimeMs);
450
+ if (onUpdate) {
451
+ updateTimer = setInterval(emitUpdate, 250);
452
+ }
419
453
  const cleanup = () => {
420
454
  clearTimeout(timeout);
455
+ if (updateTimer) clearInterval(updateTimer);
421
456
  session.listeners.delete(onWake);
422
457
  };
423
458
  session.listeners.add(onWake);
@@ -445,6 +480,36 @@ export function createExecSessionManager(options: ExecSessionManagerOptions = {}
445
480
  return result;
446
481
  }
447
482
 
483
+ function makeSnapshotResult(session: ExecSession, waitMs: number, maxOutputTokens?: number, unconsumedOnly = false): UnifiedExecResult {
484
+ const snapshot = unconsumedOnly ? peekUnconsumedOutput(session, maxOutputTokens) : truncateOutput(session.buffer, maxOutputTokens);
485
+ return makeSnapshotFromOutput(session, waitMs, snapshot);
486
+ }
487
+
488
+ function makeSnapshotSince(session: ExecSession, waitMs: number, baseline: string, maxOutputTokens?: number): UnifiedExecResult {
489
+ return makeSnapshotFromOutput(session, waitMs, peekOutputSince(session, baseline, maxOutputTokens));
490
+ }
491
+
492
+ function makeSnapshotFromOutput(
493
+ session: ExecSession,
494
+ waitMs: number,
495
+ snapshot: { output: string; original_token_count?: number },
496
+ ): UnifiedExecResult {
497
+ const result: UnifiedExecResult = {
498
+ chunk_id: generateChunkId(),
499
+ wall_time_seconds: waitMs / 1000,
500
+ output: snapshot.output,
501
+ };
502
+ if (snapshot.original_token_count !== undefined) {
503
+ result.original_token_count = snapshot.original_token_count;
504
+ }
505
+ if (session.exitCode === undefined || session.exitCode === null) {
506
+ result.session_id = session.id;
507
+ } else {
508
+ result.exit_code = session.exitCode;
509
+ }
510
+ return result;
511
+ }
512
+
448
513
  function createPipeSession(input: ExecCommandInput, workdir: string, shell: string, signal?: AbortSignal): PipeExecSession {
449
514
  const login = input.login ?? true;
450
515
  const execution = resolveExecution(input.shell, input.cmd);
@@ -537,7 +602,7 @@ export function createExecSessionManager(options: ExecSessionManagerOptions = {}
537
602
  }
538
603
 
539
604
  return {
540
- exec: async (input, cwd, signal) => {
605
+ exec: async (input, cwd, signal, onUpdate) => {
541
606
  const shell = resolveShell(input.shell);
542
607
  const workdir = resolveWorkdir(cwd, input.workdir);
543
608
  const session = input.tty
@@ -546,17 +611,20 @@ export function createExecSessionManager(options: ExecSessionManagerOptions = {}
546
611
  sessions.set(session.id, session);
547
612
  rememberCommand(session.id, session.command);
548
613
 
614
+ onUpdate?.(makeSnapshotResult(session, 0, input.max_output_tokens, true));
549
615
  const waitedMs = await waitForExitOrTimeout(
550
616
  session,
551
617
  clampExecYieldTime(input.yield_time_ms, defaultExecYieldTimeMs, session.interactive, minNonInteractiveExecYieldTimeMs),
618
+ onUpdate ? (elapsedMs) => onUpdate(makeSnapshotResult(session, elapsedMs, input.max_output_tokens)) : undefined,
552
619
  );
553
620
  return makeResult(session, waitedMs, input.max_output_tokens);
554
621
  },
555
- write: async (input) => {
622
+ write: async (input, onUpdate) => {
556
623
  const session = sessions.get(input.session_id);
557
624
  if (!session) {
558
625
  throw new Error(`Unknown process id ${input.session_id}`);
559
626
  }
627
+ const updateBaseline = session.buffer;
560
628
  if (input.chars && input.chars.length > 0) {
561
629
  if (!session.interactive) {
562
630
  throw new Error("stdin is closed for this session; rerun exec_command with tty=true to keep stdin open");
@@ -565,6 +633,7 @@ export function createExecSessionManager(options: ExecSessionManagerOptions = {}
565
633
  session.child.write(input.chars);
566
634
  }
567
635
  }
636
+ onUpdate?.(makeSnapshotSince(session, 0, updateBaseline, input.max_output_tokens));
568
637
  const waitedMs =
569
638
  session.exitCode === undefined
570
639
  ? await waitForExitOrTimeout(
@@ -575,6 +644,7 @@ export function createExecSessionManager(options: ExecSessionManagerOptions = {}
575
644
  !input.chars || input.chars.length === 0,
576
645
  minEmptyWriteYieldTimeMs,
577
646
  ),
647
+ onUpdate ? (elapsedMs) => onUpdate(makeSnapshotSince(session, elapsedMs, updateBaseline, input.max_output_tokens)) : undefined,
578
648
  )
579
649
  : 0;
580
650
  return makeResult(session, waitedMs, input.max_output_tokens);
@@ -111,12 +111,16 @@ export function registerWriteStdinTool(pi: ExtensionAPI, sessions: ExecSessionMa
111
111
  description: "Writes to or polls a running exec session.",
112
112
  promptSnippet: "Write to an exec session.",
113
113
  parameters: WRITE_STDIN_PARAMETERS,
114
- async execute(_toolCallId, params) {
114
+ async execute(_toolCallId, params, _signal, onUpdate) {
115
115
  const typed = parseWriteStdinParams(params);
116
116
  const command = sessions.getSessionCommand(typed.session_id);
117
117
  let result: UnifiedExecResult;
118
118
  try {
119
- result = await sessions.write(typed);
119
+ const toToolResult = (partial: UnifiedExecResult) => ({
120
+ content: [{ type: "text" as const, text: formatUnifiedExecResult(partial, command) }],
121
+ details: partial,
122
+ });
123
+ result = await sessions.write(typed, onUpdate ? (partial) => onUpdate(toToolResult(partial)) : undefined);
120
124
  } catch (error) {
121
125
  const message = error instanceof Error ? error.message : String(error);
122
126
  throw new Error(`write_stdin failed: ${message}`);
@@ -133,7 +137,7 @@ export function registerWriteStdinTool(pi: ExtensionAPI, sessions: ExecSessionMa
133
137
  return new Text(renderWriteStdinCall(sessionId, input, command, theme), 0, 0);
134
138
  },
135
139
  renderResult(result, { expanded, isPartial }, theme) {
136
- if (isPartial || !expanded) return createEmptyResultComponent();
140
+ if (!expanded) return createEmptyResultComponent();
137
141
  const state = getResultState(result);
138
142
  const output = renderTerminalText(state.output);
139
143
  let text = theme.fg("dim", output || "(no output)");