oh-langfuse 0.1.78 → 0.1.80

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.
@@ -2,17 +2,27 @@ import fs from "node:fs";
2
2
  import fsp from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import os from "node:os";
5
- import { execFileSync, spawnSync } from "node:child_process";
6
- import { fileURLToPath } from "node:url";
7
- import { defaultWindowsNpmCliCandidates, listSystemCliCandidates } from "./cli-detection-utils.mjs";
8
- import { writeRuntimeInstallRecord } from "./runtime-state-utils.mjs";
5
+ import { execFileSync, spawnSync } from "node:child_process";
6
+ import { fileURLToPath } from "node:url";
7
+ import { defaultWindowsNpmCliCandidates, listSystemCliCandidates } from "./cli-detection-utils.mjs";
8
+ import { writeRuntimeInstallRecord } from "./runtime-state-utils.mjs";
9
9
 
10
10
  const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
11
11
  const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8"));
12
12
 
13
+ const DEFAULT_LANGFUSE_BASE_URL = "https://metrics.openharmonyinsight.cn";
14
+ const LEGACY_LANGFUSE_BASE_URLS = new Set([
15
+ "http://120.46.221.227:3000",
16
+ "https://120.46.221.227:3000",
17
+ ]);
13
18
  const USER_ID_PATTERN = /^[a-z](?:\d{8}|wx\d{7})$/;
14
19
  const USER_ID_PATTERN_TEXT = "^[a-z](?:\\d{8}|wx\\d{7})$";
15
20
 
21
+ function normalizeLegacyBaseUrl(baseUrl) {
22
+ const value = String(baseUrl || "").trim().replace(/\/+$/, "");
23
+ return LEGACY_LANGFUSE_BASE_URLS.has(value) ? DEFAULT_LANGFUSE_BASE_URL : baseUrl;
24
+ }
25
+
16
26
  function normalizeUserId(v) {
17
27
  return String(v || "").trim();
18
28
  }
@@ -162,7 +172,7 @@ function writeAutoUpdateHelper(target) {
162
172
  const helperDir = autoUpdateHelperDir();
163
173
  ensureDir(helperDir);
164
174
  const runtimePath = autoUpdateRuntimePath();
165
- const fallbackArgs = ["-y", "--registry=https://registry.npmjs.org/", "oh-langfuse@latest", "auto-update", target, "--skip-check", "--startup-status"];
175
+ const fallbackArgs = ["-y", "--registry=https://registry.npmjs.org/", "oh-langfuse@latest", "auto-update", target, "--skip-check", "--startup-status"];
166
176
 
167
177
  if (process.platform === "win32") {
168
178
  const helper = path.join(helperDir, `oh-langfuse-auto-update-${target}.cmd`);
@@ -177,7 +187,7 @@ function writeAutoUpdateHelper(target) {
177
187
  "exit /b 0",
178
188
  ""
179
189
  ];
180
- fs.writeFileSync(helper, lines.join(os.EOL), "utf8");
190
+ fs.writeFileSync(helper, lines.join(os.EOL), "utf8");
181
191
  return helper;
182
192
  }
183
193
 
@@ -222,16 +232,16 @@ function existingCliCandidate(candidate, shimDir) {
222
232
  }
223
233
  }
224
234
 
