oh-langfuse 0.1.59 → 0.1.61
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 +245 -245
- package/bin/cli.js +22 -22
- package/codex_langfuse_notify.py +422 -56
- package/package.json +6 -6
- package/scripts/auto-update-runtime.mjs +28 -15
- package/scripts/codex-langfuse-setup.mjs +127 -117
- package/scripts/langfuse-setup.mjs +71 -71
- package/scripts/opencode-langfuse-run.mjs +23 -23
- package/scripts/opencode-langfuse-setup.mjs +140 -64
- package/scripts/real-self-verify.mjs +114 -104
- package/scripts/update-langfuse-runtime.mjs +7 -6
- package/scripts/verify-update-runtime.mjs +162 -162
- package/scripts/verify-update-utils.mjs +60 -60
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
import { spawnSync } from "node:child_process";
|
|
4
|
-
import { resolveOpencodeCli } from "./resolve-opencode-cli.mjs";
|
|
5
|
-
|
|
6
|
-
const USER_ID_PATTERN = /^[a-z](?:\d{8}|wx\d{7})$/;
|
|
7
|
-
const USER_ID_PATTERN_TEXT = "^[a-z](?:\\d{8}|wx\\d{7})$";
|
|
8
|
-
|
|
9
|
-
function normalizeUserId(v) {
|
|
10
|
-
return String(v || "").trim();
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function assertValidUserId(userId) {
|
|
14
|
-
if (!USER_ID_PATTERN.test(normalizeUserId(userId))) {
|
|
15
|
-
throw new Error(`User ID must match ${USER_ID_PATTERN_TEXT}, for example h00613222 or hwx1234567.`);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { resolveOpencodeCli } from "./resolve-opencode-cli.mjs";
|
|
5
|
+
|
|
6
|
+
const USER_ID_PATTERN = /^[a-z](?:\d{8}|wx\d{7})$/;
|
|
7
|
+
const USER_ID_PATTERN_TEXT = "^[a-z](?:\\d{8}|wx\\d{7})$";
|
|
8
|
+
|
|
9
|
+
function normalizeUserId(v) {
|
|
10
|
+
return String(v || "").trim();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function assertValidUserId(userId) {
|
|
14
|
+
if (!USER_ID_PATTERN.test(normalizeUserId(userId))) {
|
|
15
|
+
throw new Error(`User ID must match ${USER_ID_PATTERN_TEXT}, for example h00613222 or hwx1234567.`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
18
|
|
|
19
19
|
function parseArgs(argv) {
|
|
20
20
|
const args = {};
|
|
@@ -31,7 +31,7 @@ function parseArgs(argv) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
function runNodeScript(scriptPath, extraArgs = []) {
|
|
34
|
-
const r = spawnSync(process.execPath, [scriptPath, ...extraArgs], { stdio: "inherit" });
|
|
34
|
+
const r = spawnSync(process.execPath, [scriptPath, ...extraArgs], { stdio: "inherit", encoding: "utf8" });
|
|
35
35
|
if (r.status !== 0) process.exit(r.status ?? 1);
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -42,12 +42,12 @@ function main() {
|
|
|
42
42
|
args.publicKey || process.env.LANGFUSE_PUBLIC_KEY || "pk-lf-da0c90a7-6e93-4eb7-bb86-c1047c8d187d";
|
|
43
43
|
const secretKey =
|
|
44
44
|
args.secretKey || process.env.LANGFUSE_SECRET_KEY || "sk-lf-0269b85d-bfdc-442c-bfa3-e737954e3315";
|
|
45
|
-
const baseUrl = args.langfuseBaseUrl || process.env.LANGFUSE_BASEURL || "http://120.46.221.227:3000";
|
|
46
|
-
const userId = normalizeUserId(args.userId || args.userid || "");
|
|
47
|
-
if (!userId) {
|
|
48
|
-
throw new Error("Missing userId. Run with --userId=your-id.");
|
|
49
|
-
}
|
|
50
|
-
assertValidUserId(userId);
|
|
45
|
+
const baseUrl = args.langfuseBaseUrl || process.env.LANGFUSE_BASEURL || "http://120.46.221.227:3000";
|
|
46
|
+
const userId = normalizeUserId(args.userId || args.userid || "");
|
|
47
|
+
if (!userId) {
|
|
48
|
+
throw new Error("Missing userId. Run with --userId=your-id.");
|
|
49
|
+
}
|
|
50
|
+
assertValidUserId(userId);
|
|
51
51
|
|
|
52
52
|
// 1) 先执行 setup:默认会写入 Windows 用户级 LANGFUSE_*(从桌面/开始菜单启动 OpenCode 也能读到)
|
|
53
53
|
const scriptsDir = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -89,7 +89,7 @@ function main() {
|
|
|
89
89
|
|
|
90
90
|
console.log(`启动 OpenCode:${resolved} ${cmdArgs.join(" ")}`.trim() + ". userId: " + (userId || "<none>"));
|
|
91
91
|
const useShell = process.platform === "win32" && /\.(cmd|bat)$/i.test(resolved);
|
|
92
|
-
const r = spawnSync(resolved, cmdArgs, { stdio: "inherit", env, shell: useShell });
|
|
92
|
+
const r = spawnSync(resolved, cmdArgs, { stdio: "inherit", encoding: "utf8", env, shell: useShell });
|
|
93
93
|
if (r.error?.code === "ENOENT") {
|
|
94
94
|
console.error(
|
|
95
95
|
`无法执行 "${resolved}"。若这是 .cmd,请尝试:npm run opencode:langfuse:run -- --cmd=${path.join(
|
|
@@ -510,45 +510,45 @@ function getPatchedLangfuseDistIndexJs() {
|
|
|
510
510
|
" const processor = new LangfuseSpanProcessor({ publicKey, secretKey, baseUrl, environment });",
|
|
511
511
|
" const spanProcessors = [processor, createMetricsSpanProcessor(userId)];",
|
|
512
512
|
" if (userId) spanProcessors.push(createUserIdSpanProcessor(userId));",
|
|
513
|
-
"",
|
|
514
|
-
" const sdk = new NodeSDK({ spanProcessors });",
|
|
515
|
-
" const sdkStartPromise = Promise.resolve().then(() => sdk.start()).catch((err) => {",
|
|
516
|
-
' log("warn", `OTEL SDK start failed: ${err?.message ?? err}`);',
|
|
517
|
-
" });",
|
|
518
|
-
" const getMetricsTracer = () => trace.getTracer('oh-langfuse-opencode-metrics');",
|
|
519
|
-
" const knownSkillNames = await collectKnownSkillNames();",
|
|
520
|
-
" const startupSkillUsages = detectOpencodeSkillUsages(process.argv.join('\\n'), knownSkillNames);",
|
|
513
|
+
"",
|
|
514
|
+
" const sdk = new NodeSDK({ spanProcessors });",
|
|
515
|
+
" const sdkStartPromise = Promise.resolve().then(() => sdk.start()).catch((err) => {",
|
|
516
|
+
' log("warn", `OTEL SDK start failed: ${err?.message ?? err}`);',
|
|
517
|
+
" });",
|
|
518
|
+
" const getMetricsTracer = () => trace.getTracer('oh-langfuse-opencode-metrics');",
|
|
519
|
+
" const knownSkillNames = await collectKnownSkillNames();",
|
|
520
|
+
" const startupSkillUsages = detectOpencodeSkillUsages(process.argv.join('\\n'), knownSkillNames);",
|
|
521
521
|
" const messageTextById = new Map();",
|
|
522
522
|
" const skillUsagesByMessageId = new Map();",
|
|
523
523
|
" const skillUsagesBySessionId = new Map();",
|
|
524
524
|
" const startupSkillSessionIds = new Set();",
|
|
525
525
|
" const toolCallIdsByMessageId = new Map();",
|
|
526
526
|
" const toolCallIdsBySessionId = new Map();",
|
|
527
|
-
" const toolResultIdsByMessageId = new Map();",
|
|
528
|
-
" const toolResultIdsBySessionId = new Map();",
|
|
529
|
-
" const emittedMessageIds = new Set();",
|
|
530
|
-
"",
|
|
527
|
+
" const toolResultIdsByMessageId = new Map();",
|
|
528
|
+
" const toolResultIdsBySessionId = new Map();",
|
|
529
|
+
" const emittedMessageIds = new Set();",
|
|
530
|
+
"",
|
|
531
531
|
' log("info", `OTEL tracing initialized -> ${baseUrl}`);',
|
|
532
532
|
' if (userId) log("info", `LANGFUSE userId configured -> ${userId}`);',
|
|
533
533
|
' if (knownSkillNames.length) log("info", `OpenCode skills discovered -> ${knownSkillNames.length}`);',
|
|
534
534
|
"",
|
|
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();",
|
|
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();",
|
|
541
541
|
" } catch (error) {",
|
|
542
542
|
' log("warn", `OTEL forceFlush failed on ${reason}: ${error?.message ?? error}`);',
|
|
543
543
|
" }",
|
|
544
544
|
" };",
|
|
545
545
|
" const shutdown = async (reason) => {",
|
|
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();",
|
|
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 = async (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,8 +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
|
-
" await sdkStartPromise;",
|
|
621
|
-
" const span = getMetricsTracer().startSpan('OpenCode Agent Turn');",
|
|
620
|
+
" await sdkStartPromise;",
|
|
621
|
+
" const span = getMetricsTracer().startSpan('OpenCode Agent Turn');",
|
|
622
622
|
" const text = messageTextById.get(messageId) || '';",
|
|
623
623
|
" const skillUsages = dedupeSkillUsages([...(skillUsagesByMessageId.get(messageId) ?? []), ...(skillUsagesBySessionId.get(sessionId) ?? [])]);",
|
|
624
624
|
" const interactionId = `opencode:${userId || \"unknown\"}:${sessionId || \"unknown\"}:${messageId}`;",
|
|
@@ -674,9 +674,9 @@ function getPatchedLangfuseDistIndexJs() {
|
|
|
674
674
|
' log("warn", "OpenTelemetry experimental feature is disabled in Opencode config - tracing disabled");',
|
|
675
675
|
" }",
|
|
676
676
|
" },",
|
|
677
|
-
" event: async ({ event }) => {",
|
|
678
|
-
" const eventType = pickEventString(event?.name, event?.type, eventPayload(event)?.name, eventPayload(event)?.type);",
|
|
679
|
-
" await recordInteractionMetric(event);",
|
|
677
|
+
" event: async ({ event }) => {",
|
|
678
|
+
" const eventType = pickEventString(event?.name, event?.type, eventPayload(event)?.name, eventPayload(event)?.type);",
|
|
679
|
+
" await recordInteractionMetric(event);",
|
|
680
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") {',
|
|
681
681
|
" await flush(eventType);",
|
|
682
682
|
" }",
|
|
@@ -719,7 +719,7 @@ function writeWindowsLauncherCmd(opencodeDir, { publicKey, secretKey, baseUrl, u
|
|
|
719
719
|
cmd.push(" exit /b %ERRORLEVEL%");
|
|
720
720
|
cmd.push(")");
|
|
721
721
|
cmd.push("opencode %*");
|
|
722
|
-
fs.writeFileSync(p, cmd.join("\r\n") + "\r\n", "utf8");
|
|
722
|
+
fs.writeFileSync(p, "\ufeff" + cmd.join("\r\n") + "\r\n", "utf8");
|
|
723
723
|
return p;
|
|
724
724
|
}
|
|
725
725
|
|
|
@@ -754,7 +754,7 @@ function writeAutoUpdateHelper(target) {
|
|
|
754
754
|
"exit /b 0",
|
|
755
755
|
""
|
|
756
756
|
];
|
|
757
|
-
fs.writeFileSync(helper, lines.join(os.EOL), "utf8");
|
|
757
|
+
fs.writeFileSync(helper, "\ufeff" + lines.join(os.EOL), "utf8");
|
|
758
758
|
return helper;
|
|
759
759
|
}
|
|
760
760
|
|
|
@@ -802,7 +802,7 @@ function writeWindowsUpdateCheckScript(dir) {
|
|
|
802
802
|
"}",
|
|
803
803
|
""
|
|
804
804
|
].join("\r\n");
|
|
805
|
-
fs.writeFileSync(p, ps, "utf8");
|
|
805
|
+
fs.writeFileSync(p, "\ufeff" + ps, "utf8");
|
|
806
806
|
return p;
|
|
807
807
|
}
|
|
808
808
|
|
|
@@ -820,7 +820,7 @@ function writeOpencodeCommandShim(opencodeDir, { publicKey, secretKey, baseUrl,
|
|
|
820
820
|
cmd.push(windowsAutoUpdateCommand("opencode"));
|
|
821
821
|
cmd.push(`call ${cmdQuote(realOpencodeCli)} %*`);
|
|
822
822
|
cmd.push("exit /b %ERRORLEVEL%");
|
|
823
|
-
fs.writeFileSync(shim, cmd.join("\r\n") + "\r\n", "utf8");
|
|
823
|
+
fs.writeFileSync(shim, "\ufeff" + cmd.join("\r\n") + "\r\n", "utf8");
|
|
824
824
|
return { shim, shimDir };
|
|
825
825
|
}
|
|
826
826
|
|
|
@@ -925,17 +925,32 @@ function printNpmDiagnostics() {
|
|
|
925
925
|
} catch (_) {}
|
|
926
926
|
}
|
|
927
927
|
|
|
928
|
-
|
|
928
|
+
/**
|
|
929
|
+
* 重置终端状态,防止子进程异常退出后遗留 ANSI 转义序列。
|
|
930
|
+
*/
|
|
931
|
+
function resetTerminalState() {
|
|
932
|
+
if (process.stdout.isTTY) {
|
|
933
|
+
// \x1b[0m - 重置所有颜色和属性
|
|
934
|
+
// \x1b[?25h - 显示光标(防止 npm 隐藏光标后未恢复)
|
|
935
|
+
// \x1b[r - 重置滚动区域
|
|
936
|
+
process.stdout.write('\x1b[0m\x1b[?25h\x1b[r');
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
function runNpmInstallCapture(npmArgs, options = {}) {
|
|
929
941
|
return new Promise((resolve) => {
|
|
930
942
|
const cliJs = getNpmCliJsPath();
|
|
931
943
|
const npmExe = getNpmExecutable();
|
|
932
944
|
const command = fs.existsSync(cliJs) ? process.execPath : npmExe;
|
|
933
945
|
const args = fs.existsSync(cliJs) ? [cliJs, ...npmArgs] : npmArgs;
|
|
934
946
|
const useShell = process.platform === "win32" && !fs.existsSync(cliJs) && /\.(cmd|bat)$/i.test(npmExe);
|
|
947
|
+
const timeoutMs = options.timeoutMs ?? 600000; // 默认 10 分钟超时
|
|
948
|
+
|
|
935
949
|
const child = spawn(command, args, {
|
|
936
950
|
encoding: "utf8",
|
|
937
951
|
shell: useShell,
|
|
938
|
-
windowsHide: true
|
|
952
|
+
windowsHide: true,
|
|
953
|
+
timeout: timeoutMs
|
|
939
954
|
});
|
|
940
955
|
let stdout = "";
|
|
941
956
|
let stderr = "";
|
|
@@ -960,12 +975,23 @@ function runNpmInstallCapture(npmArgs) {
|
|
|
960
975
|
});
|
|
961
976
|
child.on("error", (error) => {
|
|
962
977
|
clearInterval(timer);
|
|
978
|
+
resetTerminalState();
|
|
963
979
|
resolve({ status: null, stdout, stderr, error });
|
|
964
980
|
});
|
|
965
981
|
child.on("close", (status) => {
|
|
966
982
|
clearInterval(timer);
|
|
983
|
+
if (status !== 0) {
|
|
984
|
+
resetTerminalState();
|
|
985
|
+
}
|
|
967
986
|
resolve({ status, stdout, stderr });
|
|
968
987
|
});
|
|
988
|
+
child.on("timeout", () => {
|
|
989
|
+
clearInterval(timer);
|
|
990
|
+
console.error("");
|
|
991
|
+
console.error(`npm install timed out after ${Math.round(timeoutMs / 1000)}s, killing process...`);
|
|
992
|
+
resetTerminalState();
|
|
993
|
+
child.kill('SIGTERM');
|
|
994
|
+
});
|
|
969
995
|
});
|
|
970
996
|
}
|
|
971
997
|
|
|
@@ -974,29 +1000,78 @@ function npmInstallLooksLikeMissingVersion(result) {
|
|
|
974
1000
|
return text.includes("etarget") || text.includes("notarget") || text.includes("no matching version found");
|
|
975
1001
|
}
|
|
976
1002
|
|
|
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
|
-
|
|
984
|
-
|
|
985
|
-
const
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
const
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1003
|
+
function isOfficialNpmRegistry(registry) {
|
|
1004
|
+
return /^https?:\/\/registry\.npmjs\.org\/?$/i.test(String(registry || "").trim());
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
const OFFICIAL_NPM_REGISTRY = "https://registry.npmjs.org/";
|
|
1008
|
+
const DEFAULT_NPM_REGISTRY = "https://registry.npmmirror.com/";
|
|
1009
|
+
|
|
1010
|
+
function isChinaNetwork() {
|
|
1011
|
+
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone || "";
|
|
1012
|
+
return tz.includes("Shanghai") || tz.includes("Chongqing") || tz.includes("Harbin");
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
async function checkInstalledVersion(opencodeDir, pkgName) {
|
|
1016
|
+
const pkgJsonPath = path.join(opencodeDir, "node_modules", pkgName, "package.json");
|
|
1017
|
+
if (!fs.existsSync(pkgJsonPath)) return null;
|
|
1018
|
+
try {
|
|
1019
|
+
const txt = stripBom(fs.readFileSync(pkgJsonPath, "utf8"));
|
|
1020
|
+
if (!txt.trim()) return null;
|
|
1021
|
+
const installed = parseJsonRelaxed(txt, pkgJsonPath);
|
|
1022
|
+
return installed?.version || null;
|
|
1023
|
+
} catch {
|
|
1024
|
+
return null;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
async function runNpmInstallOrThrow({ opencodeDir, pkgName = "opencode-plugin-langfuse", npmRegistry = "" }) {
|
|
1029
|
+
const installedVersion = await checkInstalledVersion(opencodeDir, pkgName);
|
|
1030
|
+
const currentVersion = packageJson.version;
|
|
1031
|
+
|
|
1032
|
+
if (installedVersion === currentVersion) {
|
|
1033
|
+
console.log(`插件版本 ${currentVersion} 已安装,跳过 npm install ...`);
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
if (installedVersion) {
|
|
1038
|
+
console.log(`插件版本 ${installedVersion} 已安装,升级到 ${currentVersion} ...`);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
const isOfficial = npmRegistry && isOfficialNpmRegistry(npmRegistry);
|
|
1042
|
+
const effectiveRegistry = npmRegistry || (isChinaNetwork() ? DEFAULT_NPM_REGISTRY : OFFICIAL_NPM_REGISTRY);
|
|
1043
|
+
|
|
1044
|
+
const npmArgs = [
|
|
1045
|
+
"install", pkgName, "--prefix", opencodeDir,
|
|
1046
|
+
"--package-lock=false", "--no-save",
|
|
1047
|
+
"--audit=false", "--fund=false",
|
|
1048
|
+
"--prefer-offline",
|
|
1049
|
+
"--registry", effectiveRegistry
|
|
1050
|
+
];
|
|
1051
|
+
|
|
1052
|
+
const cliJs = getNpmCliJsPath();
|
|
1053
|
+
console.log(`使用 npm:${fs.existsSync(cliJs) ? `node ${cliJs}` : getNpmExecutable()}`);
|
|
1054
|
+
console.log(`使用 npm registry:${effectiveRegistry}`);
|
|
1055
|
+
console.log("Installing OpenCode Langfuse plugin. This can take a few minutes on slow networks...");
|
|
1056
|
+
|
|
1057
|
+
let r = await runNpmInstallCapture(npmArgs, { timeoutMs: 600000 });
|
|
1058
|
+
|
|
1059
|
+
if (!r.error && r.status !== 0 && npmInstallLooksLikeMissingVersion(r) && !isOfficialNpmRegistry(effectiveRegistry)) {
|
|
1060
|
+
console.error("");
|
|
1061
|
+
console.error(`npm registry appears to be missing a package version. Retrying with ${OFFICIAL_NPM_REGISTRY} ...`);
|
|
1062
|
+
const retryArgs = [
|
|
1063
|
+
"install", pkgName, "--prefix", opencodeDir,
|
|
1064
|
+
"--package-lock=false", "--no-save",
|
|
1065
|
+
"--audit=false", "--fund=false",
|
|
1066
|
+
"--prefer-offline",
|
|
1067
|
+
"--registry", OFFICIAL_NPM_REGISTRY
|
|
1068
|
+
];
|
|
1069
|
+
r = await runNpmInstallCapture(retryArgs, { timeoutMs: 600000 });
|
|
1070
|
+
if (!r.error && r.status === 0) return;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
999
1073
|
if (!r.error && r.status === 0) return;
|
|
1074
|
+
|
|
1000
1075
|
printNpmDiagnostics();
|
|
1001
1076
|
const npmLabel = fs.existsSync(cliJs) ? `node ${cliJs}` : getNpmExecutable();
|
|
1002
1077
|
throw new Error(
|
|
@@ -1011,12 +1086,13 @@ function setWindowsUserEnv({ publicKey, secretKey, baseUrl }) {
|
|
|
1011
1086
|
`[Environment]::SetEnvironmentVariable('LANGFUSE_PUBLIC_KEY', ${psQuote(publicKey)}, 'User');`,
|
|
1012
1087
|
`[Environment]::SetEnvironmentVariable('LANGFUSE_SECRET_KEY', ${psQuote(secretKey)}, 'User');`,
|
|
1013
1088
|
`[Environment]::SetEnvironmentVariable('LANGFUSE_BASEURL', ${psQuote(baseUrl)}, 'User');`
|
|
1014
|
-
|
|
1015
|
-
const r = spawnSync("powershell", ["-NoProfile", "-Command", cmd], { stdio: "inherit" });
|
|
1089
|
+
].join(" ");
|
|
1090
|
+
const r = spawnSync("powershell", ["-NoProfile", "-Command", cmd], { stdio: "inherit", encoding: "utf8" });
|
|
1016
1091
|
if (r.status !== 0) {
|
|
1017
|
-
throw new Error("
|
|
1092
|
+
throw new Error("写入用户级环境变量失败。");
|
|
1018
1093
|
}
|
|
1019
1094
|
}
|
|
1095
|
+
}
|
|
1020
1096
|
|
|
1021
1097
|
function prependWindowsUserPath(dir) {
|
|
1022
1098
|
if (process.platform !== "win32") return false;
|
|
@@ -1033,7 +1109,7 @@ function prependWindowsUserPath(dir) {
|
|
|
1033
1109
|
" [Environment]::SetEnvironmentVariable('Path', $next, 'User');",
|
|
1034
1110
|
"}"
|
|
1035
1111
|
].join(" ");
|
|
1036
|
-
const r = spawnSync("powershell", ["-NoProfile", "-Command", cmd], { stdio: "inherit" });
|
|
1112
|
+
const r = spawnSync("powershell", ["-NoProfile", "-Command", cmd], { stdio: "inherit", encoding: "utf8" });
|
|
1037
1113
|
if (r.status !== 0) {
|
|
1038
1114
|
throw new Error("写入用户 PATH 失败:无法把 OpenCode Langfuse shim 加入 PATH。");
|
|
1039
1115
|
}
|
|
@@ -1110,7 +1186,7 @@ async function main() {
|
|
|
1110
1186
|
throw new Error("缺少参数:--userId=你的工号");
|
|
1111
1187
|
}
|
|
1112
1188
|
assertValidUserId(userId);
|
|
1113
|
-
const npmRegistry = args.npmRegistry || process.env.OPENCODE_NPM_REGISTRY || "";
|
|
1189
|
+
const npmRegistry = args.npmRegistry || process.env.OPENCODE_NPM_REGISTRY || "";
|
|
1114
1190
|
|
|
1115
1191
|
const home = os.homedir();
|
|
1116
1192
|
const opencodeDir = path.join(home, ".config", "opencode");
|
|
@@ -1213,7 +1289,7 @@ async function main() {
|
|
|
1213
1289
|
"$ErrorActionPreference = 'Stop';",
|
|
1214
1290
|
`[Environment]::SetEnvironmentVariable('LANGFUSE_USER_ID', ${psQuote(userId)}, 'User');`
|
|
1215
1291
|
].join(" ");
|
|
1216
|
-
spawnSync("powershell", ["-NoProfile", "-Command", cmd], { stdio: "inherit" });
|
|
1292
|
+
spawnSync("powershell", ["-NoProfile", "-Command", cmd], { stdio: "inherit", encoding: "utf8" });
|
|
1217
1293
|
}
|
|
1218
1294
|
if (opencodeShim?.shimDir) {
|
|
1219
1295
|
prependWindowsUserPath(opencodeShim.shimDir);
|