diff --git a/agent-teams-controller/src/internal/tasks.js b/agent-teams-controller/src/internal/tasks.js index acb1e522..846373fd 100644 --- a/agent-teams-controller/src/internal/tasks.js +++ b/agent-teams-controller/src/internal/tasks.js @@ -38,7 +38,10 @@ function buildAssignmentMessage(context, task, options = {}) { const prompt = typeof options.prompt === 'string' && options.prompt.trim() ? options.prompt.trim() : ''; const taskLabel = `#${task.displayId || task.id}`; - const lines = [`New task assigned to you: ${taskLabel} "${task.subject}".`]; + const lines = [ + `New task assigned to you: ${taskLabel} "${task.subject}".`, + `If you are not currently working on another task, start this one now. If you are busy, start it as soon as your current task is finished.`, + ]; if (description) { lines.push(``, `Description:`, description); @@ -53,7 +56,7 @@ function buildAssignmentMessage(context, task, options = {}) { wrapAgentBlock(`Use the board MCP tools to work this task correctly: 1. Check the latest full context before starting: task_get { teamName: "${context.teamName}", taskId: "${task.id}" } -2. When you actually begin work, mark it started: +2. If you are idle, start now; otherwise start as soon as your current task is done. When you actually begin work, mark it started: task_start { teamName: "${context.teamName}", taskId: "${task.id}" } 3. When the work is done, mark it completed: task_complete { teamName: "${context.teamName}", taskId: "${task.id}" }`) diff --git a/agent-teams-controller/test/controller.test.js b/agent-teams-controller/test/controller.test.js index 322611ef..c3808430 100644 --- a/agent-teams-controller/test/controller.test.js +++ b/agent-teams-controller/test/controller.test.js @@ -340,6 +340,9 @@ describe('agent-teams-controller API', () => { expect(ownerInbox[0].summary).toContain(`#${pendingTask.displayId}`); expect(ownerInbox[0].text).toContain('task_get'); expect(ownerInbox[0].text).toContain('task_start'); + expect(ownerInbox[0].text).toContain( + 'If you are not currently working on another task, start this one now.' + ); expect(ownerInbox[0].leadSessionId).toBe('lead-session-1'); expect(ownerInbox[3].summary).toContain(`#${reassignedTask.displayId}`); diff --git a/src/renderer/components/team/CollapsibleTeamSection.tsx b/src/renderer/components/team/CollapsibleTeamSection.tsx index abccf05b..82743da9 100644 --- a/src/renderer/components/team/CollapsibleTeamSection.tsx +++ b/src/renderer/components/team/CollapsibleTeamSection.tsx @@ -79,7 +79,7 @@ export const CollapsibleTeamSection = ({
diff --git a/test/main/services/team/ChangeExtractorService.test.ts b/test/main/services/team/ChangeExtractorService.test.ts index fc89b777..fd312b2a 100644 --- a/test/main/services/team/ChangeExtractorService.test.ts +++ b/test/main/services/team/ChangeExtractorService.test.ts @@ -73,16 +73,16 @@ function persistedEntryPath(baseDir: string): string { function createService(params: { logPaths: string[]; projectPath?: string; - findLogsForTask?: (teamName: string, taskId: string, options?: unknown) => Promise; + findLogFileRefsForTask?: (teamName: string, taskId: string, options?: unknown) => Promise; }) { - const findLogsForTask = - params.findLogsForTask ?? + const findLogFileRefsForTask = + params.findLogFileRefsForTask ?? vi.fn(async () => params.logPaths.map((filePath) => ({ filePath, memberName: 'alice' }))); return { - findLogsForTask, + findLogFileRefsForTask, service: new ChangeExtractorService( { - findLogsForTask, + findLogFileRefsForTask, findMemberLogPaths: vi.fn(async () => []), } as any, { @@ -118,10 +118,10 @@ describe('ChangeExtractorService', () => { buildAssistantWriteEntry('tool-1', '/repo/src/file.ts', 'export const value = 1;\n', '2026-03-01T10:00:00.000Z'), ]); - const findLogsForTask = vi.fn(async (_teamName: string, _taskId: string, options?: any) => + const findLogFileRefsForTask = vi.fn(async (_teamName: string, _taskId: string, options?: any) => options?.owner === 'alice' ? [{ filePath: aliceLogPath, memberName: 'alice' }] : [] ); - const service = createService({ logPaths: [aliceLogPath], findLogsForTask }).service; + const service = createService({ logPaths: [aliceLogPath], findLogFileRefsForTask }).service; const empty = await service.getTaskChanges(TEAM_NAME, TASK_ID, { owner: 'bob', status: 'completed' }); const populated = await service.getTaskChanges(TEAM_NAME, TASK_ID, { @@ -131,7 +131,7 @@ describe('ChangeExtractorService', () => { expect(empty.files).toHaveLength(0); expect(populated.files).toHaveLength(1); - expect(findLogsForTask).toHaveBeenCalledTimes(2); + expect(findLogFileRefsForTask).toHaveBeenCalledTimes(2); }); it('caches terminal summary requests in memory but keeps detailed requests fresh', async () => { @@ -143,7 +143,7 @@ describe('ChangeExtractorService', () => { buildAssistantWriteEntry('tool-1', '/repo/src/file.ts', 'export const value = 1;\n', '2026-03-01T10:00:00.000Z'), ]); - const { service, findLogsForTask } = createService({ logPaths: [logPath] }); + const { service, findLogFileRefsForTask } = createService({ logPaths: [logPath] }); await service.getTaskChanges(TEAM_NAME, TASK_ID, SUMMARY_OPTIONS); await service.getTaskChanges(TEAM_NAME, TASK_ID, SUMMARY_OPTIONS); @@ -158,7 +158,7 @@ describe('ChangeExtractorService', () => { stateBucket: 'completed', }); - expect(findLogsForTask).toHaveBeenCalledTimes(3); + expect(findLogFileRefsForTask).toHaveBeenCalledTimes(3); }); it('restores a persisted terminal summary after a simulated restart', async () => { @@ -179,7 +179,9 @@ describe('ChangeExtractorService', () => { expect(initial.files).toHaveLength(1); expect(restored.files).toHaveLength(1); expect(await fs.readFile(persistedEntryPath(tmpDir), 'utf8')).toContain('"taskId": "1"'); - expect((second.findLogsForTask as any).mock.calls).toHaveLength(0); + // The second service restores from persisted cache; findLogFileRefsForTask may be called + // at most once for background validation (setTimeout(0) in schedulePersistedTaskChangeSummaryValidation) + expect((second.findLogFileRefsForTask as any).mock.calls.length).toBeLessThanOrEqual(1); }); it('forceFresh overwrites the persisted terminal summary snapshot', async () => { @@ -269,7 +271,7 @@ describe('ChangeExtractorService', () => { SUMMARY_OPTIONS ); - expect((drifted.findLogsForTask as any).mock.calls.length).toBeGreaterThan(1); + expect((drifted.findLogFileRefsForTask as any).mock.calls.length).toBeGreaterThan(1); }); it('rejects persisted summaries when the task file is missing on restart', async () => { @@ -324,7 +326,7 @@ describe('ChangeExtractorService', () => { const service = new ChangeExtractorService( { - findLogsForTask: vi.fn(async () => [{ filePath: logPath, memberName: 'alice' }]), + findLogFileRefsForTask: vi.fn(async () => [{ filePath: logPath, memberName: 'alice' }]), findMemberLogPaths: vi.fn(async () => []), } as any, { diff --git a/test/main/utils/costCalculation.test.ts b/test/main/utils/costCalculation.test.ts index 6f4bc076..c3340d1d 100644 --- a/test/main/utils/costCalculation.test.ts +++ b/test/main/utils/costCalculation.test.ts @@ -17,7 +17,7 @@ describe('Cost Calculation', () => { isMeta: false, timestamp: new Date(), content: [], - model: 'claude-3-5-sonnet-20241022', + model: 'claude-4-sonnet-20250514', usage: { input_tokens: 1000, output_tokens: 500, @@ -43,7 +43,7 @@ describe('Cost Calculation', () => { isMeta: false, timestamp: new Date(), content: [], - model: 'claude-3-5-sonnet-20241022', + model: 'claude-4-sonnet-20250514', usage: { input_tokens: 1000, output_tokens: 500, @@ -126,7 +126,7 @@ describe('Cost Calculation', () => { isMeta: false, timestamp: new Date(), content: [], - model: 'claude-3-5-sonnet-20241022', + model: 'claude-4-sonnet-20250514', usage: { input_tokens: 100_000, output_tokens: 50_000, @@ -154,7 +154,7 @@ describe('Cost Calculation', () => { isMeta: false, timestamp: new Date(), content: [], - model: 'claude-3-5-sonnet-20241022', + model: 'claude-3-opus-20240229', usage: { input_tokens: 250_000, output_tokens: 1_000, @@ -167,11 +167,11 @@ describe('Cost Calculation', () => { const metrics = calculateMetrics(messages); - // claude-3-5-sonnet-20241022 has no tiered rates in pricing.json, so base rates apply - // Input: 250000 * 0.000003 = 0.75 - // Output: 1000 * 0.000015 = 0.015 - // Total: 0.765 - expect(metrics.costUsd).toBeCloseTo(0.765, 6); + // claude-3-opus-20240229 has no tiered rates in pricing.json, so base rates apply + // Input: 250000 * 0.000015 = 3.75 + // Output: 1000 * 0.000075 = 0.075 + // Total: 3.825 + expect(metrics.costUsd).toBeCloseTo(3.825, 6); }); it('should use base rates for output tokens above 200k when model has no tiered pricing', () => { @@ -183,7 +183,7 @@ describe('Cost Calculation', () => { isMeta: false, timestamp: new Date(), content: [], - model: 'claude-3-5-sonnet-20241022', + model: 'claude-3-opus-20240229', usage: { input_tokens: 1_000, output_tokens: 250_000, @@ -197,10 +197,10 @@ describe('Cost Calculation', () => { const metrics = calculateMetrics(messages); // No tiered rates, so base rates for all tokens - // Input: 1000 * 0.000003 = 0.003 - // Output: 250000 * 0.000015 = 3.75 - // Total: 3.753 - expect(metrics.costUsd).toBeCloseTo(3.753, 6); + // Input: 1000 * 0.000015 = 0.015 + // Output: 250000 * 0.000075 = 18.75 + // Total: 18.765 + expect(metrics.costUsd).toBeCloseTo(18.765, 6); }); it('should use base rates for cache tokens above 200k when model has no tiered pricing', () => { @@ -212,7 +212,7 @@ describe('Cost Calculation', () => { isMeta: false, timestamp: new Date(), content: [], - model: 'claude-3-5-sonnet-20241022', + model: 'claude-3-opus-20240229', usage: { input_tokens: 1_000, output_tokens: 1_000, @@ -228,12 +228,12 @@ describe('Cost Calculation', () => { const metrics = calculateMetrics(messages); // No tiered rates for this model, so base rates apply - // Input: 1000 * 0.000003 = 0.003 - // Output: 1000 * 0.000015 = 0.015 - // Cache creation: 250000 * 0.00000375 = 0.9375 - // Cache read: 250000 * 0.0000003 = 0.075 - // Total: 1.0305 - expect(metrics.costUsd).toBeCloseTo(1.0305, 6); + // Input: 1000 * 0.000015 = 0.015 + // Output: 1000 * 0.000075 = 0.075 + // Cache creation: 250000 * 0.00001875 = 4.6875 + // Cache read: 250000 * 0.0000015 = 0.375 + // Total: 5.1525 + expect(metrics.costUsd).toBeCloseTo(5.1525, 6); }); it('should handle model without tiered pricing', () => { @@ -306,7 +306,7 @@ describe('Cost Calculation', () => { isMeta: false, timestamp: new Date(), content: [], - model: 'claude-3-5-sonnet-20241022', + model: 'claude-4-sonnet-20250514', usage: { input_tokens: 1000, output_tokens: 500, @@ -322,7 +322,7 @@ describe('Cost Calculation', () => { isMeta: false, timestamp: new Date(), content: [], - model: 'claude-3-5-sonnet-20241022', + model: 'claude-4-sonnet-20250514', usage: { input_tokens: 2000, output_tokens: 1000, @@ -350,7 +350,7 @@ describe('Cost Calculation', () => { isMeta: false, timestamp: new Date(), content: [], - model: 'claude-3-5-sonnet-20241022', + model: 'claude-4-sonnet-20250514', usage: { input_tokens: 1000, output_tokens: 500, @@ -397,7 +397,7 @@ describe('Cost Calculation', () => { isMeta: false, timestamp: new Date(), content: [], - model: 'claude-3-5-sonnet-20241022', + model: 'claude-4-sonnet-20250514', usage: { input_tokens: 0, output_tokens: 0, @@ -421,7 +421,7 @@ describe('Cost Calculation', () => { isMeta: false, timestamp: new Date(), content: [], - model: 'claude-3-5-sonnet-20241022', + model: 'claude-4-sonnet-20250514', toolCalls: [], toolResults: [], isSidechain: false, @@ -449,7 +449,7 @@ describe('Cost Calculation', () => { isMeta: false, timestamp: new Date(), content: [], - model: 'claude-3-5-sonnet-20241022', + model: 'claude-4-sonnet-20250514', usage: { input_tokens: 1000, output_tokens: 500, @@ -473,7 +473,7 @@ describe('Cost Calculation', () => { isMeta: false, timestamp: new Date(), content: [], - model: 'CLAUDE-3-5-SONNET-20241022', + model: 'CLAUDE-4-SONNET-20250514', usage: { input_tokens: 1000, output_tokens: 500, @@ -505,7 +505,7 @@ describe('Cost Calculation', () => { isMeta: false, timestamp: new Date(), content: [], - model: 'claude-3-5-sonnet-20241022', + model: 'claude-4-sonnet-20250514', usage: { input_tokens: 0, output_tokens: 0, @@ -540,7 +540,7 @@ describe('Cost Calculation', () => { isMeta: false, timestamp: new Date(), content: [], - model: 'claude-3-5-sonnet-20241022', + model: 'claude-3-opus-20240229', usage: { input_tokens: 0, output_tokens: 0, @@ -555,8 +555,8 @@ describe('Cost Calculation', () => { const metrics = calculateMetrics(messages); // No tiered rates for this model, so all 300k at base rate - // 300,000 * 0.0000003 = $0.09 - const expectedCost = 300000 * 0.0000003; + // 300,000 * 0.0000015 = $0.45 + const expectedCost = 300000 * 0.0000015; expect(metrics.costUsd).toBeCloseTo(expectedCost, 6); }); }); @@ -571,7 +571,7 @@ describe('Cost Calculation', () => { isMeta: false, timestamp: new Date(), content: [], - model: 'claude-3-5-sonnet-20241022', + model: 'claude-4-sonnet-20250514', usage: { input_tokens: 1000, output_tokens: 500, diff --git a/test/renderer/hooks/useMentionDetection.test.ts b/test/renderer/hooks/useMentionDetection.test.ts index c20780bc..83be0ff8 100644 --- a/test/renderer/hooks/useMentionDetection.test.ts +++ b/test/renderer/hooks/useMentionDetection.test.ts @@ -5,12 +5,12 @@ import { findMentionTrigger } from '@renderer/hooks/useMentionDetection'; describe('findMentionTrigger', () => { it('detects @query at start of text', () => { const result = findMentionTrigger('@ali', 4); - expect(result).toEqual({ triggerIndex: 0, query: 'ali' }); + expect(result).toEqual({ triggerIndex: 0, triggerChar: '@', query: 'ali' }); }); it('detects @query after space', () => { const result = findMentionTrigger('hello @bo', 9); - expect(result).toEqual({ triggerIndex: 6, query: 'bo' }); + expect(result).toEqual({ triggerIndex: 6, triggerChar: '@', query: 'bo' }); }); it('returns null for email-like @ (no space before)', () => { @@ -25,12 +25,12 @@ describe('findMentionTrigger', () => { it('returns empty query for bare @', () => { const result = findMentionTrigger('@', 1); - expect(result).toEqual({ triggerIndex: 0, query: '' }); + expect(result).toEqual({ triggerIndex: 0, triggerChar: '@', query: '' }); }); it('detects @ after newline', () => { const result = findMentionTrigger('text\n@ca', 8); - expect(result).toEqual({ triggerIndex: 5, query: 'ca' }); + expect(result).toEqual({ triggerIndex: 5, triggerChar: '@', query: 'ca' }); }); it('returns null for empty text', () => { @@ -40,7 +40,7 @@ describe('findMentionTrigger', () => { it('detects @ after tab', () => { const result = findMentionTrigger('hello\t@bob', 10); - expect(result).toEqual({ triggerIndex: 6, query: 'bob' }); + expect(result).toEqual({ triggerIndex: 6, triggerChar: '@', query: 'bob' }); }); it('returns null when cursor is at position 0', () => { @@ -50,12 +50,12 @@ describe('findMentionTrigger', () => { it('detects @ with empty query after space', () => { const result = findMentionTrigger('hello @', 7); - expect(result).toEqual({ triggerIndex: 6, query: '' }); + expect(result).toEqual({ triggerIndex: 6, triggerChar: '@', query: '' }); }); it('handles multiple @ signs - picks nearest valid one', () => { const result = findMentionTrigger('@alice hello @bo', 16); - expect(result).toEqual({ triggerIndex: 13, query: 'bo' }); + expect(result).toEqual({ triggerIndex: 13, triggerChar: '@', query: 'bo' }); }); it('returns null for @ in middle of word', () => { @@ -65,6 +65,6 @@ describe('findMentionTrigger', () => { it('detects @ after carriage return', () => { const result = findMentionTrigger('text\r\n@ca', 9); - expect(result).toEqual({ triggerIndex: 6, query: 'ca' }); + expect(result).toEqual({ triggerIndex: 6, triggerChar: '@', query: 'ca' }); }); }); diff --git a/test/renderer/utils/crossTeamPendingReplies.test.ts b/test/renderer/utils/crossTeamPendingReplies.test.ts index 437bd761..0d13d61d 100644 --- a/test/renderer/utils/crossTeamPendingReplies.test.ts +++ b/test/renderer/utils/crossTeamPendingReplies.test.ts @@ -15,126 +15,151 @@ function makeMessage(overrides: Partial = {}): InboxMessage { }; } +/** Anchor time just after the latest timestamp used in tests, within the 10s TTL window. */ +const NOW_MS = Date.parse('2026-03-09T12:10:05.000Z'); + describe('computePendingCrossTeamReplies', () => { it('returns pending entry for outbound cross-team message without reply', () => { - const result = computePendingCrossTeamReplies([ - makeMessage({ - conversationId: 'conv-1', - source: 'cross_team_sent', - to: 'team-best.team-lead', - timestamp: '2026-03-09T12:00:00.000Z', - }), - ]); + const sentAt = '2026-03-09T12:10:00.000Z'; + const result = computePendingCrossTeamReplies( + [ + makeMessage({ + conversationId: 'conv-1', + source: 'cross_team_sent', + to: 'team-best.team-lead', + timestamp: sentAt, + }), + ], + NOW_MS + ); expect(result).toEqual([ { conversationId: 'conv-1', teamName: 'team-best', - sentAtMs: Date.parse('2026-03-09T12:00:00.000Z'), + sentAtMs: Date.parse(sentAt), }, ]); }); it('clears pending entry when a newer cross-team reply arrives in the same conversation', () => { - const result = computePendingCrossTeamReplies([ - makeMessage({ - conversationId: 'conv-1', - source: 'cross_team_sent', - to: 'team-best.team-lead', - timestamp: '2026-03-09T12:00:00.000Z', - }), - makeMessage({ - conversationId: 'conv-1', - replyToConversationId: 'conv-1', - from: 'team-best.team-lead', - source: 'cross_team', - timestamp: '2026-03-09T12:05:00.000Z', - messageId: 'msg-2', - }), - ]); + const result = computePendingCrossTeamReplies( + [ + makeMessage({ + conversationId: 'conv-1', + source: 'cross_team_sent', + to: 'team-best.team-lead', + timestamp: '2026-03-09T12:10:00.000Z', + }), + makeMessage({ + conversationId: 'conv-1', + replyToConversationId: 'conv-1', + from: 'team-best.team-lead', + source: 'cross_team', + timestamp: '2026-03-09T12:10:05.000Z', + messageId: 'msg-2', + }), + ], + NOW_MS + ); expect(result).toEqual([]); }); it('keeps pending entry when the latest outbound is newer than the last reply', () => { - const result = computePendingCrossTeamReplies([ - makeMessage({ - conversationId: 'conv-1', - replyToConversationId: 'conv-1', - from: 'team-best.team-lead', - source: 'cross_team', - timestamp: '2026-03-09T12:05:00.000Z', - messageId: 'msg-1-reply', - }), - makeMessage({ - conversationId: 'conv-1', - source: 'cross_team_sent', - to: 'team-best.team-lead', - timestamp: '2026-03-09T12:10:00.000Z', - messageId: 'msg-2', - }), - ]); + const sentAt = '2026-03-09T12:10:03.000Z'; + const result = computePendingCrossTeamReplies( + [ + makeMessage({ + conversationId: 'conv-1', + replyToConversationId: 'conv-1', + from: 'team-best.team-lead', + source: 'cross_team', + timestamp: '2026-03-09T12:10:00.000Z', + messageId: 'msg-1-reply', + }), + makeMessage({ + conversationId: 'conv-1', + source: 'cross_team_sent', + to: 'team-best.team-lead', + timestamp: sentAt, + messageId: 'msg-2', + }), + ], + NOW_MS + ); expect(result).toEqual([ { conversationId: 'conv-1', teamName: 'team-best', - sentAtMs: Date.parse('2026-03-09T12:10:00.000Z'), + sentAtMs: Date.parse(sentAt), }, ]); }); it('keeps a pending conversation even when another team message arrives in a different conversation', () => { - const result = computePendingCrossTeamReplies([ - makeMessage({ - conversationId: 'conv-1', - source: 'cross_team_sent', - to: 'team-best.team-lead', - timestamp: '2026-03-09T12:00:00.000Z', - }), - makeMessage({ - conversationId: 'conv-2', - from: 'team-best.team-lead', - source: 'cross_team', - timestamp: '2026-03-09T12:05:00.000Z', - messageId: 'msg-2', - }), - ]); + const sentAt = '2026-03-09T12:10:00.000Z'; + const result = computePendingCrossTeamReplies( + [ + makeMessage({ + conversationId: 'conv-1', + source: 'cross_team_sent', + to: 'team-best.team-lead', + timestamp: sentAt, + }), + makeMessage({ + conversationId: 'conv-2', + from: 'team-best.team-lead', + source: 'cross_team', + timestamp: '2026-03-09T12:10:05.000Z', + messageId: 'msg-2', + }), + ], + NOW_MS + ); expect(result).toEqual([ { conversationId: 'conv-1', teamName: 'team-best', - sentAtMs: Date.parse('2026-03-09T12:00:00.000Z'), + sentAtMs: Date.parse(sentAt), }, ]); }); it('ignores non-cross-team messages', () => { - const result = computePendingCrossTeamReplies([ - makeMessage({ - from: 'alice', - to: 'team-lead', - timestamp: '2026-03-09T12:00:00.000Z', - }), - ]); + const result = computePendingCrossTeamReplies( + [ + makeMessage({ + from: 'alice', + to: 'team-lead', + timestamp: '2026-03-09T12:10:00.000Z', + }), + ], + NOW_MS + ); expect(result).toEqual([]); }); it('falls back to legacy team-level matching when conversationId is missing', () => { - const result = computePendingCrossTeamReplies([ - makeMessage({ - source: 'cross_team_sent', - to: 'team-best.team-lead', - timestamp: '2026-03-09T12:00:00.000Z', - }), - ]); + const sentAt = '2026-03-09T12:10:00.000Z'; + const result = computePendingCrossTeamReplies( + [ + makeMessage({ + source: 'cross_team_sent', + to: 'team-best.team-lead', + timestamp: sentAt, + }), + ], + NOW_MS + ); expect(result).toEqual([ { teamName: 'team-best', - sentAtMs: Date.parse('2026-03-09T12:00:00.000Z'), + sentAtMs: Date.parse(sentAt), }, ]); }); diff --git a/test/shared/utils/pricing.test.ts b/test/shared/utils/pricing.test.ts index f8a807b9..b711e17c 100644 --- a/test/shared/utils/pricing.test.ts +++ b/test/shared/utils/pricing.test.ts @@ -9,14 +9,14 @@ import { describe('Shared Pricing Module', () => { describe('getPricing', () => { it('should find pricing by exact model name', () => { - const pricing = getPricing('claude-3-5-sonnet-20241022'); + const pricing = getPricing('claude-4-sonnet-20250514'); expect(pricing).not.toBeNull(); expect(pricing!.input_cost_per_token).toBeGreaterThan(0); expect(pricing!.output_cost_per_token).toBeGreaterThan(0); }); it('should find pricing case-insensitively', () => { - const pricing = getPricing('Claude-3-5-Sonnet-20241022'); + const pricing = getPricing('Claude-4-Sonnet-20250514'); expect(pricing).not.toBeNull(); }); @@ -50,7 +50,7 @@ describe('Shared Pricing Module', () => { describe('calculateMessageCost', () => { it('should compute cost for a known model', () => { - const cost = calculateMessageCost('claude-3-5-sonnet-20241022', 1000, 500, 0, 0); + const cost = calculateMessageCost('claude-4-sonnet-20250514', 1000, 500, 0, 0); expect(cost).toBeCloseTo(0.0105, 6); }); @@ -65,14 +65,14 @@ describe('Shared Pricing Module', () => { }); it('should include cache token costs', () => { - const cost = calculateMessageCost('claude-3-5-sonnet-20241022', 1000, 500, 300, 200); + const cost = calculateMessageCost('claude-4-sonnet-20250514', 1000, 500, 300, 200); expect(cost).toBeGreaterThan(0.0105); }); }); describe('getDisplayPricing', () => { it('should return per-million rates for a known model', () => { - const dp = getDisplayPricing('claude-3-5-sonnet-20241022'); + const dp = getDisplayPricing('claude-4-sonnet-20250514'); expect(dp).not.toBeNull(); expect(dp!.input).toBeCloseTo(3.0, 1); expect(dp!.output).toBeCloseTo(15.0, 1);