- Add Agent Profile module for managing agent identity, soul, tools, memory, and bootstrap configuration - Add profile CLI (pnpm agent:profile) for creating/listing/showing profiles - Default sessionId to UUIDv7 instead of "default" - Expose Agent.sessionId as public readonly property - Improve exec/process tools error handling (no more crashes on spawn errors) - Add 'output' action to process tool for reading stdout/stderr - Better tool descriptions to guide agent in choosing exec vs process Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
97 lines
3.3 KiB
TypeScript
97 lines
3.3 KiB
TypeScript
import { Agent as PiAgentCore, type AgentEvent, type AgentMessage } from "@mariozechner/pi-agent-core";
|
||
import { v7 as uuidv7 } from "uuid";
|
||
import type { AgentOptions, AgentRunResult } from "./types.js";
|
||
import { createAgentOutput } from "./output.js";
|
||
import { resolveModel, resolveTools } from "./tools.js";
|
||
import { SessionManager } from "./session/session-manager.js";
|
||
import { ProfileManager } from "./profile/index.js";
|
||
|
||
export class Agent {
|
||
private readonly agent: PiAgentCore;
|
||
private readonly output;
|
||
private readonly session: SessionManager;
|
||
private readonly profile?: ProfileManager;
|
||
|
||
/** 当前会话 ID */
|
||
readonly sessionId: string;
|
||
|
||
constructor(options: AgentOptions = {}) {
|
||
const stdout = options.logger?.stdout ?? process.stdout;
|
||
const stderr = options.logger?.stderr ?? process.stderr;
|
||
this.output = createAgentOutput({ stdout, stderr });
|
||
|
||
this.agent = new PiAgentCore();
|
||
|
||
// 加载 Agent Profile(如果指定了 profileId)
|
||
if (options.profileId) {
|
||
this.profile = new ProfileManager({
|
||
profileId: options.profileId,
|
||
baseDir: options.profileBaseDir,
|
||
});
|
||
const systemPrompt = this.profile.buildSystemPrompt();
|
||
if (systemPrompt) {
|
||
this.agent.setSystemPrompt(systemPrompt);
|
||
}
|
||
} else if (options.systemPrompt) {
|
||
// 直接使用传入的 systemPrompt
|
||
this.agent.setSystemPrompt(options.systemPrompt);
|
||
}
|
||
|
||
this.sessionId = options.sessionId ?? uuidv7();
|
||
this.session = new SessionManager({ sessionId: this.sessionId });
|
||
const storedMeta = this.session.getMeta();
|
||
if (!options.thinkingLevel && storedMeta?.thinkingLevel) {
|
||
this.agent.setThinkingLevel(storedMeta.thinkingLevel as any);
|
||
} else if (options.thinkingLevel) {
|
||
this.agent.setThinkingLevel(options.thinkingLevel);
|
||
}
|
||
|
||
const model = options.provider && options.model ? resolveModel(options) : resolveModel({
|
||
...options,
|
||
provider: storedMeta?.provider,
|
||
model: storedMeta?.model,
|
||
});
|
||
this.agent.setModel(model);
|
||
this.agent.setTools(resolveTools(options));
|
||
|
||
const restoredMessages = this.session.loadMessages();
|
||
if (restoredMessages.length > 0) {
|
||
this.agent.replaceMessages(restoredMessages);
|
||
}
|
||
|
||
this.session.saveMeta({
|
||
provider: this.agent.state.model?.provider,
|
||
model: this.agent.state.model?.id,
|
||
thinkingLevel: this.agent.state.thinkingLevel,
|
||
});
|
||
|
||
this.agent.subscribe((event: AgentEvent) => {
|
||
this.output.handleEvent(event);
|
||
this.handleSessionEvent(event);
|
||
});
|
||
}
|
||
|
||
async run(prompt: string): Promise<AgentRunResult> {
|
||
this.output.state.lastAssistantText = "";
|
||
await this.agent.prompt(prompt);
|
||
return { text: this.output.state.lastAssistantText, error: this.agent.state.error };
|
||
}
|
||
|
||
private handleSessionEvent(event: AgentEvent) {
|
||
if (event.type === "message_end") {
|
||
const message = event.message as AgentMessage;
|
||
this.session.saveMessage(message);
|
||
if (message.role === "assistant") {
|
||
void this.maybeCompact();
|
||
}
|
||
}
|
||
}
|
||
|
||
private async maybeCompact() {
|
||
const messages = this.agent.state.messages.slice();
|
||
const result = await this.session.maybeCompact(messages);
|
||
if (result?.kept) {
|
||
this.agent.replaceMessages(result.kept);
|
||
}
|
||
}
|
||
}
|