@narumitw/pi-langfuse 0.35.0 → 0.39.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 +9 -2
- package/package.json +5 -4
- package/src/langfuse.ts +104 -73
package/README.md
CHANGED
|
@@ -144,7 +144,11 @@ Completed observations are exported in batches while Pi remains live. Neither `a
|
|
|
144
144
|
/langfuse
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
-
The command opens one context-aware menu
|
|
147
|
+
The command opens one context-aware standard menu in TUI or RPC mode. Its state lines show the
|
|
148
|
+
current session's tracing state, endpoint, content-capture mode, initialization failure when
|
|
149
|
+
applicable, and private configuration path. It never displays credentials. Escape closes the menu.
|
|
150
|
+
Credential and endpoint text inputs remain extension-owned because they enforce secret-preserving
|
|
151
|
+
setup/update semantics rather than standard navigation.
|
|
148
152
|
|
|
149
153
|
Available actions depend on that state:
|
|
150
154
|
|
|
@@ -153,7 +157,10 @@ Available actions depend on that state:
|
|
|
153
157
|
- **Update Langfuse for this Pi agent directory** appears when a valid config exists.
|
|
154
158
|
- **Show setup and privacy help** explains the agent-directory scope, manual configuration path, and content-capture risk.
|
|
155
159
|
|
|
156
|
-
Connection actions state their agent-directory scope and per-process restart requirement before
|
|
160
|
+
Connection actions state their agent-directory scope and per-process restart requirement before
|
|
161
|
+
selection. Command arguments are intentionally ignored so remembered subcommands cannot silently
|
|
162
|
+
bypass the menu. Print and JSON modes reject the interactive command with an observable error that
|
|
163
|
+
includes current tracing state and the manual configuration path.
|
|
157
164
|
|
|
158
165
|
## 🔐 Privacy
|
|
159
166
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@narumitw/pi-langfuse",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.0",
|
|
4
4
|
"description": "Pi extension that traces LLM generations and tool activity to Langfuse.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,15 +31,16 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@langfuse/otel": "^5.9.1",
|
|
33
33
|
"@langfuse/tracing": "^5.9.1",
|
|
34
|
+
"@narumitw/pi-tui-kit": "<1",
|
|
34
35
|
"@opentelemetry/api": "^1.9.1",
|
|
35
36
|
"@opentelemetry/exporter-trace-otlp-http": "^0.221.0",
|
|
36
37
|
"@opentelemetry/sdk-trace-base": "^2.10.0",
|
|
37
38
|
"@opentelemetry/sdk-trace-node": "^2.10.0"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
|
-
"@biomejs/biome": "2.5.
|
|
41
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
42
|
-
"@types/node": "26.1.
|
|
41
|
+
"@biomejs/biome": "2.5.6",
|
|
42
|
+
"@earendil-works/pi-coding-agent": "0.83.0",
|
|
43
|
+
"@types/node": "26.1.2",
|
|
43
44
|
"typescript": "7.0.2"
|
|
44
45
|
},
|
|
45
46
|
"repository": {
|
package/src/langfuse.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
ExtensionCommandContext,
|
|
4
4
|
ExtensionContext,
|
|
5
5
|
} from "@earendil-works/pi-coding-agent";
|
|
6
|
+
import { defineMenu, runMenu } from "@narumitw/pi-tui-kit";
|
|
6
7
|
import {
|
|
7
8
|
DEFAULT_BASE_URL,
|
|
8
9
|
type LangfuseConfig,
|
|
@@ -55,89 +56,118 @@ export function createLangfuseExtension(
|
|
|
55
56
|
let hasStoredConfig = false;
|
|
56
57
|
let configurationNotice: string | undefined;
|
|
57
58
|
let sessionGeneration = 0;
|
|
59
|
+
let menuController = new AbortController();
|
|
58
60
|
let configWriteQueue = Promise.resolve();
|
|
59
61
|
let nextAttemptReason: string | undefined;
|
|
60
62
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
formatNonInteractiveStatus(activeConfig, configPath, initializationError),
|
|
67
|
-
"warning",
|
|
68
|
-
);
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const menuGeneration = sessionGeneration;
|
|
73
|
-
const menuRecorder = recorder;
|
|
74
|
-
const menuConfig = activeConfig;
|
|
75
|
-
const choice = await ctx.ui.select(
|
|
76
|
-
formatMenuTitle(activeConfig, configPath, initializationError, configurationNotice),
|
|
77
|
-
[
|
|
78
|
-
...(menuRecorder ? [FLUSH_ACTION] : []),
|
|
79
|
-
hasStoredConfig ? UPDATE_ACTION : SET_UP_ACTION,
|
|
80
|
-
HELP_ACTION,
|
|
81
|
-
],
|
|
63
|
+
async function showLangfuseMenu(ctx: ExtensionCommandContext) {
|
|
64
|
+
if (!ctx.hasUI) {
|
|
65
|
+
ctx.ui.notify(
|
|
66
|
+
formatNonInteractiveStatus(activeConfig, configPath, initializationError),
|
|
67
|
+
"warning",
|
|
82
68
|
);
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
"
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const menuGeneration = sessionGeneration;
|
|
72
|
+
type Screen = "main";
|
|
73
|
+
type Action = "flush" | "configure" | "help";
|
|
74
|
+
const menu = defineMenu<undefined, Screen, Action>({
|
|
75
|
+
start: "main",
|
|
76
|
+
screens: {
|
|
77
|
+
main: () => ({
|
|
78
|
+
kind: "actions",
|
|
79
|
+
title: "Langfuse",
|
|
80
|
+
lines: formatMenuTitle(
|
|
81
|
+
activeConfig,
|
|
82
|
+
configPath,
|
|
83
|
+
initializationError,
|
|
84
|
+
configurationNotice,
|
|
85
|
+
)
|
|
86
|
+
.split("\n")
|
|
87
|
+
.slice(1),
|
|
88
|
+
items: [
|
|
89
|
+
...(recorder ? [{ id: "flush", label: FLUSH_ACTION, action: "flush" as const }] : []),
|
|
90
|
+
{
|
|
91
|
+
id: "configure",
|
|
92
|
+
label: hasStoredConfig ? UPDATE_ACTION : SET_UP_ACTION,
|
|
93
|
+
action: "configure",
|
|
94
|
+
},
|
|
95
|
+
{ id: "help", label: HELP_ACTION, action: "help" },
|
|
96
|
+
],
|
|
97
|
+
hint: "close",
|
|
98
|
+
}),
|
|
99
|
+
},
|
|
100
|
+
actions: {
|
|
101
|
+
flush: async () => {
|
|
102
|
+
const menuRecorder = recorder;
|
|
103
|
+
const menuConfig = activeConfig;
|
|
104
|
+
if (!menuRecorder) {
|
|
105
|
+
ctx.ui.notify("Langfuse tracing is not enabled for this session.", "warning");
|
|
106
|
+
return { kind: "close" };
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
await menuRecorder.flush();
|
|
110
|
+
if (menuGeneration !== sessionGeneration) return { kind: "close" };
|
|
111
|
+
ctx.ui.notify("Langfuse traces flushed for this session.", "info");
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (menuGeneration !== sessionGeneration) return { kind: "close" };
|
|
114
|
+
ctx.ui.notify(`Langfuse flush failed: ${formatError(error, menuConfig)}`, "error");
|
|
115
|
+
}
|
|
116
|
+
return { kind: "close" };
|
|
117
|
+
},
|
|
118
|
+
configure: async () => {
|
|
119
|
+
const loaded = await loadConfig(configPath);
|
|
120
|
+
if (menuGeneration !== sessionGeneration) return { kind: "close" };
|
|
121
|
+
configPath = loaded.path;
|
|
122
|
+
const next = await promptForConfig(
|
|
123
|
+
ctx,
|
|
124
|
+
loaded.ok ? loaded.config : undefined,
|
|
125
|
+
() => menuGeneration === sessionGeneration,
|
|
125
126
|
);
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
127
|
+
if (!next || menuGeneration !== sessionGeneration) return { kind: "close" };
|
|
128
|
+
try {
|
|
129
|
+
const write = configWriteQueue.then(() => writeConfig(next, loaded.path));
|
|
130
|
+
configWriteQueue = write.then(
|
|
131
|
+
() => undefined,
|
|
132
|
+
() => undefined,
|
|
133
|
+
);
|
|
134
|
+
await write;
|
|
135
|
+
if (menuGeneration !== sessionGeneration) return { kind: "close" };
|
|
136
|
+
hasStoredConfig = true;
|
|
137
|
+
configurationNotice =
|
|
138
|
+
"Saved; restart each Pi process to use it in subsequent sessions.";
|
|
139
|
+
ctx.ui.notify(
|
|
140
|
+
`Saved Langfuse config to ${loaded.path} for this Pi agent directory. Restart each Pi process to apply it to subsequent sessions.`,
|
|
141
|
+
"info",
|
|
142
|
+
);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
if (menuGeneration !== sessionGeneration) return { kind: "close" };
|
|
145
|
+
ctx.ui.notify(`Failed to save Langfuse config: ${formatError(error, next)}`, "error");
|
|
146
|
+
}
|
|
147
|
+
return { kind: "close" };
|
|
148
|
+
},
|
|
149
|
+
help: async () => {
|
|
150
|
+
ctx.ui.notify(formatHelp(configPath), "info");
|
|
151
|
+
return { kind: "close" };
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
await runMenu(ctx, menu, {
|
|
156
|
+
getState: () => undefined,
|
|
157
|
+
signal: menuController.signal,
|
|
158
|
+
isCurrent: () => menuGeneration === sessionGeneration,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
132
161
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
},
|
|
162
|
+
pi.registerCommand("langfuse", {
|
|
163
|
+
description: "Open interactive Langfuse tracing controls",
|
|
164
|
+
handler: async (_args, ctx) => showLangfuseMenu(ctx),
|
|
137
165
|
});
|
|
138
166
|
|
|
139
167
|
pi.on("session_start", async (_event, ctx) => {
|
|
140
168
|
const generation = ++sessionGeneration;
|
|
169
|
+
menuController.abort(new DOMException("Langfuse session replaced", "AbortError"));
|
|
170
|
+
menuController = new AbortController();
|
|
141
171
|
recorder = undefined;
|
|
142
172
|
activeConfig = undefined;
|
|
143
173
|
configPath = undefined;
|
|
@@ -300,6 +330,7 @@ export function createLangfuseExtension(
|
|
|
300
330
|
|
|
301
331
|
pi.on("session_shutdown", async (event, ctx) => {
|
|
302
332
|
const generation = ++sessionGeneration;
|
|
333
|
+
menuController.abort(new DOMException("Langfuse session shut down", "AbortError"));
|
|
303
334
|
const activeRecorder = recorder;
|
|
304
335
|
const shutdownConfig = activeConfig;
|
|
305
336
|
const pendingConfigWrite = configWriteQueue;
|