@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.
- package/README.md +46 -3
- package/dist/chunks/chunk-N7WSLB5Q.ts +88 -0
- package/dist/chunks/chunk-N7WSLB5Q.ts.map +7 -0
- package/dist/chunks/{runtime-6ZHLVC3A.ts → runtime-GLRAF7FE.ts} +2 -2
- package/dist/chunks/runtime-GLRAF7FE.ts.map +7 -0
- package/dist/config.d.ts +1 -1
- package/dist/durable-projection.d.ts +9 -0
- package/dist/durable-recorder.d.ts +49 -0
- package/dist/durable.d.ts +25 -0
- package/dist/durable.js +638 -0
- package/dist/durable.js.map +7 -0
- package/dist/index.js +248 -159
- package/dist/index.js.map +4 -4
- package/dist/index.ts +244 -16
- package/dist/index.ts.map +4 -4
- package/dist/library-chunks/chunk-B64FXCNE.js +84 -0
- package/dist/library-chunks/chunk-B64FXCNE.js.map +7 -0
- package/dist/library-chunks/chunk-ETFOPFEP.js +10 -0
- package/dist/library-chunks/chunk-ETFOPFEP.js.map +7 -0
- package/dist/library-chunks/chunk-JPFN6GGS.js +153 -0
- package/dist/library-chunks/chunk-JPFN6GGS.js.map +7 -0
- package/dist/library-chunks/{runtime-A6SQ6VEH.js → runtime-6KYOZVOE.js} +5 -3
- package/dist/library-chunks/runtime-6KYOZVOE.js.map +7 -0
- package/dist/public-api.d.ts +1 -3
- package/dist/runtime-api.d.ts +3 -0
- package/dist/runtime-defaults.d.ts +1 -0
- package/dist/runtime.d.ts +1 -1
- package/dist/sanitizer.d.ts +1 -1
- package/docs/durable.md +126 -0
- package/package.json +22 -2
- package/src/config.ts +4 -1
- package/src/durable-projection.ts +106 -0
- package/src/durable-recorder.ts +493 -0
- package/src/durable.ts +195 -0
- package/src/public-api.ts +6 -12
- package/src/runtime-api.ts +8 -0
- package/src/runtime-core.ts +2 -1
- package/src/runtime-defaults.ts +1 -0
- package/src/runtime.ts +2 -1
- package/src/sanitizer.ts +16 -4
- package/dist/chunks/chunk-DBQAWNKG.ts +0 -316
- package/dist/chunks/chunk-DBQAWNKG.ts.map +0 -7
- package/dist/chunks/runtime-6ZHLVC3A.ts.map +0 -7
- package/dist/library-chunks/chunk-SAJ76IYU.js +0 -316
- package/dist/library-chunks/chunk-SAJ76IYU.js.map +0 -7
- package/dist/library-chunks/runtime-A6SQ6VEH.js.map +0 -7
package/src/durable.ts
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import type { Context } from "@earendil-works/chord";
|
|
2
|
+
import { withCancel } from "@earendil-works/chord/context";
|
|
3
|
+
import {
|
|
4
|
+
type AgentEventStream,
|
|
5
|
+
type ConversationId,
|
|
6
|
+
type EntryId,
|
|
7
|
+
type Harness,
|
|
8
|
+
type SubmissionId,
|
|
9
|
+
watchEvents,
|
|
10
|
+
} from "@earendil-works/pi-durable";
|
|
11
|
+
import { type DurableCorrelation, DurableRecorder, type DurableRecorderOptions } from "./durable-recorder.js";
|
|
12
|
+
import { getLangfuseRuntimeInternal, type LangfuseRuntime } from "./runtime-core.js";
|
|
13
|
+
|
|
14
|
+
export type { DurableCorrelation } from "./durable-recorder.js";
|
|
15
|
+
export {
|
|
16
|
+
type CreateLangfuseRuntimeOptions,
|
|
17
|
+
createLangfuseRuntime,
|
|
18
|
+
type LangfuseRuntime,
|
|
19
|
+
type LangfuseRuntimeConfig,
|
|
20
|
+
} from "./runtime-api.js";
|
|
21
|
+
export { createLangfuseRuntimeFromBackend } from "./runtime-core.js";
|
|
22
|
+
export type { Observation, ObservationAttributes, ObservationType, TraceBackend } from "./tracing.js";
|
|
23
|
+
|
|
24
|
+
export interface PiLangfuseDurableOptions extends Omit<DurableRecorderOptions, "conversationId" | "requestId"> {
|
|
25
|
+
harness: Harness;
|
|
26
|
+
conversationId: ConversationId;
|
|
27
|
+
/** Governs observation only. Cancellation never aborts execution or resumes recovery. */
|
|
28
|
+
context: Context;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface PiLangfuseDurableConversation {
|
|
32
|
+
readonly active: boolean;
|
|
33
|
+
/** Resolves when observation has detached, including cancellation and Harness closure. */
|
|
34
|
+
readonly closed: Promise<void>;
|
|
35
|
+
/** Read committed status without waiting for or starting agent work. Also supports late submission attachment. */
|
|
36
|
+
observeSubmission(id: SubmissionId, correlation?: DurableCorrelation): Promise<void>;
|
|
37
|
+
/** Detach and close observations, without flushing/shutting down the runtime or modifying durable work. */
|
|
38
|
+
dispose(): Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const observedConversations = new WeakMap<Harness, Set<ConversationId>>();
|
|
42
|
+
|
|
43
|
+
/** Attach a fail-open observer using only public durable APIs. A failed attachment returns an inactive controller. */
|
|
44
|
+
export async function createPiLangfuseDurableConversation(
|
|
45
|
+
runtime: LangfuseRuntime,
|
|
46
|
+
options: PiLangfuseDurableOptions,
|
|
47
|
+
): Promise<PiLangfuseDurableConversation> {
|
|
48
|
+
options = { ...options };
|
|
49
|
+
const { context, cancel } = withCancel(options.context);
|
|
50
|
+
let stream: AgentEventStream | undefined;
|
|
51
|
+
let recorder: DurableRecorder | undefined;
|
|
52
|
+
let releaseRuntime: (() => void) | undefined;
|
|
53
|
+
let disposed = false;
|
|
54
|
+
let ownsConversation = false;
|
|
55
|
+
let initialization: Promise<void> = Promise.resolve();
|
|
56
|
+
let disposePromise: Promise<void> | undefined;
|
|
57
|
+
let resolveClosed!: () => void;
|
|
58
|
+
const closed = new Promise<void>((resolve) => {
|
|
59
|
+
resolveClosed = resolve;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
function report(stage: string): void {
|
|
63
|
+
if (recorder) recorder.report(stage);
|
|
64
|
+
else {
|
|
65
|
+
try {
|
|
66
|
+
Promise.resolve(options.onError?.(stage)).catch(() => undefined);
|
|
67
|
+
} catch {
|
|
68
|
+
/* Fail open. */
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function readEntry(id: EntryId) {
|
|
74
|
+
const entry = await options.harness.commit((tx) => tx.entry(id), context);
|
|
75
|
+
return disposed ? undefined : entry;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function observeSubmission(id: SubmissionId, correlation?: DurableCorrelation): Promise<void> {
|
|
79
|
+
if (disposed) return;
|
|
80
|
+
try {
|
|
81
|
+
const submission = await options.harness.submission(id, context);
|
|
82
|
+
if (disposed || !submission) return;
|
|
83
|
+
const record = await submission.status(context); // wait() would resume paused recovery.
|
|
84
|
+
if (disposed) return;
|
|
85
|
+
await recorder?.submission(record, readEntry, correlation, true);
|
|
86
|
+
} catch {
|
|
87
|
+
if (!disposed) report("submission status read");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function dispose(): Promise<void> {
|
|
92
|
+
if (disposePromise) return disposePromise;
|
|
93
|
+
disposed = true;
|
|
94
|
+
cancel();
|
|
95
|
+
if (ownsConversation) {
|
|
96
|
+
observedConversations.get(options.harness)?.delete(options.conversationId);
|
|
97
|
+
ownsConversation = false;
|
|
98
|
+
}
|
|
99
|
+
releaseRuntime?.();
|
|
100
|
+
releaseRuntime = undefined;
|
|
101
|
+
recorder?.dispose();
|
|
102
|
+
disposePromise = (async () => {
|
|
103
|
+
await initialization;
|
|
104
|
+
try {
|
|
105
|
+
await stream?.stop();
|
|
106
|
+
} catch {
|
|
107
|
+
report("observer stop");
|
|
108
|
+
}
|
|
109
|
+
resolveClosed();
|
|
110
|
+
})();
|
|
111
|
+
return disposePromise;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const controller: PiLangfuseDurableConversation = {
|
|
115
|
+
get active() {
|
|
116
|
+
return !disposed && !runtime.closed && stream !== undefined;
|
|
117
|
+
},
|
|
118
|
+
closed,
|
|
119
|
+
observeSubmission,
|
|
120
|
+
dispose,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
async function initialize(): Promise<void> {
|
|
124
|
+
try {
|
|
125
|
+
const conversations = observedConversations.get(options.harness) ?? new Set<ConversationId>();
|
|
126
|
+
if (conversations.has(options.conversationId)) throw new Error("Conversation already has a tracing observer.");
|
|
127
|
+
conversations.add(options.conversationId);
|
|
128
|
+
observedConversations.set(options.harness, conversations);
|
|
129
|
+
ownsConversation = true;
|
|
130
|
+
const internal = getLangfuseRuntimeInternal(runtime);
|
|
131
|
+
releaseRuntime = internal.registerSession(dispose);
|
|
132
|
+
const attached = await watchEvents(options.harness, options.conversationId, context);
|
|
133
|
+
// Retain the owned stream before callback-capable trace acquisition so partial disposal joins it.
|
|
134
|
+
stream = attached;
|
|
135
|
+
if (disposed || runtime.closed) {
|
|
136
|
+
void dispose();
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
// Failed attachment must not open a trace or publish its ID.
|
|
140
|
+
recorder = new DurableRecorder(internal.backend, { ...options, metadata: { ...options.metadata } });
|
|
141
|
+
if (disposed || runtime.closed) {
|
|
142
|
+
recorder.dispose();
|
|
143
|
+
void dispose();
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
recorder.snapshot(stream.snapshot, true);
|
|
147
|
+
stream.start(async (events) => {
|
|
148
|
+
for (const event of events) {
|
|
149
|
+
if (disposed) return;
|
|
150
|
+
try {
|
|
151
|
+
if (event.type === "snapshot") {
|
|
152
|
+
recorder?.snapshot(event);
|
|
153
|
+
const ids = new Set([
|
|
154
|
+
...(recorder?.submissionIds ?? []),
|
|
155
|
+
...(event.run?.inputs ?? []),
|
|
156
|
+
...event.inbox.map((item) => item.id),
|
|
157
|
+
]);
|
|
158
|
+
for (const id of ids) {
|
|
159
|
+
await observeSubmission(id);
|
|
160
|
+
if (disposed) return;
|
|
161
|
+
}
|
|
162
|
+
} else if (event.type === "submission") {
|
|
163
|
+
await recorder?.submission(event.record, readEntry);
|
|
164
|
+
if (disposed) return;
|
|
165
|
+
} else recorder?.event(event);
|
|
166
|
+
} catch {
|
|
167
|
+
if (!disposed) report("observer delivery");
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
// Observe closure even if the host never awaits closed. No execution wait is used.
|
|
172
|
+
void stream.closed
|
|
173
|
+
.then(
|
|
174
|
+
() => dispose(),
|
|
175
|
+
() => {
|
|
176
|
+
report("observer closure");
|
|
177
|
+
return dispose();
|
|
178
|
+
},
|
|
179
|
+
)
|
|
180
|
+
.catch(() => undefined);
|
|
181
|
+
const ids = new Set([...(stream.snapshot.run?.inputs ?? []), ...stream.snapshot.inbox.map((item) => item.id)]);
|
|
182
|
+
for (const id of ids) {
|
|
183
|
+
await observeSubmission(id);
|
|
184
|
+
if (disposed) return;
|
|
185
|
+
}
|
|
186
|
+
} catch {
|
|
187
|
+
report("observer initialization");
|
|
188
|
+
void dispose();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
initialization = initialize();
|
|
192
|
+
await initialization;
|
|
193
|
+
if (disposed) await dispose();
|
|
194
|
+
return controller;
|
|
195
|
+
}
|
package/src/public-api.ts
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
import type { CreateLangfuseRuntimeOptions, LangfuseRuntime } from "./runtime-core.js";
|
|
2
|
-
|
|
3
1
|
export {
|
|
4
2
|
createPiLangfuseSession,
|
|
5
3
|
type PiLangfuseSession,
|
|
6
4
|
type PiLangfuseSessionOptions,
|
|
7
5
|
} from "./pi-session.js";
|
|
8
|
-
export
|
|
9
|
-
CreateLangfuseRuntimeOptions,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
export async function createLangfuseRuntime(options: CreateLangfuseRuntimeOptions = {}): Promise<LangfuseRuntime> {
|
|
15
|
-
const runtime = await import("./runtime.js");
|
|
16
|
-
return runtime.createLangfuseRuntime(options);
|
|
17
|
-
}
|
|
6
|
+
export {
|
|
7
|
+
type CreateLangfuseRuntimeOptions,
|
|
8
|
+
createLangfuseRuntime,
|
|
9
|
+
type LangfuseRuntime,
|
|
10
|
+
type LangfuseRuntimeConfig,
|
|
11
|
+
} from "./runtime-api.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CreateLangfuseRuntimeOptions, LangfuseRuntime } from "./runtime-core.js";
|
|
2
|
+
|
|
3
|
+
export type { CreateLangfuseRuntimeOptions, LangfuseRuntime, LangfuseRuntimeConfig } from "./runtime-core.js";
|
|
4
|
+
|
|
5
|
+
export async function createLangfuseRuntime(options: CreateLangfuseRuntimeOptions = {}): Promise<LangfuseRuntime> {
|
|
6
|
+
const runtime = await import("./runtime.js");
|
|
7
|
+
return runtime.createLangfuseRuntime(options);
|
|
8
|
+
}
|
package/src/runtime-core.ts
CHANGED
|
@@ -61,7 +61,8 @@ class ManagedLangfuseRuntime implements LangfuseRuntimeInternal {
|
|
|
61
61
|
shutdown(): Promise<void> {
|
|
62
62
|
if (this.shutdownPromise) return this.shutdownPromise;
|
|
63
63
|
this.state = "closing";
|
|
64
|
-
|
|
64
|
+
// Publish the promise before invoking controller callbacks, which can reenter shutdown().
|
|
65
|
+
this.shutdownPromise = Promise.resolve().then(() => this.shutdownNow());
|
|
65
66
|
return this.shutdownPromise;
|
|
66
67
|
}
|
|
67
68
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DEFAULT_BASE_URL = "https://us.cloud.langfuse.com";
|
package/src/runtime.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from "@langfuse/tracing";
|
|
10
10
|
import type { SpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
11
11
|
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
|
|
12
|
-
import {
|
|
12
|
+
import type { LangfuseConfig } from "./config.js";
|
|
13
13
|
import {
|
|
14
14
|
type CreateLangfuseRuntimeOptions,
|
|
15
15
|
createLangfuseRuntimeFromBackend,
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
type LangfuseRuntime,
|
|
18
18
|
type LangfuseRuntimeInternal,
|
|
19
19
|
} from "./runtime-core.js";
|
|
20
|
+
import { DEFAULT_BASE_URL } from "./runtime-defaults.js";
|
|
20
21
|
import type { Observation, ObservationAttributes, ObservationType, TraceBackend } from "./tracing.js";
|
|
21
22
|
|
|
22
23
|
export type {
|
package/src/sanitizer.ts
CHANGED
|
@@ -7,13 +7,18 @@ const BASE64_DATA_URI = /data:[^,\s"'`]*;base64,[^\s"'`]*/gi;
|
|
|
7
7
|
const BASE64_DATA_URI_OMITTED = "[base64 data URI omitted]";
|
|
8
8
|
const OPAQUE_SIGNATURE_KEYS = new Set(["thinkingSignature", "textSignature", "thoughtSignature"]);
|
|
9
9
|
|
|
10
|
-
export function sanitizeTraceValue(value: unknown): unknown {
|
|
11
|
-
const budget = { remaining: MAX_CAPTURE_BYTES };
|
|
10
|
+
export function sanitizeTraceValue(value: unknown, redactCredentialFields = false): unknown {
|
|
11
|
+
const budget = { remaining: MAX_CAPTURE_BYTES, redactCredentialFields };
|
|
12
12
|
const sanitized = sanitize(value, new WeakSet<object>(), 0, budget);
|
|
13
13
|
return serializedBytes(sanitized) <= MAX_CAPTURE_BYTES ? sanitized : TRUNCATED;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
function sanitize(
|
|
16
|
+
function sanitize(
|
|
17
|
+
value: unknown,
|
|
18
|
+
active: WeakSet<object>,
|
|
19
|
+
depth: number,
|
|
20
|
+
budget: { remaining: number; redactCredentialFields?: boolean },
|
|
21
|
+
): unknown {
|
|
17
22
|
if (budget.remaining <= byteLength(TRUNCATED)) return TRUNCATED;
|
|
18
23
|
if (value === null || typeof value === "number" || typeof value === "boolean") {
|
|
19
24
|
return consume(value, budget);
|
|
@@ -73,7 +78,14 @@ function sanitize(value: unknown, active: WeakSet<object>, depth: number, budget
|
|
|
73
78
|
const sanitizedKey = sanitizeString(key, keyBudget);
|
|
74
79
|
budget.remaining -= byteLength(sanitizedKey) + 4;
|
|
75
80
|
const property = readProperty(record, key);
|
|
76
|
-
if (
|
|
81
|
+
if (
|
|
82
|
+
budget.redactCredentialFields &&
|
|
83
|
+
/^(?:(?:x[-_])?api[-_]?(?:key|token|secret)|authorization|proxy[-_]?authorization|cookies?|password|credentials?|secret|token|bearer|client[-_]?secret|private[-_]?key|secret[-_]?key|public[-_]?key|access[-_]?token|refresh[-_]?token)$/iu.test(
|
|
84
|
+
key,
|
|
85
|
+
)
|
|
86
|
+
) {
|
|
87
|
+
output[sanitizedKey] = consume("[credential field omitted]", budget);
|
|
88
|
+
} else if (redactData && key === "data") {
|
|
77
89
|
output[sanitizedKey] = consume("[base64 omitted]", budget);
|
|
78
90
|
} else if (!property.ok) output[sanitizedKey] = consume("[unreadable property]", budget);
|
|
79
91
|
else output[sanitizedKey] = sanitize(property.value, active, depth + 1, budget);
|
|
@@ -1,316 +0,0 @@
|
|
|
1
|
-
// @generated by scripts/build-runtime.mjs; do not edit.
|
|
2
|
-
// @ts-nocheck -- the generated entry uses a .ts extension for Pi's Jiti loader.
|
|
3
|
-
|
|
4
|
-
// src/config.ts
|
|
5
|
-
import { randomUUID } from "node:crypto";
|
|
6
|
-
import { chmod, mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
7
|
-
import { dirname, join } from "node:path";
|
|
8
|
-
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
9
|
-
var CONFIG_FILE_NAME = "pi-langfuse.json";
|
|
10
|
-
var DEFAULT_BASE_URL = "https://us.cloud.langfuse.com";
|
|
11
|
-
function langfuseConfigPath() {
|
|
12
|
-
return join(getAgentDir(), CONFIG_FILE_NAME);
|
|
13
|
-
}
|
|
14
|
-
var configSaveQueues = /* @__PURE__ */ new Map();
|
|
15
|
-
function writeLangfuseConfig(patch, path = langfuseConfigPath()) {
|
|
16
|
-
const previous = configSaveQueues.get(path) ?? Promise.resolve();
|
|
17
|
-
const operation = previous.then(() => writeLangfuseConfigNow(patch, path));
|
|
18
|
-
const tail = operation.then(
|
|
19
|
-
() => void 0,
|
|
20
|
-
() => void 0
|
|
21
|
-
);
|
|
22
|
-
configSaveQueues.set(path, tail);
|
|
23
|
-
void tail.then(() => {
|
|
24
|
-
if (configSaveQueues.get(path) === tail) configSaveQueues.delete(path);
|
|
25
|
-
});
|
|
26
|
-
return operation;
|
|
27
|
-
}
|
|
28
|
-
async function writeLangfuseConfigNow(patch, path) {
|
|
29
|
-
const currentDocument = await readLangfuseDocumentForUpdate(path);
|
|
30
|
-
const normalized = normalizeLangfuseConfig({ ...currentDocument, ...patch });
|
|
31
|
-
if (!normalized.ok) throw new Error(normalized.reason);
|
|
32
|
-
const {
|
|
33
|
-
publicKey: _publicKey,
|
|
34
|
-
secretKey: _secretKey,
|
|
35
|
-
baseUrl: _baseUrl,
|
|
36
|
-
environment: _environment,
|
|
37
|
-
release: _release,
|
|
38
|
-
userId: _userId,
|
|
39
|
-
captureContent: _captureContent,
|
|
40
|
-
...unknownFields
|
|
41
|
-
} = currentDocument;
|
|
42
|
-
const nextDocument = { ...unknownFields, ...normalized.config };
|
|
43
|
-
await mkdir(dirname(path), { recursive: true, mode: 448 });
|
|
44
|
-
const tempPath = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
45
|
-
try {
|
|
46
|
-
await writeFile(tempPath, `${JSON.stringify(nextDocument, null, 2)}
|
|
47
|
-
`, {
|
|
48
|
-
encoding: "utf8",
|
|
49
|
-
mode: 384
|
|
50
|
-
});
|
|
51
|
-
await chmod(tempPath, 384);
|
|
52
|
-
await rename(tempPath, path);
|
|
53
|
-
return normalized.config;
|
|
54
|
-
} catch (error) {
|
|
55
|
-
await rm(tempPath, { force: true }).catch(() => void 0);
|
|
56
|
-
throw error;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
async function readLangfuseDocumentForUpdate(path) {
|
|
60
|
-
let parsed;
|
|
61
|
-
try {
|
|
62
|
-
parsed = JSON.parse(await readFile(path, "utf8"));
|
|
63
|
-
} catch (error) {
|
|
64
|
-
if (isNodeError(error) && error.code === "ENOENT") return {};
|
|
65
|
-
throw new Error(
|
|
66
|
-
`Cannot update ${CONFIG_FILE_NAME} because the existing file is malformed or unreadable; repair it first.`
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
const normalized = normalizeLangfuseConfig(parsed);
|
|
70
|
-
if (!normalized.ok) {
|
|
71
|
-
throw new Error(
|
|
72
|
-
`Cannot update ${CONFIG_FILE_NAME} because the existing file has invalid recognized fields; repair it first.`
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
return { ...parsed };
|
|
76
|
-
}
|
|
77
|
-
async function loadLangfuseConfig(path = langfuseConfigPath()) {
|
|
78
|
-
await configSaveQueues.get(path);
|
|
79
|
-
const warnings = [];
|
|
80
|
-
const permissionFailure = await ensurePrivatePermissions(path, warnings);
|
|
81
|
-
if (permissionFailure) {
|
|
82
|
-
return {
|
|
83
|
-
ok: false,
|
|
84
|
-
path,
|
|
85
|
-
warnings,
|
|
86
|
-
reason: `Refusing to load credentials: ${permissionFailure}`
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
let text;
|
|
90
|
-
try {
|
|
91
|
-
text = await readFile(path, "utf8");
|
|
92
|
-
} catch (error) {
|
|
93
|
-
if (isNodeError(error) && error.code === "ENOENT") {
|
|
94
|
-
return { ok: false, path, warnings, reason: `Configuration file not found: ${path}` };
|
|
95
|
-
}
|
|
96
|
-
return {
|
|
97
|
-
ok: false,
|
|
98
|
-
path,
|
|
99
|
-
warnings,
|
|
100
|
-
reason: `Failed to read ${path}: ${formatError(error)}`
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
let parsed;
|
|
104
|
-
try {
|
|
105
|
-
parsed = JSON.parse(text);
|
|
106
|
-
} catch {
|
|
107
|
-
return {
|
|
108
|
-
ok: false,
|
|
109
|
-
path,
|
|
110
|
-
warnings,
|
|
111
|
-
reason: `Failed to parse ${path} as JSON; repair the file before retrying.`
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
const normalized = normalizeLangfuseConfig(parsed);
|
|
115
|
-
if (!normalized.ok) return { ok: false, path, warnings, reason: normalized.reason };
|
|
116
|
-
return { ok: true, config: normalized.config, path, warnings };
|
|
117
|
-
}
|
|
118
|
-
function normalizeLangfuseConfig(value) {
|
|
119
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
120
|
-
return { ok: false, reason: "pi-langfuse.json must contain a JSON object." };
|
|
121
|
-
}
|
|
122
|
-
const input = value;
|
|
123
|
-
const publicKey = normalizeString(input.publicKey);
|
|
124
|
-
const secretKey = normalizeString(input.secretKey);
|
|
125
|
-
if (!publicKey) {
|
|
126
|
-
return { ok: false, reason: "pi-langfuse.json requires a literal publicKey string." };
|
|
127
|
-
}
|
|
128
|
-
if (!secretKey) {
|
|
129
|
-
return { ok: false, reason: "pi-langfuse.json requires a literal secretKey string." };
|
|
130
|
-
}
|
|
131
|
-
if (isInterpolation(publicKey)) {
|
|
132
|
-
return {
|
|
133
|
-
ok: false,
|
|
134
|
-
reason: "pi-langfuse.json publicKey must be literal; environment and command interpolation are not supported."
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
if (isInterpolation(secretKey)) {
|
|
138
|
-
return {
|
|
139
|
-
ok: false,
|
|
140
|
-
reason: "pi-langfuse.json secretKey must be literal; environment and command interpolation are not supported."
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
const rawBaseUrl = input.baseUrl === void 0 ? DEFAULT_BASE_URL : normalizeString(input.baseUrl);
|
|
144
|
-
if (!rawBaseUrl) {
|
|
145
|
-
return { ok: false, reason: "pi-langfuse.json baseUrl must be a non-empty string." };
|
|
146
|
-
}
|
|
147
|
-
const baseUrl = normalizeBaseUrl(rawBaseUrl);
|
|
148
|
-
if (!baseUrl) {
|
|
149
|
-
return {
|
|
150
|
-
ok: false,
|
|
151
|
-
reason: "pi-langfuse.json baseUrl must use HTTP or HTTPS without credentials, a query, or a fragment."
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
if (input.captureContent !== void 0 && typeof input.captureContent !== "boolean") {
|
|
155
|
-
return { ok: false, reason: "pi-langfuse.json captureContent must be a boolean." };
|
|
156
|
-
}
|
|
157
|
-
const environment = optionalString(input.environment, "environment");
|
|
158
|
-
if (!environment.ok) return environment;
|
|
159
|
-
if (environment.value && (environment.value.length > 40 || !/^(?!langfuse)[a-z0-9_-]+$/u.test(environment.value))) {
|
|
160
|
-
return {
|
|
161
|
-
ok: false,
|
|
162
|
-
reason: "pi-langfuse.json environment must be at most 40 lowercase letters, numbers, hyphens, or underscores and must not start with langfuse."
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
const release = optionalString(input.release, "release");
|
|
166
|
-
if (!release.ok) return release;
|
|
167
|
-
const userId = optionalString(input.userId, "userId");
|
|
168
|
-
if (!userId.ok) return userId;
|
|
169
|
-
if (userId.value && userId.value.length > 200) {
|
|
170
|
-
return { ok: false, reason: "pi-langfuse.json userId must be at most 200 characters." };
|
|
171
|
-
}
|
|
172
|
-
return {
|
|
173
|
-
ok: true,
|
|
174
|
-
config: {
|
|
175
|
-
publicKey,
|
|
176
|
-
secretKey,
|
|
177
|
-
baseUrl,
|
|
178
|
-
...environment.value ? { environment: environment.value } : {},
|
|
179
|
-
...release.value ? { release: release.value } : {},
|
|
180
|
-
...userId.value ? { userId: userId.value } : {},
|
|
181
|
-
captureContent: input.captureContent ?? true
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
async function ensurePrivatePermissions(path, warnings) {
|
|
186
|
-
try {
|
|
187
|
-
const file = await stat(path);
|
|
188
|
-
if ((file.mode & 511) !== 384) {
|
|
189
|
-
await chmod(path, 384);
|
|
190
|
-
const repaired = await stat(path);
|
|
191
|
-
if ((repaired.mode & 511) !== 384) {
|
|
192
|
-
throw new Error(`permissions remained ${(repaired.mode & 511).toString(8)}`);
|
|
193
|
-
}
|
|
194
|
-
warnings.push(`Restricted ${path} permissions to 0600.`);
|
|
195
|
-
}
|
|
196
|
-
} catch (error) {
|
|
197
|
-
if (isNodeError(error) && error.code === "ENOENT") return void 0;
|
|
198
|
-
const failure = `Failed to enforce 0600 permissions for ${path}: ${formatError(error)}`;
|
|
199
|
-
warnings.push(failure);
|
|
200
|
-
return failure;
|
|
201
|
-
}
|
|
202
|
-
return void 0;
|
|
203
|
-
}
|
|
204
|
-
function normalizeBaseUrl(value) {
|
|
205
|
-
try {
|
|
206
|
-
const url = new URL(value);
|
|
207
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") return void 0;
|
|
208
|
-
if (url.username || url.password || url.search || url.hash) return void 0;
|
|
209
|
-
return url.toString().replace(/\/+$/, "");
|
|
210
|
-
} catch {
|
|
211
|
-
return void 0;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
function optionalString(value, name) {
|
|
215
|
-
if (value === void 0) return { ok: true };
|
|
216
|
-
const normalized = normalizeString(value);
|
|
217
|
-
return normalized ? { ok: true, value: normalized } : { ok: false, reason: `pi-langfuse.json ${name} must be a non-empty string.` };
|
|
218
|
-
}
|
|
219
|
-
function normalizeString(value) {
|
|
220
|
-
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
221
|
-
}
|
|
222
|
-
function isInterpolation(value) {
|
|
223
|
-
return value.startsWith("$") || value.startsWith("!");
|
|
224
|
-
}
|
|
225
|
-
function isNodeError(error) {
|
|
226
|
-
return error instanceof Error && "code" in error;
|
|
227
|
-
}
|
|
228
|
-
function formatError(error) {
|
|
229
|
-
return error instanceof Error ? error.message : String(error);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// src/runtime-core.ts
|
|
233
|
-
var RUNTIME_INTERNAL_KEY = /* @__PURE__ */ Symbol.for("@narumitw/pi-langfuse/runtime-internal/v2");
|
|
234
|
-
var ManagedLangfuseRuntime = class {
|
|
235
|
-
constructor(backend) {
|
|
236
|
-
this.backend = backend;
|
|
237
|
-
}
|
|
238
|
-
backend;
|
|
239
|
-
state = "open";
|
|
240
|
-
sessionDisposers = /* @__PURE__ */ new Set();
|
|
241
|
-
flushQueue = Promise.resolve();
|
|
242
|
-
shutdownPromise;
|
|
243
|
-
get closed() {
|
|
244
|
-
return this.state !== "open";
|
|
245
|
-
}
|
|
246
|
-
registerSession(disposer) {
|
|
247
|
-
if (this.state !== "open") throw new Error("Langfuse runtime is shutting down or already closed.");
|
|
248
|
-
this.sessionDisposers.add(disposer);
|
|
249
|
-
return () => this.sessionDisposers.delete(disposer);
|
|
250
|
-
}
|
|
251
|
-
flush() {
|
|
252
|
-
if (this.state !== "open") {
|
|
253
|
-
return Promise.reject(new Error("Langfuse runtime is shutting down or already closed."));
|
|
254
|
-
}
|
|
255
|
-
const operation = this.flushQueue.then(() => this.backend.forceFlush());
|
|
256
|
-
this.flushQueue = operation.then(
|
|
257
|
-
() => void 0,
|
|
258
|
-
() => void 0
|
|
259
|
-
);
|
|
260
|
-
return operation;
|
|
261
|
-
}
|
|
262
|
-
shutdown() {
|
|
263
|
-
if (this.shutdownPromise) return this.shutdownPromise;
|
|
264
|
-
this.state = "closing";
|
|
265
|
-
this.shutdownPromise = this.shutdownNow();
|
|
266
|
-
return this.shutdownPromise;
|
|
267
|
-
}
|
|
268
|
-
async shutdownNow() {
|
|
269
|
-
const errors = [];
|
|
270
|
-
for (const dispose of [...this.sessionDisposers]) {
|
|
271
|
-
try {
|
|
272
|
-
await dispose("Langfuse runtime shut down before the Pi session was disposed.");
|
|
273
|
-
} catch (error) {
|
|
274
|
-
errors.push(error);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
this.sessionDisposers.clear();
|
|
278
|
-
await this.flushQueue;
|
|
279
|
-
try {
|
|
280
|
-
await this.backend.forceFlush();
|
|
281
|
-
} catch (error) {
|
|
282
|
-
errors.push(error);
|
|
283
|
-
}
|
|
284
|
-
try {
|
|
285
|
-
await this.backend.shutdown();
|
|
286
|
-
} catch (error) {
|
|
287
|
-
errors.push(error);
|
|
288
|
-
} finally {
|
|
289
|
-
this.state = "closed";
|
|
290
|
-
}
|
|
291
|
-
if (errors.length === 1) throw errors[0];
|
|
292
|
-
if (errors.length > 1) throw new AggregateError(errors, "Langfuse runtime shutdown failed.");
|
|
293
|
-
}
|
|
294
|
-
};
|
|
295
|
-
function getLangfuseRuntimeInternal(runtime) {
|
|
296
|
-
const internal = runtime[RUNTIME_INTERNAL_KEY];
|
|
297
|
-
if (!internal) throw new Error("Langfuse runtime was not created by @narumitw/pi-langfuse.");
|
|
298
|
-
return internal;
|
|
299
|
-
}
|
|
300
|
-
function createLangfuseRuntimeFromBackend(backend) {
|
|
301
|
-
return registerLangfuseRuntime(new ManagedLangfuseRuntime(backend));
|
|
302
|
-
}
|
|
303
|
-
function registerLangfuseRuntime(runtime) {
|
|
304
|
-
Object.defineProperty(runtime, RUNTIME_INTERNAL_KEY, { value: runtime });
|
|
305
|
-
return runtime;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
export {
|
|
309
|
-
DEFAULT_BASE_URL,
|
|
310
|
-
writeLangfuseConfig,
|
|
311
|
-
loadLangfuseConfig,
|
|
312
|
-
normalizeLangfuseConfig,
|
|
313
|
-
getLangfuseRuntimeInternal,
|
|
314
|
-
createLangfuseRuntimeFromBackend
|
|
315
|
-
};
|
|
316
|
-
//# sourceMappingURL=chunk-DBQAWNKG.ts.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/config.ts", "../../src/runtime-core.ts"],
|
|
4
|
-
"sourcesContent": ["import { randomUUID } from \"node:crypto\";\nimport { chmod, mkdir, readFile, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { getAgentDir } from \"@earendil-works/pi-coding-agent\";\n\nconst CONFIG_FILE_NAME = \"pi-langfuse.json\";\nexport const DEFAULT_BASE_URL = \"https://us.cloud.langfuse.com\";\n\nexport interface LangfuseConfig {\n publicKey: string;\n secretKey: string;\n baseUrl: string;\n environment?: string;\n release?: string;\n userId?: string;\n captureContent: boolean;\n}\n\nexport type LangfuseConfigPatch = Partial<LangfuseConfig>;\n\nexport type LangfuseConfigResult =\n | { ok: true; config: LangfuseConfig; path: string; warnings: string[] }\n | { ok: false; path: string; warnings: string[]; reason: string };\n\nexport function langfuseConfigPath(): string {\n return join(getAgentDir(), CONFIG_FILE_NAME);\n}\n\nconst configSaveQueues = new Map<string, Promise<void>>();\n\nexport async function waitForLangfuseConfigWrites(path = langfuseConfigPath()): Promise<void> {\n await configSaveQueues.get(path);\n}\n\nexport function writeLangfuseConfig(patch: LangfuseConfigPatch, path = langfuseConfigPath()): Promise<LangfuseConfig> {\n const previous = configSaveQueues.get(path) ?? Promise.resolve();\n const operation = previous.then(() => writeLangfuseConfigNow(patch, path));\n const tail = operation.then(\n () => undefined,\n () => undefined,\n );\n configSaveQueues.set(path, tail);\n void tail.then(() => {\n if (configSaveQueues.get(path) === tail) configSaveQueues.delete(path);\n });\n return operation;\n}\n\nasync function writeLangfuseConfigNow(patch: LangfuseConfigPatch, path: string): Promise<LangfuseConfig> {\n const currentDocument = await readLangfuseDocumentForUpdate(path);\n const normalized = normalizeLangfuseConfig({ ...currentDocument, ...patch });\n if (!normalized.ok) throw new Error(normalized.reason);\n const {\n publicKey: _publicKey,\n secretKey: _secretKey,\n baseUrl: _baseUrl,\n environment: _environment,\n release: _release,\n userId: _userId,\n captureContent: _captureContent,\n ...unknownFields\n } = currentDocument;\n const nextDocument = { ...unknownFields, ...normalized.config };\n\n await mkdir(dirname(path), { recursive: true, mode: 0o700 });\n const tempPath = `${path}.${process.pid}.${randomUUID()}.tmp`;\n try {\n await writeFile(tempPath, `${JSON.stringify(nextDocument, null, 2)}\\n`, {\n encoding: \"utf8\",\n mode: 0o600,\n });\n await chmod(tempPath, 0o600);\n await rename(tempPath, path);\n return normalized.config;\n } catch (error) {\n await rm(tempPath, { force: true }).catch(() => undefined);\n throw error;\n }\n}\n\nasync function readLangfuseDocumentForUpdate(path: string): Promise<Record<string, unknown>> {\n let parsed: unknown;\n try {\n parsed = JSON.parse(await readFile(path, \"utf8\")) as unknown;\n } catch (error) {\n if (isNodeError(error) && error.code === \"ENOENT\") return {};\n throw new Error(\n `Cannot update ${CONFIG_FILE_NAME} because the existing file is malformed or unreadable; repair it first.`,\n );\n }\n const normalized = normalizeLangfuseConfig(parsed);\n if (!normalized.ok) {\n throw new Error(\n `Cannot update ${CONFIG_FILE_NAME} because the existing file has invalid recognized fields; repair it first.`,\n );\n }\n return { ...(parsed as Record<string, unknown>) };\n}\n\nexport async function loadLangfuseConfig(path = langfuseConfigPath()): Promise<LangfuseConfigResult> {\n await configSaveQueues.get(path);\n const warnings: string[] = [];\n const permissionFailure = await ensurePrivatePermissions(path, warnings);\n if (permissionFailure) {\n return {\n ok: false,\n path,\n warnings,\n reason: `Refusing to load credentials: ${permissionFailure}`,\n };\n }\n\n let text: string;\n try {\n text = await readFile(path, \"utf8\");\n } catch (error) {\n if (isNodeError(error) && error.code === \"ENOENT\") {\n return { ok: false, path, warnings, reason: `Configuration file not found: ${path}` };\n }\n return {\n ok: false,\n path,\n warnings,\n reason: `Failed to read ${path}: ${formatError(error)}`,\n };\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(text) as unknown;\n } catch {\n return {\n ok: false,\n path,\n warnings,\n reason: `Failed to parse ${path} as JSON; repair the file before retrying.`,\n };\n }\n\n const normalized = normalizeLangfuseConfig(parsed);\n if (!normalized.ok) return { ok: false, path, warnings, reason: normalized.reason };\n return { ok: true, config: normalized.config, path, warnings };\n}\n\nexport function normalizeLangfuseConfig(\n value: unknown,\n): { ok: true; config: LangfuseConfig } | { ok: false; reason: string } {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return { ok: false, reason: \"pi-langfuse.json must contain a JSON object.\" };\n }\n const input = value as Record<string, unknown>;\n const publicKey = normalizeString(input.publicKey);\n const secretKey = normalizeString(input.secretKey);\n if (!publicKey) {\n return { ok: false, reason: \"pi-langfuse.json requires a literal publicKey string.\" };\n }\n if (!secretKey) {\n return { ok: false, reason: \"pi-langfuse.json requires a literal secretKey string.\" };\n }\n if (isInterpolation(publicKey)) {\n return {\n ok: false,\n reason: \"pi-langfuse.json publicKey must be literal; environment and command interpolation are not supported.\",\n };\n }\n if (isInterpolation(secretKey)) {\n return {\n ok: false,\n reason: \"pi-langfuse.json secretKey must be literal; environment and command interpolation are not supported.\",\n };\n }\n\n const rawBaseUrl = input.baseUrl === undefined ? DEFAULT_BASE_URL : normalizeString(input.baseUrl);\n if (!rawBaseUrl) {\n return { ok: false, reason: \"pi-langfuse.json baseUrl must be a non-empty string.\" };\n }\n const baseUrl = normalizeBaseUrl(rawBaseUrl);\n if (!baseUrl) {\n return {\n ok: false,\n reason: \"pi-langfuse.json baseUrl must use HTTP or HTTPS without credentials, a query, or a fragment.\",\n };\n }\n\n if (input.captureContent !== undefined && typeof input.captureContent !== \"boolean\") {\n return { ok: false, reason: \"pi-langfuse.json captureContent must be a boolean.\" };\n }\n const environment = optionalString(input.environment, \"environment\");\n if (!environment.ok) return environment;\n if (environment.value && (environment.value.length > 40 || !/^(?!langfuse)[a-z0-9_-]+$/u.test(environment.value))) {\n return {\n ok: false,\n reason:\n \"pi-langfuse.json environment must be at most 40 lowercase letters, numbers, hyphens, or underscores and must not start with langfuse.\",\n };\n }\n const release = optionalString(input.release, \"release\");\n if (!release.ok) return release;\n const userId = optionalString(input.userId, \"userId\");\n if (!userId.ok) return userId;\n if (userId.value && userId.value.length > 200) {\n return { ok: false, reason: \"pi-langfuse.json userId must be at most 200 characters.\" };\n }\n\n return {\n ok: true,\n config: {\n publicKey,\n secretKey,\n baseUrl,\n ...(environment.value ? { environment: environment.value } : {}),\n ...(release.value ? { release: release.value } : {}),\n ...(userId.value ? { userId: userId.value } : {}),\n captureContent: input.captureContent ?? true,\n },\n };\n}\n\nasync function ensurePrivatePermissions(path: string, warnings: string[]): Promise<string | undefined> {\n try {\n const file = await stat(path);\n if ((file.mode & 0o777) !== 0o600) {\n await chmod(path, 0o600);\n const repaired = await stat(path);\n if ((repaired.mode & 0o777) !== 0o600) {\n throw new Error(`permissions remained ${(repaired.mode & 0o777).toString(8)}`);\n }\n warnings.push(`Restricted ${path} permissions to 0600.`);\n }\n } catch (error) {\n if (isNodeError(error) && error.code === \"ENOENT\") return undefined;\n const failure = `Failed to enforce 0600 permissions for ${path}: ${formatError(error)}`;\n warnings.push(failure);\n return failure;\n }\n return undefined;\n}\n\nfunction normalizeBaseUrl(value: string): string | undefined {\n try {\n const url = new URL(value);\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") return undefined;\n if (url.username || url.password || url.search || url.hash) return undefined;\n return url.toString().replace(/\\/+$/, \"\");\n } catch {\n return undefined;\n }\n}\n\nfunction optionalString(value: unknown, name: string): { ok: true; value?: string } | { ok: false; reason: string } {\n if (value === undefined) return { ok: true };\n const normalized = normalizeString(value);\n return normalized\n ? { ok: true, value: normalized }\n : { ok: false, reason: `pi-langfuse.json ${name} must be a non-empty string.` };\n}\n\nfunction normalizeString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() ? value.trim() : undefined;\n}\n\nfunction isInterpolation(value: string): boolean {\n return value.startsWith(\"$\") || value.startsWith(\"!\");\n}\n\nfunction isNodeError(error: unknown): error is NodeJS.ErrnoException {\n return error instanceof Error && \"code\" in error;\n}\n\nfunction formatError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n", "import type { TraceBackend } from \"./tracing.js\";\n\nexport interface LangfuseRuntimeConfig {\n publicKey?: string;\n secretKey?: string;\n baseUrl?: string;\n environment?: string;\n release?: string;\n}\n\nexport interface CreateLangfuseRuntimeOptions {\n config?: LangfuseRuntimeConfig;\n env?: NodeJS.ProcessEnv | false;\n}\n\nexport interface LangfuseRuntime {\n readonly closed: boolean;\n flush(): Promise<void>;\n shutdown(): Promise<void>;\n}\n\ntype RuntimeSessionDisposer = (statusMessage: string) => void | Promise<void>;\n\nexport interface LangfuseRuntimeInternal extends LangfuseRuntime {\n readonly backend: TraceBackend;\n registerSession(disposer: RuntimeSessionDisposer): () => void;\n}\n\nconst RUNTIME_INTERNAL_KEY = Symbol.for(\"@narumitw/pi-langfuse/runtime-internal/v2\");\n\nclass ManagedLangfuseRuntime implements LangfuseRuntimeInternal {\n private state: \"open\" | \"closing\" | \"closed\" = \"open\";\n private readonly sessionDisposers = new Set<RuntimeSessionDisposer>();\n private flushQueue = Promise.resolve();\n private shutdownPromise: Promise<void> | undefined;\n\n constructor(readonly backend: TraceBackend) {}\n\n get closed(): boolean {\n return this.state !== \"open\";\n }\n\n registerSession(disposer: RuntimeSessionDisposer): () => void {\n if (this.state !== \"open\") throw new Error(\"Langfuse runtime is shutting down or already closed.\");\n this.sessionDisposers.add(disposer);\n return () => this.sessionDisposers.delete(disposer);\n }\n\n flush(): Promise<void> {\n if (this.state !== \"open\") {\n return Promise.reject(new Error(\"Langfuse runtime is shutting down or already closed.\"));\n }\n const operation = this.flushQueue.then(() => this.backend.forceFlush());\n this.flushQueue = operation.then(\n () => undefined,\n () => undefined,\n );\n return operation;\n }\n\n shutdown(): Promise<void> {\n if (this.shutdownPromise) return this.shutdownPromise;\n this.state = \"closing\";\n this.shutdownPromise = this.shutdownNow();\n return this.shutdownPromise;\n }\n\n private async shutdownNow(): Promise<void> {\n const errors: unknown[] = [];\n for (const dispose of [...this.sessionDisposers]) {\n try {\n await dispose(\"Langfuse runtime shut down before the Pi session was disposed.\");\n } catch (error) {\n errors.push(error);\n }\n }\n this.sessionDisposers.clear();\n\n await this.flushQueue;\n try {\n await this.backend.forceFlush();\n } catch (error) {\n errors.push(error);\n }\n try {\n await this.backend.shutdown();\n } catch (error) {\n errors.push(error);\n } finally {\n this.state = \"closed\";\n }\n\n if (errors.length === 1) throw errors[0];\n if (errors.length > 1) throw new AggregateError(errors, \"Langfuse runtime shutdown failed.\");\n }\n}\n\nexport function getLangfuseRuntimeInternal(runtime: LangfuseRuntime): LangfuseRuntimeInternal {\n const internal = (runtime as LangfuseRuntime & { [key: symbol]: LangfuseRuntimeInternal | undefined })[\n RUNTIME_INTERNAL_KEY\n ];\n if (!internal) throw new Error(\"Langfuse runtime was not created by @narumitw/pi-langfuse.\");\n return internal;\n}\n\nexport function createLangfuseRuntimeFromBackend(backend: TraceBackend): LangfuseRuntime {\n return registerLangfuseRuntime(new ManagedLangfuseRuntime(backend));\n}\n\nexport function registerLangfuseRuntime(runtime: LangfuseRuntimeInternal): LangfuseRuntimeInternal {\n Object.defineProperty(runtime, RUNTIME_INTERNAL_KEY, { value: runtime });\n return runtime;\n}\n"],
|
|
5
|
-
"mappings": ";;;;AAAA,SAAS,kBAAkB;AAC3B,SAAS,OAAO,OAAO,UAAU,QAAQ,IAAI,MAAM,iBAAiB;AACpE,SAAS,SAAS,YAAY;AAC9B,SAAS,mBAAmB;AAE5B,IAAM,mBAAmB;AAClB,IAAM,mBAAmB;AAkBzB,SAAS,qBAA6B;AAC3C,SAAO,KAAK,YAAY,GAAG,gBAAgB;AAC7C;AAEA,IAAM,mBAAmB,oBAAI,IAA2B;AAMjD,SAAS,oBAAoB,OAA4B,OAAO,mBAAmB,GAA4B;AACpH,QAAM,WAAW,iBAAiB,IAAI,IAAI,KAAK,QAAQ,QAAQ;AAC/D,QAAM,YAAY,SAAS,KAAK,MAAM,uBAAuB,OAAO,IAAI,CAAC;AACzE,QAAM,OAAO,UAAU;AAAA,IACrB,MAAM;AAAA,IACN,MAAM;AAAA,EACR;AACA,mBAAiB,IAAI,MAAM,IAAI;AAC/B,OAAK,KAAK,KAAK,MAAM;AACnB,QAAI,iBAAiB,IAAI,IAAI,MAAM,KAAM,kBAAiB,OAAO,IAAI;AAAA,EACvE,CAAC;AACD,SAAO;AACT;AAEA,eAAe,uBAAuB,OAA4B,MAAuC;AACvG,QAAM,kBAAkB,MAAM,8BAA8B,IAAI;AAChE,QAAM,aAAa,wBAAwB,EAAE,GAAG,iBAAiB,GAAG,MAAM,CAAC;AAC3E,MAAI,CAAC,WAAW,GAAI,OAAM,IAAI,MAAM,WAAW,MAAM;AACrD,QAAM;AAAA,IACJ,WAAW;AAAA,IACX,WAAW;AAAA,IACX,SAAS;AAAA,IACT,aAAa;AAAA,IACb,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,gBAAgB;AAAA,IAChB,GAAG;AAAA,EACL,IAAI;AACJ,QAAM,eAAe,EAAE,GAAG,eAAe,GAAG,WAAW,OAAO;AAE9D,QAAM,MAAM,QAAQ,IAAI,GAAG,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAC3D,QAAM,WAAW,GAAG,IAAI,IAAI,QAAQ,GAAG,IAAI,WAAW,CAAC;AACvD,MAAI;AACF,UAAM,UAAU,UAAU,GAAG,KAAK,UAAU,cAAc,MAAM,CAAC,CAAC;AAAA,GAAM;AAAA,MACtE,UAAU;AAAA,MACV,MAAM;AAAA,IACR,CAAC;AACD,UAAM,MAAM,UAAU,GAAK;AAC3B,UAAM,OAAO,UAAU,IAAI;AAC3B,WAAO,WAAW;AAAA,EACpB,SAAS,OAAO;AACd,UAAM,GAAG,UAAU,EAAE,OAAO,KAAK,CAAC,EAAE,MAAM,MAAM,MAAS;AACzD,UAAM;AAAA,EACR;AACF;AAEA,eAAe,8BAA8B,MAAgD;AAC3F,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,MAAM,SAAS,MAAM,MAAM,CAAC;AAAA,EAClD,SAAS,OAAO;AACd,QAAI,YAAY,KAAK,KAAK,MAAM,SAAS,SAAU,QAAO,CAAC;AAC3D,UAAM,IAAI;AAAA,MACR,iBAAiB,gBAAgB;AAAA,IACnC;AAAA,EACF;AACA,QAAM,aAAa,wBAAwB,MAAM;AACjD,MAAI,CAAC,WAAW,IAAI;AAClB,UAAM,IAAI;AAAA,MACR,iBAAiB,gBAAgB;AAAA,IACnC;AAAA,EACF;AACA,SAAO,EAAE,GAAI,OAAmC;AAClD;AAEA,eAAsB,mBAAmB,OAAO,mBAAmB,GAAkC;AACnG,QAAM,iBAAiB,IAAI,IAAI;AAC/B,QAAM,WAAqB,CAAC;AAC5B,QAAM,oBAAoB,MAAM,yBAAyB,MAAM,QAAQ;AACvE,MAAI,mBAAmB;AACrB,WAAO;AAAA,MACL,IAAI;AAAA,MACJ;AAAA,MACA;AAAA,MACA,QAAQ,iCAAiC,iBAAiB;AAAA,IAC5D;AAAA,EACF;AAEA,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,SAAS,MAAM,MAAM;AAAA,EACpC,SAAS,OAAO;AACd,QAAI,YAAY,KAAK,KAAK,MAAM,SAAS,UAAU;AACjD,aAAO,EAAE,IAAI,OAAO,MAAM,UAAU,QAAQ,iCAAiC,IAAI,GAAG;AAAA,IACtF;AACA,WAAO;AAAA,MACL,IAAI;AAAA,MACJ;AAAA,MACA;AAAA,MACA,QAAQ,kBAAkB,IAAI,KAAK,YAAY,KAAK,CAAC;AAAA,IACvD;AAAA,EACF;AAEA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,IAAI;AAAA,EAC1B,QAAQ;AACN,WAAO;AAAA,MACL,IAAI;AAAA,MACJ;AAAA,MACA;AAAA,MACA,QAAQ,mBAAmB,IAAI;AAAA,IACjC;AAAA,EACF;AAEA,QAAM,aAAa,wBAAwB,MAAM;AACjD,MAAI,CAAC,WAAW,GAAI,QAAO,EAAE,IAAI,OAAO,MAAM,UAAU,QAAQ,WAAW,OAAO;AAClF,SAAO,EAAE,IAAI,MAAM,QAAQ,WAAW,QAAQ,MAAM,SAAS;AAC/D;AAEO,SAAS,wBACd,OACsE;AACtE,MAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;AAC/D,WAAO,EAAE,IAAI,OAAO,QAAQ,+CAA+C;AAAA,EAC7E;AACA,QAAM,QAAQ;AACd,QAAM,YAAY,gBAAgB,MAAM,SAAS;AACjD,QAAM,YAAY,gBAAgB,MAAM,SAAS;AACjD,MAAI,CAAC,WAAW;AACd,WAAO,EAAE,IAAI,OAAO,QAAQ,wDAAwD;AAAA,EACtF;AACA,MAAI,CAAC,WAAW;AACd,WAAO,EAAE,IAAI,OAAO,QAAQ,wDAAwD;AAAA,EACtF;AACA,MAAI,gBAAgB,SAAS,GAAG;AAC9B,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,QAAQ;AAAA,IACV;AAAA,EACF;AACA,MAAI,gBAAgB,SAAS,GAAG;AAC9B,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,QAAM,aAAa,MAAM,YAAY,SAAY,mBAAmB,gBAAgB,MAAM,OAAO;AACjG,MAAI,CAAC,YAAY;AACf,WAAO,EAAE,IAAI,OAAO,QAAQ,uDAAuD;AAAA,EACrF;AACA,QAAM,UAAU,iBAAiB,UAAU;AAC3C,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,MAAI,MAAM,mBAAmB,UAAa,OAAO,MAAM,mBAAmB,WAAW;AACnF,WAAO,EAAE,IAAI,OAAO,QAAQ,qDAAqD;AAAA,EACnF;AACA,QAAM,cAAc,eAAe,MAAM,aAAa,aAAa;AACnE,MAAI,CAAC,YAAY,GAAI,QAAO;AAC5B,MAAI,YAAY,UAAU,YAAY,MAAM,SAAS,MAAM,CAAC,6BAA6B,KAAK,YAAY,KAAK,IAAI;AACjH,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,QACE;AAAA,IACJ;AAAA,EACF;AACA,QAAM,UAAU,eAAe,MAAM,SAAS,SAAS;AACvD,MAAI,CAAC,QAAQ,GAAI,QAAO;AACxB,QAAM,SAAS,eAAe,MAAM,QAAQ,QAAQ;AACpD,MAAI,CAAC,OAAO,GAAI,QAAO;AACvB,MAAI,OAAO,SAAS,OAAO,MAAM,SAAS,KAAK;AAC7C,WAAO,EAAE,IAAI,OAAO,QAAQ,0DAA0D;AAAA,EACxF;AAEA,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,QAAQ;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAI,YAAY,QAAQ,EAAE,aAAa,YAAY,MAAM,IAAI,CAAC;AAAA,MAC9D,GAAI,QAAQ,QAAQ,EAAE,SAAS,QAAQ,MAAM,IAAI,CAAC;AAAA,MAClD,GAAI,OAAO,QAAQ,EAAE,QAAQ,OAAO,MAAM,IAAI,CAAC;AAAA,MAC/C,gBAAgB,MAAM,kBAAkB;AAAA,IAC1C;AAAA,EACF;AACF;AAEA,eAAe,yBAAyB,MAAc,UAAiD;AACrG,MAAI;AACF,UAAM,OAAO,MAAM,KAAK,IAAI;AAC5B,SAAK,KAAK,OAAO,SAAW,KAAO;AACjC,YAAM,MAAM,MAAM,GAAK;AACvB,YAAM,WAAW,MAAM,KAAK,IAAI;AAChC,WAAK,SAAS,OAAO,SAAW,KAAO;AACrC,cAAM,IAAI,MAAM,yBAAyB,SAAS,OAAO,KAAO,SAAS,CAAC,CAAC,EAAE;AAAA,MAC/E;AACA,eAAS,KAAK,cAAc,IAAI,uBAAuB;AAAA,IACzD;AAAA,EACF,SAAS,OAAO;AACd,QAAI,YAAY,KAAK,KAAK,MAAM,SAAS,SAAU,QAAO;AAC1D,UAAM,UAAU,0CAA0C,IAAI,KAAK,YAAY,KAAK,CAAC;AACrF,aAAS,KAAK,OAAO;AACrB,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,SAAS,iBAAiB,OAAmC;AAC3D,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,KAAK;AACzB,QAAI,IAAI,aAAa,WAAW,IAAI,aAAa,SAAU,QAAO;AAClE,QAAI,IAAI,YAAY,IAAI,YAAY,IAAI,UAAU,IAAI,KAAM,QAAO;AACnE,WAAO,IAAI,SAAS,EAAE,QAAQ,QAAQ,EAAE;AAAA,EAC1C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,eAAe,OAAgB,MAA4E;AAClH,MAAI,UAAU,OAAW,QAAO,EAAE,IAAI,KAAK;AAC3C,QAAM,aAAa,gBAAgB,KAAK;AACxC,SAAO,aACH,EAAE,IAAI,MAAM,OAAO,WAAW,IAC9B,EAAE,IAAI,OAAO,QAAQ,oBAAoB,IAAI,+BAA+B;AAClF;AAEA,SAAS,gBAAgB,OAAoC;AAC3D,SAAO,OAAO,UAAU,YAAY,MAAM,KAAK,IAAI,MAAM,KAAK,IAAI;AACpE;AAEA,SAAS,gBAAgB,OAAwB;AAC/C,SAAO,MAAM,WAAW,GAAG,KAAK,MAAM,WAAW,GAAG;AACtD;AAEA,SAAS,YAAY,OAAgD;AACnE,SAAO,iBAAiB,SAAS,UAAU;AAC7C;AAEA,SAAS,YAAY,OAAwB;AAC3C,SAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC9D;;;ACnPA,IAAM,uBAAuB,uBAAO,IAAI,2CAA2C;AAEnF,IAAM,yBAAN,MAAgE;AAAA,EAM9D,YAAqB,SAAuB;AAAvB;AAAA,EAAwB;AAAA,EAAxB;AAAA,EALb,QAAuC;AAAA,EAC9B,mBAAmB,oBAAI,IAA4B;AAAA,EAC5D,aAAa,QAAQ,QAAQ;AAAA,EAC7B;AAAA,EAIR,IAAI,SAAkB;AACpB,WAAO,KAAK,UAAU;AAAA,EACxB;AAAA,EAEA,gBAAgB,UAA8C;AAC5D,QAAI,KAAK,UAAU,OAAQ,OAAM,IAAI,MAAM,sDAAsD;AACjG,SAAK,iBAAiB,IAAI,QAAQ;AAClC,WAAO,MAAM,KAAK,iBAAiB,OAAO,QAAQ;AAAA,EACpD;AAAA,EAEA,QAAuB;AACrB,QAAI,KAAK,UAAU,QAAQ;AACzB,aAAO,QAAQ,OAAO,IAAI,MAAM,sDAAsD,CAAC;AAAA,IACzF;AACA,UAAM,YAAY,KAAK,WAAW,KAAK,MAAM,KAAK,QAAQ,WAAW,CAAC;AACtE,SAAK,aAAa,UAAU;AAAA,MAC1B,MAAM;AAAA,MACN,MAAM;AAAA,IACR;AACA,WAAO;AAAA,EACT;AAAA,EAEA,WAA0B;AACxB,QAAI,KAAK,gBAAiB,QAAO,KAAK;AACtC,SAAK,QAAQ;AACb,SAAK,kBAAkB,KAAK,YAAY;AACxC,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAc,cAA6B;AACzC,UAAM,SAAoB,CAAC;AAC3B,eAAW,WAAW,CAAC,GAAG,KAAK,gBAAgB,GAAG;AAChD,UAAI;AACF,cAAM,QAAQ,gEAAgE;AAAA,MAChF,SAAS,OAAO;AACd,eAAO,KAAK,KAAK;AAAA,MACnB;AAAA,IACF;AACA,SAAK,iBAAiB,MAAM;AAE5B,UAAM,KAAK;AACX,QAAI;AACF,YAAM,KAAK,QAAQ,WAAW;AAAA,IAChC,SAAS,OAAO;AACd,aAAO,KAAK,KAAK;AAAA,IACnB;AACA,QAAI;AACF,YAAM,KAAK,QAAQ,SAAS;AAAA,IAC9B,SAAS,OAAO;AACd,aAAO,KAAK,KAAK;AAAA,IACnB,UAAE;AACA,WAAK,QAAQ;AAAA,IACf;AAEA,QAAI,OAAO,WAAW,EAAG,OAAM,OAAO,CAAC;AACvC,QAAI,OAAO,SAAS,EAAG,OAAM,IAAI,eAAe,QAAQ,mCAAmC;AAAA,EAC7F;AACF;AAEO,SAAS,2BAA2B,SAAmD;AAC5F,QAAM,WAAY,QAChB,oBACF;AACA,MAAI,CAAC,SAAU,OAAM,IAAI,MAAM,4DAA4D;AAC3F,SAAO;AACT;AAEO,SAAS,iCAAiC,SAAwC;AACvF,SAAO,wBAAwB,IAAI,uBAAuB,OAAO,CAAC;AACpE;AAEO,SAAS,wBAAwB,SAA2D;AACjG,SAAO,eAAe,SAAS,sBAAsB,EAAE,OAAO,QAAQ,CAAC;AACvE,SAAO;AACT;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|