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 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/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..2f5f4cf 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.tokenUsageConversation = 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', @@ -423,11 +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.tokenUsageStatement, + //interaction.conversationStats, + //interaction.tokenUsageTurn, + //interaction.tokenUsageStatement, + //interaction.tokenUsageConversation, ); const { messageId: _messageId, toolResponse, bbaiResponse, isError } = await this.toolManager.handleToolUse( interaction, @@ -435,13 +438,13 @@ 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, - interaction.tokenUsageStatement, + //interaction.conversationStats, + //interaction.tokenUsageStatement, // token usage is recorded with the tool use ); return toolResponse; diff --git a/api/src/editor/projectEditor.ts b/api/src/editor/projectEditor.ts index ac170f4..d94040a 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 { 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'; @@ -155,10 +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 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: '', }, }); } 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..cb0275a 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,13 @@ 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 +162,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; @@ -268,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 c03fb15..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.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 9b5ae2d..a2dfab7 100644 --- a/api/src/llms/llmTool.ts +++ b/api/src/llms/llmTool.ts @@ -1,9 +1,9 @@ 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 LLMConversationInteraction from './interactions/conversationInteraction.ts'; +import ProjectEditor from '../editor/projectEditor.ts'; export type LLMToolInputSchema = JSONSchema4; export type LLMToolRunResultContent = string | LLMMessageContentPart | LLMMessageContentParts; @@ -18,6 +18,11 @@ export interface LLMToolRunResult { bbaiResponse: string; } +export interface ToolFormatter { + formatToolUse(toolName: string, input: object): string; + formatToolResult(toolName: string, result: LLMToolRunResultContent): string; +} + abstract class LLMTool { constructor( public name: string, @@ -31,7 +36,6 @@ abstract class LLMTool { const validate = ajv.compile(this.input_schema); return validate(input) as boolean; } - abstract runTool( interaction: LLMConversationInteraction, toolUse: LLMAnswerToolUse, diff --git a/api/src/llms/llmToolManager.ts b/api/src/llms/llmToolManager.ts index 0373b13..895dc23 100644 --- a/api/src/llms/llmToolManager.ts +++ b/api/src/llms/llmToolManager.ts @@ -1,13 +1,14 @@ 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'; 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 { 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'; @@ -15,24 +16,64 @@ import { LLMValidationErrorOptions } from '../errors/error.ts'; export type LLMToolManagerToolSetType = 'coding' | 'research' | 'creative'; class LLMToolManager { + 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 LLMToolApplyPatch()); // Claude isn't good enough yet writing diff patches //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); diff --git a/api/src/llms/providers/anthropicLLM.ts b/api/src/llms/providers/anthropicLLM.ts index 1f134fa..9189158 100755 --- a/api/src/llms/providers/anthropicLLM.ts +++ b/api/src/llms/providers/anthropicLLM.ts @@ -1,14 +1,10 @@ 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'; +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'; @@ -120,10 +116,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/requestFilesTool.ts b/api/src/llms/tools/requestFilesTool.ts index 5ceeb03..15e4361 100644 --- a/api/src/llms/tools/requestFilesTool.ts +++ b/api/src/llms/tools/requestFilesTool.ts @@ -1,11 +1,45 @@ -import LLMTool, { LLMToolInputSchema, LLMToolRunResult } from '../llmTool.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, LLMMessageContentPartTextBlock } from 'api/llms/llmMessage.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)}`; + } + } +} + */ + export class LLMToolRequestFiles extends LLMTool { + /* + private static formatter = new RequestFilesToolFormatter(); + private registerFormatter() { + LLMToolManager.registerToolFormatter(this.name, LLMToolRequestFiles.formatter); + } + */ + constructor() { super( 'request_files', diff --git a/api/src/llms/tools/rewriteFileTool.ts b/api/src/llms/tools/rewriteFileTool.ts new file mode 100644 index 0000000..19c8228 --- /dev/null +++ b/api/src/llms/tools/rewriteFileTool.ts @@ -0,0 +1,125 @@ +import LLMTool, { LLMToolInputSchema, LLMToolRunResult } from '../llmTool.ts'; +import LLMConversationInteraction from '../interactions/conversationInteraction.ts'; +import ProjectEditor from '../../editor/projectEditor.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}`); + + 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; + } + } + + 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); + } + + await Deno.writeTextFile(fullFilePath, content); + projectEditor.patchedFiles.add(filePath); + projectEditor.patchContents.set(filePath, JSON.stringify(content)); + + // 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); + } + + 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, + ); + + 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); + + throw createError(ErrorType.FileHandling, errorMessage, { + name: 'rewrite-file', + filePath: filePath, + operation: 'rewrite-file', + } as FileHandlingErrorOptions); + } + } +} 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 new file mode 100644 index 0000000..1519719 --- /dev/null +++ b/api/tests/t/llms/tools/rewriteFileTool.test.ts @@ -0,0 +1,125 @@ +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: '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: '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: '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/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/cli/src/utils/terminalHandler.utils.ts b/cli/src/utils/terminalHandler.utils.ts index 33ea52c..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', ); } @@ -203,48 +203,27 @@ 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 leftPadding = ' '; - 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) => { + return color(`${leftPadding}${label}: ${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(`${leftPadding}${symbols.sparkles} Conversation Started ${symbols.sparkles}`)); + console.log(output); console.log(''); if (expectingMoreInput && this.spinner) { 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.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/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 1f205a7..92db6d1 100644 --- a/src/shared/utils/conversationLogger.utils.ts +++ b/src/shared/utils/conversationLogger.utils.ts @@ -1,10 +1,10 @@ import { join } from '@std/path'; import { ensureDir } from '@std/fs'; -import type { ConversationId, ConversationMetrics, TokenUsage } 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'; +import { logger } from 'shared/logger.ts'; import { LLMMessageContentPart, LLMMessageContentPartImageBlock, @@ -25,7 +25,9 @@ export class ConversationLogger { timestamp: string, content: string, conversationStats: ConversationMetrics, - tokenUsage: TokenUsage, + tokenUsageTurn: TokenUsage, + tokenUsageStatement: TokenUsage, + tokenUsageConversation: ConversationTokenUsage, ) => {}, ) {} @@ -48,18 +50,54 @@ export class ConversationLogger { private async logEntry( type: ConversationLoggerEntryType, message: string, - conversationStats?: ConversationMetrics, - tokenUsage?: 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(); - if (!tokenUsage) tokenUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 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); - await this.appendToLog(entry); - await this.logEntryHandler(type, timestamp, message, conversationStats, tokenUsage); + try { + await this.logEntryHandler( + type, + timestamp, + message, + conversationStats, + tokenUsageTurn, + tokenUsageStatement, + tokenUsageConversation, + ); + } catch (error) { + console.error('Error in logEntryHandler:', error); + } + + const entry = LogFormatter.createRawEntryWithSeparator( + type, + timestamp, + message, + conversationStats, + tokenUsageTurn, + tokenUsageStatement, + tokenUsageConversation, + ); + + try { + await this.appendToLog(entry); + } catch (error) { + console.error('Error appending to log:', error); + } } async logUserMessage(message: string, conversationStats?: ConversationMetrics) { @@ -69,41 +107,77 @@ 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) { - await this.logEntry('auxiliary', message); + await this.logEntry( + 'auxiliary', + message, + ); } async logToolUse( toolName: string, - input: object, + toolInput: 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); + let message: string; + try { + message = `Tool: ${toolName}\nInput: \n${JSON.stringify(toolInput, null, 2)}`; + } catch (error) { + console.error(`Error formatting tool use for ${toolName}:`, error); + message = `Tool: ${toolName}\nInput: [Error formatting input]`; + } + try { + await this.logEntry( + 'tool_use', + message, + conversationStats, + tokenUsageTurn, + tokenUsageStatement, + tokenUsageConversation, + ); + } catch (error) { + console.error('Error in logEntry:', error); + } } 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) - ? '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, tokenUsage); + 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); } async logError(error: string) { @@ -114,4 +188,23 @@ export class ConversationLogger { // const message = `Diff Patch for ${filePath}:\n${patch}`; // await this.logEntry('text_change', message); //} + + 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; + } + } catch (error) { + console.error('Error in formatDefaultToolResult:', error); + return '[Error formatting result]'; + } + } } 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}`); + } + } } diff --git a/src/shared/utils/logFormatter.utils.ts b/src/shared/utils/logFormatter.utils.ts index 1b84525..7a18d18 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; @@ -165,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(' '), ''); @@ -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 diff --git a/test_tool_use.txt b/test_tool_use.txt new file mode 100644 index 0000000..0caf4da --- /dev/null +++ b/test_tool_use.txt @@ -0,0 +1,5 @@ +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 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