diff --git a/package.json b/package.json index 6d9c37de..4ac55e5b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "open-alice", - "version": "0.50.0-beta.1", + "version": "0.51.0-beta.1", "description": "File-based trading agent engine", "type": "module", "main": "dist/electron/main.js", diff --git a/src/ai-providers/preset-catalog.ts b/src/ai-providers/preset-catalog.ts index 3d365752..829f46ed 100644 --- a/src/ai-providers/preset-catalog.ts +++ b/src/ai-providers/preset-catalog.ts @@ -73,7 +73,6 @@ export const CLAUDE_OAUTH: PresetDef = { }), models: [ { id: 'claude-opus-4-8', label: 'Claude Opus 4.8' }, - { id: 'claude-opus-4-7', label: 'Claude Opus 4.7' }, { id: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6' }, ], } @@ -84,7 +83,7 @@ export const CLAUDE_API: PresetDef = { description: 'Pay per token via Anthropic API', category: 'official', defaultName: 'Claude (API Key)', - hint: 'Model is switchable here or from the profile list anytime. Opus is ~5× the cost of Sonnet; Haiku is cheapest for high-volume work.', + hint: 'Model is switchable here or from the profile list anytime. Opus is ~5× the cost of Sonnet.', zodSchema: z.object({ backend: z.literal('agent-sdk'), loginMethod: z.literal('api-key'), @@ -93,9 +92,7 @@ export const CLAUDE_API: PresetDef = { }), models: [ { id: 'claude-opus-4-8', label: 'Claude Opus 4.8' }, - { id: 'claude-opus-4-7', label: 'Claude Opus 4.7' }, { id: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6' }, - { id: 'claude-haiku-4-5', label: 'Claude Haiku 4.5' }, ], regions: [{ id: 'official', label: 'Official (api.anthropic.com)', wires: { anthropic: '' } }], writeOnlyFields: ['apiKey'], @@ -118,7 +115,6 @@ export const CODEX_OAUTH: PresetDef = { models: [ { id: 'gpt-5.5', label: 'GPT 5.5' }, { id: 'gpt-5.4', label: 'GPT 5.4' }, - { id: 'gpt-5.4-mini', label: 'GPT 5.4 Mini' }, ], } @@ -137,7 +133,6 @@ export const CODEX_API: PresetDef = { models: [ { id: 'gpt-5.5', label: 'GPT 5.5' }, { id: 'gpt-5.4', label: 'GPT 5.4' }, - { id: 'gpt-5.4-mini', label: 'GPT 5.4 Mini' }, ], // Same key + base; the shape is how you call it. Responses is OpenAI's // current API (what codex speaks); Chat Completions is the legacy shape @@ -215,12 +210,12 @@ export const GLM: PresetDef = { description: 'Zhipu GLM models via Claude Agent SDK (Anthropic-compatible)', category: 'third-party', defaultName: 'GLM', - hint: 'China console: bigmodel.cn — International console: z.ai. API keys are region-locked. GLM 5.1 is the current flagship, served on both regions.', + hint: 'China console: bigmodel.cn — International console: z.ai. API keys are region-locked. GLM 5.2 is the current flagship, served on both regions.', zodSchema: z.object({ backend: z.literal('agent-sdk'), loginMethod: z.literal('api-key'), baseUrl: z.string().default('https://open.bigmodel.cn/api/anthropic').describe('API endpoint'), - model: z.string().default('glm-5.1').describe('Model'), + model: z.string().default('glm-5.2').describe('Model'), apiKey: z.string().min(1).describe('GLM API key'), }), regions: [ @@ -232,9 +227,7 @@ export const GLM: PresetDef = { } }, ], models: [ - { id: 'glm-5.1', label: 'GLM 5.1' }, - { id: 'glm-4.7', label: 'GLM 4.7' }, - { id: 'glm-4.5-air', label: 'GLM 4.5 Air' }, + { id: 'glm-5.2', label: 'GLM 5.2' }, ], writeOnlyFields: ['apiKey'], } @@ -257,7 +250,7 @@ export const KIMI: PresetDef = { backend: z.literal('agent-sdk'), loginMethod: z.literal('api-key'), baseUrl: z.string().default('https://api.moonshot.cn/anthropic').describe('API endpoint'), - model: z.string().default('kimi-k2.6').describe('Model'), + model: z.string().default('kimi-k2.7-code').describe('Model'), apiKey: z.string().min(1).describe('Moonshot API key'), }), regions: [ @@ -269,8 +262,8 @@ export const KIMI: PresetDef = { } }, ], models: [ + { id: 'kimi-k2.7-code', label: 'Kimi K2.7 Code' }, { id: 'kimi-k2.6', label: 'Kimi K2.6' }, - { id: 'kimi-k2.5', label: 'Kimi K2.5' }, ], writeOnlyFields: ['apiKey'], } @@ -298,7 +291,6 @@ export const DEEPSEEK: PresetDef = { ], models: [ { id: 'deepseek-v4-pro', label: 'DeepSeek V4 Pro (flagship)' }, - { id: 'deepseek-v4-flash', label: 'DeepSeek V4 Flash (cheap/fast)' }, ], writeOnlyFields: ['apiKey'], } diff --git a/src/workspaces/agent-probe.ts b/src/workspaces/agent-probe.ts index a8b41ec5..057b24fc 100644 --- a/src/workspaces/agent-probe.ts +++ b/src/workspaces/agent-probe.ts @@ -41,6 +41,16 @@ export interface CodexProbeInput { wireApi: 'chat' | 'responses'; } +/** Does this error mean the model MANDATES extended thinking? Some reasoning + * models (e.g. Kimi k2.7) 400 with "invalid thinking: only type=enabled is + * allowed for this model" when the request omits thinking — they can't run it + * disabled. We detect that to retry with thinking on, rather than enabling it + * for every model (Claude/GLM/etc. don't need it and some reject the param). */ +function isThinkingRequiredError(err: unknown): boolean { + const e = err as { status?: number; message?: string } | undefined; + return e?.status === 400 && typeof e?.message === 'string' && /thinking/i.test(e.message); +} + export async function probeAnthropic(input: ClaudeProbeInput): Promise { // `authToken` makes the SDK send `Authorization: Bearer`; `apiKey` makes it // send `x-api-key`. Pick exactly one — sending both can trip gateways that @@ -48,19 +58,34 @@ export async function probeAnthropic(input: ClaudeProbeInput): Promise msg.content .filter((b): b is Anthropic.TextBlock => b.type === 'text') .map((b) => b.text) .join(''); - return { text }; + + try { + const msg = await client.messages.create({ + model: input.model, + // Enough room for a reasoning model to finish thinking AND emit a visible + // reply on a trivial prompt — a tiny budget gets spent entirely on reasoning, + // leaving empty content (the "(empty reply)" the user saw). One-off per Test. + max_tokens: 512, + messages: [{ role: 'user', content: 'Hi' }], + }); + return { text: extract(msg) }; + } catch (err) { + if (!isThinkingRequiredError(err)) throw err; + // Thinking-mandatory model: retry with it enabled. budget_tokens must be + // < max_tokens and Anthropic's floor is 1024, so bump max_tokens to match. + const msg = await client.messages.create({ + model: input.model, + max_tokens: 2048, + thinking: { type: 'enabled', budget_tokens: 1024 }, + messages: [{ role: 'user', content: 'Hi' }], + }); + return { text: extract(msg) }; + } } export async function probeOpenAI(input: CodexProbeInput): Promise { diff --git a/ui/src/components/workspace/WorkspaceAIConfigModal.tsx b/ui/src/components/workspace/WorkspaceAIConfigModal.tsx index 26dcc84a..f3a47dc4 100644 --- a/ui/src/components/workspace/WorkspaceAIConfigModal.tsx +++ b/ui/src/components/workspace/WorkspaceAIConfigModal.tsx @@ -463,7 +463,7 @@ export function WorkspaceAIConfigModal({ wsId, onClose }: Props) { value={form.model} suggestions={modelSuggestions} onChange={(v) => setForm({ ...form, model: v })} - placeholder={tab === 'claude' ? 'claude-sonnet-4-6' : tab === 'opencode' || tab === 'pi' ? 'deepseek-chat' : 'gpt-4o'} + placeholder={tab === 'claude' ? 'claude-opus-4-8' : tab === 'opencode' || tab === 'pi' ? 'deepseek-chat' : 'gpt-5.5'} /> {modelSuggestions.length > 0 && (

Suggestions from the matched provider — or type any model id.