diff --git a/build.ts b/build.ts index 857aefe8ef..6fe75cc4c1 100644 --- a/build.ts +++ b/build.ts @@ -59,6 +59,7 @@ const result = await Bun.build({ splitting: true, define: getMacroDefines(), features, + external: ['openai'], }) if (!result.success) { diff --git a/packages/builtin-tools/src/tools/AgentTool/AgentTool.tsx b/packages/builtin-tools/src/tools/AgentTool/AgentTool.tsx index 82b729386c..d9282ae68d 100644 --- a/packages/builtin-tools/src/tools/AgentTool/AgentTool.tsx +++ b/packages/builtin-tools/src/tools/AgentTool/AgentTool.tsx @@ -5,6 +5,7 @@ import type { AssistantMessage, Message as MessageType, NormalizedUserMessage, + UserMessage, } from 'src/types/message.js' import { getQuerySourceForAgent } from 'src/utils/promptCategory.js' import { z } from 'zod/v4' @@ -1464,7 +1465,7 @@ export const AgentTool = buildTool({ ) { onProgress({ toolUseID: message.toolUseID as string, - data: message.data, + data: message.data as ShellProgress, }) } @@ -1497,7 +1498,7 @@ export const AgentTool = buildTool({ onProgress({ toolUseID: `agent_${assistantMessage.message.id}`, data: { - message: m, + message: m as AssistantMessage | UserMessage, type: 'agent_progress', // prompt only needed on first progress message (UI.tsx:624 // reads progressMessages[0]). Omit here to avoid duplication. diff --git a/packages/builtin-tools/src/tools/AgentTool/UI.tsx b/packages/builtin-tools/src/tools/AgentTool/UI.tsx index 4ba99149a0..db39210344 100644 --- a/packages/builtin-tools/src/tools/AgentTool/UI.tsx +++ b/packages/builtin-tools/src/tools/AgentTool/UI.tsx @@ -1,59 +1,48 @@ -import type { - ContentBlock, - ToolResultBlockParam, - ToolUseBlockParam, -} from '@anthropic-ai/sdk/resources/index.mjs' -type BetaContentBlock = ContentBlock | ToolResultBlockParam -import * as React from 'react' -import { ConfigurableShortcutHint } from 'src/components/ConfigurableShortcutHint.js' -import { - CtrlOToExpand, - SubAgentProvider, -} from 'src/components/CtrlOToExpand.js' -import { Byline, KeyboardShortcutHint } from '@anthropic/ink' -import type { z } from 'zod/v4' -import { AgentProgressLine } from 'src/components/AgentProgressLine.js' -import { FallbackToolUseErrorMessage } from 'src/components/FallbackToolUseErrorMessage.js' -import { FallbackToolUseRejectedMessage } from 'src/components/FallbackToolUseRejectedMessage.js' -import { Markdown } from 'src/components/Markdown.js' -import { Message as MessageComponent } from 'src/components/Message.js' -import { MessageResponse } from 'src/components/MessageResponse.js' -import { ToolUseLoader } from 'src/components/ToolUseLoader.js' -import { Box, Text } from '@anthropic/ink' -import { getDumpPromptsPath } from 'src/services/api/dumpPrompts.js' -import { findToolByName, type Tools } from 'src/Tool.js' -import type { Message, ProgressMessage } from 'src/types/message.js' -import type { AgentToolProgress } from 'src/types/tools.js' -import { count } from 'src/utils/array.js' -import { - getSearchOrReadFromContent, - getSearchReadSummaryText, -} from 'src/utils/collapseReadSearch.js' -import { getDisplayPath } from 'src/utils/file.js' -import { formatDuration, formatNumber } from 'src/utils/format.js' -import { - buildSubagentLookups, - createAssistantMessage, - EMPTY_LOOKUPS, -} from 'src/utils/messages.js' -import type { ModelAlias } from 'src/utils/model/aliases.js' -import { - getMainLoopModel, - parseUserSpecifiedModel, - renderModelName, -} from 'src/utils/model/model.js' -import type { Theme, ThemeName } from 'src/utils/theme.js' -import type { - outputSchema, - Progress, - RemoteLaunchedOutput, -} from './AgentTool.js' -import { inputSchema } from './AgentTool.js' -import { getAgentColor } from './agentColorManager.js' -import { GENERAL_PURPOSE_AGENT } from './built-in/generalPurposeAgent.js' -import { BetaUsage } from '@anthropic-ai/sdk/resources/beta.mjs' - -const MAX_PROGRESS_MESSAGES_TO_SHOW = 3 +import type { ContentBlock, ToolResultBlockParam, ToolUseBlockParam } from '@anthropic-ai/sdk/resources/index.mjs'; +type BetaContentBlock = ContentBlock | ToolResultBlockParam; +import * as React from 'react'; +import { ConfigurableShortcutHint } from 'src/components/ConfigurableShortcutHint.js'; +import { CtrlOToExpand, SubAgentProvider } from 'src/components/CtrlOToExpand.js'; +import { Byline, KeyboardShortcutHint } from '@anthropic/ink'; +import type { z } from 'zod/v4'; +import { AgentProgressLine } from 'src/components/AgentProgressLine.js'; +import { FallbackToolUseErrorMessage } from 'src/components/FallbackToolUseErrorMessage.js'; +import { FallbackToolUseRejectedMessage } from 'src/components/FallbackToolUseRejectedMessage.js'; +import { Markdown } from 'src/components/Markdown.js'; +import { Message as MessageComponent } from 'src/components/Message.js'; +import { MessageResponse } from 'src/components/MessageResponse.js'; +import { ToolUseLoader } from 'src/components/ToolUseLoader.js'; +import { Box, Text } from '@anthropic/ink'; +import { getDumpPromptsPath } from 'src/services/api/dumpPrompts.js'; +import { findToolByName, type Tools } from 'src/Tool.js'; +import type { Message, MessageContent, ProgressMessage } from 'src/types/message.js'; +import type { AgentToolProgress } from 'src/types/tools.js'; +import { count } from 'src/utils/array.js'; +import { getSearchOrReadFromContent, getSearchReadSummaryText } from 'src/utils/collapseReadSearch.js'; +import { getDisplayPath } from 'src/utils/file.js'; +import { formatDuration, formatNumber } from 'src/utils/format.js'; +import { buildSubagentLookups, createAssistantMessage, EMPTY_LOOKUPS } from 'src/utils/messages.js'; +import type { ModelAlias } from 'src/utils/model/aliases.js'; +import { getMainLoopModel, parseUserSpecifiedModel, renderModelName } from 'src/utils/model/model.js'; +import type { Theme, ThemeName } from 'src/utils/theme.js'; +import type { outputSchema, Progress, RemoteLaunchedOutput } from './AgentTool.js'; +import { inputSchema } from './AgentTool.js'; +import { getAgentColor } from './agentColorManager.js'; +import { GENERAL_PURPOSE_AGENT } from './built-in/generalPurposeAgent.js'; +import { BetaUsage } from '@anthropic-ai/sdk/resources/beta.mjs'; + +const MAX_PROGRESS_MESSAGES_TO_SHOW = 3; + +/** API `content` 可能是字符串或块数组;此处统一成块数组以便 `.some` / 遍历。 */ +function asContentBlockArray(content: MessageContent | undefined): BetaContentBlock[] { + if (content == null) { + return []; + } + if (typeof content === 'string') { + return []; + } + return content as BetaContentBlock[]; +} /** * Guard: checks if progress data has a `message` field (agent_progress or @@ -87,12 +76,18 @@ function getSearchOrReadInfo( // Check tool_use (assistant message) if (message.type === 'assistant') { - return getSearchOrReadFromContent(message.message.content[0], tools) + const blocks = asContentBlockArray(message.message.content); + const first = blocks[0]; + if (!first) { + return null; + } + return getSearchOrReadFromContent(first, tools); } // Check tool_result (user message) - find corresponding tool use from the map if (message.type === 'user') { - const content = message.message.content[0] + const blocks = asContentBlockArray(message.message.content); + const content = blocks[0]; if (content?.type === 'tool_result') { const toolUse = toolUseByID.get(content.tool_use_id) if (toolUse) { @@ -173,7 +168,7 @@ function processProgressMessages( for (const msg of agentMessages) { // Track tool_use blocks as we see them if (msg.data.message.type === 'assistant') { - for (const c of msg.data.message.message.content) { + for (const c of asContentBlockArray(msg.data.message.message.content)) { if (c.type === 'tool_use') { toolUseByID.set(c.id, c as ToolUseBlockParam) } @@ -555,11 +550,11 @@ export function renderToolUseProgressMessage( if (!hasProgressMessage(msg.data)) { return false } - const message = msg.data.message - return message.message.content.some( + const message = msg.data.message; + return asContentBlockArray(message.message.content).some( (content: BetaContentBlock) => content.type === 'tool_use', - ) - }) + ); + }); const latestAssistant = progressMessages.findLast( (msg): msg is ProgressMessage => @@ -568,12 +563,14 @@ export function renderToolUseProgressMessage( let tokens = null if (latestAssistant?.data.message.type === 'assistant') { - const usage = latestAssistant.data.message.message.usage - tokens = - (usage.cache_creation_input_tokens ?? 0) + - (usage.cache_read_input_tokens ?? 0) + - usage.input_tokens + - usage.output_tokens + const usage = latestAssistant.data.message.message.usage; + if (usage) { + tokens = + Number(usage.cache_creation_input_tokens ?? 0) + + Number(usage.cache_read_input_tokens ?? 0) + + Number(usage.input_tokens ?? 0) + + Number(usage.output_tokens ?? 0); + } } return { toolUseCount, tokens } @@ -631,10 +628,10 @@ export function renderToolUseProgressMessage( if (!hasProgressMessage(data)) { return false } - return data.message.message.content.some( + return asContentBlockArray(data.message.message.content).some( (content: BetaContentBlock) => content.type === 'tool_use', - ) - }) + ); + }); const firstData = progressMessages[0]?.data const prompt = @@ -801,9 +798,9 @@ function calculateAgentStats(progressMessages: ProgressMessage[]): { const message = msg.data.message return ( message.type === 'user' && - message.message.content.some((content: BetaContentBlock) => content.type === 'tool_result') - ) - }) + asContentBlockArray(message.message.content).some((content: BetaContentBlock) => content.type === 'tool_result') + ); + }); const latestAssistant = progressMessages.findLast( (msg): msg is ProgressMessage => @@ -812,12 +809,14 @@ function calculateAgentStats(progressMessages: ProgressMessage[]): { let tokens = null if (latestAssistant?.data.message.type === 'assistant') { - const usage = latestAssistant.data.message.message.usage - tokens = - (usage.cache_creation_input_tokens ?? 0) + - (usage.cache_read_input_tokens ?? 0) + - usage.input_tokens + - usage.output_tokens + const usage = latestAssistant.data.message.message.usage; + if (usage) { + tokens = + Number(usage.cache_creation_input_tokens ?? 0) + + Number(usage.cache_read_input_tokens ?? 0) + + Number(usage.input_tokens ?? 0) + + Number(usage.output_tokens ?? 0); + } } return { toolUseCount, tokens } @@ -1036,7 +1035,7 @@ export function extractLastToolInfo( continue } if (pm.data.message.type === 'assistant') { - for (const c of pm.data.message.message.content) { + for (const c of asContentBlockArray(pm.data.message.message.content)) { if (c.type === 'tool_use') { toolUseByID.set(c.id, c as ToolUseBlockParam) } @@ -1072,21 +1071,19 @@ export function extractLastToolInfo( } // Find the last tool_result message - const lastToolResult = progressMessages.findLast( - (msg): msg is ProgressMessage => { - if (!hasProgressMessage(msg.data)) { - return false - } - const message = msg.data.message - return ( - message.type === 'user' && - message.message.content.some((c: BetaContentBlock) => c.type === 'tool_result') - ) - }, - ) + const lastToolResult = progressMessages.findLast((msg): msg is ProgressMessage => { + if (!hasProgressMessage(msg.data)) { + return false; + } + const message = msg.data.message; + return ( + message.type === 'user' && + asContentBlockArray(message.message.content).some((c: BetaContentBlock) => c.type === 'tool_result') + ); + }); if (lastToolResult?.data.message.type === 'user') { - const toolResultBlock = lastToolResult.data.message.message.content.find( + const toolResultBlock = asContentBlockArray(lastToolResult.data.message.message.content).find( (c: BetaContentBlock) => c.type === 'tool_result', ) diff --git a/packages/builtin-tools/src/tools/SkillTool/SkillTool.ts b/packages/builtin-tools/src/tools/SkillTool/SkillTool.ts index bc18e24470..e7f4fc6287 100644 --- a/packages/builtin-tools/src/tools/SkillTool/SkillTool.ts +++ b/packages/builtin-tools/src/tools/SkillTool/SkillTool.ts @@ -251,7 +251,7 @@ async function executeForkedSkill( onProgress({ toolUseID: `skill_${parentMessage.message.id}`, data: { - message: m, + message: m as AssistantMessage | UserMessage, type: 'skill_progress', prompt: skillContent, agentId, diff --git a/src/commands/login/login.tsx b/src/commands/login/login.tsx index b4329fe628..ea01527ba4 100644 --- a/src/commands/login/login.tsx +++ b/src/commands/login/login.tsx @@ -22,6 +22,9 @@ import { resetAutoModeGateCheck, resetBypassPermissionsCheck, } from '../../utils/permissions/bypassPermissionsKillswitch.js' +import { applyConfigEnvironmentVariables } from '../../utils/managedEnv.js' +import { resetModelStrings } from '../../utils/model/modelStrings.js' +import { resetSettingsCache } from '../../utils/settings/settingsCache.js' import { resetUserCache } from '../../utils/user.js' export async function call( @@ -40,6 +43,9 @@ export async function call( // Reset cost state when switching accounts resetCostState() // Refresh remotely managed settings after login (non-blocking) + resetSettingsCache() + applyConfigEnvironmentVariables() + resetModelStrings() void refreshRemoteManagedSettings() // Refresh policy limits after login (non-blocking) void refreshPolicyLimits() diff --git a/src/services/api/grok/client.ts b/src/services/api/grok/client.ts index 060d126363..87bb4f4f21 100644 --- a/src/services/api/grok/client.ts +++ b/src/services/api/grok/client.ts @@ -1,4 +1,4 @@ -import OpenAI from 'openai' +import type OpenAI from 'openai' import { getProxyFetchOptions } from 'src/utils/proxy.js' /** @@ -12,17 +12,18 @@ const DEFAULT_BASE_URL = 'https://api.x.ai/v1' let cachedClient: OpenAI | null = null -export function getGrokClient(options?: { +export async function getGrokClient(options?: { maxRetries?: number fetchOverride?: typeof fetch source?: string -}): OpenAI { +}): Promise { if (cachedClient) return cachedClient + const { default: OpenAIClass } = await import('openai') const apiKey = process.env.GROK_API_KEY || process.env.XAI_API_KEY || '' const baseURL = process.env.GROK_BASE_URL || DEFAULT_BASE_URL - const client = new OpenAI({ + const client = new OpenAIClass({ apiKey, baseURL, maxRetries: options?.maxRetries ?? 0, diff --git a/src/services/api/grok/index.ts b/src/services/api/grok/index.ts index 3198e85f68..536ec7b0bc 100644 --- a/src/services/api/grok/index.ts +++ b/src/services/api/grok/index.ts @@ -65,7 +65,7 @@ export async function* queryModelGrok( const openaiTools = anthropicToolsToOpenAI(standardTools) const openaiToolChoice = anthropicToolChoiceToOpenAI(options.toolChoice) - const client = getGrokClient({ + const client = await getGrokClient({ maxRetries: 0, fetchOverride: options.fetchOverride as typeof fetch | undefined, source: options.querySource, @@ -187,7 +187,8 @@ export async function* queryModelGrok( } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error) - logForDebugging(`[Grok] Error: ${errorMessage}`, { level: 'error' }) + const stack = error instanceof Error ? `\n${error.stack}` : '' + logForDebugging(`[Grok] Error: ${errorMessage}${stack}`, { level: 'error' }) yield createAssistantAPIErrorMessage({ content: `API Error: ${errorMessage}`, apiError: 'api_error', diff --git a/src/services/api/grok/modelMapping.ts b/src/services/api/grok/modelMapping.ts index f3e40edbc5..2a62f5ef64 100644 --- a/src/services/api/grok/modelMapping.ts +++ b/src/services/api/grok/modelMapping.ts @@ -5,27 +5,31 @@ * or override the entire mapping via GROK_MODEL_MAP env var (JSON string): * GROK_MODEL_MAP='{"opus":"grok-4","sonnet":"grok-3","haiku":"grok-3-mini-fast"}' */ -const DEFAULT_MODEL_MAP: Record = { - 'claude-sonnet-4-20250514': 'grok-3-mini-fast', - 'claude-sonnet-4-5-20250929': 'grok-3-mini-fast', - 'claude-sonnet-4-6': 'grok-3-mini-fast', - 'claude-opus-4-20250514': 'grok-4.20-reasoning', - 'claude-opus-4-1-20250805': 'grok-4.20-reasoning', - 'claude-opus-4-5-20251101': 'grok-4.20-reasoning', - 'claude-opus-4-6': 'grok-4.20-reasoning', - 'claude-haiku-4-5-20251001': 'grok-3-mini-fast', - 'claude-3-5-haiku-20241022': 'grok-3-mini-fast', - 'claude-3-7-sonnet-20250219': 'grok-3-mini-fast', - 'claude-3-5-sonnet-20241022': 'grok-3-mini-fast', +function getDefaultModelMap(): Record { + return { + 'claude-sonnet-4-20250514': 'grok-3-mini-fast', + 'claude-sonnet-4-5-20250929': 'grok-3-mini-fast', + 'claude-sonnet-4-6': 'grok-3-mini-fast', + 'claude-opus-4-20250514': 'grok-4.20-reasoning', + 'claude-opus-4-1-20250805': 'grok-4.20-reasoning', + 'claude-opus-4-5-20251101': 'grok-4.20-reasoning', + 'claude-opus-4-6': 'grok-4.20-reasoning', + 'claude-haiku-4-5-20251001': 'grok-3-mini-fast', + 'claude-3-5-haiku-20241022': 'grok-3-mini-fast', + 'claude-3-7-sonnet-20250219': 'grok-3-mini-fast', + 'claude-3-5-sonnet-20241022': 'grok-3-mini-fast', + } } /** * Family-level mapping defaults (used by GROK_MODEL_MAP). */ -const DEFAULT_FAMILY_MAP: Record = { - opus: 'grok-4.20-reasoning', - sonnet: 'grok-3-mini-fast', - haiku: 'grok-3-mini-fast', +function getDefaultFamilyMap(): Record { + return { + opus: 'grok-4.20-reasoning', + sonnet: 'grok-3-mini-fast', + haiku: 'grok-3-mini-fast', + } } function getModelFamily(model: string): 'haiku' | 'sonnet' | 'opus' | null { @@ -93,13 +97,13 @@ export function resolveGrokModel(anthropicModel: string): string { } // 5. Exact model name lookup - if (DEFAULT_MODEL_MAP[cleanModel]) { - return DEFAULT_MODEL_MAP[cleanModel] + if (getDefaultModelMap()[cleanModel]) { + return getDefaultModelMap()[cleanModel] } // 6. Family-level default - if (family && DEFAULT_FAMILY_MAP[family]) { - return DEFAULT_FAMILY_MAP[family] + if (family && getDefaultFamilyMap()[family]) { + return getDefaultFamilyMap()[family] } // 7. Pass through diff --git a/src/services/api/openai/client.ts b/src/services/api/openai/client.ts index 62a37dfbc6..adaae3116e 100644 --- a/src/services/api/openai/client.ts +++ b/src/services/api/openai/client.ts @@ -1,4 +1,4 @@ -import OpenAI from 'openai' +import type OpenAI from 'openai' import { getProxyFetchOptions } from 'src/utils/proxy.js' import { isEnvTruthy } from 'src/utils/envUtils.js' @@ -13,17 +13,18 @@ import { isEnvTruthy } from 'src/utils/envUtils.js' let cachedClient: OpenAI | null = null -export function getOpenAIClient(options?: { +export async function getOpenAIClient(options?: { maxRetries?: number fetchOverride?: typeof fetch source?: string -}): OpenAI { +}): Promise { if (cachedClient) return cachedClient + const { default: OpenAIClass } = await import('openai') const apiKey = process.env.OPENAI_API_KEY || '' const baseURL = process.env.OPENAI_BASE_URL - const client = new OpenAI({ + const client = new OpenAIClass({ apiKey, ...(baseURL && { baseURL }), maxRetries: options?.maxRetries ?? 0, diff --git a/src/services/api/openai/index.ts b/src/services/api/openai/index.ts index 0409070063..7cc69ba233 100644 --- a/src/services/api/openai/index.ts +++ b/src/services/api/openai/index.ts @@ -290,7 +290,7 @@ export async function* queryModelOpenAI( const maxTokens = options.maxOutputTokensOverride ?? upperLimit // 11. Get client - const client = getOpenAIClient({ + const client = await getOpenAIClient({ maxRetries: 0, fetchOverride: options.fetchOverride as unknown as typeof fetch, source: options.querySource, @@ -432,7 +432,8 @@ export async function* queryModelOpenAI( } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error) - logForDebugging(`[OpenAI] Error: ${errorMessage}`, { level: 'error' }) + const stack = error instanceof Error ? `\n${error.stack}` : '' + logForDebugging(`[OpenAI] Error: ${errorMessage}${stack}`, { level: 'error' }) yield createAssistantAPIErrorMessage({ content: `API Error: ${errorMessage}`, apiError: 'api_error', diff --git a/src/services/api/openai/modelMapping.ts b/src/services/api/openai/modelMapping.ts index 7cb49c7f99..2b88774faa 100644 --- a/src/services/api/openai/modelMapping.ts +++ b/src/services/api/openai/modelMapping.ts @@ -2,18 +2,20 @@ * Default mapping from Anthropic model names to OpenAI model names. * Used only when ANTHROPIC_DEFAULT_*_MODEL env vars are not set. */ -const DEFAULT_MODEL_MAP: Record = { - 'claude-sonnet-4-20250514': 'gpt-4o', - 'claude-sonnet-4-5-20250929': 'gpt-4o', - 'claude-sonnet-4-6': 'gpt-4o', - 'claude-opus-4-20250514': 'o3', - 'claude-opus-4-1-20250805': 'o3', - 'claude-opus-4-5-20251101': 'o3', - 'claude-opus-4-6': 'o3', - 'claude-haiku-4-5-20251001': 'gpt-4o-mini', - 'claude-3-5-haiku-20241022': 'gpt-4o-mini', - 'claude-3-7-sonnet-20250219': 'gpt-4o', - 'claude-3-5-sonnet-20241022': 'gpt-4o', +function getDefaultModelMap(): Record { + return { + 'claude-sonnet-4-20250514': 'gpt-4o', + 'claude-sonnet-4-5-20250929': 'gpt-4o', + 'claude-sonnet-4-6': 'gpt-4o', + 'claude-opus-4-20250514': 'o3', + 'claude-opus-4-1-20250805': 'o3', + 'claude-opus-4-5-20251101': 'o3', + 'claude-opus-4-6': 'o3', + 'claude-haiku-4-5-20251001': 'gpt-4o-mini', + 'claude-3-5-haiku-20241022': 'gpt-4o-mini', + 'claude-3-7-sonnet-20250219': 'gpt-4o', + 'claude-3-5-sonnet-20241022': 'gpt-4o', + } } /** @@ -59,5 +61,5 @@ export function resolveOpenAIModel(anthropicModel: string): string { if (anthropicOverride) return anthropicOverride } - return DEFAULT_MODEL_MAP[cleanModel] ?? cleanModel + return getDefaultModelMap()?.[cleanModel] ?? cleanModel } diff --git a/src/types/tools.ts b/src/types/tools.ts index 218d782f62..8d7c3e5d5d 100644 --- a/src/types/tools.ts +++ b/src/types/tools.ts @@ -1,12 +1,107 @@ -// Auto-generated stub — replace with real implementation -export type AgentToolProgress = any; -export type BashProgress = any; -export type MCPProgress = any; -export type REPLToolProgress = any; -export type SkillToolProgress = any; -export type TaskOutputProgress = any; -export type ToolProgressData = any; -export type WebSearchProgress = any; -export type ShellProgress = any; -export type PowerShellProgress = any; -export type SdkWorkflowProgress = any; +import type { TaskType } from '../Task.js' +import type { AgentId } from './ids.js' +import type { AssistantMessage, UserMessage } from './message.js' + +/** Bash 工具轮询命令输出时上报的载荷,用于终端内展示执行进度与尾部输出。 */ +export type BashProgress = { + type: 'bash_progress' // discriminant,区分其它工具进度 + output: string // 当前用于展示的尾部输出(可能被截断) + fullOutput: string // 累积的完整 stdout(用于 verbose 或统计) + elapsedTimeSeconds: number // 已运行秒数,用于计时 UI + totalLines: number // 累计行数估计(用于「+N lines」提示) + totalBytes?: number // 累计字节估计(用于体积提示) + taskId?: string // 关联的后台 shell 任务 ID(若已注册) + timeoutMs?: number // 命令超时上限,用于与时间显示联动 +} + +/** PowerShell 工具轮询输出时的载荷,语义与 Bash 进度一致,仅 discriminant 不同。 */ +export type PowerShellProgress = { + type: 'powershell_progress' // discriminant + output: string // 当前用于展示的尾部输出 + fullOutput: string // 累积完整输出 + elapsedTimeSeconds: number // 已运行秒数 + totalLines: number // 累计行数 + totalBytes: number // 累计字节(PS 路径上始终计算) + timeoutMs?: number // 超时上限 + taskId?: string // 后台任务 ID +} + +/** Bash / PowerShell 共享的 shell 进度联合类型;子代理转发 bash/ps 进度或 `!` 命令 UI 使用。 */ +export type ShellProgress = BashProgress | PowerShellProgress + +/** Agent 子工具在运行过程中推送的中间消息载荷,用于侧栏/折叠展示子对话与工具调用。 */ +export type AgentToolProgress = { + type: 'agent_progress' // discriminant + message: AssistantMessage | UserMessage // normalizeMessages 产生的单块用户/助手消息 + prompt: string // 子代理初始提示;首条进度可含全文,后续常为空字符串去重 + agentId: AgentId // 子代理作用域 ID,用于区分并发代理 +} + +/** Skill 工具分叉执行技能时的进度载荷,结构与 agent_progress 类似但标识技能流水线。 */ +export type SkillToolProgress = { + type: 'skill_progress' // discriminant + message: AssistantMessage | UserMessage // 技能代理管线中的单块用户/助手消息 + prompt: string // 技能正文或指令,用于进度卡片展示上下文 + agentId: AgentId // 技能执行所用代理 ID +} + +/** + * MCP 工具生命周期与 SDK `onprogress` 回调的统一载荷(MCPTool/UI 会解构 progress/total)。 + * `status` 区分阶段;流式数值字段仅在 SDK 上报时出现。 + */ +export type MCPProgress = { + type: 'mcp_progress' // discriminant + status: 'started' | 'completed' | 'failed' | 'progress' // 调用生命周期阶段 + serverName: string // MCP 服务器名称(连接配置中的逻辑名) + toolName: string // 正在执行的远程工具名 + elapsedTimeMs?: number // 完成或失败时从发起调用起算的耗时(毫秒) + progress?: number // SDK 流式进度当前值(用于进度条) + total?: number // SDK 流式进度总值(若可得) + progressMessage?: string // 服务器或 SDK 提供的进度说明 +} + +/** TaskOutput 在阻塞等待后台任务完成时发出的等待提示(尚未拿到最终结果)。 */ +export type TaskOutputProgress = { + type: 'waiting_for_task' // discriminant + taskDescription: string // 任务的人类可读描述(来自 AppState) + taskType: TaskType // 任务类别(如 local_agent),用于 UI 解释 +} + +/** Web 搜索适配器在检索过程中上报的中间状态(查询变化与结果统计)。 */ +export type WebSearchProgress = + | { + type: 'query_update' // 查询被规范化或重写后的更新 + query?: string // 当前生效的搜索查询文本 + } + | { + type: 'search_results_received' // 已从提供商收到一批结果 + query?: string // 对应的查询文本 + resultCount?: number // 本批或累计结果数量,用于展示 Found N results + } + +/** + * REPL 工具当前实现不调用 `onProgress`;使用 `never` 表示无运行时载荷, + * 仅占位以满足 Tool 泛型与统一导出。 + */ +export type REPLToolProgress = never + +/** + * SDK `task_progress` 事件中 `workflow_progress` 数组的元素: + * 客户端按注释约定用 `${type}:${index}` 做 upsert,并按 `phaseIndex` 聚合阶段。 + * (工作流详细字段可在后续接入时扩展。) + */ +export type SdkWorkflowProgress = { + type: string // 事件类别,与 index 组成稳定键 + index: number // 同类事件的序号 + phaseIndex: number // 所属工作流阶段的索引 +} + +/** 所有内置工具在 `ToolProgress.data` 中可能出现的 discriminated 联合(不含 hook_progress)。 */ +export type ToolProgressData = + | AgentToolProgress + | SkillToolProgress + | BashProgress + | PowerShellProgress + | MCPProgress + | TaskOutputProgress + | WebSearchProgress diff --git a/src/utils/model/modelStrings.ts b/src/utils/model/modelStrings.ts index 5b7be104fd..4b1d973300 100644 --- a/src/utils/model/modelStrings.ts +++ b/src/utils/model/modelStrings.ts @@ -143,7 +143,13 @@ export function getModelStrings(): ModelStrings { } return applyModelOverrides(ms) } - +/** + * Reset the modelStrings cache so it re-initializes with the current provider on next access. + * Call this after switching providers (e.g. after /login). + */ +export function resetModelStrings(): void { + setModelStringsState(null as unknown as ModelStrings) +} /** * Ensure model strings are fully initialized. * For Bedrock users, this waits for the profile fetch to complete.