@braintrust/pi-extension 0.3.1 → 0.4.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.
- package/README.md +2 -0
- package/package.json +4 -4
- package/src/index.test.ts +49 -0
- package/src/index.ts +18 -4
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @braintrust/pi-extension
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@braintrust/pi-extension)
|
|
4
|
+
|
|
3
5
|
Braintrust extension for [pi](https://github.com/mariozechner/pi-coding-agent).
|
|
4
6
|
|
|
5
7
|
Today this extension automatically traces pi sessions, turns, model calls, and tool executions to Braintrust.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@braintrust/pi-extension",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Braintrust extension for pi. Includes automatic tracing for pi sessions, turns, LLM calls, and tool executions to Braintrust.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"braintrust",
|
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
"access": "public"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"braintrust": "^3.
|
|
27
|
+
"braintrust": "^3.9.0",
|
|
28
28
|
"valibot": "^1.3.1"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@mariozechner/pi-ai": "^0.
|
|
32
|
-
"@mariozechner/pi-coding-agent": "^0.
|
|
31
|
+
"@mariozechner/pi-ai": "^0.68.0",
|
|
32
|
+
"@mariozechner/pi-coding-agent": "^0.68.0",
|
|
33
33
|
"@types/node": "^25.6.0",
|
|
34
34
|
"typescript": "^6.0.2",
|
|
35
35
|
"vite-plus": "^0.1.16",
|
package/src/index.test.ts
CHANGED
|
@@ -411,6 +411,55 @@ describe("braintrustPiExtension", () => {
|
|
|
411
411
|
);
|
|
412
412
|
});
|
|
413
413
|
|
|
414
|
+
it("records the structured shutdown reason on the finalized root span", async () => {
|
|
415
|
+
const { emit } = await createHarness();
|
|
416
|
+
|
|
417
|
+
await emit("session_start");
|
|
418
|
+
await emit("before_agent_start", {
|
|
419
|
+
prompt: "Inspect the package",
|
|
420
|
+
images: [],
|
|
421
|
+
});
|
|
422
|
+
await emit("session_shutdown", { reason: "quit" });
|
|
423
|
+
|
|
424
|
+
const rootFinalizeLog = mockState.logSpans
|
|
425
|
+
.map((entry) => entry.event as Record<string, unknown>)
|
|
426
|
+
.find(
|
|
427
|
+
(event) =>
|
|
428
|
+
(event.metadata as Record<string, unknown> | undefined)?.last_close_reason === "quit",
|
|
429
|
+
);
|
|
430
|
+
expect(rootFinalizeLog).toBeDefined();
|
|
431
|
+
expect(mockState.endSpans.length).toBeGreaterThan(0);
|
|
432
|
+
expect(mockState.flushCalls).toBeGreaterThan(0);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it("does not finalize the root span on reload shutdowns", async () => {
|
|
436
|
+
const { emit } = await createHarness();
|
|
437
|
+
|
|
438
|
+
await emit("session_start");
|
|
439
|
+
await emit("before_agent_start", {
|
|
440
|
+
prompt: "Inspect the package",
|
|
441
|
+
images: [],
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
const startsBefore = mockState.startSpans.length;
|
|
445
|
+
const endsBefore = mockState.endSpans.length;
|
|
446
|
+
const flushesBefore = mockState.flushCalls;
|
|
447
|
+
|
|
448
|
+
await emit("session_shutdown", { reason: "reload" });
|
|
449
|
+
|
|
450
|
+
// No additional span endings during reload, but pending writes are still flushed.
|
|
451
|
+
expect(mockState.startSpans.length).toBe(startsBefore);
|
|
452
|
+
expect(mockState.endSpans.length).toBe(endsBefore);
|
|
453
|
+
expect(mockState.flushCalls).toBeGreaterThan(flushesBefore);
|
|
454
|
+
const reloadClose = mockState.logSpans
|
|
455
|
+
.map((entry) => entry.event as Record<string, unknown>)
|
|
456
|
+
.some(
|
|
457
|
+
(event) =>
|
|
458
|
+
(event.metadata as Record<string, unknown> | undefined)?.last_close_reason === "reload",
|
|
459
|
+
);
|
|
460
|
+
expect(reloadClose).toBe(false);
|
|
461
|
+
});
|
|
462
|
+
|
|
414
463
|
it("hides all UI when showUi is false", async () => {
|
|
415
464
|
mockState.config.showUi = false;
|
|
416
465
|
|
package/src/index.ts
CHANGED
|
@@ -132,7 +132,7 @@ function getPreviousSessionFile(event: unknown): string | undefined {
|
|
|
132
132
|
return typeof event.previousSessionFile === "string" ? event.previousSessionFile : undefined;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
function
|
|
135
|
+
function getEventReason(event: unknown): string | undefined {
|
|
136
136
|
if (!isPlainObject(event)) return undefined;
|
|
137
137
|
return typeof event.reason === "string" ? event.reason : undefined;
|
|
138
138
|
}
|
|
@@ -606,7 +606,7 @@ export default function braintrustPiExtension(pi: ExtensionAPI): void {
|
|
|
606
606
|
pi.on("session_start", async (event, ctx) => {
|
|
607
607
|
refreshTracingUi(ctx);
|
|
608
608
|
|
|
609
|
-
const reason =
|
|
609
|
+
const reason = getEventReason(event);
|
|
610
610
|
if (reason === "new" || reason === "resume" || reason === "fork") {
|
|
611
611
|
await rolloverSession(
|
|
612
612
|
ctx,
|
|
@@ -837,13 +837,27 @@ export default function braintrustPiExtension(pi: ExtensionAPI): void {
|
|
|
837
837
|
await finishTurn("agent_end", Date.now(), finalAssistant);
|
|
838
838
|
});
|
|
839
839
|
|
|
840
|
-
pi.on("session_shutdown", async (
|
|
840
|
+
pi.on("session_shutdown", async (event, ctx) => {
|
|
841
841
|
if (ctx.hasUI) {
|
|
842
842
|
ctx.ui.setStatus(TRACING_STATUS_KEY, undefined);
|
|
843
843
|
ctx.ui.setWidget(TRACING_WIDGET_KEY, undefined);
|
|
844
844
|
}
|
|
845
|
+
|
|
846
|
+
// pi 0.68.0+ exposes a structured reason ("quit" | "reload" | "new" | "resume"
|
|
847
|
+
// | "fork"). Older pi hosts pass no payload, so we fall back to the generic
|
|
848
|
+
// label to stay backwards-compatible and keep the existing metadata shape.
|
|
849
|
+
const reason = getEventReason(event) ?? "session_shutdown";
|
|
850
|
+
logger.debug("session_shutdown", { reason });
|
|
851
|
+
|
|
845
852
|
if (client && !clientInitializationError) {
|
|
846
|
-
|
|
853
|
+
// On reload the same pi session is about to resume in a freshly imported
|
|
854
|
+
// extension instance, which restores its state from the persisted store and
|
|
855
|
+
// keeps writing to the existing root span. Finalizing here would close that
|
|
856
|
+
// root span out from under the reloaded instance, so we just flush pending
|
|
857
|
+
// writes and let the new instance continue the trace.
|
|
858
|
+
if (reason !== "reload") {
|
|
859
|
+
await finalizeSession(reason);
|
|
860
|
+
}
|
|
847
861
|
await client.flush();
|
|
848
862
|
}
|
|
849
863
|
activeSession = undefined;
|