@narumitw/pi-langfuse 0.49.4 → 0.50.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 +4 -3
- package/package.json +2 -2
- package/src/config.ts +8 -0
- package/src/langfuse.ts +1 -0
- package/src/runtime.ts +12 -3
- package/src/tracing.ts +9 -1
package/README.md
CHANGED
|
@@ -61,13 +61,14 @@ You can also create the file manually:
|
|
|
61
61
|
"baseUrl": "https://us.cloud.langfuse.com",
|
|
62
62
|
"environment": "development",
|
|
63
63
|
"release": "local",
|
|
64
|
+
"userId": "your-user-id",
|
|
64
65
|
"captureContent": true
|
|
65
66
|
}
|
|
66
67
|
```
|
|
67
68
|
|
|
68
69
|
`publicKey` and `secretKey` are required literal strings. Environment-variable and command interpolation are intentionally unsupported. `baseUrl` defaults to `https://us.cloud.langfuse.com`; regional and self-hosted HTTP or HTTPS endpoints are supported. Prefer HTTPS because HTTP sends Langfuse credentials and trace content without transport encryption.
|
|
69
70
|
|
|
70
|
-
`environment` and `
|
|
71
|
+
`environment`, `release`, and `userId` are optional Langfuse trace attributes. An environment must match Langfuse's contract: at most 40 lowercase letters, numbers, hyphens, or underscores, and it cannot start with `langfuse`. `userId` populates the Langfuse user dimension, which is what the Sessions and Traces views group by; Langfuse accepts at most 200 characters, and leaving it unset reports no user. Set `captureContent` to `false` to trace timing, model, usage, cost, status, and bounded diagnostic metadata without sending prompts, provider-request snapshots, responses, or tool content.
|
|
71
72
|
|
|
72
73
|
The extension automatically restricts an existing config file to mode `0600` and refuses to load credentials if that protection cannot be enforced. You can also set it explicitly:
|
|
73
74
|
|
|
@@ -75,7 +76,7 @@ The extension automatically restricts an existing config file to mode `0600` and
|
|
|
75
76
|
chmod 600 ~/.pi/agent/pi-langfuse.json
|
|
76
77
|
```
|
|
77
78
|
|
|
78
|
-
Restart Pi after changing credentials, endpoint, environment, release, or `captureContent`. The isolated OpenTelemetry tracer provider is initialized once per Pi process and selected only for Langfuse; it does not replace Pi's process-global provider or send Langfuse observations to another extension's exporter.
|
|
79
|
+
Restart Pi after changing credentials, endpoint, environment, release, or `captureContent`. Changes to `userId` apply to new sessions without restarting Pi. The isolated OpenTelemetry tracer provider is initialized once per Pi process and selected only for Langfuse; it does not replace Pi's process-global provider or send Langfuse observations to another extension's exporter.
|
|
79
80
|
|
|
80
81
|
## 🔭 What is traced
|
|
81
82
|
|
|
@@ -166,7 +167,7 @@ includes current tracing state and the manual configuration path.
|
|
|
166
167
|
|
|
167
168
|
With content capture enabled, traces can contain user prompts, model responses, tool arguments, and tool results. These may include source code, file contents, shell output, or other sensitive project data. Review your Langfuse retention and access controls before enabling this extension.
|
|
168
169
|
|
|
169
|
-
Git branch names, commit ids, working directory, session/leaf ids, model identity, usage/cost, aggregate counts, and allowlisted response-header values are metadata. They remain exported when `captureContent` is `false`; branch names and diagnostic header values can themselves contain operational details.
|
|
170
|
+
Git branch names, commit ids, working directory, session/leaf ids, the configured `userId`, model identity, usage/cost, aggregate counts, and allowlisted response-header values are metadata. They remain exported when `captureContent` is `false`; branch names and diagnostic header values can themselves contain operational details, and a `userId` may itself be personally identifying if you set it to an email address or a real name. Choose a pseudonymous value when that matters, or leave `userId` unset to export no user at all.
|
|
170
171
|
|
|
171
172
|
The built-in mask specifically protects Langfuse credentials; it is not a general secret scanner. Set `"captureContent": false` in `pi-langfuse.json` when prompts, provider-request snapshots, responses, and tool content must remain local. Compaction summaries, tool partial results, opaque continuation signatures, authorization headers, cookies, and unapproved response headers are never exported in either mode.
|
|
172
173
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@narumitw/pi-langfuse",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.50.0",
|
|
4
4
|
"description": "Pi extension that traces LLM generations and tool activity to Langfuse.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@langfuse/otel": "^5.10.0",
|
|
45
45
|
"@langfuse/tracing": "^5.10.0",
|
|
46
|
-
"@narumitw/pi-tui-kit": "^0.
|
|
46
|
+
"@narumitw/pi-tui-kit": "^0.55.0",
|
|
47
47
|
"@opentelemetry/api": "^1.9.1",
|
|
48
48
|
"@opentelemetry/exporter-trace-otlp-http": "^0.221.0",
|
|
49
49
|
"@opentelemetry/sdk-trace-base": "^2.10.0",
|
package/src/config.ts
CHANGED
|
@@ -12,6 +12,7 @@ export interface LangfuseConfig {
|
|
|
12
12
|
baseUrl: string;
|
|
13
13
|
environment?: string;
|
|
14
14
|
release?: string;
|
|
15
|
+
userId?: string;
|
|
15
16
|
captureContent: boolean;
|
|
16
17
|
}
|
|
17
18
|
|
|
@@ -61,6 +62,7 @@ async function writeLangfuseConfigNow(
|
|
|
61
62
|
baseUrl: _baseUrl,
|
|
62
63
|
environment: _environment,
|
|
63
64
|
release: _release,
|
|
65
|
+
userId: _userId,
|
|
64
66
|
captureContent: _captureContent,
|
|
65
67
|
...unknownFields
|
|
66
68
|
} = currentDocument;
|
|
@@ -209,6 +211,11 @@ export function normalizeLangfuseConfig(
|
|
|
209
211
|
}
|
|
210
212
|
const release = optionalString(input.release, "release");
|
|
211
213
|
if (!release.ok) return release;
|
|
214
|
+
const userId = optionalString(input.userId, "userId");
|
|
215
|
+
if (!userId.ok) return userId;
|
|
216
|
+
if (userId.value && userId.value.length > 200) {
|
|
217
|
+
return { ok: false, reason: "pi-langfuse.json userId must be at most 200 characters." };
|
|
218
|
+
}
|
|
212
219
|
|
|
213
220
|
return {
|
|
214
221
|
ok: true,
|
|
@@ -218,6 +225,7 @@ export function normalizeLangfuseConfig(
|
|
|
218
225
|
baseUrl,
|
|
219
226
|
...(environment.value ? { environment: environment.value } : {}),
|
|
220
227
|
...(release.value ? { release: release.value } : {}),
|
|
228
|
+
...(userId.value ? { userId: userId.value } : {}),
|
|
221
229
|
captureContent: input.captureContent ?? true,
|
|
222
230
|
},
|
|
223
231
|
};
|
package/src/langfuse.ts
CHANGED
|
@@ -201,6 +201,7 @@ export function createLangfuseExtension(
|
|
|
201
201
|
activeConfig = result.config;
|
|
202
202
|
recorder = new TraceRecorder(backend, {
|
|
203
203
|
sessionId: ctx.sessionManager.getSessionId(),
|
|
204
|
+
...(result.config.userId ? { userId: result.config.userId } : {}),
|
|
204
205
|
cwd: ctx.cwd,
|
|
205
206
|
mode: ctx.mode,
|
|
206
207
|
captureContent: result.config.captureContent,
|
package/src/runtime.ts
CHANGED
|
@@ -39,14 +39,15 @@ class ProductionObservation implements Observation {
|
|
|
39
39
|
constructor(readonly native: LangfuseObservation) {}
|
|
40
40
|
|
|
41
41
|
update(attributes: ObservationAttributes): Observation {
|
|
42
|
-
const { sessionId, ...observationAttributes } = attributes;
|
|
42
|
+
const { sessionId, userId, ...observationAttributes } = attributes;
|
|
43
43
|
this.native.updateOtelSpanAttributes(observationAttributes as LangfuseObservationAttributes);
|
|
44
44
|
applySessionId(this.native, sessionId);
|
|
45
|
+
applyUserId(this.native, userId);
|
|
45
46
|
return this;
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
updateTrace(attributes: ObservationAttributes): Observation {
|
|
49
|
-
const { input, output, metadata, name, sessionId, tags, version } = attributes;
|
|
50
|
+
const { input, output, metadata, name, sessionId, userId, tags, version } = attributes;
|
|
50
51
|
if (input !== undefined || output !== undefined) {
|
|
51
52
|
this.native.setTraceIO({ input, output });
|
|
52
53
|
}
|
|
@@ -54,6 +55,7 @@ class ProductionObservation implements Observation {
|
|
|
54
55
|
this.native.otelSpan.setAttribute(LangfuseOtelSpanAttributes.TRACE_NAME, name);
|
|
55
56
|
}
|
|
56
57
|
applySessionId(this.native, sessionId);
|
|
58
|
+
applyUserId(this.native, userId);
|
|
57
59
|
if (tags !== undefined) {
|
|
58
60
|
this.native.otelSpan.setAttribute(LangfuseOtelSpanAttributes.TRACE_TAGS, tags);
|
|
59
61
|
}
|
|
@@ -86,13 +88,14 @@ class ProductionTraceBackend implements TraceBackend {
|
|
|
86
88
|
attributes: ObservationAttributes,
|
|
87
89
|
options: { asType: ObservationType; parent?: Observation },
|
|
88
90
|
): Observation {
|
|
89
|
-
const { sessionId, ...observationAttributes } = attributes;
|
|
91
|
+
const { sessionId, userId, ...observationAttributes } = attributes;
|
|
90
92
|
const parent = options.parent;
|
|
91
93
|
const native =
|
|
92
94
|
parent instanceof ProductionObservation
|
|
93
95
|
? startChild(parent.native, name, observationAttributes, options.asType)
|
|
94
96
|
: startRoot(name, observationAttributes, options.asType);
|
|
95
97
|
applySessionId(native, sessionId);
|
|
98
|
+
applyUserId(native, userId);
|
|
96
99
|
return new ProductionObservation(native);
|
|
97
100
|
}
|
|
98
101
|
|
|
@@ -186,6 +189,12 @@ function applySessionId(observation: LangfuseObservation, sessionId: string | un
|
|
|
186
189
|
}
|
|
187
190
|
}
|
|
188
191
|
|
|
192
|
+
function applyUserId(observation: LangfuseObservation, userId: string | undefined): void {
|
|
193
|
+
if (userId !== undefined) {
|
|
194
|
+
observation.otelSpan.setAttribute(LangfuseOtelSpanAttributes.TRACE_USER_ID, userId);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
189
198
|
function startRoot(
|
|
190
199
|
name: string,
|
|
191
200
|
attributes: ObservationAttributes,
|
package/src/tracing.ts
CHANGED
|
@@ -40,6 +40,7 @@ export interface ObservationAttributes {
|
|
|
40
40
|
version?: string;
|
|
41
41
|
name?: string;
|
|
42
42
|
sessionId?: string;
|
|
43
|
+
userId?: string;
|
|
43
44
|
tags?: string[];
|
|
44
45
|
}
|
|
45
46
|
|
|
@@ -63,6 +64,7 @@ export interface TraceBackend {
|
|
|
63
64
|
|
|
64
65
|
interface RecorderContext {
|
|
65
66
|
sessionId: string;
|
|
67
|
+
userId?: string;
|
|
66
68
|
cwd: string;
|
|
67
69
|
mode: string;
|
|
68
70
|
captureContent: boolean;
|
|
@@ -239,7 +241,12 @@ export class TraceRecorder {
|
|
|
239
241
|
attributes: ObservationAttributes,
|
|
240
242
|
options: { asType: ObservationType; parent?: Observation },
|
|
241
243
|
): Observation {
|
|
242
|
-
|
|
244
|
+
const { sessionId, userId } = this.context;
|
|
245
|
+
return this.backend.start(
|
|
246
|
+
name,
|
|
247
|
+
{ ...attributes, sessionId, ...(userId ? { userId } : {}) },
|
|
248
|
+
options,
|
|
249
|
+
);
|
|
243
250
|
}
|
|
244
251
|
|
|
245
252
|
hasActiveTrace(): boolean {
|
|
@@ -287,6 +294,7 @@ export class TraceRecorder {
|
|
|
287
294
|
this.root.updateTrace?.({
|
|
288
295
|
name: "pi.trace",
|
|
289
296
|
sessionId: this.context.sessionId,
|
|
297
|
+
...(this.context.userId ? { userId: this.context.userId } : {}),
|
|
290
298
|
version: TRACE_SCHEMA_VERSION,
|
|
291
299
|
input: traceInput,
|
|
292
300
|
metadata,
|