Skip to content

Commit 9b45382

Browse files
feat: add configurable Claude model selection
- Add support for multiple Claude models (3.5/3.7 Sonnet, 4 Sonnet/Opus) - Implement smart planner model selection based on main model - Add conditional thinking capabilities (disabled for 3.5 Sonnet) - Enhance cost tracking with model-specific pricing - Add new settings commands: --set-model and --list-models - Include cost warnings for expensive models (4 Opus) - Update documentation with model configuration guide 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 1941a4e commit 9b45382

10 files changed

Lines changed: 369 additions & 53 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@
1010

1111
### Latest Changes (since v1.2)
1212

13+
- **Configurable Models**: Added support for user-selectable Claude models
14+
- Support for Claude 3.5 Sonnet, 3.7 Sonnet, 4 Sonnet, and 4 Opus
15+
- Smart planner model selection based on main model choice
16+
- Conditional thinking capabilities (disabled for 3.5 Sonnet)
17+
- Multi-model cost tracking with model-specific pricing
18+
- New settings commands: `--set-model` and `--list-models`
19+
- Cost warnings for expensive models (4 Opus)
1320
- Added new export functionality with prompt ID-based system
1421
- Enhanced session logging capabilities
1522
- Refactored user settings management for improved efficiency

README.md

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ bash command execution capabilities using Claude 3 API.
1111
- Editor mode for AI-assisted text file manipulation
1212
- Bash mode for intelligent command execution
1313
- Hybrid mode for combined capabilities
14+
- **Configurable AI Models**:
15+
- Support for multiple Claude models (3.5 Sonnet, 3.7 Sonnet, 4 Sonnet, 4 Opus)
16+
- Smart planner model selection based on main model choice
17+
- Model-specific cost tracking and pricing
18+
- Easy model switching via settings commands
1419
- **Enhanced Export System**:
1520
- Prompt ID-based export functionality
1621
- Dedicated export paths
@@ -24,8 +29,11 @@ bash command execution capabilities using Claude 3 API.
2429
- **Comprehensive Logging**
2530
- Text response content tracking
2631
- Debug logging for history entries
27-
- Token usage tracking
28-
- **Cost Calculation**
32+
- Multi-model token usage tracking
33+
- **Advanced Cost Calculation**
34+
- Model-specific pricing
35+
- Per-model cost breakdown
36+
- Cost warnings for expensive models
2937
- **History Management** with database integration
3038
- **Clipboard Management** with cross-platform support
3139
- **Tool Configuration Management**
@@ -90,6 +98,32 @@ deno run -A src/main.ts --mode=bash --no-agi "your command"
9098
./build/ComputerUseAgent --export "prompt-id" # Export session data
9199
```
92100

101+
### Model Configuration
102+
103+
Configure which Claude model to use for AI operations:
104+
105+
```sh
106+
# Set model to Claude 4 Sonnet
107+
deno run -A src/main.ts settings --set-model "4-sonnet"
108+
109+
# List available models
110+
deno run -A src/main.ts settings --list-models
111+
112+
# View current settings including selected model
113+
deno run -A src/main.ts settings --list
114+
```
115+
116+
**Available Models:**
117+
- `3.5-sonnet` - Claude 3.5 Sonnet (Default, most cost-effective)
118+
- `3.7-sonnet` - Claude 3.7 Sonnet (Enhanced reasoning capabilities)
119+
- `4-sonnet` - Claude 4 Sonnet (Latest generation)
120+
- `4-opus` - Claude 4 Opus (Most capable, higher cost)
121+
122+
**Smart Features:**
123+
- Automatic planner model selection based on your chosen model
124+
- Model-specific cost tracking and warnings
125+
- Thinking capabilities automatically enabled for supported models
126+
93127
## Project Structure
94128

95129
- `src/`: Source code directory

plans/configurableModels.md

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# Configurable Models Implementation Plan
2+
3+
## Overview
4+
Add user-configurable model selection to ComputerUseAgent, allowing users to choose between different Claude models via settings.
5+
6+
## Supported Models
7+
- `claude-3-5-sonnet-20241022` (3.5 Sonnet) - Default
8+
- `claude-3-7-sonnet-20250219` (3.7 Sonnet)
9+
- `claude-sonnet-4-20250514` (4 Sonnet)
10+
- `claude-opus-4-20250514` (4 Opus)
11+
12+
## Implementation Steps
13+
14+
### 1. Update UserSettings Interface
15+
- Add `model?: string` field to `UserSettings` interface in `src/types/interfaces.ts`
16+
- Set default model to current model (`claude-3-5-sonnet-20241022`)
17+
18+
### 2. Update Settings Configuration
19+
- Modify `DEFAULT_SETTINGS` in `src/config/settings.ts` to include default model
20+
- Add helper function `getSelectedModel()` to retrieve user's model preference
21+
- Add model validation function to ensure only supported models are allowed
22+
23+
### 3. Update Constants Configuration
24+
- Modify `API_CONFIG` in `src/config/constants.ts` to use configurable model
25+
- Replace hardcoded `MODEL` with dynamic model selection
26+
- **Smart Planner Model Selection**: Configure `REASONING_MODEL` based on main model:
27+
- If main model is 3.5 Sonnet → Use 3.7 Sonnet for planning BUT exclude thinking budget
28+
- If main model is 3.7 Sonnet → Use 3.7 Sonnet for planning with thinking capabilities
29+
- If main model is 4 Sonnet/Opus → Use same model for planning with full thinking capabilities
30+
- Keep `INTENT_MODEL` (Haiku) separate as it serves specific purposes
31+
32+
### 4. Update Settings Command
33+
- Add `--set-model` flag to settings command in `src/commands/settings.ts`
34+
- Add `--list-models` flag to show available models
35+
- Include model validation when setting new model
36+
- Update help text to include model configuration options
37+
38+
### 5. Update Session Classes
39+
- Modify classes that use `API_CONFIG.MODEL` to dynamically get model from settings
40+
- Ensure all Claude API calls use the configured model
41+
- **Implement Thinking Budget Control**: Modify planner to conditionally include thinking based on model
42+
- **Update cost tracking calls**: Add model name parameter to updateTokenUsage() calls
43+
- Key files to update:
44+
- `src/modules/hybrid/hybrid_session.ts` - Update token tracking with model names
45+
- `src/modules/planner/planner.ts` - Add logic to exclude thinking budget for 3.5 Sonnet, track planning model costs
46+
- Any other files that directly reference `API_CONFIG.MODEL`
47+
48+
### 6. Update Cost Tracking Infrastructure
49+
- **SessionLogger class**: Add model-specific token tracking
50+
- **Database schema**: Keep existing cost field for backward compatibility
51+
- **Cost calculation**: Create model pricing lookup function
52+
- **API call sites**: Pass model name to updateTokenUsage() throughout codebase
53+
54+
## Model Mapping
55+
Create a mapping between user-friendly names and actual model identifiers:
56+
```typescript
57+
const MODEL_MAP = {
58+
"3.5-sonnet": "claude-3-5-sonnet-20241022",
59+
"3.7-sonnet": "claude-3-7-sonnet-20250219",
60+
"4-sonnet": "claude-sonnet-4-20250514",
61+
"4-opus": "claude-opus-4-20250514"
62+
}
63+
```
64+
65+
## Usage Examples
66+
```bash
67+
# Set model to 4 Sonnet
68+
deno run -A src/main.ts settings --set-model "4-sonnet"
69+
70+
# List available models
71+
deno run -A src/main.ts settings --list-models
72+
73+
# View current settings including model
74+
deno run -A src/main.ts settings --list
75+
```
76+
77+
## Cost Tracking and Reporting
78+
79+
### Model Pricing (as of May 2025)
80+
Based on current Anthropic pricing:
81+
82+
| Model | Input Price (per 1M tokens) | Output Price (per 1M tokens) | With Caching Input (90% off) | With Caching Output (90% off) |
83+
|-------|----------------------------|------------------------------|------------------------------|-------------------------------|
84+
| 3.5 Sonnet | $3.00 | $15.00 | $0.30 | $1.50 |
85+
| 3.7 Sonnet | $3.00 | $15.00 | $0.30 | $1.50 |
86+
| **4 Sonnet** | **$3.00** | **$15.00** | **$0.30** | **$1.50** |
87+
| **4 Opus** | **$15.00** | **$75.00** | **$1.50** | **$7.50** |
88+
89+
### Current Cost Implementation Analysis
90+
**Existing Cost Tracking (SessionLogger in src/utils/session.ts):**
91+
1. **Hard-coded pricing**: Uses fixed $3/$15 per million tokens (3.5/3.7 Sonnet pricing)
92+
2. **Single model tracking**: Only tracks one set of tokens per session
93+
3. **Simple aggregation**: Combines all token usage regardless of which model was used
94+
4. **Database storage**: Saves total cost per session to SQLite database
95+
96+
**Current Limitations:**
97+
1. **No model-specific costs**: All API calls treated as same model pricing
98+
2. **Planning model invisible**: REASONING_MODEL usage not tracked separately
99+
3. **Fixed pricing**: Doesn't account for different model costs (4 Opus is 5x more expensive)
100+
4. **No per-model breakdown**: Can't see which model consumed which tokens
101+
102+
### Enhanced Cost Tracking Implementation
103+
**Update SessionLogger class (src/utils/session.ts):**
104+
1. **Dynamic pricing**: Replace hard-coded costs with model-specific pricing lookup
105+
2. **Multi-model tracking**: Track tokens per model type (main vs planning)
106+
3. **Cost breakdown**: Maintain separate cost calculations for each model
107+
4. **Backward compatibility**: Keep existing getTotalCost() for database storage
108+
109+
**Changes needed in SessionLogger:**
110+
```typescript
111+
class SessionLogger {
112+
private modelTokenUsage = new Map<string, {input: number, output: number}>()
113+
114+
updateTokenUsage(inputTokens: number, outputTokens: number, modelName: string): void
115+
getModelCosts(): Map<string, number>
116+
getTotalCost(): number // Updated to sum all model costs
117+
}
118+
```
119+
120+
**Update cost calculation methods:**
121+
- Replace hard-coded $3/$15 with dynamic pricing based on model
122+
- Add model parameter to updateTokenUsage() calls
123+
- Modify getTotalCost() to sum costs across all models used
124+
125+
## Validation Rules
126+
- Only allow predefined model names
127+
- Provide clear error messages for invalid models
128+
- Fall back to default model if configured model becomes invalid
129+
- Warn users about cost implications when selecting expensive models
130+
131+
## Backward Compatibility
132+
- Existing users without model setting will use default (3.5 Sonnet)
133+
- No breaking changes to existing functionality
134+
- Settings file will be automatically updated with default model on first access
135+
- Cost tracking will start from implementation date forward

src/commands/settings.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import {parseArgs} from "jsr:@std/cli/parse-args"
2-
import {loadUserSettings, saveUserSettings} from "../config/settings.ts"
2+
import {loadUserSettings, saveUserSettings, validateModel, getAvailableModels} from "../config/settings.ts"
33
import {parseFlagForHelp} from "../utils/functions.ts"
44

55
export async function handleSettings(args: string[]): Promise<void> {
66
const settingsFlags = {
7-
string: ["set-name", "set-jina-key", "set-config", "set-editor"],
8-
boolean: ["list"],
7+
string: ["set-name", "set-jina-key", "set-config", "set-editor", "set-model"],
8+
boolean: ["list", "list-models"],
99
}
1010
const flags = parseArgs(args, settingsFlags)
1111

@@ -26,6 +26,30 @@ export async function handleSettings(args: string[]): Promise<void> {
2626
settings.jinaApiKey = flags["set-jina-key"]
2727
console.log("Jina API key has been set")
2828
}
29+
else if (flags["list-models"]) {
30+
const models = getAvailableModels()
31+
console.log("Available models:")
32+
models.forEach(model => {
33+
const current = settings.model === model ? " (current)" : ""
34+
console.log(` ${model}${current}`)
35+
})
36+
return
37+
}
38+
else if (flags["set-model"]) {
39+
const model = flags["set-model"]
40+
if (!validateModel(model)) {
41+
console.error(`Invalid model: ${model}`)
42+
console.log("Available models:", getAvailableModels().join(", "))
43+
return
44+
}
45+
settings.model = model
46+
console.log(`Model set to: ${model}`)
47+
48+
// Warn about cost implications for expensive models
49+
if (model === "4-opus") {
50+
console.log("⚠️ Warning: 4 Opus is significantly more expensive than other models")
51+
}
52+
}
2953
else if (flags.list) {
3054
console.log(JSON.stringify(settings, null, 2))
3155
return

src/config/constants.ts

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {join} from "jsr:@std/path"
22
import Anthropic from "anthropic"
33
import {homedir} from "node:os"
4-
import {isJinaAvailable, loadUserSettings} from "./settings.ts"
4+
import {isJinaAvailable, loadUserSettings, getSelectedModel, getModelMap} from "./settings.ts"
55

66
export const EDITOR_DIR = join(homedir(), ".ComputerUseAgent", "editor_dir")
77
export const SESSIONS_DIR = join(homedir(), ".ComputerUseAgent", "sessions")
@@ -143,16 +143,41 @@ Note: When chaining operations, use separate BASH_TOOL commands and store result
143143
144144
`
145145

