@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
289 lines (240 loc) • 7.91 kB
text/typescript
/**
* 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);
}
}