From 71e88fd267e6a573b4d52dfc617f7d9a900744de Mon Sep 17 00:00:00 2001 From: iliya Date: Tue, 3 Mar 2026 13:39:02 +0200 Subject: [PATCH] refactor: enhance team model selection logic and improve test setup - Introduced a new utility function, computeEffectiveTeamModel, to streamline the logic for determining the effective model based on selected options and context. - Updated CreateTeamDialog and LaunchTeamDialog components to utilize the new utility for cleaner code. - Refactored TeamProvisioningService test to improve type handling for child processes, ensuring compatibility with vitest. Made-with: Cursor --- resources/pricing.json | 96 ++++++++++++++----- .../team/dialogs/CreateTeamDialog.tsx | 13 +-- .../team/dialogs/LaunchTeamDialog.tsx | 9 +- .../team/dialogs/TeamModelSelector.tsx | 15 +++ .../team/TeamProvisioningService.test.ts | 16 ++-- 5 files changed, 102 insertions(+), 47 deletions(-) diff --git a/resources/pricing.json b/resources/pricing.json index 02a106c6..85868da3 100644 --- a/resources/pricing.json +++ b/resources/pricing.json @@ -160,7 +160,9 @@ "supports_pdf_input": true, "supports_response_schema": true, "supports_tool_choice": true, - "supports_vision": true + "supports_vision": true, + "cache_read_input_token_cost": 2.5e-8, + "cache_creation_input_token_cost": 3.125e-7 }, "anthropic.claude-3-opus-20240229-v1:0": { "input_cost_per_token": 0.000015, @@ -173,7 +175,9 @@ "supports_function_calling": true, "supports_response_schema": true, "supports_tool_choice": true, - "supports_vision": true + "supports_vision": true, + "cache_read_input_token_cost": 0.0000015, + "cache_creation_input_token_cost": 0.00001875 }, "anthropic.claude-3-sonnet-20240229-v1:0": { "input_cost_per_token": 0.000003, @@ -187,7 +191,9 @@ "supports_pdf_input": true, "supports_response_schema": true, "supports_tool_choice": true, - "supports_vision": true + "supports_vision": true, + "cache_read_input_token_cost": 3e-7, + "cache_creation_input_token_cost": 0.00000375 }, "anthropic.claude-instant-v1": { "input_cost_per_token": 8e-7, @@ -668,7 +674,9 @@ "supports_pdf_input": true, "supports_response_schema": true, "supports_tool_choice": true, - "supports_vision": true + "supports_vision": true, + "cache_read_input_token_cost": 3e-7, + "cache_creation_input_token_cost": 0.00000375 }, "apac.anthropic.claude-3-5-sonnet-20241022-v2:0": { "cache_creation_input_token_cost": 0.00000375, @@ -701,7 +709,9 @@ "supports_pdf_input": true, "supports_response_schema": true, "supports_tool_choice": true, - "supports_vision": true + "supports_vision": true, + "cache_read_input_token_cost": 2.5e-8, + "cache_creation_input_token_cost": 3.125e-7 }, "apac.anthropic.claude-haiku-4-5-20251001-v1:0": { "cache_creation_input_token_cost": 0.000001375, @@ -737,7 +747,9 @@ "supports_pdf_input": true, "supports_response_schema": true, "supports_tool_choice": true, - "supports_vision": true + "supports_vision": true, + "cache_read_input_token_cost": 3e-7, + "cache_creation_input_token_cost": 0.00000375 }, "apac.anthropic.claude-sonnet-4-20250514-v1:0": { "cache_creation_input_token_cost": 0.00000375, @@ -1005,7 +1017,9 @@ "supports_function_calling": true, "supports_response_schema": true, "supports_tool_choice": true, - "supports_vision": true + "supports_vision": true, + "cache_read_input_token_cost": 3e-7, + "cache_creation_input_token_cost": 0.00000375 }, "bedrock/us-east-1/anthropic.claude-instant-v1": { "input_cost_per_token": 8e-7, @@ -1049,7 +1063,9 @@ "supports_pdf_input": true, "supports_response_schema": true, "supports_tool_choice": true, - "supports_vision": true + "supports_vision": true, + "cache_read_input_token_cost": 3.6e-7, + "cache_creation_input_token_cost": 0.0000045 }, "bedrock/us-gov-east-1/anthropic.claude-3-haiku-20240307-v1:0": { "input_cost_per_token": 3e-7, @@ -1063,7 +1079,9 @@ "supports_pdf_input": true, "supports_response_schema": true, "supports_tool_choice": true, - "supports_vision": true + "supports_vision": true, + "cache_read_input_token_cost": 3e-8, + "cache_creation_input_token_cost": 3.75e-7 }, "bedrock/us-gov-east-1/claude-sonnet-4-5-20250929-v1:0": { "input_cost_per_token": 0.0000033, @@ -1081,7 +1099,9 @@ "supports_reasoning": true, "supports_response_schema": true, "supports_tool_choice": true, - "supports_vision": true + "supports_vision": true, + "cache_read_input_token_cost": 3.3e-7, + "cache_creation_input_token_cost": 0.000004125 }, "bedrock/us-gov-west-1/anthropic.claude-3-7-sonnet-20250219-v1:0": { "cache_creation_input_token_cost": 0.0000045, @@ -1115,7 +1135,9 @@ "supports_pdf_input": true, "supports_response_schema": true, "supports_tool_choice": true, - "supports_vision": true + "supports_vision": true, + "cache_read_input_token_cost": 3.6e-7, + "cache_creation_input_token_cost": 0.0000045 }, "bedrock/us-gov-west-1/anthropic.claude-3-haiku-20240307-v1:0": { "input_cost_per_token": 3e-7, @@ -1129,7 +1151,9 @@ "supports_pdf_input": true, "supports_response_schema": true, "supports_tool_choice": true, - "supports_vision": true + "supports_vision": true, + "cache_read_input_token_cost": 3e-8, + "cache_creation_input_token_cost": 3.75e-7 }, "bedrock/us-gov-west-1/claude-sonnet-4-5-20250929-v1:0": { "input_cost_per_token": 0.0000033, @@ -1147,7 +1171,9 @@ "supports_reasoning": true, "supports_response_schema": true, "supports_tool_choice": true, - "supports_vision": true + "supports_vision": true, + "cache_read_input_token_cost": 3.3e-7, + "cache_creation_input_token_cost": 0.000004125 }, "bedrock/us-west-2/anthropic.claude-instant-v1": { "input_cost_per_token": 8e-7, @@ -2093,7 +2119,9 @@ "supports_pdf_input": true, "supports_prompt_caching": true, "supports_response_schema": true, - "supports_tool_choice": true + "supports_tool_choice": true, + "cache_read_input_token_cost": 2.5e-8, + "cache_creation_input_token_cost": 3.125e-7 }, "eu.anthropic.claude-haiku-4-5-20251001-v1:0": { "cache_creation_input_token_cost": 0.000001375, @@ -2130,7 +2158,9 @@ "supports_pdf_input": true, "supports_response_schema": true, "supports_tool_choice": true, - "supports_vision": true + "supports_vision": true, + "cache_read_input_token_cost": 3e-7, + "cache_creation_input_token_cost": 0.00000375 }, "eu.anthropic.claude-3-5-sonnet-20241022-v2:0": { "input_cost_per_token": 0.000003, @@ -2147,7 +2177,9 @@ "supports_prompt_caching": true, "supports_response_schema": true, "supports_tool_choice": true, - "supports_vision": true + "supports_vision": true, + "cache_read_input_token_cost": 3e-7, + "cache_creation_input_token_cost": 0.00000375 }, "eu.anthropic.claude-3-7-sonnet-20250219-v1:0": { "input_cost_per_token": 0.000003, @@ -2165,7 +2197,9 @@ "supports_reasoning": true, "supports_response_schema": true, "supports_tool_choice": true, - "supports_vision": true + "supports_vision": true, + "cache_read_input_token_cost": 3e-7, + "cache_creation_input_token_cost": 0.00000375 }, "eu.anthropic.claude-3-haiku-20240307-v1:0": { "input_cost_per_token": 2.5e-7, @@ -2179,7 +2213,9 @@ "supports_pdf_input": true, "supports_response_schema": true, "supports_tool_choice": true, - "supports_vision": true + "supports_vision": true, + "cache_read_input_token_cost": 2.5e-8, + "cache_creation_input_token_cost": 3.125e-7 }, "eu.anthropic.claude-3-opus-20240229-v1:0": { "input_cost_per_token": 0.000015, @@ -2192,7 +2228,9 @@ "supports_function_calling": true, "supports_response_schema": true, "supports_tool_choice": true, - "supports_vision": true + "supports_vision": true, + "cache_read_input_token_cost": 0.0000015, + "cache_creation_input_token_cost": 0.00001875 }, "eu.anthropic.claude-3-sonnet-20240229-v1:0": { "input_cost_per_token": 0.000003, @@ -2206,7 +2244,9 @@ "supports_pdf_input": true, "supports_response_schema": true, "supports_tool_choice": true, - "supports_vision": true + "supports_vision": true, + "cache_read_input_token_cost": 3e-7, + "cache_creation_input_token_cost": 0.00000375 }, "eu.anthropic.claude-opus-4-1-20250805-v1:0": { "cache_creation_input_token_cost": 0.00001875, @@ -2872,7 +2912,9 @@ "supports_pdf_input": true, "supports_response_schema": true, "supports_tool_choice": true, - "supports_vision": true + "supports_vision": true, + "cache_read_input_token_cost": 3e-7, + "cache_creation_input_token_cost": 0.00000375 }, "us.anthropic.claude-3-5-sonnet-20241022-v2:0": { "cache_creation_input_token_cost": 0.00000375, @@ -2925,7 +2967,9 @@ "supports_pdf_input": true, "supports_response_schema": true, "supports_tool_choice": true, - "supports_vision": true + "supports_vision": true, + "cache_read_input_token_cost": 2.5e-8, + "cache_creation_input_token_cost": 3.125e-7 }, "us.anthropic.claude-3-opus-20240229-v1:0": { "input_cost_per_token": 0.000015, @@ -2938,7 +2982,9 @@ "supports_function_calling": true, "supports_response_schema": true, "supports_tool_choice": true, - "supports_vision": true + "supports_vision": true, + "cache_read_input_token_cost": 0.0000015, + "cache_creation_input_token_cost": 0.00001875 }, "us.anthropic.claude-3-sonnet-20240229-v1:0": { "input_cost_per_token": 0.000003, @@ -2952,7 +2998,9 @@ "supports_pdf_input": true, "supports_response_schema": true, "supports_tool_choice": true, - "supports_vision": true + "supports_vision": true, + "cache_read_input_token_cost": 3e-7, + "cache_creation_input_token_cost": 0.00000375 }, "us.anthropic.claude-opus-4-1-20250805-v1:0": { "cache_creation_input_token_cost": 0.00001875, diff --git a/src/renderer/components/team/dialogs/CreateTeamDialog.tsx b/src/renderer/components/team/dialogs/CreateTeamDialog.tsx index d6e4ccdb..853be867 100644 --- a/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +++ b/src/renderer/components/team/dialogs/CreateTeamDialog.tsx @@ -31,7 +31,7 @@ import { AlertTriangle, CheckCircle2, Loader2 } from 'lucide-react'; import { ExtendedContextCheckbox } from './ExtendedContextCheckbox'; import { ProjectPathSelector } from './ProjectPathSelector'; -import { TeamModelSelector } from './TeamModelSelector'; +import { computeEffectiveTeamModel, TeamModelSelector } from './TeamModelSelector'; import type { MemberDraft } from '@renderer/components/team/members/membersEditorTypes'; @@ -445,13 +445,10 @@ export const CreateTeamDialog = ({ [members] ); - const effectiveModel = useMemo(() => { - const base = selectedModel || undefined; - if (!extendedContext) return base; - // 1M context is only supported for opus and sonnet - if (base === 'haiku') return base; - return base ? `${base}[1m]` : 'sonnet[1m]'; - }, [selectedModel, extendedContext]); + const effectiveModel = useMemo( + () => computeEffectiveTeamModel(selectedModel, extendedContext), + [selectedModel, extendedContext] + ); const sanitizedTeamName = sanitizeTeamName(teamName.trim()); diff --git a/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx b/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx index 06a0c429..a452edcf 100644 --- a/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +++ b/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx @@ -22,7 +22,7 @@ import { normalizePath } from '@renderer/utils/pathNormalize'; import { AlertTriangle, CheckCircle2, Loader2, RotateCcw } from 'lucide-react'; import { ProjectPathSelector } from './ProjectPathSelector'; -import { TeamModelSelector } from './TeamModelSelector'; +import { computeEffectiveTeamModel, TeamModelSelector } from './TeamModelSelector'; import type { ActiveTeamRef } from './CreateTeamDialog'; import type { MentionSuggestion } from '@renderer/types/mention'; @@ -256,12 +256,7 @@ export const LaunchTeamDialog = ({ teamName, cwd: effectiveCwd, prompt: promptDraft.value.trim() || undefined, - model: (() => { - if (!extendedContext) return selectedModel || undefined; - // 1M context is only supported for opus and sonnet - if (selectedModel === 'haiku') return selectedModel; - return selectedModel ? `${selectedModel}[1m]` : 'sonnet[1m]'; - })(), + model: computeEffectiveTeamModel(selectedModel, extendedContext), clearContext: clearContext || undefined, }); resetFormState(); diff --git a/src/renderer/components/team/dialogs/TeamModelSelector.tsx b/src/renderer/components/team/dialogs/TeamModelSelector.tsx index 607b244d..cb38faa5 100644 --- a/src/renderer/components/team/dialogs/TeamModelSelector.tsx +++ b/src/renderer/components/team/dialogs/TeamModelSelector.tsx @@ -10,6 +10,21 @@ const MODEL_OPTIONS = [ { value: 'haiku', label: 'Haiku 4.5' }, ] as const; +/** + * Computes the effective model string for team provisioning. + * - Without extended context: returns base model or undefined. + * - With extended context: haiku stays as-is; opus/sonnet get [1m] suffix; default → sonnet[1m]. + */ +export function computeEffectiveTeamModel( + selectedModel: string, + extendedContext: boolean +): string | undefined { + const base = selectedModel || undefined; + if (!extendedContext) return base; + if (base === 'haiku') return base; + return base ? `${base}[1m]` : 'sonnet[1m]'; +} + export interface TeamModelSelectorProps { value: string; onValueChange: (value: string) => void; diff --git a/test/main/services/team/TeamProvisioningService.test.ts b/test/main/services/team/TeamProvisioningService.test.ts index 9915cf97..e17724bd 100644 --- a/test/main/services/team/TeamProvisioningService.test.ts +++ b/test/main/services/team/TeamProvisioningService.test.ts @@ -1,3 +1,4 @@ +import type { ChildProcess } from 'child_process'; import { EventEmitter } from 'events'; import { beforeEach, describe, expect, it, vi } from 'vitest'; @@ -19,13 +20,12 @@ function allowConsoleLogs() { vi.spyOn(console, 'warn').mockImplementation(() => {}); } -function createFakeChild(exitCode: number): NodeJS.Process { - const child = new EventEmitter() as NodeJS.Process & { - stdout: EventEmitter; - stderr: EventEmitter; - }; - child.stdout = new EventEmitter(); - child.stderr = new EventEmitter(); +function createFakeChild(exitCode: number): ChildProcess { + const child = Object.assign(new EventEmitter(), { + stdout: null, + stderr: null, + stdin: null, + }) as unknown as ChildProcess; setImmediate(() => child.emit('close', exitCode)); return child; } @@ -45,7 +45,7 @@ describe('TeamProvisioningService', () => { if (callCount === 1) { throw new Error('spawn EINVAL'); } - return createFakeChild(0) as ReturnType; + return createFakeChild(0); }); const svc = new TeamProvisioningService();