From 154272275907c3d6f50c90b1f5e65a6a80db5f7e Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Thu, 15 Aug 2024 21:35:46 +1000 Subject: [PATCH 01/31] Implemented RewriteFileTool class to rewrite or create files within the project directory Applied patches from BBai to 1 file Files modified: - api/src/llms/tools/rewriteFileTool.ts --- api/src/llms/tools/rewriteFileTool.ts | 38 +++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 api/src/llms/tools/rewriteFileTool.ts diff --git a/api/src/llms/tools/rewriteFileTool.ts b/api/src/llms/tools/rewriteFileTool.ts new file mode 100644 index 0000000..6e1bb9d --- /dev/null +++ b/api/src/llms/tools/rewriteFileTool.ts @@ -0,0 +1,38 @@ +import { LLMTool } from '../llmTool.ts'; +import { isPathWithinProject } from '../../utils/fileHandling.utils.ts'; +import { ensureFileSync, writeTextFileSync } from 'std/fs/mod.ts'; + +export class RewriteFileTool extends LLMTool { + name = 'rewrite_file'; + description = 'Rewrite an entire file or create a new one'; + + async execute(params: { filePath: string; content: string; createIfMissing?: boolean }): Promise { + const { filePath, content, createIfMissing = true } = params; + + if (!isPathWithinProject(filePath)) { + throw new Error('File path is not within the project directory'); + } + + try { + if (createIfMissing) { + ensureFileSync(filePath); + } + writeTextFileSync(filePath, content); + return `File ${filePath} has been successfully rewritten or created.`; + } catch (error) { + throw new Error(`Failed to rewrite or create file: ${error.message}`); + } + } + + getParameterSchema() { + return { + type: 'object', + properties: { + filePath: { type: 'string', description: 'The path of the file to be rewritten or created' }, + content: { type: 'string', description: 'The new content of the file' }, + createIfMissing: { type: 'boolean', description: 'Create the file if it does not exist', default: true } + }, + required: ['filePath', 'content'] + }; + } +} From 4d81be18073b8401e30c98e6edcea745b701efc5 Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Thu, 15 Aug 2024 21:36:15 +1000 Subject: [PATCH 02/31] Added RewriteFileTool to the list of registered tools in LLMToolManager Applied patches from BBai to 1 file Files modified: - api/src/llms/llmToolManager.ts --- api/src/llms/llmToolManager.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/src/llms/llmToolManager.ts b/api/src/llms/llmToolManager.ts index 0373b13..1af1208 100644 --- a/api/src/llms/llmToolManager.ts +++ b/api/src/llms/llmToolManager.ts @@ -8,6 +8,7 @@ import { LLMToolSearchProject } from './tools/searchProjectTool.ts'; import { LLMToolRunCommand } from './tools/runCommandTool.ts'; //import { LLMToolApplyPatch } from './tools/applyPatchTool.ts'; import { LLMToolSearchAndReplace } from './tools/searchAndReplaceTool.ts'; +import { RewriteFileTool } from './tools/rewriteFileTool.ts'; //import { LLMToolVectorSearch } from './tools/vectorSearchTool.ts'; import { createError, ErrorType } from '../utils/error.utils.ts'; import { LLMValidationErrorOptions } from '../errors/error.ts'; @@ -30,6 +31,7 @@ class LLMToolManager { this.registerTool(new LLMToolSearchProject()); this.registerTool(new LLMToolSearchAndReplace()); this.registerTool(new LLMToolRunCommand()); + this.registerTool(new RewriteFileTool()); //this.registerTool(new LLMToolApplyPatch()); // Claude isn't good enough yet writing diff patches //this.registerTool(new LLMToolVectorSearch()); } From e50e2f3595a91f71f075c08cf63685e19ef60564 Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Thu, 15 Aug 2024 21:36:34 +1000 Subject: [PATCH 03/31] Added unit tests for the RewriteFileTool class Applied patches from BBai to 1 file Files modified: - api/tests/t/llms/tools/rewriteFileTool.test.ts --- .../t/llms/tools/rewriteFileTool.test.ts | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 api/tests/t/llms/tools/rewriteFileTool.test.ts diff --git a/api/tests/t/llms/tools/rewriteFileTool.test.ts b/api/tests/t/llms/tools/rewriteFileTool.test.ts new file mode 100644 index 0000000..09392ec --- /dev/null +++ b/api/tests/t/llms/tools/rewriteFileTool.test.ts @@ -0,0 +1,68 @@ +import { assertEquals, assertThrows } from 'std/testing/asserts.ts'; +import { RewriteFileTool } from '../../../../src/llms/tools/rewriteFileTool.ts'; +import { ensureFileSync, existsSync } from 'std/fs/mod.ts'; +import { resolve } from 'std/path/mod.ts'; + +Deno.test({ + name: 'RewriteFileTool - rewrite existing file', + async fn() { + const tempDir = await Deno.makeTempDir(); + const testFilePath = resolve(tempDir, 'test.txt'); + ensureFileSync(testFilePath); + await Deno.writeTextFile(testFilePath, 'Original content'); + + const tool = new RewriteFileTool(); + const result = await tool.execute({ + filePath: testFilePath, + content: 'New content', + }); + + assertEquals(result, `File ${testFilePath} has been successfully rewritten or created.`); + const newContent = await Deno.readTextFile(testFilePath); + assertEquals(newContent, 'New content'); + + await Deno.remove(tempDir, { recursive: true }); + }, + sanitizeResources: false, + sanitizeOps: false, +}); + +Deno.test({ + name: 'RewriteFileTool - create new file', + async fn() { + const tempDir = await Deno.makeTempDir(); + const testFilePath = resolve(tempDir, 'newfile.txt'); + + const tool = new RewriteFileTool(); + const result = await tool.execute({ + filePath: testFilePath, + content: 'New file content', + }); + + assertEquals(result, `File ${testFilePath} has been successfully rewritten or created.`); + assertEquals(existsSync(testFilePath), true); + const newContent = await Deno.readTextFile(testFilePath); + assertEquals(newContent, 'New file content'); + + await Deno.remove(tempDir, { recursive: true }); + }, + sanitizeResources: false, + sanitizeOps: false, +}); + +Deno.test({ + name: 'RewriteFileTool - throw error for file outside project', + async fn() { + const tool = new RewriteFileTool(); + await assertThrows( + () => tool.execute({ + filePath: '/tmp/outside_project.txt', + content: 'This should fail', + }), + Error, + 'File path is not within the project directory' + ); + }, + sanitizeResources: false, + sanitizeOps: false, +}); From 0cbd46ab1af7c49d2454dc68406d1846d01615b9 Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Thu, 15 Aug 2024 21:36:51 +1000 Subject: [PATCH 04/31] Updated imports to use fully qualified URLs for Deno standard library modules. Applied patches from BBai to 1 file Files modified: - api/tests/t/llms/tools/rewriteFileTool.test.ts --- api/tests/t/llms/tools/rewriteFileTool.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/tests/t/llms/tools/rewriteFileTool.test.ts b/api/tests/t/llms/tools/rewriteFileTool.test.ts index 09392ec..953ef56 100644 --- a/api/tests/t/llms/tools/rewriteFileTool.test.ts +++ b/api/tests/t/llms/tools/rewriteFileTool.test.ts @@ -1,7 +1,7 @@ -import { assertEquals, assertThrows } from 'std/testing/asserts.ts'; +import { assertEquals, assertThrows } from 'https://deno.land/std/testing/asserts.ts'; import { RewriteFileTool } from '../../../../src/llms/tools/rewriteFileTool.ts'; -import { ensureFileSync, existsSync } from 'std/fs/mod.ts'; -import { resolve } from 'std/path/mod.ts'; +import { ensureFileSync, existsSync } from 'https://deno.land/std/fs/mod.ts'; +import { resolve } from 'https://deno.land/std/path/mod.ts'; Deno.test({ name: 'RewriteFileTool - rewrite existing file', From 5da3a778b8d8441fb22d184b3607585f11684c00 Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Thu, 15 Aug 2024 21:37:03 +1000 Subject: [PATCH 05/31] Updated import paths for Deno standard library modules Applied patches from BBai to 1 file Files modified: - api/src/llms/tools/rewriteFileTool.ts --- api/src/llms/tools/rewriteFileTool.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/llms/tools/rewriteFileTool.ts b/api/src/llms/tools/rewriteFileTool.ts index 6e1bb9d..dfacc85 100644 --- a/api/src/llms/tools/rewriteFileTool.ts +++ b/api/src/llms/tools/rewriteFileTool.ts @@ -1,6 +1,6 @@ import { LLMTool } from '../llmTool.ts'; import { isPathWithinProject } from '../../utils/fileHandling.utils.ts'; -import { ensureFileSync, writeTextFileSync } from 'std/fs/mod.ts'; +import { ensureFileSync, writeTextFileSync } from 'https://deno.land/std/fs/mod.ts'; export class RewriteFileTool extends LLMTool { name = 'rewrite_file'; From 7a0777df914be5b828be928da837727dfdd1addd Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Thu, 15 Aug 2024 21:37:20 +1000 Subject: [PATCH 06/31] Refactored RewriteFileTool to use async/await and updated input schema Applied patches from BBai to 1 file Files modified: - api/src/llms/tools/rewriteFileTool.ts --- api/src/llms/tools/rewriteFileTool.ts | 30 ++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/api/src/llms/tools/rewriteFileTool.ts b/api/src/llms/tools/rewriteFileTool.ts index dfacc85..aabdcf8 100644 --- a/api/src/llms/tools/rewriteFileTool.ts +++ b/api/src/llms/tools/rewriteFileTool.ts @@ -1,30 +1,46 @@ -import { LLMTool } from '../llmTool.ts'; +import LLMTool from '../llmTool.ts'; import { isPathWithinProject } from '../../utils/fileHandling.utils.ts'; -import { ensureFileSync, writeTextFileSync } from 'https://deno.land/std/fs/mod.ts'; +import { ensureFile, writeTextFile } from 'https://deno.land/std/fs/mod.ts'; -export class RewriteFileTool extends LLMTool { +export class RewriteFileTool implements LLMTool { name = 'rewrite_file'; description = 'Rewrite an entire file or create a new one'; async execute(params: { filePath: string; content: string; createIfMissing?: boolean }): Promise { + return this.runTool(params); const { filePath, content, createIfMissing = true } = params; - if (!isPathWithinProject(filePath)) { + if (!await isPathWithinProject(Deno.cwd(), filePath)) { throw new Error('File path is not within the project directory'); } try { if (createIfMissing) { - ensureFileSync(filePath); + await ensureFile(filePath); } - writeTextFileSync(filePath, content); + await writeTextFile(filePath, content); return `File ${filePath} has been successfully rewritten or created.`; } catch (error) { throw new Error(`Failed to rewrite or create file: ${error.message}`); } } - getParameterSchema() { + input_schema = { + type: 'object', + properties: { + filePath: { type: 'string', description: 'The path of the file to be rewritten or created' }, + content: { type: 'string', description: 'The new content of the file' }, + createIfMissing: { type: 'boolean', description: 'Create the file if it does not exist', default: true } + }, + required: ['filePath', 'content'] + }; + + validateInput(input: unknown): boolean { + // Implement input validation logic here + return true; + } + + async runTool(params: { filePath: string; content: string; createIfMissing?: boolean }): Promise { return { type: 'object', properties: { From 5361fe4e9776c18dc316a4898c29441bf17e01c4 Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Thu, 15 Aug 2024 21:37:48 +1000 Subject: [PATCH 07/31] Added ConversationTokenUsage type to conversationLogger.utils.ts Applied patches from BBai to 1 file Files modified: - src/shared/utils/conversationLogger.utils.ts --- src/shared/utils/conversationLogger.utils.ts | 82 ++++++++++++++++---- 1 file changed, 67 insertions(+), 15 deletions(-) diff --git a/src/shared/utils/conversationLogger.utils.ts b/src/shared/utils/conversationLogger.utils.ts index 1f205a7..fd7e575 100644 --- a/src/shared/utils/conversationLogger.utils.ts +++ b/src/shared/utils/conversationLogger.utils.ts @@ -1,7 +1,7 @@ import { join } from '@std/path'; import { ensureDir } from '@std/fs'; -import type { ConversationId, ConversationMetrics, TokenUsage } from 'shared/types.ts'; +import type { ConversationId, ConversationMetrics, TokenUsage, ConversationTokenUsage } from 'shared/types.ts'; import { getBbaiDataDir } from 'shared/dataDir.ts'; import { LogFormatter } from 'shared/logFormatter.ts'; //import { logger } from 'shared/logger.ts'; @@ -25,7 +25,9 @@ export class ConversationLogger { timestamp: string, content: string, conversationStats: ConversationMetrics, - tokenUsage: TokenUsage, + tokenUsageTurn: TokenUsage, + tokenUsageStatement: TokenUsage, + tokenUsageConversation: ConversationTokenUsage, ) => {}, ) {} @@ -49,17 +51,39 @@ export class ConversationLogger { type: ConversationLoggerEntryType, message: string, conversationStats?: ConversationMetrics, - tokenUsage?: TokenUsage, + tokenUsageTurn?: TokenUsage, + tokenUsageStatement?: TokenUsage, + tokenUsageConversation?: ConversationTokenUsage, ) { const timestamp = this.getTimestamp(); - if (!tokenUsage) tokenUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 }; + if (!tokenUsageTurn) tokenUsageTurn = { inputTokens: 0, outputTokens: 0, totalTokens: 0 }; + if (!tokenUsageStatement) tokenUsageStatement = { inputTokens: 0, outputTokens: 0, totalTokens: 0 }; + if (!tokenUsageConversation) { + tokenUsageConversation = { inputTokensTotal: 0, outputTokensTotal: 0, totalTokensTotal: 0 }; + } if (!conversationStats) conversationStats = { statementCount: 0, turnCount: 0, totalTurnCount: 0 }; - //const entry = LogFormatter.createRawEntryWithSeparator(type, timestamp, message, tokenUsage); - const entry = LogFormatter.createRawEntryWithSeparator(type, timestamp, message, conversationStats, tokenUsage); + //const entry = LogFormatter.createRawEntryWithSeparator(type, timestamp, message, tokenUsageTurn); + const entry = LogFormatter.createRawEntryWithSeparator( + type, + timestamp, + message, + conversationStats, + tokenUsageTurn, + tokenUsageStatement, + tokenUsageConversation, + ); await this.appendToLog(entry); - await this.logEntryHandler(type, timestamp, message, conversationStats, tokenUsage); + await this.logEntryHandler( + type, + timestamp, + message, + conversationStats, + tokenUsageTurn, + tokenUsageStatement, + tokenUsageConversation, + ); } async logUserMessage(message: string, conversationStats?: ConversationMetrics) { @@ -69,9 +93,18 @@ export class ConversationLogger { async logAssistantMessage( message: string, conversationStats?: ConversationMetrics, - tokenUsage?: TokenUsage, + tokenUsageTurn?: TokenUsage, + tokenUsageStatement?: TokenUsage, + tokenUsageConversation?: ConversationTokenUsage, ) { - await this.logEntry('assistant', message, conversationStats, tokenUsage); + await this.logEntry( + 'assistant', + message, + conversationStats, + tokenUsageTurn, + tokenUsageStatement, + tokenUsageConversation, + ); } async logAuxiliaryMessage(message: string) { @@ -82,17 +115,29 @@ export class ConversationLogger { toolName: string, input: object, conversationStats?: ConversationMetrics, - tokenUsage?: TokenUsage, + tokenUsageTurn?: TokenUsage, + tokenUsageStatement?: TokenUsage, + tokenUsageConversation?: ConversationTokenUsage, ) { - const message = `Tool: ${toolName}\nInput: \n${JSON.stringify(input, null, 2)}`; - await this.logEntry('tool_use', message, conversationStats, tokenUsage); + //const message = `Tool: ${toolName}\nInput: \n${JSON.stringify(input, null, 2)}`; + const message = `Tool: ${toolName}\nInput: \n${JSON.stringify(input)}`; + await this.logEntry( + 'tool_use', + message, + conversationStats, + tokenUsageTurn, + tokenUsageStatement, + tokenUsageConversation, + ); } async logToolResult( toolName: string, result: string | LLMMessageContentPart | LLMMessageContentParts, - conversationStats?: ConversationMetrics, - tokenUsage?: TokenUsage, + //conversationStats?: ConversationMetrics, + //tokenUsageTurn?: TokenUsage, + //tokenUsageStatement?: TokenUsage, + //tokenUsageConversation?: ConversationTokenUsage, ) { const message = `Tool: ${toolName}\nResult: ${ Array.isArray(result) @@ -103,7 +148,14 @@ export class ConversationLogger { ? 'text' in result ? (result as LLMMessageContentPartTextBlock).text : JSON.stringify(result, null, 2) : result }`; - await this.logEntry('tool_result', message, conversationStats, tokenUsage); + await this.logEntry( + 'tool_result', + message, + //conversationStats, + //tokenUsageTurn, + //tokenUsageStatement, + //tokenUsageConversation, + ); } async logError(error: string) { From f50022ed19cd4674197302681ca4e54d74e603ba Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Thu, 15 Aug 2024 21:38:10 +1000 Subject: [PATCH 08/31] Refactored the `rewriteFileTool` to accept an `LLMConversationInteraction`, `LLMAnswerToolUse`, and `ProjectEditor` instead of just file path and content. Applied patches from BBai to 1 file Files modified: - api/src/llms/tools/rewriteFileTool.ts --- api/src/llms/tools/rewriteFileTool.ts | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/api/src/llms/tools/rewriteFileTool.ts b/api/src/llms/tools/rewriteFileTool.ts index aabdcf8..e7cf222 100644 --- a/api/src/llms/tools/rewriteFileTool.ts +++ b/api/src/llms/tools/rewriteFileTool.ts @@ -1,6 +1,11 @@ import LLMTool from '../llmTool.ts'; import { isPathWithinProject } from '../../utils/fileHandling.utils.ts'; import { ensureFile, writeTextFile } from 'https://deno.land/std/fs/mod.ts'; +import { JSONSchema4 } from 'json-schema'; +import LLMConversationInteraction from '../interactions/conversationInteraction.ts'; +import { LLMAnswerToolUse } from '../llmMessage.ts'; +import ProjectEditor from '../../editor/projectEditor.ts'; +import { LLMToolRunResult } from '../llmTool.ts'; export class RewriteFileTool implements LLMTool { name = 'rewrite_file'; @@ -19,18 +24,22 @@ export class RewriteFileTool implements LLMTool { await ensureFile(filePath); } await writeTextFile(filePath, content); - return `File ${filePath} has been successfully rewritten or created.`; + return { + messageId: '', + toolResponse: `File ${filePath} has been successfully rewritten or created.`, + bbaiResponse: '' + }; } catch (error) { throw new Error(`Failed to rewrite or create file: ${error.message}`); } } - input_schema = { - type: 'object', + input_schema: JSONSchema4 = { + type: 'object' as const, properties: { - filePath: { type: 'string', description: 'The path of the file to be rewritten or created' }, + filePath: { type: 'string' as const, description: 'The path of the file to be rewritten or created' }, content: { type: 'string', description: 'The new content of the file' }, - createIfMissing: { type: 'boolean', description: 'Create the file if it does not exist', default: true } + createIfMissing: { type: 'boolean' as const, description: 'Create the file if it does not exist', default: true } }, required: ['filePath', 'content'] }; @@ -40,7 +49,8 @@ export class RewriteFileTool implements LLMTool { return true; } - async runTool(params: { filePath: string; content: string; createIfMissing?: boolean }): Promise { + async runTool(interaction: LLMConversationInteraction, toolUse: LLMAnswerToolUse, projectEditor: ProjectEditor): Promise { + const params = toolUse.toolInput as { filePath: string; content: string; createIfMissing?: boolean }; return { type: 'object', properties: { From 6a5dd0b08ab4a0565798ad1a36a0b8f9bf96f422 Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Thu, 15 Aug 2024 21:38:30 +1000 Subject: [PATCH 09/31] Refactored conversation logger to handle missing conversation metrics and token usage data Applied patches from BBai to 1 file Files modified: - src/shared/utils/conversationLogger.utils.ts --- src/shared/utils/conversationLogger.utils.ts | 24 +++++++++----------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/shared/utils/conversationLogger.utils.ts b/src/shared/utils/conversationLogger.utils.ts index fd7e575..9c9fe98 100644 --- a/src/shared/utils/conversationLogger.utils.ts +++ b/src/shared/utils/conversationLogger.utils.ts @@ -50,18 +50,16 @@ export class ConversationLogger { private async logEntry( type: ConversationLoggerEntryType, message: string, - conversationStats?: ConversationMetrics, - tokenUsageTurn?: TokenUsage, - tokenUsageStatement?: TokenUsage, - tokenUsageConversation?: ConversationTokenUsage, + conversationStats: ConversationMetrics, + tokenUsageTurn: TokenUsage, + tokenUsageStatement: TokenUsage, ) { const timestamp = this.getTimestamp(); - if (!tokenUsageTurn) tokenUsageTurn = { inputTokens: 0, outputTokens: 0, totalTokens: 0 }; - if (!tokenUsageStatement) tokenUsageStatement = { inputTokens: 0, outputTokens: 0, totalTokens: 0 }; - if (!tokenUsageConversation) { - tokenUsageConversation = { inputTokensTotal: 0, outputTokensTotal: 0, totalTokensTotal: 0 }; - } - if (!conversationStats) conversationStats = { statementCount: 0, turnCount: 0, totalTurnCount: 0 }; + const tokenUsageConversation: ConversationTokenUsage = { + inputTokensTotal: tokenUsageTurn.inputTokens + tokenUsageStatement.inputTokens, + outputTokensTotal: tokenUsageTurn.outputTokens + tokenUsageStatement.outputTokens, + totalTokensTotal: tokenUsageTurn.totalTokens + tokenUsageStatement.totalTokens + }; //const entry = LogFormatter.createRawEntryWithSeparator(type, timestamp, message, tokenUsageTurn); const entry = LogFormatter.createRawEntryWithSeparator( @@ -87,7 +85,7 @@ export class ConversationLogger { } async logUserMessage(message: string, conversationStats?: ConversationMetrics) { - await this.logEntry('user', message, conversationStats); + await this.logEntry('user', message, conversationStats, { inputTokens: 0, outputTokens: 0, totalTokens: 0 }, { inputTokens: 0, outputTokens: 0, totalTokens: 0 }); } async logAssistantMessage( @@ -108,7 +106,7 @@ export class ConversationLogger { } async logAuxiliaryMessage(message: string) { - await this.logEntry('auxiliary', message); + await this.logEntry('auxiliary', message, { statementCount: 0, turnCount: 0, totalTurnCount: 0 }, { inputTokens: 0, outputTokens: 0, totalTokens: 0 }, { inputTokens: 0, outputTokens: 0, totalTokens: 0 }); } async logToolUse( @@ -159,7 +157,7 @@ export class ConversationLogger { } async logError(error: string) { - await this.logEntry('error', error); + await this.logEntry('error', error, { statementCount: 0, turnCount: 0, totalTurnCount: 0 }, { inputTokens: 0, outputTokens: 0, totalTokens: 0 }, { inputTokens: 0, outputTokens: 0, totalTokens: 0 }); } //async logTextChange(filePath: string, patch: string) { From 6dd17a31ff7e851c1452c965f174b1e3028dba1c Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Thu, 15 Aug 2024 22:48:00 +1000 Subject: [PATCH 10/31] Fixes for rewrite file tool --- api/deno.lock | 33 ++++ api/src/controllers/orchestratorController.ts | 26 +-- api/src/errors/error.ts | 1 + api/src/llms/interactions/baseInteraction.ts | 23 ++- .../interactions/conversationInteraction.ts | 2 +- api/src/llms/llmToolManager.ts | 8 +- api/src/llms/providers/anthropicLLM.ts | 7 +- api/src/llms/tools/rewriteFileTool.ts | 169 +++++++++++------ .../t/llms/tools/rewriteFileTool.test.ts | 175 ++++++++++++------ cli/src/utils/websocketManager.ts | 2 +- deno.lock | 131 +++++++++++++ src/shared/types.ts | 1 + src/shared/utils/conversationLogger.utils.ts | 36 ++-- src/shared/utils/logFormatter.utils.ts | 28 ++- 14 files changed, 483 insertions(+), 159 deletions(-) diff --git a/api/deno.lock b/api/deno.lock index 9aa4071..5176261 100644 --- a/api/deno.lock +++ b/api/deno.lock @@ -543,6 +543,7 @@ "https://deno.land/std/datetime/mod.ts": "https://deno.land/std@0.224.0/datetime/mod.ts", "https://deno.land/std/fs/mod.ts": "https://deno.land/std@0.224.0/fs/mod.ts", "https://deno.land/std/path/mod.ts": "https://deno.land/std@0.224.0/path/mod.ts", + "https://deno.land/std/testing/asserts.ts": "https://deno.land/std@0.224.0/testing/asserts.ts", "https://deno.land/std/testing/bdd.ts": "https://deno.land/std@0.224.0/testing/bdd.ts", "https://deno.land/std/yaml/mod.ts": "https://deno.land/std@0.224.0/yaml/mod.ts", "https://deno.land/x/emit/mod.ts": "https://deno.land/x/emit@0.40.0/mod.ts", @@ -709,8 +710,35 @@ "https://deno.land/std@0.221.0/encoding/base64.ts": "8ccae67a1227b875340a8582ff707f37b131df435b07080d3bb58e07f5f97807", "https://deno.land/std@0.221.0/fmt/colors.ts": "d239d84620b921ea520125d778947881f62c50e78deef2657073840b8af9559a", "https://deno.land/std@0.221.0/io/types.ts": "acecb3074c730b5ff487ba4fe9ce51e67bd982aa07c95e5f5679b7b2f24ad129", + "https://deno.land/std@0.224.0/assert/_constants.ts": "a271e8ef5a573f1df8e822a6eb9d09df064ad66a4390f21b3e31f820a38e0975", "https://deno.land/std@0.224.0/assert/assert.ts": "09d30564c09de846855b7b071e62b5974b001bb72a4b797958fe0660e7849834", + "https://deno.land/std@0.224.0/assert/assert_almost_equals.ts": "9e416114322012c9a21fa68e187637ce2d7df25bcbdbfd957cd639e65d3cf293", + "https://deno.land/std@0.224.0/assert/assert_array_includes.ts": "14c5094471bc8e4a7895fc6aa5a184300d8a1879606574cb1cd715ef36a4a3c7", + "https://deno.land/std@0.224.0/assert/assert_equals.ts": "3bbca947d85b9d374a108687b1a8ba3785a7850436b5a8930d81f34a32cb8c74", + "https://deno.land/std@0.224.0/assert/assert_exists.ts": "43420cf7f956748ae6ed1230646567b3593cb7a36c5a5327269279c870c5ddfd", + "https://deno.land/std@0.224.0/assert/assert_false.ts": "3e9be8e33275db00d952e9acb0cd29481a44fa0a4af6d37239ff58d79e8edeff", + "https://deno.land/std@0.224.0/assert/assert_greater.ts": "5e57b201fd51b64ced36c828e3dfd773412c1a6120c1a5a99066c9b261974e46", + "https://deno.land/std@0.224.0/assert/assert_greater_or_equal.ts": "9870030f997a08361b6f63400273c2fb1856f5db86c0c3852aab2a002e425c5b", + "https://deno.land/std@0.224.0/assert/assert_instance_of.ts": "e22343c1fdcacfaea8f37784ad782683ec1cf599ae9b1b618954e9c22f376f2c", + "https://deno.land/std@0.224.0/assert/assert_is_error.ts": "f856b3bc978a7aa6a601f3fec6603491ab6255118afa6baa84b04426dd3cc491", + "https://deno.land/std@0.224.0/assert/assert_less.ts": "60b61e13a1982865a72726a5fa86c24fad7eb27c3c08b13883fb68882b307f68", + "https://deno.land/std@0.224.0/assert/assert_less_or_equal.ts": "d2c84e17faba4afe085e6c9123a63395accf4f9e00150db899c46e67420e0ec3", + "https://deno.land/std@0.224.0/assert/assert_match.ts": "ace1710dd3b2811c391946954234b5da910c5665aed817943d086d4d4871a8b7", + "https://deno.land/std@0.224.0/assert/assert_not_equals.ts": "78d45dd46133d76ce624b2c6c09392f6110f0df9b73f911d20208a68dee2ef29", + "https://deno.land/std@0.224.0/assert/assert_not_instance_of.ts": "3434a669b4d20cdcc5359779301a0588f941ffdc2ad68803c31eabdb4890cf7a", + "https://deno.land/std@0.224.0/assert/assert_not_match.ts": "df30417240aa2d35b1ea44df7e541991348a063d9ee823430e0b58079a72242a", + "https://deno.land/std@0.224.0/assert/assert_not_strict_equals.ts": "37f73880bd672709373d6dc2c5f148691119bed161f3020fff3548a0496f71b8", + "https://deno.land/std@0.224.0/assert/assert_object_match.ts": "411450fd194fdaabc0089ae68f916b545a49d7b7e6d0026e84a54c9e7eed2693", + "https://deno.land/std@0.224.0/assert/assert_rejects.ts": "4bee1d6d565a5b623146a14668da8f9eb1f026a4f338bbf92b37e43e0aa53c31", + "https://deno.land/std@0.224.0/assert/assert_strict_equals.ts": "b4f45f0fd2e54d9029171876bd0b42dd9ed0efd8f853ab92a3f50127acfa54f5", + "https://deno.land/std@0.224.0/assert/assert_string_includes.ts": "496b9ecad84deab72c8718735373feb6cdaa071eb91a98206f6f3cb4285e71b8", + "https://deno.land/std@0.224.0/assert/assert_throws.ts": "c6508b2879d465898dab2798009299867e67c570d7d34c90a2d235e4553906eb", "https://deno.land/std@0.224.0/assert/assertion_error.ts": "ba8752bd27ebc51f723702fac2f54d3e94447598f54264a6653d6413738a8917", + "https://deno.land/std@0.224.0/assert/equal.ts": "bddf07bb5fc718e10bb72d5dc2c36c1ce5a8bdd3b647069b6319e07af181ac47", + "https://deno.land/std@0.224.0/assert/fail.ts": "0eba674ffb47dff083f02ced76d5130460bff1a9a68c6514ebe0cdea4abadb68", + "https://deno.land/std@0.224.0/assert/mod.ts": "48b8cb8a619ea0b7958ad7ee9376500fe902284bb36f0e32c598c3dc34cbd6f3", + "https://deno.land/std@0.224.0/assert/unimplemented.ts": "8c55a5793e9147b4f1ef68cd66496b7d5ba7a9e7ca30c6da070c1a58da723d73", + "https://deno.land/std@0.224.0/assert/unreachable.ts": "5ae3dbf63ef988615b93eb08d395dda771c96546565f9e521ed86f6510c29e19", "https://deno.land/std@0.224.0/async/_util.ts": "3e94e674c974c5c9277f6b3ba2d4e8403c320ba5cebb891f50afa3af4b8e0ac9", "https://deno.land/std@0.224.0/async/abortable.ts": "ea6ddb98c1c6f066d5b26c8fd030e2d5afa54571182674aa07929c39dfa8c5b2", "https://deno.land/std@0.224.0/async/deadline.ts": "008929d69b1efdd11b1fa55784bb4882add6adf8722868b26e87f68e35efc573", @@ -730,6 +758,7 @@ "https://deno.land/std@0.224.0/datetime/mod.ts": "6a49163dd0c7986658ed425b3555da5312252e5f5156bced6ae7199e651aa83b", "https://deno.land/std@0.224.0/datetime/parse.ts": "d6dea8b3e394908dfd016bbe4545f260da4032493294aee1d10a3cf194c191cb", "https://deno.land/std@0.224.0/datetime/week_of_year.ts": "ecaff056953753db3dabc9a769f80ff7ea7be79ae1e34edb5e2bda336f685757", + "https://deno.land/std@0.224.0/fmt/colors.ts": "508563c0659dd7198ba4bbf87e97f654af3c34eb56ba790260f252ad8012e1c5", "https://deno.land/std@0.224.0/fs/_create_walk_entry.ts": "5d9d2aaec05bcf09a06748b1684224d33eba7a4de24cf4cf5599991ca6b5b412", "https://deno.land/std@0.224.0/fs/_get_file_info_type.ts": "da7bec18a7661dba360a1db475b826b18977582ce6fc9b25f3d4ee0403fe8cbd", "https://deno.land/std@0.224.0/fs/_is_same_path.ts": "709c95868345fea051c58b9e96af95cff94e6ae98dfcff2b66dee0c212c4221f", @@ -747,6 +776,9 @@ "https://deno.land/std@0.224.0/fs/mod.ts": "c25e6802cbf27f3050f60b26b00c2d8dba1cb7fcdafe34c66006a7473b7b34d4", "https://deno.land/std@0.224.0/fs/move.ts": "ca205d848908d7f217353bc5c623627b1333490b8b5d3ef4cab600a700c9bd8f", "https://deno.land/std@0.224.0/fs/walk.ts": "cddf87d2705c0163bff5d7767291f05b0f46ba10b8b28f227c3849cace08d303", + "https://deno.land/std@0.224.0/internal/diff.ts": "6234a4b493ebe65dc67a18a0eb97ef683626a1166a1906232ce186ae9f65f4e6", + "https://deno.land/std@0.224.0/internal/format.ts": "0a98ee226fd3d43450245b1844b47003419d34d210fa989900861c79820d21c2", + "https://deno.land/std@0.224.0/internal/mod.ts": "534125398c8e7426183e12dc255bb635d94e06d0f93c60a297723abe69d3b22e", "https://deno.land/std@0.224.0/path/_common/assert_path.ts": "dbdd757a465b690b2cc72fc5fb7698c51507dec6bfafce4ca500c46b76ff7bd8", "https://deno.land/std@0.224.0/path/_common/basename.ts": "569744855bc8445f3a56087fd2aed56bdad39da971a8d92b138c9913aecc5fa2", "https://deno.land/std@0.224.0/path/_common/common.ts": "ef73c2860694775fe8ffcbcdd387f9f97c7a656febf0daa8c73b56f4d8a7bd4c", @@ -825,6 +857,7 @@ "https://deno.land/std@0.224.0/path/windows/to_file_url.ts": "40e560ee4854fe5a3d4d12976cef2f4e8914125c81b11f1108e127934ced502e", "https://deno.land/std@0.224.0/path/windows/to_namespaced_path.ts": "4ffa4fb6fae321448d5fe810b3ca741d84df4d7897e61ee29be961a6aac89a4c", "https://deno.land/std@0.224.0/testing/_test_suite.ts": "f10a8a6338b60c403f07a76f3f46bdc9f1e1a820c0a1decddeb2949f7a8a0546", + "https://deno.land/std@0.224.0/testing/asserts.ts": "d0cdbabadc49cc4247a50732ee0df1403fdcd0f95360294ad448ae8c240f3f5c", "https://deno.land/std@0.224.0/testing/bdd.ts": "3e4de4ff6d8f348b5574661cef9501b442046a59079e201b849d0e74120d476b", "https://deno.land/std@0.224.0/yaml/_dumper/dumper.ts": "08b595b40841a2e1c75303f5096392323b6baf8e9662430a91e3b36fbe175fe9", "https://deno.land/std@0.224.0/yaml/_dumper/dumper_state.ts": "9e29f700ea876ed230b43f11fa006fcb1a62eedc1e27d32baaeaf3210f19f1e7", diff --git a/api/src/controllers/orchestratorController.ts b/api/src/controllers/orchestratorController.ts index 97611bb..be97be7 100644 --- a/api/src/controllers/orchestratorController.ts +++ b/api/src/controllers/orchestratorController.ts @@ -46,7 +46,7 @@ class OrchestratorController { public llmProvider: LLM; public eventManager!: EventManager; private projectEditorRef!: WeakRef; - private _providerRequestCount: number = 0; + //private _providerRequestCount: number = 0; // counts across all interactions // count of turns for most recent statement in most recent interaction private _turnCount: number = 0; @@ -267,13 +267,10 @@ class OrchestratorController { const persistence = await new ConversationPersistence(interaction.id, this.projectEditor).init(); // Include the latest stats and usage in the saved conversation - const conversationData = { - ...interaction, - stats: this.interactionStats.get(interaction.id), - tokenUsage: this._tokenUsageTotals, - } as unknown as LLMConversationInteraction; + //interaction.conversationStats = this.interactionStats.get(interaction.id), + //interaction.tokenUsageInteraction = this.interactionTokenUsage.get(interaction.id), - await persistence.saveConversation(conversationData); + await persistence.saveConversation(interaction); // Save system prompt and project info if running in local development if (config.api?.environment === 'localdev') { @@ -362,7 +359,9 @@ class OrchestratorController { timestamp: string, content: string, conversationStats: ConversationMetrics, - tokenUsage: TokenUsage, + tokenUsageTurn: TokenUsage, + tokenUsageStatement: TokenUsage, + tokenUsageConversation: ConversationTokenUsage, ): Promise => { const conversationEntry: ConversationEntry = { type, @@ -371,8 +370,9 @@ class OrchestratorController { conversationTitle: this.primaryInteraction.title, content, conversationStats, - tokenUsageConversation: this.tokenUsageTotals, - tokenUsageStatement: tokenUsage, + tokenUsageTurn: tokenUsageTurn, + tokenUsageStatement: tokenUsageStatement, + tokenUsageConversation: tokenUsageConversation, }; this.eventManager.emit( 'projectEditor:conversationEntry', @@ -427,7 +427,9 @@ class OrchestratorController { toolUse.toolName, toolUse.toolInput, interaction.conversationStats, + interaction.tokenUsageTurn, interaction.tokenUsageStatement, + interaction.tokenUsageInteraction, ); const { messageId: _messageId, toolResponse, bbaiResponse, isError } = await this.toolManager.handleToolUse( interaction, @@ -440,8 +442,8 @@ class OrchestratorController { interaction.conversationLogger?.logToolResult( toolUse.toolName, `BBai was ${isError ? 'unsuccessful' : 'successful'} with tool run: \n${bbaiResponse}`, - interaction.conversationStats, - interaction.tokenUsageStatement, + //interaction.conversationStats, + //interaction.tokenUsageStatement, // token usage is recorded with the tool use ); return toolResponse; diff --git a/api/src/errors/error.ts b/api/src/errors/error.ts index 88fa639..c3ed5eb 100644 --- a/api/src/errors/error.ts +++ b/api/src/errors/error.ts @@ -124,6 +124,7 @@ export interface FileHandlingErrorOptions extends ErrorOptions { | 'patch' | 'search-project' | 'search-replace' + | 'rewrite-file' | 'request-files' | 'remove-files'; } diff --git a/api/src/llms/interactions/baseInteraction.ts b/api/src/llms/interactions/baseInteraction.ts index 208083b..e8a0201 100644 --- a/api/src/llms/interactions/baseInteraction.ts +++ b/api/src/llms/interactions/baseInteraction.ts @@ -64,7 +64,9 @@ class LLMInteraction { timestamp: string, content: string, conversationStats: ConversationMetrics, - tokenUsage: TokenUsage, + tokenUsageTurn: TokenUsage, + tokenUsageStatement: TokenUsage, + tokenUsageConversation: ConversationTokenUsage, ) => { await this.llm.invoke( LLMCallbackType.LOG_ENTRY_HANDLER, @@ -72,7 +74,9 @@ class LLMInteraction { timestamp, content, conversationStats, - tokenUsage, + tokenUsageTurn, + tokenUsageStatement, + tokenUsageConversation, ); }; this.conversationLogger = await new ConversationLogger(projectRoot, this.id, logEntryHandler).init(); @@ -139,6 +143,14 @@ class LLMInteraction { this._tokenUsageStatement = tokenUsage; } + public get tokenUsageInteraction(): ConversationTokenUsage { + return this._tokenUsageInteraction; + } + public set tokenUsageInteraction(tokenUsage: ConversationTokenUsage) { + this._tokenUsageInteraction = tokenUsage; + } + + public get inputTokensTotal(): number { return this._tokenUsageInteraction.inputTokensTotal; } @@ -151,13 +163,6 @@ class LLMInteraction { return this._tokenUsageInteraction.totalTokensTotal; } - public get tokenUsageInteraction(): ConversationTokenUsage { - return this._tokenUsageInteraction; - } - public set tokenUsageInteraction(tokenUsage: ConversationTokenUsage) { - this._tokenUsageInteraction = tokenUsage; - } - //public updateTotals(tokenUsage: TokenUsage, providerRequests: number): void { public updateTotals(tokenUsage: TokenUsage): void { this._tokenUsageInteraction.totalTokensTotal += tokenUsage.totalTokens; diff --git a/api/src/llms/interactions/conversationInteraction.ts b/api/src/llms/interactions/conversationInteraction.ts index c03fb15..a26b628 100644 --- a/api/src/llms/interactions/conversationInteraction.ts +++ b/api/src/llms/interactions/conversationInteraction.ts @@ -373,7 +373,7 @@ class LLMConversationInteraction extends LLMInteraction { const conversationStats: ConversationMetrics = this.getAllStats(); const tokenUsage: TokenUsage = response.messageResponse.usage; - this.conversationLogger.logAssistantMessage(msg, conversationStats, tokenUsage); + this.conversationLogger.logAssistantMessage(msg, conversationStats, tokenUsage, this._tokenUsageStatement, this._tokenUsageInteraction); this._statementCount++; return response; diff --git a/api/src/llms/llmToolManager.ts b/api/src/llms/llmToolManager.ts index 1af1208..6b7867e 100644 --- a/api/src/llms/llmToolManager.ts +++ b/api/src/llms/llmToolManager.ts @@ -6,9 +6,9 @@ import ProjectEditor from '../editor/projectEditor.ts'; import { LLMToolRequestFiles } from './tools/requestFilesTool.ts'; import { LLMToolSearchProject } from './tools/searchProjectTool.ts'; import { LLMToolRunCommand } from './tools/runCommandTool.ts'; -//import { LLMToolApplyPatch } from './tools/applyPatchTool.ts'; +import { LLMToolApplyPatch } from './tools/applyPatchTool.ts'; import { LLMToolSearchAndReplace } from './tools/searchAndReplaceTool.ts'; -import { RewriteFileTool } from './tools/rewriteFileTool.ts'; +import { LLMToolRewriteFile } from './tools/rewriteFileTool.ts'; //import { LLMToolVectorSearch } from './tools/vectorSearchTool.ts'; import { createError, ErrorType } from '../utils/error.utils.ts'; import { LLMValidationErrorOptions } from '../errors/error.ts'; @@ -29,10 +29,10 @@ class LLMToolManager { private registerDefaultTools(): void { this.registerTool(new LLMToolRequestFiles()); this.registerTool(new LLMToolSearchProject()); + this.registerTool(new LLMToolRewriteFile()); this.registerTool(new LLMToolSearchAndReplace()); + this.registerTool(new LLMToolApplyPatch()); // Claude isn't good enough yet writing diff patches this.registerTool(new LLMToolRunCommand()); - this.registerTool(new RewriteFileTool()); - //this.registerTool(new LLMToolApplyPatch()); // Claude isn't good enough yet writing diff patches //this.registerTool(new LLMToolVectorSearch()); } diff --git a/api/src/llms/providers/anthropicLLM.ts b/api/src/llms/providers/anthropicLLM.ts index 1f134fa..be4662c 100755 --- a/api/src/llms/providers/anthropicLLM.ts +++ b/api/src/llms/providers/anthropicLLM.ts @@ -1,14 +1,13 @@ import Anthropic from 'anthropic'; import type { ClientOptions } from 'anthropic'; -import { AnthropicModel, LLMCallbackType, LLMProvider } from '../../types.ts'; +import { AnthropicModel, LLMCallbackType, LLMProvider } from 'api/types.ts'; import LLM from './baseLLM.ts'; import LLMInteraction from '../interactions/baseInteraction.ts'; import LLMMessage, { LLMMessageContentParts, LLMMessageContentPartTextBlock, - LLMMessageContentPartToolResultBlock, -} from '../llmMessage.ts'; +} from 'api/llms/llmMessage.ts'; import LLMTool from '../llmTool.ts'; import { createError } from '../../utils/error.utils.ts'; import { ErrorType, LLMErrorOptions } from '../../errors/error.ts'; @@ -120,10 +119,12 @@ class AnthropicLLM extends LLM { try { //logger.info('llms-anthropic-speakWith-messageParams', messageParams); + //const { data: anthropicMessageStream, response: anthropicResponse } = await this.anthropic.beta.promptCaching.messages.create( const { data: anthropicMessageStream, response: anthropicResponse } = await this.anthropic.messages.create( messageParams as Anthropic.MessageCreateParams, { headers: { 'anthropic-beta': 'max-tokens-3-5-sonnet-2024-07-15' }, + //headers: { 'anthropic-beta': ['max-tokens-3-5-sonnet-2024-07-15', 'prompt-caching-2024-07-31'] }, }, ).withResponse(); diff --git a/api/src/llms/tools/rewriteFileTool.ts b/api/src/llms/tools/rewriteFileTool.ts index e7cf222..19c8228 100644 --- a/api/src/llms/tools/rewriteFileTool.ts +++ b/api/src/llms/tools/rewriteFileTool.ts @@ -1,64 +1,125 @@ -import LLMTool from '../llmTool.ts'; -import { isPathWithinProject } from '../../utils/fileHandling.utils.ts'; -import { ensureFile, writeTextFile } from 'https://deno.land/std/fs/mod.ts'; -import { JSONSchema4 } from 'json-schema'; +import LLMTool, { LLMToolInputSchema, LLMToolRunResult } from '../llmTool.ts'; import LLMConversationInteraction from '../interactions/conversationInteraction.ts'; -import { LLMAnswerToolUse } from '../llmMessage.ts'; import ProjectEditor from '../../editor/projectEditor.ts'; -import { LLMToolRunResult } from '../llmTool.ts'; +import ConversationPersistence from '../../storage/conversationPersistence.ts'; +import { isPathWithinProject } from '../../utils/fileHandling.utils.ts'; +import { createError, ErrorType } from '../../utils/error.utils.ts'; +import { FileHandlingErrorOptions } from '../../errors/error.ts'; +import { LLMAnswerToolUse } from 'api/llms/llmMessage.ts'; +import { logger } from 'shared/logger.ts'; +import { ensureDir } from '@std/fs'; +import { dirname, join } from '@std/path'; + +export class LLMToolRewriteFile extends LLMTool { + constructor() { + super( + 'rewrite_file', + 'Rewrite an entire file or create a new one', + ); + } + + get input_schema(): LLMToolInputSchema { + return { + type: 'object', + properties: { + filePath: { type: 'string', description: 'The path of the file to be rewritten or created' }, + content: { type: 'string', description: 'The new content of the file' }, + createIfMissing: { + type: 'boolean', + description: 'Create the file if it does not exist', + default: true, + }, + }, + required: ['filePath', 'content'], + }; + } + + async runTool( + interaction: LLMConversationInteraction, + toolUse: LLMAnswerToolUse, + projectEditor: ProjectEditor, + ): Promise { + const { toolUseId: _toolUseId, toolInput } = toolUse; + const { filePath, content, createIfMissing = true } = toolInput as { + filePath: string; + content: string; + createIfMissing?: boolean; + }; + + if (!await isPathWithinProject(projectEditor.projectRoot, filePath)) { + throw createError(ErrorType.FileHandling, `Access denied: ${filePath} is outside the project directory`, { + name: 'rewrite-file', + filePath, + operation: 'rewrite-file', + } as FileHandlingErrorOptions); + } + + const fullFilePath = join(projectEditor.projectRoot, filePath); + logger.info(`Handling rewrite for file: ${fullFilePath}`); -export class RewriteFileTool implements LLMTool { - name = 'rewrite_file'; - description = 'Rewrite an entire file or create a new one'; + try { + let isNewFile = false; + try { + await Deno.stat(fullFilePath); + } catch (error) { + if (error instanceof Deno.errors.NotFound && createIfMissing) { + isNewFile = true; + logger.info(`File ${fullFilePath} not found. Creating new file.`); + // Create missing directories + await ensureDir(dirname(fullFilePath)); + logger.info(`Created directory structure for ${fullFilePath}`); + } else { + throw error; + } + } - async execute(params: { filePath: string; content: string; createIfMissing?: boolean }): Promise { - return this.runTool(params); - const { filePath, content, createIfMissing = true } = params; + if (!content) { + const noChangesMessage = + `No changes were made to the file: ${filePath}. The content for the file is empty.`; + logger.info(noChangesMessage); + throw createError(ErrorType.FileHandling, noChangesMessage, { + name: 'rewrite-file', + filePath: filePath, + operation: 'rewrite-file', + } as FileHandlingErrorOptions); + } - if (!await isPathWithinProject(Deno.cwd(), filePath)) { - throw new Error('File path is not within the project directory'); - } + await Deno.writeTextFile(fullFilePath, content); + projectEditor.patchedFiles.add(filePath); + projectEditor.patchContents.set(filePath, JSON.stringify(content)); - try { - if (createIfMissing) { - await ensureFile(filePath); - } - await writeTextFile(filePath, content); - return { - messageId: '', - toolResponse: `File ${filePath} has been successfully rewritten or created.`, - bbaiResponse: '' - }; - } catch (error) { - throw new Error(`Failed to rewrite or create file: ${error.message}`); - } - } + // Log the applied changes + if (interaction) { + logger.info(`Saving conversation rewrite file: ${interaction.id}`); + const persistence = new ConversationPersistence(interaction.id, projectEditor); + await persistence.logPatch(filePath, JSON.stringify(content)); + await projectEditor.orchestratorController.stageAndCommitAfterPatching(interaction); + } - input_schema: JSONSchema4 = { - type: 'object' as const, - properties: { - filePath: { type: 'string' as const, description: 'The path of the file to be rewritten or created' }, - content: { type: 'string', description: 'The new content of the file' }, - createIfMissing: { type: 'boolean' as const, description: 'Create the file if it does not exist', default: true } - }, - required: ['filePath', 'content'] - }; + const { messageId, toolResponse } = projectEditor.orchestratorController.toolManager.finalizeToolUse( + interaction, + toolUse, + isNewFile + ? `File created and contents written successfully to file: ${filePath}` + : `Contents written successfully to file: ${filePath}`, + false, + //projectEditor, + ); - validateInput(input: unknown): boolean { - // Implement input validation logic here - return true; - } + const bbaiResponse = `BBai applied file contents to: ${filePath}`; + return { messageId, toolResponse, bbaiResponse }; + } catch (error) { + if (error.name === 'rewrite-file') { + throw error; + } + let errorMessage = `Failed to write contents to ${filePath}: ${error.message}`; + logger.error(errorMessage); - async runTool(interaction: LLMConversationInteraction, toolUse: LLMAnswerToolUse, projectEditor: ProjectEditor): Promise { - const params = toolUse.toolInput as { filePath: string; content: string; createIfMissing?: boolean }; - return { - type: 'object', - properties: { - filePath: { type: 'string', description: 'The path of the file to be rewritten or created' }, - content: { type: 'string', description: 'The new content of the file' }, - createIfMissing: { type: 'boolean', description: 'Create the file if it does not exist', default: true } - }, - required: ['filePath', 'content'] - }; - } + throw createError(ErrorType.FileHandling, errorMessage, { + name: 'rewrite-file', + filePath: filePath, + operation: 'rewrite-file', + } as FileHandlingErrorOptions); + } + } } diff --git a/api/tests/t/llms/tools/rewriteFileTool.test.ts b/api/tests/t/llms/tools/rewriteFileTool.test.ts index 953ef56..97429ea 100644 --- a/api/tests/t/llms/tools/rewriteFileTool.test.ts +++ b/api/tests/t/llms/tools/rewriteFileTool.test.ts @@ -1,68 +1,125 @@ -import { assertEquals, assertThrows } from 'https://deno.land/std/testing/asserts.ts'; -import { RewriteFileTool } from '../../../../src/llms/tools/rewriteFileTool.ts'; -import { ensureFileSync, existsSync } from 'https://deno.land/std/fs/mod.ts'; -import { resolve } from 'https://deno.land/std/path/mod.ts'; +import { assert, assertEquals, assertThrows } from '../../../deps.ts'; +import { join } from '@std/path'; +import { existsSync } from '@std/fs'; + +import LLMConversationInteraction from '../../../../src/llms/interactions/conversationInteraction.ts'; +import { LLMToolRewriteFile } from '../../../../src/llms/tools/rewriteFileTool.ts'; +import ProjectEditor from '../../../../src/editor/projectEditor.ts'; +import { LLMAnswerToolUse } from 'api/llms/llmMessage.ts'; +import { GitUtils } from 'shared/git.ts'; + +const projectEditor = await getProjectEditor(Deno.makeTempDirSync()); +const testProjectRoot = projectEditor.projectRoot; +console.log('Project editor root:', testProjectRoot); + +// Ensure all file paths are relative to testProjectRoot +const getTestFilePath = (filename: string) => join(testProjectRoot, filename); + +async function getProjectEditor(testProjectRoot: string): Promise { + await GitUtils.initGit(testProjectRoot); + return await new ProjectEditor(testProjectRoot).init(); +} + +// Add this function at the beginning of the test file, after the imports +async function createTestInteraction(conversationId: string): Promise { + //const orchestratorController = await new OrchestratorController(projectEditor).init(); + //orchestratorController.primaryInteractionId = 'test-conversation'; + //const interaction = await orchestratorController.initializePrimaryInteraction('test-conversation'); + const interaction = await projectEditor.initConversation(conversationId); + return interaction as LLMConversationInteraction; +} +const interaction = await createTestInteraction('test-conversation'); Deno.test({ - name: 'RewriteFileTool - rewrite existing file', - async fn() { - const tempDir = await Deno.makeTempDir(); - const testFilePath = resolve(tempDir, 'test.txt'); - ensureFileSync(testFilePath); - await Deno.writeTextFile(testFilePath, 'Original content'); - - const tool = new RewriteFileTool(); - const result = await tool.execute({ - filePath: testFilePath, - content: 'New content', - }); - - assertEquals(result, `File ${testFilePath} has been successfully rewritten or created.`); - const newContent = await Deno.readTextFile(testFilePath); - assertEquals(newContent, 'New content'); - - await Deno.remove(tempDir, { recursive: true }); - }, - sanitizeResources: false, - sanitizeOps: false, + name: 'Rewrite File Tool - rewrite existing file', + async fn() { + const tool = new LLMToolRewriteFile(); + + // Create a test file + const testFile = 'test.txt'; + const testFilePath = getTestFilePath(testFile); + await Deno.writeTextFile(testFilePath, 'Original content'); + + const toolUse: LLMAnswerToolUse = { + toolValidation: { validated: true, results: '' }, + toolUseId: 'test-id', + toolName: 'rewrite_file', + toolInput: { + filePath: testFile, + content: 'New content', + }, + }; + + const result = await tool.runTool(interaction, toolUse, projectEditor); + + assert(result.toolResponse.includes(`Contents written successfully to file: ${testFile}`)); + + const newContent = await Deno.readTextFile(testFilePath); + assertEquals(newContent, 'New content'); + }, + sanitizeResources: false, + sanitizeOps: false, }); Deno.test({ - name: 'RewriteFileTool - create new file', - async fn() { - const tempDir = await Deno.makeTempDir(); - const testFilePath = resolve(tempDir, 'newfile.txt'); - - const tool = new RewriteFileTool(); - const result = await tool.execute({ - filePath: testFilePath, - content: 'New file content', - }); - - assertEquals(result, `File ${testFilePath} has been successfully rewritten or created.`); - assertEquals(existsSync(testFilePath), true); - const newContent = await Deno.readTextFile(testFilePath); - assertEquals(newContent, 'New file content'); - - await Deno.remove(tempDir, { recursive: true }); - }, - sanitizeResources: false, - sanitizeOps: false, + name: 'Rewrite File Tool - create new file', + async fn() { + const tool = new LLMToolRewriteFile(); + + // Create a test file + const testFile = 'new-test.txt'; + const testFilePath = getTestFilePath(testFile); + + const toolUse: LLMAnswerToolUse = { + toolValidation: { validated: true, results: '' }, + toolUseId: 'test-id', + toolName: 'rewrite_file', + toolInput: { + filePath: testFile, + content: 'New file content', + }, + }; + + const result = await tool.runTool(interaction, toolUse, projectEditor); + + assert(result.toolResponse.includes(`File created and contents written successfully to file: ${testFile}`)); + + assert(await Deno.stat(testFilePath)); + + const newContent = await Deno.readTextFile(testFilePath); + assertEquals(newContent, 'New file content'); + }, + sanitizeResources: false, + sanitizeOps: false, }); +/* Deno.test({ - name: 'RewriteFileTool - throw error for file outside project', - async fn() { - const tool = new RewriteFileTool(); - await assertThrows( - () => tool.execute({ - filePath: '/tmp/outside_project.txt', - content: 'This should fail', - }), - Error, - 'File path is not within the project directory' - ); - }, - sanitizeResources: false, - sanitizeOps: false, + name: 'Rewrite File Tool - throw error for file outside project', + async fn() { + const tool = new LLMToolRewriteFile(); + + // Create a test file + const testFile = '/tmp/outside_project.txt'; + //const testFilePath = '/tmp/outside_project.txt'; + + const toolUse: LLMAnswerToolUse = { + toolValidation: { validated: true, results: '' }, + toolUseId: 'test-id', + toolName: 'rewrite_file', + toolInput: { + filePath: testFile, + content: 'New content', + }, + }; + + assertThrows( + async () => await tool.runTool(interaction, toolUse, projectEditor), + Error, + `Access denied: ${testFile} is outside the project directory`, + ); + }, + sanitizeResources: false, + sanitizeOps: false, }); + */ diff --git a/cli/src/utils/websocketManager.ts b/cli/src/utils/websocketManager.ts index d97c5db..754e02f 100644 --- a/cli/src/utils/websocketManager.ts +++ b/cli/src/utils/websocketManager.ts @@ -129,7 +129,7 @@ export class WebsocketManager { } } this.cancellationRequested = false; - throw new Error('Operation cancelled'); + //throw new Error('Operation cancelled'); //console.log(`WebsocketManager: Waiting for answer event for conversation ${conversationId}`); await eventManager.once('cli:conversationWaitForAnswer' as EventName<'cli'>, conversationId) as Promise< EventPayloadMap['cli']['cli:conversationWaitForAnswer'] diff --git a/deno.lock b/deno.lock index 374dd55..57346fd 100644 --- a/deno.lock +++ b/deno.lock @@ -953,6 +953,9 @@ }, "redirects": { "https://deno.land/std/datetime/mod.ts": "https://deno.land/std@0.224.0/datetime/mod.ts", + "https://deno.land/std/fs/mod.ts": "https://deno.land/std@0.224.0/fs/mod.ts", + "https://deno.land/std/path/mod.ts": "https://deno.land/std@0.224.0/path/mod.ts", + "https://deno.land/std/testing/asserts.ts": "https://deno.land/std@0.224.0/testing/asserts.ts", "https://deno.land/x/emit/mod.ts": "https://deno.land/x/emit@0.40.0/mod.ts", "https://esm.sh/ink@4": "https://esm.sh/ink@4.4.1", "https://esm.sh/ink@5": "https://esm.sh/ink@5.0.1", @@ -1183,6 +1186,35 @@ "https://deno.land/std@0.221.0/path/windows/to_namespaced_path.ts": "4ffa4fb6fae321448d5fe810b3ca741d84df4d7897e61ee29be961a6aac89a4c", "https://deno.land/std@0.221.0/text/closest_string.ts": "8a91ee8b6d69ff96addcb7c251dad53b476ac8be9c756a0ef786abe9e13a93a5", "https://deno.land/std@0.221.0/text/levenshtein_distance.ts": "24be5cc88326bbba83ca7c1ea89259af0050cffda2817ff3a6d240ad6495eae2", + "https://deno.land/std@0.224.0/assert/_constants.ts": "a271e8ef5a573f1df8e822a6eb9d09df064ad66a4390f21b3e31f820a38e0975", + "https://deno.land/std@0.224.0/assert/assert.ts": "09d30564c09de846855b7b071e62b5974b001bb72a4b797958fe0660e7849834", + "https://deno.land/std@0.224.0/assert/assert_almost_equals.ts": "9e416114322012c9a21fa68e187637ce2d7df25bcbdbfd957cd639e65d3cf293", + "https://deno.land/std@0.224.0/assert/assert_array_includes.ts": "14c5094471bc8e4a7895fc6aa5a184300d8a1879606574cb1cd715ef36a4a3c7", + "https://deno.land/std@0.224.0/assert/assert_equals.ts": "3bbca947d85b9d374a108687b1a8ba3785a7850436b5a8930d81f34a32cb8c74", + "https://deno.land/std@0.224.0/assert/assert_exists.ts": "43420cf7f956748ae6ed1230646567b3593cb7a36c5a5327269279c870c5ddfd", + "https://deno.land/std@0.224.0/assert/assert_false.ts": "3e9be8e33275db00d952e9acb0cd29481a44fa0a4af6d37239ff58d79e8edeff", + "https://deno.land/std@0.224.0/assert/assert_greater.ts": "5e57b201fd51b64ced36c828e3dfd773412c1a6120c1a5a99066c9b261974e46", + "https://deno.land/std@0.224.0/assert/assert_greater_or_equal.ts": "9870030f997a08361b6f63400273c2fb1856f5db86c0c3852aab2a002e425c5b", + "https://deno.land/std@0.224.0/assert/assert_instance_of.ts": "e22343c1fdcacfaea8f37784ad782683ec1cf599ae9b1b618954e9c22f376f2c", + "https://deno.land/std@0.224.0/assert/assert_is_error.ts": "f856b3bc978a7aa6a601f3fec6603491ab6255118afa6baa84b04426dd3cc491", + "https://deno.land/std@0.224.0/assert/assert_less.ts": "60b61e13a1982865a72726a5fa86c24fad7eb27c3c08b13883fb68882b307f68", + "https://deno.land/std@0.224.0/assert/assert_less_or_equal.ts": "d2c84e17faba4afe085e6c9123a63395accf4f9e00150db899c46e67420e0ec3", + "https://deno.land/std@0.224.0/assert/assert_match.ts": "ace1710dd3b2811c391946954234b5da910c5665aed817943d086d4d4871a8b7", + "https://deno.land/std@0.224.0/assert/assert_not_equals.ts": "78d45dd46133d76ce624b2c6c09392f6110f0df9b73f911d20208a68dee2ef29", + "https://deno.land/std@0.224.0/assert/assert_not_instance_of.ts": "3434a669b4d20cdcc5359779301a0588f941ffdc2ad68803c31eabdb4890cf7a", + "https://deno.land/std@0.224.0/assert/assert_not_match.ts": "df30417240aa2d35b1ea44df7e541991348a063d9ee823430e0b58079a72242a", + "https://deno.land/std@0.224.0/assert/assert_not_strict_equals.ts": "37f73880bd672709373d6dc2c5f148691119bed161f3020fff3548a0496f71b8", + "https://deno.land/std@0.224.0/assert/assert_object_match.ts": "411450fd194fdaabc0089ae68f916b545a49d7b7e6d0026e84a54c9e7eed2693", + "https://deno.land/std@0.224.0/assert/assert_rejects.ts": "4bee1d6d565a5b623146a14668da8f9eb1f026a4f338bbf92b37e43e0aa53c31", + "https://deno.land/std@0.224.0/assert/assert_strict_equals.ts": "b4f45f0fd2e54d9029171876bd0b42dd9ed0efd8f853ab92a3f50127acfa54f5", + "https://deno.land/std@0.224.0/assert/assert_string_includes.ts": "496b9ecad84deab72c8718735373feb6cdaa071eb91a98206f6f3cb4285e71b8", + "https://deno.land/std@0.224.0/assert/assert_throws.ts": "c6508b2879d465898dab2798009299867e67c570d7d34c90a2d235e4553906eb", + "https://deno.land/std@0.224.0/assert/assertion_error.ts": "ba8752bd27ebc51f723702fac2f54d3e94447598f54264a6653d6413738a8917", + "https://deno.land/std@0.224.0/assert/equal.ts": "bddf07bb5fc718e10bb72d5dc2c36c1ce5a8bdd3b647069b6319e07af181ac47", + "https://deno.land/std@0.224.0/assert/fail.ts": "0eba674ffb47dff083f02ced76d5130460bff1a9a68c6514ebe0cdea4abadb68", + "https://deno.land/std@0.224.0/assert/mod.ts": "48b8cb8a619ea0b7958ad7ee9376500fe902284bb36f0e32c598c3dc34cbd6f3", + "https://deno.land/std@0.224.0/assert/unimplemented.ts": "8c55a5793e9147b4f1ef68cd66496b7d5ba7a9e7ca30c6da070c1a58da723d73", + "https://deno.land/std@0.224.0/assert/unreachable.ts": "5ae3dbf63ef988615b93eb08d395dda771c96546565f9e521ed86f6510c29e19", "https://deno.land/std@0.224.0/datetime/_date_time_formatter.ts": "b810c4c0d8f7aafe765fb963efdbf704acceb0730f5b242a7ec46df37834307c", "https://deno.land/std@0.224.0/datetime/constants.ts": "5df80a84e301da6db5793804122c034d2d090da31f1e1c5fdaa831fc70a45da7", "https://deno.land/std@0.224.0/datetime/day_of_year.ts": "5e513e239bd473e86d6a869d154720e9b6b8c86e1f14330ed5bb97575d9fa07b", @@ -1192,6 +1224,105 @@ "https://deno.land/std@0.224.0/datetime/mod.ts": "6a49163dd0c7986658ed425b3555da5312252e5f5156bced6ae7199e651aa83b", "https://deno.land/std@0.224.0/datetime/parse.ts": "d6dea8b3e394908dfd016bbe4545f260da4032493294aee1d10a3cf194c191cb", "https://deno.land/std@0.224.0/datetime/week_of_year.ts": "ecaff056953753db3dabc9a769f80ff7ea7be79ae1e34edb5e2bda336f685757", + "https://deno.land/std@0.224.0/fmt/colors.ts": "508563c0659dd7198ba4bbf87e97f654af3c34eb56ba790260f252ad8012e1c5", + "https://deno.land/std@0.224.0/fs/_create_walk_entry.ts": "5d9d2aaec05bcf09a06748b1684224d33eba7a4de24cf4cf5599991ca6b5b412", + "https://deno.land/std@0.224.0/fs/_get_file_info_type.ts": "da7bec18a7661dba360a1db475b826b18977582ce6fc9b25f3d4ee0403fe8cbd", + "https://deno.land/std@0.224.0/fs/_is_same_path.ts": "709c95868345fea051c58b9e96af95cff94e6ae98dfcff2b66dee0c212c4221f", + "https://deno.land/std@0.224.0/fs/_is_subdir.ts": "c68b309d46cc8568ed83c000f608a61bbdba0943b7524e7a30f9e450cf67eecd", + "https://deno.land/std@0.224.0/fs/_to_path_string.ts": "29bfc9c6c112254961d75cbf6ba814d6de5349767818eb93090cecfa9665591e", + "https://deno.land/std@0.224.0/fs/copy.ts": "7ab12a16adb65d155d4943c88081ca16ce3b0b5acada64c1ce93800653678039", + "https://deno.land/std@0.224.0/fs/empty_dir.ts": "e400e96e1d2c8c558a5a1712063bd43939e00619c1d1cc29959babc6f1639418", + "https://deno.land/std@0.224.0/fs/ensure_dir.ts": "51a6279016c65d2985f8803c848e2888e206d1b510686a509fa7cc34ce59d29f", + "https://deno.land/std@0.224.0/fs/ensure_file.ts": "67608cf550529f3d4aa1f8b6b36bf817bdc40b14487bf8f60e61cbf68f507cf3", + "https://deno.land/std@0.224.0/fs/ensure_link.ts": "5c98503ebfa9cc05e2f2efaa30e91e60b4dd5b43ebbda82f435c0a5c6e3ffa01", + "https://deno.land/std@0.224.0/fs/ensure_symlink.ts": "cafe904cebacb9a761977d6dbf5e3af938be946a723bb394080b9a52714fafe4", + "https://deno.land/std@0.224.0/fs/eol.ts": "18c4ac009d0318504c285879eb7f47942643f13619e0ff070a0edc59353306bd", + "https://deno.land/std@0.224.0/fs/exists.ts": "3d38cb7dcbca3cf313be343a7b8af18a87bddb4b5ca1bd2314be12d06533b50f", + "https://deno.land/std@0.224.0/fs/expand_glob.ts": "2e428d90acc6676b2aa7b5c78ef48f30641b13f1fe658e7976c9064fb4b05309", + "https://deno.land/std@0.224.0/fs/mod.ts": "c25e6802cbf27f3050f60b26b00c2d8dba1cb7fcdafe34c66006a7473b7b34d4", + "https://deno.land/std@0.224.0/fs/move.ts": "ca205d848908d7f217353bc5c623627b1333490b8b5d3ef4cab600a700c9bd8f", + "https://deno.land/std@0.224.0/fs/walk.ts": "cddf87d2705c0163bff5d7767291f05b0f46ba10b8b28f227c3849cace08d303", + "https://deno.land/std@0.224.0/internal/diff.ts": "6234a4b493ebe65dc67a18a0eb97ef683626a1166a1906232ce186ae9f65f4e6", + "https://deno.land/std@0.224.0/internal/format.ts": "0a98ee226fd3d43450245b1844b47003419d34d210fa989900861c79820d21c2", + "https://deno.land/std@0.224.0/internal/mod.ts": "534125398c8e7426183e12dc255bb635d94e06d0f93c60a297723abe69d3b22e", + "https://deno.land/std@0.224.0/path/_common/assert_path.ts": "dbdd757a465b690b2cc72fc5fb7698c51507dec6bfafce4ca500c46b76ff7bd8", + "https://deno.land/std@0.224.0/path/_common/basename.ts": "569744855bc8445f3a56087fd2aed56bdad39da971a8d92b138c9913aecc5fa2", + "https://deno.land/std@0.224.0/path/_common/common.ts": "ef73c2860694775fe8ffcbcdd387f9f97c7a656febf0daa8c73b56f4d8a7bd4c", + "https://deno.land/std@0.224.0/path/_common/constants.ts": "dc5f8057159f4b48cd304eb3027e42f1148cf4df1fb4240774d3492b5d12ac0c", + "https://deno.land/std@0.224.0/path/_common/dirname.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8", + "https://deno.land/std@0.224.0/path/_common/format.ts": "92500e91ea5de21c97f5fe91e178bae62af524b72d5fcd246d6d60ae4bcada8b", + "https://deno.land/std@0.224.0/path/_common/from_file_url.ts": "d672bdeebc11bf80e99bf266f886c70963107bdd31134c4e249eef51133ceccf", + "https://deno.land/std@0.224.0/path/_common/glob_to_reg_exp.ts": "6cac16d5c2dc23af7d66348a7ce430e5de4e70b0eede074bdbcf4903f4374d8d", + "https://deno.land/std@0.224.0/path/_common/normalize.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8", + "https://deno.land/std@0.224.0/path/_common/normalize_string.ts": "33edef773c2a8e242761f731adeb2bd6d683e9c69e4e3d0092985bede74f4ac3", + "https://deno.land/std@0.224.0/path/_common/relative.ts": "faa2753d9b32320ed4ada0733261e3357c186e5705678d9dd08b97527deae607", + "https://deno.land/std@0.224.0/path/_common/strip_trailing_separators.ts": "7024a93447efcdcfeaa9339a98fa63ef9d53de363f1fbe9858970f1bba02655a", + "https://deno.land/std@0.224.0/path/_common/to_file_url.ts": "7f76adbc83ece1bba173e6e98a27c647712cab773d3f8cbe0398b74afc817883", + "https://deno.land/std@0.224.0/path/_interface.ts": "8dfeb930ca4a772c458a8c7bbe1e33216fe91c253411338ad80c5b6fa93ddba0", + "https://deno.land/std@0.224.0/path/_os.ts": "8fb9b90fb6b753bd8c77cfd8a33c2ff6c5f5bc185f50de8ca4ac6a05710b2c15", + "https://deno.land/std@0.224.0/path/basename.ts": "7ee495c2d1ee516ffff48fb9a93267ba928b5a3486b550be73071bc14f8cc63e", + "https://deno.land/std@0.224.0/path/common.ts": "03e52e22882402c986fe97ca3b5bb4263c2aa811c515ce84584b23bac4cc2643", + "https://deno.land/std@0.224.0/path/constants.ts": "0c206169ca104938ede9da48ac952de288f23343304a1c3cb6ec7625e7325f36", + "https://deno.land/std@0.224.0/path/dirname.ts": "85bd955bf31d62c9aafdd7ff561c4b5fb587d11a9a5a45e2b01aedffa4238a7c", + "https://deno.land/std@0.224.0/path/extname.ts": "593303db8ae8c865cbd9ceec6e55d4b9ac5410c1e276bfd3131916591b954441", + "https://deno.land/std@0.224.0/path/format.ts": "6ce1779b0980296cf2bc20d66436b12792102b831fd281ab9eb08fa8a3e6f6ac", + "https://deno.land/std@0.224.0/path/from_file_url.ts": "911833ae4fd10a1c84f6271f36151ab785955849117dc48c6e43b929504ee069", + "https://deno.land/std@0.224.0/path/glob_to_regexp.ts": "7f30f0a21439cadfdae1be1bf370880b415e676097fda584a63ce319053b5972", + "https://deno.land/std@0.224.0/path/is_absolute.ts": "4791afc8bfd0c87f0526eaa616b0d16e7b3ab6a65b62942e50eac68de4ef67d7", + "https://deno.land/std@0.224.0/path/is_glob.ts": "a65f6195d3058c3050ab905705891b412ff942a292bcbaa1a807a74439a14141", + "https://deno.land/std@0.224.0/path/join.ts": "ae2ec5ca44c7e84a235fd532e4a0116bfb1f2368b394db1c4fb75e3c0f26a33a", + "https://deno.land/std@0.224.0/path/join_globs.ts": "5b3bf248b93247194f94fa6947b612ab9d3abd571ca8386cf7789038545e54a0", + "https://deno.land/std@0.224.0/path/mod.ts": "f6bd79cb08be0e604201bc9de41ac9248582699d1b2ee0ab6bc9190d472cf9cd", + "https://deno.land/std@0.224.0/path/normalize.ts": "4155743ccceeed319b350c1e62e931600272fad8ad00c417b91df093867a8352", + "https://deno.land/std@0.224.0/path/normalize_glob.ts": "cc89a77a7d3b1d01053b9dcd59462b75482b11e9068ae6c754b5cf5d794b374f", + "https://deno.land/std@0.224.0/path/parse.ts": "77ad91dcb235a66c6f504df83087ce2a5471e67d79c402014f6e847389108d5a", + "https://deno.land/std@0.224.0/path/posix/_util.ts": "1e3937da30f080bfc99fe45d7ed23c47dd8585c5e473b2d771380d3a6937cf9d", + "https://deno.land/std@0.224.0/path/posix/basename.ts": "d2fa5fbbb1c5a3ab8b9326458a8d4ceac77580961b3739cd5bfd1d3541a3e5f0", + "https://deno.land/std@0.224.0/path/posix/common.ts": "26f60ccc8b2cac3e1613000c23ac5a7d392715d479e5be413473a37903a2b5d4", + "https://deno.land/std@0.224.0/path/posix/constants.ts": "93481efb98cdffa4c719c22a0182b994e5a6aed3047e1962f6c2c75b7592bef1", + "https://deno.land/std@0.224.0/path/posix/dirname.ts": "76cd348ffe92345711409f88d4d8561d8645353ac215c8e9c80140069bf42f00", + "https://deno.land/std@0.224.0/path/posix/extname.ts": "e398c1d9d1908d3756a7ed94199fcd169e79466dd88feffd2f47ce0abf9d61d2", + "https://deno.land/std@0.224.0/path/posix/format.ts": "185e9ee2091a42dd39e2a3b8e4925370ee8407572cee1ae52838aed96310c5c1", + "https://deno.land/std@0.224.0/path/posix/from_file_url.ts": "951aee3a2c46fd0ed488899d024c6352b59154c70552e90885ed0c2ab699bc40", + "https://deno.land/std@0.224.0/path/posix/glob_to_regexp.ts": "76f012fcdb22c04b633f536c0b9644d100861bea36e9da56a94b9c589a742e8f", + "https://deno.land/std@0.224.0/path/posix/is_absolute.ts": "cebe561ad0ae294f0ce0365a1879dcfca8abd872821519b4fcc8d8967f888ede", + "https://deno.land/std@0.224.0/path/posix/is_glob.ts": "8a8b08c08bf731acf2c1232218f1f45a11131bc01de81e5f803450a5914434b9", + "https://deno.land/std@0.224.0/path/posix/join.ts": "7fc2cb3716aa1b863e990baf30b101d768db479e70b7313b4866a088db016f63", + "https://deno.land/std@0.224.0/path/posix/join_globs.ts": "a9475b44645feddceb484ee0498e456f4add112e181cb94042cdc6d47d1cdd25", + "https://deno.land/std@0.224.0/path/posix/mod.ts": "2301fc1c54a28b349e20656f68a85f75befa0ee9b6cd75bfac3da5aca9c3f604", + "https://deno.land/std@0.224.0/path/posix/normalize.ts": "baeb49816a8299f90a0237d214cef46f00ba3e95c0d2ceb74205a6a584b58a91", + "https://deno.land/std@0.224.0/path/posix/normalize_glob.ts": "9c87a829b6c0f445d03b3ecadc14492e2864c3ebb966f4cea41e98326e4435c6", + "https://deno.land/std@0.224.0/path/posix/parse.ts": "09dfad0cae530f93627202f28c1befa78ea6e751f92f478ca2cc3b56be2cbb6a", + "https://deno.land/std@0.224.0/path/posix/relative.ts": "3907d6eda41f0ff723d336125a1ad4349112cd4d48f693859980314d5b9da31c", + "https://deno.land/std@0.224.0/path/posix/resolve.ts": "08b699cfeee10cb6857ccab38fa4b2ec703b0ea33e8e69964f29d02a2d5257cf", + "https://deno.land/std@0.224.0/path/posix/to_file_url.ts": "7aa752ba66a35049e0e4a4be5a0a31ac6b645257d2e031142abb1854de250aaf", + "https://deno.land/std@0.224.0/path/posix/to_namespaced_path.ts": "28b216b3c76f892a4dca9734ff1cc0045d135532bfd9c435ae4858bfa5a2ebf0", + "https://deno.land/std@0.224.0/path/relative.ts": "ab739d727180ed8727e34ed71d976912461d98e2b76de3d3de834c1066667add", + "https://deno.land/std@0.224.0/path/resolve.ts": "a6f977bdb4272e79d8d0ed4333e3d71367cc3926acf15ac271f1d059c8494d8d", + "https://deno.land/std@0.224.0/path/to_file_url.ts": "88f049b769bce411e2d2db5bd9e6fd9a185a5fbd6b9f5ad8f52bef517c4ece1b", + "https://deno.land/std@0.224.0/path/to_namespaced_path.ts": "b706a4103b104cfadc09600a5f838c2ba94dbcdb642344557122dda444526e40", + "https://deno.land/std@0.224.0/path/windows/_util.ts": "d5f47363e5293fced22c984550d5e70e98e266cc3f31769e1710511803d04808", + "https://deno.land/std@0.224.0/path/windows/basename.ts": "6bbc57bac9df2cec43288c8c5334919418d784243a00bc10de67d392ab36d660", + "https://deno.land/std@0.224.0/path/windows/common.ts": "26f60ccc8b2cac3e1613000c23ac5a7d392715d479e5be413473a37903a2b5d4", + "https://deno.land/std@0.224.0/path/windows/constants.ts": "5afaac0a1f67b68b0a380a4ef391bf59feb55856aa8c60dfc01bd3b6abb813f5", + "https://deno.land/std@0.224.0/path/windows/dirname.ts": "33e421be5a5558a1346a48e74c330b8e560be7424ed7684ea03c12c21b627bc9", + "https://deno.land/std@0.224.0/path/windows/extname.ts": "165a61b00d781257fda1e9606a48c78b06815385e7d703232548dbfc95346bef", + "https://deno.land/std@0.224.0/path/windows/format.ts": "bbb5ecf379305b472b1082cd2fdc010e44a0020030414974d6029be9ad52aeb6", + "https://deno.land/std@0.224.0/path/windows/from_file_url.ts": "ced2d587b6dff18f963f269d745c4a599cf82b0c4007356bd957cb4cb52efc01", + "https://deno.land/std@0.224.0/path/windows/glob_to_regexp.ts": "e45f1f89bf3fc36f94ab7b3b9d0026729829fabc486c77f414caebef3b7304f8", + "https://deno.land/std@0.224.0/path/windows/is_absolute.ts": "4a8f6853f8598cf91a835f41abed42112cebab09478b072e4beb00ec81f8ca8a", + "https://deno.land/std@0.224.0/path/windows/is_glob.ts": "8a8b08c08bf731acf2c1232218f1f45a11131bc01de81e5f803450a5914434b9", + "https://deno.land/std@0.224.0/path/windows/join.ts": "8d03530ab89195185103b7da9dfc6327af13eabdcd44c7c63e42e27808f50ecf", + "https://deno.land/std@0.224.0/path/windows/join_globs.ts": "a9475b44645feddceb484ee0498e456f4add112e181cb94042cdc6d47d1cdd25", + "https://deno.land/std@0.224.0/path/windows/mod.ts": "2301fc1c54a28b349e20656f68a85f75befa0ee9b6cd75bfac3da5aca9c3f604", + "https://deno.land/std@0.224.0/path/windows/normalize.ts": "78126170ab917f0ca355a9af9e65ad6bfa5be14d574c5fb09bb1920f52577780", + "https://deno.land/std@0.224.0/path/windows/normalize_glob.ts": "9c87a829b6c0f445d03b3ecadc14492e2864c3ebb966f4cea41e98326e4435c6", + "https://deno.land/std@0.224.0/path/windows/parse.ts": "08804327b0484d18ab4d6781742bf374976de662f8642e62a67e93346e759707", + "https://deno.land/std@0.224.0/path/windows/relative.ts": "3e1abc7977ee6cc0db2730d1f9cb38be87b0ce4806759d271a70e4997fc638d7", + "https://deno.land/std@0.224.0/path/windows/resolve.ts": "8dae1dadfed9d46ff46cc337c9525c0c7d959fb400a6308f34595c45bdca1972", + "https://deno.land/std@0.224.0/path/windows/to_file_url.ts": "40e560ee4854fe5a3d4d12976cef2f4e8914125c81b11f1108e127934ced502e", + "https://deno.land/std@0.224.0/path/windows/to_namespaced_path.ts": "4ffa4fb6fae321448d5fe810b3ca741d84df4d7897e61ee29be961a6aac89a4c", + "https://deno.land/std@0.224.0/testing/asserts.ts": "d0cdbabadc49cc4247a50732ee0df1403fdcd0f95360294ad448ae8c240f3f5c", "https://deno.land/std@0.53.0/fmt/colors.ts": "ec9d653672a9a3c7b6eafe53c5bc797364a2db2dcf766ab649c1155fea7a80b2", "https://deno.land/std@0.97.0/fmt/colors.ts": "db22b314a2ae9430ae7460ce005e0a7130e23ae1c999157e3bb77cf55800f7e4", "https://deno.land/std@0.97.0/testing/_diff.ts": "961eaf6d9f5b0a8556c9d835bbc6fa74f5addd7d3b02728ba7936ff93364f7a3", diff --git a/src/shared/types.ts b/src/shared/types.ts index 34cc02b..0526168 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -69,6 +69,7 @@ export interface ConversationEntry { type: ConversationLoggerEntryType; timestamp: string; content: string; + tokenUsageTurn: TokenUsage; tokenUsageStatement: TokenUsage; tokenUsageConversation: ConversationTokenUsage; conversationStats: ConversationMetrics; diff --git a/src/shared/utils/conversationLogger.utils.ts b/src/shared/utils/conversationLogger.utils.ts index 9c9fe98..ff2c3b5 100644 --- a/src/shared/utils/conversationLogger.utils.ts +++ b/src/shared/utils/conversationLogger.utils.ts @@ -1,7 +1,7 @@ import { join } from '@std/path'; import { ensureDir } from '@std/fs'; -import type { ConversationId, ConversationMetrics, TokenUsage, ConversationTokenUsage } from 'shared/types.ts'; +import { ConversationId, ConversationMetrics, ConversationTokenUsage, TokenUsage } from 'shared/types.ts'; import { getBbaiDataDir } from 'shared/dataDir.ts'; import { LogFormatter } from 'shared/logFormatter.ts'; //import { logger } from 'shared/logger.ts'; @@ -50,18 +50,25 @@ export class ConversationLogger { private async logEntry( type: ConversationLoggerEntryType, message: string, - conversationStats: ConversationMetrics, - tokenUsageTurn: TokenUsage, - tokenUsageStatement: TokenUsage, + conversationStats: ConversationMetrics = { statementCount: 0, turnCount: 0, totalTurnCount: 0 }, + tokenUsageTurn: TokenUsage = { + inputTokens: 0, + outputTokens: 0, + totalTokens: 0, + }, + tokenUsageStatement: TokenUsage = { + inputTokens: 0, + outputTokens: 0, + totalTokens: 0, + }, + tokenUsageConversation: ConversationTokenUsage = { + inputTokensTotal: 0, + outputTokensTotal: 0, + totalTokensTotal: 0, + }, ) { const timestamp = this.getTimestamp(); - const tokenUsageConversation: ConversationTokenUsage = { - inputTokensTotal: tokenUsageTurn.inputTokens + tokenUsageStatement.inputTokens, - outputTokensTotal: tokenUsageTurn.outputTokens + tokenUsageStatement.outputTokens, - totalTokensTotal: tokenUsageTurn.totalTokens + tokenUsageStatement.totalTokens - }; - //const entry = LogFormatter.createRawEntryWithSeparator(type, timestamp, message, tokenUsageTurn); const entry = LogFormatter.createRawEntryWithSeparator( type, timestamp, @@ -85,7 +92,7 @@ export class ConversationLogger { } async logUserMessage(message: string, conversationStats?: ConversationMetrics) { - await this.logEntry('user', message, conversationStats, { inputTokens: 0, outputTokens: 0, totalTokens: 0 }, { inputTokens: 0, outputTokens: 0, totalTokens: 0 }); + await this.logEntry('user', message, conversationStats); } async logAssistantMessage( @@ -106,7 +113,10 @@ export class ConversationLogger { } async logAuxiliaryMessage(message: string) { - await this.logEntry('auxiliary', message, { statementCount: 0, turnCount: 0, totalTurnCount: 0 }, { inputTokens: 0, outputTokens: 0, totalTokens: 0 }, { inputTokens: 0, outputTokens: 0, totalTokens: 0 }); + await this.logEntry( + 'auxiliary', + message, + ); } async logToolUse( @@ -157,7 +167,7 @@ export class ConversationLogger { } async logError(error: string) { - await this.logEntry('error', error, { statementCount: 0, turnCount: 0, totalTurnCount: 0 }, { inputTokens: 0, outputTokens: 0, totalTokens: 0 }, { inputTokens: 0, outputTokens: 0, totalTokens: 0 }); + await this.logEntry('error', error); } //async logTextChange(filePath: string, patch: string) { diff --git a/src/shared/utils/logFormatter.utils.ts b/src/shared/utils/logFormatter.utils.ts index 1b84525..d8d8320 100644 --- a/src/shared/utils/logFormatter.utils.ts +++ b/src/shared/utils/logFormatter.utils.ts @@ -5,7 +5,7 @@ import { BufReader } from '@std/io'; import { colors } from 'cliffy/ansi/mod.ts'; import { getBbaiDataDir } from 'shared/dataDir.ts'; -import { ConversationId, ConversationMetrics, TokenUsage } from 'shared/types.ts'; +import { ConversationId, ConversationMetrics, ConversationTokenUsage, TokenUsage } from 'shared/types.ts'; import { config } from 'shared/configManager.ts'; // Define theme colors. @@ -104,6 +104,8 @@ export class LogFormatter { message: string, conversationStats: ConversationMetrics, tokenUsage: TokenUsage, + tokenUsageStatement?: TokenUsage, + tokenUsageConversation?: ConversationTokenUsage, ): string { // [TODO] add token usage to header line const { label } = LogFormatter.iconColorMap[type] || { label: 'Unknown' }; @@ -116,8 +118,18 @@ export class LogFormatter { message: string, conversationStats: ConversationMetrics, tokenUsage: TokenUsage, + tokenUsageStatement?: TokenUsage, + tokenUsageConversation?: ConversationTokenUsage, ): string { - let rawEntry = LogFormatter.createRawEntry(type, timestamp, message, conversationStats, tokenUsage); + let rawEntry = LogFormatter.createRawEntry( + type, + timestamp, + message, + conversationStats, + tokenUsage, + tokenUsageStatement, + tokenUsageConversation, + ); // Ensure entry ends with a single newline and the separator rawEntry = rawEntry.trimEnd() + '\n' + LogFormatter.getEntrySeparator() + '\n'; return rawEntry; @@ -297,12 +309,22 @@ export async function writeLogEntry( message: string, conversationStats: ConversationMetrics, tokenUsage: TokenUsage, + tokenUsageStatement?: TokenUsage, + tokenUsageConversation?: ConversationTokenUsage, ): Promise { const bbaiDataDir = await getBbaiDataDir(Deno.cwd()); const logFile = join(bbaiDataDir, 'conversations', conversationId, 'conversation.log'); const timestamp = new Date().toISOString(); - const entry = LogFormatter.createRawEntryWithSeparator(type, timestamp, message, conversationStats, tokenUsage); + const entry = LogFormatter.createRawEntryWithSeparator( + type, + timestamp, + message, + conversationStats, + tokenUsage, + tokenUsageStatement, + tokenUsageConversation, + ); try { // Append the entry to the log file From ce2dd54385156c19ef85b1595c39e4afe4f6b4f3 Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Thu, 15 Aug 2024 23:05:50 +1000 Subject: [PATCH 11/31] Added LLMToolRunResultContent, LLMToolFinalizeResult, and ToolFormatter interfaces to llmTool.ts. Applied patches from BBai to 1 file Files modified: - api/src/llms/llmTool.ts --- api/src/llms/llmTool.ts | 47 ++++++++++++++++------------------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/api/src/llms/llmTool.ts b/api/src/llms/llmTool.ts index 9b5ae2d..0a004ae 100644 --- a/api/src/llms/llmTool.ts +++ b/api/src/llms/llmTool.ts @@ -1,42 +1,31 @@ -import { JSONSchema4 } from 'json-schema'; -import Ajv from 'ajv'; - -import ProjectEditor from '../editor/projectEditor.ts'; -import { LLMAnswerToolUse, LLMMessageContentPart, LLMMessageContentParts } from 'api/llms/llmMessage.ts'; +import { LLMAnswerToolUse, LLMMessageContentPart, LLMMessageContentParts } from './llmMessage.ts'; import LLMConversationInteraction from './interactions/conversationInteraction.ts'; +import ProjectEditor from '../editor/projectEditor.ts'; -export type LLMToolInputSchema = JSONSchema4; export type LLMToolRunResultContent = string | LLMMessageContentPart | LLMMessageContentParts; export interface LLMToolFinalizeResult { - messageId: string; - toolResponse: string; + messageId: string; + toolResponse: string; } -export interface LLMToolRunResult { - messageId: string; - toolResponse: string; - bbaiResponse: string; + +export interface ToolFormatter { + formatToolUse(toolName: string, input: object): string; + formatToolResult(toolName: string, result: LLMToolRunResultContent): string; } abstract class LLMTool { - constructor( - public name: string, - public description: string, - ) {} - - abstract get input_schema(): LLMToolInputSchema; + abstract name: string; + abstract description: string; + abstract parameters: Record; - validateInput(input: unknown): boolean { - const ajv = new Ajv(); - const validate = ajv.compile(this.input_schema); - return validate(input) as boolean; - } + abstract validateInput(input: unknown): boolean; - abstract runTool( - interaction: LLMConversationInteraction, - toolUse: LLMAnswerToolUse, - projectEditor: ProjectEditor, - ): Promise; + abstract runTool( + interaction: LLMConversationInteraction, + toolUse: LLMAnswerToolUse, + projectEditor: ProjectEditor, + ): Promise<{ messageId: string; toolResponse: string; bbaiResponse: string }>; } -export default LLMTool; +export default LLMTool; \ No newline at end of file From 497471319d92d81d321a9f99e73bb6f9a346fbdf Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Thu, 15 Aug 2024 23:05:58 +1000 Subject: [PATCH 12/31] Added tool formatter registration and retrieval functionality to LLMToolManager. Applied patches from BBai to 1 file Files modified: - api/src/llms/llmToolManager.ts --- api/src/llms/llmToolManager.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/api/src/llms/llmToolManager.ts b/api/src/llms/llmToolManager.ts index 6b7867e..17b5433 100644 --- a/api/src/llms/llmToolManager.ts +++ b/api/src/llms/llmToolManager.ts @@ -1,5 +1,5 @@ import { logger } from 'shared/logger.ts'; -import LLMTool, { LLMToolFinalizeResult, LLMToolRunResultContent } from './llmTool.ts'; +import LLMTool, { LLMToolFinalizeResult, LLMToolRunResultContent, ToolFormatter } from './llmTool.ts'; import { LLMAnswerToolUse, LLMMessageContentPart } from 'api/llms/llmMessage.ts'; import LLMConversationInteraction from './interactions/conversationInteraction.ts'; import ProjectEditor from '../editor/projectEditor.ts'; @@ -16,6 +16,16 @@ import { LLMValidationErrorOptions } from '../errors/error.ts'; export type LLMToolManagerToolSetType = 'coding' | 'research' | 'creative'; class LLMToolManager { + static toolFormatters: Map = new Map(); + + static registerToolFormatter(toolName: string, formatter: ToolFormatter): void { + LLMToolManager.toolFormatters.set(toolName, formatter); + } + + static getToolFormatter(toolName: string): ToolFormatter | undefined { + return LLMToolManager.toolFormatters.get(toolName); + } + private tools: Map = new Map(); public toolSet: LLMToolManagerToolSetType = 'coding'; From 9784fb7b8baf32b5ff68360c6e9aa590d671411f Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Thu, 15 Aug 2024 23:06:20 +1000 Subject: [PATCH 13/31] Added tool formatter support to conversation logger Applied patches from BBai to 1 file Files modified: - src/shared/utils/conversationLogger.utils.ts --- src/shared/utils/conversationLogger.utils.ts | 41 +++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/shared/utils/conversationLogger.utils.ts b/src/shared/utils/conversationLogger.utils.ts index ff2c3b5..cdd2da7 100644 --- a/src/shared/utils/conversationLogger.utils.ts +++ b/src/shared/utils/conversationLogger.utils.ts @@ -127,8 +127,10 @@ export class ConversationLogger { tokenUsageStatement?: TokenUsage, tokenUsageConversation?: ConversationTokenUsage, ) { - //const message = `Tool: ${toolName}\nInput: \n${JSON.stringify(input, null, 2)}`; - const message = `Tool: ${toolName}\nInput: \n${JSON.stringify(input)}`; + const formatter = LLMToolManager.getToolFormatter(toolName); + const message = formatter + ? formatter.formatToolUse(toolName, input) + : `Tool: ${toolName}\nInput: \n${JSON.stringify(input, null, 2)}`; await this.logEntry( 'tool_use', message, @@ -147,23 +149,12 @@ export class ConversationLogger { //tokenUsageStatement?: TokenUsage, //tokenUsageConversation?: ConversationTokenUsage, ) { - const message = `Tool: ${toolName}\nResult: ${ - Array.isArray(result) - ? 'text' in result[0] - ? (result[0] as LLMMessageContentPartTextBlock).text - : JSON.stringify(result[0], null, 2) - : typeof result !== 'string' - ? 'text' in result ? (result as LLMMessageContentPartTextBlock).text : JSON.stringify(result, null, 2) - : result - }`; - await this.logEntry( - 'tool_result', - message, - //conversationStats, - //tokenUsageTurn, - //tokenUsageStatement, - //tokenUsageConversation, - ); + const formatter = LLMToolManager.getToolFormatter(toolName); + const message = formatter + ? formatter.formatToolResult(toolName, result) + : `Tool: ${toolName}\nResult: ${this.formatDefaultToolResult(result)}`; + await this.logEntry('tool_result', message); + } } async logError(error: string) { @@ -174,4 +165,16 @@ export class ConversationLogger { // const message = `Diff Patch for ${filePath}:\n${patch}`; // await this.logEntry('text_change', message); //} + + private formatDefaultToolResult(result: string | LLMMessageContentPart | LLMMessageContentParts): string { + if (Array.isArray(result)) { + return 'text' in result[0] + ? (result[0] as LLMMessageContentPartTextBlock).text + : JSON.stringify(result[0], null, 2); + } else if (typeof result !== 'string') { + return 'text' in result ? (result as LLMMessageContentPartTextBlock).text : JSON.stringify(result, null, 2); + } else { + return result; + } + } } From 23fc03d4bde5d91803df02945c5e1233192cd4ee Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Thu, 15 Aug 2024 23:06:33 +1000 Subject: [PATCH 14/31] Added RequestFilesToolFormatter and registered it with LLMToolManager Applied patches from BBai to 1 file Files modified: - api/src/llms/tools/requestFilesTool.ts --- api/src/llms/tools/requestFilesTool.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/api/src/llms/tools/requestFilesTool.ts b/api/src/llms/tools/requestFilesTool.ts index 5ceeb03..ccc3221 100644 --- a/api/src/llms/tools/requestFilesTool.ts +++ b/api/src/llms/tools/requestFilesTool.ts @@ -5,6 +5,23 @@ import { LLMAnswerToolUse, LLMMessageContentPartTextBlock } from 'api/llms/llmMe import ProjectEditor from '../../editor/projectEditor.ts'; import { createError, ErrorType } from '../../utils/error.utils.ts'; +class RequestFilesToolFormatter implements ToolFormatter { + formatToolUse(toolName: string, input: object): string { + const { fileNames } = input as { fileNames: string[] }; + return `Tool: ${toolName}\nRequested files: ${fileNames.join(', ')}`; + } + + formatToolResult(toolName: string, result: string | LLMMessageContentPart | LLMMessageContentParts): string { + if (typeof result === 'string') { + return `Tool: ${toolName}\nResult: ${result}`; + } else if (Array.isArray(result)) { + return `Tool: ${toolName}\nResult: ${result.map(part => 'text' in part ? part.text : JSON.stringify(part)).join('\n')}`; + } else { + return `Tool: ${toolName}\nResult: ${'text' in result ? result.text : JSON.stringify(result)}`; + } + } +} + export class LLMToolRequestFiles extends LLMTool { constructor() { super( From 6eb12f1eecc62d84bdea7c2ecba9535c4b68acad Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Thu, 15 Aug 2024 23:08:32 +1000 Subject: [PATCH 15/31] Added error handling to `logToolUse` and `logToolResult` functions in `conversationLogger.utils.ts` to gracefully handle errors when formatting tool use and result messages. Applied patches from BBai to 1 file Files modified: - src/shared/utils/conversationLogger.utils.ts --- src/shared/utils/conversationLogger.utils.ts | 37 +++++++++++++++----- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/src/shared/utils/conversationLogger.utils.ts b/src/shared/utils/conversationLogger.utils.ts index cdd2da7..b8db9fb 100644 --- a/src/shared/utils/conversationLogger.utils.ts +++ b/src/shared/utils/conversationLogger.utils.ts @@ -127,10 +127,18 @@ export class ConversationLogger { tokenUsageStatement?: TokenUsage, tokenUsageConversation?: ConversationTokenUsage, ) { - const formatter = LLMToolManager.getToolFormatter(toolName); - const message = formatter - ? formatter.formatToolUse(toolName, input) - : `Tool: ${toolName}\nInput: \n${JSON.stringify(input, null, 2)}`; + let message: string; + try { + const formatter = LLMToolManager.getToolFormatter(toolName); + if (formatter) { + message = formatter.formatToolUse(toolName, input); + } else { + message = `Tool: ${toolName}\nInput: \n${JSON.stringify(input, null, 2)}`; + } + } catch (error) { + console.error(`Error formatting tool use for ${toolName}:`, error); + message = `Tool: ${toolName}\nInput: [Error formatting input]`; + } await this.logEntry( 'tool_use', message, @@ -149,10 +157,18 @@ export class ConversationLogger { //tokenUsageStatement?: TokenUsage, //tokenUsageConversation?: ConversationTokenUsage, ) { - const formatter = LLMToolManager.getToolFormatter(toolName); - const message = formatter - ? formatter.formatToolResult(toolName, result) - : `Tool: ${toolName}\nResult: ${this.formatDefaultToolResult(result)}`; + let message: string; + try { + const formatter = LLMToolManager.getToolFormatter(toolName); + if (formatter) { + message = formatter.formatToolResult(toolName, result); + } else { + message = `Tool: ${toolName}\nResult: ${this.formatDefaultToolResult(result)}`; + } + } catch (error) { + console.error(`Error formatting tool result for ${toolName}:`, error); + message = `Tool: ${toolName}\nResult: [Error formatting result]`; + } await this.logEntry('tool_result', message); } } @@ -167,6 +183,7 @@ export class ConversationLogger { //} private formatDefaultToolResult(result: string | LLMMessageContentPart | LLMMessageContentParts): string { + try { if (Array.isArray(result)) { return 'text' in result[0] ? (result[0] as LLMMessageContentPartTextBlock).text @@ -176,5 +193,9 @@ export class ConversationLogger { } else { return result; } + } catch (error) { + console.error('Error in formatDefaultToolResult:', error); + return '[Error formatting result]'; + } } } From 0f3692fa495c2a00d4047345ab8b43b9d3be0864 Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Thu, 15 Aug 2024 23:13:34 +1000 Subject: [PATCH 16/31] Registered RequestFilesTool formatter and added static formatter property Applied patches from BBai to 1 file Files modified: - api/src/llms/tools/requestFilesTool.ts --- api/src/llms/tools/requestFilesTool.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/api/src/llms/tools/requestFilesTool.ts b/api/src/llms/tools/requestFilesTool.ts index ccc3221..0ccac97 100644 --- a/api/src/llms/tools/requestFilesTool.ts +++ b/api/src/llms/tools/requestFilesTool.ts @@ -23,6 +23,12 @@ class RequestFilesToolFormatter implements ToolFormatter { } export class LLMToolRequestFiles extends LLMTool { + private static formatter = new RequestFilesToolFormatter(); + + private registerFormatter() { + LLMToolManager.registerToolFormatter(this.name, LLMToolRequestFiles.formatter); + } + constructor() { super( 'request_files', From 1c58a9d7391b1a581504ea73f481257b5b7c2860 Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Thu, 15 Aug 2024 23:36:54 +1000 Subject: [PATCH 17/31] Implemented LLMToolManager class with tool loading, registration, and handling of tool usage Applied patches from BBai to 1 file Files modified: - api/src/llms/llmToolManager.ts --- api/src/llms/llmToolManager.ts | 260 +++++++++++++++++---------------- 1 file changed, 131 insertions(+), 129 deletions(-) diff --git a/api/src/llms/llmToolManager.ts b/api/src/llms/llmToolManager.ts index 17b5433..99ef25f 100644 --- a/api/src/llms/llmToolManager.ts +++ b/api/src/llms/llmToolManager.ts @@ -1,146 +1,148 @@ import { logger } from 'shared/logger.ts'; import LLMTool, { LLMToolFinalizeResult, LLMToolRunResultContent, ToolFormatter } from './llmTool.ts'; -import { LLMAnswerToolUse, LLMMessageContentPart } from 'api/llms/llmMessage.ts'; +import { LLMAnswerToolUse, LLMMessageContentPart } from './llmMessage.ts'; import LLMConversationInteraction from './interactions/conversationInteraction.ts'; import ProjectEditor from '../editor/projectEditor.ts'; -import { LLMToolRequestFiles } from './tools/requestFilesTool.ts'; -import { LLMToolSearchProject } from './tools/searchProjectTool.ts'; -import { LLMToolRunCommand } from './tools/runCommandTool.ts'; -import { LLMToolApplyPatch } from './tools/applyPatchTool.ts'; -import { LLMToolSearchAndReplace } from './tools/searchAndReplaceTool.ts'; -import { LLMToolRewriteFile } from './tools/rewriteFileTool.ts'; -//import { LLMToolVectorSearch } from './tools/vectorSearchTool.ts'; import { createError, ErrorType } from '../utils/error.utils.ts'; import { LLMValidationErrorOptions } from '../errors/error.ts'; export type LLMToolManagerToolSetType = 'coding' | 'research' | 'creative'; class LLMToolManager { - static toolFormatters: Map = new Map(); + private static instance: LLMToolManager; + private tools: Map = new Map(); + private toolFormatters: Map = new Map(); + public toolSet: LLMToolManagerToolSetType = 'coding'; - static registerToolFormatter(toolName: string, formatter: ToolFormatter): void { - LLMToolManager.toolFormatters.set(toolName, formatter); + private constructor() { + this.loadTools(); } - static getToolFormatter(toolName: string): ToolFormatter | undefined { - return LLMToolManager.toolFormatters.get(toolName); + static getInstance(): LLMToolManager { + if (!LLMToolManager.instance) { + LLMToolManager.instance = new LLMToolManager(); + } + return LLMToolManager.instance; } - private tools: Map = new Map(); - public toolSet: LLMToolManagerToolSetType = 'coding'; - - constructor(toolSet?: LLMToolManagerToolSetType) { - if (toolSet) { - this.toolSet = toolSet; - } - this.registerDefaultTools(); - } - - private registerDefaultTools(): void { - this.registerTool(new LLMToolRequestFiles()); - this.registerTool(new LLMToolSearchProject()); - this.registerTool(new LLMToolRewriteFile()); - this.registerTool(new LLMToolSearchAndReplace()); - this.registerTool(new LLMToolApplyPatch()); // Claude isn't good enough yet writing diff patches - this.registerTool(new LLMToolRunCommand()); - //this.registerTool(new LLMToolVectorSearch()); - } - - registerTool(tool: LLMTool): void { - this.tools.set(tool.name, tool); - } - - getTool(name: string): LLMTool | undefined { - return this.tools.get(name); - } - - getAllTools(): LLMTool[] { - return Array.from(this.tools.values()); - } - - async handleToolUse( - interaction: LLMConversationInteraction, - toolUse: LLMAnswerToolUse, - projectEditor: ProjectEditor, - ): Promise<{ messageId: string; toolResponse: string; bbaiResponse: string; isError: boolean }> { - try { - const tool = this.getTool(toolUse.toolName); - if (!tool) { - logger.warn(`Unknown tool used: ${toolUse.toolName}`); - throw new Error(`Unknown tool used: ${toolUse.toolName}`); - } - - if (!toolUse.toolValidation.validated && !tool.validateInput(toolUse.toolInput)) { - throw createError(ErrorType.LLMValidation, `Invalid input for ${toolUse.toolName} tool`, { - name: `tool_use-${toolUse.toolName}`, - validation_type: 'input_schema', - validation_error: 'Input does not match the required schema', - } as LLMValidationErrorOptions); - } else { - logger.info( - `handleToolUse - Tool ${toolUse.toolName} is already validated with results: ${toolUse.toolValidation.results}`, - ); - } - //logger.debug(`handleToolUse - calling runTool for ${toolUse.toolName}`); - - // runTool will call finalizeToolUse, which handles addMessageForToolResult - const { messageId, toolResponse, bbaiResponse } = await tool.runTool(interaction, toolUse, projectEditor); - return { messageId, toolResponse, bbaiResponse, isError: false }; - } catch (error) { - logger.error(`Error executing tool ${toolUse.toolName}: ${error.message}`); - const { messageId, toolResponse } = this.finalizeToolUse( - interaction, - toolUse, - error.message, - true, - //projectEditor, - ); - return { - messageId, - toolResponse: `Error with ${toolUse.toolName}: ${toolResponse}`, - bbaiResponse: 'BBai could not run the tool', - isError: true, - }; - } - } - - finalizeToolUse( - interaction: LLMConversationInteraction, - toolUse: LLMAnswerToolUse, - toolRunResultContent: LLMToolRunResultContent, - isError: boolean, - //projectEditor: ProjectEditor, - ): LLMToolFinalizeResult { - //logger.debug(`finalizeToolUse - calling addMessageForToolResult for ${toolUse.toolName}`); - const messageId = interaction.addMessageForToolResult(toolUse.toolUseId, toolRunResultContent, isError) || - ''; - const toolResponse = isError - ? `Tool ${toolUse.toolName} failed to run:\n${this.getContentFromToolResult(toolRunResultContent)}` - : `Tool ${toolUse.toolName} executed successfully:\n${this.getContentFromToolResult(toolRunResultContent)}`; - - return { messageId, toolResponse }; - } - - private getContentFromToolResult(toolRunResultContent: LLMToolRunResultContent): string { - if (Array.isArray(toolRunResultContent)) { - return toolRunResultContent.map((part) => this.getTextContent(part)).join('\n'); - } else if (typeof toolRunResultContent !== 'string') { - return this.getTextContent(toolRunResultContent); - } else { - return toolRunResultContent; - } - } - - private getTextContent(content: LLMMessageContentPart): string { - if ('text' in content) { - return content.text; - } else if ('image' in content) { - return '[Image content]'; - } else if ('tool_use_id' in content) { - return `[Tool result: ${content.tool_use_id}]`; - } - return '[Unknown content]'; - } + private async loadTools() { + const toolsDir = new URL('./tools', import.meta.url); + for await (const entry of Deno.readDir(toolsDir)) { + if (entry.isFile && entry.name.endsWith('Tool.ts')) { + try { + const module = await import(`./tools/${entry.name}`); + const ToolClass = Object.values(module)[0] as typeof LLMTool; + const tool = new ToolClass(); + this.registerTool(tool); + + // Check if there's a formatter for this tool + if ('formatter' in ToolClass) { + this.registerToolFormatter(tool.name, (ToolClass as any).formatter); + } + } catch (error) { + logger.error(`Error loading tool ${entry.name}:`, error); + } + } + } + } + + private registerTool(tool: LLMTool): void { + this.tools.set(tool.name, tool); + } + + private registerToolFormatter(toolName: string, formatter: ToolFormatter): void { + this.toolFormatters.set(toolName, formatter); + } + + getTool(name: string): LLMTool | undefined { + return this.tools.get(name); + } + + getToolFormatter(toolName: string): ToolFormatter | undefined { + return this.toolFormatters.get(toolName); + } + + getAllTools(): LLMTool[] { + return Array.from(this.tools.values()); + } + + async handleToolUse( + interaction: LLMConversationInteraction, + toolUse: LLMAnswerToolUse, + projectEditor: ProjectEditor, + ): Promise<{ messageId: string; toolResponse: string; bbaiResponse: string; isError: boolean }> { + try { + const tool = this.getTool(toolUse.toolName); + if (!tool) { + logger.warn(`Unknown tool used: ${toolUse.toolName}`); + throw new Error(`Unknown tool used: ${toolUse.toolName}`); + } + + if (!toolUse.toolValidation.validated && !tool.validateInput(toolUse.toolInput)) { + throw createError(ErrorType.LLMValidation, `Invalid input for ${toolUse.toolName} tool`, { + name: `tool_use-${toolUse.toolName}`, + validation_type: 'input_schema', + validation_error: 'Input does not match the required schema', + } as LLMValidationErrorOptions); + } else { + logger.info( + `handleToolUse - Tool ${toolUse.toolName} is already validated with results: ${toolUse.toolValidation.results}`, + ); + } + + const { messageId, toolResponse, bbaiResponse } = await tool.runTool(interaction, toolUse, projectEditor); + return { messageId, toolResponse, bbaiResponse, isError: false }; + } catch (error) { + logger.error(`Error executing tool ${toolUse.toolName}:`, error); + const { messageId, toolResponse } = this.finalizeToolUse( + interaction, + toolUse, + error.message, + true, + ); + return { + messageId, + toolResponse: `Error with ${toolUse.toolName}: ${toolResponse}`, + bbaiResponse: 'BBai could not run the tool', + isError: true, + }; + } + } + + finalizeToolUse( + interaction: LLMConversationInteraction, + toolUse: LLMAnswerToolUse, + toolRunResultContent: LLMToolRunResultContent, + isError: boolean, + ): LLMToolFinalizeResult { + const messageId = interaction.addMessageForToolResult(toolUse.toolUseId, toolRunResultContent, isError) || ''; + const toolResponse = isError + ? `Tool ${toolUse.toolName} failed to run:\n${this.getContentFromToolResult(toolRunResultContent)}` + : `Tool ${toolUse.toolName} executed successfully:\n${this.getContentFromToolResult(toolRunResultContent)}`; + + return { messageId, toolResponse }; + } + + private getContentFromToolResult(toolRunResultContent: LLMToolRunResultContent): string { + if (Array.isArray(toolRunResultContent)) { + return toolRunResultContent.map((part) => this.getTextContent(part)).join('\n'); + } else if (typeof toolRunResultContent !== 'string') { + return this.getTextContent(toolRunResultContent); + } else { + return toolRunResultContent; + } + } + + private getTextContent(content: LLMMessageContentPart): string { + if ('text' in content) { + return content.text; + } else if ('image' in content) { + return '[Image content]'; + } else if ('tool_use_id' in content) { + return `[Tool result: ${content.tool_use_id}]`; + } + return '[Unknown content]'; + } } -export default LLMToolManager; +export default LLMToolManager; \ No newline at end of file From 1b8b3703a7edeb7158066d78d61175fc52f28bfe Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Thu, 15 Aug 2024 23:37:14 +1000 Subject: [PATCH 18/31] Implemented RequestFilesToolFormatter and LLMToolRequestFiles to handle file requests in LLM conversations Applied patches from BBai to 1 file Files modified: - api/src/llms/tools/requestFilesTool.ts --- api/src/llms/tools/requestFilesTool.ts | 142 ++++++------------------- 1 file changed, 35 insertions(+), 107 deletions(-) diff --git a/api/src/llms/tools/requestFilesTool.ts b/api/src/llms/tools/requestFilesTool.ts index 0ccac97..44abeec 100644 --- a/api/src/llms/tools/requestFilesTool.ts +++ b/api/src/llms/tools/requestFilesTool.ts @@ -1,9 +1,7 @@ -import LLMTool, { LLMToolInputSchema, LLMToolRunResult } from '../llmTool.ts'; +import LLMTool, { ToolFormatter } from '../llmTool.ts'; +import { LLMAnswerToolUse, LLMMessageContentPart, LLMMessageContentParts } from '../llmMessage.ts'; import LLMConversationInteraction from '../interactions/conversationInteraction.ts'; -import { logger } from 'shared/logger.ts'; -import { LLMAnswerToolUse, LLMMessageContentPartTextBlock } from 'api/llms/llmMessage.ts'; import ProjectEditor from '../../editor/projectEditor.ts'; -import { createError, ErrorType } from '../../utils/error.utils.ts'; class RequestFilesToolFormatter implements ToolFormatter { formatToolUse(toolName: string, input: object): string { @@ -23,108 +21,38 @@ class RequestFilesToolFormatter implements ToolFormatter { } export class LLMToolRequestFiles extends LLMTool { - private static formatter = new RequestFilesToolFormatter(); - - private registerFormatter() { - LLMToolManager.registerToolFormatter(this.name, LLMToolRequestFiles.formatter); + static formatter = new RequestFilesToolFormatter(); + + name = 'request_files'; + description = 'Request files to be added to the chat'; + parameters = { + fileNames: { + type: 'array', + items: { type: 'string' }, + description: 'Array of file names to be added to the chat', + }, + }; + + validateInput(input: unknown): boolean { + if (typeof input !== 'object' || input === null) { + return false; + } + const { fileNames } = input as { fileNames: unknown }; + return Array.isArray(fileNames) && fileNames.every((fileName) => typeof fileName === 'string'); } - constructor() { - super( - 'request_files', - 'Request files to be added to the chat', - ); - } - - get input_schema(): LLMToolInputSchema { - return { - type: 'object', - properties: { - fileNames: { - type: 'array', - items: { type: 'string' }, - description: 'Array of file names to be added to the chat', - }, - }, - required: ['fileNames'], - description: - `Request files for the chat when you need to review them or make changes. Before requesting a file, check that you don't already have it included in an earlier message`, - }; - } - - async runTool( - interaction: LLMConversationInteraction, - toolUse: LLMAnswerToolUse, - projectEditor: ProjectEditor, - ): Promise { - const { toolUseId: _toolUseId, toolInput } = toolUse; - const { fileNames } = toolInput as { fileNames: string[] }; - - try { - const filesAdded = await projectEditor.prepareFilesForConversation(fileNames); - - const contentParts = []; - const fileNamesSuccess: string[] = []; - const fileNamesError: string[] = []; - let allFilesFailed = true; - for (const fileToAdd of filesAdded) { - if (fileToAdd.metadata.error) { - contentParts.push({ - 'type': 'text', - 'text': `Error adding file ${fileToAdd.fileName}: ${fileToAdd.metadata.error}`, - } as LLMMessageContentPartTextBlock); - fileNamesError.push(fileToAdd.fileName); - } else { - contentParts.push({ - 'type': 'text', - 'text': `File added: ${fileToAdd.fileName}`, - } as LLMMessageContentPartTextBlock); - fileNamesSuccess.push(fileToAdd.fileName); - allFilesFailed = false; - } - } - - // [TODO] we're creating a bit of a circle by calling back into the toolManager in the projectEditor - // Since we're not holding onto a copy of toolManager, it should be fine - dangerous territory though - const { messageId, toolResponse } = projectEditor.orchestratorController.toolManager.finalizeToolUse( - interaction, - toolUse, - contentParts, - allFilesFailed, - //projectEditor, - ); - - projectEditor.orchestratorController.primaryInteraction.addFilesForMessage( - filesAdded, - messageId, - toolUse.toolUseId, - ); - const bbaiResponses = []; - if (fileNamesSuccess.length > 0) { - bbaiResponses.push(`BBai has added these files to the conversation: ${fileNamesSuccess.join(', ')}`); - } - if (fileNamesError.length > 0) { - bbaiResponses.push(`BBai failed to add these files to the conversation: ${fileNamesError.join(', ')}`); - } - - const bbaiResponse = bbaiResponses.join('\n\n'); - - // const storageLocation = this.determineStorageLocation(fullFilePath, content, source); - // if (storageLocation === 'system') { - // this.conversation.addFileForSystemPrompt(fileName, metadata, messageId, toolUse.toolUseId); - // } else { - // this.conversation.addFileForMessage(fileName, metadata, messageId, toolUse.toolUseId); - // } - - return { messageId, toolResponse, bbaiResponse }; - } catch (error) { - logger.error(`Error adding files to conversation: ${error.message}`); - - throw createError(ErrorType.FileHandling, `Error adding files to conversation: ${error.message}`, { - name: 'request-files', - filePath: projectEditor.projectRoot, - operation: 'request-files', - }); - } - } -} + async runTool( + interaction: LLMConversationInteraction, + toolUse: LLMAnswerToolUse, + projectEditor: ProjectEditor, + ): Promise<{ messageId: string; toolResponse: string; bbaiResponse: string }> { + const { fileNames } = toolUse.toolInput as { fileNames: string[] }; + const result = await projectEditor.addFilesToConversation(fileNames); + const messageId = interaction.addMessageForToolResult(toolUse.toolUseId, result) || ''; + return { + messageId, + toolResponse: result, + bbaiResponse: `Files added to the conversation: ${fileNames.join(', ')}`, + }; + } +} \ No newline at end of file From 46291952ac57706a2a467f69cd707e73a8f7e94c Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Fri, 16 Aug 2024 00:17:26 +1000 Subject: [PATCH 19/31] Added last commit information to file metadata in `prepareFilesForConversation` function. Applied patches from BBai to 1 file Files modified: - api/src/editor/projectEditor.ts --- api/src/editor/projectEditor.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api/src/editor/projectEditor.ts b/api/src/editor/projectEditor.ts index ac170f4..d376cb3 100644 --- a/api/src/editor/projectEditor.ts +++ b/api/src/editor/projectEditor.ts @@ -1,6 +1,7 @@ import { join } from '@std/path'; import { FILE_LISTING_TIERS, generateFileListing, isPathWithinProject } from '../utils/fileHandling.utils.ts'; +import { getLastCommitForFile } from '../utils/git.utils.ts'; import LLMConversationInteraction, { FileMetadata, ProjectInfo } from '../llms/interactions/conversationInteraction.ts'; import OrchestratorController from '../controllers/orchestratorController.ts'; import { logger } from 'shared/logger.ts'; @@ -155,10 +156,12 @@ class ProjectEditor { const fullFilePath = join(this.projectRoot, fileName); const content = await Deno.readTextFile(fullFilePath); + const lastCommit = await getLastCommitForFile(this.projectRoot, fileName); const metadata: Omit = { size: new TextEncoder().encode(content).length, lastModified: new Date(), error: null, + lastCommit: lastCommit, }; filesAdded.push({ fileName, metadata }); @@ -174,6 +177,7 @@ class ProjectEditor { size: 0, lastModified: new Date(), error: errorMessage, + lastCommit: null, }, }); } From 4791ba1715df730a927165bb5f05f5466ba8074e Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Fri, 16 Aug 2024 00:22:03 +1000 Subject: [PATCH 20/31] Added method to get last commit hash for a file Applied patches from BBai to 1 file Files modified: - src/shared/utils/git.utils.ts --- src/shared/utils/git.utils.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/shared/utils/git.utils.ts b/src/shared/utils/git.utils.ts index a3171c4..925cc44 100644 --- a/src/shared/utils/git.utils.ts +++ b/src/shared/utils/git.utils.ts @@ -67,4 +67,18 @@ export class GitUtils { throw new Error(`Failed to get current commit: ${error.message}`); } } + + static async getLastCommitForFile(repoPath: string, filePath: string): Promise { + const git: SimpleGit = this.getGit(repoPath); + + try { + const result = await git.log({ file: filePath, maxCount: 1 }); + if (result.latest) { + return result.latest.hash; + } + return null; + } catch (error) { + throw new Error(`Failed to get last commit for file ${filePath}: ${error.message}`); + } + } } From 7236ce26e34a26df8ff165d900cc786767c3496b Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Fri, 16 Aug 2024 00:22:10 +1000 Subject: [PATCH 21/31] Refactored git utility imports and usage in projectEditor.ts Applied patches from BBai to 1 file Files modified: - api/src/editor/projectEditor.ts --- api/src/editor/projectEditor.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/src/editor/projectEditor.ts b/api/src/editor/projectEditor.ts index d376cb3..0e76c92 100644 --- a/api/src/editor/projectEditor.ts +++ b/api/src/editor/projectEditor.ts @@ -1,7 +1,7 @@ import { join } from '@std/path'; import { FILE_LISTING_TIERS, generateFileListing, isPathWithinProject } from '../utils/fileHandling.utils.ts'; -import { getLastCommitForFile } from '../utils/git.utils.ts'; +//import { GitUtils } from 'shared/utils/git.utils.ts'; import LLMConversationInteraction, { FileMetadata, ProjectInfo } from '../llms/interactions/conversationInteraction.ts'; import OrchestratorController from '../controllers/orchestratorController.ts'; import { logger } from 'shared/logger.ts'; @@ -156,12 +156,12 @@ class ProjectEditor { const fullFilePath = join(this.projectRoot, fileName); const content = await Deno.readTextFile(fullFilePath); - const lastCommit = await getLastCommitForFile(this.projectRoot, fileName); + //const lastCommit = await GitUtils.getLastCommitForFile(this.projectRoot, fileName); const metadata: Omit = { size: new TextEncoder().encode(content).length, lastModified: new Date(), error: null, - lastCommit: lastCommit, + //lastCommit: lastCommit, }; filesAdded.push({ fileName, metadata }); @@ -177,7 +177,7 @@ class ProjectEditor { size: 0, lastModified: new Date(), error: errorMessage, - lastCommit: null, + //lastCommit: null, }, }); } From 6dbd6d22b8cc5fed2222e165185ce0bdcd15742a Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Fri, 16 Aug 2024 00:28:23 +1000 Subject: [PATCH 22/31] Updated API documentation with new endpoints and details Applied patches from BBai to 1 file Files modified: - API.md --- API.md | 109 +++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 74 insertions(+), 35 deletions(-) diff --git a/API.md b/API.md index 51fd39e..ed04bea 100644 --- a/API.md +++ b/API.md @@ -1,6 +1,6 @@ # BBai API Documentation -This document provides details about the endpoints available in the bbai API. +This document provides details about the endpoints available in the BBai API. ## Base URL @@ -20,60 +20,99 @@ All endpoints are relative to: `http://localhost:/api/v1` ``` ### Conversation Management -- **POST** `/conversation` - - Start a new conversation. - - Request Body: - ```json - { - "prompt": "string", - "provider": "string" (optional), - "model": "string" (optional), - "startDir": "string" - } - ``` - - Response: LLM-generated response +#### List Conversations +- **GET** `/conversation` + - Retrieve a list of conversations with pagination and filtering options. + - Query Parameters: + - `page` (integer, default: 1): Page number for pagination + - `pageSize` (integer, default: 10): Number of items per page + - `startDate` (string, format: date): Filter conversations starting from this date + - `endDate` (string, format: date): Filter conversations up to this date + - `llmProviderName` (string): Filter conversations by LLM provider name + - `startDir` (string, required): The starting directory for the project + - Response: List of conversations with pagination details + +#### Get Conversation - **GET** `/conversation/:id` - - Get details of a specific conversation. - - Response: Conversation details (to be implemented) + - Retrieve details of a specific conversation. + - Query Parameters: + - `startDir` (string, required): The starting directory for the project + - Response: Conversation details including messages, LLM provider, and token usage +#### Continue Conversation - **POST** `/conversation/:id` - Continue an existing conversation. - Request Body: ```json { - "prompt": "string", + "statement": "string", "startDir": "string" } ``` - - Response: LLM-generated response + - Response: LLM-generated response with conversation details +#### Delete Conversation - **DELETE** `/conversation/:id` - - Delete a conversation. + - Delete a specific conversation. + - Query Parameters: + - `startDir` (string, required): The starting directory for the project - Response: Deletion confirmation message +#### Clear Conversation - **POST** `/conversation/:id/clear` - - Clear the history of a conversation. + - Clear the history of a specific conversation. + - Query Parameters: + - `startDir` (string, required): The starting directory for the project - Response: Confirmation message -- **POST** `/conversation/:id/undo` - - Undo the last change in a conversation. - - Response: Confirmation message +### WebSocket Connection +- **GET** `/ws/conversation/:id` + - Establish a WebSocket connection for real-time conversation updates. + - The client can send messages with the following format: + ```json + { + "task": "greeting" | "converse" | "cancel", + "statement": "string", + "startDir": "string" + } + ``` + - The server will emit events for conversation updates, including: + - `conversationReady` + - `conversationEntry` + - `conversationAnswer` + - `conversationError` + - `conversationCancelled` + +## Note on Unimplemented Features + +The following features are mentioned in the codebase but are not fully implemented or exposed through the API: + +- Adding files to a conversation +- Removing files from a conversation +- Listing files in a conversation +- Retrieving token usage +- Running CLI commands +- Loading external content +- Retrieving conversation logs +- Undoing the last change in a conversation + +These features may be implemented in future versions of the API. + +## Error Handling + +All endpoints may return appropriate HTTP status codes for various error conditions. Common error responses include: + +- 400 Bad Request: For invalid input or missing required parameters +- 404 Not Found: When a requested resource (e.g., conversation) is not found +- 500 Internal Server Error: For unexpected server-side errors -### File Management -- **POST** `/conversation/:id/file` - - Add a file to the conversation. - - Request Body: FormData with 'file' field - - Response: File addition confirmation +Detailed error messages will be provided in the response body when applicable. -- **DELETE** `/conversation/:id/file/:fileId` - - Remove a file from the conversation. - - Response: File removal confirmation +## Authentication -- **GET** `/conversation/:id/files` - - List files in the conversation. - - Response: Array of file names +The current implementation does not include authentication. It is designed for local use only. Ensure proper security measures are in place when deploying this API in a production environment. -Note: Some endpoints like Token Usage, CLI Command, External Content, Logs, and Persistence are not currently implemented in the provided code and have been removed from this documentation. +## Versioning -Detailed request/response schemas and examples for each endpoint will be added in future updates. +This documentation is for API version 1 (`v1`). Future versions may introduce changes to the endpoint structure or functionality. \ No newline at end of file From 9fe8651e9c89e83a7bfa1938a2927e510e2f4300 Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Fri, 16 Aug 2024 00:38:07 +1000 Subject: [PATCH 23/31] Improved logging in conversationLogger.utils.ts Applied patches from BBai to 1 file Files modified: - src/shared/utils/conversationLogger.utils.ts --- src/shared/utils/conversationLogger.utils.ts | 70 +++++++++++--------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/src/shared/utils/conversationLogger.utils.ts b/src/shared/utils/conversationLogger.utils.ts index b8db9fb..21a0529 100644 --- a/src/shared/utils/conversationLogger.utils.ts +++ b/src/shared/utils/conversationLogger.utils.ts @@ -4,7 +4,7 @@ import { ensureDir } from '@std/fs'; import { ConversationId, ConversationMetrics, ConversationTokenUsage, TokenUsage } from 'shared/types.ts'; import { getBbaiDataDir } from 'shared/dataDir.ts'; import { LogFormatter } from 'shared/logFormatter.ts'; -//import { logger } from 'shared/logger.ts'; +import { logger } from 'shared/logger.ts'; import { LLMMessageContentPart, LLMMessageContentPartImageBlock, @@ -121,32 +121,37 @@ export class ConversationLogger { async logToolUse( toolName: string, - input: object, + toolInput: object, conversationStats?: ConversationMetrics, tokenUsageTurn?: TokenUsage, tokenUsageStatement?: TokenUsage, tokenUsageConversation?: ConversationTokenUsage, ) { + console.log(`Entering logToolUse for: ${toolName}`); let message: string; try { - const formatter = LLMToolManager.getToolFormatter(toolName); - if (formatter) { - message = formatter.formatToolUse(toolName, input); - } else { - message = `Tool: ${toolName}\nInput: \n${JSON.stringify(input, null, 2)}`; - } + console.log('Attempting to format tool input...'); + message = `Tool: ${toolName}\nInput: \n${JSON.stringify(toolInput, null, 2)}`; + console.log('Formatted message:', message); } catch (error) { console.error(`Error formatting tool use for ${toolName}:`, error); message = `Tool: ${toolName}\nInput: [Error formatting input]`; } - await this.logEntry( - 'tool_use', - message, - conversationStats, - tokenUsageTurn, - tokenUsageStatement, - tokenUsageConversation, - ); + console.log('Calling logEntry...'); + try { + await this.logEntry( + 'tool_use', + message, + conversationStats, + tokenUsageTurn, + tokenUsageStatement, + tokenUsageConversation, + ); + console.log('logEntry completed successfully'); + } catch (error) { + console.error('Error in logEntry:', error); + } + console.log(`Exiting logToolUse for: ${toolName}`); } async logToolResult( @@ -159,19 +164,18 @@ export class ConversationLogger { ) { let message: string; try { - const formatter = LLMToolManager.getToolFormatter(toolName); - if (formatter) { - message = formatter.formatToolResult(toolName, result); - } else { - message = `Tool: ${toolName}\nResult: ${this.formatDefaultToolResult(result)}`; - } + //const formatter = LLMToolManager.getToolFormatter(toolName); + //if (formatter) { + // message = formatter.formatToolResult(toolName, result); + //} else { + message = `Tool: ${toolName}\nResult: ${this.formatDefaultToolResult(result)}`; + //} } catch (error) { console.error(`Error formatting tool result for ${toolName}:`, error); message = `Tool: ${toolName}\nResult: [Error formatting result]`; } await this.logEntry('tool_result', message); } - } async logError(error: string) { await this.logEntry('error', error); @@ -184,15 +188,17 @@ export class ConversationLogger { private formatDefaultToolResult(result: string | LLMMessageContentPart | LLMMessageContentParts): string { try { - if (Array.isArray(result)) { - return 'text' in result[0] - ? (result[0] as LLMMessageContentPartTextBlock).text - : JSON.stringify(result[0], null, 2); - } else if (typeof result !== 'string') { - return 'text' in result ? (result as LLMMessageContentPartTextBlock).text : JSON.stringify(result, null, 2); - } else { - return result; - } + if (Array.isArray(result)) { + return 'text' in result[0] + ? (result[0] as LLMMessageContentPartTextBlock).text + : JSON.stringify(result[0], null, 2); + } else if (typeof result !== 'string') { + return 'text' in result + ? (result as LLMMessageContentPartTextBlock).text + : JSON.stringify(result, null, 2); + } else { + return result; + } } catch (error) { console.error('Error in formatDefaultToolResult:', error); return '[Error formatting result]'; From 2d8fbf75358242e8d3f548bfa29fe3e81071a211 Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Fri, 16 Aug 2024 00:40:20 +1000 Subject: [PATCH 24/31] Added test file to trigger tool use logging. Applied patches from BBai to 1 file Files modified: - test_tool_use.txt --- test_tool_use.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 test_tool_use.txt diff --git a/test_tool_use.txt b/test_tool_use.txt new file mode 100644 index 0000000..e96a042 --- /dev/null +++ b/test_tool_use.txt @@ -0,0 +1 @@ +This is a test file to trigger tool use logging. \ No newline at end of file From 886c46f62a23cfde50f61cc46806ac32eaee67b1 Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Fri, 16 Aug 2024 00:45:00 +1000 Subject: [PATCH 25/31] Added logging and error handling to the `logEntry` method in `conversationLogger.utils.ts`. Applied patches from BBai to 1 file Files modified: - src/shared/utils/conversationLogger.utils.ts --- src/shared/utils/conversationLogger.utils.ts | 38 ++++++++++++++------ 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/shared/utils/conversationLogger.utils.ts b/src/shared/utils/conversationLogger.utils.ts index 21a0529..5aa5787 100644 --- a/src/shared/utils/conversationLogger.utils.ts +++ b/src/shared/utils/conversationLogger.utils.ts @@ -67,8 +67,26 @@ export class ConversationLogger { totalTokensTotal: 0, }, ) { + console.log(`Entering logEntry method for type: ${type}`); const timestamp = this.getTimestamp(); + console.log('Calling logEntryHandler...'); + try { + await this.logEntryHandler( + type, + timestamp, + message, + conversationStats, + tokenUsageTurn, + tokenUsageStatement, + tokenUsageConversation, + ); + console.log('logEntryHandler called successfully'); + } catch (error) { + console.error('Error in logEntryHandler:', error); + } + + console.log('Creating raw entry...'); const entry = LogFormatter.createRawEntryWithSeparator( type, timestamp, @@ -78,17 +96,17 @@ export class ConversationLogger { tokenUsageStatement, tokenUsageConversation, ); - await this.appendToLog(entry); + console.log('Raw entry created'); - await this.logEntryHandler( - type, - timestamp, - message, - conversationStats, - tokenUsageTurn, - tokenUsageStatement, - tokenUsageConversation, - ); + console.log('Appending to log...'); + try { + await this.appendToLog(entry); + console.log('Successfully appended to log'); + } catch (error) { + console.error('Error appending to log:', error); + } + + console.log(`Exiting logEntry method for type: ${type}`); } async logUserMessage(message: string, conversationStats?: ConversationMetrics) { From 814d834965f48e22fc56e346c364b9eb595db73a Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Fri, 16 Aug 2024 00:45:10 +1000 Subject: [PATCH 26/31] Updated test file to trigger tool use logging Applied patches from BBai to 1 file Files modified: - test_tool_use.txt --- test_tool_use.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_tool_use.txt b/test_tool_use.txt index e96a042..02266e4 100644 --- a/test_tool_use.txt +++ b/test_tool_use.txt @@ -1 +1 @@ -This is a test file to trigger tool use logging. \ No newline at end of file +This is an updated test file to trigger tool use logging again. \ No newline at end of file From 33a5a2b9c9be1a6dee0173d309fd0ecc5156255e Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Fri, 16 Aug 2024 00:56:54 +1000 Subject: [PATCH 27/31] Refactored conversation start display to use a more compact format for narrow consoles Applied patches from BBai to 1 file Files modified: - cli/src/utils/terminalHandler.utils.ts --- cli/src/utils/terminalHandler.utils.ts | 59 +++++++++----------------- 1 file changed, 20 insertions(+), 39 deletions(-) diff --git a/cli/src/utils/terminalHandler.utils.ts b/cli/src/utils/terminalHandler.utils.ts index 33ea52c..5fb97b8 100644 --- a/cli/src/utils/terminalHandler.utils.ts +++ b/cli/src/utils/terminalHandler.utils.ts @@ -74,9 +74,9 @@ export class TerminalHandler { // preserveAspectRatio: true, //}) + ' ' + ansi.cursorTo(6, 2) + - colors.bold.blue.underline('BBai') + colors.bold.blue(' - Be Better with code and docs') + + colors.bold.blue.underline('BBai') + colors.bold.blue(' - Be Better with code and docs') //colors.bold.blue(ansi.link('BBai', 'https://bbai.tips')) + - '\n', + //+ '\n', ); } @@ -203,48 +203,29 @@ export class TerminalHandler { } const { conversationTitle } = data; - const statementCount = data.conversationStats?.statementCount || 1; // Ensure statementCount is defined - - const shortTitle = conversationTitle ? conversationTitle : ''; + const statementCount = data.conversationStats?.statementCount || 1; + const shortTitle = conversationTitle ? conversationTitle.substring(0, 30) : ''; const { columns } = Deno.consoleSize(); - const maxWidth = Math.min(columns - 2, 120); // Max width of 120 or console width - 2 - - // Calculate the required width based on the content - const contentWidth = Math.max( - unicodeWidth(stripAnsiCode(` ${symbols.sparkles} Conversation Started ${symbols.sparkles}`)), - unicodeWidth(stripAnsiCode(` ID: ${conversationId}`)), - unicodeWidth(stripAnsiCode(` Title: ${shortTitle}`)), - unicodeWidth(stripAnsiCode(` Statement: ${statementCount}`)), - ); + const isNarrow = columns < 80; - const borderWidth = Math.min(contentWidth + 4, maxWidth); // Add 4 for left and right padding - const horizontalBorder = '─'.repeat(borderWidth - 3); - - const padContent = (content: string) => { - const paddingWidth = borderWidth - unicodeWidth(stripAnsiCode(content)) - 3; // -3 for '│ ' and '│' - return content + ' '.repeat(Math.max(0, paddingWidth)); + const formatLine = (label: string, value: string, color: (s: string) => string) => { + const separator = isNarrow ? ':' : ' | '; + return color(`${label}${separator}${value}`); }; - console.log(palette.secondary(`╭${horizontalBorder}╮`)); - console.log( - palette.secondary('│') + - palette.primary(padContent(` ${symbols.sparkles} Conversation Started ${symbols.sparkles}`)) + - palette.secondary('│'), - ); - console.log(palette.secondary(`├${horizontalBorder}┤`)); - console.log( - palette.secondary('│') + palette.accent(padContent(` ID: ${conversationId}`)) + palette.secondary('│'), - ); - console.log( - palette.secondary('│') + palette.info(padContent(` Title: ${shortTitle}`)) + palette.secondary('│'), - ); - console.log( - palette.secondary('│') + - palette.success(padContent(` Statement: ${statementCount}`)) + - palette.secondary('│'), - ); - console.log(palette.secondary(`╰${horizontalBorder}╯`)); + const lines = [ + formatLine('ID', conversationId.substring(0, 8), palette.accent), + formatLine('Title', shortTitle, palette.info), + formatLine('Statement', statementCount.toString(), palette.success), + ]; + + const output = isNarrow + ? lines.join('\n') + : lines.join(' '); + + console.log(palette.primary(`${symbols.sparkles} Conversation Started ${symbols.sparkles}`)); + console.log(output); console.log(''); if (expectingMoreInput && this.spinner) { From 8f60ba1169abbdd0f2e7aa32059a883d60ba014e Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Fri, 16 Aug 2024 00:59:55 +1000 Subject: [PATCH 28/31] Added left padding to conversation start display for better readability Applied patches from BBai to 1 file Files modified: - cli/src/utils/terminalHandler.utils.ts --- cli/src/utils/terminalHandler.utils.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cli/src/utils/terminalHandler.utils.ts b/cli/src/utils/terminalHandler.utils.ts index 5fb97b8..c1a2c5a 100644 --- a/cli/src/utils/terminalHandler.utils.ts +++ b/cli/src/utils/terminalHandler.utils.ts @@ -208,10 +208,10 @@ export class TerminalHandler { const { columns } = Deno.consoleSize(); const isNarrow = columns < 80; + const leftPadding = ' '; const formatLine = (label: string, value: string, color: (s: string) => string) => { - const separator = isNarrow ? ':' : ' | '; - return color(`${label}${separator}${value}`); + return color(`${leftPadding}${label}: ${value}`); }; const lines = [ @@ -222,9 +222,9 @@ export class TerminalHandler { const output = isNarrow ? lines.join('\n') - : lines.join(' '); + : lines.join(' '); - console.log(palette.primary(`${symbols.sparkles} Conversation Started ${symbols.sparkles}`)); + console.log(palette.primary(`${leftPadding}${symbols.sparkles} Conversation Started ${symbols.sparkles}`)); console.log(output); console.log(''); From df5bd2070f1242dea6be6fcf5d7b71bae5e5dbf8 Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Fri, 16 Aug 2024 01:05:40 +1000 Subject: [PATCH 29/31] Wrote test message to confirm tool use is working as expected Applied patches from BBai to 1 file Files modified: - test_tool_use.txt --- test_tool_use.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test_tool_use.txt b/test_tool_use.txt index 02266e4..0caf4da 100644 --- a/test_tool_use.txt +++ b/test_tool_use.txt @@ -1 +1,5 @@ -This is an updated test file to trigger tool use logging again. \ No newline at end of file +This is a test message to confirm that the tool use is working correctly. + +File written at: ${new Date().toISOString()} + +If you can read this, it means the rewrite_file tool is functioning as expected! \ No newline at end of file From 442dd8e30ec628288a5af177b63d8530b1b805c9 Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Fri, 16 Aug 2024 01:14:53 +1000 Subject: [PATCH 30/31] hotfix for console logging; started custom log formatters for tools --- api/src/controllers/orchestratorController.ts | 17 +- api/src/editor/projectEditor.ts | 8 +- api/src/llms/interactions/baseInteraction.ts | 3 +- .../interactions/conversationInteraction.ts | 9 +- api/src/llms/llmTool.ts | 45 ++- api/src/llms/llmToolManager.ts | 299 ++++++++++-------- api/src/llms/providers/anthropicLLM.ts | 5 +- api/src/llms/tools/requestFilesTool.ts | 185 ++++++++--- api/src/routes/api/websocket.handlers.ts | 9 +- .../t/llms/tools/rewriteFileTool.test.ts | 2 +- cli/src/utils/terminalHandler.utils.ts | 10 +- src/shared/utils/conversationLogger.utils.ts | 15 - src/shared/utils/logFormatter.utils.ts | 6 +- 13 files changed, 365 insertions(+), 248 deletions(-) diff --git a/api/src/controllers/orchestratorController.ts b/api/src/controllers/orchestratorController.ts index be97be7..2f5f4cf 100644 --- a/api/src/controllers/orchestratorController.ts +++ b/api/src/controllers/orchestratorController.ts @@ -268,7 +268,7 @@ class OrchestratorController { // Include the latest stats and usage in the saved conversation //interaction.conversationStats = this.interactionStats.get(interaction.id), - //interaction.tokenUsageInteraction = this.interactionTokenUsage.get(interaction.id), + //interaction.tokenUsageConversation = this.interactionTokenUsage.get(interaction.id), await persistence.saveConversation(interaction); @@ -423,13 +423,14 @@ class OrchestratorController { toolUse: LLMAnswerToolUse, _response: unknown, ): Promise { - interaction.conversationLogger?.logToolUse( + logger.error(`Handling tool use for: ${toolUse.toolName}`); + await interaction.conversationLogger.logToolUse( toolUse.toolName, toolUse.toolInput, - interaction.conversationStats, - interaction.tokenUsageTurn, - interaction.tokenUsageStatement, - interaction.tokenUsageInteraction, + //interaction.conversationStats, + //interaction.tokenUsageTurn, + //interaction.tokenUsageStatement, + //interaction.tokenUsageConversation, ); const { messageId: _messageId, toolResponse, bbaiResponse, isError } = await this.toolManager.handleToolUse( interaction, @@ -437,9 +438,9 @@ class OrchestratorController { this.projectEditor, ); if (isError) { - interaction.conversationLogger?.logError(`Tool Result (${toolUse.toolName}): ${toolResponse}`); + interaction.conversationLogger.logError(`Tool Result (${toolUse.toolName}): ${toolResponse}`); } - interaction.conversationLogger?.logToolResult( + await interaction.conversationLogger.logToolResult( toolUse.toolName, `BBai was ${isError ? 'unsuccessful' : 'successful'} with tool run: \n${bbaiResponse}`, //interaction.conversationStats, diff --git a/api/src/editor/projectEditor.ts b/api/src/editor/projectEditor.ts index 0e76c92..d94040a 100644 --- a/api/src/editor/projectEditor.ts +++ b/api/src/editor/projectEditor.ts @@ -1,7 +1,7 @@ import { join } from '@std/path'; import { FILE_LISTING_TIERS, generateFileListing, isPathWithinProject } from '../utils/fileHandling.utils.ts'; -//import { GitUtils } from 'shared/utils/git.utils.ts'; +import { GitUtils } from 'shared/git.ts'; import LLMConversationInteraction, { FileMetadata, ProjectInfo } from '../llms/interactions/conversationInteraction.ts'; import OrchestratorController from '../controllers/orchestratorController.ts'; import { logger } from 'shared/logger.ts'; @@ -156,12 +156,12 @@ class ProjectEditor { const fullFilePath = join(this.projectRoot, fileName); const content = await Deno.readTextFile(fullFilePath); - //const lastCommit = await GitUtils.getLastCommitForFile(this.projectRoot, fileName); + const lastCommit = await GitUtils.getLastCommitForFile(this.projectRoot, fileName) || ''; const metadata: Omit = { size: new TextEncoder().encode(content).length, lastModified: new Date(), error: null, - //lastCommit: lastCommit, + lastCommit: lastCommit, }; filesAdded.push({ fileName, metadata }); @@ -177,7 +177,7 @@ class ProjectEditor { size: 0, lastModified: new Date(), error: errorMessage, - //lastCommit: null, + lastCommit: '', }, }); } diff --git a/api/src/llms/interactions/baseInteraction.ts b/api/src/llms/interactions/baseInteraction.ts index e8a0201..cb0275a 100644 --- a/api/src/llms/interactions/baseInteraction.ts +++ b/api/src/llms/interactions/baseInteraction.ts @@ -150,7 +150,6 @@ class LLMInteraction { this._tokenUsageInteraction = tokenUsage; } - public get inputTokensTotal(): number { return this._tokenUsageInteraction.inputTokensTotal; } @@ -273,7 +272,7 @@ class LLMInteraction { } else { this.conversationLogger?.logToolResult(toolUseId, toolRunResultContent); } - */ + */ const lastMessage = this.getLastMessage(); if (lastMessage && lastMessage.role === 'user') { diff --git a/api/src/llms/interactions/conversationInteraction.ts b/api/src/llms/interactions/conversationInteraction.ts index a26b628..45482f5 100644 --- a/api/src/llms/interactions/conversationInteraction.ts +++ b/api/src/llms/interactions/conversationInteraction.ts @@ -20,6 +20,7 @@ export interface FileMetadata { inSystemPrompt: boolean; messageId?: string; toolUseId?: string; + lastCommit?: string; error?: string | null; } export interface ProjectInfo { @@ -373,7 +374,13 @@ class LLMConversationInteraction extends LLMInteraction { const conversationStats: ConversationMetrics = this.getAllStats(); const tokenUsage: TokenUsage = response.messageResponse.usage; - this.conversationLogger.logAssistantMessage(msg, conversationStats, tokenUsage, this._tokenUsageStatement, this._tokenUsageInteraction); + this.conversationLogger.logAssistantMessage( + msg, + conversationStats, + tokenUsage, + this._tokenUsageStatement, + this._tokenUsageInteraction, + ); this._statementCount++; return response; diff --git a/api/src/llms/llmTool.ts b/api/src/llms/llmTool.ts index 0a004ae..a2dfab7 100644 --- a/api/src/llms/llmTool.ts +++ b/api/src/llms/llmTool.ts @@ -1,31 +1,46 @@ -import { LLMAnswerToolUse, LLMMessageContentPart, LLMMessageContentParts } from './llmMessage.ts'; +import { JSONSchema4 } from 'json-schema'; +import Ajv from 'ajv'; + +import { LLMAnswerToolUse, LLMMessageContentPart, LLMMessageContentParts } from 'api/llms/llmMessage.ts'; import LLMConversationInteraction from './interactions/conversationInteraction.ts'; import ProjectEditor from '../editor/projectEditor.ts'; +export type LLMToolInputSchema = JSONSchema4; export type LLMToolRunResultContent = string | LLMMessageContentPart | LLMMessageContentParts; export interface LLMToolFinalizeResult { - messageId: string; - toolResponse: string; + messageId: string; + toolResponse: string; +} +export interface LLMToolRunResult { + messageId: string; + toolResponse: string; + bbaiResponse: string; } export interface ToolFormatter { - formatToolUse(toolName: string, input: object): string; - formatToolResult(toolName: string, result: LLMToolRunResultContent): string; + formatToolUse(toolName: string, input: object): string; + formatToolResult(toolName: string, result: LLMToolRunResultContent): string; } abstract class LLMTool { - abstract name: string; - abstract description: string; - abstract parameters: Record; + constructor( + public name: string, + public description: string, + ) {} - abstract validateInput(input: unknown): boolean; + abstract get input_schema(): LLMToolInputSchema; - abstract runTool( - interaction: LLMConversationInteraction, - toolUse: LLMAnswerToolUse, - projectEditor: ProjectEditor, - ): Promise<{ messageId: string; toolResponse: string; bbaiResponse: string }>; + validateInput(input: unknown): boolean { + const ajv = new Ajv(); + const validate = ajv.compile(this.input_schema); + return validate(input) as boolean; + } + abstract runTool( + interaction: LLMConversationInteraction, + toolUse: LLMAnswerToolUse, + projectEditor: ProjectEditor, + ): Promise; } -export default LLMTool; \ No newline at end of file +export default LLMTool; diff --git a/api/src/llms/llmToolManager.ts b/api/src/llms/llmToolManager.ts index 99ef25f..895dc23 100644 --- a/api/src/llms/llmToolManager.ts +++ b/api/src/llms/llmToolManager.ts @@ -1,148 +1,175 @@ import { logger } from 'shared/logger.ts'; import LLMTool, { LLMToolFinalizeResult, LLMToolRunResultContent, ToolFormatter } from './llmTool.ts'; -import { LLMAnswerToolUse, LLMMessageContentPart } from './llmMessage.ts'; +import { LLMAnswerToolUse, LLMMessageContentPart } from 'api/llms/llmMessage.ts'; import LLMConversationInteraction from './interactions/conversationInteraction.ts'; import ProjectEditor from '../editor/projectEditor.ts'; +import { LLMToolRequestFiles } from './tools/requestFilesTool.ts'; +import { LLMToolSearchProject } from './tools/searchProjectTool.ts'; +import { LLMToolRunCommand } from './tools/runCommandTool.ts'; +import { LLMToolApplyPatch } from './tools/applyPatchTool.ts'; +import { LLMToolSearchAndReplace } from './tools/searchAndReplaceTool.ts'; +import { LLMToolRewriteFile } from './tools/rewriteFileTool.ts'; +//import { LLMToolVectorSearch } from './tools/vectorSearchTool.ts'; import { createError, ErrorType } from '../utils/error.utils.ts'; import { LLMValidationErrorOptions } from '../errors/error.ts'; export type LLMToolManagerToolSetType = 'coding' | 'research' | 'creative'; class LLMToolManager { - private static instance: LLMToolManager; - private tools: Map = new Map(); - private toolFormatters: Map = new Map(); - public toolSet: LLMToolManagerToolSetType = 'coding'; - - private constructor() { - this.loadTools(); - } - - static getInstance(): LLMToolManager { - if (!LLMToolManager.instance) { - LLMToolManager.instance = new LLMToolManager(); - } - return LLMToolManager.instance; - } - - private async loadTools() { - const toolsDir = new URL('./tools', import.meta.url); - for await (const entry of Deno.readDir(toolsDir)) { - if (entry.isFile && entry.name.endsWith('Tool.ts')) { - try { - const module = await import(`./tools/${entry.name}`); - const ToolClass = Object.values(module)[0] as typeof LLMTool; - const tool = new ToolClass(); - this.registerTool(tool); - - // Check if there's a formatter for this tool - if ('formatter' in ToolClass) { - this.registerToolFormatter(tool.name, (ToolClass as any).formatter); - } - } catch (error) { - logger.error(`Error loading tool ${entry.name}:`, error); - } - } - } - } - - private registerTool(tool: LLMTool): void { - this.tools.set(tool.name, tool); - } - - private registerToolFormatter(toolName: string, formatter: ToolFormatter): void { - this.toolFormatters.set(toolName, formatter); - } - - getTool(name: string): LLMTool | undefined { - return this.tools.get(name); - } - - getToolFormatter(toolName: string): ToolFormatter | undefined { - return this.toolFormatters.get(toolName); - } - - getAllTools(): LLMTool[] { - return Array.from(this.tools.values()); - } - - async handleToolUse( - interaction: LLMConversationInteraction, - toolUse: LLMAnswerToolUse, - projectEditor: ProjectEditor, - ): Promise<{ messageId: string; toolResponse: string; bbaiResponse: string; isError: boolean }> { - try { - const tool = this.getTool(toolUse.toolName); - if (!tool) { - logger.warn(`Unknown tool used: ${toolUse.toolName}`); - throw new Error(`Unknown tool used: ${toolUse.toolName}`); - } - - if (!toolUse.toolValidation.validated && !tool.validateInput(toolUse.toolInput)) { - throw createError(ErrorType.LLMValidation, `Invalid input for ${toolUse.toolName} tool`, { - name: `tool_use-${toolUse.toolName}`, - validation_type: 'input_schema', - validation_error: 'Input does not match the required schema', - } as LLMValidationErrorOptions); - } else { - logger.info( - `handleToolUse - Tool ${toolUse.toolName} is already validated with results: ${toolUse.toolValidation.results}`, - ); - } - - const { messageId, toolResponse, bbaiResponse } = await tool.runTool(interaction, toolUse, projectEditor); - return { messageId, toolResponse, bbaiResponse, isError: false }; - } catch (error) { - logger.error(`Error executing tool ${toolUse.toolName}:`, error); - const { messageId, toolResponse } = this.finalizeToolUse( - interaction, - toolUse, - error.message, - true, - ); - return { - messageId, - toolResponse: `Error with ${toolUse.toolName}: ${toolResponse}`, - bbaiResponse: 'BBai could not run the tool', - isError: true, - }; - } - } - - finalizeToolUse( - interaction: LLMConversationInteraction, - toolUse: LLMAnswerToolUse, - toolRunResultContent: LLMToolRunResultContent, - isError: boolean, - ): LLMToolFinalizeResult { - const messageId = interaction.addMessageForToolResult(toolUse.toolUseId, toolRunResultContent, isError) || ''; - const toolResponse = isError - ? `Tool ${toolUse.toolName} failed to run:\n${this.getContentFromToolResult(toolRunResultContent)}` - : `Tool ${toolUse.toolName} executed successfully:\n${this.getContentFromToolResult(toolRunResultContent)}`; - - return { messageId, toolResponse }; - } - - private getContentFromToolResult(toolRunResultContent: LLMToolRunResultContent): string { - if (Array.isArray(toolRunResultContent)) { - return toolRunResultContent.map((part) => this.getTextContent(part)).join('\n'); - } else if (typeof toolRunResultContent !== 'string') { - return this.getTextContent(toolRunResultContent); - } else { - return toolRunResultContent; - } - } - - private getTextContent(content: LLMMessageContentPart): string { - if ('text' in content) { - return content.text; - } else if ('image' in content) { - return '[Image content]'; - } else if ('tool_use_id' in content) { - return `[Tool result: ${content.tool_use_id}]`; - } - return '[Unknown content]'; - } + static toolFormatters: Map = new Map(); + private tools: Map = new Map(); + public toolSet: LLMToolManagerToolSetType = 'coding'; + //private static instance: LLMToolManager; + + constructor(toolSet?: LLMToolManagerToolSetType) { + if (toolSet) { + this.toolSet = toolSet; + } + this.registerDefaultTools(); + //this.loadTools(); + } + //static getInstance(): LLMToolManager { + // if (!LLMToolManager.instance) { + // LLMToolManager.instance = new LLMToolManager(); + // } + // return LLMToolManager.instance; + //} + + private registerDefaultTools(): void { + this.registerTool(new LLMToolRequestFiles()); + this.registerTool(new LLMToolSearchProject()); + this.registerTool(new LLMToolRewriteFile()); + this.registerTool(new LLMToolSearchAndReplace()); + this.registerTool(new LLMToolApplyPatch()); // Claude isn't good enough yet writing diff patches + this.registerTool(new LLMToolRunCommand()); + //this.registerTool(new LLMToolVectorSearch()); + } + /* + private async loadTools() { + const toolsDir = new URL('./tools', import.meta.url); + for await (const entry of Deno.readDir(toolsDir)) { + if (entry.isFile && entry.name.endsWith('Tool.ts')) { + try { + const module = await import(`./tools/${entry.name}`); + const ToolClass = Object.values(module)[0] as typeof LLMTool; + const tool = new ToolClass(); + this.registerTool(tool); + + // Check if there's a formatter for this tool + if ('formatter' in ToolClass) { + this.registerToolFormatter(tool.name, (ToolClass as any).formatter); + } + } catch (error) { + logger.error(`Error loading tool ${entry.name}:`, error); + } + } + } + } + */ + + static registerToolFormatter(toolName: string, formatter: ToolFormatter): void { + LLMToolManager.toolFormatters.set(toolName, formatter); + } + + static getToolFormatter(toolName: string): ToolFormatter | undefined { + return LLMToolManager.toolFormatters.get(toolName); + } + + registerTool(tool: LLMTool): void { + this.tools.set(tool.name, tool); + } + + getTool(name: string): LLMTool | undefined { + return this.tools.get(name); + } + + getAllTools(): LLMTool[] { + return Array.from(this.tools.values()); + } + + async handleToolUse( + interaction: LLMConversationInteraction, + toolUse: LLMAnswerToolUse, + projectEditor: ProjectEditor, + ): Promise<{ messageId: string; toolResponse: string; bbaiResponse: string; isError: boolean }> { + try { + const tool = this.getTool(toolUse.toolName); + if (!tool) { + logger.warn(`Unknown tool used: ${toolUse.toolName}`); + throw new Error(`Unknown tool used: ${toolUse.toolName}`); + } + + if (!toolUse.toolValidation.validated && !tool.validateInput(toolUse.toolInput)) { + throw createError(ErrorType.LLMValidation, `Invalid input for ${toolUse.toolName} tool`, { + name: `tool_use-${toolUse.toolName}`, + validation_type: 'input_schema', + validation_error: 'Input does not match the required schema', + } as LLMValidationErrorOptions); + } else { + logger.info( + `handleToolUse - Tool ${toolUse.toolName} is already validated with results: ${toolUse.toolValidation.results}`, + ); + } + //logger.debug(`handleToolUse - calling runTool for ${toolUse.toolName}`); + + // runTool will call finalizeToolUse, which handles addMessageForToolResult + const { messageId, toolResponse, bbaiResponse } = await tool.runTool(interaction, toolUse, projectEditor); + return { messageId, toolResponse, bbaiResponse, isError: false }; + } catch (error) { + logger.error(`Error executing tool ${toolUse.toolName}: ${error.message}`); + const { messageId, toolResponse } = this.finalizeToolUse( + interaction, + toolUse, + error.message, + true, + //projectEditor, + ); + return { + messageId, + toolResponse: `Error with ${toolUse.toolName}: ${toolResponse}`, + bbaiResponse: 'BBai could not run the tool', + isError: true, + }; + } + } + + finalizeToolUse( + interaction: LLMConversationInteraction, + toolUse: LLMAnswerToolUse, + toolRunResultContent: LLMToolRunResultContent, + isError: boolean, + //projectEditor: ProjectEditor, + ): LLMToolFinalizeResult { + //logger.debug(`finalizeToolUse - calling addMessageForToolResult for ${toolUse.toolName}`); + const messageId = interaction.addMessageForToolResult(toolUse.toolUseId, toolRunResultContent, isError) || + ''; + const toolResponse = isError + ? `Tool ${toolUse.toolName} failed to run:\n${this.getContentFromToolResult(toolRunResultContent)}` + : `Tool ${toolUse.toolName} executed successfully:\n${this.getContentFromToolResult(toolRunResultContent)}`; + + return { messageId, toolResponse }; + } + + private getContentFromToolResult(toolRunResultContent: LLMToolRunResultContent): string { + if (Array.isArray(toolRunResultContent)) { + return toolRunResultContent.map((part) => this.getTextContent(part)).join('\n'); + } else if (typeof toolRunResultContent !== 'string') { + return this.getTextContent(toolRunResultContent); + } else { + return toolRunResultContent; + } + } + + private getTextContent(content: LLMMessageContentPart): string { + if ('text' in content) { + return content.text; + } else if ('image' in content) { + return '[Image content]'; + } else if ('tool_use_id' in content) { + return `[Tool result: ${content.tool_use_id}]`; + } + return '[Unknown content]'; + } } -export default LLMToolManager; \ No newline at end of file +export default LLMToolManager; diff --git a/api/src/llms/providers/anthropicLLM.ts b/api/src/llms/providers/anthropicLLM.ts index be4662c..9189158 100755 --- a/api/src/llms/providers/anthropicLLM.ts +++ b/api/src/llms/providers/anthropicLLM.ts @@ -4,10 +4,7 @@ import type { ClientOptions } from 'anthropic'; import { AnthropicModel, LLMCallbackType, LLMProvider } from 'api/types.ts'; import LLM from './baseLLM.ts'; import LLMInteraction from '../interactions/baseInteraction.ts'; -import LLMMessage, { - LLMMessageContentParts, - LLMMessageContentPartTextBlock, -} from 'api/llms/llmMessage.ts'; +import LLMMessage, { LLMMessageContentParts, LLMMessageContentPartTextBlock } from 'api/llms/llmMessage.ts'; import LLMTool from '../llmTool.ts'; import { createError } from '../../utils/error.utils.ts'; import { ErrorType, LLMErrorOptions } from '../../errors/error.ts'; diff --git a/api/src/llms/tools/requestFilesTool.ts b/api/src/llms/tools/requestFilesTool.ts index 44abeec..15e4361 100644 --- a/api/src/llms/tools/requestFilesTool.ts +++ b/api/src/llms/tools/requestFilesTool.ts @@ -1,58 +1,141 @@ -import LLMTool, { ToolFormatter } from '../llmTool.ts'; -import { LLMAnswerToolUse, LLMMessageContentPart, LLMMessageContentParts } from '../llmMessage.ts'; +import LLMTool, { LLMToolInputSchema, LLMToolRunResult, ToolFormatter } from '../llmTool.ts'; +//import LLMToolManager from '../llmToolManager.ts'; import LLMConversationInteraction from '../interactions/conversationInteraction.ts'; +import { logger } from 'shared/logger.ts'; +import { + LLMAnswerToolUse, + //LLMMessageContentPart, + //LLMMessageContentParts, + LLMMessageContentPartTextBlock, +} from 'api/llms/llmMessage.ts'; import ProjectEditor from '../../editor/projectEditor.ts'; +import { createError, ErrorType } from '../../utils/error.utils.ts'; +/* class RequestFilesToolFormatter implements ToolFormatter { - formatToolUse(toolName: string, input: object): string { - const { fileNames } = input as { fileNames: string[] }; - return `Tool: ${toolName}\nRequested files: ${fileNames.join(', ')}`; - } - - formatToolResult(toolName: string, result: string | LLMMessageContentPart | LLMMessageContentParts): string { - if (typeof result === 'string') { - return `Tool: ${toolName}\nResult: ${result}`; - } else if (Array.isArray(result)) { - return `Tool: ${toolName}\nResult: ${result.map(part => 'text' in part ? part.text : JSON.stringify(part)).join('\n')}`; - } else { - return `Tool: ${toolName}\nResult: ${'text' in result ? result.text : JSON.stringify(result)}`; - } - } + formatToolUse(toolName: string, input: object): string { + const { fileNames } = input as { fileNames: string[] }; + return `Tool: ${toolName}\nRequested files: ${fileNames.join(', ')}`; + } + + formatToolResult(toolName: string, result: string | LLMMessageContentPart | LLMMessageContentParts): string { + if (typeof result === 'string') { + return `Tool: ${toolName}\nResult: ${result}`; + } else if (Array.isArray(result)) { + return `Tool: ${toolName}\nResult: ${ + result.map((part) => 'text' in part ? part.text : JSON.stringify(part)).join('\n') + }`; + } else { + return `Tool: ${toolName}\nResult: ${'text' in result ? result.text : JSON.stringify(result)}`; + } + } } + */ export class LLMToolRequestFiles extends LLMTool { - static formatter = new RequestFilesToolFormatter(); - - name = 'request_files'; - description = 'Request files to be added to the chat'; - parameters = { - fileNames: { - type: 'array', - items: { type: 'string' }, - description: 'Array of file names to be added to the chat', - }, - }; - - validateInput(input: unknown): boolean { - if (typeof input !== 'object' || input === null) { - return false; - } - const { fileNames } = input as { fileNames: unknown }; - return Array.isArray(fileNames) && fileNames.every((fileName) => typeof fileName === 'string'); - } - - async runTool( - interaction: LLMConversationInteraction, - toolUse: LLMAnswerToolUse, - projectEditor: ProjectEditor, - ): Promise<{ messageId: string; toolResponse: string; bbaiResponse: string }> { - const { fileNames } = toolUse.toolInput as { fileNames: string[] }; - const result = await projectEditor.addFilesToConversation(fileNames); - const messageId = interaction.addMessageForToolResult(toolUse.toolUseId, result) || ''; - return { - messageId, - toolResponse: result, - bbaiResponse: `Files added to the conversation: ${fileNames.join(', ')}`, - }; - } -} \ No newline at end of file + /* + private static formatter = new RequestFilesToolFormatter(); + private registerFormatter() { + LLMToolManager.registerToolFormatter(this.name, LLMToolRequestFiles.formatter); + } + */ + + constructor() { + super( + 'request_files', + 'Request files to be added to the chat', + ); + } + + get input_schema(): LLMToolInputSchema { + return { + type: 'object', + properties: { + fileNames: { + type: 'array', + items: { type: 'string' }, + description: 'Array of file names to be added to the chat', + }, + }, + required: ['fileNames'], + description: + `Request files for the chat when you need to review them or make changes. Before requesting a file, check that you don't already have it included in an earlier message`, + }; + } + + async runTool( + interaction: LLMConversationInteraction, + toolUse: LLMAnswerToolUse, + projectEditor: ProjectEditor, + ): Promise { + const { toolUseId: _toolUseId, toolInput } = toolUse; + const { fileNames } = toolInput as { fileNames: string[] }; + + try { + const filesAdded = await projectEditor.prepareFilesForConversation(fileNames); + + const contentParts = []; + const fileNamesSuccess: string[] = []; + const fileNamesError: string[] = []; + let allFilesFailed = true; + for (const fileToAdd of filesAdded) { + if (fileToAdd.metadata.error) { + contentParts.push({ + 'type': 'text', + 'text': `Error adding file ${fileToAdd.fileName}: ${fileToAdd.metadata.error}`, + } as LLMMessageContentPartTextBlock); + fileNamesError.push(fileToAdd.fileName); + } else { + contentParts.push({ + 'type': 'text', + 'text': `File added: ${fileToAdd.fileName}`, + } as LLMMessageContentPartTextBlock); + fileNamesSuccess.push(fileToAdd.fileName); + allFilesFailed = false; + } + } + + // [TODO] we're creating a bit of a circle by calling back into the toolManager in the projectEditor + // Since we're not holding onto a copy of toolManager, it should be fine - dangerous territory though + const { messageId, toolResponse } = projectEditor.orchestratorController.toolManager.finalizeToolUse( + interaction, + toolUse, + contentParts, + allFilesFailed, + //projectEditor, + ); + + projectEditor.orchestratorController.primaryInteraction.addFilesForMessage( + filesAdded, + messageId, + toolUse.toolUseId, + ); + const bbaiResponses = []; + if (fileNamesSuccess.length > 0) { + bbaiResponses.push(`BBai has added these files to the conversation: ${fileNamesSuccess.join(', ')}`); + } + if (fileNamesError.length > 0) { + bbaiResponses.push(`BBai failed to add these files to the conversation: ${fileNamesError.join(', ')}`); + } + + const bbaiResponse = bbaiResponses.join('\n\n'); + + // const storageLocation = this.determineStorageLocation(fullFilePath, content, source); + // if (storageLocation === 'system') { + // this.conversation.addFileForSystemPrompt(fileName, metadata, messageId, toolUse.toolUseId); + // } else { + // this.conversation.addFileForMessage(fileName, metadata, messageId, toolUse.toolUseId); + // } + + return { messageId, toolResponse, bbaiResponse }; + } catch (error) { + logger.error(`Error adding files to conversation: ${error.message}`); + + throw createError(ErrorType.FileHandling, `Error adding files to conversation: ${error.message}`, { + name: 'request-files', + filePath: projectEditor.projectRoot, + operation: 'request-files', + }); + } + } +} diff --git a/api/src/routes/api/websocket.handlers.ts b/api/src/routes/api/websocket.handlers.ts index 7a4558e..150a9c3 100644 --- a/api/src/routes/api/websocket.handlers.ts +++ b/api/src/routes/api/websocket.handlers.ts @@ -169,11 +169,16 @@ class WebSocketHandler { } // Method to send messages back to the client - private sendMessage = debounce((ws: WebSocket, type: string, data: any) => { + private sendMessage = (ws: WebSocket, type: string, data: any) => { //logger.debug(`Sending WebSocket message: type=${type}, data=${JSON.stringify(data)}`); //logger.debug('WebSocketHandler-sendMessage called'); ws.send(JSON.stringify({ type, data })); - }, 50); // 50ms debounce + }; + // private sendMessage = debounce((ws: WebSocket, type: string, data: any) => { + // //logger.debug(`Sending WebSocket message: type=${type}, data=${JSON.stringify(data)}`); + // //logger.debug('WebSocketHandler-sendMessage called'); + // ws.send(JSON.stringify({ type, data })); + // }, 50); // 50ms debounce } export default WebSocketHandler; diff --git a/api/tests/t/llms/tools/rewriteFileTool.test.ts b/api/tests/t/llms/tools/rewriteFileTool.test.ts index 97429ea..1519719 100644 --- a/api/tests/t/llms/tools/rewriteFileTool.test.ts +++ b/api/tests/t/llms/tools/rewriteFileTool.test.ts @@ -93,7 +93,7 @@ Deno.test({ sanitizeOps: false, }); -/* +/* Deno.test({ name: 'Rewrite File Tool - throw error for file outside project', async fn() { diff --git a/cli/src/utils/terminalHandler.utils.ts b/cli/src/utils/terminalHandler.utils.ts index c1a2c5a..508f30d 100644 --- a/cli/src/utils/terminalHandler.utils.ts +++ b/cli/src/utils/terminalHandler.utils.ts @@ -74,9 +74,9 @@ export class TerminalHandler { // preserveAspectRatio: true, //}) + ' ' + ansi.cursorTo(6, 2) + - colors.bold.blue.underline('BBai') + colors.bold.blue(' - Be Better with code and docs') - //colors.bold.blue(ansi.link('BBai', 'https://bbai.tips')) + - //+ '\n', + colors.bold.blue.underline('BBai') + colors.bold.blue(' - Be Better with code and docs'), + //colors.bold.blue(ansi.link('BBai', 'https://bbai.tips')) + + //+ '\n', ); } @@ -220,9 +220,7 @@ export class TerminalHandler { formatLine('Statement', statementCount.toString(), palette.success), ]; - const output = isNarrow - ? lines.join('\n') - : lines.join(' '); + const output = isNarrow ? lines.join('\n') : lines.join(' '); console.log(palette.primary(`${leftPadding}${symbols.sparkles} Conversation Started ${symbols.sparkles}`)); console.log(output); diff --git a/src/shared/utils/conversationLogger.utils.ts b/src/shared/utils/conversationLogger.utils.ts index 5aa5787..92db6d1 100644 --- a/src/shared/utils/conversationLogger.utils.ts +++ b/src/shared/utils/conversationLogger.utils.ts @@ -67,10 +67,8 @@ export class ConversationLogger { totalTokensTotal: 0, }, ) { - console.log(`Entering logEntry method for type: ${type}`); const timestamp = this.getTimestamp(); - console.log('Calling logEntryHandler...'); try { await this.logEntryHandler( type, @@ -81,12 +79,10 @@ export class ConversationLogger { tokenUsageStatement, tokenUsageConversation, ); - console.log('logEntryHandler called successfully'); } catch (error) { console.error('Error in logEntryHandler:', error); } - console.log('Creating raw entry...'); const entry = LogFormatter.createRawEntryWithSeparator( type, timestamp, @@ -96,17 +92,12 @@ export class ConversationLogger { tokenUsageStatement, tokenUsageConversation, ); - console.log('Raw entry created'); - console.log('Appending to log...'); try { await this.appendToLog(entry); - console.log('Successfully appended to log'); } catch (error) { console.error('Error appending to log:', error); } - - console.log(`Exiting logEntry method for type: ${type}`); } async logUserMessage(message: string, conversationStats?: ConversationMetrics) { @@ -145,17 +136,13 @@ export class ConversationLogger { tokenUsageStatement?: TokenUsage, tokenUsageConversation?: ConversationTokenUsage, ) { - console.log(`Entering logToolUse for: ${toolName}`); let message: string; try { - console.log('Attempting to format tool input...'); message = `Tool: ${toolName}\nInput: \n${JSON.stringify(toolInput, null, 2)}`; - console.log('Formatted message:', message); } catch (error) { console.error(`Error formatting tool use for ${toolName}:`, error); message = `Tool: ${toolName}\nInput: [Error formatting input]`; } - console.log('Calling logEntry...'); try { await this.logEntry( 'tool_use', @@ -165,11 +152,9 @@ export class ConversationLogger { tokenUsageStatement, tokenUsageConversation, ); - console.log('logEntry completed successfully'); } catch (error) { console.error('Error in logEntry:', error); } - console.log(`Exiting logToolUse for: ${toolName}`); } async logToolResult( diff --git a/src/shared/utils/logFormatter.utils.ts b/src/shared/utils/logFormatter.utils.ts index d8d8320..7a18d18 100644 --- a/src/shared/utils/logFormatter.utils.ts +++ b/src/shared/utils/logFormatter.utils.ts @@ -177,9 +177,9 @@ export class LogFormatter { const footer = color(`╰${'─'.repeat(this._maxLineLength - 1)}`); let formattedMessage = message.trim(); - if (type === 'tool_use') { - formattedMessage = this.prettifyJsonInMessage(formattedMessage); - } + //if (type === 'tool_use') { + // formattedMessage = this.prettifyJsonInMessage(formattedMessage); + //} //const wrappedMessage = this.wrapText(formattedMessage, color('│ '), ''); const wrappedMessage = this.wrapText(formattedMessage, color(' '), ''); From 8e2a04067955a9ff927ccd1a616aa9ea96027009 Mon Sep 17 00:00:00 2001 From: Charlie Garrison Date: Fri, 16 Aug 2024 01:16:43 +1000 Subject: [PATCH 31/31] version bump --- CHANGELOG.md | 7 +++++++ api/deno.jsonc | 2 +- cli/deno.jsonc | 2 +- deno.jsonc | 2 +- version.ts | 2 +- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 720d70c..167965c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 +## [0.0.8a-alpha] - 2024-08-15 + +### Changed + +- Hot fix for missing conversation log entries + + ## [0.0.8-alpha] - 2024-08-15 ### Changed diff --git a/api/deno.jsonc b/api/deno.jsonc index affc66b..d4de3f8 100644 --- a/api/deno.jsonc +++ b/api/deno.jsonc @@ -1,6 +1,6 @@ { "name": "bbai-api", - "version": "0.0.8-alpha", + "version": "0.0.8a-alpha", "exports": "./src/main.ts", "tasks": { "start": "deno run --allow-read --allow-write --allow-run --allow-net --allow-env src/main.ts", diff --git a/cli/deno.jsonc b/cli/deno.jsonc index b7a39b8..b216f79 100644 --- a/cli/deno.jsonc +++ b/cli/deno.jsonc @@ -1,6 +1,6 @@ { "name": "bbai-cli", - "version": "0.0.8-alpha", + "version": "0.0.8a-alpha", "exports": "./src/main.ts", "tasks": { "start": "deno run --allow-read --allow-write --allow-run --allow-net src/main.ts", diff --git a/deno.jsonc b/deno.jsonc index 05d2c0e..f427e6a 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -1,6 +1,6 @@ { "name": "bbai", - "version": "0.0.8-alpha", + "version": "0.0.8a-alpha", "exports": "./cli/src/main.ts", "tasks": { "tool:check-types": "deno check --all $DENO_ARGS", diff --git a/version.ts b/version.ts index f7bafb9..5151a8a 100644 --- a/version.ts +++ b/version.ts @@ -1 +1 @@ -export const VERSION = "0.0.8-alpha"; \ No newline at end of file +export const VERSION = "0.0.8a-alpha"; \ No newline at end of file