2026-08-04 17:41:28 +08:00

128 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
createInterface,
type Interface as ReadlineInterface,
} from "node:readline/promises";
import type { Extension } from "../../kernel";
import { Event, ExtensionId, Hook } from "../catalog";
import type { AgentService } from "../shared/agent";
import type { WorkspaceService } from "../shared/workspace";
import { parseInput } from "./input";
export function createCliExtension(): Extension {
let terminal: ReadlineInterface | undefined;
let loop: Promise<void> | undefined;
let currentRequest: AbortController | undefined;
return {
setup() {},
start(context) {
const agent = context.get<AgentService>(Hook.Agent);
const workspace = context.get<WorkspaceService>(Hook.Workspace);
const roleNames = {
user: "用户",
assistant: "助手",
system: "系统",
};
terminal = createInterface({
input: process.stdin,
output: process.stdout,
});
terminal.on("SIGINT", () => {
if (currentRequest) {
currentRequest.abort();
} else {
terminal?.close();
}
});
loop = (async () => {
try {
process.stdout.write(
`llm-to-agent\nWorkspace: ${process.cwd()}\n当前对话: ${workspace.conversationId}\n命令: /new、/switch <id>、/history、/exitCtrl+C 取消当前回复。\n\n`,
);
terminal?.setPrompt("> ");
terminal?.prompt();
for await (const line of terminal!) {
const input = parseInput(line);
if (!input) {
terminal?.prompt();
continue;
}
if (input.type === "command") {
if (input.name === "exit") break;
if (input.name === "new") {
const conversationId = await workspace.newConversation();
process.stdout.write(`已新建对话: ${conversationId}\n\n`);
} else if (input.name === "switch") {
if (!input.argument) {
process.stdout.write("用法: /switch <conversation-id>\n\n");
} else if (await workspace.switchConversation(input.argument)) {
process.stdout.write(`已切换到对话: ${workspace.conversationId}\n\n`);
} else {
process.stdout.write(`对话不存在: ${input.argument}\n\n`);
}
} else if (input.name === "history") {
const messages = await workspace.messages();
if (messages.length === 0) {
process.stdout.write("当前对话暂无消息。\n\n");
} else {
process.stdout.write(`对话 ${workspace.conversationId}\n`);
for (const message of messages) {
process.stdout.write(
`${roleNames[message.role]}: ${message.content}\n`,
);
}
process.stdout.write("\n");
}
} else {
process.stdout.write(`未知命令: /${input.name}\n\n`);
}
terminal?.prompt();
continue;
}
currentRequest = new AbortController();
try {
for await (const chunk of agent.chat(input.content, currentRequest.signal)) {
process.stdout.write(chunk);
}
process.stdout.write("\n\n");
} catch (error) {
if (currentRequest.signal.aborted) {
process.stdout.write("\n[已取消]\n\n");
} else {
process.stderr.write(`\n${String(error)}\n\n`);
}
} finally {
currentRequest = undefined;
}
terminal?.prompt();
}
} finally {
terminal?.close();
await context.emit(Event.RuntimeStopRequested, { source: "cli" });
}
})();
},
async stop() {
currentRequest?.abort();
terminal?.close();
await loop;
},
};
}
createCliExtension.id = ExtensionId.Cli;