import { BaseAgent, type BaseAgentOptions, type ExtraAgentOptions } from './base'; import { createLogger } from '@src/background/log'; import { z } from 'zod'; import type { AgentOutput } from '../types'; import { HumanMessage } from '@langchain/core/messages'; import { Actors, ExecutionState } from '../event/types'; import { ChatModelAuthError, ChatModelForbiddenError, isAuthenticationError, isForbiddenError, LLM_FORBIDDEN_ERROR_MESSAGE, } from './errors'; const logger = createLogger('PlannerAgent'); // Define Zod schema for planner output export const plannerOutputSchema = z.object({ observation: z.string(), challenges: z.string(), done: z.union([ z.boolean(), z.string().transform(val => { if (val.toLowerCase() === 'true') return true; if (val.toLowerCase() === 'false') return false; throw new Error('Invalid boolean string'); }), ]), next_steps: z.string(), reasoning: z.string(), web_task: z.union([ z.boolean(), z.string().transform(val => { if (val.toLowerCase() === 'true') return true; if (val.toLowerCase() === 'false') return false; throw new Error('Invalid boolean string'); }), ]), }); export type PlannerOutput = z.infer; export class PlannerAgent extends BaseAgent { constructor(options: BaseAgentOptions, extraOptions?: Partial) { super(plannerOutputSchema, options, { ...extraOptions, id: 'planner' }); } async execute(): Promise> { try { this.context.emitEvent(Actors.PLANNER, ExecutionState.STEP_START, 'Planning...'); // get all messages from the message manager, state message should be the last one const messages = this.context.messageManager.getMessages(); // Use full message history except the first one const plannerMessages = [this.prompt.getSystemMessage(), ...messages.slice(1)]; // Remove images from last message if vision is not enabled for planner but vision is enabled if (!this.context.options.useVisionForPlanner && this.context.options.useVision) { const lastStateMessage = plannerMessages[plannerMessages.length - 1]; let newMsg = ''; if (Array.isArray(lastStateMessage.content)) { for (const msg of lastStateMessage.content) { if (msg.type === 'text') { newMsg += msg.text; } // Skip image_url messages } } else { newMsg = lastStateMessage.content; } plannerMessages[plannerMessages.length - 1] = new HumanMessage(newMsg); } const modelOutput = await this.invoke(plannerMessages); if (!modelOutput) { throw new Error('未能验证规划器输出'); } this.context.emitEvent(Actors.PLANNER, ExecutionState.STEP_OK, modelOutput.next_steps); return { id: this.id, result: modelOutput, }; } catch (error) { // Check if this is an authentication error if (isAuthenticationError(error)) { throw new ChatModelAuthError('Planner API鉴权失败。请验证您的API密钥', error); } if (isForbiddenError(error)) { throw new ChatModelForbiddenError(LLM_FORBIDDEN_ERROR_MESSAGE, error); } const errorMessage = error instanceof Error ? error.message : String(error); this.context.emitEvent(Actors.PLANNER, ExecutionState.STEP_FAIL, `策划失败: ${errorMessage}`); return { id: this.id, error: errorMessage, }; } } }