@narumitw/pi-langfuse 0.51.2 → 0.52.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.
Files changed (46) hide show
  1. package/README.md +46 -3
  2. package/dist/chunks/chunk-N7WSLB5Q.ts +88 -0
  3. package/dist/chunks/chunk-N7WSLB5Q.ts.map +7 -0
  4. package/dist/chunks/{runtime-6ZHLVC3A.ts → runtime-GLRAF7FE.ts} +2 -2
  5. package/dist/chunks/runtime-GLRAF7FE.ts.map +7 -0
  6. package/dist/config.d.ts +1 -1
  7. package/dist/durable-projection.d.ts +9 -0
  8. package/dist/durable-recorder.d.ts +49 -0
  9. package/dist/durable.d.ts +25 -0
  10. package/dist/durable.js +638 -0
  11. package/dist/durable.js.map +7 -0
  12. package/dist/index.js +248 -159
  13. package/dist/index.js.map +4 -4
  14. package/dist/index.ts +244 -16
  15. package/dist/index.ts.map +4 -4
  16. package/dist/library-chunks/chunk-B64FXCNE.js +84 -0
  17. package/dist/library-chunks/chunk-B64FXCNE.js.map +7 -0
  18. package/dist/library-chunks/chunk-ETFOPFEP.js +10 -0
  19. package/dist/library-chunks/chunk-ETFOPFEP.js.map +7 -0
  20. package/dist/library-chunks/chunk-JPFN6GGS.js +153 -0
  21. package/dist/library-chunks/chunk-JPFN6GGS.js.map +7 -0
  22. package/dist/library-chunks/{runtime-A6SQ6VEH.js → runtime-6KYOZVOE.js} +5 -3
  23. package/dist/library-chunks/runtime-6KYOZVOE.js.map +7 -0
  24. package/dist/public-api.d.ts +1 -3
  25. package/dist/runtime-api.d.ts +3 -0
  26. package/dist/runtime-defaults.d.ts +1 -0
  27. package/dist/runtime.d.ts +1 -1
  28. package/dist/sanitizer.d.ts +1 -1
  29. package/docs/durable.md +126 -0
  30. package/package.json +22 -2
  31. package/src/config.ts +4 -1
  32. package/src/durable-projection.ts +106 -0
  33. package/src/durable-recorder.ts +493 -0
  34. package/src/durable.ts +195 -0
  35. package/src/public-api.ts +6 -12
  36. package/src/runtime-api.ts +8 -0
  37. package/src/runtime-core.ts +2 -1
  38. package/src/runtime-defaults.ts +1 -0
  39. package/src/runtime.ts +2 -1
  40. package/src/sanitizer.ts +16 -4
  41. package/dist/chunks/chunk-DBQAWNKG.ts +0 -316
  42. package/dist/chunks/chunk-DBQAWNKG.ts.map +0 -7
  43. package/dist/chunks/runtime-6ZHLVC3A.ts.map +0 -7
  44. package/dist/library-chunks/chunk-SAJ76IYU.js +0 -316
  45. package/dist/library-chunks/chunk-SAJ76IYU.js.map +0 -7
  46. package/dist/library-chunks/runtime-A6SQ6VEH.js.map +0 -7
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/durable.ts", "../src/durable-projection.ts", "../src/durable-recorder.ts"],
4
+ "sourcesContent": ["import type { Context } from \"@earendil-works/chord\";\nimport { withCancel } from \"@earendil-works/chord/context\";\nimport {\n type AgentEventStream,\n type ConversationId,\n type EntryId,\n type Harness,\n type SubmissionId,\n watchEvents,\n} from \"@earendil-works/pi-durable\";\nimport { type DurableCorrelation, DurableRecorder, type DurableRecorderOptions } from \"./durable-recorder.js\";\nimport { getLangfuseRuntimeInternal, type LangfuseRuntime } from \"./runtime-core.js\";\n\nexport type { DurableCorrelation } from \"./durable-recorder.js\";\nexport {\n type CreateLangfuseRuntimeOptions,\n createLangfuseRuntime,\n type LangfuseRuntime,\n type LangfuseRuntimeConfig,\n} from \"./runtime-api.js\";\nexport { createLangfuseRuntimeFromBackend } from \"./runtime-core.js\";\nexport type { Observation, ObservationAttributes, ObservationType, TraceBackend } from \"./tracing.js\";\n\nexport interface PiLangfuseDurableOptions extends Omit<DurableRecorderOptions, \"conversationId\" | \"requestId\"> {\n harness: Harness;\n conversationId: ConversationId;\n /** Governs observation only. Cancellation never aborts execution or resumes recovery. */\n context: Context;\n}\n\nexport interface PiLangfuseDurableConversation {\n readonly active: boolean;\n /** Resolves when observation has detached, including cancellation and Harness closure. */\n readonly closed: Promise<void>;\n /** Read committed status without waiting for or starting agent work. Also supports late submission attachment. */\n observeSubmission(id: SubmissionId, correlation?: DurableCorrelation): Promise<void>;\n /** Detach and close observations, without flushing/shutting down the runtime or modifying durable work. */\n dispose(): Promise<void>;\n}\n\nconst observedConversations = new WeakMap<Harness, Set<ConversationId>>();\n\n/** Attach a fail-open observer using only public durable APIs. A failed attachment returns an inactive controller. */\nexport async function createPiLangfuseDurableConversation(\n runtime: LangfuseRuntime,\n options: PiLangfuseDurableOptions,\n): Promise<PiLangfuseDurableConversation> {\n options = { ...options };\n const { context, cancel } = withCancel(options.context);\n let stream: AgentEventStream | undefined;\n let recorder: DurableRecorder | undefined;\n let releaseRuntime: (() => void) | undefined;\n let disposed = false;\n let ownsConversation = false;\n let initialization: Promise<void> = Promise.resolve();\n let disposePromise: Promise<void> | undefined;\n let resolveClosed!: () => void;\n const closed = new Promise<void>((resolve) => {\n resolveClosed = resolve;\n });\n\n function report(stage: string): void {\n if (recorder) recorder.report(stage);\n else {\n try {\n Promise.resolve(options.onError?.(stage)).catch(() => undefined);\n } catch {\n /* Fail open. */\n }\n }\n }\n\n async function readEntry(id: EntryId) {\n const entry = await options.harness.commit((tx) => tx.entry(id), context);\n return disposed ? undefined : entry;\n }\n\n async function observeSubmission(id: SubmissionId, correlation?: DurableCorrelation): Promise<void> {\n if (disposed) return;\n try {\n const submission = await options.harness.submission(id, context);\n if (disposed || !submission) return;\n const record = await submission.status(context); // wait() would resume paused recovery.\n if (disposed) return;\n await recorder?.submission(record, readEntry, correlation, true);\n } catch {\n if (!disposed) report(\"submission status read\");\n }\n }\n\n function dispose(): Promise<void> {\n if (disposePromise) return disposePromise;\n disposed = true;\n cancel();\n if (ownsConversation) {\n observedConversations.get(options.harness)?.delete(options.conversationId);\n ownsConversation = false;\n }\n releaseRuntime?.();\n releaseRuntime = undefined;\n recorder?.dispose();\n disposePromise = (async () => {\n await initialization;\n try {\n await stream?.stop();\n } catch {\n report(\"observer stop\");\n }\n resolveClosed();\n })();\n return disposePromise;\n }\n\n const controller: PiLangfuseDurableConversation = {\n get active() {\n return !disposed && !runtime.closed && stream !== undefined;\n },\n closed,\n observeSubmission,\n dispose,\n };\n\n async function initialize(): Promise<void> {\n try {\n const conversations = observedConversations.get(options.harness) ?? new Set<ConversationId>();\n if (conversations.has(options.conversationId)) throw new Error(\"Conversation already has a tracing observer.\");\n conversations.add(options.conversationId);\n observedConversations.set(options.harness, conversations);\n ownsConversation = true;\n const internal = getLangfuseRuntimeInternal(runtime);\n releaseRuntime = internal.registerSession(dispose);\n const attached = await watchEvents(options.harness, options.conversationId, context);\n // Retain the owned stream before callback-capable trace acquisition so partial disposal joins it.\n stream = attached;\n if (disposed || runtime.closed) {\n void dispose();\n return;\n }\n // Failed attachment must not open a trace or publish its ID.\n recorder = new DurableRecorder(internal.backend, { ...options, metadata: { ...options.metadata } });\n if (disposed || runtime.closed) {\n recorder.dispose();\n void dispose();\n return;\n }\n recorder.snapshot(stream.snapshot, true);\n stream.start(async (events) => {\n for (const event of events) {\n if (disposed) return;\n try {\n if (event.type === \"snapshot\") {\n recorder?.snapshot(event);\n const ids = new Set([\n ...(recorder?.submissionIds ?? []),\n ...(event.run?.inputs ?? []),\n ...event.inbox.map((item) => item.id),\n ]);\n for (const id of ids) {\n await observeSubmission(id);\n if (disposed) return;\n }\n } else if (event.type === \"submission\") {\n await recorder?.submission(event.record, readEntry);\n if (disposed) return;\n } else recorder?.event(event);\n } catch {\n if (!disposed) report(\"observer delivery\");\n }\n }\n });\n // Observe closure even if the host never awaits closed. No execution wait is used.\n void stream.closed\n .then(\n () => dispose(),\n () => {\n report(\"observer closure\");\n return dispose();\n },\n )\n .catch(() => undefined);\n const ids = new Set([...(stream.snapshot.run?.inputs ?? []), ...stream.snapshot.inbox.map((item) => item.id)]);\n for (const id of ids) {\n await observeSubmission(id);\n if (disposed) return;\n }\n } catch {\n report(\"observer initialization\");\n void dispose();\n }\n }\n initialization = initialize();\n await initialization;\n if (disposed) await dispose();\n return controller;\n}\n", "import type { EntryRecord } from \"@earendil-works/pi-durable\";\nimport { sanitizeTraceValue } from \"./sanitizer.js\";\nimport type { ObservationAttributes } from \"./tracing.js\";\n\ntype Message = NonNullable<EntryRecord[\"model\"]>[number];\nconst MAX_PROJECTED_ITEMS = 199;\n\nexport function durableAttributes(attributes: ObservationAttributes): ObservationAttributes {\n const copy = { ...attributes };\n if (attributes.metadata) {\n const value = sanitizeTraceValue(attributes.metadata, true);\n copy.metadata =\n value && typeof value === \"object\" && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : { \"pi.durable.metadata_omitted\": true };\n }\n for (const key of [\"name\", \"model\", \"sessionId\", \"userId\", \"statusMessage\", \"version\"] as const) {\n if (typeof copy[key] === \"string\") copy[key] = String(sanitizeTraceValue(copy[key], true));\n }\n return copy;\n}\n\nexport function durableDiagnosticCount(entry: EntryRecord | undefined): number {\n const data = entry?.data;\n return data &&\n typeof data === \"object\" &&\n !Array.isArray(data) &&\n \"diagnostics\" in data &&\n Array.isArray(data.diagnostics)\n ? data.diagnostics.length\n : 0;\n}\n\n/** Project committed messages without provider configuration, deltas or raw diagnostics. */\nexport function durableContent(\n messages: readonly Message[] | undefined,\n capture: boolean,\n diagnosticCount = 0,\n): unknown {\n if (!capture) return \"[content capture disabled]\";\n const projected: unknown[] | undefined = messages?.slice(0, MAX_PROJECTED_ITEMS).map((message) => {\n if (message.role === \"toolResult\" && message.isError)\n return { role: message.role, content: \"[tool error content omitted]\" };\n if (message.role === \"assistant\" && message.stopReason === \"error\")\n return { role: message.role, content: \"[assistant error content omitted]\" };\n // The public ToolResultEntry shape appends rendered diagnostics as its last text block.\n // That block can contain raw errors, headers or credentials even when the tool succeeds.\n const omitDiagnosticBlock =\n message.role === \"toolResult\" && diagnosticCount > 0 && message.content.at(-1)?.type === \"text\";\n const blockCount = message.content.length - (omitDiagnosticBlock ? 1 : 0);\n return {\n role: message.role,\n content:\n typeof message.content === \"string\"\n ? message.content\n : message.content.slice(0, Math.min(MAX_PROJECTED_ITEMS, blockCount)).map((block) => {\n switch (block.type) {\n case \"text\":\n return { type: \"text\", text: block.text };\n case \"thinking\":\n return { type: \"thinking\", thinking: block.thinking };\n case \"toolCall\":\n return { type: \"toolCall\", id: block.id, name: block.name, arguments: block.arguments };\n case \"image\":\n return { type: \"image\", mimeType: block.mimeType, data: \"[base64 omitted]\" };\n default:\n return { type: \"unsupported\", content: \"[block omitted]\" };\n }\n }),\n ...(Array.isArray(message.content) && blockCount > MAX_PROJECTED_ITEMS\n ? { blocksOmitted: blockCount - MAX_PROJECTED_ITEMS }\n : {}),\n ...(omitDiagnosticBlock ? { diagnosticsOmitted: diagnosticCount } : {}),\n };\n });\n if (messages && messages.length > MAX_PROJECTED_ITEMS)\n projected?.push({ messagesOmitted: messages.length - MAX_PROJECTED_ITEMS });\n return sanitizeTraceValue(projected, true);\n}\n\nexport function durableUsage(message: Message): ObservationAttributes {\n if ((message.role !== \"assistant\" && message.role !== \"toolResult\") || !message.usage) return {};\n const usageDetails: Record<string, number> = {};\n const costDetails: Record<string, number> = {};\n for (const [key, field] of [\n [\"input\", \"input\"],\n [\"output\", \"output\"],\n [\"cacheRead\", \"cache_read_input_tokens\"],\n [\"cacheWrite\", \"cache_creation_input_tokens\"],\n [\"totalTokens\", \"total\"],\n ] as const) {\n const value = message.usage[key];\n if (typeof value === \"number\" && Number.isFinite(value) && value >= 0) usageDetails[field] = value;\n }\n for (const [key, field] of [\n [\"input\", \"input\"],\n [\"output\", \"output\"],\n [\"cacheRead\", \"cache_read\"],\n [\"cacheWrite\", \"cache_write\"],\n [\"total\", \"total\"],\n ] as const) {\n const value = message.usage.cost[key];\n if (typeof value === \"number\" && Number.isFinite(value) && value >= 0) costDetails[field] = value;\n }\n return { usageDetails, costDetails };\n}\n", "import type {\n AgentEvent,\n EntryId,\n EntryRecord,\n SnapshotEvent,\n SubmissionId,\n SubmissionRecord,\n} from \"@earendil-works/pi-durable\";\nimport { durableAttributes, durableContent, durableDiagnosticCount, durableUsage } from \"./durable-projection.js\";\nimport { sanitizeTraceValue } from \"./sanitizer.js\";\nimport type { Observation, ObservationAttributes, ObservationType, TraceBackend } from \"./tracing.js\";\n\nexport interface DurableCorrelation {\n requestId?: string;\n userId?: string;\n applicationId?: string;\n}\n\nexport interface DurableRecorderOptions extends DurableCorrelation {\n conversationId: number;\n sessionId?: string;\n traceName?: string;\n captureContent?: boolean;\n metadata?: Readonly<Record<string, unknown>>;\n submissionCorrelation?: (record: SubmissionRecord) => DurableCorrelation;\n onTraceId?: (traceId: string) => void | Promise<void>;\n onError?: (stage: string) => void | Promise<void>;\n}\n\nconst SAFE_UNANSWERED_REASONS = new Set([\n \"aborted\",\n \"model_error\",\n \"no_model\",\n \"reset\",\n \"stale\",\n \"faulted\",\n \"missing_task\",\n \"task_too_old\",\n \"migration_failed\",\n]);\n\ninterface Span {\n observation?: Observation;\n ended: boolean;\n}\ninterface SubmissionSpan {\n span: Span;\n correlation: DurableCorrelation;\n terminal: boolean;\n}\ninterface RunSpan {\n span: Span;\n primary: SubmissionId;\n inputs: readonly SubmissionId[];\n correlation: DurableCorrelation;\n round: number;\n generation?: Span;\n tools: Map<string, Span>;\n completedTools: Set<string>;\n}\n\n/** Durable state machine using the same trace backend, ownership and capture core as the Pi adapter. */\nexport class DurableRecorder {\n private readonly root: Span;\n private run?: RunSpan;\n private readonly submissions = new Map<SubmissionId, SubmissionSpan>();\n private readonly pendingSubmissions = new Set<SubmissionId>();\n private readonly entries = new Set<number>();\n private readonly completedRuns = new Set<SubmissionId>();\n private disposed = false;\n private gaps = 0;\n\n constructor(\n private readonly backend: TraceBackend,\n private readonly options: DurableRecorderOptions,\n ) {\n this.root = this.start(options.traceName ?? \"pi.durable.conversation\", \"agent\", undefined, {\n metadata: {\n ...this.customMetadata(),\n \"pi.runtime\": \"durable\",\n \"pi.durable.schema_version\": \"1\",\n \"pi.durable.conversation_id\": options.conversationId,\n ...(options.applicationId ? { \"pi.application.id\": options.applicationId } : {}),\n },\n });\n this.safe(() => {\n this.root.observation?.updateTrace?.(\n durableAttributes({\n name: options.traceName ?? \"pi.durable.conversation\",\n sessionId: options.sessionId ?? String(options.conversationId),\n ...(options.userId ? { userId: options.userId } : {}),\n metadata: this.customMetadata(),\n version: \"durable-1\",\n }),\n );\n });\n const traceId = this.root.observation?.traceId;\n if (traceId) this.callback(\"trace-id callback\", () => options.onTraceId?.(traceId));\n }\n\n get submissionIds(): readonly SubmissionId[] {\n return [...this.pendingSubmissions];\n }\n\n report(stage: string): void {\n this.callback(\"error callback\", () => this.options.onError?.(stage), false);\n }\n\n private callback(stage: string, action: () => void | Promise<void>, report = true): void {\n try {\n Promise.resolve(action()).catch(() => {\n if (report) this.report(stage);\n });\n } catch {\n if (report) this.report(stage);\n }\n }\n\n private customMetadata(): Record<string, unknown> {\n const value = sanitizeTraceValue(this.options.metadata, true);\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return {};\n return Object.fromEntries(Object.entries(value).filter(([key]) => !key.startsWith(\"pi.\")));\n }\n\n private safe(action: () => void): void {\n try {\n action();\n } catch {\n this.report(\"exporter observation\");\n }\n }\n\n private start(name: string, type: ObservationType, parent?: Span, attributes: ObservationAttributes = {}): Span {\n if (this.disposed || (parent && (parent.ended || !parent.observation))) return { ended: true };\n const span: Span = { ended: false };\n const inherited = parent === this.run?.span ? this.correlationAttributes(this.run?.correlation ?? {}) : {};\n this.safe(() => {\n span.observation = this.backend.start(\n String(sanitizeTraceValue(name, true)),\n durableAttributes({\n version: \"durable-1\",\n sessionId: this.options.sessionId ?? String(this.options.conversationId),\n ...(this.options.userId ? { userId: this.options.userId } : {}),\n ...inherited,\n ...attributes,\n metadata: { ...inherited.metadata, ...attributes.metadata },\n }),\n { asType: type, parent: parent?.observation },\n );\n });\n // A missing parent handle must never become a new exporter root for descendants.\n if (!span.observation) span.ended = true;\n if (this.disposed) this.interrupted(span, \"Observer disposed during exporter start.\");\n return span;\n }\n\n private update(span: Span, attributes: ObservationAttributes): void {\n if (!span.ended)\n this.safe(() => {\n span.observation?.update(durableAttributes(attributes));\n });\n }\n\n private end(span: Span | undefined, attributes: ObservationAttributes = {}): void {\n if (!span || span.ended) return;\n span.ended = true;\n const observation = span.observation;\n // Release ended SDK handles/payloads, and claim the end before any callback can reenter disposal.\n span.observation = undefined;\n this.safe(() => {\n observation?.update(durableAttributes(attributes));\n });\n this.safe(() => {\n observation?.end();\n });\n }\n\n private interrupted(span: Span | undefined, reason: string): void {\n this.end(span, {\n level: \"WARNING\",\n statusMessage: reason,\n metadata: { \"pi.durable.outcome\": \"observation_interrupted\" },\n });\n }\n\n private correlationAttributes(correlation: DurableCorrelation): ObservationAttributes {\n return {\n metadata: {\n ...(correlation.userId ? { \"pi.user.id\": correlation.userId } : {}),\n ...(correlation.requestId ? { \"pi.request.id\": correlation.requestId } : {}),\n ...(correlation.applicationId ? { \"pi.application.id\": correlation.applicationId } : {}),\n },\n };\n }\n\n async submission(\n record: SubmissionRecord,\n readEntry: (id: EntryId) => Promise<EntryRecord | undefined>,\n correlation?: DurableCorrelation,\n snapshot = false,\n ): Promise<void> {\n if (this.disposed || record.conversationId !== this.options.conversationId) return;\n let state = this.submissions.get(record.id);\n if (!state) {\n let host: DurableCorrelation = {};\n try {\n host = this.options.submissionCorrelation?.(record) ?? {};\n } catch {\n this.report(\"submission correlation\");\n }\n if (this.disposed) return;\n const ids = sanitizeTraceValue(\n {\n requestId: record.requestId,\n userId: this.options.userId,\n applicationId: this.options.applicationId,\n ...host,\n ...correlation,\n },\n true,\n ) as DurableCorrelation;\n state = {\n span: this.start(\"pi.durable.submission\", \"span\", this.root, {\n ...this.correlationAttributes(ids),\n metadata: {\n ...this.correlationAttributes(ids).metadata,\n \"pi.durable.submission_id\": record.id,\n \"pi.durable.submission_type\": record.type,\n \"pi.durable.snapshot_observation\": snapshot,\n },\n }),\n correlation: ids,\n terminal: false,\n };\n if (this.disposed) return;\n this.submissions.set(record.id, state);\n this.pendingSubmissions.add(record.id);\n }\n if (state.terminal) return;\n // Status reads can be newer than the run snapshot. Only ordered placement events extend membership.\n if (!snapshot && record.status === \"placed\" && this.run && !this.run.inputs.includes(record.id)) {\n this.run.inputs = [...this.run.inputs, record.id];\n this.update(this.run.span, { metadata: { \"pi.durable.inputs\": this.run.inputs } });\n }\n if (correlation) {\n state.correlation = sanitizeTraceValue({ ...state.correlation, ...correlation }, true) as DurableCorrelation;\n this.update(state.span, this.correlationAttributes(state.correlation));\n }\n const run = this.run;\n if (run?.primary === record.id) {\n run.correlation = state.correlation;\n const attributes = this.correlationAttributes(state.correlation);\n this.update(run.span, attributes);\n if (run.generation) this.update(run.generation, attributes);\n for (const tool of run.tools.values()) this.update(tool, attributes);\n }\n this.update(state.span, { metadata: { \"pi.durable.submission_status\": record.status } });\n if (record.entry !== undefined && record.type === \"input\") {\n const entry = await readEntry(record.entry);\n if (this.disposed || state.terminal) return;\n this.update(state.span, { input: durableContent(entry?.model, this.options.captureContent ?? true) });\n }\n if (record.status === \"done\" || record.status === \"unanswered\") {\n let output: unknown;\n if (record.status === \"done\" && record.type === \"input\") {\n const entry = await readEntry(record.answer);\n if (this.disposed || state.terminal) return;\n // The answer ID is authoritative; no streamed fragments or last intermediate assistant inference.\n output = durableContent(entry?.model, this.options.captureContent ?? true);\n }\n const reason =\n record.status === \"unanswered\" && SAFE_UNANSWERED_REASONS.has(record.reason) ? record.reason : undefined;\n state.terminal = true;\n this.pendingSubmissions.delete(record.id);\n this.end(state.span, {\n ...(output !== undefined ? { output } : {}),\n level:\n record.status === \"done\"\n ? \"DEFAULT\"\n : reason === \"model_error\" || reason === \"no_model\" || reason === \"faulted\"\n ? \"ERROR\"\n : \"WARNING\",\n // Only known structural reason codes are safe; arbitrary reason/detail can contain provider secrets.\n metadata: {\n \"pi.durable.submission_status\": record.status,\n \"pi.durable.answer_entry_id\": record.answer,\n ...(record.status === \"unanswered\" ? { \"pi.durable.unanswered_reason\": reason ?? \"unknown\" } : {}),\n },\n });\n }\n }\n\n snapshot(snapshot: SnapshotEvent, initial = false): void {\n if (this.disposed) return;\n for (const entry of snapshot.entries) this.entries.add(entry.id);\n if (!initial) {\n this.gaps += 1;\n this.update(this.root, {\n metadata: { \"pi.durable.resync_count\": this.gaps, \"pi.durable.history_complete\": false },\n });\n } else\n this.update(this.root, {\n metadata: { \"pi.durable.history_complete\": false, \"pi.durable.attachment\": \"snapshot\" },\n });\n const primary = snapshot.run?.inputs[0];\n if (this.run && this.run.primary !== primary)\n this.closeRun(\"Run boundary not observed; snapshot replaced the observation.\");\n if (this.run) this.closeRound(\"Snapshot replaced generation/tool observation; activity may have been missed.\");\n if (snapshot.run) {\n this.beginRun(snapshot.run.inputs, true);\n // Do not turn snapshot transcript/partials/tools into historical starts or completions.\n if (this.run) this.update(this.run.span, { metadata: { \"pi.durable.observation_incomplete\": true } });\n }\n }\n\n private beginRun(inputs: readonly SubmissionId[], snapshot = false): void {\n const primary = inputs[0];\n if (primary === undefined || this.completedRuns.has(primary)) return;\n if (this.run?.primary === primary) {\n this.run.inputs = [...inputs];\n this.update(this.run.span, { metadata: { \"pi.durable.inputs\": this.run.inputs } });\n return;\n }\n if (this.run) this.closeRun(\"Unexpected run replacement.\");\n const correlation = this.submissions.get(primary)?.correlation ?? {};\n this.run = {\n span: this.start(\"pi.durable.run\", \"agent\", this.root, {\n ...this.correlationAttributes(correlation),\n metadata: {\n ...this.correlationAttributes(correlation).metadata,\n \"pi.durable.primary_submission_id\": primary,\n \"pi.durable.inputs\": [...inputs],\n \"pi.durable.snapshot_observation\": snapshot,\n },\n }),\n primary,\n inputs: [...inputs],\n correlation,\n round: 0,\n tools: new Map(),\n completedTools: new Set(),\n };\n }\n\n private closeRound(reason: string): void {\n const run = this.run;\n if (!run) return;\n this.interrupted(run.generation, reason);\n run.generation = undefined;\n for (const tool of run.tools.values()) this.interrupted(tool, reason);\n run.tools.clear();\n }\n\n private closeRun(reason?: string): void {\n const run = this.run;\n if (!run) return;\n this.closeRound(reason ?? \"Run ended without an observed descendant completion.\");\n this.end(\n run.span,\n reason\n ? { level: \"WARNING\", statusMessage: reason, metadata: { \"pi.durable.observation_incomplete\": true } }\n : {},\n );\n this.completedRuns.add(run.primary);\n this.run = undefined;\n }\n\n private generation(): Span | undefined {\n const run = this.run;\n if (!run) return undefined;\n if (!run.generation) {\n run.round += 1;\n run.generation = this.start(\"pi.durable.llm\", \"generation\", run.span, {\n metadata: { \"pi.durable.observed_generation_index\": run.round },\n });\n }\n return run.generation;\n }\n\n event(event: AgentEvent): void {\n if (this.disposed) return;\n switch (event.type) {\n case \"run_start\":\n this.beginRun(event.inputs);\n break;\n case \"run_end\":\n if (this.run?.primary === event.inputs[0]) this.closeRun();\n break;\n case \"message_start\":\n if (event.message.role === \"assistant\") this.generation();\n break;\n case \"message_end\": {\n const entry = event.entry;\n if (this.entries.has(entry.id)) break;\n this.entries.add(entry.id);\n const message = entry.model?.[0];\n if (message?.role !== \"assistant\") break;\n const generation = this.generation();\n if (!generation) break;\n this.end(generation, {\n output: durableContent(entry.model, this.options.captureContent ?? true),\n model: message.model,\n ...durableUsage(message),\n level: message.stopReason === \"error\" ? \"ERROR\" : message.stopReason === \"aborted\" ? \"WARNING\" : \"DEFAULT\",\n metadata: {\n \"pi.durable.entry_id\": entry.id,\n \"pi.durable.task_id\": entry.byTaskId,\n \"pi.durable.stop_reason\": message.stopReason,\n \"pi.durable.provider\": message.provider,\n },\n });\n if (this.run) this.run.generation = undefined;\n break;\n }\n case \"tool_execution_start\": {\n if (\n !this.run ||\n this.run.tools.has(event.toolCallId) ||\n this.run.completedTools.has(`${this.run.round}:${event.toolCallId}`)\n )\n break;\n this.run.tools.set(\n event.toolCallId,\n this.start(\"pi.durable.tool\", \"tool\", this.run.span, {\n input:\n this.options.captureContent === false\n ? \"[content capture disabled]\"\n : sanitizeTraceValue(event.args, true),\n metadata: {\n \"pi.durable.tool_call_id\": event.toolCallId,\n \"pi.durable.tool_name\": sanitizeTraceValue(event.toolName),\n \"pi.durable.observed_generation_index\": this.run.round,\n },\n }),\n );\n break;\n }\n case \"tool_execution_end\": {\n const run = this.run;\n if (!run) break;\n const key = `${run.round}:${event.toolCallId}`;\n if (run.completedTools.has(key) || (event.entry && this.entries.has(event.entry.id))) break;\n run.completedTools.add(key);\n if (event.entry) this.entries.add(event.entry.id);\n const tool =\n run.tools.get(event.toolCallId) ??\n this.start(\"pi.durable.tool\", \"tool\", run.span, {\n metadata: { \"pi.durable.start_not_observed\": true, \"pi.durable.tool_call_id\": event.toolCallId },\n });\n const message = event.entry?.model?.[0];\n this.end(tool, {\n output: durableContent(\n event.entry?.model,\n this.options.captureContent ?? true,\n durableDiagnosticCount(event.entry),\n ),\n ...(message ? durableUsage(message) : {}),\n level: message?.role === \"toolResult\" && message.isError ? \"ERROR\" : event.entry ? \"DEFAULT\" : \"WARNING\",\n metadata: { \"pi.durable.result_entry_id\": event.entry?.id, \"pi.durable.result_missing\": !event.entry },\n });\n run.tools.delete(event.toolCallId);\n break;\n }\n case \"auto_retry_start\":\n this.end(this.run?.generation, {\n level: \"ERROR\",\n statusMessage: \"Provider attempt will retry.\",\n metadata: { \"pi.durable.retry_attempt\": event.attempt },\n });\n if (this.run) this.run.generation = undefined;\n break;\n case \"task_failed\":\n if (this.run)\n this.update(this.run.span, { level: \"ERROR\", metadata: { \"pi.durable.failed_task_id\": event.taskId } });\n break;\n // turn_end is a round boundary, never a submission boundary. Deltas and arbitrary diagnostics are not captured.\n default:\n break;\n }\n }\n\n dispose(reason = \"Tracing observer disposed; execution outcome is unknown.\"): void {\n if (this.disposed) return;\n this.disposed = true;\n this.closeRun(reason);\n for (const state of this.submissions.values()) if (!state.terminal) this.interrupted(state.span, reason);\n this.end(this.root, { metadata: { \"pi.durable.resync_count\": this.gaps } });\n this.submissions.clear();\n this.pendingSubmissions.clear();\n this.entries.clear();\n this.completedRuns.clear();\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["ids"]
7
+ }
package/dist/index.js CHANGED
@@ -1,13 +1,243 @@
1
1
  // @generated by scripts/build-runtime.mjs; do not edit.
