@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 CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/@narumitw/pi-langfuse)](https://www.npmjs.com/package/@narumitw/pi-langfuse) [![Pi extension](https://img.shields.io/badge/Pi-extension-blue)](https://pi.dev) [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](./LICENSE)
4
4
 
5
- Trace Pi agent runs, model generations, retries, tools, compaction, usage, and timing in [Langfuse](https://langfuse.com/) through OpenTelemetry.
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, TTFT, usage, known costs, and safe response diagnostics.
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
- Try a local checkout:
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
- An unbuilt local checkout must be built before loading the package directory.
37
- The Langfuse v4 SDK requires Node.js 20 or newer.
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 the setup action, enter the Langfuse credentials, and restart Pi.
42
- Subsequent agent runs are traced according to the saved content-capture and metadata settings.
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, then choose **Set up Langfuse for this Pi agent directory** or **Update Langfuse for this Pi agent directory**:
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 setup and update writes until repaired; errors redact entered and stored credentials.
58
- A failed save leaves the current session and prior file unchanged.
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 belongs to the displayed Pi agent directory, not just the current conversation.
61
- Restart each running Pi process after saving; the new connection applies to subsequent sessions in that process.
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 the file manually:
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
- "environment": "development",
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
- 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.
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
- Restart Pi after changing credentials, endpoint, environment, release, or `captureContent`.
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 selected only for Langfuse; it does not replace Pi's process-global provider or send Langfuse observations to another extension's exporter.
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 at this handler, not a guaranteed final wire payload: later extensions can still replace it.
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 `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`.
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
- ```text
194
- /langfuse
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
- Available actions depend on that state:
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 your Langfuse retention and access controls before enabling this extension.
210
+ Review Langfuse retention and access controls before enabling the extension.
219
211
 
220
- 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.
221
- 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.
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
- ```txt
224
+ ```text
231
225
  packages/pi-langfuse/
232
- ├── src/
233
- │ ├── index.ts # Thin repository entrypoint
234
- │ ├── langfuse.ts # Pi lifecycle integration and slash command
235
- │ ├── tracing.ts # Observation lifecycle, outcomes, and bounded metadata
236
- │ ├── sanitizer.ts # Content bounding and opaque-signature removal
237
- │ ├── runtime.ts # Lazy Langfuse/OpenTelemetry runtime
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 package build generates the published Pi entrypoint at `dist/index.ts` and preserves `runtime.ts` as a first-use chunk.
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-247EMSG3.js.map
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-247EMSG3.js");
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}`,