146-
export const API_CONFIG = {
147-
MODEL: "claude-3-5-sonnet-20241022",
148-
REASONING_MODEL: "claude-3-7-sonnet-20250219",
149-
INTENT_MODEL: "claude-3-5-haiku-20241022",
150-
MAX_TOKENS: 8192,
151-
MIN_THINKING_TOKENS: 1024,
152-
MAX_TOKENS_WHEN_THINKING: 20000,
153-
MAX_INTENT_TOKENS: 20,
146+
function getReasoningModel(mainModel: string): string {
147+
const modelMap = getModelMap()
148+
149+
if (mainModel === modelMap["3.5-sonnet"]) {
150+
return modelMap["3.7-sonnet"]
151+
} else {
152+
return mainModel
153+
}
154+
155+
}
156+
157+
function shouldUseThinking(mainModel: string): boolean {
158+
const modelMap = getModelMap()
159+
return mainModel !== modelMap["3.5-sonnet"]
154160
}
155161

162+
export function getAPIConfig() {
163+
const selectedModel = getSelectedModel()
164+
const reasoningModel = getReasoningModel(selectedModel)
165+
const useThinking = shouldUseThinking(selectedModel)
166+
167+
return {
168+
MODEL: selectedModel,
169+
REASONING_MODEL: reasoningModel,
170+
INTENT_MODEL: "claude-3-5-haiku-20241022",
171+
USE_THINKING: useThinking,
172+
MAX_TOKENS: 8192,
173+
MIN_THINKING_TOKENS: useThinking ? 1024 : 0,
174+
MAX_TOKENS_WHEN_THINKING: useThinking ? 20000 : 8192,
175+
MAX_INTENT_TOKENS: 20,
176+
}
177+
}
178+
179+
export const API_CONFIG = getAPIConfig()
180+
156181
export const MEMORY_TOOLS: Anthropic.Beta.BetaTool[] = [
157182
{
158183
name: "add_memory",

src/config/settings.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,19 @@ import {join} from "jsr:@std/path"
22
import {homedir} from "node:os"
33
import {UserSettings} from "../types/interfaces.ts"
44

5+
const MODEL_MAP = {
6+
"3.5-sonnet": "claude-3-5-sonnet-20241022",
7+
"3.7-sonnet": "claude-3-7-sonnet-20250219",
8+
"4-sonnet": "claude-sonnet-4-20250514",
9+
"4-opus": "claude-opus-4-20250514"
10+
} as const
11+
512
const DEFAULT_SETTINGS: UserSettings = {
613
userName: "User",
714
jinaApiKey: undefined,
815
toolConfigPath: join(homedir(), ".ComputerUseAgent", "tools.json"),
9-
editorCommand: "nano"
16+
editorCommand: "nano",
17+
model: "3.5-sonnet"
1018
}
1119

1220
const SETTINGS_PATH = join(homedir(), ".ComputerUseAgent", "settings.json")
@@ -38,3 +46,21 @@ export function getConfigFileLocation(): string {
3846
const settings = loadUserSettings()
3947
return settings.toolConfigPath
4048
}
49+
50+
export function getSelectedModel(): string {
51+
const settings = loadUserSettings()
52+
const modelKey = settings.model || "3.5-sonnet"
53+
return MODEL_MAP[modelKey as keyof typeof MODEL_MAP] || MODEL_MAP["3.5-sonnet"]
54+
}
55+
56+
export function validateModel(model: string): boolean {
57+
return model in MODEL_MAP
58+
}
59+
60+
export function getAvailableModels(): string[] {
61+
return Object.keys(MODEL_MAP)
62+
}
63+
64+
export function getModelMap(): typeof MODEL_MAP {
65+
return MODEL_MAP
66+
}

src/modules/hybrid/hybrid_session.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {BaseSession} from "../../utils/session.ts"
2-
import {API_CONFIG} from "../../config/constants.ts"
2+
import {getAPIConfig} from "../../config/constants.ts"
33
import {log} from "../../config/logging.ts"
44
import {ToolHandler} from "../../utils/tool_handler.ts"
55
import {getConfigFileLocation} from "../../config/settings.ts"
@@ -54,9 +54,10 @@ export class HybridSession extends BaseSession {
5454

5555
try {
5656
while (true) {
57+
const apiConfig = getAPIConfig()
5758
const response = await this.client.beta.messages.create({
58-
model: API_CONFIG.MODEL,
59-
max_tokens: API_CONFIG.MAX_TOKENS,
59+
model: apiConfig.MODEL,
60+
max_tokens: apiConfig.MAX_TOKENS,
6061
messages: this.messages,
6162
tools: [
6263
{type: "bash_20241022", name: "bash"},
@@ -69,7 +70,7 @@ export class HybridSession extends BaseSession {
6970

7071
const inputTokens = response.usage?.input_tokens ?? 0
7172
const outputTokens = response.usage?.output_tokens ?? 0
72-
this.logger.updateTokenUsage(inputTokens, outputTokens)
73+
this.logger.updateTokenUsage(inputTokens, outputTokens, apiConfig.MODEL)
7374

7475
const responseContent = response.content.map((block) =>
7576
block.type === "text" ? {type: "text", text: block.text} : block

0 commit comments

Comments
 (0)