2
2
  // @ts-nocheck -- the generated entry uses a .ts extension for Pi's Jiti loader.
3
3
  import {
4
- DEFAULT_BASE_URL,
4
+ createLangfuseRuntime,
5
+ sanitizeTraceValue
6
+ } from "./library-chunks/chunk-JPFN6GGS.js";
7
+ import {
8
+ DEFAULT_BASE_URL
9
+ } from "./library-chunks/chunk-ETFOPFEP.js";
10
+ import {
5
11
  createLangfuseRuntimeFromBackend,
6
- getLangfuseRuntimeInternal,
7
- loadLangfuseConfig,
8
- normalizeLangfuseConfig,
9
- writeLangfuseConfig
10
- } from "./library-chunks/chunk-SAJ76IYU.js";
12
+ getLangfuseRuntimeInternal
13
+ } from "./library-chunks/chunk-B64FXCNE.js";
14
+
15
+ // src/config.ts
16
+ import { randomUUID } from "node:crypto";
17
+ import { chmod, mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
18
+ import { dirname, join } from "node:path";
19
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
20
+ var CONFIG_FILE_NAME = "pi-langfuse.json";
21
+ function langfuseConfigPath() {
22
+ return join(getAgentDir(), CONFIG_FILE_NAME);
23
+ }
24
+ var configSaveQueues = /* @__PURE__ */ new Map();
25
+ function writeLangfuseConfig(patch, path = langfuseConfigPath()) {
26
+ const previous = configSaveQueues.get(path) ?? Promise.resolve();
27
+ const operation = previous.then(() => writeLangfuseConfigNow(patch, path));
28
+ const tail = operation.then(
29
+ () => void 0,
30
+ () => void 0
31
+ );
32
+ configSaveQueues.set(path, tail);
33
+ void tail.then(() => {
34
+ if (configSaveQueues.get(path) === tail) configSaveQueues.delete(path);
35
+ });
36
+ return operation;
37
+ }
38
+ async function writeLangfuseConfigNow(patch, path) {
39
+ const currentDocument = await readLangfuseDocumentForUpdate(path);
40
+ const normalized = normalizeLangfuseConfig({ ...currentDocument, ...patch });
41
+ if (!normalized.ok) throw new Error(normalized.reason);
42
+ const {
43
+ publicKey: _publicKey,
44
+ secretKey: _secretKey,
45
+ baseUrl: _baseUrl,
46
+ environment: _environment,
47
+ release: _release,
48
+ userId: _userId,
49
+ captureContent: _captureContent,
50
+ ...unknownFields
51
+ } = currentDocument;
52
+ const nextDocument = { ...unknownFields, ...normalized.config };
53
+ await mkdir(dirname(path), { recursive: true, mode: 448 });
54
+ const tempPath = `${path}.${process.pid}.${randomUUID()}.tmp`;
55
+ try {
56
+ await writeFile(tempPath, `${JSON.stringify(nextDocument, null, 2)}
57
+ `, {
58
+ encoding: "utf8",
59
+ mode: 384
60
+ });
61
+ await chmod(tempPath, 384);
62
+ await rename(tempPath, path);
63
+ return normalized.config;
64
+ } catch (error) {
65
+ await rm(tempPath, { force: true }).catch(() => void 0);
66
+ throw error;
67
+ }
68
+ }
69
+ async function readLangfuseDocumentForUpdate(path) {
70
+ let parsed;
71
+ try {
72
+ parsed = JSON.parse(await readFile(path, "utf8"));
73
+ } catch (error) {
74
+ if (isNodeError(error) && error.code === "ENOENT") return {};
75
+ throw new Error(
76
+ `Cannot update ${CONFIG_FILE_NAME} because the existing file is malformed or unreadable; repair it first.`
77
+ );
78
+ }
79
+ const normalized = normalizeLangfuseConfig(parsed);
80
+ if (!normalized.ok) {
81
+ throw new Error(
82
+ `Cannot update ${CONFIG_FILE_NAME} because the existing file has invalid recognized fields; repair it first.`
83
+ );
84
+ }
85
+ return { ...parsed };
86
+ }
87
+ async function loadLangfuseConfig(path = langfuseConfigPath()) {
88
+ await configSaveQueues.get(path);
89
+ const warnings = [];
90
+ const permissionFailure = await ensurePrivatePermissions(path, warnings);
91
+ if (permissionFailure) {
92
+ return {
93
+ ok: false,
94
+ path,
95
+ warnings,
96
+ reason: `Refusing to load credentials: ${permissionFailure}`
97
+ };
98
+ }
99
+ let text;
100
+ try {
101
+ text = await readFile(path, "utf8");
102
+ } catch (error) {
103
+ if (isNodeError(error) && error.code === "ENOENT") {
104
+ return { ok: false, path, warnings, reason: `Configuration file not found: ${path}` };
105
+ }
106
+ return {
107
+ ok: false,
108
+ path,
109
+ warnings,
110
+ reason: `Failed to read ${path}: ${formatError(error)}`
111
+ };
112
+ }
113
+ let parsed;
114
+ try {
115
+ parsed = JSON.parse(text);
116
+ } catch {
117
+ return {
118
+ ok: false,
119
+ path,
120
+ warnings,
121
+ reason: `Failed to parse ${path} as JSON; repair the file before retrying.`
122
+ };
123
+ }
124
+ const normalized = normalizeLangfuseConfig(parsed);
125
+ if (!normalized.ok) return { ok: false, path, warnings, reason: normalized.reason };
126
+ return { ok: true, config: normalized.config, path, warnings };
127
+ }
128
+ function normalizeLangfuseConfig(value) {
129
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
130
+ return { ok: false, reason: "pi-langfuse.json must contain a JSON object." };
131
+ }
132
+ const input = value;
133
+ const publicKey = normalizeString(input.publicKey);
134
+ const secretKey = normalizeString(input.secretKey);
135
+ if (!publicKey) {
136
+ return { ok: false, reason: "pi-langfuse.json requires a literal publicKey string." };
137
+ }
138
+ if (!secretKey) {
139
+ return { ok: false, reason: "pi-langfuse.json requires a literal secretKey string." };
140
+ }
141
+ if (isInterpolation(publicKey)) {
142
+ return {
143
+ ok: false,
144
+ reason: "pi-langfuse.json publicKey must be literal; environment and command interpolation are not supported."
145
+ };
146
+ }
147
+ if (isInterpolation(secretKey)) {
148
+ return {
149
+ ok: false,
150
+ reason: "pi-langfuse.json secretKey must be literal; environment and command interpolation are not supported."
151
+ };
152
+ }
153
+ const rawBaseUrl = input.baseUrl === void 0 ? DEFAULT_BASE_URL : normalizeString(input.baseUrl);
154
+ if (!rawBaseUrl) {
155
+ return { ok: false, reason: "pi-langfuse.json baseUrl must be a non-empty string." };
156
+ }
157
+ const baseUrl = normalizeBaseUrl(rawBaseUrl);
158
+ if (!baseUrl) {
159
+ return {
160
+ ok: false,
161
+ reason: "pi-langfuse.json baseUrl must use HTTP or HTTPS without credentials, a query, or a fragment."
162
+ };
163
+ }
164
+ if (input.captureContent !== void 0 && typeof input.captureContent !== "boolean") {
165
+ return { ok: false, reason: "pi-langfuse.json captureContent must be a boolean." };
166
+ }
167
+ const environment = optionalString(input.environment, "environment");
168
+ if (!environment.ok) return environment;
169
+ if (environment.value && (environment.value.length > 40 || !/^(?!langfuse)[a-z0-9_-]+$/u.test(environment.value))) {
170
+ return {
171
+ ok: false,
172
+ reason: "pi-langfuse.json environment must be at most 40 lowercase letters, numbers, hyphens, or underscores and must not start with langfuse."
173
+ };
174
+ }
175
+ const release = optionalString(input.release, "release");
176
+ if (!release.ok) return release;
177
+ const userId = optionalString(input.userId, "userId");
178
+ if (!userId.ok) return userId;
179
+ if (userId.value && userId.value.length > 200) {
180
+ return { ok: false, reason: "pi-langfuse.json userId must be at most 200 characters." };
181
+ }
182
+ return {
183
+ ok: true,
184
+ config: {
185
+ publicKey,
186
+ secretKey,
187
+ baseUrl,
188
+ ...environment.value ? { environment: environment.value } : {},
189
+ ...release.value ? { release: release.value } : {},
190
+ ...userId.value ? { userId: userId.value } : {},
191
+ captureContent: input.captureContent ?? true
192
+ }
193
+ };
194
+ }
195
+ async function ensurePrivatePermissions(path, warnings) {
196
+ try {
197
+ const file = await stat(path);
198
+ if ((file.mode & 511) !== 384) {
199
+ await chmod(path, 384);
200
+ const repaired = await stat(path);
201
+ if ((repaired.mode & 511) !== 384) {
202
+ throw new Error(`permissions remained ${(repaired.mode & 511).toString(8)}`);
203
+ }
204
+ warnings.push(`Restricted ${path} permissions to 0600.`);
205
+ }
206
+ } catch (error) {
207
+ if (isNodeError(error) && error.code === "ENOENT") return void 0;
208
+ const failure = `Failed to enforce 0600 permissions for ${path}: ${formatError(error)}`;
209
+ warnings.push(failure);
210
+ return failure;
211
+ }
212
+ return void 0;
213
+ }
214
+ function normalizeBaseUrl(value) {
215
+ try {
216
+ const url = new URL(value);
217
+ if (url.protocol !== "http:" && url.protocol !== "https:") return void 0;
218
+ if (url.username || url.password || url.search || url.hash) return void 0;
219
+ return url.toString().replace(/\/+$/, "");
220
+ } catch {
221
+ return void 0;
222
+ }
223
+ }
224
+ function optionalString(value, name) {
225
+ if (value === void 0) return { ok: true };
226
+ const normalized = normalizeString(value);
227
+ return normalized ? { ok: true, value: normalized } : { ok: false, reason: `pi-langfuse.json ${name} must be a non-empty string.` };
228
+ }
229
+ function normalizeString(value) {
230
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
231
+ }
232
+ function isInterpolation(value) {
233
+ return value.startsWith("$") || value.startsWith("!");
234
+ }
235
+ function isNodeError(error) {
236
+ return error instanceof Error && "code" in error;
237
+ }
238
+ function formatError(error) {
239
+ return error instanceof Error ? error.message : String(error);
240
+ }
11
241
 
