diff --git a/docs/features/message-compaction.md b/docs/features/message-compaction.md index 472535d..d36432e 100644 --- a/docs/features/message-compaction.md +++ b/docs/features/message-compaction.md @@ -7,6 +7,7 @@ When agents run for extended periods, they accumulate a large history of message ### 1. Token Usage Tracking The LLM abstraction now tracks and returns: + - Total tokens used in the current completion request - Maximum allowed tokens for the model/provider @@ -15,6 +16,7 @@ This information is used to monitor context window usage and trigger appropriate ### 2. Status Updates Agents receive status updates with information about: + - Current token usage and percentage of the maximum - Cost so far - Active sub-agents and their status @@ -22,10 +24,12 @@ Agents receive status updates with information about: - Active browser sessions and their status Status updates are sent: + 1. Every 5 agent interactions (periodic updates) 2. Whenever token usage exceeds 50% of the maximum (threshold-based updates) Example status update: + ``` --- STATUS UPDATE --- Token Usage: 45,235/100,000 (45%) @@ -72,6 +76,7 @@ Agents are instructed to monitor their token usage through status updates and us ## Configuration The message compaction feature is enabled by default with reasonable defaults: + - Status updates every 5 agent interactions - Recommendation to compact at 70% token usage - Default preservation of 10 recent messages when compacting @@ -81,17 +86,20 @@ The message compaction feature is enabled by default with reasonable defaults: The system includes token limits for various models: ### Anthropic Models + - claude-3-opus-20240229: 200,000 tokens - claude-3-sonnet-20240229: 200,000 tokens - claude-3-haiku-20240307: 200,000 tokens - claude-2.1: 100,000 tokens ### OpenAI Models + - gpt-4o: 128,000 tokens - gpt-4-turbo: 128,000 tokens - gpt-3.5-turbo: 16,385 tokens ### Ollama Models + - llama2: 4,096 tokens - mistral: 8,192 tokens - mixtral: 32,768 tokens @@ -102,4 +110,4 @@ The system includes token limits for various models: - Maintains important context for agent operation - Enables longer-running agent sessions - Makes the system more robust for complex tasks -- Gives agents self-awareness of resource usage \ No newline at end of file +- Gives agents self-awareness of resource usage diff --git a/example-status-update.md b/example-status-update.md index b66cab6..5a56cc2 100644 --- a/example-status-update.md +++ b/example-status-update.md @@ -47,4 +47,4 @@ The agent can use the compactHistory tool like this: } ``` -This will summarize all but the 10 most recent messages into a single summary message, significantly reducing token usage while preserving important context. \ No newline at end of file +This will summarize all but the 10 most recent messages into a single summary message, significantly reducing token usage while preserving important context. diff --git a/packages/agent/CHANGELOG.md b/packages/agent/CHANGELOG.md index 9c272fc..c524007 100644 --- a/packages/agent/CHANGELOG.md +++ b/packages/agent/CHANGELOG.md @@ -13,10 +13,9 @@ # [mycoder-agent-v1.6.0](https://github.com/drivecore/mycoder/compare/mycoder-agent-v1.5.0...mycoder-agent-v1.6.0) (2025-03-21) - ### Features -* **browser:** add system browser detection for Playwright ([00bd879](https://github.com/drivecore/mycoder/commit/00bd879443c9de51c6ee5e227d4838905506382a)), closes [#333](https://github.com/drivecore/mycoder/issues/333) +- **browser:** add system browser detection for Playwright ([00bd879](https://github.com/drivecore/mycoder/commit/00bd879443c9de51c6ee5e227d4838905506382a)), closes [#333](https://github.com/drivecore/mycoder/issues/333) # [mycoder-agent-v1.5.0](https://github.com/drivecore/mycoder/compare/mycoder-agent-v1.4.2...mycoder-agent-v1.5.0) (2025-03-20) diff --git a/packages/agent/src/core/llm/providers/anthropic.ts b/packages/agent/src/core/llm/providers/anthropic.ts index 8c78093..95a0458 100644 --- a/packages/agent/src/core/llm/providers/anthropic.ts +++ b/packages/agent/src/core/llm/providers/anthropic.ts @@ -12,6 +12,21 @@ import { ProviderOptions, } from '../types.js'; +// Define model context window sizes for Anthropic models +const ANTHROPIC_MODEL_LIMITS: Record = { + default: 200000, + 'claude-3-7-sonnet-20250219': 200000, + 'claude-3-7-sonnet-latest': 200000, + 'claude-3-5-sonnet-20241022': 200000, + 'claude-3-5-sonnet-latest': 200000, + 'claude-3-haiku-20240307': 200000, + 'claude-3-opus-20240229': 200000, + 'claude-3-sonnet-20240229': 200000, + 'claude-2.1': 100000, + 'claude-2.0': 100000, + 'claude-instant-1.2': 100000, +}; + /** * Anthropic-specific options */ @@ -81,28 +96,16 @@ function addCacheControlToMessages( }); } -// Define model context window sizes for Anthropic models -const ANTHROPIC_MODEL_LIMITS: Record = { - 'claude-3-opus-20240229': 200000, - 'claude-3-sonnet-20240229': 200000, - 'claude-3-haiku-20240307': 200000, - 'claude-3-7-sonnet-20250219': 200000, - 'claude-2.1': 100000, - 'claude-2.0': 100000, - 'claude-instant-1.2': 100000, - // Add other models as needed -}; - function tokenUsageFromMessage(message: Anthropic.Message, model: string) { const usage = new TokenUsage(); usage.input = message.usage.input_tokens; usage.cacheWrites = message.usage.cache_creation_input_tokens ?? 0; usage.cacheReads = message.usage.cache_read_input_tokens ?? 0; usage.output = message.usage.output_tokens; - + const totalTokens = usage.input + usage.output; const maxTokens = ANTHROPIC_MODEL_LIMITS[model] || 100000; // Default fallback - + return { usage, totalTokens, @@ -196,7 +199,7 @@ export class AnthropicProvider implements LLMProvider { }); const tokenInfo = tokenUsageFromMessage(response, this.model); - + return { text: content, toolCalls: toolCalls, diff --git a/packages/agent/src/core/llm/providers/ollama.ts b/packages/agent/src/core/llm/providers/ollama.ts index 8928c8c..0edfebc 100644 --- a/packages/agent/src/core/llm/providers/ollama.ts +++ b/packages/agent/src/core/llm/providers/ollama.ts @@ -13,22 +13,6 @@ import { import { TokenUsage } from '../../tokens.js'; import { ToolCall } from '../../types.js'; -// Define model context window sizes for Ollama models -// These are approximate and may vary based on specific model configurations -const OLLAMA_MODEL_LIMITS: Record = { - 'llama2': 4096, - 'llama2-uncensored': 4096, - 'llama2:13b': 4096, - 'llama2:70b': 4096, - 'mistral': 8192, - 'mistral:7b': 8192, - 'mixtral': 32768, - 'codellama': 16384, - 'phi': 2048, - 'phi2': 2048, - 'openchat': 8192, - // Add other models as needed -}; import { LLMProvider } from '../provider.js'; import { GenerateOptions, @@ -38,6 +22,23 @@ import { FunctionDefinition, } from '../types.js'; +// Define model context window sizes for Ollama models +// These are approximate and may vary based on specific model configurations +const OLLAMA_MODEL_LIMITS: Record = { + default: 4096, + llama2: 4096, + 'llama2-uncensored': 4096, + 'llama2:13b': 4096, + 'llama2:70b': 4096, + mistral: 8192, + 'mistral:7b': 8192, + mixtral: 32768, + codellama: 16384, + phi: 2048, + phi2: 2048, + openchat: 8192, +}; + /** * Ollama-specific options */ @@ -130,16 +131,17 @@ export class OllamaProvider implements LLMProvider { const tokenUsage = new TokenUsage(); tokenUsage.output = response.eval_count || 0; tokenUsage.input = response.prompt_eval_count || 0; - + // Calculate total tokens and get max tokens for the model const totalTokens = tokenUsage.input + tokenUsage.output; - + // Extract the base model name without specific parameters const baseModelName = this.model.split(':')[0]; // Check if model exists in limits, otherwise use base model or default - const modelMaxTokens = OLLAMA_MODEL_LIMITS[this.model] || - (baseModelName ? OLLAMA_MODEL_LIMITS[baseModelName] : undefined) || - 4096; // Default fallback + const modelMaxTokens = + OLLAMA_MODEL_LIMITS[this.model] || + (baseModelName ? OLLAMA_MODEL_LIMITS[baseModelName] : undefined) || + 4096; // Default fallback return { text: content, diff --git a/packages/agent/src/core/llm/providers/openai.ts b/packages/agent/src/core/llm/providers/openai.ts index eca626a..4f84fb2 100644 --- a/packages/agent/src/core/llm/providers/openai.ts +++ b/packages/agent/src/core/llm/providers/openai.ts @@ -21,6 +21,11 @@ import type { // Define model context window sizes for OpenAI models const OPENAI_MODEL_LIMITS: Record = { + default: 128000, + 'o3-mini': 200000, + 'o1-pro': 200000, + o1: 200000, + 'o1-mini': 128000, 'gpt-4o': 128000, 'gpt-4-turbo': 128000, 'gpt-4-0125-preview': 128000, @@ -29,7 +34,6 @@ const OPENAI_MODEL_LIMITS: Record = { 'gpt-4-32k': 32768, 'gpt-3.5-turbo': 16385, 'gpt-3.5-turbo-16k': 16385, - // Add other models as needed }; /** @@ -129,7 +133,7 @@ export class OpenAIProvider implements LLMProvider { const tokenUsage = new TokenUsage(); tokenUsage.input = response.usage?.prompt_tokens || 0; tokenUsage.output = response.usage?.completion_tokens || 0; - + // Calculate total tokens and get max tokens for the model const totalTokens = tokenUsage.input + tokenUsage.output; const modelMaxTokens = OPENAI_MODEL_LIMITS[this.model] || 8192; // Default fallback @@ -217,4 +221,4 @@ export class OpenAIProvider implements LLMProvider { }, })); } -} \ No newline at end of file +} diff --git a/packages/agent/src/core/llm/types.ts b/packages/agent/src/core/llm/types.ts index 977cd51..50e5c95 100644 --- a/packages/agent/src/core/llm/types.ts +++ b/packages/agent/src/core/llm/types.ts @@ -81,8 +81,8 @@ export interface LLMResponse { toolCalls: ToolCall[]; tokenUsage: TokenUsage; // Add new fields for context window tracking - totalTokens?: number; // Total tokens used in this request - maxTokens?: number; // Maximum allowed tokens for this model + totalTokens?: number; // Total tokens used in this request + maxTokens?: number; // Maximum allowed tokens for this model } /** diff --git a/packages/agent/src/core/toolAgent/__tests__/statusUpdates.test.ts b/packages/agent/src/core/toolAgent/__tests__/statusUpdates.test.ts index e3ec626..997d73f 100644 --- a/packages/agent/src/core/toolAgent/__tests__/statusUpdates.test.ts +++ b/packages/agent/src/core/toolAgent/__tests__/statusUpdates.test.ts @@ -3,11 +3,11 @@ */ import { describe, expect, it, vi } from 'vitest'; -import { TokenTracker } from '../../tokens.js'; -import { ToolContext } from '../../types.js'; import { AgentStatus } from '../../../tools/agent/AgentTracker.js'; -import { ShellStatus } from '../../../tools/shell/ShellTracker.js'; import { SessionStatus } from '../../../tools/session/SessionTracker.js'; +import { ShellStatus } from '../../../tools/shell/ShellTracker.js'; +import { TokenTracker } from '../../tokens.js'; +import { ToolContext } from '../../types.js'; import { generateStatusUpdate } from '../statusUpdates.js'; describe('Status Updates', () => { @@ -16,7 +16,7 @@ describe('Status Updates', () => { const totalTokens = 50000; const maxTokens = 100000; const tokenTracker = new TokenTracker('test'); - + // Mock the context const context = { agentTracker: { @@ -29,14 +29,21 @@ describe('Status Updates', () => { getSessionsByStatus: vi.fn().mockReturnValue([]), }, } as unknown as ToolContext; - + // Execute - const statusMessage = generateStatusUpdate(totalTokens, maxTokens, tokenTracker, context); - + const statusMessage = generateStatusUpdate( + totalTokens, + maxTokens, + tokenTracker, + context, + ); + // Verify expect(statusMessage.role).toBe('system'); expect(statusMessage.content).toContain('--- STATUS UPDATE ---'); - expect(statusMessage.content).toContain('Token Usage: 50,000/100,000 (50%)'); + expect(statusMessage.content).toContain( + 'Token Usage: 50,000/100,000 (50%)', + ); expect(statusMessage.content).toContain('Active Sub-Agents: 0'); expect(statusMessage.content).toContain('Active Shell Processes: 0'); expect(statusMessage.content).toContain('Active Browser Sessions: 0'); @@ -47,13 +54,13 @@ describe('Status Updates', () => { // With 50% usage, it should now show the high usage warning expect(statusMessage.content).toContain('Your token usage is high'); }); - + it('should include active agents, shells, and sessions', () => { // Setup const totalTokens = 70000; const maxTokens = 100000; const tokenTracker = new TokenTracker('test'); - + // Mock the context with active agents, shells, and sessions const context = { agentTracker: { @@ -64,29 +71,36 @@ describe('Status Updates', () => { }, shellTracker: { getShells: vi.fn().mockReturnValue([ - { - id: 'shell1', - status: ShellStatus.RUNNING, - metadata: { command: 'npm test' } + { + id: 'shell1', + status: ShellStatus.RUNNING, + metadata: { command: 'npm test' }, }, ]), }, browserTracker: { getSessionsByStatus: vi.fn().mockReturnValue([ - { - id: 'session1', - status: SessionStatus.RUNNING, - metadata: { url: 'https://example.com' } + { + id: 'session1', + status: SessionStatus.RUNNING, + metadata: { url: 'https://example.com' }, }, ]), }, } as unknown as ToolContext; - + // Execute - const statusMessage = generateStatusUpdate(totalTokens, maxTokens, tokenTracker, context); - + const statusMessage = generateStatusUpdate( + totalTokens, + maxTokens, + tokenTracker, + context, + ); + // Verify - expect(statusMessage.content).toContain('Token Usage: 70,000/100,000 (70%)'); + expect(statusMessage.content).toContain( + 'Token Usage: 70,000/100,000 (70%)', + ); expect(statusMessage.content).toContain('Your token usage is high (70%)'); expect(statusMessage.content).toContain('recommended to use'); expect(statusMessage.content).toContain('Active Sub-Agents: 2'); @@ -97,4 +111,4 @@ describe('Status Updates', () => { expect(statusMessage.content).toContain('Active Browser Sessions: 1'); expect(statusMessage.content).toContain('- session1: https://example.com'); }); -}); \ No newline at end of file +}); diff --git a/packages/agent/src/core/toolAgent/statusUpdates.ts b/packages/agent/src/core/toolAgent/statusUpdates.ts index 8fd1149..e773ade 100644 --- a/packages/agent/src/core/toolAgent/statusUpdates.ts +++ b/packages/agent/src/core/toolAgent/statusUpdates.ts @@ -2,12 +2,12 @@ * Status update mechanism for agents */ +import { AgentStatus } from '../../tools/agent/AgentTracker.js'; +import { SessionStatus } from '../../tools/session/SessionTracker.js'; +import { ShellStatus } from '../../tools/shell/ShellTracker.js'; import { Message } from '../llm/types.js'; import { TokenTracker } from '../tokens.js'; import { ToolContext } from '../types.js'; -import { AgentStatus } from '../../tools/agent/AgentTracker.js'; -import { ShellStatus } from '../../tools/shell/ShellTracker.js'; -import { SessionStatus } from '../../tools/session/SessionTracker.js'; /** * Generate a status update message for the agent @@ -16,26 +16,22 @@ export function generateStatusUpdate( totalTokens: number, maxTokens: number, tokenTracker: TokenTracker, - context: ToolContext + context: ToolContext, ): Message { // Calculate token usage percentage const usagePercentage = Math.round((totalTokens / maxTokens) * 100); - + // Get active sub-agents - const activeAgents = context.agentTracker - ? getActiveAgents(context) - : []; - + const activeAgents = context.agentTracker ? getActiveAgents(context) : []; + // Get active shell processes - const activeShells = context.shellTracker - ? getActiveShells(context) - : []; - + const activeShells = context.shellTracker ? getActiveShells(context) : []; + // Get active browser sessions - const activeSessions = context.browserTracker - ? getActiveSessions(context) + const activeSessions = context.browserTracker + ? getActiveSessions(context) : []; - + // Format the status message const statusContent = [ `--- STATUS UPDATE ---`, @@ -43,20 +39,20 @@ export function generateStatusUpdate( `Cost So Far: ${tokenTracker.getTotalCost()}`, ``, `Active Sub-Agents: ${activeAgents.length}`, - ...activeAgents.map(a => `- ${a.id}: ${a.description}`), + ...activeAgents.map((a) => `- ${a.id}: ${a.description}`), ``, `Active Shell Processes: ${activeShells.length}`, - ...activeShells.map(s => `- ${s.id}: ${s.description}`), + ...activeShells.map((s) => `- ${s.id}: ${s.description}`), ``, `Active Browser Sessions: ${activeSessions.length}`, - ...activeSessions.map(s => `- ${s.id}: ${s.description}`), + ...activeSessions.map((s) => `- ${s.id}: ${s.description}`), ``, - usagePercentage >= 50 + usagePercentage >= 50 ? `Your token usage is high (${usagePercentage}%). It is recommended to use the 'compactHistory' tool now to reduce context size.` : `If token usage gets high (>50%), consider using the 'compactHistory' tool to reduce context size.`, `--- END STATUS ---`, ].join('\n'); - + return { role: 'system', content: statusContent, @@ -75,10 +71,10 @@ function formatNumber(num: number): string { */ function getActiveAgents(context: ToolContext) { const agents = context.agentTracker.getAgents(AgentStatus.RUNNING); - return agents.map(agent => ({ + return agents.map((agent) => ({ id: agent.id, description: agent.goal, - status: agent.status + status: agent.status, })); } @@ -87,10 +83,10 @@ function getActiveAgents(context: ToolContext) { */ function getActiveShells(context: ToolContext) { const shells = context.shellTracker.getShells(ShellStatus.RUNNING); - return shells.map(shell => ({ + return shells.map((shell) => ({ id: shell.id, description: shell.metadata.command, - status: shell.status + status: shell.status, })); } @@ -98,10 +94,12 @@ function getActiveShells(context: ToolContext) { * Get active browser sessions from the session tracker */ function getActiveSessions(context: ToolContext) { - const sessions = context.browserTracker.getSessionsByStatus(SessionStatus.RUNNING); - return sessions.map(session => ({ + const sessions = context.browserTracker.getSessionsByStatus( + SessionStatus.RUNNING, + ); + return sessions.map((session) => ({ id: session.id, description: session.metadata.url || 'No URL', - status: session.status + status: session.status, })); -} \ No newline at end of file +} diff --git a/packages/agent/src/core/toolAgent/toolAgentCore.ts b/packages/agent/src/core/toolAgent/toolAgentCore.ts index 12bd7f0..a7e09fb 100644 --- a/packages/agent/src/core/toolAgent/toolAgentCore.ts +++ b/packages/agent/src/core/toolAgent/toolAgentCore.ts @@ -1,18 +1,18 @@ import { zodToJsonSchema } from 'zod-to-json-schema'; +import { utilityTools } from '../../tools/utility/index.js'; import { generateText } from '../llm/core.js'; import { createProvider } from '../llm/provider.js'; import { Message, ToolUseMessage } from '../llm/types.js'; import { Tool, ToolContext } from '../types.js'; import { AgentConfig } from './config.js'; +import { generateStatusUpdate } from './statusUpdates.js'; import { logTokenUsage } from './tokenTracking.js'; import { executeTools } from './toolExecutor.js'; import { ToolAgentResult } from './types.js'; -import { generateStatusUpdate } from './statusUpdates.js'; // Import the utility tools including compactHistory -import { utilityTools } from '../../tools/utility/index.js'; // Import from our new LLM abstraction instead of Vercel AI SDK @@ -55,10 +55,10 @@ export const toolAgent = async ( baseUrl: context.baseUrl, apiKey: context.apiKey, }); - + // Add the utility tools to the tools array const allTools = [...tools, ...utilityTools]; - + // Variables for status updates let statusUpdateCounter = 0; const STATUS_UPDATE_FREQUENCY = 5; // Send status every 5 iterations by default @@ -151,33 +151,34 @@ export const toolAgent = async ( maxTokens: localContext.maxTokens, }; - const { text, toolCalls, tokenUsage, totalTokens, maxTokens } = await generateText( - provider, - generateOptions, - ); + const { text, toolCalls, tokenUsage, totalTokens, maxTokens } = + await generateText(provider, generateOptions); tokenTracker.tokenUsage.add(tokenUsage); - + // Send status updates based on frequency and token usage threshold statusUpdateCounter++; if (totalTokens && maxTokens) { const usagePercentage = Math.round((totalTokens / maxTokens) * 100); - const shouldSendByFrequency = statusUpdateCounter >= STATUS_UPDATE_FREQUENCY; + const shouldSendByFrequency = + statusUpdateCounter >= STATUS_UPDATE_FREQUENCY; const shouldSendByUsage = usagePercentage >= TOKEN_USAGE_THRESHOLD; - + // Send status update if either condition is met if (shouldSendByFrequency || shouldSendByUsage) { statusUpdateCounter = 0; - + const statusMessage = generateStatusUpdate( totalTokens, maxTokens, tokenTracker, - localContext + localContext, ); - + messages.push(statusMessage); - logger.debug(`Sent status update to agent (token usage: ${usagePercentage}%)`); + logger.debug( + `Sent status update to agent (token usage: ${usagePercentage}%)`, + ); } } diff --git a/packages/agent/src/tools/agent/AgentTracker.ts b/packages/agent/src/tools/agent/AgentTracker.ts index 0e452dc..5db5935 100644 --- a/packages/agent/src/tools/agent/AgentTracker.ts +++ b/packages/agent/src/tools/agent/AgentTracker.ts @@ -113,7 +113,7 @@ export class AgentTracker { (agent) => agent.status === status, ); } - + /** * Get list of active agents with their descriptions */ @@ -122,10 +122,10 @@ export class AgentTracker { description: string; status: AgentStatus; }> { - return this.getAgents(AgentStatus.RUNNING).map(agent => ({ + return this.getAgents(AgentStatus.RUNNING).map((agent) => ({ id: agent.id, description: agent.goal, - status: agent.status + status: agent.status, })); } diff --git a/packages/agent/src/tools/utility/__tests__/compactHistory.test.ts b/packages/agent/src/tools/utility/__tests__/compactHistory.test.ts index 47717d7..5a47219 100644 --- a/packages/agent/src/tools/utility/__tests__/compactHistory.test.ts +++ b/packages/agent/src/tools/utility/__tests__/compactHistory.test.ts @@ -1,7 +1,7 @@ /** * Tests for the compactHistory tool */ -import { describe, expect, it, vi, assert } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import { Message } from '../../../core/llm/types.js'; import { TokenTracker } from '../../../core/tokens.js'; @@ -38,7 +38,7 @@ describe('compactHistory tool', () => { { role: 'user', content: 'Hello' }, { role: 'assistant', content: 'Hi there' }, ]; - + const context = { messages, provider: 'openai', @@ -52,15 +52,18 @@ describe('compactHistory tool', () => { error: vi.fn(), }, } as unknown as ToolContext; - + // Execute - const result = await compactHistory({ preserveRecentMessages: 10 }, context); - + const result = await compactHistory( + { preserveRecentMessages: 10 }, + context, + ); + // Verify expect(result).toContain('Not enough messages'); expect(messages.length).toBe(2); // Messages should remain unchanged }); - + it('should compact messages and preserve recent ones', async () => { // Setup const messages: Message[] = [ @@ -73,7 +76,7 @@ describe('compactHistory tool', () => { { role: 'user', content: 'Recent message 1' }, { role: 'assistant', content: 'Recent response 1' }, ]; - + const context = { messages, provider: 'openai', @@ -87,10 +90,10 @@ describe('compactHistory tool', () => { error: vi.fn(), }, } as unknown as ToolContext; - + // Execute const result = await compactHistory({ preserveRecentMessages: 2 }, context); - + // Verify expect(result).toContain('Successfully compacted'); expect(messages.length).toBe(3); // 1 summary + 2 preserved messages @@ -99,14 +102,14 @@ describe('compactHistory tool', () => { expect(messages[1]?.content).toBe('Recent message 1'); // Preserved message expect(messages[2]?.content).toBe('Recent response 1'); // Preserved message }); - + it('should use custom prompt when provided', async () => { // Setup const messages: Message[] = Array.from({ length: 20 }, (_, i) => ({ role: i % 2 === 0 ? 'user' : 'assistant', content: `Message ${i + 1}`, })); - + const context = { messages, provider: 'openai', @@ -120,21 +123,24 @@ describe('compactHistory tool', () => { error: vi.fn(), }, } as unknown as ToolContext; - + // Import the actual generateText to spy on it const { generateText } = await import('../../../core/llm/core.js'); - + // Execute - await compactHistory({ - preserveRecentMessages: 5, - customPrompt: 'Custom summarization prompt' - }, context); - + await compactHistory( + { + preserveRecentMessages: 5, + customPrompt: 'Custom summarization prompt', + }, + context, + ); + // Verify expect(generateText).toHaveBeenCalled(); - + // Since we're mocking the function, we can't actually check the content // of the messages passed to it. We'll just verify it was called. expect(true).toBe(true); }); -}); \ No newline at end of file +}); diff --git a/packages/agent/src/tools/utility/compactHistory.ts b/packages/agent/src/tools/utility/compactHistory.ts index bbb8ebe..451b03c 100644 --- a/packages/agent/src/tools/utility/compactHistory.ts +++ b/packages/agent/src/tools/utility/compactHistory.ts @@ -26,7 +26,7 @@ export const CompactHistorySchema = z.object({ /** * Default compaction prompt */ -const DEFAULT_COMPACTION_PROMPT = +const DEFAULT_COMPACTION_PROMPT = "Provide a detailed but concise summary of our conversation above. Focus on information that would be helpful for continuing the conversation, including what we did, what we're doing, which files we're working on, and what we're going to do next."; /** @@ -34,38 +34,46 @@ const DEFAULT_COMPACTION_PROMPT = */ export const compactHistory = async ( params: z.infer, - context: ToolContext + context: ToolContext, ): Promise => { const { preserveRecentMessages, customPrompt } = params; const { tokenTracker, logger } = context; - + // Access messages from the toolAgentCore.ts context // Since messages are passed directly to the executeTools function const messages = (context as any).messages; - + // Need at least preserveRecentMessages + 1 to do any compaction if (!messages || messages.length <= preserveRecentMessages) { - return "Not enough messages to compact. No changes made."; + return 'Not enough messages to compact. No changes made.'; } - - logger.info(`Compacting message history, preserving ${preserveRecentMessages} recent messages`); - + + logger.info( + `Compacting message history, preserving ${preserveRecentMessages} recent messages`, + ); + // Split messages into those to compact and those to preserve - const messagesToCompact = messages.slice(0, messages.length - preserveRecentMessages); - const messagesToPreserve = messages.slice(messages.length - preserveRecentMessages); - + const messagesToCompact = messages.slice( + 0, + messages.length - preserveRecentMessages, + ); + const messagesToPreserve = messages.slice( + messages.length - preserveRecentMessages, + ); + // Create a system message with instructions for summarization const systemMessage: Message = { role: 'system', - content: 'You are an AI assistant tasked with summarizing a conversation. Provide a concise but informative summary that captures the key points, decisions, and context needed to continue the conversation effectively.', + content: + 'You are an AI assistant tasked with summarizing a conversation. Provide a concise but informative summary that captures the key points, decisions, and context needed to continue the conversation effectively.', }; - + // Create a user message with the compaction prompt const userMessage: Message = { role: 'user', - content: `${customPrompt || DEFAULT_COMPACTION_PROMPT}\n\nHere's the conversation to summarize:\n${messagesToCompact.map(m => `${m.role}: ${m.content}`).join('\n')}`, + content: `${customPrompt || DEFAULT_COMPACTION_PROMPT}\n\nHere's the conversation to summarize:\n${messagesToCompact.map((m) => `${m.role}: ${m.content}`).join('\n')}`, }; - + // Generate the summary // Create a provider from the model provider configuration const { createProvider } = await import('../../core/llm/provider.js'); @@ -73,30 +81,35 @@ export const compactHistory = async ( baseUrl: context.baseUrl, apiKey: context.apiKey, }); - + const { text, tokenUsage } = await generateText(llmProvider, { messages: [systemMessage, userMessage], temperature: 0.3, // Lower temperature for more consistent summaries }); - + // Add token usage to tracker tokenTracker.tokenUsage.add(tokenUsage); - + // Create a new message with the summary const summaryMessage: Message = { role: 'system', content: `[COMPACTED MESSAGE HISTORY]: ${text}`, }; - + // Replace the original messages array with compacted version // This modifies the array in-place messages.splice(0, messages.length, summaryMessage, ...messagesToPreserve); - + // Calculate token reduction (approximate) - const originalLength = messagesToCompact.reduce((sum, m) => sum + m.content.length, 0); + const originalLength = messagesToCompact.reduce( + (sum, m) => sum + m.content.length, + 0, + ); const newLength = summaryMessage.content.length; - const reductionPercentage = Math.round(((originalLength - newLength) / originalLength) * 100); - + const reductionPercentage = Math.round( + ((originalLength - newLength) / originalLength) * 100, + ); + return `Successfully compacted ${messagesToCompact.length} messages into a summary, preserving the ${preserveRecentMessages} most recent messages. Reduced message history size by approximately ${reductionPercentage}%.`; }; @@ -105,8 +118,12 @@ export const compactHistory = async ( */ export const CompactHistoryTool: Tool = { name: 'compactHistory', - description: 'Compacts the message history by summarizing older messages to reduce token usage', + description: + 'Compacts the message history by summarizing older messages to reduce token usage', parameters: CompactHistorySchema, returns: z.string(), - execute: compactHistory as unknown as (params: Record, context: ToolContext) => Promise, -}; \ No newline at end of file + execute: compactHistory as unknown as ( + params: Record, + context: ToolContext, + ) => Promise, +}; diff --git a/packages/agent/src/tools/utility/index.ts b/packages/agent/src/tools/utility/index.ts index 9dc7d0a..39015b3 100644 --- a/packages/agent/src/tools/utility/index.ts +++ b/packages/agent/src/tools/utility/index.ts @@ -5,4 +5,4 @@ import { CompactHistoryTool } from './compactHistory.js'; export const utilityTools = [CompactHistoryTool]; -export { CompactHistoryTool } from './compactHistory.js'; \ No newline at end of file +export { CompactHistoryTool } from './compactHistory.js'; diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 3488d63..e219b55 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,9 +1,8 @@ # [mycoder-v1.6.0](https://github.com/drivecore/mycoder/compare/mycoder-v1.5.0...mycoder-v1.6.0) (2025-03-21) - ### Features -* **browser:** add system browser detection for Playwright ([00bd879](https://github.com/drivecore/mycoder/commit/00bd879443c9de51c6ee5e227d4838905506382a)), closes [#333](https://github.com/drivecore/mycoder/issues/333) +- **browser:** add system browser detection for Playwright ([00bd879](https://github.com/drivecore/mycoder/commit/00bd879443c9de51c6ee5e227d4838905506382a)), closes [#333](https://github.com/drivecore/mycoder/issues/333) # [mycoder-v1.5.0](https://github.com/drivecore/mycoder/compare/mycoder-v1.4.1...mycoder-v1.5.0) (2025-03-20) diff --git a/packages/cli/src/commands/test-profile.ts b/packages/cli/src/commands/test-profile.ts deleted file mode 100644 index 50b54e3..0000000 --- a/packages/cli/src/commands/test-profile.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { CommandModule } from 'yargs'; - -import { SharedOptions } from '../options.js'; - -export const command: CommandModule = { - command: 'test-profile', - describe: 'Test the profiling feature', - handler: async () => { - console.log('Profile test completed successfully'); - // Profiling report will be automatically displayed by the main function - - // Force a delay to simulate some processing - await new Promise((resolve) => setTimeout(resolve, 100)); - }, -}; diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index a3afbb2..e6d21fa 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -7,7 +7,6 @@ import { hideBin } from 'yargs/helpers'; import { command as defaultCommand } from './commands/$default.js'; import { getCustomCommands } from './commands/custom.js'; -import { command as testProfileCommand } from './commands/test-profile.js'; import { command as testSentryCommand } from './commands/test-sentry.js'; import { command as toolsCommand } from './commands/tools.js'; import { SharedOptions, sharedOptions } from './options.js'; @@ -61,7 +60,6 @@ const main = async () => { .command([ defaultCommand, testSentryCommand, - testProfileCommand, toolsCommand, ...customCommands, // Add custom commands ] as CommandModule[]) diff --git a/packages/docs/docs/getting-started/linux.md b/packages/docs/docs/getting-started/linux.md index 03bf1e7..4a18b5d 100644 --- a/packages/docs/docs/getting-started/linux.md +++ b/packages/docs/docs/getting-started/linux.md @@ -153,7 +153,7 @@ MyCoder can use a browser for research. On Linux: browser: { useSystemBrowsers: true, preferredType: 'chromium', // or 'firefox' - } + }, }; ``` diff --git a/packages/docs/docs/getting-started/macos.md b/packages/docs/docs/getting-started/macos.md index a8073b3..6586ed0 100644 --- a/packages/docs/docs/getting-started/macos.md +++ b/packages/docs/docs/getting-started/macos.md @@ -162,7 +162,7 @@ MyCoder can use a browser for research. On macOS: browser: { useSystemBrowsers: true, preferredType: 'chromium', // or 'firefox' - } + }, }; ``` diff --git a/packages/docs/docs/getting-started/windows.md b/packages/docs/docs/getting-started/windows.md index ac841cd..4c7f63b 100644 --- a/packages/docs/docs/getting-started/windows.md +++ b/packages/docs/docs/getting-started/windows.md @@ -139,7 +139,7 @@ MyCoder can use a browser for research. On Windows: browser: { useSystemBrowsers: true, preferredType: 'chromium', // or 'firefox' - } + }, }; ``` diff --git a/packages/docs/docs/usage/browser-detection.md b/packages/docs/docs/usage/browser-detection.md index c41879b..8733ffa 100644 --- a/packages/docs/docs/usage/browser-detection.md +++ b/packages/docs/docs/usage/browser-detection.md @@ -22,11 +22,13 @@ This process happens automatically and is designed to be seamless for the user. MyCoder can detect and use the following browsers: ### Windows + - Google Chrome - Microsoft Edge - Mozilla Firefox ### macOS + - Google Chrome - Google Chrome Canary - Microsoft Edge @@ -35,6 +37,7 @@ MyCoder can detect and use the following browsers: - Firefox Nightly ### Linux + - Google Chrome - Chromium - Mozilla Firefox @@ -47,7 +50,7 @@ You can customize the browser detection behavior in your `mycoder.config.js` fil // mycoder.config.js export default { // Other settings... - + // System browser detection settings browser: { // Whether to use system browsers or Playwright's bundled browsers @@ -64,11 +67,11 @@ export default { ### Configuration Options Explained -| Option | Description | Default | -|--------|-------------|---------| -| `useSystemBrowsers` | Whether to use system-installed browsers if available | `true` | -| `preferredType` | Preferred browser engine type (`chromium`, `firefox`, `webkit`) | `chromium` | -| `executablePath` | Custom browser executable path (overrides automatic detection) | `null` | +| Option | Description | Default | +| ------------------- | --------------------------------------------------------------- | ---------- | +| `useSystemBrowsers` | Whether to use system-installed browsers if available | `true` | +| `preferredType` | Preferred browser engine type (`chromium`, `firefox`, `webkit`) | `chromium` | +| `executablePath` | Custom browser executable path (overrides automatic detection) | `null` | ## Browser Selection Priority @@ -124,9 +127,10 @@ export default { export default { browser: { useSystemBrowsers: true, - executablePath: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', // Windows example + executablePath: + 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', // Windows example // executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', // macOS example // executablePath: '/usr/bin/google-chrome', // Linux example }, }; -``` \ No newline at end of file +``` diff --git a/packages/docs/docs/usage/configuration.md b/packages/docs/docs/usage/configuration.md index a692956..47f4782 100644 --- a/packages/docs/docs/usage/configuration.md +++ b/packages/docs/docs/usage/configuration.md @@ -91,11 +91,11 @@ export default { MyCoder can detect and use your system-installed browsers instead of requiring Playwright's bundled browsers. This is especially useful when MyCoder is installed globally via npm. -| Option | Description | Possible Values | Default | -| ------------------------- | ------------------------------------------------ | ------------------------------ | ---------- | -| `browser.useSystemBrowsers` | Use system-installed browsers if available | `true`, `false` | `true` | -| `browser.preferredType` | Preferred browser engine type | `chromium`, `firefox`, `webkit` | `chromium` | -| `browser.executablePath` | Custom browser executable path (optional) | String path to browser executable | `null` | +| Option | Description | Possible Values | Default | +| --------------------------- | ------------------------------------------ | --------------------------------- | ---------- | +| `browser.useSystemBrowsers` | Use system-installed browsers if available | `true`, `false` | `true` | +| `browser.preferredType` | Preferred browser engine type | `chromium`, `firefox`, `webkit` | `chromium` | +| `browser.executablePath` | Custom browser executable path (optional) | String path to browser executable | `null` | Example: @@ -105,7 +105,7 @@ export default { // Show browser windows and use readability for better web content parsing headless: false, pageFilter: 'readability', - + // System browser detection settings browser: { useSystemBrowsers: true, @@ -192,7 +192,7 @@ export default { headless: false, userSession: true, pageFilter: 'readability', - + // System browser detection settings browser: { useSystemBrowsers: true, diff --git a/packages/docs/docs/usage/message-compaction.md b/packages/docs/docs/usage/message-compaction.md index d1d68b1..e28b290 100644 --- a/packages/docs/docs/usage/message-compaction.md +++ b/packages/docs/docs/usage/message-compaction.md @@ -11,6 +11,7 @@ When agents run for extended periods, they accumulate a large history of message ### Token Usage Tracking MyCoder's LLM abstraction tracks and returns: + - Total tokens used in the current completion request - Maximum allowed tokens for the model/provider @@ -19,6 +20,7 @@ This information is used to monitor context window usage and trigger appropriate ### Status Updates Agents receive status updates with information about: + - Current token usage and percentage of the maximum - Cost so far - Active sub-agents and their status @@ -26,10 +28,12 @@ Agents receive status updates with information about: - Active browser sessions and their status Status updates are sent: + 1. Every 5 agent interactions (periodic updates) 2. Whenever token usage exceeds 50% of the maximum (threshold-based updates) Example status update: + ``` --- STATUS UPDATE --- Token Usage: 45,235/100,000 (45%) @@ -77,10 +81,10 @@ Agents are instructed to monitor their token usage through status updates and us The `compactHistory` tool accepts the following parameters: -| Parameter | Type | Description | Default | -|-----------|------|-------------|---------| -| `preserveRecentMessages` | number | Number of recent messages to preserve unchanged | 10 | -| `customPrompt` | string (optional) | Custom prompt for the summarization | Default compaction prompt | +| Parameter | Type | Description | Default | +| ------------------------ | ----------------- | ----------------------------------------------- | ------------------------- | +| `preserveRecentMessages` | number | Number of recent messages to preserve unchanged | 10 | +| `customPrompt` | string (optional) | Custom prompt for the summarization | Default compaction prompt | ## Benefits @@ -95,17 +99,20 @@ The `compactHistory` tool accepts the following parameters: MyCoder includes token limits for various models: ### Anthropic Models + - claude-3-opus-20240229: 200,000 tokens - claude-3-sonnet-20240229: 200,000 tokens - claude-3-haiku-20240307: 200,000 tokens - claude-2.1: 100,000 tokens ### OpenAI Models + - gpt-4o: 128,000 tokens - gpt-4-turbo: 128,000 tokens - gpt-3.5-turbo: 16,385 tokens ### Ollama Models + - llama2: 4,096 tokens - mistral: 8,192 tokens -- mixtral: 32,768 tokens \ No newline at end of file +- mixtral: 32,768 tokens