StandaloneAgent.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. import { blue, red, yellow } from "../deps.ts";
  2. import { ApiHandler } from "../api/mod.ts";
  3. import { executeCommand, readFile, writeFile, searchFiles, listFiles, listCodeDefinitions } from "../tools/mod.ts";
  4. import type { Message, TextBlock, ToolResult } from "../types.d.ts";
  5. interface AgentConfig {
  6. api: ApiHandler;
  7. systemPrompt: string;
  8. workingDir: string;
  9. }
  10. export class StandaloneAgent {
  11. private api: ApiHandler;
  12. private systemPrompt: string;
  13. private workingDir: string;
  14. private conversationHistory: Message[] = [];
  15. constructor(config: AgentConfig) {
  16. this.api = config.api;
  17. this.systemPrompt = config.systemPrompt;
  18. this.workingDir = config.workingDir;
  19. }
  20. async runTask(task: string): Promise<void> {
  21. this.conversationHistory.push({
  22. role: "user",
  23. content: [{ type: "text", text: `<task>\n${task}\n</task>` }]
  24. });
  25. let isTaskComplete = false;
  26. const encoder = new TextEncoder();
  27. while (!isTaskComplete) {
  28. const stream = this.api.createMessage(this.systemPrompt, this.conversationHistory);
  29. let assistantMessage = "";
  30. console.log(blue("Thinking..."));
  31. for await (const chunk of stream) {
  32. if (chunk.type === "text") {
  33. assistantMessage += chunk.text;
  34. await Deno.stdout.write(encoder.encode(chunk.text));
  35. }
  36. }
  37. this.conversationHistory.push({
  38. role: "assistant",
  39. content: [{ type: "text", text: assistantMessage }]
  40. });
  41. const toolResults = await this.executeTools(assistantMessage);
  42. if (toolResults.length > 0) {
  43. this.conversationHistory.push({
  44. role: "user",
  45. content: toolResults.map(result => ({
  46. type: "text",
  47. text: `[${result.tool}] Result:${result.output}`
  48. })) as TextBlock[]
  49. });
  50. } else {
  51. if (assistantMessage.includes("<attempt_completion>")) {
  52. isTaskComplete = true;
  53. } else {
  54. this.conversationHistory.push({
  55. role: "user",
  56. content: [{
  57. type: "text",
  58. text: "You must either use available tools to accomplish the task or call attempt_completion when the task is complete."
  59. }]
  60. });
  61. }
  62. }
  63. }
  64. }
  65. private async executeTools(message: string): Promise<ToolResult[]> {
  66. const results: ToolResult[] = [];
  67. const toolRegex = /<(\w+)>\s*([\s\S]*?)\s*<\/\1>/g;
  68. let match;
  69. while ((match = toolRegex.exec(message)) !== null) {
  70. const [_, toolName, paramsXml] = match;
  71. const params: Record<string, string> = {};
  72. const paramRegex = /<(\w+)>\s*([\s\S]*?)\s*<\/\1>/g;
  73. let paramMatch;
  74. while ((paramMatch = paramRegex.exec(paramsXml)) !== null) {
  75. const [__, paramName, paramValue] = paramMatch;
  76. params[paramName] = paramValue.trim();
  77. }
  78. let output: string;
  79. try {
  80. console.log(yellow(`\nExecuting: ${this.getToolDescription(toolName, params)}`));
  81. switch (toolName) {
  82. case "execute_command":
  83. output = await executeCommand(params.command);
  84. break;
  85. case "read_file":
  86. output = await readFile(this.workingDir, params.path);
  87. break;
  88. case "write_to_file":
  89. output = await writeFile(this.workingDir, params.path, params.content);
  90. break;
  91. case "search_files":
  92. output = await searchFiles(this.workingDir, params.path, params.regex, params.file_pattern);
  93. break;
  94. case "list_files":
  95. output = await listFiles(this.workingDir, params.path, params.recursive === "true");
  96. break;
  97. case "list_code_definition_names":
  98. output = await listCodeDefinitions(this.workingDir, params.path);
  99. break;
  100. case "attempt_completion":
  101. return results;
  102. default:
  103. console.warn(red(`Unknown tool: ${toolName}`));
  104. continue;
  105. }
  106. results.push({
  107. tool: toolName,
  108. params,
  109. output: output || "(No output)"
  110. });
  111. break;
  112. } catch (error) {
  113. const errorMessage = `Error executing ${toolName}: ${error instanceof Error ? error.message : String(error)}`;
  114. console.error(red(errorMessage));
  115. results.push({
  116. tool: toolName,
  117. params,
  118. output: errorMessage
  119. });
  120. break;
  121. }
  122. }
  123. return results;
  124. }
  125. private getToolDescription(toolName: string, params: Record<string, string>): string {
  126. switch (toolName) {
  127. case "execute_command":
  128. return `Running command: ${params.command}`;
  129. case "read_file":
  130. return `Reading file: ${params.path}`;
  131. case "write_to_file":
  132. return `Writing to file: ${params.path}`;
  133. case "search_files":
  134. return `Searching for "${params.regex}" in ${params.path}`;
  135. case "list_files":
  136. return `Listing files in ${params.path}`;
  137. case "list_code_definition_names":
  138. return `Analyzing code in ${params.path}`;
  139. case "attempt_completion":
  140. return "Completing task";
  141. default:
  142. return toolName;
  143. }
  144. }
  145. }