@ccjr1120/memory-one 0.1.8 → 0.1.10
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/bin/memory-one.mjs +18 -9
- package/dist/server.js +256 -63
- package/dist/server.js.map +1 -1
- package/dist/storage.js +53 -0
- package/dist/storage.js.map +1 -1
- package/package.json +1 -1
- package/public/assets/{highlighted-body-KPVGNVTW-BeAfTlkH.js → highlighted-body-KPVGNVTW-CyX758Sl.js} +1 -1
- package/public/assets/index-B53Oo3h3.css +1 -0
- package/public/assets/index-T6RSX4r2.js +190 -0
- package/public/index.html +2 -2
- package/public/assets/index-DdYHcoCB.css +0 -1
- package/public/assets/index-iU9Ed9kk.js +0 -190
package/bin/memory-one.mjs
CHANGED
|
@@ -37,7 +37,8 @@ const messages = {
|
|
|
37
37
|
cancelled: "已取消。",
|
|
38
38
|
notRunning: "Memory One 未运行。",
|
|
39
39
|
stopped: "Memory One 已停止。",
|
|
40
|
-
updateFailed: "Memory One
|
|
40
|
+
updateFailed: "Memory One 更新失败,已保留当前版本。",
|
|
41
|
+
updateWaiting: (version) => `新版本 ${version} 尚未在 npm 全部同步,请稍后重试。`,
|
|
41
42
|
updated: "Memory One 已更新。",
|
|
42
43
|
status: (pid, value) => `Memory One 正在运行(PID ${pid}):${value}`,
|
|
43
44
|
opening: "正在打开 Memory One。",
|
|
@@ -54,7 +55,8 @@ const messages = {
|
|
|
54
55
|
cancelled: "Cancelled.",
|
|
55
56
|
notRunning: "Memory One is not running.",
|
|
56
57
|
stopped: "Memory One stopped.",
|
|
57
|
-
updateFailed: "Memory One update failed.",
|
|
58
|
+
updateFailed: "Memory One update failed; the current version was preserved.",
|
|
59
|
+
updateWaiting: (version) => `Version ${version} is not fully available from npm yet. Try again later.`,
|
|
58
60
|
updated: "Memory One updated.",
|
|
59
61
|
status: (pid, value) => `Memory One is running (PID ${pid}): ${value}`,
|
|
60
62
|
opening: "Opening Memory One.",
|
|
@@ -189,16 +191,23 @@ async function stop() {
|
|
|
189
191
|
}
|
|
190
192
|
|
|
191
193
|
async function update() {
|
|
192
|
-
const wasRunning = Boolean(currentPid());
|
|
193
|
-
if (wasRunning) await stop();
|
|
194
194
|
const npmCommand = platform() === "win32" ? "npm.cmd" : "npm";
|
|
195
|
-
const
|
|
196
|
-
if (
|
|
197
|
-
|
|
198
|
-
|
|
195
|
+
const versionResult = spawnSync(npmCommand, ["view", "@ccjr1120/memory-one@latest", "version", "--json"], { encoding: "utf8" });
|
|
196
|
+
if (versionResult.status !== 0) throw new Error(copy().updateFailed);
|
|
197
|
+
let latestVersion = "";
|
|
198
|
+
try { latestVersion = String(JSON.parse(versionResult.stdout || '""')).trim(); } catch {}
|
|
199
|
+
if (!latestVersion) throw new Error(copy().updateFailed);
|
|
200
|
+
const packageResult = spawnSync(npmCommand, ["view", `@ccjr1120/memory-one@${latestVersion}`, "dist.tarball", "--json"], { encoding: "utf8" });
|
|
201
|
+
if (packageResult.status !== 0 || !String(packageResult.stdout).trim()) throw new Error(copy().updateWaiting(latestVersion));
|
|
202
|
+
|
|
203
|
+
const wasRunning = Boolean(currentPid());
|
|
204
|
+
const result = spawnSync(npmCommand, ["install", "--global", `@ccjr1120/memory-one@${latestVersion}`], { stdio: "inherit" });
|
|
205
|
+
if (result.status !== 0) throw new Error(copy().updateFailed);
|
|
206
|
+
if (wasRunning) {
|
|
207
|
+
await stop();
|
|
208
|
+
await start();
|
|
199
209
|
}
|
|
200
210
|
console.log(copy().updated);
|
|
201
|
-
if (wasRunning) await start();
|
|
202
211
|
}
|
|
203
212
|
|
|
204
213
|
function status() {
|
package/dist/server.js
CHANGED
|
@@ -23,10 +23,10 @@ const exposeMemory = (memory) => { if (!memory || typeof memory !== "object" ||
|
|
|
23
23
|
const exposeMemories = (memories) => memories.map(exposeMemory);
|
|
24
24
|
const toStorageInput = (input) => { const { scope, ...rest } = input; return { ...rest, scope: storageScope, project: typeof scope === "string" && scope !== "global" ? scope : null }; };
|
|
25
25
|
const toStoragePatch = (patch) => { const { scope, ...rest } = patch; return "scope" in patch ? { ...rest, project: typeof scope === "string" && scope !== "global" ? scope : null } : rest; };
|
|
26
|
-
const agentSystemPrompt = "你是 Memory One
|
|
26
|
+
const agentSystemPrompt = "你是 Memory One 的记忆管家,首要职责是结合已读取的相关记忆直接回答用户的问题。你可以主动搜索和读取记忆来提高回答准确性,但不得因为普通对话、提问、纠正回答、顺带提到的偏好或项目细节而新增、更新或删除记忆。只有当用户明确要求‘记住/保存’某项内容、明确要求修改某条记忆,或明确要求‘忘记/删除’某条记忆时,才调用 memory_store、memory_update 或 memory_delete。执行更新或删除前先确认目标唯一,不要编造记忆。回复使用中文,简洁但可以使用 Markdown。";
|
|
27
27
|
const toolLabels = { memory_get_context: "读取相关上下文", memory_search: "搜索记忆", memory_get: "读取记忆", memory_list: "列出记忆", memory_store: "保存记忆", memory_update: "更新记忆", memory_delete: "删除记忆", memory_feedback: "记录反馈" };
|
|
28
28
|
const isMemoryOverviewRequest = (message) => /(?:有哪些|所有记忆|全部记忆|列出(?:全部)?|查看(?:全部)?|浏览全部|总结(?:下)?(?:我的)?记忆|总结我的特点|概括我的特点|我的画像|我的偏好和特点|我的记忆(?:有什么)?特点|记忆特点)/.test(message);
|
|
29
|
-
function sseEvent(type, payload) { return `event: ${type}\ndata: ${JSON.stringify(payload)}\n\n`; }
|
|
29
|
+
function sseEvent(type, payload, id) { return `${id === undefined ? "" : `id: ${id}\n`}event: ${type}\ndata: ${JSON.stringify(payload)}\n\n`; }
|
|
30
30
|
async function readSse(response, onEvent) {
|
|
31
31
|
if (!response.body)
|
|
32
32
|
throw new Error("provider_empty_stream");
|
|
@@ -42,8 +42,10 @@ async function readSse(response, onEvent) {
|
|
|
42
42
|
const lines = chunk.split(/\r?\n/);
|
|
43
43
|
const event = lines.find((line) => line.startsWith("event:"))?.slice(6).trim() ?? "message";
|
|
44
44
|
const data = lines.filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim()).join("\n");
|
|
45
|
-
if (data)
|
|
46
|
-
await
|
|
45
|
+
if (data && await onEvent({ event, data }) === false) {
|
|
46
|
+
await reader.cancel().catch(() => undefined);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
47
49
|
}
|
|
48
50
|
if (done)
|
|
49
51
|
break;
|
|
@@ -67,7 +69,7 @@ async function openAiRound(config, messages, tools, emit) {
|
|
|
67
69
|
const calls = new Map();
|
|
68
70
|
await readSse(response, ({ data }) => {
|
|
69
71
|
if (data === "[DONE]")
|
|
70
|
-
return;
|
|
72
|
+
return false;
|
|
71
73
|
const chunk = JSON.parse(data);
|
|
72
74
|
const delta = chunk.choices?.[0]?.delta;
|
|
73
75
|
if (delta?.content) {
|
|
@@ -126,6 +128,8 @@ async function anthropicRound(config, messages, tools, emit) {
|
|
|
126
128
|
const calls = [];
|
|
127
129
|
let current = null;
|
|
128
130
|
await readSse(response, ({ event, data }) => {
|
|
131
|
+
if (event === "message_stop")
|
|
132
|
+
return false;
|
|
129
133
|
const item = JSON.parse(data);
|
|
130
134
|
if (event === "content_block_start" && item.content_block?.type === "tool_use") {
|
|
131
135
|
current = { id: item.content_block.id, name: item.content_block.name, arguments: "" };
|
|
@@ -415,7 +419,7 @@ const publicDir = join(fileURLToPath(new URL(".", import.meta.url)), "../public"
|
|
|
415
419
|
const packageJsonPath = join(fileURLToPath(new URL(".", import.meta.url)), "../package.json");
|
|
416
420
|
const packageVersion = JSON.parse(readFileSync(packageJsonPath, "utf8")).version;
|
|
417
421
|
app.register(fastifyStatic, { root: publicDir, prefix: "/" });
|
|
418
|
-
for (const frontendRoute of ["/", "/timeline", "/
|
|
422
|
+
for (const frontendRoute of ["/", "/timeline", "/preferences", "/scopes", "/tags", "/settings", "/mcp-service"]) {
|
|
419
423
|
app.get(frontendRoute, async (_, reply) => reply.sendFile("index.html"));
|
|
420
424
|
}
|
|
421
425
|
app.get("/api/memories", async (request) => { const q = request.query; return store.list(q.scope ?? "user", q.project ?? null, Number(q.limit ?? 50)); });
|
|
@@ -465,68 +469,257 @@ app.delete("/api/mcp/keys/:id", async (request, reply) => {
|
|
|
465
469
|
const { id } = request.params;
|
|
466
470
|
return { revoked: store.revokeMcpKey(id) };
|
|
467
471
|
});
|
|
468
|
-
|
|
469
|
-
|
|
472
|
+
function emitExecutionEvent(executionId, type, data, reply) {
|
|
473
|
+
const event = store.appendAgentExecutionEvent(executionId, type, data);
|
|
474
|
+
if (reply && !reply.raw.destroyed)
|
|
475
|
+
reply.raw.write(sseEvent(type, data, event.id));
|
|
476
|
+
return event;
|
|
477
|
+
}
|
|
478
|
+
app.get("/api/agent/config", async () => store.getAgentConfig());
|
|
479
|
+
app.put("/api/agent/config", async (request, reply) => {
|
|
480
|
+
const body = request.body ?? {};
|
|
481
|
+
return reply.send(store.saveAgentConfig(body));
|
|
482
|
+
});
|
|
483
|
+
app.get("/api/agent/messages", async () => store.listAgentMessages());
|
|
484
|
+
app.post("/api/agent/messages", async (request, reply) => {
|
|
485
|
+
const body = request.body;
|
|
486
|
+
if (!body?.id || !body.role || typeof body.content !== "string")
|
|
487
|
+
return reply.code(422).send({ detail: "message_required" });
|
|
488
|
+
return store.saveAgentMessage({ id: body.id, role: body.role, content: body.content, toolCalls: body.toolCalls });
|
|
489
|
+
});
|
|
490
|
+
app.post("/api/agent/executions", async (request, reply) => {
|
|
491
|
+
const body = request.body ?? {};
|
|
492
|
+
if (!body.message?.trim())
|
|
493
|
+
return reply.code(422).send({ detail: "message_required" });
|
|
494
|
+
const userMessageId = randomUUID();
|
|
495
|
+
const assistantMessageId = randomUUID();
|
|
496
|
+
const history = (body.history ?? []).slice(-12);
|
|
497
|
+
store.saveAgentMessage({ id: userMessageId, role: "user", content: body.message });
|
|
498
|
+
store.saveAgentMessage({ id: assistantMessageId, role: "assistant", content: "", toolCalls: [] });
|
|
499
|
+
const execution = store.createAgentExecution({ messageIds: [userMessageId, assistantMessageId] });
|
|
500
|
+
const turn = agentTurnQueue.then(async () => {
|
|
501
|
+
let content = "";
|
|
502
|
+
let toolCalls = [];
|
|
503
|
+
try {
|
|
504
|
+
await runAgent({ ...body, history }, (event) => {
|
|
505
|
+
if (event.type === "delta")
|
|
506
|
+
content += event.text;
|
|
507
|
+
if (event.type === "tool")
|
|
508
|
+
toolCalls = [...toolCalls, event.tool];
|
|
509
|
+
if (event.type === "done")
|
|
510
|
+
toolCalls = event.toolCalls;
|
|
511
|
+
store.saveAgentMessage({ id: assistantMessageId, role: "assistant", content, toolCalls });
|
|
512
|
+
});
|
|
513
|
+
store.updateAgentExecution(execution.id, { status: "completed" });
|
|
514
|
+
}
|
|
515
|
+
catch (error) {
|
|
516
|
+
const errorMessage = error instanceof Error ? error.message : "agent_request_failed";
|
|
517
|
+
store.updateAgentExecution(execution.id, { status: "failed", error: errorMessage });
|
|
518
|
+
emitExecutionEvent(execution.id, "status", { status: "failed", error: errorMessage });
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
agentTurnQueue = turn.catch(() => undefined);
|
|
522
|
+
return reply.code(202).send(store.getAgentExecution(execution.id));
|
|
523
|
+
});
|
|
524
|
+
app.get("/api/agent/executions/:executionId/events", async (request, reply) => {
|
|
525
|
+
const { executionId } = request.params;
|
|
526
|
+
if (!store.getAgentExecution(executionId))
|
|
527
|
+
return reply.code(404).send({ detail: "execution_not_found" });
|
|
528
|
+
const header = request.headers["last-event-id"];
|
|
529
|
+
const query = request.query;
|
|
530
|
+
const after = Number(header ?? query.after ?? 0) || 0;
|
|
531
|
+
reply.hijack();
|
|
532
|
+
reply.raw.writeHead(200, { "content-type": "text/event-stream; charset=utf-8", "cache-control": "no-cache, no-transform", connection: "keep-alive", "x-accel-buffering": "no" });
|
|
533
|
+
let closed = false;
|
|
534
|
+
request.raw.on("close", () => { closed = true; });
|
|
535
|
+
const send = () => { for (const event of store.listAgentExecutionEvents(executionId, after))
|
|
536
|
+
if (!closed)
|
|
537
|
+
reply.raw.write(sseEvent(event.type, event.data, event.id)); };
|
|
538
|
+
send();
|
|
539
|
+
const timer = setInterval(() => { if (closed) {
|
|
540
|
+
clearInterval(timer);
|
|
541
|
+
return;
|
|
542
|
+
} send(); }, 250);
|
|
543
|
+
request.raw.on("close", () => { clearInterval(timer); if (!reply.raw.destroyed)
|
|
544
|
+
reply.raw.end(); });
|
|
545
|
+
});
|
|
546
|
+
app.get("/api/agent/executions/:executionId", async (request, reply) => {
|
|
547
|
+
const { executionId } = request.params;
|
|
548
|
+
const execution = store.getAgentExecution(executionId);
|
|
549
|
+
return execution ? execution : reply.code(404).send({ detail: "execution_not_found" });
|
|
550
|
+
});
|
|
551
|
+
app.post("/api/agent/executions/:executionId/messages", async (request, reply) => {
|
|
552
|
+
const { executionId } = request.params;
|
|
553
|
+
const execution = store.getAgentExecution(executionId);
|
|
554
|
+
const body = request.body ?? {};
|
|
555
|
+
if (!execution)
|
|
556
|
+
return reply.code(404).send({ detail: "execution_not_found" });
|
|
557
|
+
if (execution.status !== "completed" && execution.status !== "failed" && execution.status !== "cancelled")
|
|
558
|
+
return reply.code(409).send({ detail: "execution_in_progress" });
|
|
559
|
+
if (!body.message?.trim())
|
|
560
|
+
return reply.code(422).send({ detail: "message_required" });
|
|
561
|
+
const userMessageId = randomUUID();
|
|
562
|
+
const assistantMessageId = randomUUID();
|
|
563
|
+
store.saveAgentMessage({ id: userMessageId, role: "user", content: body.message });
|
|
564
|
+
store.saveAgentMessage({ id: assistantMessageId, role: "assistant", content: "", toolCalls: [] });
|
|
565
|
+
store.updateAgentExecution(executionId, { status: "running", messageIds: [...execution.messageIds, userMessageId, assistantMessageId], error: null });
|
|
566
|
+
const history = [...execution.messages, { id: userMessageId, role: "user", content: body.message }].slice(-12).map((message) => ({ role: message.role, content: message.content }));
|
|
567
|
+
const turn = agentTurnQueue.then(async () => {
|
|
568
|
+
let content = "";
|
|
569
|
+
let toolCalls = [];
|
|
570
|
+
try {
|
|
571
|
+
await runAgent({ ...body, history }, (event) => { if (event.type === "delta")
|
|
572
|
+
content += event.text; if (event.type === "tool")
|
|
573
|
+
toolCalls = [...toolCalls, event.tool]; if (event.type === "done")
|
|
574
|
+
toolCalls = event.toolCalls; store.saveAgentMessage({ id: assistantMessageId, role: "assistant", content, toolCalls }); });
|
|
575
|
+
store.updateAgentExecution(executionId, { status: "completed" });
|
|
576
|
+
}
|
|
577
|
+
catch (error) {
|
|
578
|
+
store.updateAgentExecution(executionId, { status: "failed", error: error instanceof Error ? error.message : "agent_request_failed" });
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
agentTurnQueue = turn.catch(() => undefined);
|
|
582
|
+
return reply.code(202).send(store.getAgentExecution(executionId));
|
|
583
|
+
});
|
|
584
|
+
app.post("/api/agent/executions/:executionId/cancel", async (request, reply) => {
|
|
585
|
+
const { executionId } = request.params;
|
|
586
|
+
const execution = store.getAgentExecution(executionId);
|
|
587
|
+
if (!execution)
|
|
588
|
+
return reply.code(404).send({ detail: "execution_not_found" });
|
|
589
|
+
if (execution.status === "running") {
|
|
590
|
+
store.updateAgentExecution(executionId, { status: "cancelled" });
|
|
591
|
+
emitExecutionEvent(executionId, "status", { status: "cancelled" });
|
|
592
|
+
}
|
|
593
|
+
return store.getAgentExecution(executionId);
|
|
594
|
+
});
|
|
595
|
+
app.post("/api/agent/executions/:executionId/retry", async (request, reply) => {
|
|
596
|
+
const { executionId } = request.params;
|
|
597
|
+
const execution = store.getAgentExecution(executionId);
|
|
598
|
+
if (!execution)
|
|
599
|
+
return reply.code(404).send({ detail: "execution_not_found" });
|
|
600
|
+
if (execution.status === "running")
|
|
601
|
+
return reply.code(409).send({ detail: "execution_in_progress" });
|
|
602
|
+
const lastUser = [...execution.messages].reverse().find((message) => message.role === "user");
|
|
603
|
+
if (!lastUser)
|
|
604
|
+
return reply.code(422).send({ detail: "message_required" });
|
|
605
|
+
const body = request.body ?? {};
|
|
606
|
+
const messageIds = [...execution.messageIds, randomUUID(), randomUUID()];
|
|
607
|
+
const userMessageId = messageIds.at(-2);
|
|
608
|
+
const assistantMessageId = messageIds.at(-1);
|
|
609
|
+
store.saveAgentMessage({ id: userMessageId, role: "user", content: lastUser.content });
|
|
610
|
+
store.saveAgentMessage({ id: assistantMessageId, role: "assistant", content: "", toolCalls: [] });
|
|
611
|
+
store.updateAgentExecution(executionId, { status: "running", messageIds, error: null });
|
|
612
|
+
const history = [...execution.messages.filter((message) => message.id !== lastUser.id), { role: "user", content: lastUser.content }].slice(-12).map((message) => ({ role: message.role, content: message.content }));
|
|
613
|
+
const turn = agentTurnQueue.then(async () => {
|
|
614
|
+
let content = "";
|
|
615
|
+
let toolCalls = [];
|
|
616
|
+
try {
|
|
617
|
+
await runAgent({ ...body, message: lastUser.content, history }, (event) => { if (event.type === "delta")
|
|
618
|
+
content += event.text; if (event.type === "tool")
|
|
619
|
+
toolCalls = [...toolCalls, event.tool]; if (event.type === "done")
|
|
620
|
+
toolCalls = event.toolCalls; store.saveAgentMessage({ id: assistantMessageId, role: "assistant", content, toolCalls }); });
|
|
621
|
+
store.updateAgentExecution(executionId, { status: "completed" });
|
|
622
|
+
}
|
|
623
|
+
catch (error) {
|
|
624
|
+
store.updateAgentExecution(executionId, { status: "failed", error: error instanceof Error ? error.message : "agent_request_failed" });
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
agentTurnQueue = turn.catch(() => undefined);
|
|
628
|
+
return reply.code(202).send(store.getAgentExecution(executionId));
|
|
629
|
+
});
|
|
630
|
+
app.post("/api/agent/chat", async (request, reply) => {
|
|
631
|
+
try {
|
|
632
|
+
let text = "";
|
|
633
|
+
const toolCalls = [];
|
|
634
|
+
await runAgent(request.body ?? {}, (event) => { if (event.type === "delta")
|
|
635
|
+
text += event.text; if (event.type === "done")
|
|
636
|
+
toolCalls.push(...event.toolCalls); });
|
|
637
|
+
return { reply: text, toolCalls };
|
|
638
|
+
}
|
|
639
|
+
catch (error) {
|
|
640
|
+
request.log.error(error);
|
|
641
|
+
return reply.code(500).send({ detail: error instanceof Error ? error.message : "agent_request_failed" });
|
|
642
|
+
}
|
|
643
|
+
});
|
|
470
644
|
app.post("/api/agent/models", async (request, reply) => {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
headers
|
|
479
|
-
if (
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
try {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
645
|
+
const body = request.body ?? {};
|
|
646
|
+
const provider = (body.provider || "").toLowerCase();
|
|
647
|
+
if (!["openai", "openai-compatible", "anthropic", "local"].includes(provider))
|
|
648
|
+
return reply.code(422).send({ detail: "unsupported_provider" });
|
|
649
|
+
const base = (body.base_url?.trim() || "").replace(/\/$/, "");
|
|
650
|
+
if (!base)
|
|
651
|
+
return reply.code(422).send({ detail: "base_url_required" });
|
|
652
|
+
const headers = { accept: "application/json" };
|
|
653
|
+
if (provider === "anthropic") {
|
|
654
|
+
headers["anthropic-version"] = "2023-06-01";
|
|
655
|
+
if (body.api_key?.trim())
|
|
656
|
+
headers["x-api-key"] = body.api_key.trim();
|
|
657
|
+
}
|
|
658
|
+
else if (body.api_key?.trim()) {
|
|
659
|
+
headers.authorization = `Bearer ${body.api_key.trim()}`;
|
|
660
|
+
}
|
|
661
|
+
try {
|
|
662
|
+
const response = await fetch(`${base}/models`, { headers });
|
|
663
|
+
const text = await response.text();
|
|
664
|
+
let payload = null;
|
|
665
|
+
try {
|
|
666
|
+
payload = text ? JSON.parse(text) : null;
|
|
667
|
+
}
|
|
668
|
+
catch {
|
|
669
|
+
payload = null;
|
|
670
|
+
}
|
|
671
|
+
if (!response.ok)
|
|
672
|
+
return reply.code(response.status >= 400 && response.status < 600 ? response.status : 502).send({ detail: payload?.error?.message || payload?.message || text || "model_list_failed" });
|
|
673
|
+
const entries = Array.isArray(payload?.data) ? payload.data : Array.isArray(payload) ? payload : [];
|
|
674
|
+
const models = [...new Set(entries.map((item) => typeof item === "string" ? item : item?.id).filter((item) => typeof item === "string" && item.trim().length > 0))];
|
|
675
|
+
return { models };
|
|
676
|
+
}
|
|
677
|
+
catch (error) {
|
|
678
|
+
request.log.error(error);
|
|
679
|
+
return reply.code(502).send({ detail: "model_list_unreachable" });
|
|
680
|
+
}
|
|
496
681
|
});
|
|
497
|
-
*/
|
|
498
|
-
/*
|
|
499
682
|
app.post("/api/agent/stream", async (request, reply) => {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
683
|
+
const body = (request.body ?? {});
|
|
684
|
+
const userMessageId = body.user_message_id;
|
|
685
|
+
const assistantMessageId = body.assistant_message_id;
|
|
686
|
+
if (userMessageId && body.message)
|
|
687
|
+
store.saveAgentMessage({ id: userMessageId, role: "user", content: body.message });
|
|
688
|
+
if (assistantMessageId)
|
|
689
|
+
store.saveAgentMessage({ id: assistantMessageId, role: "assistant", content: "", toolCalls: [] });
|
|
690
|
+
let assistantContent = "";
|
|
691
|
+
let assistantTools = [];
|
|
692
|
+
reply.hijack();
|
|
693
|
+
reply.raw.writeHead(200, { "content-type": "text/event-stream; charset=utf-8", "cache-control": "no-cache, no-transform", connection: "keep-alive", "x-accel-buffering": "no" });
|
|
694
|
+
const turn = agentTurnQueue.then(async () => {
|
|
695
|
+
const allMessages = store.listAgentMessages(1000).filter((item) => item.content);
|
|
696
|
+
const currentIndex = userMessageId ? allMessages.findIndex((item) => item.id === userMessageId) : allMessages.length - 1;
|
|
697
|
+
const storedHistory = allMessages.slice(0, Math.max(0, currentIndex)).map((item) => ({ role: item.role, content: item.content })).slice(-12);
|
|
698
|
+
await runAgent({ ...body, history: storedHistory }, (event) => {
|
|
699
|
+
if (event.type === "delta")
|
|
700
|
+
assistantContent += event.text;
|
|
701
|
+
if (event.type === "tool")
|
|
702
|
+
assistantTools = [...assistantTools, event.tool];
|
|
703
|
+
if (event.type === "done")
|
|
704
|
+
assistantTools = event.toolCalls;
|
|
705
|
+
if (assistantMessageId)
|
|
706
|
+
store.saveAgentMessage({ id: assistantMessageId, role: "assistant", content: assistantContent, toolCalls: assistantTools });
|
|
707
|
+
if (!reply.raw.destroyed)
|
|
708
|
+
reply.raw.write(sseEvent(event.type, event.type === "delta" ? { text: event.text } : event.type === "tool" ? event.tool : { toolCalls: event.toolCalls }));
|
|
709
|
+
});
|
|
519
710
|
});
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
711
|
+
agentTurnQueue = turn.catch(() => undefined);
|
|
712
|
+
try {
|
|
713
|
+
await turn;
|
|
714
|
+
}
|
|
715
|
+
catch (error) {
|
|
716
|
+
request.log.error(error);
|
|
717
|
+
reply.raw.write(sseEvent("error", { detail: error instanceof Error ? error.message : "agent_request_failed" }));
|
|
718
|
+
}
|
|
719
|
+
finally {
|
|
720
|
+
reply.raw.end();
|
|
721
|
+
}
|
|
528
722
|
});
|
|
529
|
-
*/
|
|
530
723
|
app.get("/api/integrations/codex", async () => getCodexIntegration());
|
|
531
724
|
app.post("/api/integrations/codex/install", async () => installCodexIntegration());
|
|
532
725
|
app.get("/api/integrations/codex/mcp", async (request) => {
|