From a2a3f59e1d6b20920ed7b9ef13a04e31d61a4937 Mon Sep 17 00:00:00 2001 From: Askhz <1361267452@qq.com> Date: Mon, 20 Apr 2026 16:00:52 +0800 Subject: [PATCH 1/4] =?UTF-8?q?refactor:=20=E7=B2=BE=E7=AE=80=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E9=80=89=E9=A1=B9=EF=BC=8C=E4=BF=9D=E7=95=99=204=20?= =?UTF-8?q?=E4=B8=AA=E4=B8=BB=E8=A6=81=E7=99=BB=E5=BD=95=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ConsoleOAuthFlow.tsx | 49 ----------------------------- 1 file changed, 49 deletions(-) diff --git a/src/components/ConsoleOAuthFlow.tsx b/src/components/ConsoleOAuthFlow.tsx index 73ba8d0a5..0ded245d3 100644 --- a/src/components/ConsoleOAuthFlow.tsx +++ b/src/components/ConsoleOAuthFlow.tsx @@ -477,43 +477,6 @@ function OAuthStatusMessage({ ), value: 'gemini_api', }, - { - label: ( - - Claude account with subscription · Pro, Max, Team, or Enterprise - {process.env.USER_TYPE === 'ant' && ( - - {'\n'} - [ANT-ONLY]{' '} - - Please use this option unless you need to login to a special org for accessing sensitive - data (e.g. customer data, HIPI data) with the Console option - - - )} - {'\n'} - - ), - value: 'claudeai', - }, - { - label: ( - - Anthropic Console account · API usage billing - {'\n'} - - ), - value: 'console', - }, - { - label: ( - - 3rd-party platform · Amazon Bedrock, Microsoft Foundry, or Vertex AI - {'\n'} - - ), - value: 'platform', - }, ]} onChange={value => { if (value === 'costrict') { @@ -616,18 +579,6 @@ function OAuthStatusMessage({ opusModel: process.env.GEMINI_DEFAULT_OPUS_MODEL ?? '', activeField: 'base_url', }); - } else if (value === 'platform') { - logEvent('tengu_oauth_platform_selected', {}); - setOAuthStatus({ state: 'platform_setup' }); - } else { - setOAuthStatus({ state: 'ready_to_start' }); - if (value === 'claudeai') { - logEvent('tengu_oauth_claudeai_selected', {}); - setLoginWithClaudeAi(true); - } else { - logEvent('tengu_oauth_console_selected', {}); - setLoginWithClaudeAi(false); - } } }} /> From d7bcf65968bb1f632bf9ee157c0fe4e3f0e250da Mon Sep 17 00:00:00 2001 From: Askhz <1361267452@qq.com> Date: Mon, 20 Apr 2026 20:40:59 +0800 Subject: [PATCH 2/4] feat: restore CoStrict login option in interactive login - Added CoStrict option to the login Select component - Implemented complete OAuth flow with browser opening and token polling - Added costrict_waiting and costrict_model_select OAuth states - Fixed TypeScript errors in fetch.ts and index.ts The CoStrict login option was previously removed during login simplification. This commit restores the full functionality including model selection after login. --- src/components/ConsoleOAuthFlow.tsx | 142 +++++++++++++++++++++++++++- src/costrict/provider/fetch.ts | 39 ++++---- src/costrict/provider/index.ts | 7 +- 3 files changed, 164 insertions(+), 24 deletions(-) diff --git a/src/components/ConsoleOAuthFlow.tsx b/src/components/ConsoleOAuthFlow.tsx index b4da18737..512230293 100644 --- a/src/components/ConsoleOAuthFlow.tsx +++ b/src/components/ConsoleOAuthFlow.tsx @@ -16,6 +16,7 @@ import { getSettings_DEPRECATED, updateSettingsForSource } from '../utils/settin import { Select } from './CustomSelect/select.js' import { Spinner } from './Spinner.js' import TextInput from './TextInput.js' +import { useSetAppState } from '../state/AppState.js' import { fi } from 'zod/v4/locales' type Props = { @@ -28,6 +29,14 @@ type Props = { type OAuthStatus = | { state: 'idle' } // Initial state, waiting to select login method | { state: 'platform_setup' } // Show platform setup info (Bedrock/Vertex/Foundry) + | { + state: 'costrict_waiting' + url: string + } // CoStrict OAuth: browser opened, waiting for user to login + | { + state: 'costrict_model_select' + models: Array<{ id: string; name?: string }> + } // CoStrict: login done, select a model | { state: 'custom_platform' baseUrl: string @@ -94,6 +103,7 @@ export function ConsoleOAuthFlow({ } return { state: 'idle' } }) + const setAppState = useSetAppState() const [pastedCode, setPastedCode] = useState('') const [cursorOffset, setCursorOffset] = useState(0) @@ -438,6 +448,7 @@ function OAuthStatusMessage({ setLoginWithClaudeAi, onDone, }: OAuthStatusMessageProps): React.ReactNode { + const setAppState = useSetAppState() switch (oauthStatus.state) { case 'idle': return ( @@ -453,6 +464,16 @@ function OAuthStatusMessage({ { + process.env.COSTRICT_MODEL = value; + setAppState(prev => ({ ...prev, mainLoopModel: value, mainLoopModelForSession: null })); + setOAuthStatus({ state: 'success' }); + void onDone(); + }} + onCancel={() => { + const selected = sortedModels[0]?.id ?? ''; + process.env.COSTRICT_MODEL = selected; + setAppState(prev => ({ ...prev, mainLoopModel: selected, mainLoopModelForSession: null })); + setOAuthStatus({ state: 'success' }); + void onDone(); + }} + /> + + + ); + } + case 'platform_setup': return ( diff --git a/src/costrict/provider/fetch.ts b/src/costrict/provider/fetch.ts index dabbf532a..14dbefc92 100644 --- a/src/costrict/provider/fetch.ts +++ b/src/costrict/provider/fetch.ts @@ -34,6 +34,10 @@ function getVersion(): string { const VERSION = getVersion() +type CoStrictFetch = typeof fetch & { + preconnect?: (url: string | URL) => void +} + /** * 创建自定义 fetch 函数,用于 CoStrict API 请求 * @@ -43,7 +47,7 @@ const VERSION = getVersion() * 3. 注入 Authorization 和 CoStrict 特有 headers * 4. 反应性 401 错误恢复(自动重试一次) */ -export function createCoStrictFetch() { +export function createCoStrictFetch(): CoStrictFetch { const costrictFetch = async ( input: RequestInfo | URL, init?: RequestInit, @@ -126,20 +130,21 @@ export function createCoStrictFetch() { } // Bun 原生支持 fetch.preconnect(共享连接池预热) // Node.js 没有 preconnect API,降级为 net.createConnection 做 TCP 预热 - if (typeof fetch.preconnect === 'function') { - costrictFetch.preconnect = fetch.preconnect.bind(fetch) - } else { - costrictFetch.preconnect = (url: string | URL) => { - try { - const { hostname, port } = new URL(typeof url === 'string' ? url : url.toString()) - const { createConnection } = require('node:net') as typeof import('node:net') - const sock = createConnection(Number(port) || 443, hostname, () => sock.destroy()) - sock.setTimeout(3000, () => sock.destroy()) - sock.on('error', () => sock.destroy()) - } catch { - // 预连接失败不影响正常流程 - } - } - } - return costrictFetch + const costrictFetchWithPreconnect = Object.assign(costrictFetch, { + preconnect: typeof fetch.preconnect === 'function' + ? fetch.preconnect.bind(fetch) + : (url: string | URL) => { + try { + const { hostname, port } = new URL(typeof url === 'string' ? url : url.toString()) + const { createConnection } = require('node:net') as typeof import('node:net') + const sock = createConnection(Number(port) || 443, hostname, () => sock.destroy()) + sock.setTimeout(3000, () => sock.destroy()) + sock.on('error', () => sock.destroy()) + } catch { + // 预连接失败不影响正常流程 + } + } + }) + + return costrictFetchWithPreconnect as CoStrictFetch } diff --git a/src/costrict/provider/index.ts b/src/costrict/provider/index.ts index 83fea97e3..7dfe7896e 100644 --- a/src/costrict/provider/index.ts +++ b/src/costrict/provider/index.ts @@ -16,12 +16,7 @@ import type { Tools } from '../../Tool.js' import type { Options } from '../../services/api/claude.js' import OpenAI from 'openai' import { getProxyFetchOptions } from '../../utils/proxy.js' -import { anthropicMessagesToOpenAI } from '../../services/api/openai/convertMessages.js' -import { - anthropicToolsToOpenAI, - anthropicToolChoiceToOpenAI, -} from '../../services/api/openai/convertTools.js' -import { adaptOpenAIStreamToAnthropic } from '../../services/api/openai/streamAdapter.js' +import { anthropicMessagesToOpenAI, anthropicToolsToOpenAI, anthropicToolChoiceToOpenAI, adaptOpenAIStreamToAnthropic } from '@ant/model-provider' import { normalizeMessagesForAPI } from '../../utils/messages.js' import { toolToAPISchema } from '../../utils/api.js' import { logForDebugging } from '../../utils/debug.js' From 425ca6bb19eb653fce8a734d1a4a8e444fda26e1 Mon Sep 17 00:00:00 2001 From: Askhz <1361267452@qq.com> Date: Mon, 20 Apr 2026 21:08:25 +0800 Subject: [PATCH 3/4] feat: cleanup CLAUDE_CODE_USE_COSTRICT env var after provider switch - Remove env var in provider.ts unset/anthropic/openai/gemini/grok handlers - Remove env var after OAuth login and model save success in ConsoleOAuthFlow - Fix modelOptions.ts to only show CoStrict models when provider IS costrict - Remove unused hasCoStrictCredentialsSync import and getCoStrictLoginOption --- src/commands/provider.ts | 2 ++ src/components/ConsoleOAuthFlow.tsx | 8 ++++++ src/utils/model/modelOptions.ts | 44 ++++++++++++++--------------- 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/src/commands/provider.ts b/src/commands/provider.ts index 19b19c021..8a3066e50 100644 --- a/src/commands/provider.ts +++ b/src/commands/provider.ts @@ -53,6 +53,7 @@ const call: LocalCommandCall = async (args, context) => { delete process.env.CLAUDE_CODE_USE_OPENAI delete process.env.CLAUDE_CODE_USE_GEMINI delete process.env.CLAUDE_CODE_USE_GROK + delete process.env.CLAUDE_CODE_USE_COSTRICT return { type: 'text', value: 'API provider cleared (will use environment variables).', @@ -131,6 +132,7 @@ const call: LocalCommandCall = async (args, context) => { delete process.env.CLAUDE_CODE_USE_OPENAI delete process.env.CLAUDE_CODE_USE_GEMINI delete process.env.CLAUDE_CODE_USE_GROK + delete process.env.CLAUDE_CODE_USE_COSTRICT // Update settings.json updateSettingsForSource('userSettings', { modelType: arg }) // Ensure settings.env gets applied to process.env diff --git a/src/components/ConsoleOAuthFlow.tsx b/src/components/ConsoleOAuthFlow.tsx index 512230293..73bb98ae8 100644 --- a/src/components/ConsoleOAuthFlow.tsx +++ b/src/components/ConsoleOAuthFlow.tsx @@ -283,6 +283,8 @@ export function ConsoleOAuthFlow({ } // Reset modelType to anthropic when using OAuth login updateSettingsForSource('userSettings', { modelType: 'anthropic' } as any) + // Clear costrict env var to prevent conflicts + delete process.env.CLAUDE_CODE_USE_COSTRICT setOAuthStatus({ state: 'success' }) void sendNotification( @@ -725,6 +727,8 @@ function OAuthStatusMessage({ }) } else { for (const [k, v] of Object.entries(env)) process.env[k] = v + // Clear costrict env var to prevent conflicts when using custom platform + delete process.env.CLAUDE_CODE_USE_COSTRICT setOAuthStatus({ state: 'success' }) void onDone() } @@ -945,6 +949,8 @@ function OAuthStatusMessage({ }) } else { for (const [k, v] of Object.entries(env)) process.env[k] = v + // Clear costrict env var to prevent conflicts when using OpenAI + delete process.env.CLAUDE_CODE_USE_COSTRICT setOAuthStatus({ state: 'success' }) void onDone() } @@ -1178,6 +1184,8 @@ function OAuthStatusMessage({ }) } else { for (const [k, v] of Object.entries(env)) process.env[k] = v + // Clear costrict env var to prevent conflicts when using Gemini + delete process.env.CLAUDE_CODE_USE_COSTRICT setOAuthStatus({ state: 'success' }) void onDone() } diff --git a/src/utils/model/modelOptions.ts b/src/utils/model/modelOptions.ts index 16798ef84..55a5c2fde 100644 --- a/src/utils/model/modelOptions.ts +++ b/src/utils/model/modelOptions.ts @@ -33,7 +33,6 @@ import { } from './model.js' import { has1mContext } from '../context.js' import { getGlobalConfig } from '../config.js' -import { hasCoStrictCredentialsSync } from '../../costrict/provider/credentials.js' // @[MODEL LAUNCH]: Update all the available and default model option strings below. @@ -380,10 +379,8 @@ function getModelOptionsBase(fastMode = false): ModelOption[] { } // CoStrict provider: 从缓存中动态展示 CoStrict 可用模型 - // 也覆盖其他 provider 但用户已有 CoStrict 凭证的场景(modelType 丢失/竞态) const isCoStrictProvider = getAPIProvider() === 'costrict' - const hasCoStrictCreds = hasCoStrictCredentialsSync() - if (isCoStrictProvider || hasCoStrictCreds) { + if (isCoStrictProvider) { // 延迟 require 避免循环依赖,且该模块只在 costrict provider 下加载 // eslint-disable-next-line @typescript-eslint/no-require-imports const { getCachedCoStrictModels } = @@ -414,21 +411,27 @@ function getModelOptionsBase(fastMode = false): ModelOption[] { ] } - // 用户没有 CoStrict 凭证,显示推荐登录选项 - return [getCoStrictLoginOption()] -} - -/** - * CoStrict 登录推荐选项 - * 当用户未登录 CoStrict 时显示,提示用户可以登录以使用 CoStrict 模型 - */ -function getCoStrictLoginOption(): ModelOption { - return { - value: 'costrict-login', - label: 'CoStrict (Recommended)', - description: - 'Sign in to CoStrict for more models and better pricing · Run /login and select CoStrict', + // PAYG 用户:展示标准模型选项(Sonnet, Opus, Haiku 等) + const paygOptions = [getDefaultOptionForUser(fastMode)] + const customSonnet = getCustomSonnetOption() + if (customSonnet) { + paygOptions.push(customSonnet) + } else { + paygOptions.push(getSonnet46Option()) } + const customOpus = getCustomOpusOption() + if (customOpus) { + paygOptions.push(customOpus) + } else { + paygOptions.push(getOpus46Option(fastMode)) + } + const customHaiku = getCustomHaikuOption() + if (customHaiku) { + paygOptions.push(customHaiku) + } else { + paygOptions.push(getHaikuOption()) + } + return paygOptions } // @[MODEL LAUNCH]: Add the new model ID to the appropriate family pattern below @@ -549,11 +552,6 @@ export function getModelOptions(fastMode = false): ModelOption[] { } else if (initialMainLoopModel !== null) { customModel = initialMainLoopModel } - // 忽略 costrict-login 伪模型值(用户点击了推荐登录选项但未完成登录) - if (customModel === 'costrict-login') { - return filterModelOptionsByAllowlist(options) - } - if (customModel === null || options.some(opt => opt.value === customModel)) { return filterModelOptionsByAllowlist(options) } else if (customModel === 'opusplan') { From 7b5e1f382d14935f2ef1760d353c06a2b22b8aab Mon Sep 17 00:00:00 2001 From: Askhz <1361267452@qq.com> Date: Mon, 20 Apr 2026 21:22:29 +0800 Subject: [PATCH 4/4] feat: auto-set default model when switching provider - Add setDefaultModel() in provider.ts to set sonnet as default when using /provider command - Add model setting in ConsoleOAuthFlow.tsx for all provider save paths: - Anthropic OAuth login - Custom platform (Anthropic Compatible) - OpenAI - Gemini - Fix modelOptions.ts to only show CoStrict models when provider IS costrict (not when creds exist) - Add standard model options for non-costrict providers (Sonnet/Opus/Haiku) --- src/commands/provider.ts | 28 ++++++++++++++++++++++++++++ src/components/ConsoleOAuthFlow.tsx | 9 +++++++++ 2 files changed, 37 insertions(+) diff --git a/src/commands/provider.ts b/src/commands/provider.ts index 8a3066e50..e7b0ff101 100644 --- a/src/commands/provider.ts +++ b/src/commands/provider.ts @@ -4,6 +4,7 @@ import { getAPIProvider } from '../utils/model/providers.js' import { updateSettingsForSource } from '../utils/settings/settings.js' import { getSettings_DEPRECATED } from '../utils/settings/settings.js' import { applyConfigEnvironmentVariables } from '../utils/managedEnv.js' +import { getDefaultSonnetModel, getDefaultOpusModel, getDefaultHaikuModel } from '../utils/model/model.js' function getEnvVarForProvider(provider: string): string { switch (provider) { @@ -43,6 +44,28 @@ const call: LocalCommandCall = async (args, context) => { return { type: 'text', value: `Current API provider: ${current}` } } + // Helper function to set default model based on provider + const setDefaultModel = (provider: string) => { + const settings = getSettings_DEPRECATED() || {} + const currentModel = settings.model + + // Only set default if no model has been explicitly set + if (!currentModel) { + let defaultModel: string + switch (provider) { + case 'anthropic': + defaultModel = getDefaultSonnetModel() + break + case 'openai': + case 'gemini': + case 'grok': + default: + defaultModel = getDefaultSonnetModel() + } + updateSettingsForSource('userSettings', { model: defaultModel }) + } + } + // unset - clear settings, fallback to env vars if (arg === 'unset') { updateSettingsForSource('userSettings', { modelType: undefined }) @@ -84,6 +107,7 @@ const call: LocalCommandCall = async (args, context) => { const hasUrl = !!mergedEnv.OPENAI_BASE_URL if (!hasKey || !hasUrl) { updateSettingsForSource('userSettings', { modelType: 'openai' }) + setDefaultModel('openai') const missing = [] if (!hasKey) missing.push('OPENAI_API_KEY') if (!hasUrl) missing.push('OPENAI_BASE_URL') @@ -100,6 +124,7 @@ const call: LocalCommandCall = async (args, context) => { const hasKey = !!(mergedEnv.GROK_API_KEY || mergedEnv.XAI_API_KEY) if (!hasKey) { updateSettingsForSource('userSettings', { modelType: 'grok' }) + setDefaultModel('grok') return { type: 'text', value: `Switched to Grok provider.\nWarning: Missing env var: GROK_API_KEY (or XAI_API_KEY)\nConfigure it via settings.json env or set manually.`, @@ -114,6 +139,7 @@ const call: LocalCommandCall = async (args, context) => { // GEMINI_BASE_URL is optional (has default) if (!hasKey) { updateSettingsForSource('userSettings', { modelType: 'gemini' }) + setDefaultModel('gemini') return { type: 'text', value: `Switched to Gemini provider.\nWarning: Missing env var: GEMINI_API_KEY\nConfigure it via /login or set manually.`, @@ -135,6 +161,8 @@ const call: LocalCommandCall = async (args, context) => { delete process.env.CLAUDE_CODE_USE_COSTRICT // Update settings.json updateSettingsForSource('userSettings', { modelType: arg }) + // Set default model for the provider + setDefaultModel(arg) // Ensure settings.env gets applied to process.env applyConfigEnvironmentVariables() return { type: 'text', value: `API provider set to ${arg}.` } diff --git a/src/components/ConsoleOAuthFlow.tsx b/src/components/ConsoleOAuthFlow.tsx index 73bb98ae8..0f6acd502 100644 --- a/src/components/ConsoleOAuthFlow.tsx +++ b/src/components/ConsoleOAuthFlow.tsx @@ -13,6 +13,7 @@ import { OAuthService } from '../services/oauth/index.js' import { getOauthAccountInfo, validateForceLoginOrg } from '../utils/auth.js' import { logError } from '../utils/log.js' import { getSettings_DEPRECATED, updateSettingsForSource } from '../utils/settings/settings.js' +import { getDefaultSonnetModel } from '../utils/model/model.js' import { Select } from './CustomSelect/select.js' import { Spinner } from './Spinner.js' import TextInput from './TextInput.js' @@ -283,6 +284,8 @@ export function ConsoleOAuthFlow({ } // Reset modelType to anthropic when using OAuth login updateSettingsForSource('userSettings', { modelType: 'anthropic' } as any) + // Set default model to sonnet for Anthropic provider + updateSettingsForSource('userSettings', { model: getDefaultSonnetModel() } as any) // Clear costrict env var to prevent conflicts delete process.env.CLAUDE_CODE_USE_COSTRICT @@ -729,6 +732,8 @@ function OAuthStatusMessage({ for (const [k, v] of Object.entries(env)) process.env[k] = v // Clear costrict env var to prevent conflicts when using custom platform delete process.env.CLAUDE_CODE_USE_COSTRICT + // Set default model to sonnet for this provider + updateSettingsForSource('userSettings', { model: getDefaultSonnetModel() } as any) setOAuthStatus({ state: 'success' }) void onDone() } @@ -951,6 +956,8 @@ function OAuthStatusMessage({ for (const [k, v] of Object.entries(env)) process.env[k] = v // Clear costrict env var to prevent conflicts when using OpenAI delete process.env.CLAUDE_CODE_USE_COSTRICT + // Set default model to sonnet for this provider + updateSettingsForSource('userSettings', { model: getDefaultSonnetModel() } as any) setOAuthStatus({ state: 'success' }) void onDone() } @@ -1186,6 +1193,8 @@ function OAuthStatusMessage({ for (const [k, v] of Object.entries(env)) process.env[k] = v // Clear costrict env var to prevent conflicts when using Gemini delete process.env.CLAUDE_CODE_USE_COSTRICT + // Set default model to sonnet for this provider + updateSettingsForSource('userSettings', { model: getDefaultSonnetModel() } as any) setOAuthStatus({ state: 'success' }) void onDone() }