@braintrust/pi-extension 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.
- package/README.md +2 -0
- package/package.json +8 -9
- package/src/config.test.ts +51 -0
- package/src/config.ts +29 -0
- package/src/index.integration.test.ts +17 -8
- package/src/index.test.ts +49 -0
- package/src/index.ts +22 -6
- package/src/types.ts +2 -0
package/README.md
CHANGED
|
@@ -108,6 +108,8 @@ Example:
|
|
|
108
108
|
| `additional_metadata` | `BRAINTRUST_ADDITIONAL_METADATA` | `{}` |
|
|
109
109
|
| `log_file` | `BRAINTRUST_LOG_FILE` | unset |
|
|
110
110
|
| `state_dir` | `BRAINTRUST_STATE_DIR` | `~/.pi/agent/state/braintrust-pi-extension` |
|
|
111
|
+
| `show_ui` | `BRAINTRUST_SHOW_UI` | `true` |
|
|
112
|
+
| `show_trace_link` | `BRAINTRUST_SHOW_TRACE_LINK` | `true` |
|
|
111
113
|
| `parent_span_id` | `PI_PARENT_SPAN_ID` | unset |
|
|
112
114
|
| `root_span_id` | `PI_ROOT_SPAN_ID` | unset |
|
|
113
115
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@braintrust/pi-extension",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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,17 +24,16 @@
|
|
|
24
24
|
"access": "public"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"braintrust": "^3.
|
|
27
|
+
"braintrust": "^3.8.0",
|
|
28
28
|
"valibot": "^1.3.1"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@mariozechner/pi-ai": "^0.
|
|
32
|
-
"@mariozechner/pi-coding-agent": "^0.
|
|
33
|
-
"@types/node": "^25.
|
|
34
|
-
"tsx": "^4.21.0",
|
|
31
|
+
"@mariozechner/pi-ai": "^0.66.1",
|
|
32
|
+
"@mariozechner/pi-coding-agent": "^0.66.1",
|
|
33
|
+
"@types/node": "^25.6.0",
|
|
35
34
|
"typescript": "^6.0.2",
|
|
36
|
-
"vite-plus": "^0.1.
|
|
37
|
-
"vitest": "^4.1.
|
|
35
|
+
"vite-plus": "^0.1.16",
|
|
36
|
+
"vitest": "^4.1.4"
|
|
38
37
|
},
|
|
39
38
|
"peerDependencies": {
|
|
40
39
|
"@mariozechner/pi-coding-agent": "*"
|
|
@@ -66,6 +65,6 @@
|
|
|
66
65
|
"test:integration": "vitest run src/index.integration.test.ts",
|
|
67
66
|
"test:watch": "vitest",
|
|
68
67
|
"typecheck": "vp check",
|
|
69
|
-
"smoke": "
|
|
68
|
+
"smoke": "vp exec node -e \"import('./src/index.ts')\""
|
|
70
69
|
}
|
|
71
70
|
}
|
package/src/config.test.ts
CHANGED
|
@@ -16,6 +16,8 @@ const ENV_KEYS = [
|
|
|
16
16
|
"BRAINTRUST_DEBUG",
|
|
17
17
|
"BRAINTRUST_LOG_FILE",
|
|
18
18
|
"BRAINTRUST_STATE_DIR",
|
|
19
|
+
"BRAINTRUST_SHOW_UI",
|
|
20
|
+
"BRAINTRUST_SHOW_TRACE_LINK",
|
|
19
21
|
"PI_PARENT_SPAN_ID",
|
|
20
22
|
"PI_ROOT_SPAN_ID",
|
|
21
23
|
"BRAINTRUST_ADDITIONAL_METADATA",
|
|
@@ -257,6 +259,53 @@ describe("loadConfig", () => {
|
|
|
257
259
|
});
|
|
258
260
|
});
|
|
259
261
|
|
|
262
|
+
it("defaults showUi and showTraceLink to true", () => {
|
|
263
|
+
const home = makeTempDir("pi-extension-home-");
|
|
264
|
+
process.env.HOME = home;
|
|
265
|
+
process.env.BRAINTRUST_STATE_DIR = join(home, "state");
|
|
266
|
+
|
|
267
|
+
const config = loadConfig(home);
|
|
268
|
+
|
|
269
|
+
expect(config.showUi).toBe(true);
|
|
270
|
+
expect(config.showTraceLink).toBe(true);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("applies show_ui and show_trace_link from config files", () => {
|
|
274
|
+
const home = makeTempDir("pi-extension-home-");
|
|
275
|
+
const cwd = join(home, "workspace");
|
|
276
|
+
|
|
277
|
+
process.env.HOME = home;
|
|
278
|
+
process.env.BRAINTRUST_STATE_DIR = join(home, "state");
|
|
279
|
+
|
|
280
|
+
writeJson(join(home, ".pi", "agent", "braintrust.json"), {
|
|
281
|
+
show_ui: false,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
const config = loadConfig(cwd);
|
|
285
|
+
|
|
286
|
+
expect(config.showUi).toBe(false);
|
|
287
|
+
expect(config.showTraceLink).toBe(true);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it("overrides show_ui and show_trace_link from environment variables", () => {
|
|
291
|
+
const home = makeTempDir("pi-extension-home-");
|
|
292
|
+
const cwd = join(home, "workspace");
|
|
293
|
+
|
|
294
|
+
process.env.HOME = home;
|
|
295
|
+
process.env.BRAINTRUST_STATE_DIR = join(home, "state");
|
|
296
|
+
process.env.BRAINTRUST_SHOW_UI = "true";
|
|
297
|
+
process.env.BRAINTRUST_SHOW_TRACE_LINK = "false";
|
|
298
|
+
|
|
299
|
+
writeJson(join(home, ".pi", "agent", "braintrust.json"), {
|
|
300
|
+
show_ui: false,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const config = loadConfig(cwd);
|
|
304
|
+
|
|
305
|
+
expect(config.showUi).toBe(true);
|
|
306
|
+
expect(config.showTraceLink).toBe(false);
|
|
307
|
+
});
|
|
308
|
+
|
|
260
309
|
it("warns when tracing is enabled without an API key", () => {
|
|
261
310
|
const home = makeTempDir("pi-extension-home-");
|
|
262
311
|
process.env.HOME = home;
|
|
@@ -290,6 +339,8 @@ describe("createLogger", () => {
|
|
|
290
339
|
additionalMetadata: {},
|
|
291
340
|
parentSpanId: undefined,
|
|
292
341
|
rootSpanId: undefined,
|
|
342
|
+
showUi: true,
|
|
343
|
+
showTraceLink: true,
|
|
293
344
|
configIssues: [],
|
|
294
345
|
};
|
|
295
346
|
|
package/src/config.ts
CHANGED
|
@@ -218,6 +218,17 @@ function applyConfig(
|
|
|
218
218
|
);
|
|
219
219
|
if (additionalMetadata !== undefined) target.additionalMetadata = additionalMetadata;
|
|
220
220
|
|
|
221
|
+
const showUi = validateOptionalBoolean(source.show_ui, issues, path, "show_ui");
|
|
222
|
+
if (showUi !== undefined) target.showUi = showUi;
|
|
223
|
+
|
|
224
|
+
const showTraceLink = validateOptionalBoolean(
|
|
225
|
+
source.show_trace_link,
|
|
226
|
+
issues,
|
|
227
|
+
path,
|
|
228
|
+
"show_trace_link",
|
|
229
|
+
);
|
|
230
|
+
if (showTraceLink !== undefined) target.showTraceLink = showTraceLink;
|
|
231
|
+
|
|
221
232
|
return {
|
|
222
233
|
parentSpanConfigured: parentSpanId !== undefined,
|
|
223
234
|
rootSpanConfigured: rootSpanId !== undefined,
|
|
@@ -238,6 +249,8 @@ export function loadConfig(cwd = process.cwd()): TraceConfig {
|
|
|
238
249
|
additionalMetadata: {},
|
|
239
250
|
parentSpanId: undefined,
|
|
240
251
|
rootSpanId: undefined,
|
|
252
|
+
showUi: true,
|
|
253
|
+
showTraceLink: true,
|
|
241
254
|
configIssues: [],
|
|
242
255
|
};
|
|
243
256
|
|
|
@@ -344,6 +357,22 @@ export function loadConfig(cwd = process.cwd()): TraceConfig {
|
|
|
344
357
|
);
|
|
345
358
|
if (envStateDir !== undefined) config.stateDir = envStateDir;
|
|
346
359
|
|
|
360
|
+
const envShowUi = validateOptionalBoolean(
|
|
361
|
+
process.env.BRAINTRUST_SHOW_UI,
|
|
362
|
+
config.configIssues,
|
|
363
|
+
"BRAINTRUST_SHOW_UI",
|
|
364
|
+
"BRAINTRUST_SHOW_UI",
|
|
365
|
+
);
|
|
366
|
+
if (envShowUi !== undefined) config.showUi = envShowUi;
|
|
367
|
+
|
|
368
|
+
const envShowTraceLink = validateOptionalBoolean(
|
|
369
|
+
process.env.BRAINTRUST_SHOW_TRACE_LINK,
|
|
370
|
+
config.configIssues,
|
|
371
|
+
"BRAINTRUST_SHOW_TRACE_LINK",
|
|
372
|
+
"BRAINTRUST_SHOW_TRACE_LINK",
|
|
373
|
+
);
|
|
374
|
+
if (envShowTraceLink !== undefined) config.showTraceLink = envShowTraceLink;
|
|
375
|
+
|
|
347
376
|
const envParentSpanId = validateOptionalNonEmptyString(
|
|
348
377
|
process.env.PI_PARENT_SPAN_ID,
|
|
349
378
|
config.configIssues,
|
|
@@ -433,6 +433,9 @@ async function createHarness(options?: {
|
|
|
433
433
|
return { agentDir, cwd, session, stateDir };
|
|
434
434
|
}
|
|
435
435
|
|
|
436
|
+
// TODO: Remove this legacy fallback once our supported pi compatibility window no
|
|
437
|
+
// longer includes pi <0.65.0, which introduced the session runtime API and the
|
|
438
|
+
// session_start-only post-transition model.
|
|
436
439
|
const resourceLoader = new DefaultResourceLoader({
|
|
437
440
|
cwd,
|
|
438
441
|
agentDir,
|
|
@@ -447,26 +450,32 @@ async function createHarness(options?: {
|
|
|
447
450
|
resourceLoader,
|
|
448
451
|
sessionManager,
|
|
449
452
|
});
|
|
453
|
+
const legacyRuntimeSession = legacySession as typeof legacySession & {
|
|
454
|
+
newSession(): Promise<boolean>;
|
|
455
|
+
switchSession(sessionPath: string): Promise<boolean>;
|
|
456
|
+
fork(entryId: string): Promise<{ cancelled: boolean; selectedText?: string }>;
|
|
457
|
+
sessionManager: SessionManager;
|
|
458
|
+
};
|
|
450
459
|
|
|
451
460
|
const session: TestSessionController = {
|
|
452
|
-
prompt: (text) =>
|
|
453
|
-
newSession: () =>
|
|
454
|
-
switchSession: (sessionPath) =>
|
|
461
|
+
prompt: (text) => legacyRuntimeSession.prompt(text),
|
|
462
|
+
newSession: () => legacyRuntimeSession.newSession(),
|
|
463
|
+
switchSession: (sessionPath) => legacyRuntimeSession.switchSession(sessionPath),
|
|
455
464
|
fork: async (entryId) => {
|
|
456
|
-
const result = await
|
|
465
|
+
const result = await legacyRuntimeSession.fork(entryId);
|
|
457
466
|
return {
|
|
458
467
|
cancelled: result.cancelled,
|
|
459
|
-
selectedText: result.selectedText,
|
|
468
|
+
selectedText: result.selectedText ?? "",
|
|
460
469
|
};
|
|
461
470
|
},
|
|
462
471
|
dispose: async () => {
|
|
463
|
-
|
|
472
|
+
legacyRuntimeSession.dispose();
|
|
464
473
|
},
|
|
465
474
|
get sessionFile() {
|
|
466
|
-
return
|
|
475
|
+
return legacyRuntimeSession.sessionFile;
|
|
467
476
|
},
|
|
468
477
|
get sessionManager() {
|
|
469
|
-
return
|
|
478
|
+
return legacyRuntimeSession.sessionManager;
|
|
470
479
|
},
|
|
471
480
|
};
|
|
472
481
|
|
package/src/index.test.ts
CHANGED
|
@@ -22,6 +22,8 @@ const mockState = vi.hoisted(() => ({
|
|
|
22
22
|
additionalMetadata: {},
|
|
23
23
|
parentSpanId: undefined,
|
|
24
24
|
rootSpanId: undefined,
|
|
25
|
+
showUi: true,
|
|
26
|
+
showTraceLink: true,
|
|
25
27
|
configIssues: [] as Array<{ path: string; message: string; severity: "error" | "warning" }>,
|
|
26
28
|
},
|
|
27
29
|
}));
|
|
@@ -117,6 +119,8 @@ beforeEach(() => {
|
|
|
117
119
|
additionalMetadata: {},
|
|
118
120
|
parentSpanId: undefined,
|
|
119
121
|
rootSpanId: undefined,
|
|
122
|
+
showUi: true,
|
|
123
|
+
showTraceLink: true,
|
|
120
124
|
configIssues: [],
|
|
121
125
|
};
|
|
122
126
|
vi.resetModules();
|
|
@@ -406,4 +410,49 @@ describe("braintrustPiExtension", () => {
|
|
|
406
410
|
undefined,
|
|
407
411
|
);
|
|
408
412
|
});
|
|
413
|
+
|
|
414
|
+
it("hides all UI when showUi is false", async () => {
|
|
415
|
+
mockState.config.showUi = false;
|
|
416
|
+
|
|
417
|
+
const { emit } = await createHarness();
|
|
418
|
+
|
|
419
|
+
await emit("session_start");
|
|
420
|
+
await emit("before_agent_start", {
|
|
421
|
+
prompt: "Inspect the package",
|
|
422
|
+
images: [],
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
const statusUpdates = mockState.statuses.filter(
|
|
426
|
+
(s) => s.key === "braintrust-tracing" && s.text !== undefined,
|
|
427
|
+
);
|
|
428
|
+
const widgetUpdates = mockState.widgets.filter(
|
|
429
|
+
(w) => w.key === "braintrust-trace-link" && w.content !== undefined,
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
expect(statusUpdates).toEqual([]);
|
|
433
|
+
expect(widgetUpdates).toEqual([]);
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
it("hides just the trace link when showTraceLink is false", async () => {
|
|
437
|
+
mockState.config.showTraceLink = false;
|
|
438
|
+
|
|
439
|
+
const { emit } = await createHarness();
|
|
440
|
+
|
|
441
|
+
await emit("session_start");
|
|
442
|
+
await emit("before_agent_start", {
|
|
443
|
+
prompt: "Inspect the package",
|
|
444
|
+
images: [],
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
const statusUpdates = mockState.statuses.filter(
|
|
448
|
+
(s) => s.key === "braintrust-tracing" && s.text !== undefined,
|
|
449
|
+
);
|
|
450
|
+
expect(statusUpdates.length).toBeGreaterThan(0);
|
|
451
|
+
expect(statusUpdates[0]?.text).toContain("Braintrust");
|
|
452
|
+
|
|
453
|
+
const widgetUpdates = mockState.widgets.filter(
|
|
454
|
+
(w) => w.key === "braintrust-trace-link" && w.content !== undefined,
|
|
455
|
+
);
|
|
456
|
+
expect(widgetUpdates).toEqual([]);
|
|
457
|
+
});
|
|
409
458
|
});
|
package/src/index.ts
CHANGED
|
@@ -193,7 +193,10 @@ function setTracingStatus(
|
|
|
193
193
|
configIssue?: ConfigIssue;
|
|
194
194
|
},
|
|
195
195
|
): void {
|
|
196
|
-
if (!ctx.hasUI)
|
|
196
|
+
if (!ctx.hasUI || !config.showUi) {
|
|
197
|
+
if (ctx.hasUI) ctx.ui.setStatus(TRACING_STATUS_KEY, undefined);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
197
200
|
|
|
198
201
|
const theme = ctx.ui.theme;
|
|
199
202
|
|
|
@@ -269,15 +272,19 @@ function displayPath(path: string): string {
|
|
|
269
272
|
|
|
270
273
|
function setTraceWidget(
|
|
271
274
|
ctx: ExtensionContext,
|
|
275
|
+
config: TraceConfig,
|
|
272
276
|
traceUrl: string | undefined,
|
|
273
277
|
configIssue: ConfigIssue | undefined,
|
|
274
278
|
): void {
|
|
275
|
-
if (!ctx.hasUI)
|
|
279
|
+
if (!ctx.hasUI || !config.showUi) {
|
|
280
|
+
if (ctx.hasUI) ctx.ui.setWidget(TRACING_WIDGET_KEY, undefined);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
276
283
|
|
|
277
284
|
const theme = ctx.ui.theme;
|
|
278
285
|
const lines: string[] = [];
|
|
279
286
|
|
|
280
|
-
if (traceUrl) {
|
|
287
|
+
if (traceUrl && config.showTraceLink) {
|
|
281
288
|
const label = makeHyperlink(
|
|
282
289
|
traceUrl,
|
|
283
290
|
theme.fg("accent", theme.underline("Braintrust trace ↗")),
|
|
@@ -334,7 +341,7 @@ export default function braintrustPiExtension(pi: ExtensionAPI): void {
|
|
|
334
341
|
missingApiKey: Boolean(config.enabled && !config.apiKey),
|
|
335
342
|
configIssue,
|
|
336
343
|
});
|
|
337
|
-
setTraceWidget(ctx, activeSession?.traceUrl, configIssue);
|
|
344
|
+
setTraceWidget(ctx, config, activeSession?.traceUrl, configIssue);
|
|
338
345
|
}
|
|
339
346
|
|
|
340
347
|
function persistTraceUrl(session: ActiveSession, traceUrl: string): void {
|
|
@@ -615,12 +622,21 @@ export default function braintrustPiExtension(pi: ExtensionAPI): void {
|
|
|
615
622
|
});
|
|
616
623
|
});
|
|
617
624
|
|
|
618
|
-
|
|
625
|
+
// TODO: Remove these legacy transition listeners once our compatibility window
|
|
626
|
+
// no longer includes pi <0.65.0.
|
|
627
|
+
const legacyPi = pi as ExtensionAPI & {
|
|
628
|
+
on(
|
|
629
|
+
event: "session_switch" | "session_fork",
|
|
630
|
+
handler: (event: unknown, ctx: ExtensionContext) => Promise<void> | void,
|
|
631
|
+
): void;
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
legacyPi.on("session_switch", async (event, ctx) => {
|
|
619
635
|
refreshTracingUi(ctx);
|
|
620
636
|
await rolloverSession(ctx, "session_switch", getPreviousSessionFile(event));
|
|
621
637
|
});
|
|
622
638
|
|
|
623
|
-
|
|
639
|
+
legacyPi.on("session_fork", async (event, ctx) => {
|
|
624
640
|
refreshTracingUi(ctx);
|
|
625
641
|
await rolloverSession(ctx, "session_fork", getPreviousSessionFile(event));
|
|
626
642
|
});
|