oh-langfuse 0.1.54 → 0.1.55
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/package.json
CHANGED
|
@@ -59,13 +59,54 @@ function tomlArray(items) {
|
|
|
59
59
|
return `[${items.map(tomlString).join(", ")}]`;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
function pythonExecutableInVenv(venvDir) {
|
|
63
|
-
return process.platform === "win32"
|
|
64
|
-
? path.join(venvDir, "Scripts", "python.exe")
|
|
65
|
-
: path.join(venvDir, "bin", "python");
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function
|
|
62
|
+
function pythonExecutableInVenv(venvDir) {
|
|
63
|
+
return process.platform === "win32"
|
|
64
|
+
? path.join(venvDir, "Scripts", "python.exe")
|
|
65
|
+
: path.join(venvDir, "bin", "python");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function pythonCanImport(pythonCmd, moduleName) {
|
|
69
|
+
try {
|
|
70
|
+
execFileSync(pythonCmd, ["-c", `import ${moduleName}`], { stdio: "ignore" });
|
|
71
|
+
return true;
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function runPipInstallWithFallback({ pythonCmd, pipIndexUrl }) {
|
|
78
|
+
const attempts = [
|
|
79
|
+
{ command: pythonCmd, args: ["-m", "pip", "install", "--user", "-U", "langfuse", "-i", pipIndexUrl] },
|
|
80
|
+
{ command: "pip3", args: ["install", "--user", "-U", "langfuse", "-i", pipIndexUrl] },
|
|
81
|
+
{ command: "pip", args: ["install", "--user", "-U", "langfuse", "-i", pipIndexUrl] }
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
const errors = [];
|
|
85
|
+
for (const attempt of attempts) {
|
|
86
|
+
try {
|
|
87
|
+
console.log(`Trying Python package install: ${attempt.command} ${attempt.args.join(" ")}`);
|
|
88
|
+
execFileSync(attempt.command, attempt.args, { stdio: "inherit" });
|
|
89
|
+
return;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
errors.push(`${attempt.command}: ${error?.message || error}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
throw new Error(
|
|
96
|
+
`Failed to install langfuse with system Python/pip. Tried python -m pip, pip3, and pip. Last errors: ${errors.join(" | ")}`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function installLangfuseWithSystemPython({ pythonCmd, pipIndexUrl }) {
|
|
101
|
+
console.log("Python venv is unavailable; falling back to system Python user install for langfuse.");
|
|
102
|
+
runPipInstallWithFallback({ pythonCmd, pipIndexUrl });
|
|
103
|
+
if (!pythonCanImport(pythonCmd, "langfuse")) {
|
|
104
|
+
throw new Error("langfuse was installed with pip, but python3 still cannot import it. Install python3-venv and rerun setup, for example: sudo apt install python3-venv");
|
|
105
|
+
}
|
|
106
|
+
return pythonCmd;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function shQuote(s) {
|
|
69
110
|
return `'${String(s).replace(/'/g, "'\"'\"'")}'`;
|
|
70
111
|
}
|
|
71
112
|
|
|
@@ -288,14 +329,14 @@ function createOrUpdateLangfuseVenv({ baseDir, pipIndexUrl = "https://pypi.tuna.
|
|
|
288
329
|
|
|
289
330
|
if (!fs.existsSync(venvPython)) {
|
|
290
331
|
console.log(`Creating Python virtual environment: ${venvDir}`);
|
|
291
|
-
try {
|
|
292
|
-
execFileSync(pythonCmd, ["-m", "venv", venvDir], { stdio: "inherit" });
|
|
293
|
-
} catch (e) {
|
|
294
|
-
if (process.platform !== "win32") {
|
|
295
|
-
|
|
296
|
-
}
|
|
297
|
-
throw new Error("Failed to create Python venv. Please confirm Python venv support is available.");
|
|
298
|
-
}
|
|
332
|
+
try {
|
|
333
|
+
execFileSync(pythonCmd, ["-m", "venv", venvDir], { stdio: "inherit" });
|
|
334
|
+
} catch (e) {
|
|
335
|
+
if (process.platform !== "win32") {
|
|
336
|
+
return installLangfuseWithSystemPython({ pythonCmd, pipIndexUrl });
|
|
337
|
+
}
|
|
338
|
+
throw new Error("Failed to create Python venv. Please confirm Python venv support is available.");
|
|
339
|
+
}
|
|
299
340
|
}
|
|
300
341
|
|
|
301
342
|
console.log("Installing/updating Python package in venv: langfuse");
|
|
@@ -303,11 +344,14 @@ function createOrUpdateLangfuseVenv({ baseDir, pipIndexUrl = "https://pypi.tuna.
|
|
|
303
344
|
execFileSync(
|
|
304
345
|
venvPython,
|
|
305
346
|
["-m", "pip", "install", "-U", "langfuse", "-i", pipIndexUrl],
|
|
306
|
-
{ stdio: "inherit" }
|
|
307
|
-
);
|
|
308
|
-
} catch (e) {
|
|
309
|
-
|
|
310
|
-
|
|
347
|
+
{ stdio: "inherit" }
|
|
348
|
+
);
|
|
349
|
+
} catch (e) {
|
|
350
|
+
if (process.platform !== "win32") {
|
|
351
|
+
return installLangfuseWithSystemPython({ pythonCmd, pipIndexUrl });
|
|
352
|
+
}
|
|
353
|
+
throw new Error(`Failed to install langfuse in venv: ${venvPython} -m pip install -U langfuse -i ${pipIndexUrl}`);
|
|
354
|
+
}
|
|
311
355
|
|
|
312
356
|
return venvPython;
|
|
313
357
|
}
|
|
@@ -343,13 +343,54 @@ function createAgentLauncher({ baseDir, target, executable }) {
|
|
|
343
343
|
return launcher;
|
|
344
344
|
}
|
|
345
345
|
|
|
346
|
-
function pythonExecutableInVenv(venvDir) {
|
|
347
|
-
return process.platform === "win32"
|
|
348
|
-
? path.join(venvDir, "Scripts", "python.exe")
|
|
349
|
-
: path.join(venvDir, "bin", "python");
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
|
|
346
|
+
function pythonExecutableInVenv(venvDir) {
|
|
347
|
+
return process.platform === "win32"
|
|
348
|
+
? path.join(venvDir, "Scripts", "python.exe")
|
|
349
|
+
: path.join(venvDir, "bin", "python");
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function pythonCanImport(pythonCmd, moduleName) {
|
|
353
|
+
try {
|
|
354
|
+
execFileSync(pythonCmd, ["-c", `import ${moduleName}`], { stdio: "ignore" });
|
|
355
|
+
return true;
|
|
356
|
+
} catch {
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function runPipInstallWithFallback({ pythonCmd, pipIndexUrl }) {
|
|
362
|
+
const attempts = [
|
|
363
|
+
{ command: pythonCmd, args: ["-m", "pip", "install", "--user", "-U", "langfuse", "-i", pipIndexUrl] },
|
|
364
|
+
{ command: "pip3", args: ["install", "--user", "-U", "langfuse", "-i", pipIndexUrl] },
|
|
365
|
+
{ command: "pip", args: ["install", "--user", "-U", "langfuse", "-i", pipIndexUrl] }
|
|
366
|
+
];
|
|
367
|
+
|
|
368
|
+
const errors = [];
|
|
369
|
+
for (const attempt of attempts) {
|
|
370
|
+
try {
|
|
371
|
+
console.log(`Trying Python package install: ${attempt.command} ${attempt.args.join(" ")}`);
|
|
372
|
+
execFileSync(attempt.command, attempt.args, { stdio: "inherit" });
|
|
373
|
+
return;
|
|
374
|
+
} catch (error) {
|
|
375
|
+
errors.push(`${attempt.command}: ${error?.message || error}`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
throw new Error(
|
|
380
|
+
`Failed to install langfuse with system Python/pip. Tried python -m pip, pip3, and pip. Last errors: ${errors.join(" | ")}`
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function installLangfuseWithSystemPython({ pythonCmd, pipIndexUrl }) {
|
|
385
|
+
console.log("Python venv is unavailable; falling back to system Python user install for langfuse.");
|
|
386
|
+
runPipInstallWithFallback({ pythonCmd, pipIndexUrl });
|
|
387
|
+
if (!pythonCanImport(pythonCmd, "langfuse")) {
|
|
388
|
+
throw new Error("langfuse was installed with pip, but python3 still cannot import it. Install python3-venv and rerun setup, for example: sudo apt install python3-venv");
|
|
389
|
+
}
|
|
390
|
+
return pythonCmd;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async function createHookLauncher({ hooksDir, hookPython, pyPath }) {
|
|
353
394
|
if (process.platform === "win32") {
|
|
354
395
|
const launcher = path.join(hooksDir, "run-langfuse-hook.cmd");
|
|
355
396
|
const content = [
|
|
@@ -379,14 +420,14 @@ function createOrUpdateLangfuseVenv({ baseDir, pipIndexUrl = "https://pypi.tuna.
|
|
|
379
420
|
|
|
380
421
|
if (!fs.existsSync(venvPython)) {
|
|
381
422
|
console.log(`Creating Python virtual environment: ${venvDir}`);
|
|
382
|
-
try {
|
|
383
|
-
execFileSync(pythonCmd, ["-m", "venv", venvDir], { stdio: "inherit" });
|
|
384
|
-
} catch (e) {
|
|
385
|
-
if (process.platform !== "win32") {
|
|
386
|
-
|
|
387
|
-
}
|
|
388
|
-
throw new Error("Failed to create Python venv. Please confirm Python venv support is available.");
|
|
389
|
-
}
|
|
423
|
+
try {
|
|
424
|
+
execFileSync(pythonCmd, ["-m", "venv", venvDir], { stdio: "inherit" });
|
|
425
|
+
} catch (e) {
|
|
426
|
+
if (process.platform !== "win32") {
|
|
427
|
+
return installLangfuseWithSystemPython({ pythonCmd, pipIndexUrl });
|
|
428
|
+
}
|
|
429
|
+
throw new Error("Failed to create Python venv. Please confirm Python venv support is available.");
|
|
430
|
+
}
|
|
390
431
|
}
|
|
391
432
|
|
|
392
433
|
console.log("Installing/updating Python package in venv: langfuse");
|
|
@@ -394,11 +435,14 @@ function createOrUpdateLangfuseVenv({ baseDir, pipIndexUrl = "https://pypi.tuna.
|
|
|
394
435
|
execFileSync(
|
|
395
436
|
venvPython,
|
|
396
437
|
["-m", "pip", "install", "-U", "langfuse", "-i", pipIndexUrl],
|
|
397
|
-
{ stdio: "inherit" }
|
|
398
|
-
);
|
|
399
|
-
} catch (e) {
|
|
400
|
-
|
|
401
|
-
|
|
438
|
+
{ stdio: "inherit" }
|
|
439
|
+
);
|
|
440
|
+
} catch (e) {
|
|
441
|
+
if (process.platform !== "win32") {
|
|
442
|
+
return installLangfuseWithSystemPython({ pythonCmd, pipIndexUrl });
|
|
443
|
+
}
|
|
444
|
+
throw new Error(`Failed to install langfuse in venv: ${venvPython} -m pip install -U langfuse -i ${pipIndexUrl}`);
|
|
445
|
+
}
|
|
402
446
|
|
|
403
447
|
return venvPython;
|
|
404
448
|
}
|
|
@@ -61,6 +61,7 @@ function main() {
|
|
|
61
61
|
const userConfigPath = path.join(home, ".config", "opencode-plugin-langfuse", "config.json");
|
|
62
62
|
const windowsLauncherPath = path.join(opencodeDir, "launch-opencode-langfuse.cmd");
|
|
63
63
|
const unixLauncherPath = path.join(opencodeDir, "launch-opencode-langfuse.sh");
|
|
64
|
+
const opencodeCommandShimPath = path.join(opencodeDir, "bin", process.platform === "win32" ? "opencode.cmd" : "opencode");
|
|
64
65
|
|
|
65
66
|
const results = [];
|
|
66
67
|
|
|
@@ -134,6 +135,14 @@ function main() {
|
|
|
134
135
|
"Run setup again and enter userId when prompted."
|
|
135
136
|
);
|
|
136
137
|
|
|
138
|
+
addResult(
|
|
139
|
+
results,
|
|
140
|
+
"opencode command shim",
|
|
141
|
+
fs.existsSync(opencodeCommandShimPath),
|
|
142
|
+
opencodeCommandShimPath,
|
|
143
|
+
"Run setup again after installing OpenCode so the direct opencode command can load Langfuse and auto-update checks."
|
|
144
|
+
);
|
|
145
|
+
|
|
137
146
|
if (process.platform === "win32") {
|
|
138
147
|
addResult(
|
|
139
148
|
results,
|
|
@@ -512,12 +512,10 @@ function getPatchedLangfuseDistIndexJs() {
|
|
|
512
512
|
" if (userId) spanProcessors.push(createUserIdSpanProcessor(userId));",
|
|
513
513
|
"",
|
|
514
514
|
" const sdk = new NodeSDK({ spanProcessors });",
|
|
515
|
-
"
|
|
516
|
-
"
|
|
517
|
-
' try { sdk.start(); }',
|
|
518
|
-
' catch (err) { log("warn", `OTEL SDK start failed: ${err?.message ?? err}`); }',
|
|
515
|
+
" const sdkStartPromise = Promise.resolve().then(() => sdk.start()).catch((err) => {",
|
|
516
|
+
' log("warn", `OTEL SDK start failed: ${err?.message ?? err}`);',
|
|
519
517
|
" });",
|
|
520
|
-
" const
|
|
518
|
+
" const getMetricsTracer = () => trace.getTracer('oh-langfuse-opencode-metrics');",
|
|
521
519
|
" const knownSkillNames = await collectKnownSkillNames();",
|
|
522
520
|
" const startupSkillUsages = detectOpencodeSkillUsages(process.argv.join('\\n'), knownSkillNames);",
|
|
523
521
|
" const messageTextById = new Map();",
|
|
@@ -534,21 +532,23 @@ function getPatchedLangfuseDistIndexJs() {
|
|
|
534
532
|
' if (userId) log("info", `LANGFUSE userId configured -> ${userId}`);',
|
|
535
533
|
' if (knownSkillNames.length) log("info", `OpenCode skills discovered -> ${knownSkillNames.length}`);',
|
|
536
534
|
"",
|
|
537
|
-
" let shutdownStarted = false;",
|
|
538
|
-
" const flush = async (reason) => {",
|
|
539
|
-
" try {",
|
|
540
|
-
' log("info", `Flushing OTEL spans on ${reason}`);',
|
|
541
|
-
" await
|
|
535
|
+
" let shutdownStarted = false;",
|
|
536
|
+
" const flush = async (reason) => {",
|
|
537
|
+
" try {",
|
|
538
|
+
' log("info", `Flushing OTEL spans on ${reason}`);',
|
|
539
|
+
" await sdkStartPromise;",
|
|
540
|
+
" await processor.forceFlush();",
|
|
542
541
|
" } catch (error) {",
|
|
543
542
|
' log("warn", `OTEL forceFlush failed on ${reason}: ${error?.message ?? error}`);',
|
|
544
543
|
" }",
|
|
545
544
|
" };",
|
|
546
545
|
" const shutdown = async (reason) => {",
|
|
547
|
-
" if (shutdownStarted) return;",
|
|
548
|
-
" shutdownStarted = true;",
|
|
549
|
-
" try {",
|
|
550
|
-
' log("info", `Shutting down OTEL SDK on ${reason}`);',
|
|
551
|
-
" await
|
|
546
|
+
" if (shutdownStarted) return;",
|
|
547
|
+
" shutdownStarted = true;",
|
|
548
|
+
" try {",
|
|
549
|
+
' log("info", `Shutting down OTEL SDK on ${reason}`);',
|
|
550
|
+
" await sdkStartPromise;",
|
|
551
|
+
" await sdk.shutdown();",
|
|
552
552
|
" } catch (error) {",
|
|
553
553
|
' log("warn", `OTEL shutdown failed on ${reason}: ${error?.message ?? error}`);',
|
|
554
554
|
" }",
|
|
@@ -586,7 +586,7 @@ function getPatchedLangfuseDistIndexJs() {
|
|
|
586
586
|
" }",
|
|
587
587
|
" set.add(activity.toolCallId || `${kind}:${set.size + 1}`);",
|
|
588
588
|
" };",
|
|
589
|
-
" const recordInteractionMetric = (event) => {",
|
|
589
|
+
" const recordInteractionMetric = async (event) => {",
|
|
590
590
|
" const payload = eventPayload(event);",
|
|
591
591
|
" const part = eventPart(event);",
|
|
592
592
|
" const partType = part?.type ?? '';",
|
|
@@ -617,7 +617,8 @@ function getPatchedLangfuseDistIndexJs() {
|
|
|
617
617
|
" const tokenMetrics = tokenMetricsFromPart(part);",
|
|
618
618
|
" const total = tokenMetrics.total ?? (tokenMetrics.input !== undefined && tokenMetrics.output !== undefined ? tokenMetrics.input + tokenMetrics.output : undefined);",
|
|
619
619
|
" const tokenAvailable = [tokenMetrics.input, tokenMetrics.output, total, tokenMetrics.cacheRead, tokenMetrics.reasoning].some((value) => value !== undefined);",
|
|
620
|
-
"
|
|
620
|
+
" await sdkStartPromise;",
|
|
621
|
+
" const span = getMetricsTracer().startSpan('Agent Turn');",
|
|
621
622
|
" const text = messageTextById.get(messageId) || '';",
|
|
622
623
|
" const skillUsages = dedupeSkillUsages([...(skillUsagesByMessageId.get(messageId) ?? []), ...(skillUsagesBySessionId.get(sessionId) ?? [])]);",
|
|
623
624
|
" const interactionId = `opencode:${userId || \"unknown\"}:${sessionId || \"unknown\"}:${messageId}`;",
|
|
@@ -673,9 +674,9 @@ function getPatchedLangfuseDistIndexJs() {
|
|
|
673
674
|
' log("warn", "OpenTelemetry experimental feature is disabled in Opencode config - tracing disabled");',
|
|
674
675
|
" }",
|
|
675
676
|
" },",
|
|
676
|
-
" event: async ({ event }) => {",
|
|
677
|
-
" const eventType = pickEventString(event?.name, event?.type, eventPayload(event)?.name, eventPayload(event)?.type);",
|
|
678
|
-
" recordInteractionMetric(event);",
|
|
677
|
+
" event: async ({ event }) => {",
|
|
678
|
+
" const eventType = pickEventString(event?.name, event?.type, eventPayload(event)?.name, eventPayload(event)?.type);",
|
|
679
|
+
" await recordInteractionMetric(event);",
|
|
679
680
|
' if (eventType === "session.idle" || eventType === "message.updated" || eventType === "message.part.updated" || eventType === "session.updated" || eventType === "session.idle.1" || eventType === "message.updated.1" || eventType === "message.part.updated.1" || eventType === "session.updated.1") {',
|
|
680
681
|
" await flush(eventType);",
|
|
681
682
|
" }",
|
|
@@ -973,24 +974,28 @@ function npmInstallLooksLikeMissingVersion(result) {
|
|
|
973
974
|
return text.includes("etarget") || text.includes("notarget") || text.includes("no matching version found");
|
|
974
975
|
}
|
|
975
976
|
|
|
976
|
-
function isOfficialNpmRegistry(registry) {
|
|
977
|
-
return /^https?:\/\/registry\.npmjs\.org\/?$/i.test(String(registry || "").trim());
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
const
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
977
|
+
function isOfficialNpmRegistry(registry) {
|
|
978
|
+
return /^https?:\/\/registry\.npmjs\.org\/?$/i.test(String(registry || "").trim());
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
const OFFICIAL_NPM_REGISTRY = "https://registry.npmjs.org/";
|
|
982
|
+
|
|
983
|
+
async function runNpmInstallOrThrow({ opencodeDir, pkgName = "opencode-plugin-langfuse", npmRegistry = "" }) {
|
|
984
|
+
const npmArgs = ["install", pkgName, "--prefix", opencodeDir, "--package-lock=false", "--no-save", "--audit=false", "--fund=false"];
|
|
985
|
+
const effectiveRegistry = npmRegistry || OFFICIAL_NPM_REGISTRY;
|
|
986
|
+
npmArgs.push("--registry", effectiveRegistry);
|
|
987
|
+
const cliJs = getNpmCliJsPath();
|
|
988
|
+
console.log(`使用 npm:${fs.existsSync(cliJs) ? `node ${cliJs}` : getNpmExecutable()}`);
|
|
989
|
+
console.log(`使用 npm registry:${effectiveRegistry}`);
|
|
990
|
+
console.log("Installing OpenCode Langfuse plugin. This can take a few minutes on slow networks...");
|
|
991
|
+
let r = await runNpmInstallCapture(npmArgs);
|
|
992
|
+
if (!r.error && r.status !== 0 && npmInstallLooksLikeMissingVersion(r) && !isOfficialNpmRegistry(effectiveRegistry)) {
|
|
993
|
+
console.error("");
|
|
994
|
+
console.error(`npm registry appears to be missing a package version. Retrying with ${OFFICIAL_NPM_REGISTRY} ...`);
|
|
995
|
+
const retryArgs = ["install", pkgName, "--prefix", opencodeDir, "--package-lock=false", "--no-save", "--audit=false", "--fund=false", "--registry", OFFICIAL_NPM_REGISTRY];
|
|
996
|
+
r = await runNpmInstallCapture(retryArgs);
|
|
997
|
+
if (!r.error && r.status === 0) return;
|
|
998
|
+
}
|
|
994
999
|
if (!r.error && r.status === 0) return;
|
|
995
1000
|
printNpmDiagnostics();
|
|
996
1001
|
const npmLabel = fs.existsSync(cliJs) ? `node ${cliJs}` : getNpmExecutable();
|
|
@@ -1105,7 +1110,7 @@ async function main() {
|
|
|
1105
1110
|
throw new Error("缺少参数:--userId=你的工号");
|
|
1106
1111
|
}
|
|
1107
1112
|
assertValidUserId(userId);
|
|
1108
|
-
const npmRegistry = args.npmRegistry || process.env.OPENCODE_NPM_REGISTRY ||
|
|
1113
|
+
const npmRegistry = args.npmRegistry || process.env.OPENCODE_NPM_REGISTRY || "";
|
|
1109
1114
|
|
|
1110
1115
|
const home = os.homedir();
|
|
1111
1116
|
const opencodeDir = path.join(home, ".config", "opencode");
|