128 lines
4.0 KiB
TypeScript
128 lines
4.0 KiB
TypeScript
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、/exit,Ctrl+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;
|