first commit
This commit is contained in:
60
demo/shared.js
Normal file
60
demo/shared.js
Normal file
@@ -0,0 +1,60 @@
|
||||
// 三个角色共用的小工具:trace 记录、Session 规则。
|
||||
// trace 是这次演示的“证据”:每个进程把自己做了什么追加到 TRACE_DIR/<角色>.jsonl。
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
function tracer(role) {
|
||||
const dir = process.env.TRACE_DIR;
|
||||
const file = dir ? path.join(dir, `${role}.jsonl`) : null;
|
||||
return (kind, data = {}) => {
|
||||
const rec = { t: Date.now(), role, pid: process.pid, host: process.env.HOST_LABEL || role, kind, ...data };
|
||||
if (file) fs.appendFileSync(file, JSON.stringify(rec) + '\n');
|
||||
return rec;
|
||||
};
|
||||
}
|
||||
|
||||
// 只看事件日志,判断这个 Session 下一步要做什么。
|
||||
// 云端 Worker 和调度器都用它:Worker 不需要记住任何东西,日志就是全部状态。
|
||||
// { kind: 'idle' } 没事可做
|
||||
// { kind: 'dispatch', calls: [...] } 模型要的工具调用还没有结果,要发给设备
|
||||
// { kind: 'model' } 有新输入(用户消息 / 工具结果 / 后台完成),该调模型了
|
||||
function pendingWork(events) {
|
||||
let lastModel = -1;
|
||||
for (let i = events.length - 1; i >= 0; i--) {
|
||||
if (events[i].type === 'model_response') { lastModel = i; break; }
|
||||
}
|
||||
if (lastModel >= 0) {
|
||||
const calls = events[lastModel].content.filter((b) => b.type === 'tool_use');
|
||||
const done = new Set(events.filter((e) => e.type === 'tool_result').map((e) => e.call_id));
|
||||
const open = calls.filter((c) => !done.has(c.id));
|
||||
if (open.length) return { kind: 'dispatch', calls: open };
|
||||
}
|
||||
const fresh = events.slice(lastModel + 1).some((e) => ['user_message', 'tool_result', 'job_finished'].includes(e.type));
|
||||
return fresh ? { kind: 'model' } : { kind: 'idle' };
|
||||
}
|
||||
|
||||
// 把事件日志翻译成模型 API 的 messages。每次调模型都从头重建,所以换一个进程来跑结果完全一样。
|
||||
function rebuildMessages(events) {
|
||||
const msgs = [];
|
||||
const pushUser = (block) => {
|
||||
const last = msgs[msgs.length - 1];
|
||||
if (last && last.role === 'user') last.content.push(block);
|
||||
else msgs.push({ role: 'user', content: [block] });
|
||||
};
|
||||
for (const e of events) {
|
||||
if (e.type === 'user_message') pushUser({ type: 'text', text: e.text });
|
||||
else if (e.type === 'model_response') msgs.push({ role: 'assistant', content: e.content });
|
||||
else if (e.type === 'tool_result') {
|
||||
const block = { type: 'tool_result', tool_use_id: e.call_id, content: e.output };
|
||||
const last = msgs[msgs.length - 1];
|
||||
// tool_result 必须紧跟在对应的 assistant 之后,排在同一轮里其它输入的前面
|
||||
if (last && last.role === 'user') last.content.unshift(block);
|
||||
else msgs.push({ role: 'user', content: [block] });
|
||||
} else if (e.type === 'job_finished') {
|
||||
pushUser({ type: 'text', text: `[后台任务完成] ${e.job_id}:\`${e.command}\` 退出码 ${e.exit_code}\n输出末尾:\n${e.tail}` });
|
||||
}
|
||||
}
|
||||
return msgs;
|
||||
}
|
||||
|
||||
module.exports = { tracer, pendingWork, rebuildMessages };
|
||||
Reference in New Issue
Block a user