@mazjq/zhiro 0.0.0-stage → 0.1.2
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/LICENSE +21 -0
- package/README.md +156 -2
- package/README.zh-CN.md +132 -0
- package/agent-skills/zhiro/SKILL.md +83 -0
- package/bin/zhiro.cjs +21 -0
- package/dist/acp-bridge.js +87 -0
- package/dist/chunk-2SSYR7VW.js +558 -0
- package/dist/chunk-4JJEOOW2.js +107 -0
- package/dist/chunk-5KCN32HQ.js +517 -0
- package/dist/chunk-F6DLONN7.js +328 -0
- package/dist/chunk-J2D7DYGW.js +680 -0
- package/dist/chunk-LH25QWMP.js +1154 -0
- package/dist/chunk-LUKCNS5M.js +180 -0
- package/dist/chunk-NNH4ZG6F.js +6 -0
- package/dist/chunk-OOHBM3BT.js +6 -0
- package/dist/chunk-UMCKC7XP.js +819 -0
- package/dist/chunk-YJNBYSVM.js +3738 -0
- package/dist/chunk-YYNUPNBH.js +2474 -0
- package/dist/chunk-ZL2RKXOF.js +44 -0
- package/dist/cli-WO7FHPTE.js +131 -0
- package/dist/login-2WAH5KPP.js +75 -0
- package/dist/main-KCG27VLD.js +7463 -0
- package/dist/research-data.js +46 -0
- package/dist/stdio-FZZLZI4P.js +32 -0
- package/dist/vault-7KJ34T7Q.js +117 -0
- package/dist/zhiro.js +62 -0
- package/package.json +57 -4
- package/ui/assets/core-CwxXejkd.js +1 -0
- package/ui/assets/index-DUzC3BlF.js +11 -0
- package/ui/assets/index-y_0yp8U5.css +2 -0
- package/ui/index.html +15 -0
- package/ui/tauri.svg +6 -0
- package/ui/vite.svg +1 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DomainError,
|
|
3
|
+
NotFound
|
|
4
|
+
} from "./chunk-LH25QWMP.js";
|
|
5
|
+
import {
|
|
6
|
+
now
|
|
7
|
+
} from "./chunk-5KCN32HQ.js";
|
|
8
|
+
import {
|
|
9
|
+
__name
|
|
10
|
+
} from "./chunk-2SSYR7VW.js";
|
|
11
|
+
|
|
12
|
+
// apps/daemon/src/service/usage.ts
|
|
13
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
14
|
+
import { ulid } from "ulid";
|
|
15
|
+
var scope = new AsyncLocalStorage();
|
|
16
|
+
var withPurpose = /* @__PURE__ */ __name((purpose, ref, fn) => scope.run({ purpose, ref, caller: scope.getStore()?.caller }, fn), "withPurpose");
|
|
17
|
+
var currentPurpose = /* @__PURE__ */ __name(() => scope.getStore() ?? { purpose: "其他", ref: "" }, "currentPurpose");
|
|
18
|
+
async function asAgentCall(via, fn) {
|
|
19
|
+
const caller = { via: via.slice(0, 32), live: true };
|
|
20
|
+
return scope.run({ purpose: "其他", ref: "", caller }, async () => {
|
|
21
|
+
try {
|
|
22
|
+
return await fn();
|
|
23
|
+
} finally {
|
|
24
|
+
caller.live = false;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
__name(asAgentCall, "asAgentCall");
|
|
29
|
+
var callingAgent = /* @__PURE__ */ __name(() => {
|
|
30
|
+
const c = scope.getStore()?.caller;
|
|
31
|
+
return c?.live ? c.via : null;
|
|
32
|
+
}, "callingAgent");
|
|
33
|
+
function recordUsage(db, msg, provider, model) {
|
|
34
|
+
const u = msg?.usage;
|
|
35
|
+
if (!u) return;
|
|
36
|
+
const s = scope.getStore() ?? { purpose: "其他", ref: "" };
|
|
37
|
+
db.prepare("insert into llm_usage(id, at, purpose, ref, provider, model, input, output, cache_read, total, cost) values (?,?,?,?,?,?,?,?,?,?,?)").run(ulid(), (/* @__PURE__ */ new Date()).toISOString(), s.purpose, s.ref, provider, model, u.input ?? 0, u.output ?? 0, u.cacheRead ?? 0, u.totalTokens ?? (u.input ?? 0) + (u.output ?? 0), u.cost?.total ?? 0);
|
|
38
|
+
}
|
|
39
|
+
__name(recordUsage, "recordUsage");
|
|
40
|
+
var LABEL = {
|
|
41
|
+
"apply.start": "网申填写",
|
|
42
|
+
"apply.read": "网申读表",
|
|
43
|
+
"profile.import": "简历/材料抽取",
|
|
44
|
+
"profile.scan": "简历/材料抽取",
|
|
45
|
+
"document.reextract": "简历/材料抽取",
|
|
46
|
+
"resume.save": "简历/材料抽取",
|
|
47
|
+
"resume.analyze": "简历分析",
|
|
48
|
+
"inbox.pull": "收件箱分类",
|
|
49
|
+
"inbox.paste": "收件箱分类",
|
|
50
|
+
"inbox.poll": "收件箱分类",
|
|
51
|
+
"inbox.rescue": "收件箱分类",
|
|
52
|
+
"listing.capture": "简章抽取",
|
|
53
|
+
"chat": "助手",
|
|
54
|
+
"llm.test": "连通测试",
|
|
55
|
+
"company.find": "找公司入口"
|
|
56
|
+
};
|
|
57
|
+
function usageSummary(db) {
|
|
58
|
+
const since = /* @__PURE__ */ __name((days) => new Date(Date.now() - days * 864e5).toISOString(), "since");
|
|
59
|
+
const rows = db.prepare(`select purpose, count(*) as calls, sum(input) as input, sum(output) as output, sum(total) as total, sum(cost) as cost,
|
|
60
|
+
count(distinct nullif(ref, '')) as jobs, sum(case when at >= ? then total else 0 end) as week
|
|
61
|
+
from llm_usage group by purpose order by total desc`).all(since(7));
|
|
62
|
+
const merged = /* @__PURE__ */ new Map();
|
|
63
|
+
for (const r of rows) {
|
|
64
|
+
const label = LABEL[r.purpose] ?? r.purpose;
|
|
65
|
+
const m = merged.get(label) ?? { purpose: label, calls: 0, input: 0, output: 0, total: 0, cost: 0, jobs: 0, week: 0 };
|
|
66
|
+
for (const k of ["calls", "input", "output", "total", "cost", "jobs", "week"]) m[k] += r[k] ?? 0;
|
|
67
|
+
merged.set(label, m);
|
|
68
|
+
}
|
|
69
|
+
const items = [...merged.values()].map((m) => ({ ...m, perJob: m.jobs ? Math.round(m.total / m.jobs) : Math.round(m.total / Math.max(m.calls, 1)) }));
|
|
70
|
+
const t = db.prepare("select count(*) as calls, coalesce(sum(total), 0) as total, coalesce(sum(cost), 0) as cost, min(at) as since from llm_usage").get();
|
|
71
|
+
return { items, total: { calls: t.calls, total: t.total, cost: t.cost, since: t.since ?? null } };
|
|
72
|
+
}
|
|
73
|
+
__name(usageSummary, "usageSummary");
|
|
74
|
+
|
|
75
|
+
// apps/daemon/src/llm.ts
|
|
76
|
+
import { existsSync as existsSync2, readFileSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, renameSync } from "node:fs";
|
|
77
|
+
import { dirname, join as join2 } from "node:path";
|
|
78
|
+
import { builtinModels } from "@earendil-works/pi-ai/providers/all";
|
|
79
|
+
|
|
80
|
+
// apps/daemon/src/service/model-jobs.ts
|
|
81
|
+
import { createHash } from "node:crypto";
|
|
82
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
83
|
+
import { join } from "node:path";
|
|
84
|
+
import { ulid as ulid2 } from "ulid";
|
|
85
|
+
var row = /* @__PURE__ */ __name((r) => ({ id: r.id, kind: r.kind, purpose: r.purpose, ref: r.ref, system: r.system, user: r.user_text, status: r.status, createdAt: r.created_at, caller: r.caller ?? "" }), "row");
|
|
86
|
+
var ModelDelegated = class extends DomainError {
|
|
87
|
+
constructor(job) {
|
|
88
|
+
super("MODEL_DELEGATED", `等外部 agent 代答:llm.pending 查看、llm.reply 回答(任务 ${job.id})`);
|
|
89
|
+
this.job = job;
|
|
90
|
+
}
|
|
91
|
+
job;
|
|
92
|
+
static {
|
|
93
|
+
__name(this, "ModelDelegated");
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
var EXT = { "image/png": "png", "image/jpeg": "jpg", "image/webp": "webp", "image/gif": "gif" };
|
|
97
|
+
function promptOf(ctx) {
|
|
98
|
+
const parts = [], images = [];
|
|
99
|
+
for (const m of ctx.messages ?? []) {
|
|
100
|
+
for (const c of typeof m.content === "string" ? [{ type: "text", text: m.content }] : m.content ?? []) {
|
|
101
|
+
if (c.type === "text") parts.push(c.text);
|
|
102
|
+
else if (c.type === "image" && EXT[c.mimeType]) images.push({ data: c.data, mimeType: c.mimeType, sha: createHash("sha256").update(String(c.data)).digest("hex").slice(0, 16) });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return { text: parts.join("\n\n"), images };
|
|
106
|
+
}
|
|
107
|
+
__name(promptOf, "promptOf");
|
|
108
|
+
function delegateComplete(db, ctx, o = {}) {
|
|
109
|
+
const system = String(ctx.systemPrompt ?? ""), p = promptOf(ctx);
|
|
110
|
+
const hash = createHash("sha256").update(system + "\0" + p.text.replace(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z/g, "") + "\0" + p.images.map((i) => i.sha).join(",")).digest("hex");
|
|
111
|
+
const r = db.prepare("select * from model_job where hash = ?").get(hash);
|
|
112
|
+
if (r && r.status !== "pending") {
|
|
113
|
+
db.prepare("update model_job set status = 'used' where id = ?").run(r.id);
|
|
114
|
+
return {
|
|
115
|
+
role: "assistant",
|
|
116
|
+
content: [{ type: "text", text: r.answer ?? "" }],
|
|
117
|
+
api: "external",
|
|
118
|
+
provider: "external",
|
|
119
|
+
model: "agent",
|
|
120
|
+
stopReason: "stop",
|
|
121
|
+
timestamp: Date.now(),
|
|
122
|
+
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (r) throw new ModelDelegated(row(r));
|
|
126
|
+
const { purpose, ref } = currentPurpose();
|
|
127
|
+
const id = ulid2();
|
|
128
|
+
let user = p.text;
|
|
129
|
+
if (p.images.length) {
|
|
130
|
+
if (o.imagesDir) {
|
|
131
|
+
const dir = join(o.imagesDir, id);
|
|
132
|
+
mkdirSync(dir, { recursive: true });
|
|
133
|
+
const files = p.images.map((im, i) => {
|
|
134
|
+
const f = join(dir, `image-${i + 1}.${EXT[im.mimeType]}`);
|
|
135
|
+
writeFileSync(f, Buffer.from(im.data, "base64"));
|
|
136
|
+
return f;
|
|
137
|
+
});
|
|
138
|
+
user += `
|
|
139
|
+
|
|
140
|
+
(随附 ${files.length} 张图片,用你自己的读图能力打开:
|
|
141
|
+
${files.join("\n")})`;
|
|
142
|
+
} else user += `
|
|
143
|
+
|
|
144
|
+
(原请求随附 ${p.images.length} 张图片未转发)`;
|
|
145
|
+
}
|
|
146
|
+
db.prepare("insert into model_job(id, kind, hash, purpose, ref, system, user_text, created_at, caller) values (?,?,?,?,?,?,?,?,?)").run(id, "complete", hash, purpose, ref, system, user, now(), o.caller ?? "");
|
|
147
|
+
throw new ModelDelegated(row(db.prepare("select * from model_job where id = ?").get(id)));
|
|
148
|
+
}
|
|
149
|
+
__name(delegateComplete, "delegateComplete");
|
|
150
|
+
function delegateRun(db, runId, mode, prompt, schema, caller = "") {
|
|
151
|
+
db.prepare("insert into model_job(id, kind, purpose, system, user_text, created_at, caller) values (?,?,?,?,?,?,?)").run(runId, "run", mode, `自己联网调研后,调 llm.reply 提交一个符合下面 JSON Schema 的 JSON 对象(text 就是这个 JSON):
|
|
152
|
+
${JSON.stringify(schema)}`, prompt, now(), caller);
|
|
153
|
+
}
|
|
154
|
+
__name(delegateRun, "delegateRun");
|
|
155
|
+
var pendingJobs = /* @__PURE__ */ __name((db, caller) => ({
|
|
156
|
+
items: (caller === void 0 ? db.prepare("select * from model_job where status = 'pending' order by created_at").all() : db.prepare("select * from model_job where status = 'pending' and caller in ('', ?) order by created_at").all(caller)).map(row)
|
|
157
|
+
}), "pendingJobs");
|
|
158
|
+
function replyJob(db, jobId, text) {
|
|
159
|
+
const r = db.prepare("select * from model_job where id = ?").get(jobId);
|
|
160
|
+
if (!r) throw new NotFound(`model job ${jobId}`);
|
|
161
|
+
if (r.kind === "run") {
|
|
162
|
+
let structured;
|
|
163
|
+
try {
|
|
164
|
+
structured = JSON.parse(text.trim().replace(/^```(?:json)?\s*|\s*```$/g, ""));
|
|
165
|
+
} catch {
|
|
166
|
+
throw new DomainError("BAD_ANSWER", "调研结果要是一个 JSON 对象(按任务里的 Schema)");
|
|
167
|
+
}
|
|
168
|
+
const run = db.prepare("select run_dir from agent_run where id = ?").get(jobId);
|
|
169
|
+
if (!run || !existsSync(run.run_dir)) throw new NotFound(`run ${jobId}`);
|
|
170
|
+
writeFileSync(join(run.run_dir, "out.json"), JSON.stringify({ structured_output: structured, result: "", session_id: "", total_cost_usd: 0 }), "utf8");
|
|
171
|
+
writeFileSync(join(run.run_dir, "exit.txt"), "0", "utf8");
|
|
172
|
+
}
|
|
173
|
+
db.prepare("update model_job set status = 'answered', answer = ?, answered_at = ? where id = ?").run(text, now(), jobId);
|
|
174
|
+
return row(db.prepare("select * from model_job where id = ?").get(jobId));
|
|
175
|
+
}
|
|
176
|
+
__name(replyJob, "replyJob");
|
|
177
|
+
function oldestWaitingMail(db) {
|
|
178
|
+
return db.prepare("select min(ref) as r from model_job where purpose like 'inbox%' and status in ('pending', 'answered') and ref != ''").get()?.r ?? null;
|
|
179
|
+
}
|
|
180
|
+
__name(oldestWaitingMail, "oldestWaitingMail");
|
|
181
|
+
|
|
182
|
+
// apps/daemon/src/llm.ts
|
|
183
|
+
var EXTERNAL = "external";
|
|
184
|
+
var isExternal = /* @__PURE__ */ __name((provider) => !provider || provider === "none" || provider === EXTERNAL, "isExternal");
|
|
185
|
+
var EXTERNAL_MODEL = { id: "agent", name: "外部 agent", provider: EXTERNAL, api: "external", input: ["text"], reasoning: false };
|
|
186
|
+
var FileCredentialStore = class {
|
|
187
|
+
constructor(path) {
|
|
188
|
+
this.path = path;
|
|
189
|
+
}
|
|
190
|
+
path;
|
|
191
|
+
static {
|
|
192
|
+
__name(this, "FileCredentialStore");
|
|
193
|
+
}
|
|
194
|
+
chain = Promise.resolve();
|
|
195
|
+
load() {
|
|
196
|
+
if (!existsSync2(this.path)) return {};
|
|
197
|
+
try {
|
|
198
|
+
return JSON.parse(readFileSync(this.path, "utf8"));
|
|
199
|
+
} catch {
|
|
200
|
+
return {};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
save(all) {
|
|
204
|
+
mkdirSync2(dirname(this.path), { recursive: true });
|
|
205
|
+
const tmp = this.path + ".tmp";
|
|
206
|
+
writeFileSync2(tmp, JSON.stringify(all, null, 2), { encoding: "utf8", mode: 384 });
|
|
207
|
+
renameSync(tmp, this.path);
|
|
208
|
+
}
|
|
209
|
+
serialize(fn) {
|
|
210
|
+
const next = this.chain.then(fn, fn);
|
|
211
|
+
this.chain = next.catch(() => void 0);
|
|
212
|
+
return next;
|
|
213
|
+
}
|
|
214
|
+
async read(providerId) {
|
|
215
|
+
return this.load()[providerId];
|
|
216
|
+
}
|
|
217
|
+
async list() {
|
|
218
|
+
return Object.entries(this.load()).map(([providerId, c]) => ({ providerId, type: c.type }));
|
|
219
|
+
}
|
|
220
|
+
modify(providerId, fn) {
|
|
221
|
+
return this.serialize(async () => {
|
|
222
|
+
const all = this.load();
|
|
223
|
+
const next = await fn(all[providerId]);
|
|
224
|
+
if (next !== void 0) {
|
|
225
|
+
all[providerId] = next;
|
|
226
|
+
this.save(all);
|
|
227
|
+
}
|
|
228
|
+
return all[providerId];
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
delete(providerId) {
|
|
232
|
+
return this.serialize(async () => {
|
|
233
|
+
const all = this.load();
|
|
234
|
+
delete all[providerId];
|
|
235
|
+
this.save(all);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
function credentialsPath(vault) {
|
|
240
|
+
return join2(vault, "config", "credentials.json");
|
|
241
|
+
}
|
|
242
|
+
__name(credentialsPath, "credentialsPath");
|
|
243
|
+
function createLlm(config) {
|
|
244
|
+
const credentials = new FileCredentialStore(credentialsPath(config.vault));
|
|
245
|
+
const models = builtinModels({ credentials });
|
|
246
|
+
const llm = {};
|
|
247
|
+
const report = /* @__PURE__ */ __name((m, model) => {
|
|
248
|
+
try {
|
|
249
|
+
llm.onUsage?.(m, model);
|
|
250
|
+
} catch {
|
|
251
|
+
}
|
|
252
|
+
}, "report");
|
|
253
|
+
for (const name of ["stream", "streamSimple"]) {
|
|
254
|
+
const orig = models[name]?.bind(models);
|
|
255
|
+
if (orig) models[name] = (model, ctx, opts) => {
|
|
256
|
+
const s = orig(model, ctx, opts);
|
|
257
|
+
s.result?.().then((m) => report(m, model)).catch(() => {
|
|
258
|
+
});
|
|
259
|
+
return s;
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
const complete = models.completeSimple.bind(models);
|
|
263
|
+
models.completeSimple = async (model, ctx, opts) => {
|
|
264
|
+
const job = /* @__PURE__ */ __name(() => {
|
|
265
|
+
if (!llm.db) throw new Error("model jobs need the daemon database");
|
|
266
|
+
return delegateComplete(llm.db, ctx, { caller: callingAgent() ?? "", imagesDir: join2(config.vault, "runs", "model-jobs") });
|
|
267
|
+
}, "job");
|
|
268
|
+
if (callingAgent()) return job();
|
|
269
|
+
const to = llm.route?.() ?? (isExternal(config.llm.provider) || llm.loggedIn === false ? { kind: "waiting" } : { kind: "model" });
|
|
270
|
+
if (to.kind === "waiting") return job();
|
|
271
|
+
if (to.kind === "unavailable") throw new Error(`BACKGROUND_UNAVAILABLE: ${to.why}`);
|
|
272
|
+
if (to.kind === "harness") {
|
|
273
|
+
if (!llm.harness) throw new Error("BACKGROUND_UNAVAILABLE: 后台一次性调用没有接上");
|
|
274
|
+
const a = await llm.harness(to.name, ctx);
|
|
275
|
+
const msg = {
|
|
276
|
+
role: "assistant",
|
|
277
|
+
content: [{ type: "text", text: a.text }],
|
|
278
|
+
api: "cli",
|
|
279
|
+
provider: to.name,
|
|
280
|
+
model: a.model || to.name,
|
|
281
|
+
stopReason: "stop",
|
|
282
|
+
timestamp: Date.now(),
|
|
283
|
+
usage: { input: a.usage.input, output: a.usage.output, cacheRead: a.usage.cacheRead, cacheWrite: 0, totalTokens: a.usage.total, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: a.costUsd ?? 0 } }
|
|
284
|
+
};
|
|
285
|
+
report(msg, { provider: to.name, id: a.model || to.name });
|
|
286
|
+
return msg;
|
|
287
|
+
}
|
|
288
|
+
return complete(model, ctx, opts);
|
|
289
|
+
};
|
|
290
|
+
const out = Object.assign(llm, {
|
|
291
|
+
models,
|
|
292
|
+
credentials,
|
|
293
|
+
model() {
|
|
294
|
+
const { provider, model } = config.llm;
|
|
295
|
+
const to = llm.route?.();
|
|
296
|
+
if (to?.kind === "harness" && !callingAgent()) return { id: to.name, name: to.name, provider: to.name, api: "cli", input: ["text", "image"], reasoning: false };
|
|
297
|
+
if (isExternal(provider)) return EXTERNAL_MODEL;
|
|
298
|
+
const m = models.getModel(provider, model);
|
|
299
|
+
if (m) return m;
|
|
300
|
+
const ids = models.getModels(provider).map((x) => x.id);
|
|
301
|
+
throw new Error(`model ${provider}/${model} not found. available for ${provider}: ${ids.slice(0, 20).join(", ") || "(unknown provider)"}`);
|
|
302
|
+
},
|
|
303
|
+
async status() {
|
|
304
|
+
const check = isExternal(config.llm.provider) ? { type: "none" } : await models.checkAuth(config.llm.provider).catch(() => void 0);
|
|
305
|
+
llm.loggedIn = !!check;
|
|
306
|
+
return { provider: config.llm.provider, model: config.llm.model, loggedIn: !!check, credentials: await credentials.list() };
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
void out.status().catch(() => {
|
|
310
|
+
});
|
|
311
|
+
return out;
|
|
312
|
+
}
|
|
313
|
+
__name(createLlm, "createLlm");
|
|
314
|
+
|
|
315
|
+
export {
|
|
316
|
+
withPurpose,
|
|
317
|
+
currentPurpose,
|
|
318
|
+
asAgentCall,
|
|
319
|
+
callingAgent,
|
|
320
|
+
recordUsage,
|
|
321
|
+
usageSummary,
|
|
322
|
+
delegateRun,
|
|
323
|
+
pendingJobs,
|
|
324
|
+
replyJob,
|
|
325
|
+
oldestWaitingMail,
|
|
326
|
+
isExternal,
|
|
327
|
+
createLlm
|
|
328
|
+
};
|