mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
feat: add authentication UI helpers for Claude Code and OpenAI providers
- Enhanced ClaudeCode provider component with Get API Key button and authentication status - Enhanced OpenAI provider component with authentication status indicator - Added translation strings for authentication UI elements - Added comprehensive test coverage for both provider components - Improved UX by showing clear authentication status and easy API key updates Addresses #9292 - improves authentication experience for Claude Code and OpenAI
This commit is contained in:
parent
744f4bd4c8
commit
335a4e00e8
5 changed files with 554 additions and 14 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import React from "react"
|
||||
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
import React, { useState } from "react"
|
||||
import { VSCodeTextField, VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
||||
import { type ProviderSettings } from "@roo-code/types"
|
||||
import { useAppTranslation } from "@src/i18n/TranslationContext"
|
||||
import { Slider } from "@src/components/ui"
|
||||
|
|
@ -7,29 +7,54 @@ import { Slider } from "@src/components/ui"
|
|||
interface ClaudeCodeProps {
|
||||
apiConfiguration: ProviderSettings
|
||||
setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void
|
||||
vscode?: any
|
||||
}
|
||||
|
||||
export const ClaudeCode: React.FC<ClaudeCodeProps> = ({ apiConfiguration, setApiConfigurationField }) => {
|
||||
export const ClaudeCode: React.FC<ClaudeCodeProps> = ({ apiConfiguration, setApiConfigurationField, vscode }) => {
|
||||
const { t } = useAppTranslation()
|
||||
const [isAuthenticating, setIsAuthenticating] = useState(false)
|
||||
|
||||
const handleInputChange = (e: Event | React.FormEvent<HTMLElement>) => {
|
||||
const element = e.target as HTMLInputElement
|
||||
setApiConfigurationField("claudeCodePath", element.value)
|
||||
}
|
||||
|
||||
const handleGetApiKey = () => {
|
||||
setIsAuthenticating(true)
|
||||
// Open Claude's website to get API key
|
||||
vscode?.postMessage({
|
||||
type: "openExternal",
|
||||
url: "https://console.anthropic.com/settings/keys",
|
||||
})
|
||||
// Show instructions
|
||||
vscode?.postMessage({
|
||||
type: "showInformationMessage",
|
||||
text: "Please create an API key on the Anthropic Console and paste it in the field above",
|
||||
})
|
||||
setIsAuthenticating(false)
|
||||
}
|
||||
|
||||
const maxOutputTokens = apiConfiguration?.claudeCodeMaxOutputTokens || 8000
|
||||
const hasPath = !!apiConfiguration?.claudeCodePath
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.claudeCodePath || ""}
|
||||
style={{ width: "100%", marginTop: 3 }}
|
||||
type="text"
|
||||
onInput={handleInputChange}
|
||||
placeholder={t("settings:providers.claudeCode.placeholder")}>
|
||||
{t("settings:providers.claudeCode.pathLabel")}
|
||||
</VSCodeTextField>
|
||||
<div className="flex gap-2 items-end mb-2">
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.claudeCodePath || ""}
|
||||
style={{ width: "100%", marginTop: 3 }}
|
||||
type="password"
|
||||
onInput={handleInputChange}
|
||||
placeholder={hasPath ? "••••••••••••••••" : t("settings:providers.claudeCode.placeholder")}>
|
||||
{t("settings:providers.claudeCode.pathLabel")}
|
||||
</VSCodeTextField>
|
||||
<VSCodeButton onClick={handleGetApiKey} disabled={isAuthenticating} appearance="secondary">
|
||||
{hasPath
|
||||
? t("settings:providers.claudeCode.updateKey")
|
||||
: t("settings:providers.claudeCode.getApiKey")}
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
|
||||
<p
|
||||
style={{
|
||||
|
|
@ -39,6 +64,17 @@ export const ClaudeCode: React.FC<ClaudeCodeProps> = ({ apiConfiguration, setApi
|
|||
}}>
|
||||
{t("settings:providers.claudeCode.description")}
|
||||
</p>
|
||||
|
||||
{hasPath && (
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: 3,
|
||||
color: "var(--vscode-notificationsInfoIcon-foreground)",
|
||||
}}>
|
||||
✓ {t("settings:providers.claudeCode.authenticated")}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
|
|
|
|||
|
|
@ -69,7 +69,16 @@ export const OpenAI = ({ apiConfiguration, setApiConfigurationField, selectedMod
|
|||
<div className="text-sm text-vscode-descriptionForeground -mt-2">
|
||||
{t("settings:providers.apiKeyStorageNotice")}
|
||||
</div>
|
||||
{!apiConfiguration?.openAiNativeApiKey && (
|
||||
{apiConfiguration?.openAiNativeApiKey ? (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-sm text-vscode-notificationsInfoIcon-foreground">
|
||||
✓ {t("settings:providers.openAi.authenticated")}
|
||||
</div>
|
||||
<VSCodeButtonLink href="https://platform.openai.com/api-keys" appearance="secondary">
|
||||
{t("settings:providers.openAi.updateKey")}
|
||||
</VSCodeButtonLink>
|
||||
</div>
|
||||
) : (
|
||||
<VSCodeButtonLink href="https://platform.openai.com/api-keys" appearance="secondary">
|
||||
{t("settings:providers.getOpenAiApiKey")}
|
||||
</VSCodeButtonLink>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,219 @@
|
|||
import { render, screen } from "@testing-library/react"
|
||||
import userEvent from "@testing-library/user-event"
|
||||
import { ClaudeCode } from "../ClaudeCode"
|
||||
import type { ProviderSettings } from "@roo-code/types"
|
||||
|
||||
vi.mock("@vscode/webview-ui-toolkit/react", () => ({
|
||||
VSCodeTextField: ({ children, value, onInput, type, placeholder }: any) => {
|
||||
const handleInput = (e: any) => {
|
||||
const event = { target: { value: e.target.value } }
|
||||
onInput?.(event)
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
{children}
|
||||
<input type={type} value={value} onInput={handleInput} placeholder={placeholder} />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
VSCodeButton: ({ children, onClick, disabled, appearance }: any) => (
|
||||
<button onClick={onClick} disabled={disabled} data-appearance={appearance}>
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock("@src/i18n/TranslationContext", () => ({
|
||||
useAppTranslation: () => ({
|
||||
t: (key: string) => {
|
||||
const translations: Record<string, string> = {
|
||||
"settings:providers.claudeCode.pathLabel": "Claude Code API Key",
|
||||
"settings:providers.claudeCode.placeholder": "Enter your Claude Code API key",
|
||||
"settings:providers.claudeCode.getApiKey": "Get API Key",
|
||||
"settings:providers.claudeCode.updateKey": "Update Key",
|
||||
"settings:providers.claudeCode.authenticated": "Authenticated with Claude Code",
|
||||
"settings:providers.claudeCode.description":
|
||||
"Optional path to your Claude Code CLI. Defaults to 'claude' if not set.",
|
||||
"settings:providers.claudeCode.maxTokensLabel": "Max Output Tokens",
|
||||
"settings:providers.claudeCode.maxTokensDescription":
|
||||
"Maximum number of output tokens for Claude Code responses. Default is 8000.",
|
||||
}
|
||||
return translations[key] || key
|
||||
},
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock("@src/components/ui", () => ({
|
||||
Slider: ({ value, onValueChange }: any) => (
|
||||
<input
|
||||
type="range"
|
||||
value={value[0]}
|
||||
onChange={(e) => onValueChange([Number(e.target.value)])}
|
||||
data-testid="slider"
|
||||
/>
|
||||
),
|
||||
}))
|
||||
|
||||
describe("ClaudeCode", () => {
|
||||
const defaultApiConfiguration: ProviderSettings = {
|
||||
claudeCodePath: "",
|
||||
claudeCodeMaxOutputTokens: 8000,
|
||||
}
|
||||
|
||||
const mockSetApiConfigurationField = vi.fn()
|
||||
const mockVscode = {
|
||||
postMessage: vi.fn(),
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("Authentication UI", () => {
|
||||
it("should show 'Get API Key' button when no API key is present", () => {
|
||||
render(
|
||||
<ClaudeCode
|
||||
apiConfiguration={defaultApiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
vscode={mockVscode}
|
||||
/>,
|
||||
)
|
||||
|
||||
const button = screen.getByRole("button")
|
||||
expect(button).toHaveTextContent("Get API Key")
|
||||
})
|
||||
|
||||
it("should show 'Update Key' button when API key is present", () => {
|
||||
const apiConfiguration = { ...defaultApiConfiguration, claudeCodePath: "test-api-key" }
|
||||
render(
|
||||
<ClaudeCode
|
||||
apiConfiguration={apiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
vscode={mockVscode}
|
||||
/>,
|
||||
)
|
||||
|
||||
const button = screen.getByRole("button")
|
||||
expect(button).toHaveTextContent("Update Key")
|
||||
})
|
||||
|
||||
it("should show authenticated status when API key is present", () => {
|
||||
const apiConfiguration = { ...defaultApiConfiguration, claudeCodePath: "test-api-key" }
|
||||
render(
|
||||
<ClaudeCode
|
||||
apiConfiguration={apiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
vscode={mockVscode}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText("✓ Authenticated with Claude Code")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("should not show authenticated status when no API key", () => {
|
||||
render(
|
||||
<ClaudeCode
|
||||
apiConfiguration={defaultApiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
vscode={mockVscode}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.queryByText("✓ Authenticated with Claude Code")).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("should open external link when Get API Key button is clicked", async () => {
|
||||
const user = userEvent.setup()
|
||||
render(
|
||||
<ClaudeCode
|
||||
apiConfiguration={defaultApiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
vscode={mockVscode}
|
||||
/>,
|
||||
)
|
||||
|
||||
const button = screen.getByRole("button")
|
||||
await user.click(button)
|
||||
|
||||
expect(mockVscode.postMessage).toHaveBeenCalledWith({
|
||||
type: "openExternal",
|
||||
url: "https://console.anthropic.com/settings/keys",
|
||||
})
|
||||
})
|
||||
|
||||
it("should show information message when Get API Key button is clicked", async () => {
|
||||
const user = userEvent.setup()
|
||||
render(
|
||||
<ClaudeCode
|
||||
apiConfiguration={defaultApiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
vscode={mockVscode}
|
||||
/>,
|
||||
)
|
||||
|
||||
const button = screen.getByRole("button")
|
||||
await user.click(button)
|
||||
|
||||
expect(mockVscode.postMessage).toHaveBeenCalledWith({
|
||||
type: "showInformationMessage",
|
||||
text: "Please create an API key on the Anthropic Console and paste it in the field above",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("API Key Input", () => {
|
||||
it("should mask API key input", () => {
|
||||
render(
|
||||
<ClaudeCode
|
||||
apiConfiguration={defaultApiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
vscode={mockVscode}
|
||||
/>,
|
||||
)
|
||||
|
||||
const input = screen.getByPlaceholderText("Enter your Claude Code API key") as HTMLInputElement
|
||||
expect(input.type).toBe("password")
|
||||
})
|
||||
|
||||
it("should show masked placeholder when API key exists", () => {
|
||||
const apiConfiguration = { ...defaultApiConfiguration, claudeCodePath: "test-api-key" }
|
||||
render(
|
||||
<ClaudeCode
|
||||
apiConfiguration={apiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
vscode={mockVscode}
|
||||
/>,
|
||||
)
|
||||
|
||||
const input = screen.getByPlaceholderText("••••••••••••••••") as HTMLInputElement
|
||||
expect(input).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe("Max Output Tokens", () => {
|
||||
it("should display current max output tokens value", () => {
|
||||
const apiConfiguration = { ...defaultApiConfiguration, claudeCodeMaxOutputTokens: 16000 }
|
||||
render(
|
||||
<ClaudeCode
|
||||
apiConfiguration={apiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
vscode={mockVscode}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText("16000")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("should use default value of 8000 when not specified", () => {
|
||||
render(
|
||||
<ClaudeCode
|
||||
apiConfiguration={{}}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
vscode={mockVscode}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText("8000")).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,269 @@
|
|||
import { render, screen } from "@testing-library/react"
|
||||
import userEvent from "@testing-library/user-event"
|
||||
import { OpenAI } from "../OpenAI"
|
||||
import type { ProviderSettings, ModelInfo } from "@roo-code/types"
|
||||
|
||||
vi.mock("@vscode/webview-ui-toolkit/react", () => ({
|
||||
VSCodeTextField: ({ children, value, onInput, type, placeholder }: any) => {
|
||||
const handleInput = (e: any) => {
|
||||
const event = { target: { value: e.target.value } }
|
||||
onInput?.(event)
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
{children && <label>{children}</label>}
|
||||
<input type={type} value={value} onInput={handleInput} placeholder={placeholder} />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock("vscrui", () => ({
|
||||
Checkbox: ({ children, checked, onChange }: any) => (
|
||||
<label>
|
||||
<input type="checkbox" checked={checked} onChange={(e) => onChange(e.target.checked)} />
|
||||
{children}
|
||||
</label>
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock("@src/i18n/TranslationContext", () => ({
|
||||
useAppTranslation: () => ({
|
||||
t: (key: string) => {
|
||||
const translations: Record<string, string> = {
|
||||
"settings:providers.openAiApiKey": "OpenAI API Key",
|
||||
"settings:providers.getOpenAiApiKey": "Get OpenAI API Key",
|
||||
"settings:providers.openAi.authenticated": "Authenticated with OpenAI",
|
||||
"settings:providers.openAi.updateKey": "Update API Key",
|
||||
"settings:providers.apiKeyStorageNotice": "API keys are stored securely in VSCode's Secret Storage",
|
||||
"settings:providers.useCustomBaseUrl": "Use custom base URL",
|
||||
"settings:placeholders.apiKey": "Enter API Key...",
|
||||
"settings:common.select": "Select",
|
||||
}
|
||||
return translations[key] || key
|
||||
},
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock("@src/components/common/VSCodeButtonLink", () => ({
|
||||
VSCodeButtonLink: ({ children, href, appearance }: any) => (
|
||||
<a href={href} data-appearance={appearance}>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock("@src/components/ui", () => ({
|
||||
Select: ({ children, value, onValueChange }: any) => (
|
||||
<select value={value} onChange={(e) => onValueChange(e.target.value)}>
|
||||
{children}
|
||||
</select>
|
||||
),
|
||||
SelectTrigger: ({ children }: any) => <div>{children}</div>,
|
||||
SelectValue: ({ placeholder }: any) => <span>{placeholder}</span>,
|
||||
SelectContent: ({ children }: any) => <>{children}</>,
|
||||
SelectItem: ({ children, value }: any) => <option value={value}>{children}</option>,
|
||||
StandardTooltip: ({ children, content }: any) => <span title={content}>{children}</span>,
|
||||
}))
|
||||
|
||||
describe("OpenAI", () => {
|
||||
const defaultApiConfiguration: ProviderSettings = {
|
||||
openAiNativeApiKey: "",
|
||||
openAiNativeBaseUrl: "",
|
||||
openAiNativeServiceTier: "default",
|
||||
}
|
||||
|
||||
const mockSetApiConfigurationField = vi.fn()
|
||||
const mockSelectedModelInfo: ModelInfo = {
|
||||
contextWindow: 128000,
|
||||
maxTokens: 8000,
|
||||
supportsPromptCache: false,
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("Authentication UI", () => {
|
||||
it("should show 'Get OpenAI API Key' button when no API key is present", () => {
|
||||
render(
|
||||
<OpenAI
|
||||
apiConfiguration={defaultApiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
selectedModelInfo={mockSelectedModelInfo}
|
||||
/>,
|
||||
)
|
||||
|
||||
const link = screen.getByText("Get OpenAI API Key")
|
||||
expect(link).toBeInTheDocument()
|
||||
expect(link.closest("a")).toHaveAttribute("href", "https://platform.openai.com/api-keys")
|
||||
})
|
||||
|
||||
it("should show authenticated status and update button when API key is present", () => {
|
||||
const apiConfiguration = { ...defaultApiConfiguration, openAiNativeApiKey: "test-api-key" }
|
||||
render(
|
||||
<OpenAI
|
||||
apiConfiguration={apiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
selectedModelInfo={mockSelectedModelInfo}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText("✓ Authenticated with OpenAI")).toBeInTheDocument()
|
||||
const updateLink = screen.getByText("Update API Key")
|
||||
expect(updateLink).toBeInTheDocument()
|
||||
expect(updateLink.closest("a")).toHaveAttribute("href", "https://platform.openai.com/api-keys")
|
||||
})
|
||||
|
||||
it("should not show authenticated status when no API key", () => {
|
||||
render(
|
||||
<OpenAI
|
||||
apiConfiguration={defaultApiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
selectedModelInfo={mockSelectedModelInfo}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.queryByText("✓ Authenticated with OpenAI")).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe("API Key Input", () => {
|
||||
it("should mask API key input", () => {
|
||||
render(
|
||||
<OpenAI
|
||||
apiConfiguration={defaultApiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
selectedModelInfo={mockSelectedModelInfo}
|
||||
/>,
|
||||
)
|
||||
|
||||
const input = screen.getByPlaceholderText("Enter API Key...") as HTMLInputElement
|
||||
expect(input.type).toBe("password")
|
||||
})
|
||||
|
||||
it("should show API key storage notice", () => {
|
||||
render(
|
||||
<OpenAI
|
||||
apiConfiguration={defaultApiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
selectedModelInfo={mockSelectedModelInfo}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText("API keys are stored securely in VSCode's Secret Storage")).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe("Custom Base URL", () => {
|
||||
it("should not show base URL input by default", () => {
|
||||
render(
|
||||
<OpenAI
|
||||
apiConfiguration={defaultApiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
selectedModelInfo={mockSelectedModelInfo}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.queryByPlaceholderText("https://api.openai.com/v1")).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("should show base URL input when checkbox is checked", async () => {
|
||||
const user = userEvent.setup()
|
||||
render(
|
||||
<OpenAI
|
||||
apiConfiguration={defaultApiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
selectedModelInfo={mockSelectedModelInfo}
|
||||
/>,
|
||||
)
|
||||
|
||||
const checkbox = screen.getByRole("checkbox")
|
||||
await user.click(checkbox)
|
||||
|
||||
expect(screen.getByPlaceholderText("https://api.openai.com/v1")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("should clear base URL when checkbox is unchecked", async () => {
|
||||
const user = userEvent.setup()
|
||||
const apiConfiguration = { ...defaultApiConfiguration, openAiNativeBaseUrl: "https://custom.url/v1" }
|
||||
render(
|
||||
<OpenAI
|
||||
apiConfiguration={apiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
selectedModelInfo={mockSelectedModelInfo}
|
||||
/>,
|
||||
)
|
||||
|
||||
const checkbox = screen.getByRole("checkbox")
|
||||
await user.click(checkbox)
|
||||
|
||||
expect(mockSetApiConfigurationField).toHaveBeenCalledWith("openAiNativeBaseUrl", "")
|
||||
})
|
||||
})
|
||||
|
||||
describe("Service Tier", () => {
|
||||
it("should not show service tier selector when model has no tiers", () => {
|
||||
render(
|
||||
<OpenAI
|
||||
apiConfiguration={defaultApiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
selectedModelInfo={mockSelectedModelInfo}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.queryByTestId("openai-service-tier")).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("should show service tier selector when model has flex or priority tiers", () => {
|
||||
const modelInfo: ModelInfo = {
|
||||
contextWindow: 128000,
|
||||
maxTokens: 8000,
|
||||
supportsPromptCache: false,
|
||||
tiers: [
|
||||
{ name: "flex", contextWindow: 128000 },
|
||||
{ name: "priority", contextWindow: 128000 },
|
||||
],
|
||||
}
|
||||
|
||||
render(
|
||||
<OpenAI
|
||||
apiConfiguration={defaultApiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
selectedModelInfo={modelInfo}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId("openai-service-tier")).toBeInTheDocument()
|
||||
expect(screen.getByText("Standard")).toBeInTheDocument()
|
||||
expect(screen.getByText("Flex")).toBeInTheDocument()
|
||||
expect(screen.getByText("Priority")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("should update service tier when selection changes", async () => {
|
||||
const modelInfo: ModelInfo = {
|
||||
contextWindow: 128000,
|
||||
maxTokens: 8000,
|
||||
supportsPromptCache: false,
|
||||
tiers: [
|
||||
{ name: "flex", contextWindow: 128000 },
|
||||
{ name: "priority", contextWindow: 128000 },
|
||||
],
|
||||
}
|
||||
|
||||
const user = userEvent.setup()
|
||||
render(
|
||||
<OpenAI
|
||||
apiConfiguration={defaultApiConfiguration}
|
||||
setApiConfigurationField={mockSetApiConfigurationField}
|
||||
selectedModelInfo={modelInfo}
|
||||
/>,
|
||||
)
|
||||
|
||||
const select = screen.getByRole("combobox")
|
||||
await user.selectOptions(select, "priority")
|
||||
|
||||
expect(mockSetApiConfigurationField).toHaveBeenCalledWith("openAiNativeServiceTier", "priority")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -332,6 +332,10 @@
|
|||
"apiKey": "API Key",
|
||||
"openAiBaseUrl": "Base URL",
|
||||
"getOpenAiApiKey": "Get OpenAI API Key",
|
||||
"openAi": {
|
||||
"authenticated": "Authenticated with OpenAI",
|
||||
"updateKey": "Update API Key"
|
||||
},
|
||||
"mistralApiKey": "Mistral API Key",
|
||||
"getMistralApiKey": "Get Mistral / Codestral API Key",
|
||||
"codestralBaseUrl": "Codestral Base URL (Optional)",
|
||||
|
|
@ -488,9 +492,12 @@
|
|||
},
|
||||
"setReasoningLevel": "Enable Reasoning Effort",
|
||||
"claudeCode": {
|
||||
"pathLabel": "Claude Code Path",
|
||||
"pathLabel": "Claude Code API Key",
|
||||
"description": "Optional path to your Claude Code CLI. Defaults to 'claude' if not set.",
|
||||
"placeholder": "Default: claude",
|
||||
"placeholder": "Enter your Claude Code API key",
|
||||
"getApiKey": "Get API Key",
|
||||
"updateKey": "Update Key",
|
||||
"authenticated": "Authenticated with Claude Code",
|
||||
"maxTokensLabel": "Max Output Tokens",
|
||||
"maxTokensDescription": "Maximum number of output tokens for Claude Code responses. Default is 8000."
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue