@narumitw/pi-langfuse 0.50.5 → 0.51.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 +125 -62
- package/dist/chunks/chunk-IASLJKKB.js +316 -0
- package/dist/chunks/chunk-IASLJKKB.js.map +7 -0
- package/dist/chunks/runtime-6MIKLWNJ.js +352 -0
- package/dist/chunks/runtime-6MIKLWNJ.js.map +7 -0
- package/dist/config.d.ts +33 -0
- package/dist/git.d.ts +5 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1551 -0
- package/dist/index.js.map +7 -0
- package/dist/index.ts +594 -521
- package/dist/index.ts.map +4 -4
- package/dist/langfuse.d.ts +13 -0
- package/dist/pi-session.d.ts +43 -0
- package/dist/public-api.d.ts +4 -0
- package/dist/runtime-core.d.ts +26 -0
- package/dist/runtime.d.ts +22 -0
- package/dist/sanitizer.d.ts +2 -0
- package/dist/tracing.d.ts +202 -0
- package/package.json +63 -59
- package/src/config.ts +211 -232
- package/src/git.ts +60 -0
- package/src/index.ts +9 -0
- package/src/langfuse.ts +307 -563
- package/src/pi-session.ts +570 -0
- package/src/public-api.ts +17 -0
- package/src/runtime-core.ts +113 -0
- package/src/runtime.ts +393 -227
- package/src/sanitizer.ts +115 -117
- package/src/tracing.ts +828 -843
- package/dist/chunks/runtime-247EMSG3.js +0 -218
- package/dist/chunks/runtime-247EMSG3.js.map +0 -7
package/dist/index.ts
CHANGED
|
@@ -1,232 +1,65 @@
|
|
|
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
|
+
import {
|
|
4
|
+
DEFAULT_BASE_URL,
|
|
5
|
+
createLangfuseRuntimeFromBackend,
|
|
6
|
+
getLangfuseRuntimeInternal,
|
|
7
|
+
loadLangfuseConfig,
|
|
8
|
+
normalizeLangfuseConfig,
|
|
9
|
+
writeLangfuseConfig
|
|
10
|
+
} from "./chunks/chunk-IASLJKKB.js";
|
|
3
11
|
|
|
4
|
-
// src/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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))) {
|
|
12
|
+
// src/git.ts
|
|
13
|
+
var GIT_LOOKUP_TIMEOUT_MS = 1e3;
|
|
14
|
+
var MAX_GIT_BRANCH_LENGTH = 256;
|
|
15
|
+
async function resolveGitMetadata(exec, cwd) {
|
|
16
|
+
const [branchResult, commit] = await Promise.all([resolveGitBranch(exec, cwd), resolveGitCommit(exec, cwd)]);
|
|
17
|
+
if (!branchResult.resolved) return void 0;
|
|
18
|
+
if (branchResult.branch) {
|
|
160
19
|
return {
|
|
161
|
-
|
|
162
|
-
|
|
20
|
+
branch: branchResult.branch,
|
|
21
|
+
...commit ? { commit } : {},
|
|
22
|
+
detached: false
|
|
163
23
|
};
|
|
164
24
|
}
|
|
165
|
-
|
|
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
|
-
};
|
|
25
|
+
return commit ? { commit, detached: true } : void 0;
|
|
184
26
|
}
|
|
185
|
-
async function
|
|
27
|
+
async function resolveGitBranch(exec, cwd) {
|
|
186
28
|
try {
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
} catch
|
|
197
|
-
|
|
198
|
-
const failure = `Failed to enforce 0600 permissions for ${path}: ${formatError(error)}`;
|
|
199
|
-
warnings.push(failure);
|
|
200
|
-
return failure;
|
|
29
|
+
const result = await exec("git", ["symbolic-ref", "--quiet", "--short", "HEAD"], {
|
|
30
|
+
cwd,
|
|
31
|
+
timeout: GIT_LOOKUP_TIMEOUT_MS
|
|
32
|
+
});
|
|
33
|
+
if (result.killed) return { resolved: false };
|
|
34
|
+
if (result.code === 1) return { resolved: true };
|
|
35
|
+
if (result.code !== 0) return { resolved: false };
|
|
36
|
+
const branch = normalizeGitBranch(result.stdout);
|
|
37
|
+
return branch ? { resolved: true, branch } : { resolved: false };
|
|
38
|
+
} catch {
|
|
39
|
+
return { resolved: false };
|
|
201
40
|
}
|
|
202
|
-
return void 0;
|
|
203
41
|
}
|
|
204
|
-
function
|
|
42
|
+
async function resolveGitCommit(exec, cwd) {
|
|
205
43
|
try {
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
44
|
+
const result = await exec("git", ["rev-parse", "--verify", "--short=12", "HEAD"], {
|
|
45
|
+
cwd,
|
|
46
|
+
timeout: GIT_LOOKUP_TIMEOUT_MS
|
|
47
|
+
});
|
|
48
|
+
if (result.code !== 0 || result.killed) return void 0;
|
|
49
|
+
const commit = result.stdout.trim();
|
|
50
|
+
return /^[0-9a-f]{4,64}$/iu.test(commit) ? commit.toLowerCase() : void 0;
|
|
210
51
|
} catch {
|
|
211
52
|
return void 0;
|
|
212
53
|
}
|
|
213
54
|
}
|
|
214
|
-
function
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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);
|
|
55
|
+
function normalizeGitBranch(value) {
|
|
56
|
+
const branch = value.trim();
|
|
57
|
+
if (!branch || branch.length > MAX_GIT_BRANCH_LENGTH) return void 0;
|
|
58
|
+
for (const character of branch) {
|
|
59
|
+
const code = character.codePointAt(0) ?? 0;
|
|
60
|
+
if (code <= 31 || code === 127) return void 0;
|
|
61
|
+
}
|
|
62
|
+
return branch;
|
|
230
63
|
}
|
|
231
64
|
|
|
232
65
|
// src/sanitizer.ts
|
|
@@ -249,9 +82,7 @@ function sanitize(value, active, depth, budget) {
|
|
|
249
82
|
return consume(value, budget);
|
|
250
83
|
}
|
|
251
84
|
if (typeof value === "string") {
|
|
252
|
-
|
|
253
|
-
const redacted = bounded.replace(BASE64_DATA_URI, BASE64_DATA_URI_OMITTED);
|
|
254
|
-
return consume(redacted, budget);
|
|
85
|
+
return consume(sanitizeString(value, Math.min(MAX_STRING_LENGTH, budget.remaining)), budget);
|
|
255
86
|
}
|
|
256
87
|
if (typeof value === "bigint") return consume(value.toString(), budget);
|
|
257
88
|
if (typeof value === "undefined" || typeof value === "function" || typeof value === "symbol") {
|
|
@@ -301,12 +132,13 @@ function sanitize(value, active, depth, budget) {
|
|
|
301
132
|
omitted = true;
|
|
302
133
|
break;
|
|
303
134
|
}
|
|
304
|
-
|
|
135
|
+
const sanitizedKey = sanitizeString(key, keyBudget);
|
|
136
|
+
budget.remaining -= byteLength(sanitizedKey) + 4;
|
|
305
137
|
const property = readProperty(record, key);
|
|
306
138
|
if (redactData && key === "data") {
|
|
307
|
-
output[
|
|
308
|
-
} else if (!property.ok) output[
|
|
309
|
-
else output[
|
|
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);
|
|
310
142
|
processed += 1;
|
|
311
143
|
}
|
|
312
144
|
} catch {
|
|
@@ -338,6 +170,9 @@ function consume(value, budget) {
|
|
|
338
170
|
budget.remaining -= size;
|
|
339
171
|
return value;
|
|
340
172
|
}
|
|
173
|
+
function sanitizeString(value, maxBytes) {
|
|
174
|
+
return truncateString(value, maxBytes).replace(BASE64_DATA_URI, BASE64_DATA_URI_OMITTED);
|
|
175
|
+
}
|
|
341
176
|
function truncateString(value, maxBytes) {
|
|
342
177
|
const boundedPrefix = value.slice(0, maxBytes);
|
|
343
178
|
if (boundedPrefix.length === value.length && byteLength(boundedPrefix) <= maxBytes) {
|
|
@@ -410,11 +245,7 @@ var TraceRecorder = class {
|
|
|
410
245
|
counters = emptyCounters();
|
|
411
246
|
startObservation(name, attributes, options) {
|
|
412
247
|
const { sessionId, userId } = this.context;
|
|
413
|
-
return this.backend.start(
|
|
414
|
-
name,
|
|
415
|
-
{ ...attributes, sessionId, ...userId ? { userId } : {} },
|
|
416
|
-
options
|
|
417
|
-
);
|
|
248
|
+
return this.backend.start(name, { ...attributes, sessionId, ...userId ? { userId } : {} }, options);
|
|
418
249
|
}
|
|
419
250
|
hasActiveTrace() {
|
|
420
251
|
return this.root !== void 0;
|
|
@@ -436,6 +267,7 @@ var TraceRecorder = class {
|
|
|
436
267
|
...hasItems(input.images) ? { images: input.images } : {}
|
|
437
268
|
});
|
|
438
269
|
const metadata = {
|
|
270
|
+
...customTraceMetadata(this.context.metadata),
|
|
439
271
|
"pi.cwd": this.context.cwd,
|
|
440
272
|
...input.git?.branch ? { "pi.git.branch": input.git.branch } : {},
|
|
441
273
|
...input.git?.commit ? { "pi.git.commit": input.git.commit } : {},
|
|
@@ -443,6 +275,7 @@ var TraceRecorder = class {
|
|
|
443
275
|
"pi.mode": this.context.mode,
|
|
444
276
|
...input.model?.id ? { "pi.model": input.model.id } : {},
|
|
445
277
|
...input.model?.provider ? { "pi.provider": input.model.provider } : {},
|
|
278
|
+
...input.requestId ? { "pi.request.id": input.requestId } : {},
|
|
446
279
|
"pi.session.id": this.context.sessionId,
|
|
447
280
|
"pi.trace.schema_version": Number(TRACE_SCHEMA_VERSION),
|
|
448
281
|
...snapshotMetadata("start", input.snapshot)
|
|
@@ -451,14 +284,21 @@ var TraceRecorder = class {
|
|
|
451
284
|
const gitTag = input.git?.branch ? `branch:${input.git.branch}` : input.git?.detached ? "git:detached" : void 0;
|
|
452
285
|
this.root = this.startObservation("pi.agent", attributes, { asType: "agent" });
|
|
453
286
|
this.root.updateTrace?.({
|
|
454
|
-
name:
|
|
287
|
+
name: normalizeTraceName(this.context.traceName),
|
|
455
288
|
sessionId: this.context.sessionId,
|
|
456
289
|
...this.context.userId ? { userId: this.context.userId } : {},
|
|
457
290
|
version: TRACE_SCHEMA_VERSION,
|
|
458
291
|
input: traceInput,
|
|
459
292
|
metadata,
|
|
460
|
-
tags:
|
|
293
|
+
tags: traceTags(this.context.tags, gitTag)
|
|
461
294
|
});
|
|
295
|
+
const traceId = this.root.traceId;
|
|
296
|
+
if (traceId && this.context.onTraceId) {
|
|
297
|
+
try {
|
|
298
|
+
void Promise.resolve(this.context.onTraceId(traceId)).catch(() => void 0);
|
|
299
|
+
} catch {
|
|
300
|
+
}
|
|
301
|
+
}
|
|
462
302
|
}
|
|
463
303
|
beginAttempt(input = {}) {
|
|
464
304
|
if (!this.root) return;
|
|
@@ -548,7 +388,11 @@ var TraceRecorder = class {
|
|
|
548
388
|
...Object.keys(requestMetadata).length > 0 ? { metadata: requestMetadata } : {},
|
|
549
389
|
version: TRACE_SCHEMA_VERSION
|
|
550
390
|
},
|
|
551
|
-
{
|
|
391
|
+
{
|
|
392
|
+
asType: "generation",
|
|
393
|
+
parent: this.turn ?? this.attempt ?? this.root,
|
|
394
|
+
...input.startedAt === void 0 ? {} : { startTime: new Date(input.startedAt) }
|
|
395
|
+
}
|
|
552
396
|
);
|
|
553
397
|
this.generation = {
|
|
554
398
|
observation,
|
|
@@ -682,10 +526,7 @@ var TraceRecorder = class {
|
|
|
682
526
|
metadata: {
|
|
683
527
|
"pi.tool.progress_update_count": tool.progressUpdates,
|
|
684
528
|
...tool.firstProgressAt !== void 0 ? {
|
|
685
|
-
"pi.tool.time_to_first_progress_ms": Math.max(
|
|
686
|
-
0,
|
|
687
|
-
tool.firstProgressAt - tool.startedAt
|
|
688
|
-
)
|
|
529
|
+
"pi.tool.time_to_first_progress_ms": Math.max(0, tool.firstProgressAt - tool.startedAt)
|
|
689
530
|
} : {}
|
|
690
531
|
},
|
|
691
532
|
...result.isError ? { level: "ERROR", statusMessage: "Pi tool execution failed." } : {}
|
|
@@ -753,12 +594,8 @@ var TraceRecorder = class {
|
|
|
753
594
|
interrupt(statusMessage, snapshot) {
|
|
754
595
|
this.closeActiveTrace(statusMessage, snapshot, "interrupted");
|
|
755
596
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
}
|
|
759
|
-
async shutdown(snapshot) {
|
|
760
|
-
this.closeActiveTrace("Pi shut down before the active trace settled.", snapshot, "interrupted");
|
|
761
|
-
await this.backend.shutdown();
|
|
597
|
+
dispose(statusMessage = "Pi session ended before the active trace settled.", snapshot) {
|
|
598
|
+
this.closeActiveTrace(statusMessage, snapshot, "interrupted");
|
|
762
599
|
}
|
|
763
600
|
closeActiveTrace(statusMessage, snapshot, forcedOutcome) {
|
|
764
601
|
this.closeTurn(statusMessage ?? "Pi turn ended when the agent run settled.");
|
|
@@ -850,10 +687,7 @@ var TraceRecorder = class {
|
|
|
850
687
|
metadata: {
|
|
851
688
|
"pi.tool.progress_update_count": tool.progressUpdates,
|
|
852
689
|
...tool.firstProgressAt !== void 0 ? {
|
|
853
|
-
"pi.tool.time_to_first_progress_ms": Math.max(
|
|
854
|
-
0,
|
|
855
|
-
tool.firstProgressAt - tool.startedAt
|
|
856
|
-
)
|
|
690
|
+
"pi.tool.time_to_first_progress_ms": Math.max(0, tool.firstProgressAt - tool.startedAt)
|
|
857
691
|
} : {}
|
|
858
692
|
}
|
|
859
693
|
});
|
|
@@ -884,6 +718,24 @@ var TraceRecorder = class {
|
|
|
884
718
|
return this.context.captureContent ? sanitizeTraceValue(value) : CONTENT_DISABLED;
|
|
885
719
|
}
|
|
886
720
|
};
|
|
721
|
+
function normalizeTraceName(value) {
|
|
722
|
+
return typeof value === "string" && value.trim() ? value.trim() : "pi.trace";
|
|
723
|
+
}
|
|
724
|
+
function traceTags(customTags, gitTag) {
|
|
725
|
+
const tags = ["pi", ...gitTag ? [gitTag] : [], ...customTags ?? []];
|
|
726
|
+
return [...new Set(tags.map(sanitizeTraceTag).filter((tag) => tag !== void 0))];
|
|
727
|
+
}
|
|
728
|
+
function sanitizeTraceTag(value) {
|
|
729
|
+
if (typeof value !== "string" || !value.trim()) return void 0;
|
|
730
|
+
const sanitized = sanitizeTraceValue(value.trim());
|
|
731
|
+
return typeof sanitized === "string" && sanitized.trim() ? sanitized.trim() : void 0;
|
|
732
|
+
}
|
|
733
|
+
function customTraceMetadata(metadata) {
|
|
734
|
+
if (!metadata) return {};
|
|
735
|
+
const sanitized = sanitizeTraceValue(metadata);
|
|
736
|
+
if (!sanitized || typeof sanitized !== "object" || Array.isArray(sanitized)) return {};
|
|
737
|
+
return Object.fromEntries(Object.entries(sanitized).filter(([key]) => !key.startsWith("pi.")));
|
|
738
|
+
}
|
|
887
739
|
function generationHttpMetadata(state) {
|
|
888
740
|
const finalStatus = state.statuses.at(-1);
|
|
889
741
|
return {
|
|
@@ -945,9 +797,7 @@ function severityForOutcome(outcome, statusMessage) {
|
|
|
945
797
|
};
|
|
946
798
|
}
|
|
947
799
|
function prefixRecord(prefix, values) {
|
|
948
|
-
return Object.fromEntries(
|
|
949
|
-
Object.entries(values).map(([key, value]) => [`${prefix}.${key}`, value])
|
|
950
|
-
);
|
|
800
|
+
return Object.fromEntries(Object.entries(values).map(([key, value]) => [`${prefix}.${key}`, value]));
|
|
951
801
|
}
|
|
952
802
|
function truncateString2(value, maxBytes) {
|
|
953
803
|
const boundedPrefix = value.slice(0, maxBytes);
|
|
@@ -976,9 +826,7 @@ function numericRecord(values) {
|
|
|
976
826
|
}
|
|
977
827
|
function positiveNumericRecord(values) {
|
|
978
828
|
return Object.fromEntries(
|
|
979
|
-
Object.entries(values).filter(
|
|
980
|
-
(entry) => finiteNumber(entry[1]) && entry[1] > 0
|
|
981
|
-
)
|
|
829
|
+
Object.entries(values).filter((entry) => finiteNumber(entry[1]) && entry[1] > 0)
|
|
982
830
|
);
|
|
983
831
|
}
|
|
984
832
|
function finiteNumber(value) {
|
|
@@ -1000,235 +848,258 @@ function emptyCounters() {
|
|
|
1000
848
|
};
|
|
1001
849
|
}
|
|
1002
850
|
|
|
1003
|
-
// src/
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
function createLangfuseExtension(dependencies = {}) {
|
|
1009
|
-
const loadConfig = dependencies.loadConfig ?? loadLangfuseConfig;
|
|
1010
|
-
const writeConfig = dependencies.writeConfig ?? writeLangfuseConfig;
|
|
1011
|
-
const createBackend = dependencies.createBackend ?? (async (config) => {
|
|
1012
|
-
const { createProductionBackend } = await import("./chunks/runtime-247EMSG3.js");
|
|
1013
|
-
return createProductionBackend(config);
|
|
851
|
+
// src/pi-session.ts
|
|
852
|
+
function createPiLangfuseSession(runtime, options = {}) {
|
|
853
|
+
const sessionOptions = snapshotSessionOptions(options);
|
|
854
|
+
return createPiLangfuseSessionController({
|
|
855
|
+
resolveSession: async () => ({ runtime, options: sessionOptions })
|
|
1014
856
|
});
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
857
|
+
}
|
|
858
|
+
function createPiLangfuseSessionController(options) {
|
|
859
|
+
let binding;
|
|
860
|
+
let runtimeForShutdown;
|
|
861
|
+
let ownerRegistration;
|
|
862
|
+
let pendingInitialization;
|
|
863
|
+
const outstandingInitializations = /* @__PURE__ */ new Set();
|
|
864
|
+
let disposePromise;
|
|
865
|
+
let disposed = false;
|
|
866
|
+
let sessionGeneration = 0;
|
|
867
|
+
let requestId;
|
|
868
|
+
let requestIdRevision = 0;
|
|
869
|
+
let nextAttemptReason;
|
|
870
|
+
let lastSnapshot;
|
|
871
|
+
let pendingProviderGeneration;
|
|
872
|
+
const controller = {
|
|
873
|
+
extension(pi) {
|
|
874
|
+
if (disposed) throw new Error("A disposed Pi Langfuse session controller cannot be bound.");
|
|
875
|
+
registerHooks(pi);
|
|
876
|
+
},
|
|
877
|
+
setRequestId(value) {
|
|
878
|
+
requestId = normalizeOptionalString(value);
|
|
879
|
+
requestIdRevision += 1;
|
|
880
|
+
},
|
|
881
|
+
dispose() {
|
|
882
|
+
if (disposePromise) return disposePromise;
|
|
883
|
+
disposed = true;
|
|
884
|
+
const initializations = invalidateAllInitializations("disposed");
|
|
885
|
+
ownerRegistration = void 0;
|
|
886
|
+
sessionGeneration += 1;
|
|
887
|
+
closeBinding(binding, "Pi Langfuse session controller was disposed.", lastSnapshot);
|
|
888
|
+
runtimeForShutdown = void 0;
|
|
889
|
+
disposePromise = joinInitializations(initializations);
|
|
890
|
+
return disposePromise;
|
|
891
|
+
},
|
|
892
|
+
get active() {
|
|
893
|
+
return binding !== void 0;
|
|
894
|
+
},
|
|
895
|
+
get runtime() {
|
|
896
|
+
return binding?.runtime;
|
|
897
|
+
},
|
|
898
|
+
async flush() {
|
|
899
|
+
const runtime = binding?.runtime;
|
|
900
|
+
if (!runtime) throw new Error("Langfuse tracing is not enabled for this session.");
|
|
901
|
+
await runtime.flush();
|
|
902
|
+
}
|
|
903
|
+
};
|
|
904
|
+
function closeBinding(target, statusMessage, snapshot) {
|
|
905
|
+
if (!target) return;
|
|
906
|
+
const wasCurrent = binding === target;
|
|
907
|
+
if (wasCurrent) {
|
|
908
|
+
binding = void 0;
|
|
909
|
+
pendingProviderGeneration = void 0;
|
|
910
|
+
}
|
|
911
|
+
target.releaseRuntime();
|
|
912
|
+
target.recorder.dispose(statusMessage, snapshot);
|
|
913
|
+
if (wasCurrent) nextAttemptReason = void 0;
|
|
914
|
+
}
|
|
915
|
+
function invalidatePendingInitialization(reason) {
|
|
916
|
+
if (!pendingInitialization) return;
|
|
917
|
+
pendingInitialization.current = false;
|
|
918
|
+
pendingInitialization.reason = reason;
|
|
919
|
+
pendingInitialization = void 0;
|
|
920
|
+
}
|
|
921
|
+
function invalidateAllInitializations(reason) {
|
|
922
|
+
const initializations = [...outstandingInitializations];
|
|
923
|
+
for (const initialization of initializations) {
|
|
924
|
+
initialization.current = false;
|
|
925
|
+
initialization.reason = reason;
|
|
926
|
+
}
|
|
927
|
+
pendingInitialization = void 0;
|
|
928
|
+
return initializations;
|
|
929
|
+
}
|
|
930
|
+
function createPendingInitialization() {
|
|
931
|
+
let complete;
|
|
932
|
+
const completion = new Promise((resolve) => {
|
|
933
|
+
complete = resolve;
|
|
934
|
+
});
|
|
935
|
+
const initialization = { current: true, completion, complete };
|
|
936
|
+
outstandingInitializations.add(initialization);
|
|
937
|
+
return initialization;
|
|
938
|
+
}
|
|
939
|
+
async function joinInitializations(initializations) {
|
|
940
|
+
const results = await Promise.all(initializations.map(({ completion }) => completion));
|
|
941
|
+
const errors = results.flatMap((result) => result.ok ? [] : [result.error]);
|
|
942
|
+
if (errors.length === 1) throw errors[0];
|
|
943
|
+
if (errors.length > 1) throw new AggregateError(errors, "Pi Langfuse session initialization cleanup failed.");
|
|
944
|
+
}
|
|
945
|
+
async function initializeSession(registration, initialization, ctx) {
|
|
946
|
+
const generation = ++sessionGeneration;
|
|
947
|
+
closeBinding(binding, "Pi session was replaced before shutdown completed.", lastSnapshot);
|
|
948
|
+
lastSnapshot = contextSnapshot(ctx);
|
|
949
|
+
nextAttemptReason = void 0;
|
|
950
|
+
options.onSessionStart?.();
|
|
951
|
+
const isCurrent = () => !disposed && initialization.current && ownerRegistration === registration && generation === sessionGeneration;
|
|
952
|
+
let resolved;
|
|
953
|
+
try {
|
|
954
|
+
resolved = await options.resolveSession(ctx, isCurrent);
|
|
955
|
+
} catch (error) {
|
|
956
|
+
if (isCurrent()) options.onInitializationError?.(error, ctx);
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
if (!isCurrent()) {
|
|
960
|
+
const reason = initialization.reason ?? "replaced";
|
|
961
|
+
if (resolved && options.shutdownRuntimeOnQuit && reason !== "quit" && reason !== "disposed" && (!runtimeForShutdown || runtimeForShutdown.closed)) {
|
|
962
|
+
runtimeForShutdown = resolved.runtime;
|
|
1034
963
|
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
initializationError,
|
|
1050
|
-
configurationNotice
|
|
1051
|
-
).split("\n").slice(1),
|
|
1052
|
-
items: [
|
|
1053
|
-
...recorder ? [{ id: "flush", label: FLUSH_ACTION, action: "flush" }] : [],
|
|
1054
|
-
{
|
|
1055
|
-
id: "configure",
|
|
1056
|
-
label: hasStoredConfig ? UPDATE_ACTION : SET_UP_ACTION,
|
|
1057
|
-
action: "configure"
|
|
1058
|
-
},
|
|
1059
|
-
{ id: "help", label: HELP_ACTION, action: "help" }
|
|
1060
|
-
],
|
|
1061
|
-
hint: "close"
|
|
1062
|
-
})
|
|
1063
|
-
},
|
|
1064
|
-
actions: {
|
|
1065
|
-
flush: async () => {
|
|
1066
|
-
const menuRecorder = recorder;
|
|
1067
|
-
const menuConfig = activeConfig;
|
|
1068
|
-
if (!menuRecorder) {
|
|
1069
|
-
ctx.ui.notify("Langfuse tracing is not enabled for this session.", "warning");
|
|
1070
|
-
return { kind: "close" };
|
|
1071
|
-
}
|
|
1072
|
-
try {
|
|
1073
|
-
await menuRecorder.flush();
|
|
1074
|
-
if (menuGeneration !== sessionGeneration) return { kind: "close" };
|
|
1075
|
-
ctx.ui.notify("Langfuse traces flushed for this session.", "info");
|
|
1076
|
-
} catch (error) {
|
|
1077
|
-
if (menuGeneration !== sessionGeneration) return { kind: "close" };
|
|
1078
|
-
ctx.ui.notify(`Langfuse flush failed: ${formatError2(error, menuConfig)}`, "error");
|
|
1079
|
-
}
|
|
1080
|
-
return { kind: "close" };
|
|
1081
|
-
},
|
|
1082
|
-
configure: async () => {
|
|
1083
|
-
const loaded = await loadConfig(configPath);
|
|
1084
|
-
if (menuGeneration !== sessionGeneration) return { kind: "close" };
|
|
1085
|
-
configPath = loaded.path;
|
|
1086
|
-
const next = await promptForConfig(
|
|
1087
|
-
ctx,
|
|
1088
|
-
loaded.ok ? loaded.config : void 0,
|
|
1089
|
-
() => menuGeneration === sessionGeneration
|
|
1090
|
-
);
|
|
1091
|
-
if (!next || menuGeneration !== sessionGeneration) return { kind: "close" };
|
|
1092
|
-
try {
|
|
1093
|
-
const write = configWriteQueue.then(() => writeConfig(next, loaded.path));
|
|
1094
|
-
configWriteQueue = write.then(
|
|
1095
|
-
() => void 0,
|
|
1096
|
-
() => void 0
|
|
1097
|
-
);
|
|
1098
|
-
await write;
|
|
1099
|
-
if (menuGeneration !== sessionGeneration) return { kind: "close" };
|
|
1100
|
-
hasStoredConfig = true;
|
|
1101
|
-
configurationNotice = "Saved; restart each Pi process to use it in subsequent sessions.";
|
|
1102
|
-
ctx.ui.notify(
|
|
1103
|
-
`Saved Langfuse config to ${loaded.path} for this Pi agent directory. Restart each Pi process to apply it to subsequent sessions.`,
|
|
1104
|
-
"info"
|
|
1105
|
-
);
|
|
1106
|
-
} catch (error) {
|
|
1107
|
-
if (menuGeneration !== sessionGeneration) return { kind: "close" };
|
|
1108
|
-
ctx.ui.notify(`Failed to save Langfuse config: ${formatError2(error, next)}`, "error");
|
|
1109
|
-
}
|
|
1110
|
-
return { kind: "close" };
|
|
1111
|
-
},
|
|
1112
|
-
help: async () => {
|
|
1113
|
-
ctx.ui.notify(formatHelp(configPath), "info");
|
|
1114
|
-
return { kind: "close" };
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
});
|
|
1118
|
-
await runMenu(ctx, menu, {
|
|
1119
|
-
getState: () => void 0,
|
|
1120
|
-
signal: menuSignal,
|
|
1121
|
-
isCurrent
|
|
964
|
+
await resolved?.releaseIfStale?.(reason);
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
if (!resolved) {
|
|
968
|
+
options.onSessionUnavailable?.();
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
try {
|
|
972
|
+
const internal = getLangfuseRuntimeInternal(resolved.runtime);
|
|
973
|
+
const recorderOptions = createRecorderOptions(ctx, resolved.options);
|
|
974
|
+
const recorder = new TraceRecorder(internal.backend, recorderOptions);
|
|
975
|
+
const nextBinding = {};
|
|
976
|
+
const releaseRuntime = internal.registerSession((statusMessage) => {
|
|
977
|
+
closeBinding(nextBinding, statusMessage, lastSnapshot);
|
|
1122
978
|
});
|
|
979
|
+
Object.assign(nextBinding, { registration, recorder, runtime: resolved.runtime, releaseRuntime });
|
|
980
|
+
if (!isCurrent()) {
|
|
981
|
+
releaseRuntime();
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
binding = nextBinding;
|
|
985
|
+
if (options.shutdownRuntimeOnQuit) runtimeForShutdown = resolved.runtime;
|
|
986
|
+
options.onSessionReady?.(resolved);
|
|
987
|
+
} catch (error) {
|
|
988
|
+
if (isCurrent()) options.onInitializationError?.(error, ctx);
|
|
1123
989
|
}
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
990
|
+
}
|
|
991
|
+
function registerHooks(pi) {
|
|
992
|
+
const registration = {};
|
|
993
|
+
const activeRecorder = () => activeRecorderFor(registration);
|
|
1128
994
|
pi.on("session_start", async (_event, ctx) => {
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
hasStoredConfig = false;
|
|
1137
|
-
configurationNotice = void 0;
|
|
1138
|
-
nextAttemptReason = void 0;
|
|
1139
|
-
await configWriteQueue;
|
|
1140
|
-
if (generation !== sessionGeneration) return;
|
|
1141
|
-
const result = await loadConfig();
|
|
1142
|
-
if (generation !== sessionGeneration) return;
|
|
1143
|
-
configPath = result.path;
|
|
1144
|
-
for (const warning of result.warnings) ctx.ui.notify(warning, "warning");
|
|
1145
|
-
if (!result.ok) {
|
|
1146
|
-
initializationError = formatConfigError(result);
|
|
1147
|
-
ctx.ui.notify(initializationError, "warning");
|
|
995
|
+
if (disposed) return;
|
|
996
|
+
pendingProviderGeneration = void 0;
|
|
997
|
+
if (ownerRegistration && ownerRegistration !== registration) {
|
|
998
|
+
options.onInitializationError?.(
|
|
999
|
+
new Error("A Pi Langfuse session controller cannot manage multiple active Pi sessions."),
|
|
1000
|
+
ctx
|
|
1001
|
+
);
|
|
1148
1002
|
return;
|
|
1149
1003
|
}
|
|
1150
|
-
|
|
1004
|
+
invalidatePendingInitialization("replaced");
|
|
1005
|
+
ownerRegistration = registration;
|
|
1006
|
+
const initialization = createPendingInitialization();
|
|
1007
|
+
pendingInitialization = initialization;
|
|
1008
|
+
let result = { ok: true };
|
|
1151
1009
|
try {
|
|
1152
|
-
|
|
1153
|
-
if (generation !== sessionGeneration) {
|
|
1154
|
-
await backend.shutdown().catch(() => void 0);
|
|
1155
|
-
return;
|
|
1156
|
-
}
|
|
1157
|
-
activeConfig = result.config;
|
|
1158
|
-
recorder = new TraceRecorder(backend, {
|
|
1159
|
-
sessionId: ctx.sessionManager.getSessionId(),
|
|
1160
|
-
...result.config.userId ? { userId: result.config.userId } : {},
|
|
1161
|
-
cwd: ctx.cwd,
|
|
1162
|
-
mode: ctx.mode,
|
|
1163
|
-
captureContent: result.config.captureContent
|
|
1164
|
-
});
|
|
1010
|
+
await initializeSession(registration, initialization, ctx);
|
|
1165
1011
|
} catch (error) {
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1012
|
+
result = { ok: false, error };
|
|
1013
|
+
throw error;
|
|
1014
|
+
} finally {
|
|
1015
|
+
initialization.complete(result);
|
|
1016
|
+
outstandingInitializations.delete(initialization);
|
|
1017
|
+
if (pendingInitialization === initialization) pendingInitialization = void 0;
|
|
1169
1018
|
}
|
|
1170
1019
|
});
|
|
1171
1020
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
1021
|
+
pendingProviderGeneration = void 0;
|
|
1022
|
+
const active = ownerRegistration === registration ? binding : void 0;
|
|
1023
|
+
if (!active || active.runtime.closed) return;
|
|
1172
1024
|
nextAttemptReason = void 0;
|
|
1173
|
-
const
|
|
1174
|
-
if (
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1025
|
+
const git = await (options.resolveGitMetadata ? options.resolveGitMetadata(pi, ctx.cwd) : resolveGitMetadata((command, args, execOptions) => pi.exec(command, args, execOptions), ctx.cwd)).catch(() => void 0);
|
|
1026
|
+
if (ownerRegistration !== registration || binding !== active || active.runtime.closed) return;
|
|
1027
|
+
lastSnapshot = contextSnapshot(ctx);
|
|
1028
|
+
startRootTrace((nextRequestId) => {
|
|
1029
|
+
active.recorder.beginAgent({
|
|
1030
|
+
prompt: event.prompt,
|
|
1031
|
+
images: event.images,
|
|
1032
|
+
model: ctx.model ? { provider: ctx.model.provider, id: ctx.model.id, api: ctx.model.api } : void 0,
|
|
1033
|
+
git,
|
|
1034
|
+
snapshot: lastSnapshot,
|
|
1035
|
+
...nextRequestId ? { requestId: nextRequestId } : {}
|
|
1036
|
+
});
|
|
1183
1037
|
});
|
|
1184
1038
|
});
|
|
1185
1039
|
pi.on("agent_start", () => {
|
|
1040
|
+
const recorder = activeRecorder();
|
|
1186
1041
|
if (!recorder) return;
|
|
1187
1042
|
recorder.beginAttempt(nextAttemptReason ? { reason: nextAttemptReason } : void 0);
|
|
1188
1043
|
nextAttemptReason = void 0;
|
|
1189
1044
|
});
|
|
1190
1045
|
pi.on("turn_start", (event, ctx) => {
|
|
1046
|
+
const recorder = activeRecorder();
|
|
1191
1047
|
if (!recorder) return;
|
|
1192
|
-
|
|
1048
|
+
lastSnapshot = contextSnapshot(ctx);
|
|
1049
|
+
ensureActiveRun(recorder, ctx, startRootTrace);
|
|
1193
1050
|
recorder.beginTurn(event.turnIndex);
|
|
1194
1051
|
});
|
|
1195
1052
|
pi.on("before_provider_request", (event, ctx) => {
|
|
1196
|
-
if (!
|
|
1197
|
-
|
|
1198
|
-
|
|
1053
|
+
if (!activeRecorder()) return;
|
|
1054
|
+
lastSnapshot = contextSnapshot(ctx);
|
|
1055
|
+
pendingProviderGeneration = {
|
|
1056
|
+
startedAt: Date.now(),
|
|
1199
1057
|
payload: event.payload,
|
|
1200
|
-
payloadStage: "before_provider_request",
|
|
1201
1058
|
model: ctx.model ? { provider: ctx.model.provider, id: ctx.model.id, api: ctx.model.api } : void 0,
|
|
1202
|
-
thinkingLevel: pi.getThinkingLevel()
|
|
1203
|
-
|
|
1059
|
+
thinkingLevel: pi.getThinkingLevel(),
|
|
1060
|
+
responses: []
|
|
1061
|
+
};
|
|
1204
1062
|
});
|
|
1205
1063
|
pi.on("after_provider_response", (event) => {
|
|
1206
|
-
|
|
1064
|
+
pendingProviderGeneration?.responses.push({ status: event.status, headers: event.headers });
|
|
1065
|
+
});
|
|
1066
|
+
pi.on("message_start", (event, ctx) => {
|
|
1067
|
+
if (event.message.role === "assistant") claimPendingProviderGeneration(ctx, activeRecorder());
|
|
1207
1068
|
});
|
|
1208
|
-
pi.on("message_update", (event) => {
|
|
1209
|
-
if (isRealOutputDelta(event.assistantMessageEvent))
|
|
1069
|
+
pi.on("message_update", (event, ctx) => {
|
|
1070
|
+
if (!isRealOutputDelta(event.assistantMessageEvent)) return;
|
|
1071
|
+
const recorder = activeRecorder();
|
|
1072
|
+
claimPendingProviderGeneration(ctx, recorder);
|
|
1073
|
+
recorder?.markGenerationFirstOutput();
|
|
1210
1074
|
});
|
|
1211
|
-
pi.on("message_end", (event) => {
|
|
1212
|
-
if (event.message.role
|
|
1075
|
+
pi.on("message_end", (event, ctx) => {
|
|
1076
|
+
if (event.message.role !== "assistant") return;
|
|
1077
|
+
const recorder = activeRecorder();
|
|
1078
|
+
claimPendingProviderGeneration(ctx, recorder);
|
|
1079
|
+
recorder?.markGenerationEnd();
|
|
1213
1080
|
});
|
|
1214
|
-
pi.on("turn_end", (event) => {
|
|
1215
|
-
|
|
1216
|
-
recorder
|
|
1081
|
+
pi.on("turn_end", (event, ctx) => {
|
|
1082
|
+
const recorder = activeRecorder();
|
|
1083
|
+
if (!recorder) return;
|
|
1084
|
+
if (event.message.role === "assistant") claimPendingProviderGeneration(ctx, recorder);
|
|
1085
|
+
lastSnapshot = contextSnapshot(ctx);
|
|
1086
|
+
if (event.message.role === "assistant") recorder.finishAssistant(event.message);
|
|
1087
|
+
recorder.finishTurn(event.turnIndex, {
|
|
1217
1088
|
message: event.message,
|
|
1218
1089
|
toolResultCount: event.toolResults.length
|
|
1219
1090
|
});
|
|
1220
1091
|
});
|
|
1221
1092
|
pi.on("tool_execution_start", (event) => {
|
|
1222
|
-
|
|
1093
|
+
activeRecorder()?.beginTool(event.toolCallId, event.toolName, event.args);
|
|
1223
1094
|
});
|
|
1224
1095
|
pi.on("tool_execution_update", (event) => {
|
|
1225
|
-
|
|
1096
|
+
activeRecorder()?.recordToolProgress(event.toolCallId);
|
|
1226
1097
|
});
|
|
1227
1098
|
pi.on("tool_result", (event) => {
|
|
1228
|
-
|
|
1099
|
+
activeRecorder()?.recordToolInput(event.toolCallId, event.input);
|
|
1229
1100
|
});
|
|
1230
1101
|
pi.on("tool_execution_end", (event) => {
|
|
1231
|
-
|
|
1102
|
+
activeRecorder()?.finishTool(event.toolCallId, {
|
|
1232
1103
|
content: event.result.content,
|
|
1233
1104
|
details: event.result.details,
|
|
1234
1105
|
isError: event.isError
|
|
@@ -1236,10 +1107,10 @@ function createLangfuseExtension(dependencies = {}) {
|
|
|
1236
1107
|
});
|
|
1237
1108
|
pi.on("agent_end", (event) => {
|
|
1238
1109
|
const message = findLastAssistant(event.messages);
|
|
1239
|
-
|
|
1110
|
+
activeRecorder()?.finishAttempt(message);
|
|
1240
1111
|
});
|
|
1241
1112
|
pi.on("session_before_compact", (event) => {
|
|
1242
|
-
|
|
1113
|
+
activeRecorder()?.beginCompaction({
|
|
1243
1114
|
reason: event.reason,
|
|
1244
1115
|
willRetry: event.willRetry,
|
|
1245
1116
|
tokensBefore: event.preparation.tokensBefore,
|
|
@@ -1250,6 +1121,7 @@ function createLangfuseExtension(dependencies = {}) {
|
|
|
1250
1121
|
});
|
|
1251
1122
|
});
|
|
1252
1123
|
pi.on("session_compact", (event) => {
|
|
1124
|
+
const recorder = activeRecorder();
|
|
1253
1125
|
const entry = event.compactionEntry;
|
|
1254
1126
|
recorder?.finishCompaction({
|
|
1255
1127
|
reason: event.reason,
|
|
@@ -1262,45 +1134,102 @@ function createLangfuseExtension(dependencies = {}) {
|
|
|
1262
1134
|
if (event.willRetry && recorder?.hasActiveTrace()) nextAttemptReason = "post_compaction";
|
|
1263
1135
|
});
|
|
1264
1136
|
pi.on("agent_settled", (_event, ctx) => {
|
|
1265
|
-
recorder
|
|
1137
|
+
const recorder = activeRecorder();
|
|
1138
|
+
if (!recorder) return;
|
|
1139
|
+
lastSnapshot = contextSnapshot(ctx);
|
|
1140
|
+
recorder.settle(lastSnapshot);
|
|
1141
|
+
pendingProviderGeneration = void 0;
|
|
1266
1142
|
nextAttemptReason = void 0;
|
|
1267
1143
|
});
|
|
1268
1144
|
pi.on("session_shutdown", async (event, ctx) => {
|
|
1145
|
+
if (ownerRegistration !== registration) return;
|
|
1146
|
+
const initializations = invalidateAllInitializations(event.reason);
|
|
1147
|
+
ownerRegistration = void 0;
|
|
1269
1148
|
const generation = ++sessionGeneration;
|
|
1270
|
-
|
|
1271
|
-
const
|
|
1272
|
-
const
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1149
|
+
options.onSessionShutdown?.();
|
|
1150
|
+
const active = binding?.registration === registration ? binding : void 0;
|
|
1151
|
+
const runtime = active?.runtime ?? runtimeForShutdown;
|
|
1152
|
+
lastSnapshot = active ? contextSnapshot(ctx) : lastSnapshot;
|
|
1153
|
+
closeBinding(
|
|
1154
|
+
active,
|
|
1155
|
+
event.reason === "quit" ? "Pi shut down before the active trace settled." : `Pi session ended before settlement (${event.reason}).`,
|
|
1156
|
+
lastSnapshot
|
|
1157
|
+
);
|
|
1158
|
+
let beforeDisposeFailure;
|
|
1279
1159
|
try {
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1160
|
+
await options.beforeSessionDispose?.();
|
|
1161
|
+
} catch (error) {
|
|
1162
|
+
beforeDisposeFailure = { error };
|
|
1163
|
+
}
|
|
1164
|
+
try {
|
|
1165
|
+
await joinInitializations(initializations);
|
|
1166
|
+
if (disposed || generation !== sessionGeneration) return;
|
|
1167
|
+
if (beforeDisposeFailure) throw beforeDisposeFailure.error;
|
|
1168
|
+
if (runtime) {
|
|
1169
|
+
if (event.reason === "quit" && options.shutdownRuntimeOnQuit) await runtime.shutdown();
|
|
1170
|
+
else if (options.flushOnReplacement) await runtime.flush();
|
|
1287
1171
|
}
|
|
1288
1172
|
} catch (error) {
|
|
1289
|
-
if (generation
|
|
1290
|
-
ctx.ui.notify(
|
|
1291
|
-
`Langfuse shutdown export failed: ${formatError2(error, shutdownConfig)}`,
|
|
1292
|
-
"error"
|
|
1293
|
-
);
|
|
1173
|
+
if (!disposed && generation === sessionGeneration) options.onShutdownError?.(error, ctx);
|
|
1294
1174
|
}
|
|
1295
1175
|
});
|
|
1176
|
+
}
|
|
1177
|
+
function claimPendingProviderGeneration(ctx, recorder) {
|
|
1178
|
+
const pending = pendingProviderGeneration;
|
|
1179
|
+
if (!pending || !recorder) return;
|
|
1180
|
+
pendingProviderGeneration = void 0;
|
|
1181
|
+
ensureActiveRun(recorder, ctx, startRootTrace);
|
|
1182
|
+
recorder.beginGeneration({
|
|
1183
|
+
startedAt: pending.startedAt,
|
|
1184
|
+
payload: pending.payload,
|
|
1185
|
+
payloadStage: "before_provider_request",
|
|
1186
|
+
model: pending.model,
|
|
1187
|
+
thinkingLevel: pending.thinkingLevel
|
|
1188
|
+
});
|
|
1189
|
+
for (const response of pending.responses) recorder.recordProviderResponse(response.status, response.headers);
|
|
1190
|
+
}
|
|
1191
|
+
function startRootTrace(start) {
|
|
1192
|
+
const revision = requestIdRevision;
|
|
1193
|
+
const nextRequestId = requestId;
|
|
1194
|
+
start(nextRequestId);
|
|
1195
|
+
if (requestIdRevision === revision) requestId = void 0;
|
|
1196
|
+
}
|
|
1197
|
+
function activeRecorderFor(registration) {
|
|
1198
|
+
return ownerRegistration === registration && binding?.registration === registration && !binding.runtime.closed ? binding.recorder : void 0;
|
|
1199
|
+
}
|
|
1200
|
+
return controller;
|
|
1201
|
+
}
|
|
1202
|
+
function createRecorderOptions(ctx, options = {}) {
|
|
1203
|
+
const userId = normalizeUserId(options.userId);
|
|
1204
|
+
return {
|
|
1205
|
+
sessionId: normalizeOptionalString(options.sessionId) ?? ctx.sessionManager.getSessionId(),
|
|
1206
|
+
...userId ? { userId } : {},
|
|
1207
|
+
cwd: ctx.cwd,
|
|
1208
|
+
mode: ctx.mode,
|
|
1209
|
+
captureContent: options.captureContent ?? true,
|
|
1210
|
+
...normalizeOptionalString(options.traceName) ? { traceName: normalizeOptionalString(options.traceName) } : {},
|
|
1211
|
+
...options.tags ? { tags: [...options.tags] } : {},
|
|
1212
|
+
...options.metadata ? { metadata: { ...options.metadata } } : {},
|
|
1213
|
+
...options.onTraceId ? { onTraceId: options.onTraceId } : {}
|
|
1296
1214
|
};
|
|
1297
1215
|
}
|
|
1298
|
-
function
|
|
1216
|
+
function snapshotSessionOptions(options) {
|
|
1217
|
+
normalizeUserId(options.userId);
|
|
1218
|
+
return {
|
|
1219
|
+
...options,
|
|
1220
|
+
...options.tags ? { tags: [...options.tags] } : {},
|
|
1221
|
+
...options.metadata ? { metadata: { ...options.metadata } } : {}
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
function ensureActiveRun(recorder, ctx, startRootTrace) {
|
|
1299
1225
|
if (!recorder.hasActiveTrace()) {
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1226
|
+
startRootTrace((nextRequestId) => {
|
|
1227
|
+
recorder.beginAgent({
|
|
1228
|
+
prompt: "[automatic continuation]",
|
|
1229
|
+
model: ctx.model ? { provider: ctx.model.provider, id: ctx.model.id, api: ctx.model.api } : void 0,
|
|
1230
|
+
snapshot: contextSnapshot(ctx),
|
|
1231
|
+
...nextRequestId ? { requestId: nextRequestId } : {}
|
|
1232
|
+
});
|
|
1304
1233
|
});
|
|
1305
1234
|
}
|
|
1306
1235
|
if (!recorder.hasActiveAttempt()) recorder.beginAttempt();
|
|
@@ -1321,59 +1250,200 @@ function findLastAssistant(messages) {
|
|
|
1321
1250
|
function isRealOutputDelta(event) {
|
|
1322
1251
|
return (event.type === "text_delta" || event.type === "thinking_delta" || event.type === "toolcall_delta") && typeof event.delta === "string" && event.delta.length > 0;
|
|
1323
1252
|
}
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
resolveGitBranch(exec, cwd),
|
|
1329
|
-
resolveGitCommit(exec, cwd)
|
|
1330
|
-
]);
|
|
1331
|
-
if (!branchResult.resolved) return void 0;
|
|
1332
|
-
if (branchResult.branch) {
|
|
1333
|
-
return {
|
|
1334
|
-
branch: branchResult.branch,
|
|
1335
|
-
...commit ? { commit } : {},
|
|
1336
|
-
detached: false
|
|
1337
|
-
};
|
|
1338
|
-
}
|
|
1339
|
-
return commit ? { commit, detached: true } : void 0;
|
|
1253
|
+
function normalizeUserId(value) {
|
|
1254
|
+
const userId = normalizeOptionalString(value);
|
|
1255
|
+
if (userId && userId.length > 200) throw new Error("Langfuse userId must be at most 200 characters.");
|
|
1256
|
+
return userId;
|
|
1340
1257
|
}
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
timeout: GIT_LOOKUP_TIMEOUT_MS
|
|
1346
|
-
});
|
|
1347
|
-
if (result.killed) return { resolved: false };
|
|
1348
|
-
if (result.code === 1) return { resolved: true };
|
|
1349
|
-
if (result.code !== 0) return { resolved: false };
|
|
1350
|
-
const branch = normalizeGitBranch(result.stdout);
|
|
1351
|
-
return branch ? { resolved: true, branch } : { resolved: false };
|
|
1352
|
-
} catch {
|
|
1353
|
-
return { resolved: false };
|
|
1354
|
-
}
|
|
1258
|
+
function normalizeOptionalString(value) {
|
|
1259
|
+
if (typeof value !== "string" || !value.trim()) return void 0;
|
|
1260
|
+
const sanitized = sanitizeTraceValue(value.trim());
|
|
1261
|
+
return typeof sanitized === "string" && sanitized.trim() ? sanitized.trim() : void 0;
|
|
1355
1262
|
}
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1263
|
+
|
|
1264
|
+
// src/langfuse.ts
|
|
1265
|
+
var FLUSH_ACTION = "Flush completed traces for this session";
|
|
1266
|
+
var SET_UP_ACTION = "Set up Langfuse for this Pi agent directory (restart required)";
|
|
1267
|
+
var UPDATE_ACTION = "Update Langfuse for this Pi agent directory (restart required)";
|
|
1268
|
+
var HELP_ACTION = "Show setup and privacy help";
|
|
1269
|
+
function createLangfuseExtension(dependencies = {}) {
|
|
1270
|
+
const loadConfig = dependencies.loadConfig ?? loadLangfuseConfig;
|
|
1271
|
+
const writeConfig = dependencies.writeConfig ?? writeLangfuseConfig;
|
|
1272
|
+
const resolveInjectedGitMetadata = dependencies.resolveGitMetadata;
|
|
1273
|
+
return function langfuse(pi) {
|
|
1274
|
+
let activeConfig;
|
|
1275
|
+
let loadingConfig;
|
|
1276
|
+
let runtimeConfigForShutdown;
|
|
1277
|
+
let shutdownConfig;
|
|
1278
|
+
let configPath;
|
|
1279
|
+
let initializationError;
|
|
1280
|
+
let hasStoredConfig = false;
|
|
1281
|
+
let configurationNotice;
|
|
1282
|
+
let sessionGeneration = 0;
|
|
1283
|
+
let menuController = new AbortController();
|
|
1284
|
+
let configWriteQueue = Promise.resolve();
|
|
1285
|
+
const tracing = createPiLangfuseSessionController({
|
|
1286
|
+
onSessionStart() {
|
|
1287
|
+
sessionGeneration += 1;
|
|
1288
|
+
menuController.abort(new DOMException("Langfuse session replaced", "AbortError"));
|
|
1289
|
+
menuController = new AbortController();
|
|
1290
|
+
activeConfig = void 0;
|
|
1291
|
+
loadingConfig = void 0;
|
|
1292
|
+
shutdownConfig = void 0;
|
|
1293
|
+
configPath = void 0;
|
|
1294
|
+
initializationError = void 0;
|
|
1295
|
+
hasStoredConfig = false;
|
|
1296
|
+
configurationNotice = void 0;
|
|
1297
|
+
},
|
|
1298
|
+
onSessionShutdown() {
|
|
1299
|
+
sessionGeneration += 1;
|
|
1300
|
+
menuController.abort(new DOMException("Langfuse session shut down", "AbortError"));
|
|
1301
|
+
shutdownConfig = activeConfig ?? runtimeConfigForShutdown ?? loadingConfig;
|
|
1302
|
+
activeConfig = void 0;
|
|
1303
|
+
},
|
|
1304
|
+
async resolveSession(ctx, isCurrent) {
|
|
1305
|
+
await configWriteQueue;
|
|
1306
|
+
if (!isCurrent()) return void 0;
|
|
1307
|
+
const result = await loadConfig();
|
|
1308
|
+
if (!isCurrent()) return void 0;
|
|
1309
|
+
configPath = result.path;
|
|
1310
|
+
for (const warning of result.warnings) ctx.ui.notify(warning, "warning");
|
|
1311
|
+
if (!result.ok) {
|
|
1312
|
+
initializationError = formatConfigError(result);
|
|
1313
|
+
ctx.ui.notify(initializationError, "warning");
|
|
1314
|
+
return void 0;
|
|
1315
|
+
}
|
|
1316
|
+
hasStoredConfig = true;
|
|
1317
|
+
loadingConfig = result.config;
|
|
1318
|
+
const createBackend = dependencies.createBackend;
|
|
1319
|
+
const ownsRuntime = createBackend !== void 0;
|
|
1320
|
+
const runtime = createBackend ? createLangfuseRuntimeFromBackend(await createBackend(result.config)) : await (await import("./chunks/runtime-6MIKLWNJ.js")).createLangfuseRuntime({ config: result.config, env: false });
|
|
1321
|
+
if (isCurrent() || !runtimeConfigForShutdown) runtimeConfigForShutdown = result.config;
|
|
1322
|
+
if (isCurrent()) {
|
|
1323
|
+
activeConfig = result.config;
|
|
1324
|
+
loadingConfig = void 0;
|
|
1325
|
+
}
|
|
1326
|
+
return {
|
|
1327
|
+
runtime,
|
|
1328
|
+
releaseIfStale: async (reason) => {
|
|
1329
|
+
if (ownsRuntime || reason === "quit") await runtime.shutdown();
|
|
1330
|
+
},
|
|
1331
|
+
options: {
|
|
1332
|
+
...result.config.userId ? { userId: result.config.userId } : {},
|
|
1333
|
+
captureContent: result.config.captureContent
|
|
1334
|
+
}
|
|
1335
|
+
};
|
|
1336
|
+
},
|
|
1337
|
+
...resolveInjectedGitMetadata ? { resolveGitMetadata: (_pi, cwd) => resolveInjectedGitMetadata(cwd) } : {},
|
|
1338
|
+
onInitializationError(error, ctx) {
|
|
1339
|
+
const config = loadingConfig;
|
|
1340
|
+
loadingConfig = void 0;
|
|
1341
|
+
initializationError = `Langfuse tracing could not start: ${formatError(error, config)}`;
|
|
1342
|
+
ctx.ui.notify(initializationError, "warning");
|
|
1343
|
+
},
|
|
1344
|
+
beforeSessionDispose: async () => {
|
|
1345
|
+
await configWriteQueue;
|
|
1346
|
+
},
|
|
1347
|
+
onShutdownError(error, ctx) {
|
|
1348
|
+
ctx.ui.notify(`Langfuse shutdown export failed: ${formatError(error, shutdownConfig)}`, "error");
|
|
1349
|
+
},
|
|
1350
|
+
flushOnReplacement: true,
|
|
1351
|
+
shutdownRuntimeOnQuit: true
|
|
1361
1352
|
});
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1353
|
+
async function showLangfuseMenu(ctx) {
|
|
1354
|
+
if (!ctx.hasUI) {
|
|
1355
|
+
ctx.ui.notify(formatNonInteractiveStatus(activeConfig, configPath, initializationError), "warning");
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
const menuGeneration = sessionGeneration;
|
|
1359
|
+
const menuSignal = menuController.signal;
|
|
1360
|
+
const isCurrent = () => menuGeneration === sessionGeneration && !menuSignal.aborted;
|
|
1361
|
+
const { defineMenu, runMenu } = await import("@narumitw/pi-tui-kit");
|
|
1362
|
+
if (!isCurrent()) return;
|
|
1363
|
+
const menu = defineMenu({
|
|
1364
|
+
start: "main",
|
|
1365
|
+
screens: {
|
|
1366
|
+
main: () => ({
|
|
1367
|
+
kind: "actions",
|
|
1368
|
+
title: "Langfuse",
|
|
1369
|
+
lines: formatMenuTitle(activeConfig, configPath, initializationError, configurationNotice).split("\n").slice(1),
|
|
1370
|
+
items: [
|
|
1371
|
+
...tracing.active ? [{ id: "flush", label: FLUSH_ACTION, action: "flush" }] : [],
|
|
1372
|
+
{
|
|
1373
|
+
id: "configure",
|
|
1374
|
+
label: hasStoredConfig ? UPDATE_ACTION : SET_UP_ACTION,
|
|
1375
|
+
action: "configure"
|
|
1376
|
+
},
|
|
1377
|
+
{ id: "help", label: HELP_ACTION, action: "help" }
|
|
1378
|
+
],
|
|
1379
|
+
hint: "close"
|
|
1380
|
+
})
|
|
1381
|
+
},
|
|
1382
|
+
actions: {
|
|
1383
|
+
flush: async () => {
|
|
1384
|
+
const menuConfig = activeConfig;
|
|
1385
|
+
if (!tracing.active) {
|
|
1386
|
+
ctx.ui.notify("Langfuse tracing is not enabled for this session.", "warning");
|
|
1387
|
+
return { kind: "close" };
|
|
1388
|
+
}
|
|
1389
|
+
try {
|
|
1390
|
+
await tracing.flush();
|
|
1391
|
+
if (menuGeneration !== sessionGeneration) return { kind: "close" };
|
|
1392
|
+
ctx.ui.notify("Langfuse traces flushed for this session.", "info");
|
|
1393
|
+
} catch (error) {
|
|
1394
|
+
if (menuGeneration !== sessionGeneration) return { kind: "close" };
|
|
1395
|
+
ctx.ui.notify(`Langfuse flush failed: ${formatError(error, menuConfig)}`, "error");
|
|
1396
|
+
}
|
|
1397
|
+
return { kind: "close" };
|
|
1398
|
+
},
|
|
1399
|
+
configure: async () => {
|
|
1400
|
+
const loaded = await loadConfig(configPath);
|
|
1401
|
+
if (menuGeneration !== sessionGeneration) return { kind: "close" };
|
|
1402
|
+
configPath = loaded.path;
|
|
1403
|
+
const next = await promptForConfig(
|
|
1404
|
+
ctx,
|
|
1405
|
+
loaded.ok ? loaded.config : void 0,
|
|
1406
|
+
() => menuGeneration === sessionGeneration
|
|
1407
|
+
);
|
|
1408
|
+
if (!next || menuGeneration !== sessionGeneration) return { kind: "close" };
|
|
1409
|
+
try {
|
|
1410
|
+
const write = configWriteQueue.then(() => writeConfig(next, loaded.path));
|
|
1411
|
+
configWriteQueue = write.then(
|
|
1412
|
+
() => void 0,
|
|
1413
|
+
() => void 0
|
|
1414
|
+
);
|
|
1415
|
+
await write;
|
|
1416
|
+
if (menuGeneration !== sessionGeneration) return { kind: "close" };
|
|
1417
|
+
hasStoredConfig = true;
|
|
1418
|
+
configurationNotice = "Saved; restart each Pi process to use it in subsequent sessions.";
|
|
1419
|
+
ctx.ui.notify(
|
|
1420
|
+
`Saved Langfuse config to ${loaded.path} for this Pi agent directory. Restart each Pi process to apply it to subsequent sessions.`,
|
|
1421
|
+
"info"
|
|
1422
|
+
);
|
|
1423
|
+
} catch (error) {
|
|
1424
|
+
if (menuGeneration !== sessionGeneration) return { kind: "close" };
|
|
1425
|
+
ctx.ui.notify(`Failed to save Langfuse config: ${formatError(error, next)}`, "error");
|
|
1426
|
+
}
|
|
1427
|
+
return { kind: "close" };
|
|
1428
|
+
},
|
|
1429
|
+
help: async () => {
|
|
1430
|
+
ctx.ui.notify(formatHelp(configPath), "info");
|
|
1431
|
+
return { kind: "close" };
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
});
|
|
1435
|
+
await runMenu(ctx, menu, {
|
|
1436
|
+
getState: () => void 0,
|
|
1437
|
+
signal: menuSignal,
|
|
1438
|
+
isCurrent
|
|
1439
|
+
});
|
|
1440
|
+
}
|
|
1441
|
+
pi.registerCommand("langfuse", {
|
|
1442
|
+
description: "Open interactive Langfuse tracing controls",
|
|
1443
|
+
handler: async (_args, ctx) => showLangfuseMenu(ctx)
|
|
1444
|
+
});
|
|
1445
|
+
tracing.extension(pi);
|
|
1446
|
+
};
|
|
1377
1447
|
}
|
|
1378
1448
|
async function promptForConfig(ctx, current, isCurrent) {
|
|
1379
1449
|
const secretKey = await ctx.ui.input("Langfuse secret key (leave blank to keep existing):");
|
|
@@ -1410,12 +1480,7 @@ async function promptForConfig(ctx, current, isCurrent) {
|
|
|
1410
1480
|
return patch;
|
|
1411
1481
|
}
|
|
1412
1482
|
function formatMenuTitle(activeConfig, configPath, initializationError, configurationNotice) {
|
|
1413
|
-
const lines = [
|
|
1414
|
-
"Langfuse",
|
|
1415
|
-
"",
|
|
1416
|
-
"Current session:",
|
|
1417
|
-
` Tracing: ${activeConfig ? "enabled" : "disabled"}`
|
|
1418
|
-
];
|
|
1483
|
+
const lines = ["Langfuse", "", "Current session:", ` Tracing: ${activeConfig ? "enabled" : "disabled"}`];
|
|
1419
1484
|
if (activeConfig) {
|
|
1420
1485
|
lines.push(
|
|
1421
1486
|
` Endpoint: ${activeConfig.baseUrl}`,
|
|
@@ -1463,7 +1528,7 @@ function formatConfigError(result) {
|
|
|
1463
1528
|
const setupHint = result.reason.startsWith("Configuration file not found:") ? " Run /langfuse and choose Set up Langfuse." : "";
|
|
1464
1529
|
return `Langfuse tracing is disabled: ${result.reason}${setupHint}`;
|
|
1465
1530
|
}
|
|
1466
|
-
function
|
|
1531
|
+
function formatError(error, config) {
|
|
1467
1532
|
let message = error instanceof Error ? error.message : String(error);
|
|
1468
1533
|
const secrets = [config?.publicKey, config?.secretKey].filter((secret) => Boolean(secret)).sort((left, right) => right.length - left.length);
|
|
1469
1534
|
for (const secret of secrets) {
|
|
@@ -1472,7 +1537,15 @@ function formatError2(error, config) {
|
|
|
1472
1537
|
return message;
|
|
1473
1538
|
}
|
|
1474
1539
|
var langfuse_default = createLangfuseExtension();
|
|
1540
|
+
|
|
1541
|
+
// src/public-api.ts
|
|
1542
|
+
async function createLangfuseRuntime(options = {}) {
|
|
1543
|
+
const runtime = await import("./chunks/runtime-6MIKLWNJ.js");
|
|
1544
|
+
return runtime.createLangfuseRuntime(options);
|
|
1545
|
+
}
|
|
1475
1546
|
export {
|
|
1547
|
+
createLangfuseRuntime,
|
|
1548
|
+
createPiLangfuseSession,
|
|
1476
1549
|
langfuse_default as default
|
|
1477
1550
|
};
|
|
1478
1551
|
//# sourceMappingURL=index.ts.map
|