planner.ts 3.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. import { BaseAgent, type BaseAgentOptions, type ExtraAgentOptions } from './base';
  2. import { createLogger } from '@src/background/log';
  3. import { z } from 'zod';
  4. import type { AgentOutput } from '../types';
  5. import { HumanMessage } from '@langchain/core/messages';
  6. import { Actors, ExecutionState } from '../event/types';
  7. import { isAuthenticationError } from '@src/background/utils';
  8. import { ChatModelAuthError } from './errors';
  9. const logger = createLogger('PlannerAgent');
  10. // Define Zod schema for planner output
  11. export const plannerOutputSchema = z.object({
  12. observation: z.string(),
  13. challenges: z.string(),
  14. done: z.union([
  15. z.boolean(),
  16. z.string().transform(val => {
  17. if (val.toLowerCase() === 'true') return true;
  18. if (val.toLowerCase() === 'false') return false;
  19. throw new Error('Invalid boolean string');
  20. }),
  21. ]),
  22. next_steps: z.string(),
  23. reasoning: z.string(),
  24. web_task: z.union([
  25. z.boolean(),
  26. z.string().transform(val => {
  27. if (val.toLowerCase() === 'true') return true;
  28. if (val.toLowerCase() === 'false') return false;
  29. throw new Error('Invalid boolean string');
  30. }),
  31. ]),
  32. });
  33. export type PlannerOutput = z.infer<typeof plannerOutputSchema>;
  34. export class PlannerAgent extends BaseAgent<typeof plannerOutputSchema, PlannerOutput> {
  35. constructor(options: BaseAgentOptions, extraOptions?: Partial<ExtraAgentOptions>) {
  36. super(plannerOutputSchema, options, { ...extraOptions, id: 'planner' });
  37. }
  38. async execute(): Promise<AgentOutput<PlannerOutput>> {
  39. try {
  40. this.context.emitEvent(Actors.PLANNER, ExecutionState.STEP_START, 'Planning...');
  41. // get all messages from the message manager, state message should be the last one
  42. const messages = this.context.messageManager.getMessages();
  43. // Use full message history except the first one
  44. const plannerMessages = [this.prompt.getSystemMessage(), ...messages.slice(1)];
  45. // Remove images from last message if vision is not enabled for planner but vision is enabled
  46. if (!this.context.options.useVisionForPlanner && this.context.options.useVision) {
  47. const lastStateMessage = plannerMessages[plannerMessages.length - 1];
  48. let newMsg = '';
  49. if (Array.isArray(lastStateMessage.content)) {
  50. for (const msg of lastStateMessage.content) {
  51. if (msg.type === 'text') {
  52. newMsg += msg.text;
  53. }
  54. // Skip image_url messages
  55. }
  56. } else {
  57. newMsg = lastStateMessage.content;
  58. }
  59. plannerMessages[plannerMessages.length - 1] = new HumanMessage(newMsg);
  60. }
  61. const modelOutput = await this.invoke(plannerMessages);
  62. if (!modelOutput) {
  63. throw new Error('Failed to validate planner output');
  64. }
  65. this.context.emitEvent(Actors.PLANNER, ExecutionState.STEP_OK, modelOutput.next_steps);
  66. return {
  67. id: this.id,
  68. result: modelOutput,
  69. };
  70. } catch (error) {
  71. // Check if this is an authentication error
  72. if (isAuthenticationError(error)) {
  73. throw new ChatModelAuthError('Planner API Authentication failed. Please verify your API key', error);
  74. }
  75. const errorMessage = error instanceof Error ? error.message : String(error);
  76. this.context.emitEvent(Actors.PLANNER, ExecutionState.STEP_FAIL, `Planning failed: ${errorMessage}`);
  77. return {
  78. id: this.id,
  79. error: errorMessage,
  80. };
  81. }
  82. }
  83. }