From 061251984eb96a051da16c6f6681cc980fc6720d Mon Sep 17 00:00:00 2001 From: Jonathan Evans Date: Wed, 1 Apr 2026 13:56:12 -0700 Subject: [PATCH] Add FirePass provider support for Kimi K2.5 Turbo - Add new 'firepass' provider type alongside anthropic, openai, openrouter - FirePass uses Fireworks AI's endpoint for Kimi K2.5 Turbo model - Subscription billing model ($7/week) with 256K context window - Anthropic API compatible (uses Anthropic SDK with custom baseURL) Changes: - providers.ts: Add firepass detection and base URL handling - auth.ts: Add FirePass API key management (FIREPASS_API_KEY or FIREWORKS_API_KEY) - config.ts: Add firepassApiKey and firepass auth provider - client.ts: Add firepass client creation with custom baseURL - http.ts: Add firepass auth headers - modelStrings.ts: Return Kimi K2.5 Turbo model ID for firepass - model.ts: Add Kimi display name handling and default model logic - modelOptions.ts: Simplified model picker for firepass (Kimi K2.5 Turbo only) - status.tsx: Display FirePass in status bar - login.tsx: Add FirePass option to provider selection - FirepassLoginFlow.tsx: New component for FirePass login flow Usage: 1. Run /login and select "FirePass" 2. Enter your Fireworks API key 3. Model picker shows Kimi K2.5 Turbo --- src/commands/login/login.tsx | 20 ++++++- src/components/FirepassLoginFlow.tsx | 89 ++++++++++++++++++++++++++++ src/services/api/client.ts | 22 +++++++ src/utils/auth.ts | 46 +++++++++++++- src/utils/config.ts | 3 +- src/utils/http.ts | 17 ++++++ src/utils/model/model.ts | 27 ++++++++- src/utils/model/modelOptions.ts | 84 ++++++++++++++++++++++++++ src/utils/model/modelStrings.ts | 13 ++++ src/utils/model/providers.ts | 69 +++++++++++++++++++-- src/utils/status.tsx | 7 +++ 11 files changed, 389 insertions(+), 8 deletions(-) create mode 100644 src/components/FirepassLoginFlow.tsx diff --git a/src/commands/login/login.tsx b/src/commands/login/login.tsx index a7419473..7435fdbd 100644 --- a/src/commands/login/login.tsx +++ b/src/commands/login/login.tsx @@ -11,6 +11,7 @@ import { ConfigurableShortcutHint } from '../../components/ConfigurableShortcutH import { ConsoleOAuthFlow } from '../../components/ConsoleOAuthFlow.js' import { Select } from '../../components/CustomSelect/select.js' import { Dialog } from '../../components/design-system/Dialog.js' +import { FirepassLoginFlow } from '../../components/FirepassLoginFlow.js' import { OpenAILoginFlow } from '../../components/OpenAILoginFlow.js' import { OpenRouterLoginFlow } from '../../components/OpenRouterLoginFlow.js' import { useMainLoopModel } from '../../hooks/useMainLoopModel.js' @@ -29,7 +30,7 @@ import { } from '../../utils/permissions/bypassPermissionsKillswitch.js' import { resetUserCache } from '../../utils/user.js' -type AuthProviderChoice = 'anthropic' | 'openai' | 'openrouter' +type AuthProviderChoice = 'anthropic' | 'openai' | 'openrouter' | 'firepass' export async function call( onDone: LocalJSXCommandOnDone, @@ -124,6 +125,18 @@ export function Login(props: { ), value: 'openrouter', }, + { + label: ( + + FirePass{' '} + + Fireworks API key with Kimi K2.5 Turbo subscription + + {'\n'} + + ), + value: 'firepass', + }, ], [], ) @@ -162,6 +175,11 @@ export function Login(props: { onDone={onFlowDone} startingMessage="Better-Clawd can use OpenRouter with your OpenRouter API key." /> + ) : selectedProvider === 'firepass' ? ( + ) : ( void + startingMessage?: string +} + +export function FirepassLoginFlow({ + onDone, + startingMessage, +}: FirepassLoginFlowProps): React.ReactNode { + const [isBusy, setIsBusy] = useState(false) + const [status, setStatus] = useState(null) + const [inputValue, setInputValue] = useState('') + const [cursorOffset, setCursorOffset] = useState(0) + + async function handleSubmit(value: string): Promise { + const trimmed = value.trim() + if (!trimmed) { + return + } + + setIsBusy(true) + setStatus(null) + try { + await saveFirepassApiKey(trimmed) + onDone() + } catch (error) { + setStatus(error instanceof Error ? error.message : String(error)) + } finally { + setIsBusy(false) + } + } + + if (isBusy) { + return ( + + + + Configuring FirePass login for Better-Clawd... + + + FirePass uses your Fireworks API key with the Anthropic-compatible + endpoint at `https://api.fireworks.ai/inference`. + + + ) + } + + return ( + + + {startingMessage ?? + 'Better-Clawd can use FirePass with your Fireworks API key.'} + + + FirePass provides subscription-based access to Kimi K2.5 Turbo with no + per-token charges. Get FirePass at{' '} + https://app.fireworks.ai/fire-pass + + + Paste your Fireworks API key: + { + setInputValue('') + setCursorOffset(0) + }} + cursorOffset={cursorOffset} + onChangeCursorOffset={setCursorOffset} + columns={72} + mask="*" + /> + + {status ? {status} : null} + + Press Enter to save, or Esc{' '} + to cancel. + + + ) +} \ No newline at end of file diff --git a/src/services/api/client.ts b/src/services/api/client.ts index 386f8d79..490344eb 100644 --- a/src/services/api/client.ts +++ b/src/services/api/client.ts @@ -6,6 +6,7 @@ import { getAnthropicApiKey, getApiKeyFromApiKeyHelper, getClaudeAIOAuthTokens, + getFirepassApiKey, getOpenAIApiKey, getOpenRouterApiKey, isClaudeAISubscriber, @@ -17,6 +18,7 @@ import { getUserAgent } from 'src/utils/http.js' import { getSmallFastModel } from 'src/utils/model/model.js' import { getAPIProvider, + getFirepassBaseUrl, getOpenAIBaseUrl, getOpenRouterBaseUrl, isFirstPartyAnthropicBaseUrl, @@ -316,6 +318,26 @@ export async function getAnthropicClient({ return new Anthropic(clientConfig) } + if (provider === 'firepass') { + // FirePass uses Fireworks AI's Anthropic-compatible endpoint + // Requires an active FirePass subscription for the kimi-k2p5-turbo router + const firepassKey = apiKey || getFirepassApiKey() + if (!firepassKey) { + throw new Error( + 'FirePass provider selected but no FirePass/Fireworks API key is configured. Set FIREPASS_API_KEY or FIREWORKS_API_KEY.', + ) + } + + const clientConfig: ConstructorParameters[0] = { + apiKey: firepassKey, + baseURL: getFirepassBaseUrl(), + ...ARGS, + ...(isDebugToStdErr() && { logger: createStderrLogger() }), + } + + return new Anthropic(clientConfig) + } + if (provider === 'openai') { await refreshOpenAIAuthTokenIfNeeded() const openAIKey = apiKey || getOpenAIApiKey() diff --git a/src/utils/auth.ts b/src/utils/auth.ts index 5a3d984b..69de610b 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -239,6 +239,12 @@ export type OpenRouterApiKeySource = | '/login managed OpenRouter key' | 'none' +export type FirepassApiKeySource = + | 'FIREPASS_API_KEY' + | 'FIREWORKS_API_KEY' + | '/login managed FirePass key' + | 'none' + export function getAnthropicApiKey(): null | string { const { key } = getAnthropicApiKeyWithSource() return key @@ -307,10 +313,33 @@ export function getOpenRouterApiKeyWithSource(): { : { key: null, source: 'none' } } +export function getFirepassApiKey(): null | string { + return getFirepassApiKeyWithSource().key +} + +export function getFirepassApiKeyWithSource(): { + key: null | string + source: FirepassApiKeySource +} { + // Check FIREPASS_API_KEY first, then fall back to FIREWORKS_API_KEY + if (process.env.FIREPASS_API_KEY) { + return { key: process.env.FIREPASS_API_KEY, source: 'FIREPASS_API_KEY' } + } + if (process.env.FIREWORKS_API_KEY) { + return { key: process.env.FIREWORKS_API_KEY, source: 'FIREWORKS_API_KEY' } + } + + const key = getGlobalConfig().firepassApiKey + return key + ? { key, source: '/login managed FirePass key' } + : { key: null, source: 'none' } +} + export function getConfiguredAuthProvider(): | 'anthropic' | 'openrouter' - | 'openai' { + | 'openai' + | 'firepass' { const storedProvider = getGlobalConfig().authProvider if (storedProvider) { return storedProvider @@ -322,6 +351,8 @@ export function getConfiguredAuthProvider(): return 'openrouter' case 'openai': return 'openai' + case 'firepass': + return 'firepass' default: return 'anthropic' } @@ -1406,6 +1437,19 @@ export async function saveOpenRouterApiKey(apiKey: string): Promise { })) } +export async function saveFirepassApiKey(apiKey: string): Promise { + if (!isValidApiKey(apiKey)) { + throw new Error( + 'Invalid API key format. API key must contain only alphanumeric characters, dashes, and underscores.', + ) + } + saveGlobalConfig(current => ({ + ...current, + authProvider: 'firepass', + firepassApiKey: apiKey, + })) +} + export function isCustomApiKeyApproved(apiKey: string): boolean { const config = getGlobalConfig() const normalizedKey = normalizeApiKeyForConfig(apiKey) diff --git a/src/utils/config.ts b/src/utils/config.ts index 6fa44486..2057f0ca 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -221,7 +221,7 @@ export type GlobalConfig = { approved?: string[] rejected?: string[] } - authProvider?: 'anthropic' | 'openrouter' | 'openai' + authProvider?: 'anthropic' | 'openrouter' | 'openai' | 'firepass' primaryApiKey?: string // Primary API key for the user when no environment variable is set, set via oauth (TODO: rename) openAiApiKey?: string openAiAccessToken?: string @@ -229,6 +229,7 @@ export type GlobalConfig = { openAiTokenExpiresAt?: number openAiWorkspaceId?: string openRouterApiKey?: string + firepassApiKey?: string hasAcknowledgedCostThreshold?: boolean hasSeenUndercoverAutoNotice?: boolean // ant-only: whether the one-time auto-undercover explainer has been shown hasSeenUltraplanTerms?: boolean // ant-only: whether the one-time CCR terms notice has been shown in the ultraplan launch dialog diff --git a/src/utils/http.ts b/src/utils/http.ts index f875bdd5..bc9e7da8 100644 --- a/src/utils/http.ts +++ b/src/utils/http.ts @@ -11,6 +11,7 @@ import { OAUTH_BETA_HEADER } from '../constants/oauth.js' import { getAnthropicApiKey, getClaudeAIOAuthTokens, + getFirepassApiKey, getOpenAIApiKey, getOpenRouterApiKey, handleOAuth401Error, @@ -100,6 +101,22 @@ export function getAuthHeaders(): AuthHeaders { } } + if (provider === 'firepass') { + const apiKey = getFirepassApiKey() + if (!apiKey) { + return { + headers: {}, + error: 'No FirePass API key available', + } + } + // FirePass uses x-api-key header like Anthropic + return { + headers: { + 'x-api-key': apiKey, + }, + } + } + if (isClaudeAISubscriber()) { const oauthTokens = getClaudeAIOAuthTokens() if (!oauthTokens?.accessToken) { diff --git a/src/utils/model/model.ts b/src/utils/model/model.ts index a9f8eb1a..b2337f4a 100644 --- a/src/utils/model/model.ts +++ b/src/utils/model/model.ts @@ -109,6 +109,10 @@ export function getDefaultOpusModel(): ModelName { if (process.env.ANTHROPIC_DEFAULT_OPUS_MODEL) { return process.env.ANTHROPIC_DEFAULT_OPUS_MODEL } + // FirePass uses Kimi K2.5 Turbo for all model tiers + if (getAPIProvider() === 'firepass') { + return getModelStrings().opus46 + } // 3P providers (Bedrock, Vertex, Foundry) — kept as a separate branch // even when values match, since 3P availability lags firstParty and // these will diverge again at the next model launch. @@ -123,6 +127,10 @@ export function getDefaultSonnetModel(): ModelName { if (process.env.ANTHROPIC_DEFAULT_SONNET_MODEL) { return process.env.ANTHROPIC_DEFAULT_SONNET_MODEL } + // FirePass uses Kimi K2.5 Turbo for all model tiers + if (getAPIProvider() === 'firepass') { + return getModelStrings().sonnet46 + } // Default to Sonnet 4.5 for 3P since they may not have 4.6 yet if (getAPIProvider() !== 'firstParty') { return getModelStrings().sonnet45 @@ -135,7 +143,10 @@ export function getDefaultHaikuModel(): ModelName { if (process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL) { return process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL } - + // FirePass uses Kimi K2.5 Turbo for all model tiers + if (getAPIProvider() === 'firepass') { + return getModelStrings().haiku45 + } // Haiku 4.5 is available on all platforms (first-party, Foundry, Bedrock, Vertex) return getModelStrings().haiku45 } @@ -187,6 +198,11 @@ export function getDefaultMainLoopModelSetting(): ModelName | ModelAlias { ) } + // FirePass uses Kimi K2.5 Turbo for all users + if (getAPIProvider() === 'firepass') { + return getModelStrings().sonnet46 // Returns Kimi K2.5 Turbo + } + // Max users get Opus as default if (isMaxSubscriber()) { return getDefaultOpusModel() + (isOpus1mMergeEnabled() ? '[1m]' : '') @@ -350,6 +366,10 @@ export function renderModelSetting(setting: ModelName | ModelAlias): string { * if the model is not recognized as a public model. */ export function getPublicModelDisplayName(model: ModelName): string | null { + // FirePass Kimi K2.5 Turbo + if (model.includes('kimi-k2p5-turbo') || model.includes('kimi-k2.5-turbo')) { + return 'Kimi K2.5 Turbo' + } switch (model) { case getModelStrings().opus46: return 'Opus 4.6' @@ -576,6 +596,11 @@ export function getMarketingNameForModel(modelId: string): string | undefined { return undefined } + // FirePass Kimi K2.5 Turbo + if (modelId.includes('kimi-k2p5-turbo') || modelId.includes('kimi-k2.5-turbo')) { + return 'Kimi K2.5 Turbo' + } + const has1m = modelId.toLowerCase().includes('[1m]') const canonical = getCanonicalName(modelId) diff --git a/src/utils/model/modelOptions.ts b/src/utils/model/modelOptions.ts index fa3fd026..fcb16b7c 100644 --- a/src/utils/model/modelOptions.ts +++ b/src/utils/model/modelOptions.ts @@ -45,6 +45,7 @@ export type ModelOption = { export function getDefaultOptionForUser(fastMode = false): ModelOption { const provider = getAPIProvider() const isOpenAI = provider === 'openai' + const isFirepass = provider === 'firepass' if (process.env.USER_TYPE === 'ant') { const currentModel = renderDefaultModelSetting( getDefaultMainLoopModelSetting(), @@ -68,6 +69,14 @@ export function getDefaultOptionForUser(fastMode = false): ModelOption { // PAYG const is3P = provider !== 'firstParty' + if (isFirepass) { + return { + value: null, + label: 'Default (recommended)', + description: 'Use Kimi K2.5 Turbo (FirePass subscription)', + descriptionForModel: 'Kimi K2.5 Turbo - FirePass subscription with 256K context', + } + } return { value: null, label: 'Default (recommended)', @@ -109,6 +118,16 @@ function getSonnet46Option(): ModelOption { 'GPT-5.4 - recommended for most coding and agentic tasks on OpenAI', } } + if (provider === 'firepass') { + const firepassModel = getModelStrings().sonnet46 + return { + value: firepassModel, + label: 'Kimi K2.5 Turbo', + description: 'Kimi K2.5 Turbo · FirePass subscription model', + descriptionForModel: + 'Kimi K2.5 Turbo - FirePass subscription with 256K context, no per-token charges', + } + } return { value: is3P ? getModelStrings().sonnet46 : 'sonnet', label: 'Sonnet', @@ -156,6 +175,16 @@ function getOpus46Option(fastMode = false): ModelOption { 'GPT-5.4 - most capable OpenAI model for complex coding work', } } + if (provider === 'firepass') { + const firepassModel = getModelStrings().opus46 + return { + value: firepassModel, + label: 'Kimi K2.5 Turbo', + description: 'Kimi K2.5 Turbo · FirePass subscription model', + descriptionForModel: + 'Kimi K2.5 Turbo - FirePass subscription with 256K context, no per-token charges', + } + } return { value: is3P ? getModelStrings().opus46 : 'opus', label: 'Opus', @@ -176,6 +205,17 @@ export function getSonnet46_1MOption(): ModelOption { 'GPT-5.4 for long-running OpenAI sessions and large codebases', } } + if (provider === 'firepass') { + // FirePass Kimi has 256K context, no need for separate 1M option + const firepassModel = getModelStrings().sonnet46 + return { + value: firepassModel, + label: 'Kimi K2.5 Turbo', + description: 'Kimi K2.5 Turbo · 256K context included', + descriptionForModel: + 'Kimi K2.5 Turbo - 256K context window for long sessions', + } + } return { value: is3P ? getModelStrings().sonnet46 + '[1m]' : 'sonnet[1m]', label: 'Sonnet (1M context)', @@ -197,6 +237,17 @@ export function getOpus46_1MOption(fastMode = false): ModelOption { 'GPT-5.4 for long-running OpenAI sessions and large codebases', } } + if (provider === 'firepass') { + // FirePass Kimi has 256K context, no need for separate 1M option + const firepassModel = getModelStrings().opus46 + return { + value: firepassModel, + label: 'Kimi K2.5 Turbo', + description: 'Kimi K2.5 Turbo · 256K context included', + descriptionForModel: + 'Kimi K2.5 Turbo - 256K context window for long sessions', + } + } return { value: is3P ? getModelStrings().opus46 + '[1m]' : 'opus[1m]', label: 'Opus (1M context)', @@ -234,6 +285,16 @@ function getHaiku45Option(): ModelOption { 'GPT-5.4 Mini - fastest OpenAI option for quick answers and lightweight tasks', } } + if (provider === 'firepass') { + const firepassModel = getModelStrings().haiku45 + return { + value: firepassModel, + label: 'Kimi K2.5 Turbo', + description: 'Kimi K2.5 Turbo · FirePass subscription model', + descriptionForModel: + 'Kimi K2.5 Turbo - FirePass subscription with 256K context, no per-token charges', + } + } return { value: 'haiku', label: 'Haiku', @@ -255,6 +316,16 @@ function getHaiku35Option(): ModelOption { 'GPT-5.4 Mini - lower latency OpenAI model for simple tasks', } } + if (provider === 'firepass') { + const firepassModel = getModelStrings().haiku35 + return { + value: firepassModel, + label: 'Kimi K2.5 Turbo', + description: 'Kimi K2.5 Turbo · FirePass subscription model', + descriptionForModel: + 'Kimi K2.5 Turbo - FirePass subscription with 256K context, no per-token charges', + } + } return { value: 'haiku', label: 'Haiku', @@ -405,6 +476,19 @@ function getModelOptionsBase(fastMode = false): ModelOption[] { return payg1POptions } + // FirePass: Simple list with just Kimi K2.5 Turbo + if (getAPIProvider() === 'firepass') { + const firepassModel = getModelStrings().sonnet46 + return [ + getDefaultOptionForUser(fastMode), + { + value: firepassModel, + label: 'Kimi K2.5 Turbo', + description: 'Kimi K2.5 Turbo · 256K context, subscription billing', + }, + ] + } + // PAYG 3P: Default (Sonnet 4.5) + Sonnet (3P custom) or Sonnet 4.6/1M + Opus (3P custom) or Opus 4.1/Opus 4.6/Opus1M + Haiku + Opus 4.1 const payg3pOptions = [getDefaultOptionForUser(fastMode)] diff --git a/src/utils/model/modelStrings.ts b/src/utils/model/modelStrings.ts index 1db7e348..1e2d7fc7 100644 --- a/src/utils/model/modelStrings.ts +++ b/src/utils/model/modelStrings.ts @@ -35,10 +35,23 @@ function getBuiltinModelStrings(provider: APIProvider): ModelStrings { const out = getBuiltinModelStrings('firstParty') as Record out.sonnet46 = process.env.OPENROUTER_SONNET_MODEL || 'anthropic/claude-sonnet-4.6' + out.opus46 = process.env.OPENROUTER_OPUS_MODEL || 'anthropic/claude-opus-4.6' return out as ModelStrings } + if (provider === 'firepass') { + // FirePass uses Fireworks AI's Kimi K2.5 Turbo by default + // Users can override with FIREPASS_MODEL env var + const out = getBuiltinModelStrings('firstParty') as Record + const firepassModel = + process.env.FIREPASS_MODEL || 'accounts/fireworks/routers/kimi-k2p5-turbo' + out.haiku45 = firepassModel + out.sonnet46 = firepassModel + out.opus46 = firepassModel + return out as ModelStrings + } + const out = {} as ModelStrings for (const key of MODEL_KEYS) { out[key] = ALL_MODEL_CONFIGS[key][provider] diff --git a/src/utils/model/providers.ts b/src/utils/model/providers.ts index c48dc6f7..615861b6 100644 --- a/src/utils/model/providers.ts +++ b/src/utils/model/providers.ts @@ -5,6 +5,7 @@ export type APIProvider = | 'firstParty' | 'openrouter' | 'openai' + | 'firepass' | 'bedrock' | 'vertex' | 'foundry' @@ -20,10 +21,11 @@ function getStoredProviderPreference(): APIProvider | null { require('../env.js') as typeof import('../env.js') const raw = readFileSync(getGlobalClaudeFile(), 'utf8') const config = JSON.parse(raw) as { - authProvider?: 'anthropic' | 'openrouter' | 'openai' + authProvider?: 'anthropic' | 'openrouter' | 'openai' | 'firepass' openRouterApiKey?: string openAiApiKey?: string openAiAccessToken?: string + firepassApiKey?: string } switch (config.authProvider) { @@ -33,6 +35,8 @@ function getStoredProviderPreference(): APIProvider | null { return config.openAiApiKey || config.openAiAccessToken ? 'openai' : null + case 'firepass': + return config.firepassApiKey ? 'firepass' : null case 'anthropic': return 'firstParty' default: @@ -57,6 +61,10 @@ function getExplicitProviderOverride(): APIProvider | null { return 'openrouter' case 'openai': return 'openai' + case 'firepass': + case 'fire-pass': + case 'fire_pass': + return 'firepass' case 'bedrock': return 'bedrock' case 'vertex': @@ -98,6 +106,28 @@ export function isOpenAIConfigured(): boolean { ) } +export function isFirepassBaseUrl(baseUrl?: string | null): boolean { + if (!baseUrl) { + return false + } + try { + const host = new URL(baseUrl).host + return host === 'api.fireworks.ai' + } catch { + return false + } +} + +export function isFirepassConfigured(): boolean { + return ( + getExplicitProviderOverride() === 'firepass' || + Boolean(process.env.FIREPASS_API_KEY) || + Boolean(process.env.FIREWORKS_API_KEY) || + isFirepassBaseUrl(process.env.FIREPASS_BASE_URL) || + isFirepassBaseUrl(process.env.ANTHROPIC_BASE_URL) + ) +} + export function getOpenRouterBaseUrl(): string { const configuredBaseUrl = process.env.OPENROUTER_BASE_URL const fallbackBaseUrl = 'https://openrouter.ai/api' @@ -129,6 +159,35 @@ export function getOpenAIBaseUrl(): string { return process.env.OPENAI_BASE_URL ?? 'https://api.openai.com/v1' } +/** + * Get the FirePass base URL for Anthropic-compatible API. + * FirePass uses Fireworks AI's inference endpoint with Anthropic compatibility. + * Default: https://api.fireworks.ai/inference (Anthropic SDK appends /v1/messages) + */ +export function getFirepassBaseUrl(): string { + const configuredBaseUrl = process.env.FIREPASS_BASE_URL + if (!configuredBaseUrl) { + return 'https://api.fireworks.ai/inference' + } + + try { + const url = new URL(configuredBaseUrl) + + // Normalize path for Anthropic SDK compatibility + // SDK appends /v1/messages, so base should be /inference not /inference/v1 + if (url.host === 'api.fireworks.ai') { + const normalizedPath = url.pathname.replace(/\/+$/, '') + if (normalizedPath === '/inference/v1' || normalizedPath === '/v1') { + url.pathname = '/inference' + } + } + + return url.toString().replace(/\/$/, '') + } catch { + return configuredBaseUrl + } +} + export function getAPIProvider(): APIProvider { const explicitProvider = getExplicitProviderOverride() if (explicitProvider) { @@ -143,9 +202,11 @@ export function getAPIProvider(): APIProvider { ? 'foundry' : isOpenAIConfigured() ? 'openai' - : isOpenRouterConfigured() - ? 'openrouter' - : getStoredProviderPreference() ?? 'firstParty' + : isFirepassConfigured() + ? 'firepass' + : isOpenRouterConfigured() + ? 'openrouter' + : getStoredProviderPreference() ?? 'firstParty' } export function getAPIProviderForStatsig(): AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS { diff --git a/src/utils/status.tsx b/src/utils/status.tsx index ef4b946e..bc239cd1 100644 --- a/src/utils/status.tsx +++ b/src/utils/status.tsx @@ -13,6 +13,7 @@ import { getIdeClientName, type IDEExtensionInstallationStatus, isJetBrainsIde, import { getClaudeAiUserDefaultModelDescription, modelDisplayString } from './model/model.js'; import { getAPIProvider, + getFirepassBaseUrl, getOpenAIBaseUrl, getOpenRouterBaseUrl, } from './model/providers.js'; @@ -248,6 +249,7 @@ export function buildAPIProviderProperties(): Property[] { const providerLabel = { openrouter: 'OpenRouter', openai: 'OpenAI', + firepass: 'FirePass', bedrock: 'AWS Bedrock', vertex: 'Google Vertex AI', foundry: 'Microsoft Foundry' @@ -275,6 +277,11 @@ export function buildAPIProviderProperties(): Property[] { label: 'OpenAI base URL', value: getOpenAIBaseUrl() }); + } else if (apiProvider === 'firepass') { + properties.push({ + label: 'FirePass base URL', + value: getFirepassBaseUrl() + }); } else if (apiProvider === 'bedrock') { const bedrockBaseUrl = process.env.BEDROCK_BASE_URL; if (bedrockBaseUrl) {