UNPKG

@agentics.org/sparc2

Version:

SPARC 2.0 - Autonomous Vector Coding Agent + MCP. SPARC 2.0, vectorized AI code analysis, is an intelligent coding agent framework built to automate and streamline software development. It combines secure execution environments, and version control into a

289 lines (240 loc) 7.91 kB
/** * Agent Executor * Executes agent flows based on configuration */ import { logMessage } from "../../logger.ts"; import { AgentConfig, AgentContext, AgentFlow, AgentStep, Assistant, ChatMessage, } from "../types.ts"; import { OpenAIProvider } from "../providers/openai-provider.ts"; import { TOOL_DEFINITIONS, TOOLS } from "./tools.ts"; /** * Agent executor for running agent flows */ export class AgentExecutor { private config: AgentConfig; private assistants: Record<string, Assistant> = {}; /** * Create a new agent executor * @param config Agent configuration */ constructor(config: AgentConfig) { this.config = config; } /** * Execute an agent flow * @param flowName Name of the flow to execute * @param context Initial context for the flow * @returns The final context after execution */ async executeFlow(flowName: string, context: AgentContext): Promise<AgentContext> { const flow = this.config.flows[flowName]; if (!flow) { throw new Error(`Flow not found: ${flowName}`); } await logMessage("info", `Executing agent flow: ${flowName}`, { flowName, description: flow.description, }); // Start with the first step let currentStep = Object.keys(flow.steps)[0]; let currentContext = { ...context }; // Execute steps until we reach the end while (currentStep) { const step = flow.steps[currentStep]; await logMessage("info", `Executing step: ${currentStep}`, { stepName: currentStep, description: step.description, }); // Execute the step currentContext = await this.executeStep(step, currentContext); // Determine the next step const nextStep = flow.transitions[currentStep]; if (!nextStep) { // End of flow break; } currentStep = nextStep; } await logMessage("info", `Completed agent flow: ${flowName}`, { flowName, description: flow.description, }); return currentContext; } /** * Execute a single step in a flow * @param step The step to execute * @param context The current context * @returns The updated context */ private async executeStep(step: AgentStep, context: AgentContext): Promise<AgentContext> { try { if (step.useAssistant) { // Use an assistant for this step return await this.executeAssistantStep(step, context); } else { // Use a simple LLM call for this step return await this.executeLLMStep(step, context); } } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); await logMessage("error", `Failed to execute step: ${step.name}`, { error: errorMessage }); throw error; } } /** * Execute a step using a simple LLM call * @param step The step to execute * @param context The current context * @returns The updated context */ private async executeLLMStep(step: AgentStep, context: AgentContext): Promise<AgentContext> { const provider = step.provider; // Prepare messages const messages: ChatMessage[] = []; // Add system message if provided if (step.systemPrompt) { messages.push({ role: "system", content: step.systemPrompt, }); } // Add context message messages.push({ role: "user", content: context.input || "", }); // Get completion from provider const response = await provider.getChatCompletion(messages, { model: step.model, }); // Update context with response return { ...context, output: response.content, [step.name]: { input: context.input, output: response.content, }, }; } /** * Execute a step using an assistant * @param step The step to execute * @param context The current context * @returns The updated context */ private async executeAssistantStep( step: AgentStep, context: AgentContext, ): Promise<AgentContext> { const provider = step.provider; if (!provider.supportsAssistants()) { throw new Error(`Provider ${provider.getName()} does not support assistants`); } if (!(provider instanceof OpenAIProvider)) { throw new Error(`Only OpenAI provider supports assistants currently`); } // Get or create assistant let assistant = this.assistants[step.name]; if (!assistant) { assistant = await provider.createAssistant({ name: `${this.config.name} - ${step.name}`, description: step.description || "", model: step.model, instructions: step.assistantInstructions || "", tools: step.tools?.map((toolName) => TOOL_DEFINITIONS[toolName]) || [], }); this.assistants[step.name] = assistant; } // Create a thread const thread = await provider.getClient().beta.threads.create(); // Add message to thread await provider.getClient().beta.threads.messages.create(thread.id, { role: "user", content: context.input || "", }); // Run the assistant const run = await provider.getClient().beta.threads.runs.create(thread.id, { assistant_id: assistant.id, }); // Wait for the run to complete let runStatus = await provider.getClient().beta.threads.runs.retrieve(thread.id, run.id); while (runStatus.status !== "completed" && runStatus.status !== "failed") { // Check for tool calls if ( runStatus.status === "requires_action" && runStatus.required_action?.type === "submit_tool_outputs" ) { const toolCalls = runStatus.required_action.submit_tool_outputs.tool_calls; const toolOutputs = []; for (const toolCall of toolCalls) { const output = await this.executeToolCall(toolCall, context); toolOutputs.push({ tool_call_id: toolCall.id, output, }); } // Submit tool outputs await provider.getClient().beta.threads.runs.submitToolOutputs(thread.id, run.id, { tool_outputs: toolOutputs, }); } // Wait a bit before checking again await new Promise((resolve) => setTimeout(resolve, 1000)); // Check status again runStatus = await provider.getClient().beta.threads.runs.retrieve(thread.id, run.id); } if (runStatus.status === "failed") { throw new Error(`Assistant run failed: ${runStatus.last_error?.message || "Unknown error"}`); } // Get the assistant's response const messages = await provider.getClient().beta.threads.messages.list(thread.id); const assistantMessages = messages.data.filter((msg) => msg.role === "assistant"); if (assistantMessages.length === 0) { throw new Error("No response from assistant"); } // Get the latest message const latestMessage = assistantMessages[0]; let output = ""; for (const content of latestMessage.content) { if (content.type === "text") { output += content.text.value; } } // Update context with response return { ...context, output, [step.name]: { input: context.input, output, }, }; } /** * Execute a tool call * @param toolCall The tool call to execute * @param context The current context * @returns The tool call output */ private async executeToolCall(toolCall: any, context: AgentContext): Promise<string> { if (toolCall.type !== "function") { throw new Error(`Unsupported tool call type: ${toolCall.type}`); } const { name, arguments: args } = toolCall.function; const parsedArgs = JSON.parse(args); // Execute the tool const tool = TOOLS[name]; if (!tool) { throw new Error(`Unknown tool: ${name}`); } return await tool(parsedArgs, context); } }