225
- function listCliCandidatesFromPath(target) {
226
- return listSystemCliCandidates(target);
227
- }
228
-
229
- function resolveAgentCli({ target, preferred = "", shimDir = "" }) {
230
- const candidates = [
231
- preferred,
232
- ...defaultWindowsNpmCliCandidates(target),
233
- ...listCliCandidatesFromPath(target)
234
- ];
235
+ function listCliCandidatesFromPath(target) {
236
+ return listSystemCliCandidates(target);
237
+ }
238
+
239
+ function resolveAgentCli({ target, preferred = "", shimDir = "" }) {
240
+ const candidates = [
241
+ preferred,
242
+ ...defaultWindowsNpmCliCandidates(target),
243
+ ...listCliCandidatesFromPath(target)
244
+ ];
235
245
  for (const candidate of candidates) {
236
246
  const found = existingCliCandidate(candidate, shimDir);
237
247
  if (found) return found;
@@ -285,7 +295,7 @@ function writeAgentCommandShim({ baseDir, target, executable, realCli, publicKey
285
295
  "exit /b %ERRORLEVEL%",
286
296
  ""
287
297
  ].filter(Boolean).join(os.EOL);
288
- fs.writeFileSync(shim, content, "utf8");
298
+ fs.writeFileSync(shim, content, "utf8");
289
299
  return { shim, shimDir };
290
300
  }
291
301
 
@@ -317,7 +327,7 @@ function createAgentLauncher({ baseDir, target, executable }) {
317
327
  `${executable} %*`,
318
328
  ""
319
329
  ].join(os.EOL);
320
- fs.writeFileSync(launcher, content, "utf8");
330
+ fs.writeFileSync(launcher, content, "utf8");
321
331
  return launcher;
322
332
  }
323
333
 
@@ -340,87 +350,87 @@ function pythonExecutableInVenv(venvDir) {
340
350
  : path.join(venvDir, "bin", "python");
341
351
  }
342
352
 
343
- function pythonEnv(extraPythonPath = "") {
344
- const env = { ...process.env, PYTHONUTF8: "1" };
345
- if (extraPythonPath) {
346
- env.PYTHONPATH = process.env.PYTHONPATH ? `${extraPythonPath}${path.delimiter}${process.env.PYTHONPATH}` : extraPythonPath;
347
- }
348
- return env;
349
- }
350
-
351
- function pythonCanImport(pythonCmd, moduleName, extraPythonPath = "") {
352
- try {
353
- execFileSync(pythonCmd, ["-c", `import ${moduleName}`], { stdio: "ignore", encoding: "utf8", env: pythonEnv(extraPythonPath) });
354
- return true;
355
- } catch {
356
- return false;
357
- }
358
- }
359
-
360
- function installLangfuseIntoTargetDir({ pythonCmd, baseDir, pipIndexUrl }) {
361
- const targetDir = path.join(baseDir, "langfuse-python");
362
- ensureDir(targetDir);
363
- console.log(`Installing Python package into isolated target: ${targetDir}`);
364
- execFileSync(
365
- pythonCmd,
366
- ["-m", "pip", "install", "--target", targetDir, "--upgrade", "langfuse", "-i", pipIndexUrl],
367
- { stdio: "inherit", encoding: "utf8", env: pythonEnv() }
368
- );
369
- if (!pythonCanImport(pythonCmd, "langfuse", targetDir)) {
370
- throw new Error(`langfuse was installed into ${targetDir}, but ${pythonCmd} still cannot import it with PYTHONPATH.`);
371
- }
372
- return { python: pythonCmd, pythonPath: targetDir };
373
- }
374
-
375
- function runPipInstallWithFallback({ pythonCmd, baseDir, pipIndexUrl }) {
376
- const attempts = [
377
- () => installLangfuseIntoTargetDir({ pythonCmd, baseDir, pipIndexUrl }),
378
- () => installLangfuseIntoTargetDir({ pythonCmd: "python3", baseDir, pipIndexUrl }),
379
- () => installLangfuseIntoTargetDir({ pythonCmd: "python", baseDir, pipIndexUrl })
380
- ];
381
-
382
- const errors = [];
383
- for (const attempt of attempts) {
384
- try {
385
- return attempt();
386
- } catch (error) {
387
- errors.push(error?.message || String(error));
388
- }
389
- }
390
-
391
- throw new Error(
392
- `Failed to install langfuse into isolated Python target. Last errors: ${errors.join(" | ")}`
393
- );
394
- }
395
-
396
- function installLangfuseWithSystemPython({ pythonCmd, baseDir, pipIndexUrl }) {
397
- console.log("Python venv is unavailable; falling back to isolated PYTHONPATH install for langfuse.");
398
- return runPipInstallWithFallback({ pythonCmd, baseDir, pipIndexUrl });
399
- }
400
-
401
- async function createHookLauncher({ hooksDir, hookPython, hookPythonPath = "", pyPath }) {
402
- if (process.platform === "win32") {
403
- const launcher = path.join(hooksDir, "run-langfuse-hook.cmd");
404
- const content = [
405
- "@echo off",
406
- hookPythonPath ? `set PYTHONPATH=${hookPythonPath};%PYTHONPATH%` : null,
407
- `${cmdQuote(hookPython)} ${cmdQuote(pyPath)}`,
408
- ""
409
- ].filter(Boolean).join(os.EOL);
410
- await fsp.writeFile(launcher, content, "utf8");
411
- return launcher;
412
- }
413
-
414
- const launcher = path.join(hooksDir, "run-langfuse-hook.sh");
415
- const content = [
416
- "#!/usr/bin/env sh",
417
- hookPythonPath ? `if [ -n "\${PYTHONPATH:-}" ]; then export PYTHONPATH=${shQuote(hookPythonPath)}:"$PYTHONPATH"; else export PYTHONPATH=${shQuote(hookPythonPath)}; fi` : null,
418
- `exec ${shQuote(hookPython)} ${shQuote(pyPath)}`,
419
- ""
420
- ].filter(Boolean).join("\n");
421
- await fsp.writeFile(launcher, content, "utf8");
422
- fs.chmodSync(launcher, 0o755);
423
- return launcher;
353
+ function pythonEnv(extraPythonPath = "") {
354
+ const env = { ...process.env, PYTHONUTF8: "1" };
355
+ if (extraPythonPath) {
356
+ env.PYTHONPATH = process.env.PYTHONPATH ? `${extraPythonPath}${path.delimiter}${process.env.PYTHONPATH}` : extraPythonPath;
357
+ }
358
+ return env;
359
+ }
360
+
361
+ function pythonCanImport(pythonCmd, moduleName, extraPythonPath = "") {
362
+ try {
363
+ execFileSync(pythonCmd, ["-c", `import ${moduleName}`], { stdio: "ignore", encoding: "utf8", env: pythonEnv(extraPythonPath) });
364
+ return true;
365
+ } catch {
366
+ return false;
367
+ }
368
+ }
369
+
370
+ function installLangfuseIntoTargetDir({ pythonCmd, baseDir, pipIndexUrl }) {
371
+ const targetDir = path.join(baseDir, "langfuse-python");
372
+ ensureDir(targetDir);
373
+ console.log(`Installing Python package into isolated target: ${targetDir}`);
374
+ execFileSync(
375
+ pythonCmd,
376
+ ["-m", "pip", "install", "--target", targetDir, "--upgrade", "langfuse", "-i", pipIndexUrl],
377
+ { stdio: "inherit", encoding: "utf8", env: pythonEnv() }
378
+ );
379
+ if (!pythonCanImport(pythonCmd, "langfuse", targetDir)) {
380
+ throw new Error(`langfuse was installed into ${targetDir}, but ${pythonCmd} still cannot import it with PYTHONPATH.`);
381
+ }
382
+ return { python: pythonCmd, pythonPath: targetDir };
383
+ }
384
+
385
+ function runPipInstallWithFallback({ pythonCmd, baseDir, pipIndexUrl }) {
386
+ const attempts = [
387
+ () => installLangfuseIntoTargetDir({ pythonCmd, baseDir, pipIndexUrl }),
388
+ () => installLangfuseIntoTargetDir({ pythonCmd: "python3", baseDir, pipIndexUrl }),
389
+ () => installLangfuseIntoTargetDir({ pythonCmd: "python", baseDir, pipIndexUrl })
390
+ ];
391
+
392
+ const errors = [];
393
+ for (const attempt of attempts) {
394
+ try {
395
+ return attempt();
396
+ } catch (error) {
397
+ errors.push(error?.message || String(error));
398
+ }
399
+ }
400
+
401
+ throw new Error(
402
+ `Failed to install langfuse into isolated Python target. Last errors: ${errors.join(" | ")}`
403
+ );
404
+ }
405
+
406
+ function installLangfuseWithSystemPython({ pythonCmd, baseDir, pipIndexUrl }) {
407
+ console.log("Python venv is unavailable; falling back to isolated PYTHONPATH install for langfuse.");
408
+ return runPipInstallWithFallback({ pythonCmd, baseDir, pipIndexUrl });
409
+ }
410
+
411
+ async function createHookLauncher({ hooksDir, hookPython, hookPythonPath = "", pyPath }) {
412
+ if (process.platform === "win32") {
413
+ const launcher = path.join(hooksDir, "run-langfuse-hook.cmd");
414
+ const content = [
415
+ "@echo off",
416
+ hookPythonPath ? `set PYTHONPATH=${hookPythonPath};%PYTHONPATH%` : null,
417
+ `${cmdQuote(hookPython)} ${cmdQuote(pyPath)}`,
418
+ ""
419
+ ].filter(Boolean).join(os.EOL);
420
+ await fsp.writeFile(launcher, content, "utf8");
421
+ return launcher;
422
+ }
423
+
424
+ const launcher = path.join(hooksDir, "run-langfuse-hook.sh");
425
+ const content = [
426
+ "#!/usr/bin/env sh",
427
+ hookPythonPath ? `if [ -n "\${PYTHONPATH:-}" ]; then export PYTHONPATH=${shQuote(hookPythonPath)}:"$PYTHONPATH"; else export PYTHONPATH=${shQuote(hookPythonPath)}; fi` : null,
428
+ `exec ${shQuote(hookPython)} ${shQuote(pyPath)}`,
429
+ ""
430
+ ].filter(Boolean).join("\n");
431
+ await fsp.writeFile(launcher, content, "utf8");
432
+ fs.chmodSync(launcher, 0o755);
433
+ return launcher;
424
434
  }
425
435
 
426
436
  function createOrUpdateLangfuseVenv({ baseDir, pipIndexUrl = "https://pypi.tuna.tsinghua.edu.cn/simple" }) {
@@ -431,13 +441,13 @@ function createOrUpdateLangfuseVenv({ baseDir, pipIndexUrl = "https://pypi.tuna.
431
441
  if (!fs.existsSync(venvPython)) {
432
442
  console.log(`Creating Python virtual environment: ${venvDir}`);
433
443
  try {
434
- execFileSync(pythonCmd, ["-m", "venv", venvDir], { stdio: "inherit", encoding: "utf8", env: { ...process.env, PYTHONUTF8: "1" } });
435
- } catch (e) {
436
- if (process.platform !== "win32") {
437
- return installLangfuseWithSystemPython({ pythonCmd, baseDir, pipIndexUrl });
438
- }
439
- throw new Error("Failed to create Python venv. Please confirm Python venv support is available.");
440
- }
444
+ execFileSync(pythonCmd, ["-m", "venv", venvDir], { stdio: "inherit", encoding: "utf8", env: { ...process.env, PYTHONUTF8: "1" } });
445
+ } catch (e) {
446
+ if (process.platform !== "win32") {
447
+ return installLangfuseWithSystemPython({ pythonCmd, baseDir, pipIndexUrl });
448
+ }
449
+ throw new Error("Failed to create Python venv. Please confirm Python venv support is available.");
450
+ }
441
451
  }
442
452
 
443
453
  console.log("Installing/updating Python package in venv: langfuse");
@@ -446,23 +456,23 @@ function createOrUpdateLangfuseVenv({ baseDir, pipIndexUrl = "https://pypi.tuna.
446
456
  venvPython,
447
457
  ["-m", "pip", "install", "-U", "langfuse", "-i", pipIndexUrl],
448
458
  { stdio: "inherit", encoding: "utf8", env: { ...process.env, PYTHONUTF8: "1" } }
449
- );
450
- } catch (e) {
451
- if (process.platform !== "win32") {
452
- return installLangfuseWithSystemPython({ pythonCmd, baseDir, pipIndexUrl });
453
- }
454
- throw new Error(`Failed to install langfuse in venv: ${venvPython} -m pip install -U langfuse -i ${pipIndexUrl}`);
455
- }
456
-
457
- if (!pythonCanImport(venvPython, "langfuse")) {
458
- if (process.platform !== "win32") {
459
- return installLangfuseWithSystemPython({ pythonCmd, baseDir, pipIndexUrl });
460
- }
461
- throw new Error(`langfuse was installed in venv, but cannot be imported by ${venvPython}`);
462
- }
463
-
464
- return { python: venvPython, pythonPath: "" };
465
- }
459
+ );
460
+ } catch (e) {
461
+ if (process.platform !== "win32") {
462
+ return installLangfuseWithSystemPython({ pythonCmd, baseDir, pipIndexUrl });
463
+ }
464
+ throw new Error(`Failed to install langfuse in venv: ${venvPython} -m pip install -U langfuse -i ${pipIndexUrl}`);
465
+ }
466
+
467
+ if (!pythonCanImport(venvPython, "langfuse")) {
468
+ if (process.platform !== "win32") {
469
+ return installLangfuseWithSystemPython({ pythonCmd, baseDir, pipIndexUrl });
470
+ }
471
+ throw new Error(`langfuse was installed in venv, but cannot be imported by ${venvPython}`);
472
+ }
473
+
474
+ return { python: venvPython, pythonPath: "" };
475
+ }
466
476
 
467
477
  async function main() {
468
478
  const args = parseArgs(process.argv.slice(2));
@@ -473,13 +483,14 @@ async function main() {
473
483
  }
474
484
  assertValidUserId(userId);
475
485
 
476
- const langfuseHost =
486
+ const langfuseHost = normalizeLegacyBaseUrl(
477
487
  args.langfuseBaseUrl ||
478
488
  args.langfuseHost ||
479
489
  args.host ||
480
490
  process.env.LANGFUSE_BASEURL ||
481
491
  process.env.LANGFUSE_HOST ||
482
- "http://120.46.221.227:3000";
492
+ DEFAULT_LANGFUSE_BASE_URL
493
+ );
483
494
 
484
495
  const publicKey =
485
496
  args.publicKey || process.env.LANGFUSE_PUBLIC_KEY || "pk-lf-da0c90a7-6e93-4eb7-bb86-c1047c8d187d";
@@ -543,9 +554,9 @@ async function main() {
543
554
  if (nextPyText !== pyText) {
544
555
  await fsp.writeFile(pyPath, nextPyText, "utf8");
545
556
  }
546
- const hookRuntime = createOrUpdateLangfuseVenv({ baseDir: claudeDir, pipIndexUrl });
547
- const hookPython = hookRuntime.python;
548
- const hookLauncher = await createHookLauncher({ hooksDir, hookPython, hookPythonPath: hookRuntime.pythonPath, pyPath });
557
+ const hookRuntime = createOrUpdateLangfuseVenv({ baseDir: claudeDir, pipIndexUrl });
558
+ const hookPython = hookRuntime.python;
559
+ const hookLauncher = await createHookLauncher({ hooksDir, hookPython, hookPythonPath: hookRuntime.pythonPath, pyPath });
549
560
  const claudeShimDir = path.join(claudeDir, "bin");
550
561
  const realClaudeCli = resolveAgentCli({ target: "claude", preferred: args.cmd || "", shimDir: claudeShimDir });
551
562
  const directShim = writeAgentCommandShim({