12
242
  // src/git.ts
13
243
  var GIT_LOOKUP_TIMEOUT_MS = 1e3;
@@ -62,141 +292,6 @@ function normalizeGitBranch(value) {
62
292
  return branch;
63
293
  }
64
294
 
65
- // src/sanitizer.ts
66
- var MAX_CAPTURE_BYTES = 64 * 1024;
67
- var MAX_STRING_LENGTH = 5e4;
68
- var MAX_COLLECTION_LENGTH = 200;
69
- var MAX_DEPTH = 12;
70
- var TRUNCATED = "[truncated: content budget exceeded]";
71
- var BASE64_DATA_URI = /data:[^,\s"'`]*;base64,[^\s"'`]*/gi;
72
- var BASE64_DATA_URI_OMITTED = "[base64 data URI omitted]";
73
- var OPAQUE_SIGNATURE_KEYS = /* @__PURE__ */ new Set(["thinkingSignature", "textSignature", "thoughtSignature"]);
74
- function sanitizeTraceValue(value) {
75
- const budget = { remaining: MAX_CAPTURE_BYTES };
76
- const sanitized = sanitize(value, /* @__PURE__ */ new WeakSet(), 0, budget);
77
- return serializedBytes(sanitized) <= MAX_CAPTURE_BYTES ? sanitized : TRUNCATED;
78
- }
79
- function sanitize(value, active, depth, budget) {
80
- if (budget.remaining <= byteLength(TRUNCATED)) return TRUNCATED;
81
- if (value === null || typeof value === "number" || typeof value === "boolean") {
82
- return consume(value, budget);
83
- }
84
- if (typeof value === "string") {
85
- return consume(sanitizeString(value, Math.min(MAX_STRING_LENGTH, budget.remaining)), budget);
86
- }
87
- if (typeof value === "bigint") return consume(value.toString(), budget);
88
- if (typeof value === "undefined" || typeof value === "function" || typeof value === "symbol") {
89
- return void 0;
90
- }
91
- if (value instanceof Date) {
92
- return consume(Number.isNaN(value.getTime()) ? "[invalid date]" : value.toISOString(), budget);
93
- }
94
- if (value instanceof Error) {
95
- return sanitize({ name: value.name, message: value.message }, active, depth, budget);
96
- }
97
- if (depth >= MAX_DEPTH) return consume("[maximum depth reached]", budget);
98
- if (active.has(value)) return consume("[circular]", budget);
99
- active.add(value);
100
- let result;
101
- if (Array.isArray(value)) {
102
- const items = [];
103
- for (const item of value.slice(0, MAX_COLLECTION_LENGTH)) {
104
- if (budget.remaining <= byteLength(TRUNCATED)) break;
105
- items.push(sanitize(item, active, depth + 1, budget));
106
- }
107
- if (items.length < value.length) items.push(`[${value.length - items.length} items omitted]`);
108
- result = items;
109
- } else {
110
- const record = value;
111
- const output = {};
112
- const objectType = readProperty(record, "type").value;
113
- const redactData = objectType === "image" || objectType === "base64";
114
- let visited = 0;
115
- let processed = 0;
116
- let omitted = false;
117
- try {
118
- for (const key in record) {
119
- if (visited >= MAX_COLLECTION_LENGTH || budget.remaining <= byteLength(TRUNCATED)) {
120
- omitted = true;
121
- break;
122
- }
123
- visited += 1;
124
- if (!Object.hasOwn(record, key) || OPAQUE_SIGNATURE_KEYS.has(key)) continue;
125
- const keyBudget = Math.min(MAX_STRING_LENGTH, Math.max(0, budget.remaining - 4));
126
- if (key.length > keyBudget) {
127
- omitted = true;
128
- break;
129
- }
130
- const keyBytes = byteLength(key);
131
- if (keyBytes > keyBudget) {
132
- omitted = true;
133
- break;
134
- }
135
- const sanitizedKey = sanitizeString(key, keyBudget);
136
- budget.remaining -= byteLength(sanitizedKey) + 4;
137
- const property = readProperty(record, key);
138
- if (redactData && key === "data") {
139
- output[sanitizedKey] = consume("[base64 omitted]", budget);
140
- } else if (!property.ok) output[sanitizedKey] = consume("[unreadable property]", budget);
141
- else output[sanitizedKey] = sanitize(property.value, active, depth + 1, budget);
142
- processed += 1;
143
- }
144
- } catch {
145
- if (processed === 0) {
146
- active.delete(value);
147
- return consume("[unreadable object]", budget);
148
- }
149
- omitted = true;
150
- }
151
- if (omitted) output.$truncated = "additional object entries omitted";
152
- result = output;
153
- }
154
- active.delete(value);
155
- return result;
156
- }
157
- function readProperty(record, key) {
158
- try {
159
- return { ok: true, value: record[key] };
160
- } catch {
161
- return { ok: false };
162
- }
163
- }
164
- function consume(value, budget) {
165
- const size = serializedBytes(value);
166
- if (size > budget.remaining) {
167
- budget.remaining -= byteLength(TRUNCATED);
168
- return TRUNCATED;
169
- }
170
- budget.remaining -= size;
171
- return value;
172
- }
173
- function sanitizeString(value, maxBytes) {
174
- return truncateString(value, maxBytes).replace(BASE64_DATA_URI, BASE64_DATA_URI_OMITTED);
175
- }
176
- function truncateString(value, maxBytes) {
177
- const boundedPrefix = value.slice(0, maxBytes);
178
- if (boundedPrefix.length === value.length && byteLength(boundedPrefix) <= maxBytes) {
179
- return boundedPrefix;
180
- }
181
- const suffix = "\u2026 [truncated]";
182
- const target = Math.max(0, maxBytes - byteLength(suffix) - 2);
183
- let bytes = 0;
184
- let output = "";
185
- for (const character of boundedPrefix) {
186
- const size = byteLength(character);
187
- if (bytes + size > target) break;
188
- output += character;
189
- bytes += size;
190
- }
191
- return `${output}${suffix}`;
192
- }
193
- function serializedBytes(value) {
194
- return byteLength(JSON.stringify(value) ?? "null");
195
- }
196
- function byteLength(value) {
197
- return Buffer.byteLength(value, "utf8");
198
- }
199
-
200
295
  // src/tracing.ts
201
296
  var TRACE_SCHEMA_VERSION = "2";
202
297
  var MAX_HEADER_VALUE_LENGTH = 1024;
@@ -753,7 +848,7 @@ function allowlistedResponseHeaders(headers) {
753
848
  for (const [rawName, rawValue] of Object.entries(headers)) {
754
849
  const name = rawName.toLowerCase();
755
850
  if (!ALLOWED_RESPONSE_HEADERS.has(name) || typeof rawValue !== "string") continue;
756
- output[name] = truncateString2(rawValue, MAX_HEADER_VALUE_LENGTH);
851
+ output[name] = truncateString(rawValue, MAX_HEADER_VALUE_LENGTH);
757
852
  }
758
853
  return output;
759
854
  }
@@ -799,24 +894,24 @@ function severityForOutcome(outcome, statusMessage) {
799
894
  function prefixRecord(prefix, values) {
800
895
  return Object.fromEntries(Object.entries(values).map(([key, value]) => [`${prefix}.${key}`, value]));
801
896
  }
802
- function truncateString2(value, maxBytes) {
897
+ function truncateString(value, maxBytes) {
803
898
  const boundedPrefix = value.slice(0, maxBytes);
804
- if (boundedPrefix.length === value.length && byteLength2(boundedPrefix) <= maxBytes) {
899
+ if (boundedPrefix.length === value.length && byteLength(boundedPrefix) <= maxBytes) {
805
900
  return boundedPrefix;
806
901
  }
807
902
  const suffix = "\u2026 [truncated]";
808
- const target = Math.max(0, maxBytes - byteLength2(suffix) - 2);
903
+ const target = Math.max(0, maxBytes - byteLength(suffix) - 2);
809
904
  let bytes = 0;
810
905
  let output = "";
811
906
  for (const character of boundedPrefix) {
812
- const size = byteLength2(character);
907
+ const size = byteLength(character);
813
908
  if (bytes + size > target) break;
814
909
  output += character;
815
910
  bytes += size;
816
911
  }
817
912
  return `${output}${suffix}`;
818
913
  }
819
- function byteLength2(value) {
914
+ function byteLength(value) {
820
915
  return Buffer.byteLength(value, "utf8");
821
916
  }
822
917
  function numericRecord(values) {
@@ -1317,7 +1412,7 @@ function createLangfuseExtension(dependencies = {}) {
1317
1412
  loadingConfig = result.config;
1318
1413
  const createBackend = dependencies.createBackend;
1319
1414
  const ownsRuntime = createBackend !== void 0;
1320
- const runtime = createBackend ? createLangfuseRuntimeFromBackend(await createBackend(result.config)) : await (await import("./library-chunks/runtime-A6SQ6VEH.js")).createLangfuseRuntime({ config: result.config, env: false });
1415
+ const runtime = createBackend ? createLangfuseRuntimeFromBackend(await createBackend(result.config)) : await (await import("./library-chunks/runtime-6KYOZVOE.js")).createLangfuseRuntime({ config: result.config, env: false });
1321
1416
  if (isCurrent() || !runtimeConfigForShutdown) runtimeConfigForShutdown = result.config;
1322
1417
  if (isCurrent()) {
1323
1418
  activeConfig = result.config;
@@ -1338,14 +1433,14 @@ function createLangfuseExtension(dependencies = {}) {
1338
1433
  onInitializationError(error, ctx) {
1339
1434
  const config = loadingConfig;
1340
1435
  loadingConfig = void 0;
1341
- initializationError = `Langfuse tracing could not start: ${formatError(error, config)}`;
1436
+ initializationError = `Langfuse tracing could not start: ${formatError2(error, config)}`;
1342
1437
  ctx.ui.notify(initializationError, "warning");
1343
1438
  },
1344
1439
  beforeSessionDispose: async () => {
1345
1440
  await configWriteQueue;
1346
1441
  },
1347
1442
  onShutdownError(error, ctx) {
1348
- ctx.ui.notify(`Langfuse shutdown export failed: ${formatError(error, shutdownConfig)}`, "error");
1443
+ ctx.ui.notify(`Langfuse shutdown export failed: ${formatError2(error, shutdownConfig)}`, "error");
1349
1444
  },
1350
1445
  flushOnReplacement: true,
1351
1446
  shutdownRuntimeOnQuit: true
@@ -1392,7 +1487,7 @@ function createLangfuseExtension(dependencies = {}) {
1392
1487
  ctx.ui.notify("Langfuse traces flushed for this session.", "info");
1393
1488
  } catch (error) {
1394
1489
  if (menuGeneration !== sessionGeneration) return { kind: "close" };
1395
- ctx.ui.notify(`Langfuse flush failed: ${formatError(error, menuConfig)}`, "error");
1490
+ ctx.ui.notify(`Langfuse flush failed: ${formatError2(error, menuConfig)}`, "error");
1396
1491
  }
1397
1492
  return { kind: "close" };
1398
1493
  },
@@ -1422,7 +1517,7 @@ function createLangfuseExtension(dependencies = {}) {
1422
1517
  );
1423
1518
  } catch (error) {
1424
1519
  if (menuGeneration !== sessionGeneration) return { kind: "close" };
1425
- ctx.ui.notify(`Failed to save Langfuse config: ${formatError(error, next)}`, "error");
1520
+ ctx.ui.notify(`Failed to save Langfuse config: ${formatError2(error, next)}`, "error");
1426
1521
  }
1427
1522
  return { kind: "close" };
1428
1523
  },
@@ -1528,7 +1623,7 @@ function formatConfigError(result) {
1528
1623
  const setupHint = result.reason.startsWith("Configuration file not found:") ? " Run /langfuse and choose Set up Langfuse." : "";
1529
1624
  return `Langfuse tracing is disabled: ${result.reason}${setupHint}`;
1530
1625
  }
1531
- function formatError(error, config) {
1626
+ function formatError2(error, config) {
1532
1627
  let message = error instanceof Error ? error.message : String(error);
1533
1628
  const secrets = [config?.publicKey, config?.secretKey].filter((secret) => Boolean(secret)).sort((left, right) => right.length - left.length);
1534
1629
  for (const secret of secrets) {
@@ -1537,12 +1632,6 @@ function formatError(error, config) {
1537
1632
  return message;
1538
1633
  }
1539
1634
  var langfuse_default = createLangfuseExtension();
1540
-
1541
- // src/public-api.ts
1542
- async function createLangfuseRuntime(options = {}) {
1543
- const runtime = await import("./library-chunks/runtime-A6SQ6VEH.js");
1544
- return runtime.createLangfuseRuntime(options);
1545
- }
1546
1635
  export {
1547
1636
  createLangfuseRuntime,
1548
1637
  createPiLangfuseSession,