ai-lab-bridge 0.1.3 → 0.1.5
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/index.js +70 -20
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -136,6 +136,7 @@ const BRIDGE_VERSION = (() => {
|
|
|
136
136
|
// 连接参数由各子命令解析填充(connect/daemon 需要,status/stop 不需要)
|
|
137
137
|
let SERVER = "";
|
|
138
138
|
let TOKEN = "";
|
|
139
|
+
let sessionCwd = ""; // 当前任务的实际工作目录(emitSession 回传用)
|
|
139
140
|
|
|
140
141
|
// ---------------------------------------------------------------------------
|
|
141
142
|
// CLI 能力探测
|
|
@@ -234,7 +235,11 @@ class Session {
|
|
|
234
235
|
emitSession(cliSessionId) {
|
|
235
236
|
if (!cliSessionId || this.cliSessionId) return;
|
|
236
237
|
this.cliSessionId = cliSessionId;
|
|
237
|
-
this.emitEvent("session", {
|
|
238
|
+
this.emitEvent("session", {
|
|
239
|
+
cli_session_id: cliSessionId,
|
|
240
|
+
// 设备实际执行的工作目录(CLI cwd 或 bridge 进程启动目录),平台前端展示用
|
|
241
|
+
working_dir: sessionCwd || null,
|
|
242
|
+
});
|
|
238
243
|
}
|
|
239
244
|
|
|
240
245
|
/**
|
|
@@ -262,11 +267,26 @@ class Session {
|
|
|
262
267
|
}
|
|
263
268
|
}
|
|
264
269
|
|
|
270
|
+
/** 归一化后缓存 + 回传 usage:各家 CLI 字段名不同(input_tokens/input/prompt_tokens...),统一为平台口径 */
|
|
265
271
|
emitUsage(usage) {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
}
|
|
272
|
+
const num = (v) => (typeof v === "number" && isFinite(v) ? v : undefined);
|
|
273
|
+
const raw = usage && typeof usage === "object" ? usage : null;
|
|
274
|
+
if (!raw) return;
|
|
275
|
+
// opencode 系: {input, output, cache:{read}} / claude: {input_tokens, output_tokens, cache_read_input_tokens}
|
|
276
|
+
// codex: {input_tokens, output_tokens, total_tokens} / gemini: {inputTokenCount, outputTokenCount, ...}
|
|
277
|
+
const input = num(raw.input_tokens) ?? num(raw.input) ?? num(raw.prompt_tokens) ?? num(raw.inputTokenCount);
|
|
278
|
+
const output =
|
|
279
|
+
num(raw.output_tokens) ?? num(raw.output) ?? num(raw.completion_tokens) ?? num(raw.outputTokenCount) ?? num(raw.candidatesTokenCount);
|
|
280
|
+
const cacheRead = num(raw.cache_read_input_tokens) ?? num(raw.cache && raw.cache.read) ?? num(raw.cachedContentTokenCount);
|
|
281
|
+
if (input === undefined && output === undefined && cacheRead === undefined) return;
|
|
282
|
+
const normalized = {
|
|
283
|
+
input_tokens: input ?? null,
|
|
284
|
+
output_tokens: output ?? null,
|
|
285
|
+
total_tokens: input !== undefined || output !== undefined ? (input ?? 0) + (output ?? 0) : null,
|
|
286
|
+
cache_read_input_tokens: cacheRead ?? null,
|
|
287
|
+
};
|
|
288
|
+
this.usage = normalized;
|
|
289
|
+
this.emitEvent("usage", { usage: normalized });
|
|
270
290
|
}
|
|
271
291
|
|
|
272
292
|
emitToolUse(summary) {
|
|
@@ -467,6 +487,9 @@ function handleOpencodeLine(session, obj) {
|
|
|
467
487
|
session.emitToolUse(part.tool || part.name || (state && state.title));
|
|
468
488
|
}
|
|
469
489
|
}
|
|
490
|
+
// usage 提取:opencode 系在 step_finish 的 part.tokens / 顶层 usage 携带本轮 token 统计
|
|
491
|
+
const usageRaw = (part && (part.tokens || part.usage)) || obj.tokens || obj.usage;
|
|
492
|
+
if (usageRaw && typeof usageRaw === "object") session.emitUsage(usageRaw);
|
|
470
493
|
if (obj.type === "error" || (obj.error && typeof obj.error === "object")) {
|
|
471
494
|
const err = obj.error && typeof obj.error === "object" ? obj.error : null;
|
|
472
495
|
// 提取可读错误消息(opencode 系: error.data.message / error.message;附带 name/ref 便于定位)
|
|
@@ -546,8 +569,10 @@ function handleCursorLine(session, obj) {
|
|
|
546
569
|
|
|
547
570
|
/**
|
|
548
571
|
* Gemini CLI 家族事件解析(gemini / qwen / iflow 的 `-p --output-format stream-json`,JSONL)。
|
|
549
|
-
*
|
|
550
|
-
*
|
|
572
|
+
* 事件格式两代并存,宽松归一化:
|
|
573
|
+
* - Gemini 原生:message(text/content) / tool_use / result(response, stats)
|
|
574
|
+
* - Claude 风格(qwen 0.24+ 同源仿制):assistant(message.content[]) / tool_call / result(result, usage)
|
|
575
|
+
* 终态以进程退出码兜底。
|
|
551
576
|
*/
|
|
552
577
|
function handleGeminiLine(session, obj) {
|
|
553
578
|
if (!obj || typeof obj !== "object") return;
|
|
@@ -557,28 +582,50 @@ function handleGeminiLine(session, obj) {
|
|
|
557
582
|
if (obj.type === "message" || obj.type === "assistant") {
|
|
558
583
|
const role = obj.role || (obj.message && obj.message.role);
|
|
559
584
|
if (role === "user") return;
|
|
585
|
+
// 文本提取顺序:obj.text → obj.content → obj.message.content[](Claude 风格嵌套)
|
|
560
586
|
let text = obj.text;
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
587
|
+
let blocks = obj.content;
|
|
588
|
+
if (!blocks && obj.message && Array.isArray(obj.message.content)) {
|
|
589
|
+
blocks = obj.message.content;
|
|
590
|
+
}
|
|
591
|
+
if (!text && blocks) {
|
|
592
|
+
if (typeof blocks === "string") text = blocks;
|
|
593
|
+
else if (Array.isArray(blocks)) {
|
|
594
|
+
const parts = [];
|
|
595
|
+
for (const b of blocks) {
|
|
596
|
+
if (!b) continue;
|
|
597
|
+
if (b.type === "text" && b.text) {
|
|
598
|
+
session.endThink();
|
|
599
|
+
parts.push(b.text);
|
|
600
|
+
} else if (b.type === "thinking" && b.thinking) {
|
|
601
|
+
session.emitThink(b.thinking);
|
|
602
|
+
} else if (typeof b === "string") {
|
|
603
|
+
parts.push(b);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
text = parts.join("");
|
|
568
607
|
}
|
|
569
608
|
}
|
|
570
609
|
if (text) session.emitText(text);
|
|
571
610
|
} else if (obj.type === "tool_use" || obj.type === "tool_call") {
|
|
572
611
|
session.emitToolUse(obj.name || obj.tool_name || obj.tool || "tool");
|
|
573
612
|
} else if (obj.type === "result") {
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
613
|
+
// usage:gemini 原生在 stats,qwen/claude 风格在 usage
|
|
614
|
+
session.emitUsage(obj.stats || obj.usage);
|
|
615
|
+
const failed = obj.error || obj.is_error || (obj.subtype && obj.subtype !== "success");
|
|
616
|
+
if (failed) {
|
|
617
|
+
const msg = typeof obj.error === "string"
|
|
618
|
+
? obj.error
|
|
619
|
+
: (obj.error && obj.error.message)
|
|
620
|
+
|| (typeof obj.result === "string" ? obj.result : undefined)
|
|
621
|
+
|| `执行异常: ${obj.subtype || "unknown"}`;
|
|
577
622
|
session.finish("failed", msg);
|
|
578
623
|
return;
|
|
579
624
|
}
|
|
580
|
-
|
|
581
|
-
|
|
625
|
+
// 终态文本兜底:gemini 原生在 response,qwen/claude 风格在 result
|
|
626
|
+
const finalText = (typeof obj.response === "string" && obj.response) || (typeof obj.result === "string" && obj.result);
|
|
627
|
+
if (finalText && !session.textParts.length) {
|
|
628
|
+
session.emitText(finalText);
|
|
582
629
|
}
|
|
583
630
|
session.finish("completed");
|
|
584
631
|
}
|
|
@@ -720,6 +767,8 @@ function startSession(message) {
|
|
|
720
767
|
|
|
721
768
|
const timeoutSeconds = Number(options.timeout_seconds) > 0 ? Number(options.timeout_seconds) : 0;
|
|
722
769
|
const cwd = typeof options.cwd === "string" && options.cwd ? options.cwd : process.cwd();
|
|
770
|
+
// 供 emitSession 回传实际工作目录(Session 构造在 cwd 解析之前,用模块级变量传递)
|
|
771
|
+
sessionCwd = cwd;
|
|
723
772
|
let argv;
|
|
724
773
|
|
|
725
774
|
let promptViaStdin = true;
|
|
@@ -942,7 +991,8 @@ async function connect() {
|
|
|
942
991
|
for (const cap of capabilities) {
|
|
943
992
|
console.log(`[bridge] ${cap.cli}: ${cap.available ? `可用 (${cap.version})` : "未安装"}`);
|
|
944
993
|
}
|
|
945
|
-
|
|
994
|
+
// working_dir:bridge 进程启动目录,作为未配置 cwd 时任务的默认工作目录(平台前端展示)
|
|
995
|
+
send({ type: "register", capabilities, working_dir: process.cwd() });
|
|
946
996
|
startHeartbeat();
|
|
947
997
|
} else if (message.type === "registered") {
|
|
948
998
|
console.log("[bridge] 能力注册完成,等待任务下发");
|