mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-03-10 00:17:43 +00:00
fix: pass model parameter to Claude and Codex SDKs
Previously, the model parameter was accepted by the /api/agent endpoint and extracted from requests, but was never passed through to the Claude SDK or Codex SDK, causing all requests to use default models regardless of user selection. Changes: - Add model parameter to queryClaudeSDK() options in routes/agent.js - Add model to threadOptions in openai-codex.js - Remove unused /cost slash command and PRICING constants - Centralize all model definitions in shared/modelConstants.js - Update API documentation to dynamically load models from constants
This commit is contained in:
@@ -489,7 +489,7 @@
|
||||
<span class="endpoint-path"><span class="api-url">http://localhost:3001</span>/api/agent</span>
|
||||
</div>
|
||||
|
||||
<p>Trigger an AI agent (Claude or Cursor) to work on a project.</p>
|
||||
<p>Trigger an AI agent (Claude, Cursor, or Codex) to work on a project.</p>
|
||||
|
||||
<h4>Request Body Parameters</h4>
|
||||
<table>
|
||||
@@ -524,7 +524,7 @@
|
||||
<td><code>provider</code></td>
|
||||
<td>string</td>
|
||||
<td><span class="badge badge-optional">Optional</span></td>
|
||||
<td><code>claude</code> or <code>cursor</code> (default: <code>claude</code>)</td>
|
||||
<td><code>claude</code>, <code>cursor</code>, or <code>codex</code> (default: <code>claude</code>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>stream</code></td>
|
||||
@@ -536,7 +536,9 @@
|
||||
<td><code>model</code></td>
|
||||
<td>string</td>
|
||||
<td><span class="badge badge-optional">Optional</span></td>
|
||||
<td>Model to use (for Cursor)</td>
|
||||
<td id="model-options-cell">
|
||||
Model identifier for the AI provider (loading from constants...)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>cleanup</code></td>
|
||||
@@ -818,31 +820,51 @@ data: {"type":"done"}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<script type="module">
|
||||
// Import model constants
|
||||
import { CLAUDE_MODELS, CURSOR_MODELS, CODEX_MODELS } from '/shared/modelConstants.js';
|
||||
|
||||
// Dynamic URL replacement
|
||||
const apiUrl = window.location.origin;
|
||||
document.querySelectorAll('.api-url').forEach(el => {
|
||||
el.textContent = apiUrl;
|
||||
});
|
||||
|
||||
// Dynamically populate model documentation
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const modelCell = document.getElementById('model-options-cell');
|
||||
if (modelCell) {
|
||||
const claudeModels = CLAUDE_MODELS.OPTIONS.map(m => `<code>${m.value}</code>`).join(', ');
|
||||
const cursorModels = CURSOR_MODELS.OPTIONS.slice(0, 8).map(m => `<code>${m.value}</code>`).join(', ');
|
||||
const codexModels = CODEX_MODELS.OPTIONS.map(m => `<code>${m.value}</code>`).join(', ');
|
||||
|
||||
modelCell.innerHTML = `
|
||||
Model identifier for the AI provider:<br><br>
|
||||
<strong>Claude:</strong> ${claudeModels} (default: <code>${CLAUDE_MODELS.DEFAULT}</code>)<br><br>
|
||||
<strong>Cursor:</strong> ${cursorModels}, and more (default: <code>${CURSOR_MODELS.DEFAULT}</code>)<br><br>
|
||||
<strong>Codex:</strong> ${codexModels} (default: <code>${CODEX_MODELS.DEFAULT}</code>)
|
||||
`;
|
||||
}
|
||||
});
|
||||
|
||||
// Tab switching
|
||||
function showTab(tabName) {
|
||||
window.showTab = function(tabName) {
|
||||
const parentBlock = event.target.closest('.example-block');
|
||||
if (!parentBlock) return;
|
||||
|
||||
|
||||
parentBlock.querySelectorAll('.tab-content').forEach(tab => {
|
||||
tab.classList.remove('active');
|
||||
});
|
||||
parentBlock.querySelectorAll('.tab-button').forEach(btn => {
|
||||
btn.classList.remove('active');
|
||||
});
|
||||
|
||||
|
||||
const targetTab = parentBlock.querySelector('#' + tabName);
|
||||
if (targetTab) {
|
||||
targetTab.classList.add('active');
|
||||
event.target.classList.add('active');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- Prism.js -->
|
||||
|
||||
@@ -16,6 +16,7 @@ import { query } from '@anthropic-ai/claude-agent-sdk';
|
||||
import { promises as fs } from 'fs';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import { CLAUDE_MODELS } from '../shared/modelConstants.js';
|
||||
|
||||
// Session tracking: Map of session IDs to active query instances
|
||||
const activeSessions = new Map();
|
||||
@@ -77,7 +78,7 @@ function mapCliOptionsToSDK(options = {}) {
|
||||
|
||||
// Map model (default to sonnet)
|
||||
// Valid models: sonnet, opus, haiku, opusplan, sonnet[1m]
|
||||
sdkOptions.model = options.model || 'sonnet';
|
||||
sdkOptions.model = options.model || CLAUDE_MODELS.DEFAULT;
|
||||
console.log(`Using model: ${sdkOptions.model}`);
|
||||
|
||||
// Map system prompt configuration
|
||||
|
||||
@@ -213,7 +213,8 @@ export async function queryCodex(command, options = {}, ws) {
|
||||
workingDirectory,
|
||||
skipGitRepoCheck: true,
|
||||
sandboxMode,
|
||||
approvalPolicy
|
||||
approvalPolicy,
|
||||
model
|
||||
};
|
||||
|
||||
// Start or resume thread
|
||||
|
||||
@@ -10,6 +10,7 @@ import { queryClaudeSDK } from '../claude-sdk.js';
|
||||
import { spawnCursor } from '../cursor-cli.js';
|
||||
import { queryCodex } from '../openai-codex.js';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import { CLAUDE_MODELS, CURSOR_MODELS, CODEX_MODELS } from '../../shared/modelConstants.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -634,9 +635,14 @@ class ResponseCollector {
|
||||
* - true: Returns text/event-stream with incremental updates
|
||||
* - false: Returns complete JSON response after completion
|
||||
*
|
||||
* @param {string} model - (Optional) Model identifier for Cursor provider.
|
||||
* Only applicable when provider='cursor'.
|
||||
* Examples: 'gpt-4', 'claude-3-opus', etc.
|
||||
* @param {string} model - (Optional) Model identifier for providers.
|
||||
*
|
||||
* Claude models: 'sonnet' (default), 'opus', 'haiku', 'opusplan', 'sonnet[1m]'
|
||||
* Cursor models: 'gpt-5' (default), 'gpt-5.2', 'gpt-5.2-high', 'sonnet-4.5', 'opus-4.5',
|
||||
* 'gemini-3-pro', 'composer-1', 'auto', 'gpt-5.1', 'gpt-5.1-high',
|
||||
* 'gpt-5.1-codex', 'gpt-5.1-codex-high', 'gpt-5.1-codex-max',
|
||||
* 'gpt-5.1-codex-max-high', 'opus-4.1', 'grok', and thinking variants
|
||||
* Codex models: 'gpt-5.2' (default), 'gpt-5.1-codex-max', 'o3', 'o4-mini'
|
||||
*
|
||||
* @param {boolean} cleanup - (Optional) Auto-cleanup project directory after completion.
|
||||
* Default: true
|
||||
@@ -939,6 +945,7 @@ router.post('/', validateExternalApiKey, async (req, res) => {
|
||||
projectPath: finalProjectPath,
|
||||
cwd: finalProjectPath,
|
||||
sessionId: null, // New session
|
||||
model: model,
|
||||
permissionMode: 'bypassPermissions' // Bypass all permissions for API calls
|
||||
}, writer);
|
||||
|
||||
@@ -959,7 +966,7 @@ router.post('/', validateExternalApiKey, async (req, res) => {
|
||||
projectPath: finalProjectPath,
|
||||
cwd: finalProjectPath,
|
||||
sessionId: null,
|
||||
model: model || 'gpt-5.2',
|
||||
model: model || CODEX_MODELS.DEFAULT,
|
||||
permissionMode: 'bypassPermissions'
|
||||
}, writer);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import os from 'os';
|
||||
import matter from 'gray-matter';
|
||||
import { CLAUDE_MODELS, CURSOR_MODELS, CODEX_MODELS } from '../../shared/modelConstants.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
@@ -182,23 +183,15 @@ Custom commands can be created in:
|
||||
},
|
||||
|
||||
'/model': async (args, context) => {
|
||||
// Read available models from config or defaults
|
||||
// Read available models from centralized constants
|
||||
const availableModels = {
|
||||
claude: [
|
||||
'claude-sonnet-4.5',
|
||||
'claude-sonnet-4',
|
||||
'claude-opus-4',
|
||||
'claude-sonnet-3.5'
|
||||
],
|
||||
cursor: [
|
||||
'gpt-5',
|
||||
'sonnet-4',
|
||||
'opus-4.1'
|
||||
]
|
||||
claude: CLAUDE_MODELS.OPTIONS.map(o => o.value),
|
||||
cursor: CURSOR_MODELS.OPTIONS.map(o => o.value),
|
||||
codex: CODEX_MODELS.OPTIONS.map(o => o.value)
|
||||
};
|
||||
|
||||
const currentProvider = context?.provider || 'claude';
|
||||
const currentModel = context?.model || 'claude-sonnet-4.5';
|
||||
const currentModel = context?.model || CLAUDE_MODELS.DEFAULT;
|
||||
|
||||
return {
|
||||
type: 'builtin',
|
||||
@@ -216,50 +209,6 @@ Custom commands can be created in:
|
||||
};
|
||||
},
|
||||
|
||||
'/cost': async (args, context) => {
|
||||
// Calculate token usage and cost
|
||||
const sessionId = context?.sessionId;
|
||||
const tokenUsage = context?.tokenUsage || { used: 0, total: 200000 };
|
||||
|
||||
const costPerMillion = {
|
||||
'claude-sonnet-4.5': { input: 3, output: 15 },
|
||||
'claude-sonnet-4': { input: 3, output: 15 },
|
||||
'claude-opus-4': { input: 15, output: 75 },
|
||||
'gpt-5': { input: 5, output: 15 }
|
||||
};
|
||||
|
||||
const model = context?.model || 'claude-sonnet-4.5';
|
||||
const rates = costPerMillion[model] || costPerMillion['claude-sonnet-4.5'];
|
||||
|
||||
// Estimate 70% input, 30% output
|
||||
const estimatedInputTokens = Math.floor(tokenUsage.used * 0.7);
|
||||
const estimatedOutputTokens = Math.floor(tokenUsage.used * 0.3);
|
||||
|
||||
const inputCost = (estimatedInputTokens / 1000000) * rates.input;
|
||||
const outputCost = (estimatedOutputTokens / 1000000) * rates.output;
|
||||
const totalCost = inputCost + outputCost;
|
||||
|
||||
return {
|
||||
type: 'builtin',
|
||||
action: 'cost',
|
||||
data: {
|
||||
tokenUsage: {
|
||||
used: tokenUsage.used,
|
||||
total: tokenUsage.total,
|
||||
percentage: ((tokenUsage.used / tokenUsage.total) * 100).toFixed(1)
|
||||
},
|
||||
cost: {
|
||||
input: inputCost.toFixed(4),
|
||||
output: outputCost.toFixed(4),
|
||||
total: totalCost.toFixed(4),
|
||||
currency: 'USD'
|
||||
},
|
||||
model,
|
||||
rates
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
'/status': async (args, context) => {
|
||||
// Read version from package.json
|
||||
const packageJsonPath = path.join(path.dirname(__dirname), '..', 'package.json');
|
||||
|
||||
@@ -6,6 +6,7 @@ import { spawn } from 'child_process';
|
||||
import sqlite3 from 'sqlite3';
|
||||
import { open } from 'sqlite';
|
||||
import crypto from 'crypto';
|
||||
import { CURSOR_MODELS } from '../../shared/modelConstants.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -33,7 +34,7 @@ router.get('/config', async (req, res) => {
|
||||
config: {
|
||||
version: 1,
|
||||
model: {
|
||||
modelId: "gpt-5",
|
||||
modelId: CURSOR_MODELS.DEFAULT,
|
||||
displayName: "GPT-5"
|
||||
},
|
||||
permissions: {
|
||||
|
||||
65
shared/modelConstants.js
Normal file
65
shared/modelConstants.js
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Centralized Model Definitions
|
||||
* Single source of truth for all supported AI models
|
||||
*/
|
||||
|
||||
/**
|
||||
* Claude (Anthropic) Models
|
||||
*
|
||||
* Note: Claude uses two different formats:
|
||||
* - SDK format ('sonnet', 'opus') - used by the UI and claude-sdk.js
|
||||
* - API format ('claude-sonnet-4.5') - used by slash commands for display
|
||||
*/
|
||||
export const CLAUDE_MODELS = {
|
||||
// Models in SDK format (what the actual SDK accepts)
|
||||
OPTIONS: [
|
||||
{ value: 'sonnet', label: 'Sonnet' },
|
||||
{ value: 'opus', label: 'Opus' },
|
||||
{ value: 'haiku', label: 'Haiku' },
|
||||
{ value: 'opusplan', label: 'Opus Plan' },
|
||||
{ value: 'sonnet[1m]', label: 'Sonnet [1M]' }
|
||||
],
|
||||
|
||||
DEFAULT: 'sonnet'
|
||||
};
|
||||
|
||||
/**
|
||||
* Cursor Models
|
||||
*/
|
||||
export const CURSOR_MODELS = {
|
||||
OPTIONS: [
|
||||
{ value: 'gpt-5.2-high', label: 'GPT-5.2 High' },
|
||||
{ value: 'gemini-3-pro', label: 'Gemini 3 Pro' },
|
||||
{ value: 'opus-4.5-thinking', label: 'Claude 4.5 Opus (Thinking)' },
|
||||
{ value: 'gpt-5.2', label: 'GPT-5.2' },
|
||||
{ value: 'gpt-5.1', label: 'GPT-5.1' },
|
||||
{ value: 'gpt-5.1-high', label: 'GPT-5.1 High' },
|
||||
{ value: 'composer-1', label: 'Composer 1' },
|
||||
{ value: 'auto', label: 'Auto' },
|
||||
{ value: 'sonnet-4.5', label: 'Claude 4.5 Sonnet' },
|
||||
{ value: 'sonnet-4.5-thinking', label: 'Claude 4.5 Sonnet (Thinking)' },
|
||||
{ value: 'opus-4.5', label: 'Claude 4.5 Opus' },
|
||||
{ value: 'gpt-5.1-codex', label: 'GPT-5.1 Codex' },
|
||||
{ value: 'gpt-5.1-codex-high', label: 'GPT-5.1 Codex High' },
|
||||
{ value: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max' },
|
||||
{ value: 'gpt-5.1-codex-max-high', label: 'GPT-5.1 Codex Max High' },
|
||||
{ value: 'opus-4.1', label: 'Claude 4.1 Opus' },
|
||||
{ value: 'grok', label: 'Grok' }
|
||||
],
|
||||
|
||||
DEFAULT: 'gpt-5'
|
||||
};
|
||||
|
||||
/**
|
||||
* Codex (OpenAI) Models
|
||||
*/
|
||||
export const CODEX_MODELS = {
|
||||
OPTIONS: [
|
||||
{ value: 'gpt-5.2', label: 'GPT-5.2' },
|
||||
{ value: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max' },
|
||||
{ value: 'o3', label: 'O3' },
|
||||
{ value: 'o4-mini', label: 'O4-mini' }
|
||||
],
|
||||
|
||||
DEFAULT: 'gpt-5.2'
|
||||
};
|
||||
@@ -35,6 +35,7 @@ import { MicButton } from './MicButton.jsx';
|
||||
import { api, authenticatedFetch } from '../utils/api';
|
||||
import Fuse from 'fuse.js';
|
||||
import CommandMenu from './CommandMenu';
|
||||
import { CLAUDE_MODELS, CURSOR_MODELS, CODEX_MODELS } from '../../shared/modelConstants';
|
||||
|
||||
|
||||
// Helper function to decode HTML entities in text
|
||||
@@ -1723,13 +1724,13 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
return localStorage.getItem('selected-provider') || 'claude';
|
||||
});
|
||||
const [cursorModel, setCursorModel] = useState(() => {
|
||||
return localStorage.getItem('cursor-model') || 'gpt-5';
|
||||
return localStorage.getItem('cursor-model') || CURSOR_MODELS.DEFAULT;
|
||||
});
|
||||
const [claudeModel, setClaudeModel] = useState(() => {
|
||||
return localStorage.getItem('claude-model') || 'sonnet';
|
||||
return localStorage.getItem('claude-model') || CLAUDE_MODELS.DEFAULT;
|
||||
});
|
||||
const [codexModel, setCodexModel] = useState(() => {
|
||||
return localStorage.getItem('codex-model') || 'gpt-5.2';
|
||||
return localStorage.getItem('codex-model') || CODEX_MODELS.DEFAULT;
|
||||
});
|
||||
// Load permission mode for the current session
|
||||
useEffect(() => {
|
||||
@@ -1758,17 +1759,10 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.success && data.config?.model?.modelId) {
|
||||
// Map Cursor model IDs to our simplified names
|
||||
const modelMap = {
|
||||
'gpt-5': 'gpt-5',
|
||||
'claude-4-sonnet': 'sonnet-4',
|
||||
'sonnet-4': 'sonnet-4',
|
||||
'claude-4-opus': 'opus-4.1',
|
||||
'opus-4.1': 'opus-4.1'
|
||||
};
|
||||
const mappedModel = modelMap[data.config.model.modelId] || data.config.model.modelId;
|
||||
// Use the model from config directly
|
||||
const modelId = data.config.model.modelId;
|
||||
if (!localStorage.getItem('cursor-model')) {
|
||||
setCursorModel(mappedModel);
|
||||
setCursorModel(modelId);
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -4547,11 +4541,9 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
}}
|
||||
className="pl-4 pr-10 py-2 text-sm bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500 min-w-[140px]"
|
||||
>
|
||||
<option value="sonnet">Sonnet</option>
|
||||
<option value="opus">Opus</option>
|
||||
<option value="haiku">Haiku</option>
|
||||
<option value="opusplan">Opus Plan</option>
|
||||
<option value="sonnet[1m]">Sonnet [1M]</option>
|
||||
{CLAUDE_MODELS.OPTIONS.map(({ value, label }) => (
|
||||
<option key={value} value={value}>{label}</option>
|
||||
))}
|
||||
</select>
|
||||
) : provider === 'codex' ? (
|
||||
<select
|
||||
@@ -4563,10 +4555,9 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
}}
|
||||
className="pl-4 pr-10 py-2 text-sm bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-gray-500 focus:border-gray-500 min-w-[140px]"
|
||||
>
|
||||
<option value="gpt-5.2">GPT-5.2</option>
|
||||
<option value="gpt-5.1-codex-max">GPT-5.1 Codex Max</option>
|
||||
<option value="o3">O3</option>
|
||||
<option value="o4-mini">O4-mini</option>
|
||||
{CODEX_MODELS.OPTIONS.map(({ value, label }) => (
|
||||
<option key={value} value={value}>{label}</option>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
<select
|
||||
@@ -4579,23 +4570,9 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
className="pl-4 pr-10 py-2 text-sm bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500 min-w-[140px]"
|
||||
disabled={provider !== 'cursor'}
|
||||
>
|
||||
<option value="gpt-5.2-high">GPT-5.2 High</option>
|
||||
<option value="gemini-3-pro">Gemini 3 Pro</option>
|
||||
<option value="opus-4.5-thinking">Claude 4.5 Opus (Thinking)</option>
|
||||
<option value="gpt-5.2">GPT-5.2</option>
|
||||
<option value="gpt-5.1">GPT-5.1</option>
|
||||
<option value="gpt-5.1-high">GPT-5.1 High</option>
|
||||
<option value="composer-1">Composer 1</option>
|
||||
<option value="auto">Auto</option>
|
||||
<option value="sonnet-4.5">Claude 4.5 Sonnet</option>
|
||||
<option value="sonnet-4.5-thinking">Claude 4.5 Sonnet (Thinking)</option>
|
||||
<option value="opus-4.5">Claude 4.5 Opus</option>
|
||||
<option value="gpt-5.1-codex">GPT-5.1 Codex</option>
|
||||
<option value="gpt-5.1-codex-high">GPT-5.1 Codex High</option>
|
||||
<option value="gpt-5.1-codex-max">GPT-5.1 Codex Max</option>
|
||||
<option value="gpt-5.1-codex-max-high">GPT-5.1 Codex Max High</option>
|
||||
<option value="opus-4.1">Claude 4.1 Opus</option>
|
||||
<option value="grok">Grok</option>
|
||||
{CURSOR_MODELS.OPTIONS.map(({ value, label }) => (
|
||||
<option key={value} value={value}>{label}</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user