@@ -228,44 +266,49 @@ export const ThinkingBudget = ({ apiConfiguration, setApiConfigurationField, mod
)}
>
) : isReasoningEffortSupported ? (
-
-
- {t("settings:providers.reasoningEffort.label")}
-
-
{
- // "disable" turns off reasoning entirely; "none" is a valid reasoning level
- if (value === "disable") {
- setApiConfigurationField("enableReasoningEffort", false)
- setApiConfigurationField("reasoningEffort", "disable")
- } else {
- // "none", "minimal", "low", "medium", "high" all enable reasoning
- setApiConfigurationField("enableReasoningEffort", true)
- setApiConfigurationField("reasoningEffort", value as ReasoningEffortWithMinimal)
- }
- }}>
-
-
+ {maxOutputTokensControl}
+
+
+ {t("settings:providers.reasoningEffort.label")}
+
+
{
+ // "disable" turns off reasoning entirely; "none" is a valid reasoning level
+ if (value === "disable") {
+ setApiConfigurationField("enableReasoningEffort", false)
+ setApiConfigurationField("reasoningEffort", "disable")
+ } else {
+ // "none", "minimal", "low", "medium", "high" all enable reasoning
+ setApiConfigurationField("enableReasoningEffort", true)
+ setApiConfigurationField("reasoningEffort", value as ReasoningEffortWithMinimal)
}
- />
-
-
- {availableOptions.map((value) => (
-
- {value === "none" || value === "disable"
- ? t("settings:providers.reasoningEffort.none")
- : t(`settings:providers.reasoningEffort.${value}`)}
-
- ))}
-
-
-
- ) : null
+ }}>
+
+
+
+
+ {availableOptions.map((value) => (
+
+ {value === "none" || value === "disable"
+ ? t("settings:providers.reasoningEffort.none")
+ : t(`settings:providers.reasoningEffort.${value}`)}
+
+ ))}
+
+
+
+ >
+ ) : (
+ maxOutputTokensControl
+ )
}
diff --git a/webview-ui/src/components/settings/__tests__/ApiOptions.spec.tsx b/webview-ui/src/components/settings/__tests__/ApiOptions.spec.tsx
index 3fae417e45..d62f6b1395 100644
--- a/webview-ui/src/components/settings/__tests__/ApiOptions.spec.tsx
+++ b/webview-ui/src/components/settings/__tests__/ApiOptions.spec.tsx
@@ -4,7 +4,7 @@ import { render, screen, fireEvent, within } from "@/utils/test-utils"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { type ModelInfo, type ProviderSettings, openAiModelInfoSaneDefaults } from "@roo-code/types"
-import { openAiCodexDefaultModelId } from "@roo-code/types"
+import { openAiCodexDefaultModelId, zooGatewayDefaultModelId } from "@roo-code/types"
import * as ExtensionStateContext from "@src/context/ExtensionStateContext"
const { ExtensionStateContextProvider } = ExtensionStateContext
@@ -300,6 +300,28 @@ describe("ApiOptions", () => {
expect(mockSetApiConfigurationField).toHaveBeenCalledWith("apiModelId", openAiCodexDefaultModelId, false)
})
+ it("initializes zooGatewayModelId to its default when switching provider to zoo-gateway", () => {
+ // Regression: zoo-gateway was previously missing from PROVIDER_MODEL_CONFIG, so switching
+ // providers never seeded zooGatewayModelId. Configs were left without a model id, which
+ // blocked completion flows that require a dynamic-provider model id.
+ const mockSetApiConfigurationField = vi.fn()
+
+ renderApiOptions({
+ apiConfiguration: {
+ apiProvider: "anthropic",
+ // No prior zooGatewayModelId.
+ },
+ setApiConfigurationField: mockSetApiConfigurationField,
+ })
+
+ const providerSelectContainer = screen.getByTestId("provider-select")
+ const providerSelect = providerSelectContainer.querySelector("select") as HTMLSelectElement
+ fireEvent.change(providerSelect, { target: { value: "zoo-gateway" } })
+
+ expect(mockSetApiConfigurationField).toHaveBeenCalledWith("apiProvider", "zoo-gateway")
+ expect(mockSetApiConfigurationField).toHaveBeenCalledWith("zooGatewayModelId", zooGatewayDefaultModelId, false)
+ })
+
it("shows temperature and rate limit controls by default", () => {
renderApiOptions({
apiConfiguration: {},
diff --git a/webview-ui/src/components/settings/__tests__/SettingsView.change-detection.spec.tsx b/webview-ui/src/components/settings/__tests__/SettingsView.change-detection.spec.tsx
index 42239e33a3..cb5dc8ec28 100644
--- a/webview-ui/src/components/settings/__tests__/SettingsView.change-detection.spec.tsx
+++ b/webview-ui/src/components/settings/__tests__/SettingsView.change-detection.spec.tsx
@@ -286,6 +286,7 @@ describe("SettingsView - Change Detection Fix", () => {
terminalZshOhMy: false,
terminalZshP10k: false,
terminalZdotdir: false,
+ terminalProfile: undefined,
writeDelayMs: 0,
showRooIgnoredFiles: false,
maxReadFileLine: -1,
diff --git a/webview-ui/src/components/settings/__tests__/TerminalSettings.profile.spec.tsx b/webview-ui/src/components/settings/__tests__/TerminalSettings.profile.spec.tsx
new file mode 100644
index 0000000000..350ab78b05
--- /dev/null
+++ b/webview-ui/src/components/settings/__tests__/TerminalSettings.profile.spec.tsx
@@ -0,0 +1,231 @@
+// npx vitest run src/components/settings/__tests__/TerminalSettings.profile.spec.tsx
+
+import * as React from "react"
+
+import { render, screen, fireEvent, act } from "@/utils/test-utils"
+
+import { TerminalSettings } from "../TerminalSettings"
+
+// Mock translation hook to echo keys
+vi.mock("@/i18n/TranslationContext", () => ({
+ useAppTranslation: () => ({ t: (key: string) => key }),
+}))
+
+vi.mock("@src/utils/docLinks", () => ({
+ buildDocLink: () => "https://example.com",
+}))
+
+const postMessageMock = vi.fn()
+vi.mock("@/utils/vscode", () => ({
+ vscode: { postMessage: (...args: any[]) => postMessageMock(...args) },
+}))
+
+// Render Select as a list of buttons so we can drive onValueChange in tests.
+vi.mock("@/components/ui", () => ({
+ Select: ({ children, value, onValueChange, "data-testid": testId }: any) => (
+
+ {renderSelectChildren(children, onValueChange)}
+
+ ),
+ SelectTrigger: ({ children, ...rest }: any) =>
{children}
,
+ SelectValue: ({ children }: any) =>
{children}
,
+ SelectContent: ({ children }: any) =>
{children}
,
+ SelectItem: ({ children, value }: any) =>
{children}
,
+ Slider: ({ value, onValueChange }: any) => (
+
onValueChange([parseFloat(e.target.value)])} />
+ ),
+}))
+
+vi.mock("@vscode/webview-ui-toolkit/react", () => ({
+ VSCodeCheckbox: ({ checked, onChange, children }: any) => (
+
+ onChange?.(e)} />
+ {children}
+
+ ),
+ VSCodeLink: ({ children }: any) =>
{children} ,
+ VSCodeButton: ({ children, onClick, ...rest }: any) => (
+
+ {children}
+
+ ),
+}))
+
+// Helper used by the Select mock to render SelectItem children as buttons.
+function renderSelectChildren(children: any, onValueChange: (value: string) => void): any {
+ return React.Children.map(children, (child: any) => {
+ if (!child || typeof child !== "object") return child
+ const itemValue = child.props?.value ?? child.props?.["data-item-value"]
+ if (child.props?.children && itemValue === undefined) {
+ return renderSelectChildren(child.props.children, onValueChange)
+ }
+ if (itemValue !== undefined) {
+ return (
+
onValueChange(itemValue)}>
+ {child.props.children}
+
+ )
+ }
+ return child
+ })
+}
+
+describe("TerminalSettings VS Code terminal profile (#277)", () => {
+ beforeEach(() => {
+ postMessageMock.mockClear()
+ })
+
+ // The profile section applies to the VS Code integrated terminal (terminalShellIntegrationDisabled === false).
+ const setup = (terminalProfile?: string) => {
+ const setCachedStateField = vi.fn()
+ const onTerminalProfilePickerOpened = vi.fn()
+ render(
+
,
+ )
+ return { onTerminalProfilePickerOpened, setCachedStateField }
+ }
+
+ it("requests the terminal profile names on mount via the allowlisted message", () => {
+ setup()
+ const types = postMessageMock.mock.calls.map((c) => c[0]?.type)
+ expect(types).toContain("requestTerminalProfiles")
+ })
+
+ it("shows the default radio selected and no dropdown when no profile is set", () => {
+ setup()
+ const defaultRadio = screen.getByTestId("terminal-profile-default-radio")
+ expect(defaultRadio).toBeChecked()
+ expect(screen.queryByTestId("terminal-profile-dropdown")).not.toBeInTheDocument()
+ })
+
+ it("shows the override radio selected and dropdown when a profile is set and profiles are available", () => {
+ setup("Git Bash")
+ act(() => {
+ window.dispatchEvent(
+ new MessageEvent("message", {
+ data: { type: "terminalProfiles", profiles: ["Git Bash", "zsh"] },
+ }),
+ )
+ })
+ const overrideRadio = screen.getByTestId("terminal-profile-override-radio")
+ expect(overrideRadio).toBeChecked()
+ expect(screen.getByTestId("terminal-profile-dropdown")).toBeInTheDocument()
+ })
+
+ it("keeps a saved profile selected while profile names are loading", () => {
+ const { setCachedStateField } = setup("Git Bash")
+
+ expect(screen.getByTestId("terminal-profile-override-radio")).toBeChecked()
+ expect(screen.queryByTestId("terminal-profile-dropdown")).not.toBeInTheDocument()
+ expect(setCachedStateField).not.toHaveBeenCalled()
+ })
+
+ it("falls back to the default radio and clears an unavailable saved profile after profiles load", () => {
+ const { setCachedStateField } = setup("Git Bash")
+ act(() => {
+ window.dispatchEvent(
+ new MessageEvent("message", {
+ data: { type: "terminalProfiles", profiles: ["Command Prompt"] },
+ }),
+ )
+ })
+
+ expect(screen.getByTestId("terminal-profile-default-radio")).toBeChecked()
+ expect(screen.getByTestId("terminal-profile-override-radio")).not.toBeChecked()
+ expect(screen.queryByTestId("terminal-profile-dropdown")).not.toBeInTheDocument()
+ expect(setCachedStateField).toHaveBeenCalledWith("terminalProfile", undefined)
+ })
+
+ it("uses instance-local radio groups", () => {
+ render(
+ <>
+
+
+ >,
+ )
+
+ const defaultRadios = screen.getAllByTestId("terminal-profile-default-radio")
+ expect(defaultRadios[0]).toBeChecked()
+ expect(defaultRadios[1]).toBeChecked()
+ expect(defaultRadios[0]).not.toHaveAttribute("name", defaultRadios[1].getAttribute("name"))
+ })
+
+ it("populates the dropdown from received profile names and selecting one sets the profile", () => {
+ const { setCachedStateField } = setup("Git Bash")
+
+ act(() => {
+ window.dispatchEvent(
+ new MessageEvent("message", {
+ data: { type: "terminalProfiles", profiles: ["Git Bash", "zsh"] },
+ }),
+ )
+ })
+
+ fireEvent.click(screen.getByTestId("option-zsh"))
+ expect(setCachedStateField).toHaveBeenCalledWith("terminalProfile", "zsh")
+ })
+
+ it("clicking default radio sets terminalProfile to undefined", () => {
+ const { setCachedStateField } = setup("Git Bash")
+ fireEvent.click(screen.getByTestId("terminal-profile-default-radio"))
+ expect(setCachedStateField).toHaveBeenCalledWith("terminalProfile", undefined)
+ })
+
+ it("renders the native profile configure button and posts openTerminalProfilePicker when clicked", () => {
+ const { onTerminalProfilePickerOpened, setCachedStateField } = setup("Git Bash")
+ const btn = screen.getByTestId("terminal-profile-configure-button")
+ expect(btn).toBeInTheDocument()
+ fireEvent.click(btn)
+ expect(onTerminalProfilePickerOpened).toHaveBeenCalledTimes(1)
+ expect(postMessageMock).toHaveBeenCalledWith({ type: "openTerminalProfilePicker" })
+ expect(setCachedStateField).not.toHaveBeenCalledWith("terminalProfile", undefined)
+ })
+
+ it("shows picker section when VS Code integrated terminal is active (shell integration enabled)", () => {
+ render(
)
+ expect(screen.getByTestId("terminal-profile-default-radio")).toBeInTheDocument()
+ })
+
+ it("hides picker section when inline/Execa execution is active (shell integration disabled)", () => {
+ render(
)
+ expect(screen.queryByTestId("terminal-profile-default-radio")).not.toBeInTheDocument()
+ })
+
+ it("hides picker section when terminalShellIntegrationDisabled is undefined (defaults to inline mode)", () => {
+ render(
)
+ expect(screen.queryByTestId("terminal-profile-default-radio")).not.toBeInTheDocument()
+ expect(screen.queryByText("settings:terminal.inheritEnv.label")).not.toBeInTheDocument()
+ })
+
+ it("shows the command delay default as 0ms", () => {
+ render(
)
+ expect(screen.getByText("0ms")).toBeInTheDocument()
+ })
+
+ it("disables override radio and shows hint when no profiles are available", () => {
+ setup()
+ // No terminalProfiles message dispatched → profileNames stays []
+ const overrideRadio = screen.getByTestId("terminal-profile-override-radio")
+ expect(overrideRadio).toBeDisabled()
+ expect(screen.getByTestId("terminal-profile-no-profiles-hint")).toBeInTheDocument()
+ })
+
+ it("enables override radio after profiles are received", () => {
+ setup()
+ act(() => {
+ window.dispatchEvent(
+ new MessageEvent("message", {
+ data: { type: "terminalProfiles", profiles: ["zsh"] },
+ }),
+ )
+ })
+ const overrideRadio = screen.getByTestId("terminal-profile-override-radio")
+ expect(overrideRadio).not.toBeDisabled()
+ expect(screen.queryByTestId("terminal-profile-no-profiles-hint")).not.toBeInTheDocument()
+ })
+})
diff --git a/webview-ui/src/components/settings/__tests__/ThinkingBudget.spec.tsx b/webview-ui/src/components/settings/__tests__/ThinkingBudget.spec.tsx
index 141d518feb..904822f787 100644
--- a/webview-ui/src/components/settings/__tests__/ThinkingBudget.spec.tsx
+++ b/webview-ui/src/components/settings/__tests__/ThinkingBudget.spec.tsx
@@ -305,4 +305,102 @@ describe("ThinkingBudget", () => {
expect(screen.getByTestId("select-item-high")).toBeInTheDocument()
})
})
+
+ describe("configurable max output tokens (supportsMaxTokens)", () => {
+ // Mirrors Z.ai GLM models: max output budget plus a reasoning-effort dropdown,
+ // but no reasoning-budget control.
+ const glmModelInfo: ModelInfo = {
+ supportsMaxTokens: true,
+ supportsReasoningEffort: ["disable", "medium"],
+ maxTokens: 131072,
+ contextWindow: 200000,
+ supportsPromptCache: true,
+ }
+
+ const glmApiConfiguration = { apiProvider: "zai", apiModelId: "glm-5.1" } as const
+
+ it("should render the max output tokens slider alongside the reasoning effort dropdown", () => {
+ render(
)
+
+ expect(screen.getByTestId("max-output-tokens")).toBeInTheDocument()
+ expect(screen.getByTestId("reasoning-effort")).toBeInTheDocument()
+ })
+
+ it("should default the slider to the 20% clamp when modelMaxTokens is unset", () => {
+ render(
)
+
+ // 20% of 200000 = 40000 (the runtime clamp), since maxTokens (131072) exceeds it.
+ const slider = screen.getByTestId("max-output-tokens").querySelector("input[type='range']")!
+ expect(slider).toHaveValue("40000")
+ })
+
+ it("should reflect an explicit modelMaxTokens override on the slider", () => {
+ render(
+
,
+ )
+
+ const slider = screen.getByTestId("max-output-tokens").querySelector("input[type='range']")!
+ expect(slider).toHaveValue("100000")
+ })
+
+ it("should NOT persist modelMaxTokens on initial render (no user action)", () => {
+ const setApiConfigurationField = vi.fn()
+ render(
+
,
+ )
+
+ // Initialization must not write the default clamp back to settings.
+ expect(setApiConfigurationField).not.toHaveBeenCalledWith("modelMaxTokens", expect.anything())
+ expect(setApiConfigurationField).not.toHaveBeenCalledWith(
+ "modelMaxTokens",
+ expect.anything(),
+ expect.anything(),
+ )
+ })
+
+ it("should persist modelMaxTokens as a user action when the slider changes", () => {
+ const setApiConfigurationField = vi.fn()
+ render(
+
,
+ )
+
+ const slider = screen.getByTestId("max-output-tokens").querySelector("input[type='range']")!
+ fireEvent.change(slider, { target: { value: "65536" } })
+
+ // A real user edit persists modelMaxTokens without the isUserAction=false flag.
+ expect(setApiConfigurationField).toHaveBeenCalledWith("modelMaxTokens", 65536)
+ })
+
+ it("should not render the standalone slider when supportsMaxTokens is absent", () => {
+ render(
+
,
+ )
+
+ expect(screen.queryByTestId("max-output-tokens")).not.toBeInTheDocument()
+ expect(screen.getByTestId("reasoning-effort")).toBeInTheDocument()
+ })
+ })
})
diff --git a/webview-ui/src/components/settings/constants.ts b/webview-ui/src/components/settings/constants.ts
index 6769bda65d..566370c837 100644
--- a/webview-ui/src/components/settings/constants.ts
+++ b/webview-ui/src/components/settings/constants.ts
@@ -64,6 +64,7 @@ export const PROVIDERS = [
{ value: "fireworks", label: "Fireworks AI", proxy: false },
{ value: "vercel-ai-gateway", label: "Vercel AI Gateway", proxy: false },
{ value: "opencode-go", label: "Opencode Go", proxy: false },
+ { value: "zoo-gateway", label: "Zoo Gateway", proxy: false },
{ value: "minimax", label: "MiniMax", proxy: false },
{ value: "mimo", label: "Xiaomi MiMo", proxy: false },
{ value: "baseten", label: "Baseten", proxy: false },
diff --git a/webview-ui/src/components/settings/providers/ZooGateway.tsx b/webview-ui/src/components/settings/providers/ZooGateway.tsx
new file mode 100644
index 0000000000..ac99f464a4
--- /dev/null
+++ b/webview-ui/src/components/settings/providers/ZooGateway.tsx
@@ -0,0 +1,125 @@
+import { useEffect, useMemo } from "react"
+import {
+ type ProviderSettings,
+ type OrganizationAllowList,
+ type RouterModels,
+ zooGatewayDefaultModelId,
+} from "@roo-code/types"
+
+import { useExtensionState } from "@src/context/ExtensionStateContext"
+import { getZooCodeAuthUrl } from "@src/oauth/urls"
+import { useAppTranslation } from "@src/i18n/TranslationContext"
+import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink"
+
+import { ModelPicker } from "../ModelPicker"
+import { ApiErrorMessage } from "../ApiErrorMessage"
+
+type ZooGatewayProps = {
+ apiConfiguration: ProviderSettings
+ setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void
+ routerModels?: RouterModels
+ organizationAllowList: OrganizationAllowList
+ modelValidationError?: string
+ simplifySettings?: boolean
+}
+
+function isClaudeSonnetModelId(id: string) {
+ return /claude.*sonnet/i.test(id)
+}
+
+// Exported for unit tests. Picks the default Zoo Gateway model id, preferring
+// Claude Sonnet 4.5 → Sonnet 4 → first available Sonnet → first model overall.
+export function pickZooGatewayDefaultModelId(modelIds: string[]) {
+ if (modelIds.length === 0) {
+ return zooGatewayDefaultModelId
+ }
+
+ const sonnets = modelIds.filter(isClaudeSonnetModelId)
+ if (sonnets.length === 0) {
+ return modelIds[0]
+ }
+
+ return (
+ sonnets.find((id) => id === "anthropic/claude-sonnet-4.5") ??
+ sonnets.find((id) => id.includes("claude-sonnet-4.5")) ??
+ sonnets.find((id) => /sonnet-4[.-]5/i.test(id)) ??
+ sonnets.find((id) => /sonnet-4(?![.-]?\d)/i.test(id)) ??
+ sonnets[0]
+ )
+}
+
+export const ZooGateway = ({
+ apiConfiguration,
+ setApiConfigurationField,
+ routerModels,
+ organizationAllowList,
+ modelValidationError,
+ simplifySettings,
+}: ZooGatewayProps) => {
+ const { t } = useAppTranslation()
+ const { zooCodeIsAuthenticated, zooCodeUserEmail, zooCodeUserName, zooCodeBaseUrl, uriScheme, deviceName } =
+ useExtensionState()
+
+ const authUrl = getZooCodeAuthUrl(uriScheme, zooCodeBaseUrl, deviceName)
+ const resolvedDashboardBase = zooCodeBaseUrl?.replace(/\/$/, "") || "https://www.zoocode.dev"
+
+ const zooModels = useMemo(() => routerModels?.["zoo-gateway"] ?? {}, [routerModels])
+ const modelIds = useMemo(() => Object.keys(zooModels), [zooModels])
+ const resolvedDefaultModelId = useMemo(() => pickZooGatewayDefaultModelId(modelIds), [modelIds])
+
+ useEffect(() => {
+ if (modelIds.length === 0) {
+ return
+ }
+
+ const current = apiConfiguration.zooGatewayModelId
+ if (!current || !modelIds.includes(current)) {
+ setApiConfigurationField("zooGatewayModelId", resolvedDefaultModelId)
+ }
+ }, [apiConfiguration.zooGatewayModelId, modelIds, resolvedDefaultModelId, setApiConfigurationField])
+
+ return (
+ <>
+
+
+ {t("settings:providers.zooGateway.account")}
+ {zooCodeIsAuthenticated && zooCodeUserEmail && (
+ {zooCodeUserEmail}
+ )}
+
+ {!zooCodeIsAuthenticated ? (
+
+
+
+ {t("settings:providers.zooGateway.signInDescription")}
+
+
+ {t("settings:providers.zooGateway.signInButton")}
+
+
+ ) : (
+
+
+
+ {zooCodeUserName
+ ? t("settings:providers.zooGateway.authenticatedAs", { name: zooCodeUserName })
+ : t("settings:providers.zooGateway.authenticated")}
+
+
+ )}
+
+
+ >
+ )
+}
diff --git a/webview-ui/src/components/settings/providers/__tests__/ZooGateway.spec.tsx b/webview-ui/src/components/settings/providers/__tests__/ZooGateway.spec.tsx
new file mode 100644
index 0000000000..9bdda1f433
--- /dev/null
+++ b/webview-ui/src/components/settings/providers/__tests__/ZooGateway.spec.tsx
@@ -0,0 +1,189 @@
+import React from "react"
+import { render, screen, waitFor } from "@/utils/test-utils"
+import type { ModelInfo, ProviderSettings, RouterModels } from "@roo-code/types"
+
+import { ZooGateway, pickZooGatewayDefaultModelId } from "../ZooGateway"
+
+vi.mock("@src/i18n/TranslationContext", () => ({
+ useAppTranslation: () => ({
+ t: (key: string) => key,
+ }),
+}))
+
+const extensionStateMock = {
+ zooCodeIsAuthenticated: true,
+ zooCodeUserEmail: "user@example.com",
+ zooCodeUserName: "User",
+ zooCodeBaseUrl: "https://www.zoocode.dev",
+ uriScheme: "vscode",
+ deviceName: "Test Device",
+}
+
+vi.mock("@src/context/ExtensionStateContext", () => ({
+ useExtensionState: () => extensionStateMock,
+}))
+
+vi.mock("@src/oauth/urls", () => ({
+ getZooCodeAuthUrl: () => "https://www.zoocode.dev/dashboard/connect",
+}))
+
+vi.mock("../../ModelPicker", () => ({
+ ModelPicker: ({ defaultModelId }: { defaultModelId: string }) => (
+
+ ),
+}))
+
+const baseInfo: ModelInfo = {
+ maxTokens: 8192,
+ contextWindow: 200000,
+ supportsImages: false,
+ supportsPromptCache: false,
+ inputPrice: 1,
+ outputPrice: 2,
+}
+
+function buildRouterModels(modelIds: string[]): RouterModels {
+ const models = Object.fromEntries(modelIds.map((id) => [id, baseInfo]))
+ return { "zoo-gateway": models } as unknown as RouterModels
+}
+
+describe("pickZooGatewayDefaultModelId", () => {
+ it("falls back to the static default when the catalog is empty", () => {
+ expect(pickZooGatewayDefaultModelId([])).toBe("anthropic/claude-sonnet-4")
+ })
+
+ it("prefers an exact anthropic/claude-sonnet-4.5 match", () => {
+ const result = pickZooGatewayDefaultModelId([
+ "anthropic/claude-sonnet-4",
+ "anthropic/claude-sonnet-4.5",
+ "openai/gpt-4o",
+ ])
+ expect(result).toBe("anthropic/claude-sonnet-4.5")
+ })
+
+ it("matches a Bedrock-style claude-sonnet-4-5 id", () => {
+ const result = pickZooGatewayDefaultModelId([
+ "anthropic.claude-sonnet-4-20250514-v1:0",
+ "anthropic.claude-sonnet-4-5-20250929-v1:0",
+ ])
+ expect(result).toBe("anthropic.claude-sonnet-4-5-20250929-v1:0")
+ })
+
+ it("falls back to claude sonnet 4 when 4.5 is not in the catalog", () => {
+ const result = pickZooGatewayDefaultModelId(["openai/gpt-4o", "anthropic/claude-sonnet-4"])
+ expect(result).toBe("anthropic/claude-sonnet-4")
+ })
+
+ it("falls back to the first available id when no claude sonnet is present", () => {
+ const result = pickZooGatewayDefaultModelId(["openai/gpt-4o", "google/gemini-2.5-pro"])
+ expect(result).toBe("openai/gpt-4o")
+ })
+})
+
+describe("ZooGateway component", () => {
+ const baseProps = {
+ organizationAllowList: { allowAll: true, providers: {} } as ProviderSettings extends never ? never : any,
+ setApiConfigurationField: vi.fn(),
+ }
+
+ beforeEach(() => {
+ vi.clearAllMocks()
+ })
+
+ it("auto-selects the resolved default model when the profile has no model id", async () => {
+ const setApiConfigurationField = vi.fn()
+ render(
+
,
+ )
+
+ await waitFor(() => {
+ expect(setApiConfigurationField).toHaveBeenCalledWith("zooGatewayModelId", "anthropic/claude-sonnet-4.5")
+ })
+ })
+
+ it("reassigns a stale model id that is not in the catalog", async () => {
+ const setApiConfigurationField = vi.fn()
+ render(
+
,
+ )
+
+ await waitFor(() => {
+ expect(setApiConfigurationField).toHaveBeenCalledWith(
+ "zooGatewayModelId",
+ "anthropic.claude-sonnet-4-5-20250929-v1:0",
+ )
+ })
+ })
+
+ it("does not overwrite a model id that is already valid for the catalog", async () => {
+ const setApiConfigurationField = vi.fn()
+ render(
+
,
+ )
+
+ await waitFor(() => {
+ expect(setApiConfigurationField).not.toHaveBeenCalled()
+ })
+ })
+
+ it("does nothing while the catalog is still empty (router models loading)", () => {
+ const setApiConfigurationField = vi.fn()
+ render(
+
,
+ )
+
+ expect(setApiConfigurationField).not.toHaveBeenCalled()
+ })
+
+ it("renders the sign-in validation error inline when not authenticated", () => {
+ const original = extensionStateMock.zooCodeIsAuthenticated
+ extensionStateMock.zooCodeIsAuthenticated = false
+ try {
+ render(
+
,
+ )
+
+ expect(screen.getByText("settings:validation.zooGatewaySignIn")).toBeInTheDocument()
+ } finally {
+ extensionStateMock.zooCodeIsAuthenticated = original
+ }
+ })
+})
diff --git a/webview-ui/src/components/settings/providers/index.ts b/webview-ui/src/components/settings/providers/index.ts
index 1e10979c63..d5dd0d0ded 100644
--- a/webview-ui/src/components/settings/providers/index.ts
+++ b/webview-ui/src/components/settings/providers/index.ts
@@ -23,6 +23,7 @@ export { LiteLLM } from "./LiteLLM"
export { Fireworks } from "./Fireworks"
export { VercelAiGateway } from "./VercelAiGateway"
export { OpenCodeGo } from "./OpenCodeGo"
+export { ZooGateway } from "./ZooGateway"
export { MiniMax } from "./MiniMax"
export { Mimo } from "./Mimo"
export { Baseten } from "./Baseten"
diff --git a/webview-ui/src/components/settings/utils/providerModelConfig.ts b/webview-ui/src/components/settings/utils/providerModelConfig.ts
index e7eefacb01..9cc9dafa01 100644
--- a/webview-ui/src/components/settings/utils/providerModelConfig.ts
+++ b/webview-ui/src/components/settings/utils/providerModelConfig.ts
@@ -6,7 +6,9 @@ import {
moonshotDefaultModelId,
geminiDefaultModelId,
mistralDefaultModelId,
+ openRouterDefaultModelId,
openAiNativeDefaultModelId,
+ openAiCodexDefaultModelId,
qwenCodeDefaultModelId,
vertexDefaultModelId,
xaiDefaultModelId,
@@ -17,6 +19,13 @@ import {
minimaxDefaultModelId,
basetenDefaultModelId,
mimoDefaultModelId,
+ poeDefaultModelId,
+ requestyDefaultModelId,
+ unboundDefaultModelId,
+ litellmDefaultModelId,
+ vercelAiGatewayDefaultModelId,
+ opencodeGoDefaultModelId,
+ zooGatewayDefaultModelId,
} from "@roo-code/types"
import { MODELS_BY_PROVIDER } from "../constants"
@@ -85,6 +94,68 @@ export const getDefaultModelIdForProvider = (provider: ProviderName, apiConfigur
return PROVIDER_DEFAULT_MODEL_IDS[provider] ?? ""
}
+export type ProviderModelConfig = {
+ field: keyof ProviderSettings
+ default?: string
+}
+
+// Minimal per-provider config used by ApiOptions for model-id field wiring.
+// Kept in this file to keep ApiOptions.tsx from growing a second registry.
+const PROVIDER_MODEL_CONFIG: Partial
> = {
+ openrouter: { field: "openRouterModelId", default: openRouterDefaultModelId },
+ requesty: { field: "requestyModelId", default: requestyDefaultModelId },
+ unbound: { field: "unboundModelId", default: unboundDefaultModelId },
+ litellm: { field: "litellmModelId", default: litellmDefaultModelId },
+ anthropic: { field: "apiModelId", default: anthropicDefaultModelId },
+ "openai-codex": { field: "apiModelId", default: openAiCodexDefaultModelId },
+ "qwen-code": { field: "apiModelId", default: qwenCodeDefaultModelId },
+ "openai-native": { field: "apiModelId", default: openAiNativeDefaultModelId },
+ gemini: { field: "apiModelId", default: geminiDefaultModelId },
+ deepseek: { field: "apiModelId", default: deepSeekDefaultModelId },
+ moonshot: { field: "apiModelId", default: moonshotDefaultModelId },
+ minimax: { field: "apiModelId", default: minimaxDefaultModelId },
+ mimo: { field: "apiModelId", default: mimoDefaultModelId },
+ mistral: { field: "apiModelId", default: mistralDefaultModelId },
+ xai: { field: "apiModelId", default: xaiDefaultModelId },
+ baseten: { field: "apiModelId", default: basetenDefaultModelId },
+ bedrock: { field: "apiModelId", default: bedrockDefaultModelId },
+ vertex: { field: "apiModelId", default: vertexDefaultModelId },
+ sambanova: { field: "apiModelId", default: sambaNovaDefaultModelId },
+ zai: { field: "apiModelId" },
+ fireworks: { field: "apiModelId", default: fireworksDefaultModelId },
+ poe: { field: "apiModelId", default: poeDefaultModelId },
+ "vercel-ai-gateway": { field: "vercelAiGatewayModelId", default: vercelAiGatewayDefaultModelId },
+ "opencode-go": { field: "opencodeGoModelId", default: opencodeGoDefaultModelId },
+ "zoo-gateway": { field: "zooGatewayModelId", default: zooGatewayDefaultModelId },
+ openai: { field: "openAiModelId" },
+ ollama: { field: "ollamaModelId" },
+ lmstudio: { field: "lmStudioModelId" },
+}
+
+export function getProviderModelConfig(provider: string, apiConfiguration?: ProviderSettings) {
+ const config = PROVIDER_MODEL_CONFIG[provider as ProviderName]
+ if (!config) return undefined
+
+ if (provider === "zai") {
+ return {
+ ...config,
+ default: getDefaultModelIdForProvider(provider as ProviderName, apiConfiguration),
+ }
+ }
+
+ return config
+}
+
+// Custom mapping for doc URL slugs. Default is provider key.
+const PROVIDER_DOCS_SLUGS: Partial> = {
+ "openai-native": "openai",
+ openai: "openai-compatible",
+}
+
+export function getProviderDocsSlug(provider: string) {
+ return PROVIDER_DOCS_SLUGS[provider as ProviderName] ?? provider
+}
+
export const getStaticModelsForProvider = (
provider: ProviderName,
customArnLabel?: string,
diff --git a/webview-ui/src/components/ui/hooks/useSelectedModel.ts b/webview-ui/src/components/ui/hooks/useSelectedModel.ts
index 62d6213722..d3ebb6c0dd 100644
--- a/webview-ui/src/components/ui/hooks/useSelectedModel.ts
+++ b/webview-ui/src/components/ui/hooks/useSelectedModel.ts
@@ -358,6 +358,15 @@ function getSelectedModel({
const info = routerModels["opencode-go"]?.[id] ?? opencodeGoDefaultModelInfo
return { id, info }
}
+ case "zoo-gateway": {
+ const id = getValidatedModelId(
+ apiConfiguration.zooGatewayModelId,
+ routerModels["zoo-gateway"],
+ defaultModelId,
+ )
+ const info = routerModels["zoo-gateway"]?.[id]
+ return { id, info }
+ }
// case "anthropic":
// case "fake-ai":
default: {
diff --git a/webview-ui/src/components/welcome/WelcomeViewProvider.tsx b/webview-ui/src/components/welcome/WelcomeViewProvider.tsx
index 7667d32ad6..26033739d5 100644
--- a/webview-ui/src/components/welcome/WelcomeViewProvider.tsx
+++ b/webview-ui/src/components/welcome/WelcomeViewProvider.tsx
@@ -20,14 +20,17 @@ const DEFAULT_WELCOME_API_CONFIGURATION: ProviderSettings = {
openRouterModelId: openRouterDefaultModelId,
}
-const getWelcomeApiConfiguration = (apiConfiguration?: ProviderSettings): ProviderSettings => {
+const getWelcomeApiConfiguration = (
+ apiConfiguration?: ProviderSettings,
+ zooCodeIsAuthenticated?: boolean,
+): ProviderSettings => {
// validateApiConfiguration treats a missing apiProvider as valid (no switch case matches),
// so we explicitly fall back here before delegating to it for incomplete-but-set configs.
if (!apiConfiguration?.apiProvider) {
return DEFAULT_WELCOME_API_CONFIGURATION
}
- const validationError = validateApiConfiguration(apiConfiguration)
+ const validationError = validateApiConfiguration(apiConfiguration, undefined, undefined, zooCodeIsAuthenticated)
if (validationError) {
return DEFAULT_WELCOME_API_CONFIGURATION
}
@@ -36,12 +39,14 @@ const getWelcomeApiConfiguration = (apiConfiguration?: ProviderSettings): Provid
}
const WelcomeViewProvider = () => {
- const { apiConfiguration, currentApiConfigName, setApiConfiguration, uriScheme } = useExtensionState()
+ const { apiConfiguration, currentApiConfigName, setApiConfiguration, uriScheme, zooCodeIsAuthenticated } =
+ useExtensionState()
const { t } = useAppTranslation()
const [errorMessage, setErrorMessage] = useState(undefined)
const [showProviderSetup, setShowProviderSetup] = useState(false)
const [welcomeApiConfiguration, setWelcomeApiConfiguration] = useState()
- const effectiveApiConfiguration = welcomeApiConfiguration ?? getWelcomeApiConfiguration(apiConfiguration)
+ const effectiveApiConfiguration =
+ welcomeApiConfiguration ?? getWelcomeApiConfiguration(apiConfiguration, zooCodeIsAuthenticated)
const setApiConfigurationFieldForApiOptions = useCallback(
(field: K, value: ProviderSettings[K]) => {
@@ -56,7 +61,7 @@ const WelcomeViewProvider = () => {
const handleGetStarted = useCallback(() => {
if (!showProviderSetup) {
- const initialApiConfiguration = getWelcomeApiConfiguration(apiConfiguration)
+ const initialApiConfiguration = getWelcomeApiConfiguration(apiConfiguration, zooCodeIsAuthenticated)
setWelcomeApiConfiguration(initialApiConfiguration)
setApiConfiguration(initialApiConfiguration)
@@ -65,7 +70,7 @@ const WelcomeViewProvider = () => {
return
}
- const error = validateApiConfiguration(effectiveApiConfiguration)
+ const error = validateApiConfiguration(effectiveApiConfiguration, undefined, undefined, zooCodeIsAuthenticated)
if (error) {
setErrorMessage(error)
@@ -78,7 +83,14 @@ const WelcomeViewProvider = () => {
text: currentApiConfigName,
apiConfiguration: effectiveApiConfiguration,
})
- }, [showProviderSetup, apiConfiguration, setApiConfiguration, effectiveApiConfiguration, currentApiConfigName])
+ }, [
+ showProviderSetup,
+ apiConfiguration,
+ setApiConfiguration,
+ effectiveApiConfiguration,
+ currentApiConfigName,
+ zooCodeIsAuthenticated,
+ ])
if (!showProviderSetup) {
return (
diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx
index 10b49e3de0..a60bd89db2 100644
--- a/webview-ui/src/context/ExtensionStateContext.tsx
+++ b/webview-ui/src/context/ExtensionStateContext.tsx
@@ -84,6 +84,7 @@ export interface ExtensionStateContextType extends ExtensionState {
setTerminalShellIntegrationDisabled: (value: boolean) => void
terminalZdotdir?: boolean
setTerminalZdotdir: (value: boolean) => void
+ terminalProfile?: string
setTtsEnabled: (value: boolean) => void
setTtsSpeed: (value: number) => void
setEnableCheckpoints: (value: boolean) => void
@@ -233,6 +234,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
terminalZshOhMy: false, // Default Oh My Zsh integration setting
terminalZshP10k: false, // Default Powerlevel10k integration setting
terminalZdotdir: false, // Default ZDOTDIR handling setting
+ terminalProfile: undefined, // Default VS Code terminal profile (use VS Code default)
historyPreviewCollapsed: false, // Initialize the new state (default to expanded)
reasoningBlockCollapsed: true, // Default to collapsed
enterBehavior: "send", // Default: Enter sends, Shift+Enter creates newline
diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json
index 6f952dfc97..8aec101137 100644
--- a/webview-ui/src/i18n/locales/ca/settings.json
+++ b/webview-ui/src/i18n/locales/ca/settings.json
@@ -560,6 +560,13 @@
"placeholder": "Per defecte: claude",
"maxTokensLabel": "Tokens màxims de sortida",
"maxTokensDescription": "Nombre màxim de tokens de sortida per a les respostes de Claude Code. El valor per defecte és 8000."
+ },
+ "zooGateway": {
+ "account": "Compte de Zoo Code",
+ "signInButton": "Iniciar sessió a Zoo Code",
+ "signInDescription": "Inicia sessió per utilitzar Zoo Gateway amb el teu compte",
+ "authenticated": "Autenticat",
+ "authenticatedAs": "Autenticat com a {{name}}"
}
},
"checkpoints": {
@@ -741,6 +748,14 @@
"inheritEnv": {
"label": "Hereta variables d'entorn",
"description": "Activa per heretar variables d'entorn del procés pare de VS Code. <0>Aprèn-ne més0>"
+ },
+ "profile": {
+ "label": "Substitució del terminal de Zoo Code",
+ "default": "Utilitza el perfil predeterminat de VS Code (recomanat)",
+ "description": "Per defecte, Zoo Code utilitza la shell que VS Code té configurada. Seleccioneu Substituir per triar un perfil de shell amb ruta explícita exposat per VS Code. Els perfils de només font (p. ex., l'entrada integrada de PowerShell) no es poden llistar aquí. <0>Més informació0>",
+ "overrideLabel": "Substituir la shell per a Zoo Code",
+ "configureButton": "Trieu el perfil predeterminat a VS Code",
+ "noProfiles": "(no s'han trobat perfils amb ruta a terminal.integrated.profiles)"
}
},
"advancedSettings": {
@@ -901,7 +916,8 @@
"providerNotAllowed": "El proveïdor '{{provider}}' no està permès per la vostra organització",
"modelNotAllowed": "El model '{{model}}' no està permès per al proveïdor '{{provider}}' per la vostra organització",
"profileInvalid": "Aquest perfil conté un proveïdor o model que no està permès per la vostra organització",
- "qwenCodeOauthPath": "Has de proporcionar una ruta vàlida de credencials OAuth"
+ "qwenCodeOauthPath": "Has de proporcionar una ruta vàlida de credencials OAuth",
+ "zooGatewaySignIn": "Inicia sessió a Zoo Code per utilitzar Zoo Gateway."
},
"placeholders": {
"apiKey": "Introduïu la clau API...",
diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json
index d9feec46ef..10bfe12f79 100644
--- a/webview-ui/src/i18n/locales/de/settings.json
+++ b/webview-ui/src/i18n/locales/de/settings.json
@@ -560,6 +560,13 @@
"placeholder": "Standard: claude",
"maxTokensLabel": "Maximale Ausgabe-Tokens",
"maxTokensDescription": "Maximale Anzahl an Ausgabe-Tokens für Claude Code-Antworten. Standard ist 8000."
+ },
+ "zooGateway": {
+ "account": "Zoo Code Konto",
+ "signInButton": "Bei Zoo Code anmelden",
+ "signInDescription": "Melde dich an, um Zoo Gateway mit deinem Konto zu nutzen",
+ "authenticated": "Authentifiziert",
+ "authenticatedAs": "Authentifiziert als {{name}}"
}
},
"checkpoints": {
@@ -741,6 +748,14 @@
"inheritEnv": {
"label": "Umgebungsvariablen erben",
"description": "Schalte dies ein, um Umgebungsvariablen vom übergeordneten VS Code-Prozess zu erben. <0>Mehr erfahren0>"
+ },
+ "profile": {
+ "label": "Zoo Code Terminal-Überschreibung",
+ "default": "VS Code-Standardprofil verwenden (empfohlen)",
+ "description": "Standardmäßig verwendet Zoo Code die in VS Code konfigurierte Shell. Wähle Überschreiben, um ein pfadbasiertes Shell-Profil zu wählen, das VS Code bereitstellt. Quellenbasierte Profile (z. B. der integrierte PowerShell-Eintrag) können hier nicht aufgelistet werden. <0>Mehr erfahren0>",
+ "overrideLabel": "Shell für Zoo Code überschreiben",
+ "configureButton": "Standardprofil in VS Code auswählen",
+ "noProfiles": "(keine pfadbasierten Profile in terminal.integrated.profiles gefunden)"
}
},
"advancedSettings": {
@@ -901,7 +916,8 @@
"providerNotAllowed": "Anbieter '{{provider}}' ist von deiner Organisation nicht erlaubt",
"modelNotAllowed": "Modell '{{model}}' ist für Anbieter '{{provider}}' von deiner Organisation nicht erlaubt",
"profileInvalid": "Dieses Profil enthält einen Anbieter oder ein Modell, das von deiner Organisation nicht erlaubt ist",
- "qwenCodeOauthPath": "Du musst einen gültigen OAuth-Anmeldedaten-Pfad angeben"
+ "qwenCodeOauthPath": "Du musst einen gültigen OAuth-Anmeldedaten-Pfad angeben",
+ "zooGatewaySignIn": "Melde dich bei Zoo Code an, um Zoo Gateway zu verwenden."
},
"placeholders": {
"apiKey": "API-Schlüssel eingeben...",
diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json
index d51f5da8c5..8c3f2190d2 100644
--- a/webview-ui/src/i18n/locales/en/settings.json
+++ b/webview-ui/src/i18n/locales/en/settings.json
@@ -548,6 +548,13 @@
"learnMore": "Learn more about provider routing"
}
},
+ "zooGateway": {
+ "account": "Zoo Code Account",
+ "signInButton": "Sign in to Zoo Code",
+ "signInDescription": "Sign in to use Zoo Gateway with your account",
+ "authenticated": "Authenticated",
+ "authenticatedAs": "Authenticated as {{name}}"
+ },
"customModel": {
"capabilities": "Configure the capabilities and pricing for your custom OpenAI-compatible model. Be careful when specifying the model capabilities, as they can affect how Zoo Code performs.",
"maxTokens": {
@@ -804,6 +811,14 @@
"inheritEnv": {
"label": "Inherit environment variables",
"description": "Turn this on to inherit environment variables from the parent VS Code process. <0>Learn more0>"
+ },
+ "profile": {
+ "label": "Zoo Code terminal override",
+ "default": "Use VS Code default profile (recommended)",
+ "overrideLabel": "Override shell for Zoo Code",
+ "configureButton": "Choose default profile in VS Code",
+ "noProfiles": "(no path-based profiles found in terminal.integrated.profiles)",
+ "description": "By default Zoo Code uses whatever shell VS Code is configured to use. Select Override to pick a path-based shell profile exposed by VS Code. Source-only profiles (e.g. the built-in PowerShell entry) cannot be listed here. <0>Learn more0>"
}
},
"advancedSettings": {
@@ -964,7 +979,8 @@
"providerNotAllowed": "Provider '{{provider}}' is not allowed by your organization",
"modelNotAllowed": "Model '{{model}}' is not allowed for provider '{{provider}}' by your organization",
"profileInvalid": "This profile contains a provider or model that is not allowed by your organization",
- "qwenCodeOauthPath": "You must provide a valid OAuth credentials path."
+ "qwenCodeOauthPath": "You must provide a valid OAuth credentials path.",
+ "zooGatewaySignIn": "Sign in to Zoo Code to use Zoo Gateway."
},
"placeholders": {
"apiKey": "Enter API Key...",
diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json
index e7ae567f17..64cd7eec58 100644
--- a/webview-ui/src/i18n/locales/es/settings.json
+++ b/webview-ui/src/i18n/locales/es/settings.json
@@ -560,6 +560,13 @@
"placeholder": "Por defecto: claude",
"maxTokensLabel": "Tokens máximos de salida",
"maxTokensDescription": "Número máximo de tokens de salida para las respuestas de Claude Code. El valor predeterminado es 8000."
+ },
+ "zooGateway": {
+ "account": "Cuenta de Zoo Code",
+ "signInButton": "Iniciar sesión en Zoo Code",
+ "signInDescription": "Inicia sesión para usar Zoo Gateway con tu cuenta",
+ "authenticated": "Autenticado",
+ "authenticatedAs": "Autenticado como {{name}}"
}
},
"checkpoints": {
@@ -741,6 +748,14 @@
"inheritEnv": {
"label": "Heredar variables de entorno",
"description": "Activa para heredar variables de entorno del proceso padre de VS Code. <0>Más información0>"
+ },
+ "profile": {
+ "label": "Anulación del terminal de Zoo Code",
+ "default": "Usar perfil predeterminado de VS Code (recomendado)",
+ "description": "De forma predeterminada, Zoo Code usa la shell que VS Code tiene configurada. Selecciona Anular para elegir un perfil de shell con ruta expuesto por VS Code. Los perfiles solo de fuente (p. ej., la entrada integrada de PowerShell) no se pueden listar aquí. <0>Más información0>",
+ "overrideLabel": "Anular shell para Zoo Code",
+ "configureButton": "Elegir perfil predeterminado en VS Code",
+ "noProfiles": "(no se encontraron perfiles con ruta en terminal.integrated.profiles)"
}
},
"advancedSettings": {
@@ -901,7 +916,8 @@
"providerNotAllowed": "El proveedor '{{provider}}' no está permitido por su organización",
"modelNotAllowed": "El modelo '{{model}}' no está permitido para el proveedor '{{provider}}' por su organización",
"profileInvalid": "Este perfil contiene un proveedor o modelo que no está permitido por su organización",
- "qwenCodeOauthPath": "Debes proporcionar una ruta válida de credenciales OAuth"
+ "qwenCodeOauthPath": "Debes proporcionar una ruta válida de credenciales OAuth",
+ "zooGatewaySignIn": "Inicia sesión en Zoo Code para usar Zoo Gateway."
},
"placeholders": {
"apiKey": "Ingrese clave API...",
diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json
index 5a7a940c09..518bcb50ba 100644
--- a/webview-ui/src/i18n/locales/fr/settings.json
+++ b/webview-ui/src/i18n/locales/fr/settings.json
@@ -560,6 +560,13 @@
"placeholder": "Défaut : claude",
"maxTokensLabel": "Jetons de sortie max",
"maxTokensDescription": "Nombre maximum de jetons de sortie pour les réponses de Claude Code. La valeur par défaut est 8000."
+ },
+ "zooGateway": {
+ "account": "Compte Zoo Code",
+ "signInButton": "Se connecter à Zoo Code",
+ "signInDescription": "Connectez-vous pour utiliser Zoo Gateway avec votre compte",
+ "authenticated": "Authentifié",
+ "authenticatedAs": "Authentifié en tant que {{name}}"
}
},
"checkpoints": {
@@ -741,6 +748,14 @@
"inheritEnv": {
"label": "Hériter des variables d'environnement",
"description": "Activez pour hériter des variables d'environnement du processus parent VS Code. <0>En savoir plus0>"
+ },
+ "profile": {
+ "label": "Remplacement du terminal Zoo Code",
+ "default": "Utiliser le profil par défaut VS Code (recommandé)",
+ "description": "Par défaut, Zoo Code utilise le shell configuré dans VS Code. Sélectionnez Remplacer pour choisir un profil shell avec chemin exposé par VS Code. Les profils source uniquement (ex. : l'entrée PowerShell intégrée) ne peuvent pas être listés ici. <0>En savoir plus0>",
+ "overrideLabel": "Remplacer le shell pour Zoo Code",
+ "configureButton": "Choisir le profil par défaut dans VS Code",
+ "noProfiles": "(aucun profil avec chemin trouvé dans terminal.integrated.profiles)"
}
},
"advancedSettings": {
@@ -901,7 +916,8 @@
"providerNotAllowed": "Le fournisseur '{{provider}}' n'est pas autorisé par votre organisation",
"modelNotAllowed": "Le modèle '{{model}}' n'est pas autorisé pour le fournisseur '{{provider}}' par votre organisation",
"profileInvalid": "Ce profil contient un fournisseur ou un modèle qui n'est pas autorisé par votre organisation",
- "qwenCodeOauthPath": "Tu dois fournir un chemin valide pour les identifiants OAuth"
+ "qwenCodeOauthPath": "Tu dois fournir un chemin valide pour les identifiants OAuth",
+ "zooGatewaySignIn": "Connectez-vous à Zoo Code pour utiliser Zoo Gateway."
},
"placeholders": {
"apiKey": "Saisissez la clé API...",
diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json
index d83c775550..7af670dc03 100644
--- a/webview-ui/src/i18n/locales/hi/settings.json
+++ b/webview-ui/src/i18n/locales/hi/settings.json
@@ -560,6 +560,13 @@
"placeholder": "डिफ़ॉल्ट: claude",
"maxTokensLabel": "अधिकतम आउटपुट टोकन",
"maxTokensDescription": "Claude Code प्रतिक्रियाओं के लिए आउटपुट टोकन की अधिकतम संख्या। डिफ़ॉल्ट 8000 है।"
+ },
+ "zooGateway": {
+ "account": "Zoo Code खाता",
+ "signInButton": "Zoo Code में साइन इन करें",
+ "signInDescription": "अपने खाते के साथ Zoo Gateway का उपयोग करने के लिए साइन इन करें",
+ "authenticated": "प्रमाणित",
+ "authenticatedAs": "{{name}} के रूप में प्रमाणित"
}
},
"checkpoints": {
@@ -741,6 +748,14 @@
"inheritEnv": {
"label": "पर्यावरण चर विरासत में लें",
"description": "पैरेंट VS Code प्रोसेस से पर्यावरण चर विरासत में लेने के लिए इसे चालू करें। <0>अधिक जानें0>"
+ },
+ "profile": {
+ "label": "Zoo Code टर्मिनल ओवरराइड",
+ "default": "VS Code डिफ़ॉल्ट प्रोफ़ाइल उपयोग करें (अनुशंसित)",
+ "description": "डिफ़ॉल्ट रूप से Zoo Code VS Code में कॉन्फ़िगर की गई शेल का उपयोग करता है। VS Code द्वारा प्रदर्शित पथ-आधारित शेल प्रोफ़ाइल चुनने के लिए ओवरराइड चुनें। केवल-स्रोत प्रोफ़ाइल (जैसे, अंतर्निर्मित PowerShell प्रविष्टि) यहाँ सूचीबद्ध नहीं किए जा सकते। <0>अधिक जानें0>",
+ "overrideLabel": "Zoo Code के लिए शेल ओवरराइड करें",
+ "configureButton": "VS Code में डिफ़ॉल्ट प्रोफ़ाइल चुनें",
+ "noProfiles": "(terminal.integrated.profiles में कोई पथ-आधारित प्रोफ़ाइल नहीं मिली)"
}
},
"advancedSettings": {
@@ -901,7 +916,8 @@
"providerNotAllowed": "प्रदाता '{{provider}}' आपके संगठन द्वारा अनुमत नहीं है",
"modelNotAllowed": "मॉडल '{{model}}' प्रदाता '{{provider}}' के लिए आपके संगठन द्वारा अनुमत नहीं है",
"profileInvalid": "इस प्रोफ़ाइल में एक प्रदाता या मॉडल शामिल है जो आपके संगठन द्वारा अनुमत नहीं है",
- "qwenCodeOauthPath": "आपको एक वैध OAuth क्रेडेंशियल पथ प्रदान करना होगा"
+ "qwenCodeOauthPath": "आपको एक वैध OAuth क्रेडेंशियल पथ प्रदान करना होगा",
+ "zooGatewaySignIn": "Zoo Gateway का उपयोग करने के लिए Zoo Code में साइन इन करें।"
},
"placeholders": {
"apiKey": "API कुंजी दर्ज करें...",
diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json
index c217c7c0f2..cd03385dba 100644
--- a/webview-ui/src/i18n/locales/id/settings.json
+++ b/webview-ui/src/i18n/locales/id/settings.json
@@ -560,6 +560,13 @@
"placeholder": "Default: claude",
"maxTokensLabel": "Token Output Maks",
"maxTokensDescription": "Jumlah maksimum token output untuk respons Claude Code. Default adalah 8000."
+ },
+ "zooGateway": {
+ "account": "Akun Zoo Code",
+ "signInButton": "Masuk ke Zoo Code",
+ "signInDescription": "Masuk untuk menggunakan Zoo Gateway dengan akun Anda",
+ "authenticated": "Terautentikasi",
+ "authenticatedAs": "Terautentikasi sebagai {{name}}"
}
},
"checkpoints": {
@@ -741,6 +748,14 @@
"inheritEnv": {
"label": "Warisi variabel lingkungan",
"description": "Aktifkan untuk mewarisi variabel lingkungan dari proses induk VS Code. <0>Pelajari lebih lanjut0>"
+ },
+ "profile": {
+ "label": "Penimpaan terminal Zoo Code",
+ "default": "Gunakan profil default VS Code (direkomendasikan)",
+ "description": "Secara default Zoo Code menggunakan shell yang dikonfigurasi VS Code. Pilih Timpa untuk memilih profil shell berbasis jalur yang diekspos oleh VS Code. Profil hanya sumber (mis., entri PowerShell bawaan) tidak dapat tercantum di sini. <0>Pelajari lebih lanjut0>",
+ "overrideLabel": "Timpa shell untuk Zoo Code",
+ "configureButton": "Pilih profil default di VS Code",
+ "noProfiles": "(tidak ada profil berbasis jalur di terminal.integrated.profiles)"
}
},
"advancedSettings": {
@@ -901,7 +916,8 @@
"providerNotAllowed": "Provider '{{provider}}' tidak diizinkan oleh organisasi kamu",
"modelNotAllowed": "Model '{{model}}' tidak diizinkan untuk provider '{{provider}}' oleh organisasi kamu",
"profileInvalid": "Profil ini berisi provider atau model yang tidak diizinkan oleh organisasi kamu",
- "qwenCodeOauthPath": "Kamu harus memberikan jalur kredensial OAuth yang valid"
+ "qwenCodeOauthPath": "Kamu harus memberikan jalur kredensial OAuth yang valid",
+ "zooGatewaySignIn": "Masuk ke Zoo Code untuk menggunakan Zoo Gateway."
},
"placeholders": {
"apiKey": "Masukkan API Key...",
diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json
index 7e40c7379b..0cf1d167fe 100644
--- a/webview-ui/src/i18n/locales/it/settings.json
+++ b/webview-ui/src/i18n/locales/it/settings.json
@@ -560,6 +560,13 @@
"placeholder": "Predefinito: claude",
"maxTokensLabel": "Token di output massimi",
"maxTokensDescription": "Numero massimo di token di output per le risposte di Claude Code. Il valore predefinito è 8000."
+ },
+ "zooGateway": {
+ "account": "Account Zoo Code",
+ "signInButton": "Accedi a Zoo Code",
+ "signInDescription": "Accedi per utilizzare Zoo Gateway con il tuo account",
+ "authenticated": "Autenticato",
+ "authenticatedAs": "Autenticato come {{name}}"
}
},
"checkpoints": {
@@ -741,6 +748,14 @@
"inheritEnv": {
"label": "Eredita variabili d'ambiente",
"description": "Attiva per ereditare le variabili d'ambiente dal processo padre di VS Code. <0>Scopri di più0>"
+ },
+ "profile": {
+ "label": "Override terminale Zoo Code",
+ "default": "Usa il profilo predefinito di VS Code (consigliato)",
+ "description": "Per impostazione predefinita Zoo Code usa la shell configurata in VS Code. Seleziona Override per scegliere un profilo shell con percorso esposto da VS Code. I profili solo sorgente (es. la voce PowerShell integrata) non possono essere elencati qui. <0>Ulteriori informazioni0>",
+ "overrideLabel": "Sostituisci shell per Zoo Code",
+ "configureButton": "Scegli il profilo predefinito in VS Code",
+ "noProfiles": "(nessun profilo con percorso trovato in terminal.integrated.profiles)"
}
},
"advancedSettings": {
@@ -901,7 +916,8 @@
"providerNotAllowed": "Il fornitore '{{provider}}' non è consentito dalla tua organizzazione",
"modelNotAllowed": "Il modello '{{model}}' non è consentito per il fornitore '{{provider}}' dalla tua organizzazione.",
"profileInvalid": "Questo profilo contiene un fornitore o un modello non consentito dalla tua organizzazione.",
- "qwenCodeOauthPath": "Devi fornire un percorso valido per le credenziali OAuth"
+ "qwenCodeOauthPath": "Devi fornire un percorso valido per le credenziali OAuth",
+ "zooGatewaySignIn": "Accedi a Zoo Code per utilizzare Zoo Gateway."
},
"placeholders": {
"apiKey": "Inserisci chiave API...",
diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json
index 5483e07cdd..7260f846bb 100644
--- a/webview-ui/src/i18n/locales/ja/settings.json
+++ b/webview-ui/src/i18n/locales/ja/settings.json
@@ -560,6 +560,13 @@
"placeholder": "デフォルト:claude",
"maxTokensLabel": "最大出力トークン",
"maxTokensDescription": "Claude Codeレスポンスの最大出力トークン数。デフォルトは8000です。"
+ },
+ "zooGateway": {
+ "account": "Zoo Code アカウント",
+ "signInButton": "Zoo Code にサインイン",
+ "signInDescription": "Zoo Gateway をアカウントで使用するにはサインインしてください",
+ "authenticated": "認証済み",
+ "authenticatedAs": "{{name}} として認証済み"
}
},
"checkpoints": {
@@ -741,6 +748,14 @@
"inheritEnv": {
"label": "環境変数を継承",
"description": "親VS Codeプロセスから環境変数を継承するには、これをオンにします。<0>詳細情報0>"
+ },
+ "profile": {
+ "label": "Zoo Code ターミナルの上書き",
+ "default": "VS Code のデフォルトプロファイルを使用する(推奨)",
+ "description": "デフォルトでは Zoo Code は VS Code に設定されたシェルを使用します。VS Code が公開するパスベースのシェルプロファイルを選択するには「上書き」を選択してください。ソースのみのプロファイル(例:組み込みの PowerShell エントリ)はここに表示できません。 <0>詳細を見る0>",
+ "overrideLabel": "Zoo Code 用シェルを上書き",
+ "configureButton": "VS Code でデフォルトプロファイルを選択",
+ "noProfiles": "(terminal.integrated.profiles にパスベースのプロファイルが見つかりません)"
}
},
"advancedSettings": {
@@ -901,7 +916,8 @@
"providerNotAllowed": "プロバイダー「{{provider}}」は組織によって許可されていません",
"modelNotAllowed": "モデル「{{model}}」はプロバイダー「{{provider}}」に対して組織によって許可されていません",
"profileInvalid": "このプロファイルには、組織によって許可されていないプロバイダーまたはモデルが含まれています",
- "qwenCodeOauthPath": "有効なOAuth認証情報のパスを提供する必要があります"
+ "qwenCodeOauthPath": "有効なOAuth認証情報のパスを提供する必要があります",
+ "zooGatewaySignIn": "Zoo Gatewayを使用するにはZoo Codeにサインインしてください。"
},
"placeholders": {
"apiKey": "API キーを入力...",
diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json
index 80bd3b2ab5..2b64067467 100644
--- a/webview-ui/src/i18n/locales/ko/settings.json
+++ b/webview-ui/src/i18n/locales/ko/settings.json
@@ -560,6 +560,13 @@
"placeholder": "기본값: claude",
"maxTokensLabel": "최대 출력 토큰",
"maxTokensDescription": "Claude Code 응답의 최대 출력 토큰 수. 기본값은 8000입니다."
+ },
+ "zooGateway": {
+ "account": "Zoo Code 계정",
+ "signInButton": "Zoo Code에 로그인",
+ "signInDescription": "계정으로 Zoo Gateway를 사용하려면 로그인하세요",
+ "authenticated": "인증됨",
+ "authenticatedAs": "{{name}}(으)로 인증됨"
}
},
"checkpoints": {
@@ -741,6 +748,14 @@
"inheritEnv": {
"label": "환경 변수 상속",
"description": "부모 VS Code 프로세���에서 환경 변수를 상속하려면 이 기능을 켜십시오. <0>자세히 알아보기0>"
+ },
+ "profile": {
+ "label": "Zoo Code 터미널 재정의",
+ "default": "VS Code 기본 프로필 사용 (권장)",
+ "description": "기본적으로 Zoo Code는 VS Code에 구성된 쉘을 사용합니다. VS Code가 노출하는 경로 기반 쉘 프로필을 선택하려면 재정의를 선택하세요. 소스 전용 프로필(예: 내장 PowerShell 항목)은 여기에 나열할 수 없습니다. <0>자세히 알아보기0>",
+ "overrideLabel": "Zoo Code용 쉘 재정의",
+ "configureButton": "VS Code에서 기본 프로필 선택",
+ "noProfiles": "(terminal.integrated.profiles에 경로 기반 프로필이 없습니다)"
}
},
"advancedSettings": {
@@ -901,7 +916,8 @@
"providerNotAllowed": "제공자 '{{provider}}'는 조직에서 허용되지 않습니다",
"modelNotAllowed": "모델 '{{model}}'은 제공자 '{{provider}}'에 대해 조직에서 허용되지 않습니다",
"profileInvalid": "이 프로필에는 조직에서 허용되지 않는 제공자 또는 모델이 포함되어 있습니다",
- "qwenCodeOauthPath": "유효한 OAuth 자격 증명 경로를 제공해야 합니다"
+ "qwenCodeOauthPath": "유효한 OAuth 자격 증명 경로를 제공해야 합니다",
+ "zooGatewaySignIn": "Zoo Gateway를 사용하려면 Zoo Code에 로그인하세요."
},
"placeholders": {
"apiKey": "API 키 입력...",
diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json
index 4b7c20d6f7..36cec47c1e 100644
--- a/webview-ui/src/i18n/locales/nl/settings.json
+++ b/webview-ui/src/i18n/locales/nl/settings.json
@@ -560,6 +560,13 @@
"placeholder": "Standaard: claude",
"maxTokensLabel": "Max Output Tokens",
"maxTokensDescription": "Maximaal aantal output-tokens voor Claude Code-reacties. Standaard is 8000."
+ },
+ "zooGateway": {
+ "account": "Zoo Code Account",
+ "signInButton": "Inloggen bij Zoo Code",
+ "signInDescription": "Log in om Zoo Gateway met je account te gebruiken",
+ "authenticated": "Geauthenticeerd",
+ "authenticatedAs": "Geauthenticeerd als {{name}}"
}
},
"checkpoints": {
@@ -741,6 +748,14 @@
"inheritEnv": {
"label": "Omgevingsvariabelen overnemen",
"description": "Schakel in om omgevingsvariabelen over te nemen van het bovenliggende VS Code-proces. <0>Meer informatie0>"
+ },
+ "profile": {
+ "label": "Zoo Code terminal-overschrijving",
+ "default": "VS Code standaardprofiel gebruiken (aanbevolen)",
+ "description": "Standaard gebruikt Zoo Code de shell die in VS Code is geconfigureerd. Selecteer Overschrijven om een shell-profiel met pad te kiezen dat VS Code beschikbaar stelt. Uitsluitend op bron gebaseerde profielen (bijv. de ingebouwde PowerShell-vermelding) kunnen hier niet worden vermeld. <0>Meer informatie0>",
+ "overrideLabel": "Shell voor Zoo Code overschrijven",
+ "configureButton": "Standaardprofiel kiezen in VS Code",
+ "noProfiles": "(geen profielen met pad gevonden in terminal.integrated.profiles)"
}
},
"advancedSettings": {
@@ -901,7 +916,8 @@
"providerNotAllowed": "Provider '{{provider}}' is niet toegestaan door je organisatie",
"modelNotAllowed": "Model '{{model}}' is niet toegestaan voor provider '{{provider}}' door je organisatie",
"profileInvalid": "Dit profiel bevat een provider of model dat niet is toegestaan door je organisatie",
- "qwenCodeOauthPath": "Je moet een geldig OAuth-referentiepad opgeven"
+ "qwenCodeOauthPath": "Je moet een geldig OAuth-referentiepad opgeven",
+ "zooGatewaySignIn": "Log in bij Zoo Code om Zoo Gateway te gebruiken."
},
"placeholders": {
"apiKey": "Voer API-sleutel in...",
diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json
index 9ede811b29..e561ba9a48 100644
--- a/webview-ui/src/i18n/locales/pl/settings.json
+++ b/webview-ui/src/i18n/locales/pl/settings.json
@@ -560,6 +560,13 @@
"placeholder": "Domyślnie: claude",
"maxTokensLabel": "Maksymalna liczba tokenów wyjściowych",
"maxTokensDescription": "Maksymalna liczba tokenów wyjściowych dla odpowiedzi Claude Code. Domyślnie 8000."
+ },
+ "zooGateway": {
+ "account": "Konto Zoo Code",
+ "signInButton": "Zaloguj się do Zoo Code",
+ "signInDescription": "Zaloguj się, aby korzystać z Zoo Gateway ze swoim kontem",
+ "authenticated": "Uwierzytelniono",
+ "authenticatedAs": "Uwierzytelniono jako {{name}}"
}
},
"checkpoints": {
@@ -741,6 +748,14 @@
"inheritEnv": {
"label": "Dziedzicz zmienne środowiskowe",
"description": "Włącz, aby dziedziczyć zmienne środowiskowe z procesu nadrzędnego VS Code. <0>Dowiedz się więcej0>"
+ },
+ "profile": {
+ "label": "Nadpisanie terminala Zoo Code",
+ "default": "Użyj domyślnego profilu VS Code (zalecane)",
+ "description": "Domyślnie Zoo Code używa powłoki skonfigurowanej w VS Code. Wybierz Nadpisanie, aby wybrać profil powłoki ze ścieżką udostępniony przez VS Code. Profile tylko ze źródłem (np. wbudowany wpis PowerShell) nie mogą być tutaj wyświetlone. <0>Dowiedz się więcej0>",
+ "overrideLabel": "Nadpisz powłokę dla Zoo Code",
+ "configureButton": "Wybierz domyślny profil w VS Code",
+ "noProfiles": "(brak profili ze ścieżką w terminal.integrated.profiles)"
}
},
"advancedSettings": {
@@ -901,7 +916,8 @@
"providerNotAllowed": "Dostawca '{{provider}}' nie jest dozwolony przez Twoją organizację",
"modelNotAllowed": "Model '{{model}}' nie jest dozwolony dla dostawcy '{{provider}}' przez Twoją organizację",
"profileInvalid": "Ten profil zawiera dostawcę lub model, który nie jest dozwolony przez Twoją organizację",
- "qwenCodeOauthPath": "Musisz podać prawidłową ścieżkę do poświadczeń OAuth"
+ "qwenCodeOauthPath": "Musisz podać prawidłową ścieżkę do poświadczeń OAuth",
+ "zooGatewaySignIn": "Zaloguj się do Zoo Code, aby korzystać z Zoo Gateway."
},
"placeholders": {
"apiKey": "Wprowadź klucz API...",
diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json
index 1ff078d66b..e36ae3ea4a 100644
--- a/webview-ui/src/i18n/locales/pt-BR/settings.json
+++ b/webview-ui/src/i18n/locales/pt-BR/settings.json
@@ -560,6 +560,13 @@
"placeholder": "Padrão: claude",
"maxTokensLabel": "Tokens de saída máximos",
"maxTokensDescription": "Número máximo de tokens de saída para respostas do Claude Code. O padrão é 8000."
+ },
+ "zooGateway": {
+ "account": "Conta Zoo Code",
+ "signInButton": "Entrar no Zoo Code",
+ "signInDescription": "Entre para usar o Zoo Gateway com sua conta",
+ "authenticated": "Autenticado",
+ "authenticatedAs": "Autenticado como {{name}}"
}
},
"checkpoints": {
@@ -741,6 +748,14 @@
"inheritEnv": {
"label": "Herdar variáveis de ambiente",
"description": "Ative isso para herdar variáveis de ambiente do processo pai do VS Code. <0>Saiba mais0>"
+ },
+ "profile": {
+ "label": "Substituição de terminal do Zoo Code",
+ "default": "Usar perfil padrão do VS Code (recomendado)",
+ "description": "Por padrão, o Zoo Code usa o shell configurado no VS Code. Selecione Substituir para escolher um perfil de shell com caminho exposto pelo VS Code. Perfis somente de fonte (ex.: a entrada integrada do PowerShell) não podem ser listados aqui. <0>Saiba mais0>",
+ "overrideLabel": "Substituir shell para o Zoo Code",
+ "configureButton": "Escolher perfil padrão no VS Code",
+ "noProfiles": "(nenhum perfil com caminho encontrado em terminal.integrated.profiles)"
}
},
"advancedSettings": {
@@ -901,7 +916,8 @@
"providerNotAllowed": "O provedor '{{provider}}' não é permitido pela sua organização",
"modelNotAllowed": "O modelo '{{model}}' não é permitido para o provedor '{{provider}}' pela sua organização",
"profileInvalid": "Este perfil contém um provedor ou modelo que não é permitido pela sua organização",
- "qwenCodeOauthPath": "Você deve fornecer um caminho válido de credenciais OAuth"
+ "qwenCodeOauthPath": "Você deve fornecer um caminho válido de credenciais OAuth",
+ "zooGatewaySignIn": "Faça login no Zoo Code para usar o Zoo Gateway."
},
"placeholders": {
"apiKey": "Digite a chave API...",
diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json
index b1ce9c3050..d677485bea 100644
--- a/webview-ui/src/i18n/locales/ru/settings.json
+++ b/webview-ui/src/i18n/locales/ru/settings.json
@@ -560,6 +560,13 @@
"placeholder": "По умолчанию: claude",
"maxTokensLabel": "Макс. выходных токенов",
"maxTokensDescription": "Максимальное количество выходных токенов для ответов Claude Code. По умолчанию 8000."
+ },
+ "zooGateway": {
+ "account": "Учетная запись Zoo Code",
+ "signInButton": "Войти в Zoo Code",
+ "signInDescription": "Войдите, чтобы использовать Zoo Gateway с вашей учетной записью",
+ "authenticated": "Аутентифицирован",
+ "authenticatedAs": "Аутентифицирован как {{name}}"
}
},
"checkpoints": {
@@ -741,6 +748,14 @@
"inheritEnv": {
"label": "Наследовать переменные среды",
"description": "Включите для наследования переменных среды от родительского процесса VS Code. <0>Подробнее0>"
+ },
+ "profile": {
+ "label": "Переопределение терминала Zoo Code",
+ "default": "Использовать профиль VS Code по умолчанию (рекомендуется)",
+ "description": "По умолчанию Zoo Code использует оболочку, настроенную в VS Code. Выберите Переопределить, чтобы указать профиль оболочки с путём, предоставленный VS Code. Профили только с источником (например, встроенная запись PowerShell) не могут быть перечислены здесь. <0>Подробнее0>",
+ "overrideLabel": "Переопределить оболочку для Zoo Code",
+ "configureButton": "Выбрать профиль по умолчанию в VS Code",
+ "noProfiles": "(профили с путём в terminal.integrated.profiles не найдены)"
}
},
"advancedSettings": {
@@ -901,7 +916,8 @@
"providerNotAllowed": "Провайдер '{{provider}}' не разрешен вашей организацией",
"modelNotAllowed": "Модель '{{model}}' не разрешена для провайдера '{{provider}}' вашей организацией",
"profileInvalid": "Этот профиль содержит провайдера или модель, которые не разрешены вашей организацией",
- "qwenCodeOauthPath": "Вы должны указать допустимый путь к учетным данным OAuth"
+ "qwenCodeOauthPath": "Вы должны указать допустимый путь к учетным данным OAuth",
+ "zooGatewaySignIn": "Войдите в Zoo Code, чтобы использовать Zoo Gateway."
},
"placeholders": {
"apiKey": "Введите API-ключ...",
diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json
index 367d8f6114..2d5ed06b15 100644
--- a/webview-ui/src/i18n/locales/tr/settings.json
+++ b/webview-ui/src/i18n/locales/tr/settings.json
@@ -560,6 +560,13 @@
"placeholder": "Varsayılan: claude",
"maxTokensLabel": "Maksimum Çıktı Token sayısı",
"maxTokensDescription": "Claude Code yanıtları için maksimum çıktı token sayısı. Varsayılan 8000'dir."
+ },
+ "zooGateway": {
+ "account": "Zoo Code Hesabı",
+ "signInButton": "Zoo Code'a giriş yap",
+ "signInDescription": "Hesabınızla Zoo Gateway kullanmak için giriş yapın",
+ "authenticated": "Kimlik doğrulandı",
+ "authenticatedAs": "{{name}} olarak kimlik doğrulandı"
}
},
"checkpoints": {
@@ -741,6 +748,14 @@
"inheritEnv": {
"label": "Ortam değişkenlerini devral",
"description": "Ana VS Code işleminden ortam değişkenlerini devralmak için bunu açın. <0>Daha fazla bilgi edinin0>"
+ },
+ "profile": {
+ "label": "Zoo Code terminal geçersiz kılma",
+ "default": "VS Code varsayılan profilini kullan (önerilir)",
+ "description": "Varsayılan olarak Zoo Code, VS Code'da yapılandırılmış kabuğu kullanır. VS Code'un sunduğu yol tabanlı bir kabuk profili seçmek için Geçersiz Kıl'ı seçin. Yalnızca kaynak profiller (örn. yerleşik PowerShell girişi) burada listelenemez. <0>Daha fazla bilgi0>",
+ "overrideLabel": "Zoo Code için kabuk geçersiz kıl",
+ "configureButton": "VS Code'da varsayılan profili seç",
+ "noProfiles": "(terminal.integrated.profiles'da yol tabanlı profil bulunamadı)"
}
},
"advancedSettings": {
@@ -901,7 +916,8 @@
"providerNotAllowed": "Sağlayıcı '{{provider}}' kuruluşunuz tarafından izin verilmiyor",
"modelNotAllowed": "Model '{{model}}' sağlayıcı '{{provider}}' için kuruluşunuz tarafından izin verilmiyor",
"profileInvalid": "Bu profil, kuruluşunuz tarafından izin verilmeyen bir sağlayıcı veya model içeriyor",
- "qwenCodeOauthPath": "Geçerli bir OAuth kimlik bilgileri yolu sağlamalısın"
+ "qwenCodeOauthPath": "Geçerli bir OAuth kimlik bilgileri yolu sağlamalısınız",
+ "zooGatewaySignIn": "Zoo Gateway'i kullanmak için Zoo Code'a giriş yapın."
},
"placeholders": {
"apiKey": "API anahtarını girin...",
diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json
index 75c836f027..e8a8049afa 100644
--- a/webview-ui/src/i18n/locales/vi/settings.json
+++ b/webview-ui/src/i18n/locales/vi/settings.json
@@ -560,6 +560,13 @@
"placeholder": "Mặc định: claude",
"maxTokensLabel": "Số token đầu ra tối đa",
"maxTokensDescription": "Số lượng token đầu ra tối đa cho các phản hồi của Claude Code. Mặc định là 8000."
+ },
+ "zooGateway": {
+ "account": "Tài khoản Zoo Code",
+ "signInButton": "Đăng nhập vào Zoo Code",
+ "signInDescription": "Đăng nhập để sử dụng Zoo Gateway với tài khoản của bạn",
+ "authenticated": "Đã xác thực",
+ "authenticatedAs": "Đã xác thực với tên {{name}}"
}
},
"checkpoints": {
@@ -741,6 +748,14 @@
"inheritEnv": {
"label": "Kế thừa biến môi trường",
"description": "Bật tính năng này để kế thừa các biến môi trường từ quy trình mẹ của VS Code. <0>Tìm hiểu thêm0>"
+ },
+ "profile": {
+ "label": "Ghi đè terminal Zoo Code",
+ "default": "Dùng hồ sơ mặc định của VS Code (khuyến nghị)",
+ "description": "Theo mặc định Zoo Code sử dụng shell được cấu hình trong VS Code. Chọn Ghi đè để chọn hồ sơ shell dựa trên đường dẫn được VS Code cung cấp. Các hồ sơ chỉ nguồn (ví dụ: mục PowerShell tích hợp) không thể được liệt kê ở đây. <0>Tìm hiểu thêm0>",
+ "overrideLabel": "Ghi đè shell cho Zoo Code",
+ "configureButton": "Chọn hồ sơ mặc định trong VS Code",
+ "noProfiles": "(không tìm thấy hồ sơ dựa trên đường dẫn trong terminal.integrated.profiles)"
}
},
"advancedSettings": {
@@ -901,7 +916,8 @@
"providerNotAllowed": "Nhà cung cấp '{{provider}}' không được phép bởi tổ chức của bạn",
"modelNotAllowed": "Mô hình '{{model}}' không được phép cho nhà cung cấp '{{provider}}' bởi tổ chức của bạn",
"profileInvalid": "Hồ sơ này chứa một nhà cung cấp hoặc mô hình không được phép bởi tổ chức của bạn",
- "qwenCodeOauthPath": "Bạn phải cung cấp đường dẫn thông tin xác thực OAuth hợp lệ"
+ "qwenCodeOauthPath": "Bạn phải cung cấp đường dẫn thông tin xác thực OAuth hợp lệ",
+ "zooGatewaySignIn": "Đăng nhập vào Zoo Code để sử dụng Zoo Gateway."
},
"placeholders": {
"apiKey": "Nhập khóa API...",
diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json
index a265e9c387..21942c6150 100644
--- a/webview-ui/src/i18n/locales/zh-CN/settings.json
+++ b/webview-ui/src/i18n/locales/zh-CN/settings.json
@@ -560,6 +560,13 @@
"placeholder": "默认:claude",
"maxTokensLabel": "最大输出 Token",
"maxTokensDescription": "Claude Code 响应的最大输出 Token 数量。默认为 8000。"
+ },
+ "zooGateway": {
+ "account": "Zoo Code 账户",
+ "signInButton": "登录 Zoo Code",
+ "signInDescription": "登录以使用您的账户访问 Zoo Gateway",
+ "authenticated": "已认证",
+ "authenticatedAs": "已认证为 {{name}}"
}
},
"checkpoints": {
@@ -741,6 +748,14 @@
"inheritEnv": {
"label": "继承环境变量",
"description": "启用此选项以从父 VS Code 进程继承环境变量。<0>了解更多0>"
+ },
+ "profile": {
+ "label": "Zoo Code 终端覆盖",
+ "default": "使用 VS Code 默认配置文件(推荐)",
+ "description": "默认情况下,Zoo Code 使用 VS Code 配置的 Shell。选择覆盖可从 VS Code 公开的路径型 Shell 配置文件中选取。仅含来源的配置文件(如内置的 PowerShell 条目)无法在此列出。 <0>了解更多0>",
+ "overrideLabel": "为 Zoo Code 覆盖 Shell",
+ "configureButton": "在 VS Code 中选择默认配置文件",
+ "noProfiles": "(在 terminal.integrated.profiles 中未找到路径型配置文件)"
}
},
"advancedSettings": {
@@ -901,7 +916,8 @@
"providerNotAllowed": "提供商 '{{provider}}' 不允许用于您的组织",
"modelNotAllowed": "模型 '{{model}}' 不允许用于提供商 '{{provider}}',您的组织不允许",
"profileInvalid": "此配置文件包含您的组织不允许的提供商或模型",
- "qwenCodeOauthPath": "您必须提供有效的 OAuth 凭证路径"
+ "qwenCodeOauthPath": "您必须提供有效的 OAuth 凭证路径",
+ "zooGatewaySignIn": "请登录 Zoo Code 以使用 Zoo Gateway。"
},
"placeholders": {
"apiKey": "请输入 API 密钥...",
diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json
index 386202b905..f87222ab77 100644
--- a/webview-ui/src/i18n/locales/zh-TW/settings.json
+++ b/webview-ui/src/i18n/locales/zh-TW/settings.json
@@ -570,6 +570,13 @@
"placeholder": "預設:claude",
"maxTokensLabel": "最大輸出 Token",
"maxTokensDescription": "Claude Code 回應的最大輸出 Token 數量。預設為 8000。"
+ },
+ "zooGateway": {
+ "account": "Zoo Code 帳戶",
+ "signInButton": "登入 Zoo Code",
+ "signInDescription": "登入以使用您的帳戶存取 Zoo Gateway",
+ "authenticated": "已認證",
+ "authenticatedAs": "已認證為 {{name}}"
}
},
"checkpoints": {
@@ -751,6 +758,14 @@
"inheritEnv": {
"label": "繼承環境變數",
"description": "啟用此選項以從父 VS Code 程序繼承環境變數。<0>了解更多0>"
+ },
+ "profile": {
+ "label": "Zoo Code 終端機覆寫",
+ "default": "使用 VS Code 預設設定檔(建議)",
+ "description": "預設情況下,Zoo Code 使用 VS Code 設定的 Shell。選擇覆寫可從 VS Code 公開的路徑型 Shell 設定檔中選取。僅含來源的設定檔(如內建的 PowerShell 項目)無法在此列出。 <0>了解更多0>",
+ "overrideLabel": "為 Zoo Code 覆寫 Shell",
+ "configureButton": "在 VS Code 中選擇預設設定檔",
+ "noProfiles": "(在 terminal.integrated.profiles 中未找到路徑型設定檔)"
}
},
"advancedSettings": {
@@ -911,7 +926,8 @@
"providerNotAllowed": "供應商 '{{provider}}' 不允許用於您的組織。",
"modelNotAllowed": "模型 '{{model}}' 不允許用於供應商 '{{provider}}',此設定已被組織禁止",
"profileInvalid": "此設定檔包含您的組織不允許的供應商或模型",
- "qwenCodeOauthPath": "您必須提供有效的 OAuth 憑證路徑"
+ "qwenCodeOauthPath": "您必須提供有效的 OAuth 憑證路徑",
+ "zooGatewaySignIn": "請登入 Zoo Code 以使用 Zoo Gateway。"
},
"placeholders": {
"apiKey": "請輸入 API 金鑰...",
diff --git a/webview-ui/src/utils/__tests__/validate.spec.ts b/webview-ui/src/utils/__tests__/validate.spec.ts
index dab2b4fefa..5d4f54b927 100644
--- a/webview-ui/src/utils/__tests__/validate.spec.ts
+++ b/webview-ui/src/utils/__tests__/validate.spec.ts
@@ -16,7 +16,12 @@ vi.mock("i18next", () => ({
},
}))
-import { getModelValidationError, validateApiConfigurationExcludingModelErrors, validateBedrockArn } from "../validate"
+import {
+ getModelValidationError,
+ validateApiConfiguration,
+ validateApiConfigurationExcludingModelErrors,
+ validateBedrockArn,
+} from "../validate"
describe("Model Validation Functions", () => {
const mockRouterModels: RouterModels = {
@@ -47,6 +52,7 @@ describe("Model Validation Functions", () => {
poe: {},
deepseek: {},
"opencode-go": {},
+ "zoo-gateway": {},
}
const allowAllOrganization: OrganizationAllowList = {
@@ -210,6 +216,96 @@ describe("Model Validation Functions", () => {
expect(result).toBe("settings:validation.modelId")
})
})
+
+ describe("Zoo Gateway validation", () => {
+ describe("validateApiConfiguration (welcome-view entry point)", () => {
+ it("returns a sign-in error when neither profile token nor Zoo auth is present", () => {
+ const config: ProviderSettings = {
+ apiProvider: "zoo-gateway",
+ zooGatewayModelId: "anthropic/claude-sonnet-4",
+ }
+
+ const result = validateApiConfiguration(config, mockRouterModels, allowAllOrganization, false)
+ expect(result).toBe("settings:validation.zooGatewaySignIn")
+ })
+
+ it("returns undefined when Zoo Code auth is active without a profile token", () => {
+ const config: ProviderSettings = {
+ apiProvider: "zoo-gateway",
+ zooGatewayModelId: "anthropic/claude-sonnet-4",
+ }
+
+ const result = validateApiConfiguration(config, mockRouterModels, allowAllOrganization, true)
+ expect(result).toBeUndefined()
+ })
+
+ it("returns undefined when a profile session token is set", () => {
+ const config: ProviderSettings = {
+ apiProvider: "zoo-gateway",
+ zooGatewayModelId: "anthropic/claude-sonnet-4",
+ zooSessionToken: "zoo_ext_test_token",
+ }
+
+ const result = validateApiConfiguration(config, mockRouterModels, allowAllOrganization, false)
+ expect(result).toBeUndefined()
+ })
+ })
+
+ describe("validateApiConfigurationExcludingModelErrors (settings form)", () => {
+ // The settings form short-circuits zoo-gateway and renders the sign-in
+ // error inline in `ZooGateway.tsx`, so this entry point must never
+ // surface a zoo-gateway-specific error regardless of auth state.
+ it("returns undefined for zoo-gateway when unauthenticated and no token", () => {
+ const config: ProviderSettings = {
+ apiProvider: "zoo-gateway",
+ zooGatewayModelId: "anthropic/claude-sonnet-4",
+ }
+
+ const result = validateApiConfigurationExcludingModelErrors(
+ config,
+ mockRouterModels,
+ allowAllOrganization,
+ )
+ expect(result).toBeUndefined()
+ })
+
+ it("returns undefined for zoo-gateway when a profile token is set", () => {
+ const config: ProviderSettings = {
+ apiProvider: "zoo-gateway",
+ zooGatewayModelId: "anthropic/claude-sonnet-4",
+ zooSessionToken: "zoo_ext_test_token",
+ }
+
+ const result = validateApiConfigurationExcludingModelErrors(
+ config,
+ mockRouterModels,
+ allowAllOrganization,
+ )
+ expect(result).toBeUndefined()
+ })
+
+ it("surfaces PROVIDER_NOT_ALLOWED for zoo-gateway when organization disallows it", () => {
+ const orgWithoutZooGateway: OrganizationAllowList = {
+ allowAll: false,
+ providers: {
+ openrouter: { allowAll: true },
+ },
+ }
+
+ const config: ProviderSettings = {
+ apiProvider: "zoo-gateway",
+ zooGatewayModelId: "anthropic/claude-sonnet-4",
+ }
+
+ const result = validateApiConfigurationExcludingModelErrors(
+ config,
+ mockRouterModels,
+ orgWithoutZooGateway,
+ )
+ expect(result).toContain("settings:validation.providerNotAllowed")
+ })
+ })
+ })
})
describe("validateBedrockArn", () => {
diff --git a/webview-ui/src/utils/validate.ts b/webview-ui/src/utils/validate.ts
index 0e6e1c6b9d..3de6480802 100644
--- a/webview-ui/src/utils/validate.ts
+++ b/webview-ui/src/utils/validate.ts
@@ -17,8 +17,9 @@ export function validateApiConfiguration(
apiConfiguration: ProviderSettings,
routerModels?: RouterModels,
organizationAllowList?: OrganizationAllowList,
+ zooCodeIsAuthenticated?: boolean,
): string | undefined {
- const keysAndIdsPresentErrorMessage = validateModelsAndKeysProvided(apiConfiguration)
+ const keysAndIdsPresentErrorMessage = validateModelsAndKeysProvided(apiConfiguration, zooCodeIsAuthenticated)
if (keysAndIdsPresentErrorMessage) {
return keysAndIdsPresentErrorMessage
@@ -36,7 +37,10 @@ export function validateApiConfiguration(
return validateDynamicProviderModelId(apiConfiguration, routerModels)
}
-function validateModelsAndKeysProvided(apiConfiguration: ProviderSettings): string | undefined {
+function validateModelsAndKeysProvided(
+ apiConfiguration: ProviderSettings,
+ zooCodeIsAuthenticated?: boolean,
+): string | undefined {
switch (apiConfiguration.apiProvider) {
case "openrouter":
if (!apiConfiguration.openRouterApiKey) {
@@ -128,6 +132,11 @@ function validateModelsAndKeysProvided(apiConfiguration: ProviderSettings): stri
return i18next.t("settings:validation.apiKey")
}
break
+ case "zoo-gateway":
+ if (!apiConfiguration.zooSessionToken && !zooCodeIsAuthenticated) {
+ return i18next.t("settings:validation.zooGatewaySignIn")
+ }
+ break
case "baseten":
if (!apiConfiguration.basetenApiKey) {
return i18next.t("settings:validation.apiKey")
@@ -282,16 +291,23 @@ export function getModelValidationError(
* Validates API configuration but excludes model-specific errors.
* This is used for the general API error display to prevent duplication
* when model errors are shown in the model selector.
+ *
+ * Zoo Gateway's sign-in error is rendered inline by the `ZooGateway` provider
+ * component, so we skip the keys/sign-in check here. Organization provider
+ * restrictions still need to be enforced for zoo-gateway, so the org allowlist
+ * check below runs for every provider.
*/
export function validateApiConfigurationExcludingModelErrors(
apiConfiguration: ProviderSettings,
_routerModels?: RouterModels, // Keeping this for compatibility with the old function.
organizationAllowList?: OrganizationAllowList,
): string | undefined {
- const keysAndIdsPresentErrorMessage = validateModelsAndKeysProvided(apiConfiguration)
+ if (apiConfiguration.apiProvider !== "zoo-gateway") {
+ const keysAndIdsPresentErrorMessage = validateModelsAndKeysProvided(apiConfiguration)
- if (keysAndIdsPresentErrorMessage) {
- return keysAndIdsPresentErrorMessage
+ if (keysAndIdsPresentErrorMessage) {
+ return keysAndIdsPresentErrorMessage
+ }
}
const organizationAllowListError = validateProviderAgainstOrganizationSettings(