@ian-pascoe/pi-codemode 0.1.0 → 0.3.0

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.
@@ -39,7 +39,7 @@ import {
39
39
  } from "./pi-tool-bridge.js";
40
40
 
41
41
  const CODEMODE_EXECUTE_DESCRIPTION =
42
- "Execute a TypeScript Cell in a persistent isolated Deno CodeMode Session. Top-level declarations become Notebook Bindings. Use the read-only tools object for registered Pi tools.";
42
+ "Execute a TypeScript Cell in a persistent isolated Deno CodeMode Session. Reuse a Session ID to retain Notebook Bindings; a new Session reclaims the least-recently-used idle Session at capacity. Use the read-only tools object for registered Pi tools. Cells may call console.log, console.info, console.warn, console.error, and console.debug; captured output arrives only with terminal results.";
43
43
 
44
44
  type PiCodeModeGeneration = {
45
45
  readonly captured: CapturedPiAgentSession;
@@ -126,11 +126,11 @@ class PiCodeModeLifecycleController {
126
126
  this.pi.on("session_start", async (_event, context) => this.startSession(context));
127
127
  this.pi.on("before_agent_start", () => this.synchronizeCurrentGeneration());
128
128
  this.pi.on("tool_execution_end", () => this.synchronizeCurrentGeneration());
129
- this.pi.on("session_shutdown", async (event) => this.shutdownSession(event.reason));
129
+ this.pi.on("session_shutdown", async () => this.shutdownSession());
130
130
  }
131
131
 
132
132
  private async startSession(context: ExtensionContext): Promise<void> {
133
- await this.shutdownSession("replacement");
133
+ await this.shutdownSession();
134
134
  const capturedResult = capturePiAgentSession(this.pi);
135
135
  if (!capturedResult.ok) {
136
136
  this.notifyWarning(context, capturedResult.warning);
@@ -207,6 +207,10 @@ class PiCodeModeLifecycleController {
207
207
  },
208
208
  result: async (input) => coordinator.result(input.sessionId),
209
209
  cancel: async (input) => coordinator.cancel(input.sessionId),
210
+ sessions: async () => ({
211
+ result: "success",
212
+ sessions: [...coordinator.listSessions()],
213
+ }),
210
214
  };
211
215
  generation = {
212
216
  captured,
@@ -243,7 +247,7 @@ class PiCodeModeLifecycleController {
243
247
  } catch {
244
248
  // Observer cleanup is presentation-only; execution resources still require release.
245
249
  }
246
- await coordinator.shutdown("startup failure");
250
+ await coordinator.shutdown();
247
251
  await sessionFiles.close();
248
252
  if (this.generation === generation) this.generation = undefined;
249
253
  this.notifyWarning(
@@ -253,13 +257,16 @@ class PiCodeModeLifecycleController {
253
257
  return;
254
258
  }
255
259
 
256
- for (const definition of createRenderedCodeModeToolDefinitions(
257
- operations,
258
- generation.executeDescription,
259
- (sessionId) => coordinator.formatSessionPrefix(sessionId),
260
- )) {
261
- this.pi.registerTool(definition);
262
- }
260
+ const [executeTool, resultTool, cancelTool, sessionsTool] =
261
+ createRenderedCodeModeToolDefinitions(
262
+ operations,
263
+ generation.executeDescription,
264
+ (sessionId) => coordinator.formatSessionPrefix(sessionId),
265
+ );
266
+ this.pi.registerTool(executeTool);
267
+ this.pi.registerTool(resultTool);
268
+ this.pi.registerTool(cancelTool);
269
+ this.pi.registerTool(sessionsTool);
263
270
  generation.toolsRegistered = true;
264
271
  this.synchronizeGeneration(generation);
265
272
  }
@@ -382,12 +389,8 @@ class PiCodeModeLifecycleController {
382
389
  now: CODEMODE_SYSTEM_RUNTIME.now,
383
390
  signal: AbortSignal.any([batch.signal, terminationController.signal]),
384
391
  onTerminate: () => terminationController.abort(),
385
- };
386
- if (outerAssistantMessage !== undefined) {
387
- Object.assign(bridgeOptions, { outerAssistantMessage });
388
- }
389
- if (batch.onUpdate !== undefined) {
390
- Object.assign(bridgeOptions, {
392
+ ...(outerAssistantMessage !== undefined && { outerAssistantMessage }),
393
+ ...(batch.onUpdate !== undefined && {
391
394
  onUpdate: (_callId: string, update: AgentToolResult<unknown>) => {
392
395
  const outerUpdate: AgentToolResult<CodeModeResultDetails> = {
393
396
  content: update.content,
@@ -395,8 +398,8 @@ class PiCodeModeLifecycleController {
395
398
  };
396
399
  batch.onUpdate?.(outerUpdate);
397
400
  },
398
- });
399
- }
401
+ }),
402
+ };
400
403
  const bridged = await executePiToolBridgeBatch(bridgeCaptured, bridgeOptions);
401
404
  const bridgedResults = new Map<string, CodeModeNestedToolResult>(
402
405
  bridged.calls.map((outcome) => [
@@ -424,16 +427,16 @@ class PiCodeModeLifecycleController {
424
427
  "Pi CodeMode nested tool returned no result",
425
428
  ),
426
429
  );
427
- const batchResult = { results, presentation: bridged.presentation };
428
- if (bridged.usage !== undefined) Object.assign(batchResult, { usage: bridged.usage });
429
- if (bridged.addedToolNames.length > 0) {
430
- Object.assign(batchResult, { addedToolNames: bridged.addedToolNames });
431
- }
432
- if (bridged.terminate) Object.assign(batchResult, { terminate: true });
433
- return batchResult;
430
+ return {
431
+ results,
432
+ presentation: bridged.presentation,
433
+ ...(bridged.usage !== undefined && { usage: bridged.usage }),
434
+ ...(bridged.addedToolNames.length > 0 && { addedToolNames: bridged.addedToolNames }),
435
+ ...(bridged.terminate && { terminate: true }),
436
+ };
434
437
  }
435
438
 
436
- private async shutdownSession(reason: string): Promise<void> {
439
+ private async shutdownSession(): Promise<void> {
437
440
  const generation = this.generation;
438
441
  if (generation === undefined || !generation.active) return;
439
442
  generation.active = false;
@@ -443,7 +446,7 @@ class PiCodeModeLifecycleController {
443
446
  // Observer cleanup is presentation-only; execution resources still require release.
444
447
  }
445
448
  try {
446
- await generation.coordinator.shutdown(reason);
449
+ await generation.coordinator.shutdown();
447
450
  } finally {
448
451
  try {
449
452
  await generation.sessionFiles.close();
@@ -12,6 +12,7 @@ import {
12
12
  parseCodeModeJsonValue,
13
13
  type CodeModeJsonValue,
14
14
  } from "./codemode-tool-contract.js";
15
+ import { addCodeModeUsage, boundedCodeModeElapsedMs } from "./codemode-session-coordinator.js";
15
16
  import type { CapturedPiAgentSession } from "./pi-agent-session-capture.js";
16
17
 
17
18
  const PI_TOOL_BRIDGE_RESULT_LIMIT_BYTES = 8 * 1024 * 1024;
@@ -505,36 +506,6 @@ function translatePiToolResult(
505
506
  };
506
507
  }
507
508
 
508
- function addUsage(left: Usage | undefined, right: Usage | undefined): Usage | undefined {
509
- if (right === undefined) return left;
510
- if (left === undefined) return right;
511
- const reasoning =
512
- left.reasoning === undefined && right.reasoning === undefined
513
- ? undefined
514
- : (left.reasoning ?? 0) + (right.reasoning ?? 0);
515
- const cacheWrite1h =
516
- left.cacheWrite1h === undefined && right.cacheWrite1h === undefined
517
- ? undefined
518
- : (left.cacheWrite1h ?? 0) + (right.cacheWrite1h ?? 0);
519
- const combined: Usage = {
520
- input: left.input + right.input,
521
- output: left.output + right.output,
522
- cacheRead: left.cacheRead + right.cacheRead,
523
- cacheWrite: left.cacheWrite + right.cacheWrite,
524
- totalTokens: left.totalTokens + right.totalTokens,
525
- cost: {
526
- input: left.cost.input + right.cost.input,
527
- output: left.cost.output + right.cost.output,
528
- cacheRead: left.cost.cacheRead + right.cost.cacheRead,
529
- cacheWrite: left.cost.cacheWrite + right.cost.cacheWrite,
530
- total: left.cost.total + right.cost.total,
531
- },
532
- };
533
- if (cacheWrite1h !== undefined) combined.cacheWrite1h = cacheWrite1h;
534
- if (reasoning !== undefined) combined.reasoning = reasoning;
535
- return combined;
536
- }
537
-
538
509
  function terminatedPiToolCall(callId: string): FinalizedPiToolCall {
539
510
  return createBridgeFailure(
540
511
  callId,
@@ -553,12 +524,6 @@ function presentationOutcome(
553
524
  : "failed";
554
525
  }
555
526
 
556
- function elapsedMilliseconds(startedAt: number, finishedAt: number): number {
557
- const elapsed = Math.round(finishedAt - startedAt);
558
- if (!Number.isFinite(elapsed)) return 0;
559
- return Math.min(Number.MAX_SAFE_INTEGER, Math.max(0, elapsed));
560
- }
561
-
562
527
  function timedFinalizedPiToolCall(
563
528
  call: PiToolBridgeCall,
564
529
  finalized: FinalizedPiToolCall,
@@ -569,7 +534,7 @@ function timedFinalizedPiToolCall(
569
534
  finalized,
570
535
  presentation: {
571
536
  callId: call.callId,
572
- elapsedMs: elapsedMilliseconds(startedAt, now()),
537
+ elapsedMs: boundedCodeModeElapsedMs(startedAt, now()),
573
538
  name: call.name,
574
539
  outcome: presentationOutcome(finalized.outcome),
575
540
  },
@@ -590,7 +555,9 @@ function collectPiToolBridgeBatch(
590
555
  const addedToolNames: string[] = [];
591
556
  const seenToolNames = new Set<string>();
592
557
  for (const { finalized } of timedCalls) {
593
- usage = addUsage(usage, finalized.usage);
558
+ if (finalized.usage !== undefined) {
559
+ usage = addCodeModeUsage(usage, finalized.usage);
560
+ }
594
561
  for (const toolName of finalized.addedToolNames) {
595
562
  if (seenToolNames.has(toolName)) continue;
596
563
  seenToolNames.add(toolName);