diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index 39aefc75ca..302360cb10 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -196,6 +196,7 @@ export const globalSettingsSchema = z.object({ includeTaskHistoryInEnhance: z.boolean().optional(), historyPreviewCollapsed: z.boolean().optional(), reasoningBlockCollapsed: z.boolean().optional(), + showSpeedInfo: z.boolean().optional(), /** * Controls the keyboard behavior for sending messages in the chat input. * - "send": Enter sends message, Shift+Enter creates newline (default) diff --git a/packages/types/src/providers/zgsm.ts b/packages/types/src/providers/zgsm.ts index 662ee91af2..170d426a13 100644 --- a/packages/types/src/providers/zgsm.ts +++ b/packages/types/src/providers/zgsm.ts @@ -2,7 +2,7 @@ import { ModelInfo } from "../model.js" export const zgsmDefaultModelId = "Auto" -export const zgsmModels = { +export const zgsmModelsConfig = { default: { maxTokens: 8192, contextWindow: 128_000, diff --git a/packages/types/src/tool.ts b/packages/types/src/tool.ts index f4feb12b74..6b38ba13ec 100644 --- a/packages/types/src/tool.ts +++ b/packages/types/src/tool.ts @@ -95,7 +95,7 @@ export const NATIVE_TOOL_DEFAULTS = { * @param protocol - The tool protocol to check * @returns True if protocol is native */ -export function isNativeProtocol(protocol: ToolProtocol): boolean { +export function isNativeProtocol(protocol?: ToolProtocol): boolean { return protocol === TOOL_PROTOCOL.NATIVE } diff --git a/src/api/index.ts b/src/api/index.ts index 8b0a906dd6..e66f7832c0 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -108,7 +108,7 @@ export interface ApiHandlerCreateMessageMetadata { responseIdTimestamp?: number responseEndTimestamp?: number completionTokens?: number - }) => void + }) => Promise } export interface ApiHandler { diff --git a/src/api/providers/fetchers/modelCache.ts b/src/api/providers/fetchers/modelCache.ts index b220198f2f..0d8798da3f 100644 --- a/src/api/providers/fetchers/modelCache.ts +++ b/src/api/providers/fetchers/modelCache.ts @@ -82,9 +82,8 @@ async function fetchModelsFromProvider(options: GetModelsOptions): Promise {}) } } } @@ -438,11 +441,13 @@ export class ZgsmAiHandler extends BaseProvider implements SingleCompletionHandl stream: true as const, ...(isGrokXAI ? {} : { stream_options: { include_usage: true } }), ...(reasoning && reasoning), - ...(metadata?.tools && { tools: this.convertToolsForOpenAI(metadata.tools) }), - ...(metadata?.tool_choice && { tool_choice: metadata.tool_choice }), - ...(metadata?.toolProtocol === "native" && { - parallel_tool_calls: metadata.parallelToolCalls ?? false, - }), + ...(isNativeProtocol(metadata?.toolProtocol) + ? { + ...(metadata?.tools && { tools: this.convertToolsForOpenAI(metadata.tools) }), + ...(metadata?.tool_choice && { tool_choice: metadata.tool_choice }), + ...{ parallel_tool_calls: metadata?.parallelToolCalls ?? false }, + } + : undefined), extra_body: { mode: metadata?.mode, }, @@ -468,7 +473,6 @@ export class ZgsmAiHandler extends BaseProvider implements SingleCompletionHandl role: "user", content: systemPrompt, } - const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming & { extra_body: any } = { model: modelId, messages: isDeepseekReasoner @@ -476,11 +480,13 @@ export class ZgsmAiHandler extends BaseProvider implements SingleCompletionHandl : isLegacyFormat ? [systemMessage, ...convertToSimpleMessages(messages)] : [systemMessage, ...convertToOpenAiMessages(messages, { mergeToolResultText: true })], - ...(metadata?.tools && { tools: this.convertToolsForOpenAI(metadata.tools) }), - ...(metadata?.tool_choice && { tool_choice: metadata.tool_choice }), - ...(metadata?.toolProtocol === "native" && { - parallel_tool_calls: metadata.parallelToolCalls ?? false, - }), + ...(isNativeProtocol(metadata?.toolProtocol) + ? { + ...(metadata?.tools && { tools: this.convertToolsForOpenAI(metadata.tools) }), + ...(metadata?.tool_choice && { tool_choice: metadata.tool_choice }), + ...{ parallel_tool_calls: metadata?.parallelToolCalls ?? false }, + } + : undefined), extra_body: { prompt_mode: metadata?.mode, }, @@ -501,7 +507,6 @@ export class ZgsmAiHandler extends BaseProvider implements SingleCompletionHandl selectReason?: string, requestId?: string, isNative?: boolean, - modelId?: string, responseIdTimestamp?: number, requestIdTimestamp?: number, onPerformanceTiming?: (timing: { @@ -509,8 +514,12 @@ export class ZgsmAiHandler extends BaseProvider implements SingleCompletionHandl responseIdTimestamp?: number responseEndTimestamp?: number completionTokens?: number - }) => void, + }) => Promise, ): ApiStream { + // Check if request was aborted + if (this.abortController?.signal.aborted) { + return + } const matcher = new XmlMatcher( "think", (chunk) => @@ -625,16 +634,14 @@ export class ZgsmAiHandler extends BaseProvider implements SingleCompletionHandl // Emit performance timing data via callback (frontend will calculate metrics) const responseEndTimestamp = Date.now() - if (responseIdTimestamp && requestIdTimestamp && lastUsage.completion_tokens) { + if (onPerformanceTiming && responseIdTimestamp && requestIdTimestamp && lastUsage.completion_tokens) { // Emit timing data via callback - if (onPerformanceTiming) { - onPerformanceTiming({ - requestIdTimestamp, - responseIdTimestamp, - responseEndTimestamp, - completionTokens: lastUsage.completion_tokens, - }) - } + onPerformanceTiming({ + requestIdTimestamp, + responseIdTimestamp, + responseEndTimestamp, + completionTokens: lastUsage.completion_tokens, + }).catch(() => {}) } } } @@ -685,23 +692,22 @@ export class ZgsmAiHandler extends BaseProvider implements SingleCompletionHandl } async updateModelInfo() { - const id = this.options.zgsmModelId ?? zgsmDefaultModelId - const info = - ( - await getModels({ - provider: "zgsm", - baseUrl: `${this.options.zgsmBaseUrl?.trim() || ZgsmAuthConfig.getInstance().getDefaultApiBaseUrl()}`, - apiKey: this.options.zgsmAccessToken, - }) - )[id] ?? zgsmModels.default + try { + const id = this.options.zgsmModelId ?? zgsmDefaultModelId + const info = + ( + await getModels({ + provider: "zgsm", + baseUrl: `${this.options.zgsmBaseUrl?.trim() || ZgsmAuthConfig.getInstance().getDefaultApiBaseUrl()}`, + apiKey: this.options.zgsmAccessToken, + }) + )[id] ?? zgsmModels.default - if (id.toLowerCase().includes("gemini")) { - Object.assign(info, { - supportsNativeTools: false, - }) + this.modelInfo = info + } catch (error) { + this.logger.error(`[updateModelInfo] ${error.message}`) + this.modelInfo = zgsmModels.default } - - this.modelInfo = info } override getModel() { @@ -754,7 +760,7 @@ export class ZgsmAiHandler extends BaseProvider implements SingleCompletionHandl "user", ), }, - timeout: 15000, + timeout: 60_000, signal: metadata?.signal, }), ) @@ -790,11 +796,13 @@ export class ZgsmAiHandler extends BaseProvider implements SingleCompletionHandl ...(isGrokXAI ? {} : { stream_options: { include_usage: true } }), reasoning_effort: modelInfo.reasoningEffort as "low" | "medium" | "high" | undefined, temperature: undefined, - ...(metadata?.tools && { tools: this.convertToolsForOpenAI(metadata.tools) }), - ...(metadata?.tool_choice && { tool_choice: metadata.tool_choice }), - ...(metadata?.toolProtocol === "native" && { - parallel_tool_calls: metadata.parallelToolCalls ?? false, - }), + ...(isNativeProtocol(metadata?.toolProtocol) + ? { + ...(metadata?.tools && { tools: this.convertToolsForOpenAI(metadata.tools) }), + ...(metadata?.tool_choice && { tool_choice: metadata.tool_choice }), + ...{ parallel_tool_calls: metadata?.parallelToolCalls ?? false }, + } + : undefined), } // O3 family models do not support the deprecated max_tokens parameter @@ -826,11 +834,13 @@ export class ZgsmAiHandler extends BaseProvider implements SingleCompletionHandl ], reasoning_effort: modelInfo.reasoningEffort as "low" | "medium" | "high" | undefined, temperature: undefined, - ...(metadata?.tools && { tools: this.convertToolsForOpenAI(metadata.tools) }), - ...(metadata?.tool_choice && { tool_choice: metadata.tool_choice }), - ...(metadata?.toolProtocol === "native" && { - parallel_tool_calls: metadata.parallelToolCalls ?? false, - }), + ...(isNativeProtocol(metadata?.toolProtocol) + ? { + ...(metadata?.tools && { tools: this.convertToolsForOpenAI(metadata.tools) }), + ...(metadata?.tool_choice && { tool_choice: metadata.tool_choice }), + ...{ parallel_tool_calls: metadata?.parallelToolCalls ?? false }, + } + : undefined), } // O3 family models do not support the deprecated max_tokens parameter @@ -873,6 +883,10 @@ export class ZgsmAiHandler extends BaseProvider implements SingleCompletionHandl } private async *handleStreamResponse(stream: AsyncIterable): ApiStream { + // Check if request was aborted + if (this.abortController?.signal.aborted) { + return + } const activeToolCallIds = new Set() for await (const chunk of stream) { diff --git a/src/core/costrict/activate.ts b/src/core/costrict/activate.ts index 7715f3489b..aa8d2a37d1 100644 --- a/src/core/costrict/activate.ts +++ b/src/core/costrict/activate.ts @@ -48,6 +48,8 @@ import { t } from "../../i18n" import prettyBytes from "pretty-bytes" import { ensureProjectWikiSubtasksExists } from "./wiki/projectWikiHelpers" import { isJetbrainsPlatform } from "../../utils/platform" +import type { ModelRecord } from "../../shared/api" +import type { ModelInfo } from "@roo-code/types" const HISTORY_WARN_SIZE = 1000 * 1000 * 1000 * 3 @@ -223,9 +225,21 @@ export async function activate( }) setTimeout(() => { loginTip() - flushModels({ provider: "zgsm" }, true) // init project-wiki subtasks. ensureProjectWikiSubtasksExists() + flushModels({ provider: "zgsm" }, true, (models: ModelRecord) => { + const openAiModels = [] as string[] + const fullResponseData = [] as ModelInfo[] + for (const [id, value] of Object.entries(models)) { + openAiModels.push(id) + fullResponseData.push(value) + } + provider.postMessageToWebview({ + type: "zgsmModels", + openAiModels, + fullResponseData, + }) + }) }, 2000) } diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 57951d9eac..be3c01b7e3 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -96,7 +96,7 @@ import { getWorkspacePath } from "../../utils/path" import { formatResponse } from "../prompts/responses" import { SYSTEM_PROMPT } from "../prompts/system" import { buildNativeToolsArray } from "./build-tools" -import { getRooDirectoriesForCwd } from "../../services/roo-config/index.js" +// import { getRooDirectoriesForCwd } from "../../services/roo-config/index.js" // core modules import { ToolRepetitionDetector } from "../tools/ToolRepetitionDetector" @@ -3796,6 +3796,7 @@ export class Task extends EventEmitter implements TaskLike { autoCondenseContext = true, autoCondenseContextPercent = 100, profileThresholds = {}, + showSpeedInfo = false, } = state ?? {} // Get condensing configuration for automatic triggers. @@ -3997,7 +3998,9 @@ export class Task extends EventEmitter implements TaskLike { // tools continue using XML even if NTC settings have since changed. const modelInfo = this.api.getModel().info const taskProtocol = this._taskToolProtocol ?? TOOL_PROTOCOL.XML - const shouldIncludeTools = taskProtocol === TOOL_PROTOCOL.NATIVE && (modelInfo.supportsNativeTools ?? false) + const shouldIncludeTools = + taskProtocol === TOOL_PROTOCOL.NATIVE && + (modelInfo.supportsNativeTools ?? ["zgsm", "gemini-cli"].includes(apiConfiguration?.apiProvider || "")) // Build complete tools array: native tools + dynamic MCP tools, filtered by mode restrictions let allTools: OpenAI.Chat.ChatCompletionTool[] = [] @@ -4043,6 +4046,40 @@ export class Task extends EventEmitter implements TaskLike { language: state?.language, instanceId: this.instanceId, userId: id, + onRequestHeadersReady: (headers: Record) => { + this.lastApiRequestHeaders = headers + }, + onPerformanceTiming: + !showSpeedInfo || apiConfiguration?.apiProvider !== "zgsm" + ? undefined + : async (timing: { + requestIdTimestamp?: number + responseIdTimestamp?: number + responseEndTimestamp?: number + completionTokens?: number + }) => { + // Find and update the api_req_started message with raw timing data + const lastApiReqIndex = findLastIndex( + this.clineMessages, + (msg) => msg.type === "say" && msg.say === "api_req_started", + ) + if (lastApiReqIndex >= 0 && this.clineMessages[lastApiReqIndex]) { + const existingData = JSON.parse(this.clineMessages[lastApiReqIndex].text || "{}") + this.clineMessages[lastApiReqIndex].text = JSON.stringify({ + ...existingData, + requestIdTimestamp: timing.requestIdTimestamp, + responseIdTimestamp: timing.responseIdTimestamp, + responseEndTimestamp: timing.responseEndTimestamp, + completionTokens: timing.completionTokens, + } satisfies ClineApiReqInfo) + // Notify frontend that the message has been updated + const provider = this.providerRef.deref() + await provider?.postMessageToWebview({ + type: "messageUpdated", + clineMessage: this.clineMessages[lastApiReqIndex], + }) + } + }, // Include tools and tool protocol when using native protocol and model supports it ...(shouldIncludeTools ? { @@ -4065,40 +4102,7 @@ export class Task extends EventEmitter implements TaskLike { const stream = this.api.createMessage( systemPrompt, cleanConversationHistory as unknown as Anthropic.Messages.MessageParam[], - { - ...metadata, - onRequestHeadersReady: (headers: Record) => { - this.lastApiRequestHeaders = headers - }, - onPerformanceTiming: async (timing: { - requestIdTimestamp?: number - responseIdTimestamp?: number - responseEndTimestamp?: number - completionTokens?: number - }) => { - // Find and update the api_req_started message with raw timing data - const lastApiReqIndex = findLastIndex( - this.clineMessages, - (msg) => msg.type === "say" && msg.say === "api_req_started", - ) - if (lastApiReqIndex >= 0 && this.clineMessages[lastApiReqIndex]) { - const existingData = JSON.parse(this.clineMessages[lastApiReqIndex].text || "{}") - this.clineMessages[lastApiReqIndex].text = JSON.stringify({ - ...existingData, - requestIdTimestamp: timing.requestIdTimestamp, - responseIdTimestamp: timing.responseIdTimestamp, - responseEndTimestamp: timing.responseEndTimestamp, - completionTokens: timing.completionTokens, - } satisfies ClineApiReqInfo) - // Notify frontend that the message has been updated - const provider = this.providerRef.deref() - await provider?.postMessageToWebview({ - type: "messageUpdated", - clineMessage: this.clineMessages[lastApiReqIndex], - }) - } - }, - }, + metadata, ) const iterator = stream[Symbol.asyncIterator]() diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index c1bcfad326..e4041b1af8 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1975,6 +1975,7 @@ export class ClineProvider terminalCompressProgressBar, historyPreviewCollapsed, reasoningBlockCollapsed, + showSpeedInfo, enterBehavior, cloudUserInfo, cloudIsAuthenticated, @@ -2131,6 +2132,7 @@ export class ClineProvider hasSystemPromptOverride, historyPreviewCollapsed: historyPreviewCollapsed ?? false, reasoningBlockCollapsed: reasoningBlockCollapsed ?? true, + showSpeedInfo: showSpeedInfo ?? false, enterBehavior: enterBehavior ?? "send", cloudUserInfo, cloudIsAuthenticated: cloudIsAuthenticated ?? false, @@ -2384,6 +2386,7 @@ export class ClineProvider maxConcurrentFileReads: stateValues.maxConcurrentFileReads ?? 5, historyPreviewCollapsed: stateValues.historyPreviewCollapsed ?? false, reasoningBlockCollapsed: stateValues.reasoningBlockCollapsed ?? true, + showSpeedInfo: stateValues.showSpeedInfo ?? false, enterBehavior: stateValues.enterBehavior ?? "send", cloudUserInfo, cloudIsAuthenticated, diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index b306b3a36f..0d75093de6 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -321,6 +321,7 @@ export type ExtensionState = Pick< | "openRouterImageGenerationSelectedModel" | "includeTaskHistoryInEnhance" | "reasoningBlockCollapsed" + | "showSpeedInfo" | "errorCode" | "enterBehavior" | "includeCurrentTime" diff --git a/src/utils/__tests__/resolveToolProtocol.spec.ts b/src/utils/__tests__/resolveToolProtocol.spec.ts index 1e1b0aef0f..51a0ba0f76 100644 --- a/src/utils/__tests__/resolveToolProtocol.spec.ts +++ b/src/utils/__tests__/resolveToolProtocol.spec.ts @@ -125,7 +125,7 @@ describe("resolveToolProtocol", () => { it("should fallback to XML when no preference or model info", () => { const settings: ProviderSettings = {} const result = resolveToolProtocol(settings) - expect(result).toBe(TOOL_PROTOCOL.XML) + expect(result).toBe(TOOL_PROTOCOL.NATIVE) }) it("should fallback to XML when model info is undefined", () => { @@ -133,13 +133,13 @@ describe("resolveToolProtocol", () => { apiProvider: "openai-native", } const result = resolveToolProtocol(settings, undefined) - expect(result).toBe(TOOL_PROTOCOL.XML) + expect(result).toBe(TOOL_PROTOCOL.NATIVE) }) - it("should fallback to XML for empty settings", () => { + it("should fallback to NATIVE for empty settings", () => { const settings: ProviderSettings = {} const result = resolveToolProtocol(settings) - expect(result).toBe(TOOL_PROTOCOL.XML) + expect(result).toBe(TOOL_PROTOCOL.NATIVE) }) }) diff --git a/src/utils/resolveToolProtocol.ts b/src/utils/resolveToolProtocol.ts index 383e4c7b79..cc5705c90f 100644 --- a/src/utils/resolveToolProtocol.ts +++ b/src/utils/resolveToolProtocol.ts @@ -44,18 +44,8 @@ export function resolveToolProtocol( return _providerSettings.toolProtocol } - // 3. Model supports native tools - if (_modelInfo?.supportsNativeTools === true) { - return TOOL_PROTOCOL.NATIVE - } - - // 4. Model default tool protocol - if (_modelInfo?.defaultToolProtocol) { - return _modelInfo.defaultToolProtocol - } - // 5. Final fallback - return TOOL_PROTOCOL.XML + return _providerSettings.apiProvider === "zgsm" ? TOOL_PROTOCOL.XML : TOOL_PROTOCOL.NATIVE } /** diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 5bc177e060..45cd7466b4 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -201,8 +201,16 @@ export const ChatRowContent = ({ }: ChatRowContentProps) => { const { t, i18n } = useTranslation() - const { mcpServers, alwaysAllowMcp, currentCheckpoint, mode, apiConfiguration, clineMessages, reviewTask } = - useExtensionState() + const { + mcpServers, + alwaysAllowMcp, + currentCheckpoint, + mode, + apiConfiguration, + clineMessages, + reviewTask, + showSpeedInfo, + } = useExtensionState() const { logoPic, userInfo } = useZgsmUserInfo(apiConfiguration?.zgsmAccessToken) const { info: model } = useSelectedModel(apiConfiguration) const [showCopySuccess, setShowCopySuccess] = useState(false) @@ -320,18 +328,7 @@ export const ChatRowContent = ({ ] } - return [ - undefined, - undefined, - undefined, - undefined, - undefined, - undefined, - undefined, - undefined, - undefined, - undefined, - ] + return [] }, [message.text, message.say]) // When resuming task, last wont be api_req_failed but a resume_task @@ -1285,13 +1282,13 @@ export const ChatRowContent = ({ {(selectReason || firstTokenLatency !== undefined || tokensPerSecond !== undefined) && (
- {(selectedLLM || originModelId) && ( + {/* {(selectedLLM || originModelId) && (
{isAuto ? t("chat:autoMode.selectedLLM", { selectedLLM }) : originModelId}
- )} + )} */} {selectReason && (
)} - {firstTokenLatency !== undefined && ( + {showSpeedInfo && firstTokenLatency !== undefined && (
{t("chat:performance.firstToken")}: {firstTokenLatency}s
)} - {totalDuration !== undefined && ( + {showSpeedInfo && totalDuration !== undefined && (
{t("chat:performance.totalDuration")}: {totalDuration}s
)} - {tokensPerSecond !== undefined && ( + {showSpeedInfo && tokensPerSecond !== undefined && (
- {tokensPerSecond} {t("chat:performance.tokensPerSecond")} + {t("chat:performance.tokensPerSecond", { time: tokensPerSecond })}
)}
diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 1994f9fef3..cf812597bb 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -474,7 +474,7 @@ const ApiOptions = ({ // }, [selectedProvider]) const defaultProtocol = - selectedModelInfo?.defaultToolProtocol || + selectedModelInfo?.defaultToolProtocol ?? (selectedProvider === "zgsm" ? TOOL_PROTOCOL.XML : TOOL_PROTOCOL.NATIVE) const showToolProtocolSelector = ["openai", "zgsm", "gemini-cli"].includes(selectedProvider) || selectedModelInfo?.supportsNativeTools === true diff --git a/webview-ui/src/components/settings/ProviderRenderer.tsx b/webview-ui/src/components/settings/ProviderRenderer.tsx index a65df3960d..879a914271 100644 --- a/webview-ui/src/components/settings/ProviderRenderer.tsx +++ b/webview-ui/src/components/settings/ProviderRenderer.tsx @@ -4,7 +4,7 @@ import { ModelPicker } from "./ModelPicker" import { type ProviderSettings, type ModelInfo, - zgsmModels, + zgsmModelsConfig as zgsmModels, zgsmDefaultModelId, openRouterDefaultModelId, requestyDefaultModelId, diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index f0c5951fde..ef8a1da450 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -214,6 +214,7 @@ const SettingsView = forwardRef(({ onDone, t openRouterImageApiKey, openRouterImageGenerationSelectedModel, reasoningBlockCollapsed, + showSpeedInfo, enterBehavior, includeCurrentTime, includeCurrentCost, @@ -413,6 +414,7 @@ const SettingsView = forwardRef(({ onDone, t condensingApiConfigId: condensingApiConfigId || "", includeTaskHistoryInEnhance: includeTaskHistoryInEnhance ?? true, reasoningBlockCollapsed: reasoningBlockCollapsed ?? true, + showSpeedInfo: showSpeedInfo ?? false, enterBehavior: enterBehavior ?? "send", includeCurrentTime: includeCurrentTime ?? true, includeCurrentCost: includeCurrentCost ?? true, @@ -876,7 +878,9 @@ const SettingsView = forwardRef(({ onDone, t {activeTab === "ui" && ( )} diff --git a/webview-ui/src/components/settings/UISettings.tsx b/webview-ui/src/components/settings/UISettings.tsx index b4e5a4e861..3aab79659d 100644 --- a/webview-ui/src/components/settings/UISettings.tsx +++ b/webview-ui/src/components/settings/UISettings.tsx @@ -11,13 +11,17 @@ import { ExtensionStateContextType } from "@/context/ExtensionStateContext" interface UISettingsProps extends HTMLAttributes { reasoningBlockCollapsed: boolean + showSpeedInfo: boolean enterBehavior: "send" | "newline" + apiConfiguration?: any setCachedStateField: SetCachedStateField } export const UISettings = ({ reasoningBlockCollapsed, + showSpeedInfo, enterBehavior, + apiConfiguration, setCachedStateField, ...props }: UISettingsProps) => { @@ -38,6 +42,16 @@ export const UISettings = ({ }) } + // showSpeedInfo + const handleShowSpeedInfoChange = (showSpeedInfo: boolean) => { + setCachedStateField("showSpeedInfo", showSpeedInfo) + + // Track telemetry event + telemetryClient.capture("ui_settings_show_speed_info_changed", { + enabled: showSpeedInfo, + }) + } + const handleEnterBehaviorChange = (requireCtrlEnter: boolean) => { const newBehavior = requireCtrlEnter ? "newline" : "send" setCachedStateField("enterBehavior", newBehavior) @@ -72,6 +86,20 @@ export const UISettings = ({
+ {/* Show Speed Info Setting */} + {apiConfiguration?.apiProvider === "zgsm" && ( +
+ handleShowSpeedInfoChange(e.target.checked)} + data-testid="show-speed-info-checkbox"> + {t("settings:ui.showSpeedInfo.label")} + +
+ {t("settings:ui.showSpeedInfo.description")} +
+
+ )} {/* Enter Key Behavior Setting */}
{ const defaultProps = { reasoningBlockCollapsed: false, + showSpeedInfo: false, + apiConfiguration: { + apiProvider: "zgsm", + }, enterBehavior: "send" as const, setCachedStateField: vi.fn(), } diff --git a/webview-ui/src/components/settings/providers/ZgsmAI.tsx b/webview-ui/src/components/settings/providers/ZgsmAI.tsx index 6f88a24c56..88414fb482 100644 --- a/webview-ui/src/components/settings/providers/ZgsmAI.tsx +++ b/webview-ui/src/components/settings/providers/ZgsmAI.tsx @@ -8,7 +8,7 @@ import { type ModelInfo, type ReasoningEffort, azureOpenAiDefaultApiVersion, - zgsmModels, + zgsmModelsConfig as zgsmModels, zgsmDefaultModelId, OrganizationAllowList, } from "@roo-code/types" diff --git a/webview-ui/src/components/ui/hooks/useSelectedModel.ts b/webview-ui/src/components/ui/hooks/useSelectedModel.ts index 24ae68c740..39b8eae759 100644 --- a/webview-ui/src/components/ui/hooks/useSelectedModel.ts +++ b/webview-ui/src/components/ui/hooks/useSelectedModel.ts @@ -27,7 +27,7 @@ import { internationalZAiModels, mainlandZAiModels, fireworksModels, - zgsmModels, + zgsmModelsConfig as zgsmModels, featherlessModels, ioIntelligenceModels, basetenModels, diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index e908fae964..53bce6a612 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -156,6 +156,8 @@ export interface ExtensionStateContextType extends ExtensionState { setTerminalCompressProgressBar: (value: boolean) => void setHistoryPreviewCollapsed: (value: boolean) => void setReasoningBlockCollapsed: (value: boolean) => void + setShowSpeedInfo: (value: boolean) => void + showSpeedInfo?: boolean enterBehavior?: "send" | "newline" setEnterBehavior: (value: "send" | "newline") => void autoCondenseContext: boolean @@ -264,6 +266,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode terminalCompressProgressBar: true, // Default to compress progress bar output historyPreviewCollapsed: false, // Initialize the new state (default to expanded) reasoningBlockCollapsed: true, // Default to collapsed + showSpeedInfo: false, // Default to not showing speed info enterBehavior: "send", // Default: Enter sends, Shift+Enter creates newline cloudUserInfo: null, cloudIsAuthenticated: false, @@ -521,6 +524,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode const contextValue: ExtensionStateContextType = { ...state, reasoningBlockCollapsed: state.reasoningBlockCollapsed ?? true, + showSpeedInfo: state.showSpeedInfo ?? false, didHydrateState, showWelcome, theme, @@ -647,6 +651,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode setState((prevState) => ({ ...prevState, historyPreviewCollapsed: value })), setReasoningBlockCollapsed: (value) => setState((prevState) => ({ ...prevState, reasoningBlockCollapsed: value })), + setShowSpeedInfo: (value) => setState((prevState) => ({ ...prevState, showSpeedInfo: value })), enterBehavior: state.enterBehavior ?? "send", setEnterBehavior: (value) => setState((prevState) => ({ ...prevState, enterBehavior: value })), setHasOpenedModeSelector: (value) => setState((prevState) => ({ ...prevState, hasOpenedModeSelector: value })), diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index 386ddf9f4d..a33f692a5e 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -2,8 +2,8 @@ "greeting": "Welcome to Roo Code!", "performance": { "firstToken": "First token arrival time", - "totalDuration": "Total duration", - "tokensPerSecond": "tokens/s" + "totalDuration": "SSE Total duration", + "tokensPerSecond": "~{{time}} token/s" }, "task": { "viewParentTask": "View Parent Task", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index f4dc606c86..44df55a4bf 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -66,6 +66,10 @@ "label": "Collapse Thinking messages by default", "description": "When enabled, thinking blocks will be collapsed by default until you interact with them" }, + "showSpeedInfo": { + "label": "Show speed info", + "description": "When enabled, shows token generation speed metrics in messages" + }, "requireCtrlEnterToSend": { "label": "Require {{primaryMod}}+Enter to send messages", "description": "When enabled, you must press {{primaryMod}}+Enter to send messages instead of just Enter" diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index 5dbd66d0c1..546a704cb4 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -2,8 +2,8 @@ "greeting": "欢迎使用 Roo Code", "performance": { "firstToken": "首token达到时间", - "totalDuration": "总耗时", - "tokensPerSecond": "tokens/s" + "totalDuration": "推送总耗时", + "tokensPerSecond": "约 {{time}} token/s" }, "task": { "viewParentTask": "查看父任务", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index 9d14652270..1118fa9e22 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -989,6 +989,10 @@ "label": "默认折叠「思考」消息", "description": "启用后,「思考」块将默认折叠,直到您与其交互" }, + "showSpeedInfo": { + "label": "显示速度信息", + "description": "启用后,在消息中显示 token 生成速度指标" + }, "requireCtrlEnterToSend": { "label": "需要 {{primaryMod}}+Enter 发送消息", "description": "启用后,必须按 {{primaryMod}}+Enter 发送消息,而不仅仅是 Enter" diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index c3fefb1d55..a0dd91b0fc 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -2,8 +2,8 @@ "greeting": "歡迎使用 Roo Code!", "performance": { "firstToken": "首token達到時間", - "totalDuration": "總耗時", - "tokensPerSecond": "tokens/s" + "totalDuration": "傳送總耗時", + "tokensPerSecond": "约 {{time}} token/s" }, "task": { "viewParentTask": "查看父工作", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index 222199e441..b882a3af0a 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -989,6 +989,10 @@ "label": "預設折疊「思考」訊息", "description": "啟用後,「思考」塊將預設折疊,直到您與其互動" }, + "showSpeedInfo": { + "label": "顯示速度資訊", + "description": "啟用後,在訊息中顯示 token 生成速度指標" + }, "requireCtrlEnterToSend": { "label": "需要 {{primaryMod}}+Enter 傳送訊息", "description": "啟用後,必須按 {{primaryMod}}+Enter 傳送訊息,而不只是 Enter"