@narumitw/pi-langfuse 0.50.4 → 0.50.6
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 +46 -61
- package/dist/chunks/{runtime-247EMSG3.js → runtime-X75FRK2E.js} +2 -5
- package/dist/chunks/runtime-X75FRK2E.js.map +7 -0
- package/dist/index.ts +12 -48
- package/dist/index.ts.map +2 -2
- package/package.json +55 -59
- package/src/config.ts +211 -232
- package/src/langfuse.ts +484 -536
- package/src/runtime.ts +201 -218
- package/src/sanitizer.ts +112 -117
- package/src/tracing.ts +787 -840
- package/dist/chunks/runtime-247EMSG3.js.map +0 -7
package/README.md
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@narumitw/pi-langfuse) [](https://pi.dev) [](./LICENSE)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Export Pi agent runs, model generations, retries, tools, compaction, usage, and timing to [Langfuse](https://langfuse.com/) through OpenTelemetry.
|
|
6
6
|
|
|
7
7
|
## ✨ Features
|
|
8
8
|
|
|
9
9
|
- Groups each settled agent run into one trace with indexed attempt spans for retries and queued continuations.
|
|
10
|
-
- Records bounded provider requests, final assistant output, identity,
|
|
10
|
+
- Records bounded provider requests, final assistant output, model identity, time to first content, usage, known costs, and safe response diagnostics.
|
|
11
11
|
- Captures final tool inputs and outputs, progress timing, duration, failures, and structural compaction events.
|
|
12
12
|
- Adds session, context, Git branch, commit, and aggregate counters as searchable metadata.
|
|
13
13
|
- Supports metadata-only tracing when content capture is disabled.
|
|
@@ -26,24 +26,26 @@ Try the published package without installing it:
|
|
|
26
26
|
pi -e npm:@narumitw/pi-langfuse
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
Build and load a local checkout from the repository root:
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
32
|
npm --workspace @narumitw/pi-langfuse run build
|
|
33
33
|
pi -e ./packages/pi-langfuse
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
The Langfuse
|
|
36
|
+
The package declares `dist/index.ts`, so Pi cannot load an unbuilt local checkout.
|
|
37
|
+
The installed Langfuse SDK dependencies require Node.js 20 or newer.
|
|
38
|
+
Pi extensions run with your user permissions.
|
|
39
|
+
Review this extension and its data-export behavior before installing it.
|
|
38
40
|
|
|
39
41
|
## 🚀 Quick start
|
|
40
42
|
|
|
41
|
-
Run `/langfuse`, choose
|
|
42
|
-
|
|
43
|
+
Run `/langfuse`, choose **Set up Langfuse for this Pi agent directory**, enter your credentials, and restart Pi.
|
|
44
|
+
New agent runs are then traced with the saved content-capture and metadata settings.
|
|
43
45
|
|
|
44
46
|
## ⚙️ Settings
|
|
45
47
|
|
|
46
|
-
Run the interactive manager
|
|
48
|
+
Run the interactive manager and choose **Set up Langfuse for this Pi agent directory** or **Update Langfuse for this Pi agent directory**:
|
|
47
49
|
|
|
48
50
|
```text
|
|
49
51
|
/langfuse
|
|
@@ -54,25 +56,24 @@ Leave either key blank to preserve its existing value when updating a valid conf
|
|
|
54
56
|
Leave the base URL blank to use `https://us.cloud.langfuse.com`.
|
|
55
57
|
The first successful setup creates the file.
|
|
56
58
|
Within one Pi process, updates run in invocation order, reread the latest valid private document, preserve unknown fields, and save atomically with mode `0600`.
|
|
57
|
-
Malformed JSON or invalid recognized fields block
|
|
58
|
-
|
|
59
|
+
Malformed JSON or invalid recognized fields block writes until you repair the file.
|
|
60
|
+
Errors redact entered and stored credentials.
|
|
61
|
+
A failed save leaves the current session and previous file unchanged.
|
|
59
62
|
|
|
60
|
-
Configuration
|
|
61
|
-
Restart
|
|
63
|
+
Configuration applies to the displayed Pi agent directory, not only the current conversation.
|
|
64
|
+
Restart every running Pi process after saving so later sessions use the new connection.
|
|
62
65
|
`/reload` is not sufficient because the isolated Langfuse runtime is initialized once per process.
|
|
63
66
|
In print or JSON mode, edit the file manually because the interactive manager is unavailable.
|
|
64
67
|
|
|
65
|
-
You can also create
|
|
68
|
+
You can also create `<getAgentDir()>/pi-langfuse.json` (normally `~/.pi/agent/pi-langfuse.json`) manually.
|
|
69
|
+
This minimal example keeps prompts, responses, and tool content local:
|
|
66
70
|
|
|
67
71
|
```json
|
|
68
72
|
{
|
|
69
73
|
"publicKey": "pk-lf-...",
|
|
70
74
|
"secretKey": "sk-lf-...",
|
|
71
75
|
"baseUrl": "https://us.cloud.langfuse.com",
|
|
72
|
-
"
|
|
73
|
-
"release": "local",
|
|
74
|
-
"userId": "your-user-id",
|
|
75
|
-
"captureContent": true
|
|
76
|
+
"captureContent": false
|
|
76
77
|
}
|
|
77
78
|
```
|
|
78
79
|
|
|
@@ -84,7 +85,9 @@ Prefer HTTPS because HTTP sends Langfuse credentials and trace content without t
|
|
|
84
85
|
`environment`, `release`, and `userId` are optional Langfuse trace attributes.
|
|
85
86
|
An environment must match Langfuse's contract: at most 40 lowercase letters, numbers, hyphens, or underscores, and it cannot start with `langfuse`.
|
|
86
87
|
`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.
|
|
87
|
-
|
|
88
|
+
`captureContent` defaults to `true` when omitted.
|
|
89
|
+
Set it to `false` to trace only timing, model, usage, cost, status, and bounded diagnostic metadata.
|
|
90
|
+
In that mode, pi-langfuse does not export prompts, provider-request snapshots, responses, or tool content.
|
|
88
91
|
|
|
89
92
|
The extension automatically restricts an existing config file to mode `0600` and refuses to load credentials if that protection cannot be enforced.
|
|
90
93
|
You can also set it explicitly:
|
|
@@ -93,9 +96,10 @@ You can also set it explicitly:
|
|
|
93
96
|
chmod 600 ~/.pi/agent/pi-langfuse.json
|
|
94
97
|
```
|
|
95
98
|
|
|
96
|
-
|
|
99
|
+
Changes to credentials, endpoint, environment, release, or `captureContent` require a Pi process restart.
|
|
97
100
|
Changes to `userId` apply to new sessions without restarting Pi.
|
|
98
|
-
The isolated OpenTelemetry tracer provider is initialized once per Pi process and
|
|
101
|
+
The isolated OpenTelemetry tracer provider is initialized once per Pi process and used only for Langfuse.
|
|
102
|
+
It does not replace Pi's process-global provider or send Langfuse observations to another extension's exporter.
|
|
99
103
|
|
|
100
104
|
## 🔭 What is traced
|
|
101
105
|
|
|
@@ -148,7 +152,8 @@ Each `pi.llm` generation records:
|
|
|
148
152
|
- additive input, output, cache-read, cache-write, and total token usage plus known positive input, output, cache-read, cache-write, and total cost buckets;
|
|
149
153
|
- non-additive `pi.usage.reasoning_tokens` and `pi.usage.cache_write_1h_tokens` in metadata so subsets are not double-counted.
|
|
150
154
|
|
|
151
|
-
The request snapshot is the payload visible
|
|
155
|
+
The request snapshot is the payload visible to this handler, not a guaranteed final wire payload.
|
|
156
|
+
A later extension can still replace it.
|
|
152
157
|
Final assistant content is reconciled from `turn_end` and `agent_end` after message transformation.
|
|
153
158
|
A recovered sequence such as `429 -> 200` remains queryable in HTTP metadata but is not an error; the final assistant outcome decides generation severity.
|
|
154
159
|
|
|
@@ -162,7 +167,8 @@ Existing `pi.tool.call_id` and `pi.tool.name` correlation fields remain.
|
|
|
162
167
|
`tool_execution_update` partial-result bodies are never captured.
|
|
163
168
|
Duplicate, parallel, failed, no-progress, and interrupted tools are closed independently.
|
|
164
169
|
|
|
165
|
-
An active `pi.compaction` records
|
|
170
|
+
An active `pi.compaction` records its reason, retry state, source, token count, message counts, branch-entry count, and split-turn state.
|
|
171
|
+
The exported field names are `pi.compaction.reason`, `pi.compaction.will_retry`, `pi.compaction.from_extension`, `pi.compaction.tokens_before`, `pi.compaction.messages_to_summarize`, `pi.compaction.turn_prefix_messages`, `pi.compaction.branch_entries`, and `pi.compaction.is_split_turn`.
|
|
166
172
|
It adds `pi.compaction.read_file_count`, `pi.compaction.modified_file_count`, and `pi.compaction.usage.*` / `pi.compaction.cost.*` when Pi reports them.
|
|
167
173
|
It never records the summary, custom instructions, or message bodies.
|
|
168
174
|
Manual compaction outside an active agent trace is ignored; incomplete compaction closes as a warning at settlement or shutdown.
|
|
@@ -190,35 +196,23 @@ To wait for completed exports, run `/langfuse` and choose **Flush completed trac
|
|
|
190
196
|
|
|
191
197
|
## 💬 Commands
|
|
192
198
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
The command opens one context-aware standard menu in TUI or RPC mode.
|
|
198
|
-
Its state lines show the current session's tracing state, endpoint, content-capture mode, initialization failure when applicable, and private configuration path.
|
|
199
|
-
It never displays credentials.
|
|
200
|
-
Escape closes the menu.
|
|
201
|
-
Credential and endpoint text inputs remain extension-owned because they enforce secret-preserving setup/update semantics rather than standard navigation.
|
|
199
|
+
Run `/langfuse` to configure tracing, inspect its state without exposing credentials, or flush completed traces for the current session in TUI or RPC mode.
|
|
200
|
+
Arguments are ignored for compatibility and cannot bypass the menu.
|
|
201
|
+
Print and JSON modes reject the command with an error containing tracing state and the manual configuration path.
|
|
202
202
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
- **Flush completed traces for this session** appears first when tracing is active and waits for completed observations to export.
|
|
206
|
-
- **Set up Langfuse for this Pi agent directory** appears when no valid config was loaded.
|
|
207
|
-
- **Update Langfuse for this Pi agent directory** appears when a valid config exists.
|
|
208
|
-
- **Show setup and privacy help** explains the agent-directory scope, manual configuration path, and content-capture risk.
|
|
209
|
-
|
|
210
|
-
Connection actions state their agent-directory scope and per-process restart requirement before selection.
|
|
211
|
-
Command arguments are intentionally ignored so remembered subcommands cannot silently bypass the menu.
|
|
212
|
-
Print and JSON modes reject the interactive command with an observable error that includes current tracing state and the manual configuration path.
|
|
203
|
+
Connection changes apply to the current Pi agent directory and require each process to restart; see [Settings](#-settings).
|
|
204
|
+
Flushing exports completed observations to the configured endpoint; review [Security and privacy](#-security-and-privacy) before enabling content capture.
|
|
213
205
|
|
|
214
206
|
## 🔒 Security and privacy
|
|
215
207
|
|
|
216
208
|
With content capture enabled, traces can contain user prompts, model responses, tool arguments, and tool results.
|
|
217
209
|
These may include source code, file contents, shell output, or other sensitive project data.
|
|
218
|
-
Review
|
|
210
|
+
Review Langfuse retention and access controls before enabling the extension.
|
|
219
211
|
|
|
220
|
-
Git branch names, commit ids, working directory, session
|
|
221
|
-
|
|
212
|
+
Metadata includes Git branch names, commit ids, working directory, session and leaf ids, the configured `userId`, model identity, usage, cost, aggregate counts, and allowlisted response-header values.
|
|
213
|
+
This metadata remains exported when `captureContent` is `false`.
|
|
214
|
+
Branch names and diagnostic header values can contain operational details.
|
|
215
|
+
A `userId` can identify you if you set it to an email address or real name.
|
|
222
216
|
Choose a pseudonymous value when that matters, or leave `userId` unset to export no user at all.
|
|
223
217
|
|
|
224
218
|
The built-in mask specifically protects Langfuse credentials; it is not a general secret scanner.
|
|
@@ -227,26 +221,17 @@ Compaction summaries, tool partial results, opaque continuation signatures, auth
|
|
|
227
221
|
|
|
228
222
|
## 🗂️ Package layout
|
|
229
223
|
|
|
230
|
-
```
|
|
224
|
+
```text
|
|
231
225
|
packages/pi-langfuse/
|
|
232
|
-
├── src/
|
|
233
|
-
│ ├── index.ts
|
|
234
|
-
│
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
│ └── config.ts # Private pi-langfuse.json loading and validation
|
|
239
|
-
├── dist/ # Generated Jiti runtime and lazy chunks
|
|
240
|
-
├── scripts/build-runtime.mjs
|
|
241
|
-
├── test/
|
|
242
|
-
├── README.md
|
|
243
|
-
├── LICENSE
|
|
244
|
-
├── tsconfig.json
|
|
245
|
-
└── package.json
|
|
226
|
+
├── src/ # Authoritative implementation and helpers
|
|
227
|
+
│ ├── index.ts # Thin Pi entrypoint
|
|
228
|
+
│ └── langfuse.ts # Tracing lifecycle and command
|
|
229
|
+
├── dist/ # Generated Jiti runtime
|
|
230
|
+
├── scripts/build-runtime.mjs # Runtime builder
|
|
231
|
+
└── test/ # Behavior and lifecycle coverage
|
|
246
232
|
```
|
|
247
233
|
|
|
248
|
-
The
|
|
249
|
-
The source modules remain authoritative.
|
|
234
|
+
The generated runtime is built from `src/index.ts` and does not import back into `src`.
|
|
250
235
|
|
|
251
236
|
## 🔎 Keywords
|
|
252
237
|
|
|
@@ -40,10 +40,7 @@ var ProductionObservation = class {
|
|
|
40
40
|
this.native.otelSpan.setAttribute(LangfuseOtelSpanAttributes.VERSION, version);
|
|
41
41
|
}
|
|
42
42
|
for (const [key, value] of Object.entries(metadata ?? {})) {
|
|
43
|
-
this.native.otelSpan.setAttribute(
|
|
44
|
-
`${LangfuseOtelSpanAttributes.TRACE_METADATA}.${key}`,
|
|
45
|
-
String(value)
|
|
46
|
-
);
|
|
43
|
+
this.native.otelSpan.setAttribute(`${LangfuseOtelSpanAttributes.TRACE_METADATA}.${key}`, String(value));
|
|
47
44
|
}
|
|
48
45
|
return this;
|
|
49
46
|
}
|
|
@@ -215,4 +212,4 @@ export {
|
|
|
215
212
|
createProductionBackend,
|
|
216
213
|
maskSecrets
|
|
217
214
|
};
|
|
218
|
-
//# sourceMappingURL=runtime-
|
|
215
|
+
//# sourceMappingURL=runtime-X75FRK2E.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/runtime.ts"],
|
|
4
|
+
"sourcesContent": ["import { createHash } from \"node:crypto\";\nimport { LangfuseSpanProcessor } from \"@langfuse/otel\";\nimport {\n type LangfuseObservation,\n type LangfuseObservationAttributes,\n LangfuseOtelSpanAttributes,\n setLangfuseTracerProvider,\n startObservation,\n} from \"@langfuse/tracing\";\nimport type { SpanProcessor } from \"@opentelemetry/sdk-trace-base\";\nimport { NodeTracerProvider } from \"@opentelemetry/sdk-trace-node\";\nimport type { LangfuseConfig } from \"./config.js\";\nimport type { Observation, ObservationAttributes, ObservationType, TraceBackend } from \"./tracing.js\";\n\nconst RUNTIME_KEY = Symbol.for(\"@narumitw/pi-langfuse/runtime/v1\");\n\ninterface SharedRuntime {\n fingerprint: string;\n backend: ProductionTraceBackend;\n shutdown: boolean;\n}\n\ntype GlobalWithRuntime = typeof globalThis & {\n [RUNTIME_KEY]?: Promise<SharedRuntime>;\n};\n\nexport interface RuntimeFactories {\n createProcessor(config: LangfuseConfig): SpanProcessor;\n createProvider(processor: SpanProcessor): NodeTracerProvider;\n selectProvider(provider: NodeTracerProvider): void;\n}\n\nclass ProductionObservation implements Observation {\n constructor(readonly native: LangfuseObservation) {}\n\n update(attributes: ObservationAttributes): Observation {\n const { sessionId, userId, ...observationAttributes } = attributes;\n this.native.updateOtelSpanAttributes(observationAttributes as LangfuseObservationAttributes);\n applySessionId(this.native, sessionId);\n applyUserId(this.native, userId);\n return this;\n }\n\n updateTrace(attributes: ObservationAttributes): Observation {\n const { input, output, metadata, name, sessionId, userId, tags, version } = attributes;\n if (input !== undefined || output !== undefined) {\n this.native.setTraceIO({ input, output });\n }\n if (name !== undefined) {\n this.native.otelSpan.setAttribute(LangfuseOtelSpanAttributes.TRACE_NAME, name);\n }\n applySessionId(this.native, sessionId);\n applyUserId(this.native, userId);\n if (tags !== undefined) {\n this.native.otelSpan.setAttribute(LangfuseOtelSpanAttributes.TRACE_TAGS, tags);\n }\n if (version !== undefined) {\n this.native.otelSpan.setAttribute(LangfuseOtelSpanAttributes.VERSION, version);\n }\n for (const [key, value] of Object.entries(metadata ?? {})) {\n this.native.otelSpan.setAttribute(`${LangfuseOtelSpanAttributes.TRACE_METADATA}.${key}`, String(value));\n }\n return this;\n }\n\n end(endTime?: number): Observation {\n this.native.end(endTime);\n return this;\n }\n}\n\nclass ProductionTraceBackend implements TraceBackend {\n constructor(\n private readonly provider: NodeTracerProvider,\n private readonly processor: SpanProcessor,\n ) {}\n\n start(\n name: string,\n attributes: ObservationAttributes,\n options: { asType: ObservationType; parent?: Observation },\n ): Observation {\n const { sessionId, userId, ...observationAttributes } = attributes;\n const parent = options.parent;\n const native =\n parent instanceof ProductionObservation\n ? startChild(parent.native, name, observationAttributes, options.asType)\n : startRoot(name, observationAttributes, options.asType);\n applySessionId(native, sessionId);\n applyUserId(native, userId);\n return new ProductionObservation(native);\n }\n\n async forceFlush(): Promise<void> {\n await this.processor.forceFlush();\n }\n\n async shutdown(): Promise<void> {\n await this.provider.shutdown();\n }\n}\n\nexport async function createProductionBackend(\n config: LangfuseConfig,\n factoryOverrides: Partial<RuntimeFactories> = {},\n): Promise<TraceBackend> {\n const globalRuntime = globalThis as GlobalWithRuntime;\n const fingerprint = configFingerprint(config);\n const existing = globalRuntime[RUNTIME_KEY];\n if (existing) {\n const runtime = await existing;\n if (runtime.shutdown) {\n throw new Error(\"Langfuse tracing was already shut down; restart Pi to enable it again.\");\n }\n if (runtime.fingerprint !== fingerprint) {\n throw new Error(\"Langfuse configuration changed; restart Pi to apply the new credentials.\");\n }\n return runtime.backend;\n }\n\n const factories = { ...defaultFactories, ...factoryOverrides };\n const initializing = initializeRuntime(config, fingerprint, factories);\n globalRuntime[RUNTIME_KEY] = initializing;\n try {\n return (await initializing).backend;\n } catch (error) {\n delete globalRuntime[RUNTIME_KEY];\n throw error;\n }\n}\n\nasync function initializeRuntime(\n config: LangfuseConfig,\n fingerprint: string,\n factories: RuntimeFactories,\n): Promise<SharedRuntime> {\n const processor = factories.createProcessor(config);\n let provider: NodeTracerProvider | undefined;\n try {\n provider = factories.createProvider(processor);\n factories.selectProvider(provider);\n } catch (error) {\n await (provider?.shutdown() ?? processor.shutdown()).catch(() => undefined);\n throw error;\n }\n const backend = new ProductionTraceBackend(provider, processor);\n const runtime: SharedRuntime = { fingerprint, backend, shutdown: false };\n const originalShutdown = backend.shutdown.bind(backend);\n backend.shutdown = async () => {\n if (runtime.shutdown) return;\n runtime.shutdown = true;\n await originalShutdown();\n };\n return runtime;\n}\n\nconst defaultFactories: RuntimeFactories = {\n createProcessor: (config) => {\n const secrets = [config.secretKey, config.publicKey];\n return new LangfuseSpanProcessor({\n publicKey: config.publicKey,\n secretKey: config.secretKey,\n baseUrl: config.baseUrl,\n environment: config.environment ?? \"\",\n release: config.release ?? \"\",\n flushAt: 512,\n flushInterval: 5,\n timeout: 5,\n mask: ({ data }) => maskSecrets(data, secrets),\n shouldExportSpan: ({ otelSpan }) => typeof otelSpan.attributes[\"langfuse.observation.type\"] === \"string\",\n });\n },\n createProvider: (processor) => new NodeTracerProvider({ spanProcessors: [processor] }),\n selectProvider: setLangfuseTracerProvider,\n};\n\nfunction applySessionId(observation: LangfuseObservation, sessionId: string | undefined): void {\n if (sessionId !== undefined) {\n observation.otelSpan.setAttribute(LangfuseOtelSpanAttributes.TRACE_SESSION_ID, sessionId);\n }\n}\n\nfunction applyUserId(observation: LangfuseObservation, userId: string | undefined): void {\n if (userId !== undefined) {\n observation.otelSpan.setAttribute(LangfuseOtelSpanAttributes.TRACE_USER_ID, userId);\n }\n}\n\nfunction startRoot(name: string, attributes: ObservationAttributes, type: ObservationType): LangfuseObservation {\n if (type === \"agent\") {\n return startObservation(name, attributes as LangfuseObservationAttributes, { asType: \"agent\" });\n }\n if (type === \"generation\") {\n return startObservation(name, attributes as LangfuseObservationAttributes, {\n asType: \"generation\",\n });\n }\n if (type === \"tool\") {\n return startObservation(name, attributes as LangfuseObservationAttributes, { asType: \"tool\" });\n }\n return startObservation(name, attributes as LangfuseObservationAttributes, { asType: \"span\" });\n}\n\nfunction startChild(\n parent: LangfuseObservation,\n name: string,\n attributes: ObservationAttributes,\n type: ObservationType,\n): LangfuseObservation {\n if (type === \"agent\") {\n return parent.startObservation(name, attributes as LangfuseObservationAttributes, {\n asType: \"agent\",\n });\n }\n if (type === \"generation\") {\n return parent.startObservation(name, attributes as LangfuseObservationAttributes, {\n asType: \"generation\",\n });\n }\n if (type === \"tool\") {\n return parent.startObservation(name, attributes as LangfuseObservationAttributes, {\n asType: \"tool\",\n });\n }\n return parent.startObservation(name, attributes as LangfuseObservationAttributes, {\n asType: \"span\",\n });\n}\n\nfunction configFingerprint(config: LangfuseConfig): string {\n return createHash(\"sha256\")\n .update(\n `${config.publicKey}\\0${config.secretKey}\\0${config.baseUrl}\\0${config.environment ?? \"\"}\\0${config.release ?? \"\"}`,\n )\n .digest(\"hex\");\n}\n\nexport function maskSecrets(data: unknown, secrets: readonly string[]): unknown {\n return maskSecretValue(data, secrets, new WeakSet<object>());\n}\n\nfunction maskSecretValue(data: unknown, secrets: readonly string[], active: WeakSet<object>): unknown {\n if (typeof data === \"string\") {\n let masked = data.replace(/\\b(?:sk|pk)-lf-[A-Za-z0-9_-]+\\b/g, \"[LANGFUSE_KEY_REDACTED]\");\n for (const secret of secrets) {\n if (secret) masked = masked.replaceAll(secret, \"[LANGFUSE_KEY_REDACTED]\");\n }\n return masked;\n }\n if (!data || typeof data !== \"object\") return data;\n if (active.has(data)) return \"[circular]\";\n\n active.add(data);\n try {\n if (Array.isArray(data)) {\n return data.map((item) => maskSecretValue(item, secrets, active));\n }\n return Object.fromEntries(\n Object.entries(data).map(([key, value]) => [key, maskSecretValue(value, secrets, active)]),\n );\n } finally {\n active.delete(data);\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/index.ts
CHANGED
|
@@ -410,11 +410,7 @@ var TraceRecorder = class {
|
|
|
410
410
|
counters = emptyCounters();
|
|
411
411
|
startObservation(name, attributes, options) {
|
|
412
412
|
const { sessionId, userId } = this.context;
|
|
413
|
-
return this.backend.start(
|
|
414
|
-
name,
|
|
415
|
-
{ ...attributes, sessionId, ...userId ? { userId } : {} },
|
|
416
|
-
options
|
|
417
|
-
);
|
|
413
|
+
return this.backend.start(name, { ...attributes, sessionId, ...userId ? { userId } : {} }, options);
|
|
418
414
|
}
|
|
419
415
|
hasActiveTrace() {
|
|
420
416
|
return this.root !== void 0;
|
|
@@ -682,10 +678,7 @@ var TraceRecorder = class {
|
|
|
682
678
|
metadata: {
|
|
683
679
|
"pi.tool.progress_update_count": tool.progressUpdates,
|
|
684
680
|
...tool.firstProgressAt !== void 0 ? {
|
|
685
|
-
"pi.tool.time_to_first_progress_ms": Math.max(
|
|
686
|
-
0,
|
|
687
|
-
tool.firstProgressAt - tool.startedAt
|
|
688
|
-
)
|
|
681
|
+
"pi.tool.time_to_first_progress_ms": Math.max(0, tool.firstProgressAt - tool.startedAt)
|
|
689
682
|
} : {}
|
|
690
683
|
},
|
|
691
684
|
...result.isError ? { level: "ERROR", statusMessage: "Pi tool execution failed." } : {}
|
|
@@ -850,10 +843,7 @@ var TraceRecorder = class {
|
|
|
850
843
|
metadata: {
|
|
851
844
|
"pi.tool.progress_update_count": tool.progressUpdates,
|
|
852
845
|
...tool.firstProgressAt !== void 0 ? {
|
|
853
|
-
"pi.tool.time_to_first_progress_ms": Math.max(
|
|
854
|
-
0,
|
|
855
|
-
tool.firstProgressAt - tool.startedAt
|
|
856
|
-
)
|
|
846
|
+
"pi.tool.time_to_first_progress_ms": Math.max(0, tool.firstProgressAt - tool.startedAt)
|
|
857
847
|
} : {}
|
|
858
848
|
}
|
|
859
849
|
});
|
|
@@ -945,9 +935,7 @@ function severityForOutcome(outcome, statusMessage) {
|
|
|
945
935
|
};
|
|
946
936
|
}
|
|
947
937
|
function prefixRecord(prefix, values) {
|
|
948
|
-
return Object.fromEntries(
|
|
949
|
-
Object.entries(values).map(([key, value]) => [`${prefix}.${key}`, value])
|
|
950
|
-
);
|
|
938
|
+
return Object.fromEntries(Object.entries(values).map(([key, value]) => [`${prefix}.${key}`, value]));
|
|
951
939
|
}
|
|
952
940
|
function truncateString2(value, maxBytes) {
|
|
953
941
|
const boundedPrefix = value.slice(0, maxBytes);
|
|
@@ -976,9 +964,7 @@ function numericRecord(values) {
|
|
|
976
964
|
}
|
|
977
965
|
function positiveNumericRecord(values) {
|
|
978
966
|
return Object.fromEntries(
|
|
979
|
-
Object.entries(values).filter(
|
|
980
|
-
(entry) => finiteNumber(entry[1]) && entry[1] > 0
|
|
981
|
-
)
|
|
967
|
+
Object.entries(values).filter((entry) => finiteNumber(entry[1]) && entry[1] > 0)
|
|
982
968
|
);
|
|
983
969
|
}
|
|
984
970
|
function finiteNumber(value) {
|
|
@@ -1009,7 +995,7 @@ function createLangfuseExtension(dependencies = {}) {
|
|
|
1009
995
|
const loadConfig = dependencies.loadConfig ?? loadLangfuseConfig;
|
|
1010
996
|
const writeConfig = dependencies.writeConfig ?? writeLangfuseConfig;
|
|
1011
997
|
const createBackend = dependencies.createBackend ?? (async (config) => {
|
|
1012
|
-
const { createProductionBackend } = await import("./chunks/runtime-
|
|
998
|
+
const { createProductionBackend } = await import("./chunks/runtime-X75FRK2E.js");
|
|
1013
999
|
return createProductionBackend(config);
|
|
1014
1000
|
});
|
|
1015
1001
|
return function langfuse(pi) {
|
|
@@ -1026,10 +1012,7 @@ function createLangfuseExtension(dependencies = {}) {
|
|
|
1026
1012
|
let nextAttemptReason;
|
|
1027
1013
|
async function showLangfuseMenu(ctx) {
|
|
1028
1014
|
if (!ctx.hasUI) {
|
|
1029
|
-
ctx.ui.notify(
|
|
1030
|
-
formatNonInteractiveStatus(activeConfig, configPath, initializationError),
|
|
1031
|
-
"warning"
|
|
1032
|
-
);
|
|
1015
|
+
ctx.ui.notify(formatNonInteractiveStatus(activeConfig, configPath, initializationError), "warning");
|
|
1033
1016
|
return;
|
|
1034
1017
|
}
|
|
1035
1018
|
const menuGeneration = sessionGeneration;
|
|
@@ -1043,12 +1026,7 @@ function createLangfuseExtension(dependencies = {}) {
|
|
|
1043
1026
|
main: () => ({
|
|
1044
1027
|
kind: "actions",
|
|
1045
1028
|
title: "Langfuse",
|
|
1046
|
-
lines: formatMenuTitle(
|
|
1047
|
-
activeConfig,
|
|
1048
|
-
configPath,
|
|
1049
|
-
initializationError,
|
|
1050
|
-
configurationNotice
|
|
1051
|
-
).split("\n").slice(1),
|
|
1029
|
+
lines: formatMenuTitle(activeConfig, configPath, initializationError, configurationNotice).split("\n").slice(1),
|
|
1052
1030
|
items: [
|
|
1053
1031
|
...recorder ? [{ id: "flush", label: FLUSH_ACTION, action: "flush" }] : [],
|
|
1054
1032
|
{
|
|
@@ -1279,18 +1257,12 @@ function createLangfuseExtension(dependencies = {}) {
|
|
|
1279
1257
|
try {
|
|
1280
1258
|
if (event.reason === "quit") await activeRecorder.shutdown(shutdownSnapshot);
|
|
1281
1259
|
else {
|
|
1282
|
-
activeRecorder.interrupt(
|
|
1283
|
-
`Pi session ended before settlement (${event.reason}).`,
|
|
1284
|
-
shutdownSnapshot
|
|
1285
|
-
);
|
|
1260
|
+
activeRecorder.interrupt(`Pi session ended before settlement (${event.reason}).`, shutdownSnapshot);
|
|
1286
1261
|
await activeRecorder.flush();
|
|
1287
1262
|
}
|
|
1288
1263
|
} catch (error) {
|
|
1289
1264
|
if (generation !== sessionGeneration) return;
|
|
1290
|
-
ctx.ui.notify(
|
|
1291
|
-
`Langfuse shutdown export failed: ${formatError2(error, shutdownConfig)}`,
|
|
1292
|
-
"error"
|
|
1293
|
-
);
|
|
1265
|
+
ctx.ui.notify(`Langfuse shutdown export failed: ${formatError2(error, shutdownConfig)}`, "error");
|
|
1294
1266
|
}
|
|
1295
1267
|
});
|
|
1296
1268
|
};
|
|
@@ -1324,10 +1296,7 @@ function isRealOutputDelta(event) {
|
|
|
1324
1296
|
var GIT_LOOKUP_TIMEOUT_MS = 1e3;
|
|
1325
1297
|
var MAX_GIT_BRANCH_LENGTH = 256;
|
|
1326
1298
|
async function resolveGitMetadata(exec, cwd) {
|
|
1327
|
-
const [branchResult, commit] = await Promise.all([
|
|
1328
|
-
resolveGitBranch(exec, cwd),
|
|
1329
|
-
resolveGitCommit(exec, cwd)
|
|
1330
|
-
]);
|
|
1299
|
+
const [branchResult, commit] = await Promise.all([resolveGitBranch(exec, cwd), resolveGitCommit(exec, cwd)]);
|
|
1331
1300
|
if (!branchResult.resolved) return void 0;
|
|
1332
1301
|
if (branchResult.branch) {
|
|
1333
1302
|
return {
|
|
@@ -1410,12 +1379,7 @@ async function promptForConfig(ctx, current, isCurrent) {
|
|
|
1410
1379
|
return patch;
|
|
1411
1380
|
}
|
|
1412
1381
|
function formatMenuTitle(activeConfig, configPath, initializationError, configurationNotice) {
|
|
1413
|
-
const lines = [
|
|
1414
|
-
"Langfuse",
|
|
1415
|
-
"",
|
|
1416
|
-
"Current session:",
|
|
1417
|
-
` Tracing: ${activeConfig ? "enabled" : "disabled"}`
|
|
1418
|
-
];
|
|
1382
|
+
const lines = ["Langfuse", "", "Current session:", ` Tracing: ${activeConfig ? "enabled" : "disabled"}`];
|
|
1419
1383
|
if (activeConfig) {
|
|
1420
1384
|
lines.push(
|
|
1421
1385
|
` Endpoint: ${activeConfig.baseUrl}`,
|