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
This commit is contained in:
Jonathan Evans
2026-04-01 13:56:12 -07:00
Unverified
parent c9c145cf97
commit 061251984e
11 changed files with 389 additions and 8 deletions

View File

@@ -11,6 +11,7 @@ import { ConfigurableShortcutHint } from '../../components/ConfigurableShortcutH
import { ConsoleOAuthFlow } from '../../components/ConsoleOAuthFlow.js' import { ConsoleOAuthFlow } from '../../components/ConsoleOAuthFlow.js'
import { Select } from '../../components/CustomSelect/select.js' import { Select } from '../../components/CustomSelect/select.js'
import { Dialog } from '../../components/design-system/Dialog.js' import { Dialog } from '../../components/design-system/Dialog.js'
import { FirepassLoginFlow } from '../../components/FirepassLoginFlow.js'
import { OpenAILoginFlow } from '../../components/OpenAILoginFlow.js' import { OpenAILoginFlow } from '../../components/OpenAILoginFlow.js'
import { OpenRouterLoginFlow } from '../../components/OpenRouterLoginFlow.js' import { OpenRouterLoginFlow } from '../../components/OpenRouterLoginFlow.js'
import { useMainLoopModel } from '../../hooks/useMainLoopModel.js' import { useMainLoopModel } from '../../hooks/useMainLoopModel.js'
@@ -29,7 +30,7 @@ import {
} from '../../utils/permissions/bypassPermissionsKillswitch.js' } from '../../utils/permissions/bypassPermissionsKillswitch.js'
import { resetUserCache } from '../../utils/user.js' import { resetUserCache } from '../../utils/user.js'
type AuthProviderChoice = 'anthropic' | 'openai' | 'openrouter' type AuthProviderChoice = 'anthropic' | 'openai' | 'openrouter' | 'firepass'
export async function call( export async function call(
onDone: LocalJSXCommandOnDone, onDone: LocalJSXCommandOnDone,
@@ -124,6 +125,18 @@ export function Login(props: {
), ),
value: 'openrouter', value: 'openrouter',
}, },
{
label: (
<Text>
FirePass{' '}
<Text dimColor={true}>
Fireworks API key with Kimi K2.5 Turbo subscription
</Text>
{'\n'}
</Text>
),
value: 'firepass',
},
], ],
[], [],
) )
@@ -162,6 +175,11 @@ export function Login(props: {
onDone={onFlowDone} onDone={onFlowDone}
startingMessage="Better-Clawd can use OpenRouter with your OpenRouter API key." startingMessage="Better-Clawd can use OpenRouter with your OpenRouter API key."
/> />
) : selectedProvider === 'firepass' ? (
<FirepassLoginFlow
onDone={onFlowDone}
startingMessage="Better-Clawd can use FirePass with your Fireworks API key for Kimi K2.5 Turbo."
/>
) : ( ) : (
<ConsoleOAuthFlow <ConsoleOAuthFlow
onDone={onFlowDone} onDone={onFlowDone}

View File

@@ -0,0 +1,89 @@
import * as React from 'react'
import { useState } from 'react'
import { Box, Text } from '../ink.js'
import { saveFirepassApiKey } from '../utils/auth.js'
import { Spinner } from './Spinner.js'
import TextInput from './TextInput.js'
type FirepassLoginFlowProps = {
onDone: () => void
startingMessage?: string
}
export function FirepassLoginFlow({
onDone,
startingMessage,
}: FirepassLoginFlowProps): React.ReactNode {
const [isBusy, setIsBusy] = useState(false)
const [status, setStatus] = useState<string | null>(null)
const [inputValue, setInputValue] = useState('')
const [cursorOffset, setCursorOffset] = useState(0)
async function handleSubmit(value: string): Promise<void> {
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 (
<Box flexDirection="column" gap={1}>
<Box>
<Spinner />
<Text>Configuring FirePass login for Better-Clawd...</Text>
</Box>
<Text dimColor={true}>
FirePass uses your Fireworks API key with the Anthropic-compatible
endpoint at `https://api.fireworks.ai/inference`.
</Text>
</Box>
)
}
return (
<Box flexDirection="column" gap={1}>
<Text>
{startingMessage ??
'Better-Clawd can use FirePass with your Fireworks API key.'}
</Text>
<Text dimColor={true}>
FirePass provides subscription-based access to Kimi K2.5 Turbo with no
per-token charges. Get FirePass at{' '}
<Text color="cyan">https://app.fireworks.ai/fire-pass</Text>
</Text>
<Box>
<Text>Paste your Fireworks API key:</Text>
<TextInput
value={inputValue}
onChange={setInputValue}
onSubmit={handleSubmit}
onExit={() => {
setInputValue('')
setCursorOffset(0)
}}
cursorOffset={cursorOffset}
onChangeCursorOffset={setCursorOffset}
columns={72}
mask="*"
/>
</Box>
{status ? <Text color="error">{status}</Text> : null}
<Text dimColor={true}>
Press <Text bold={true}>Enter</Text> to save, or <Text bold={true}>Esc</Text>{' '}
to cancel.
</Text>
</Box>
)
}

View File

@@ -6,6 +6,7 @@ import {
getAnthropicApiKey, getAnthropicApiKey,
getApiKeyFromApiKeyHelper, getApiKeyFromApiKeyHelper,
getClaudeAIOAuthTokens, getClaudeAIOAuthTokens,
getFirepassApiKey,
getOpenAIApiKey, getOpenAIApiKey,
getOpenRouterApiKey, getOpenRouterApiKey,
isClaudeAISubscriber, isClaudeAISubscriber,
@@ -17,6 +18,7 @@ import { getUserAgent } from 'src/utils/http.js'
import { getSmallFastModel } from 'src/utils/model/model.js' import { getSmallFastModel } from 'src/utils/model/model.js'
import { import {
getAPIProvider, getAPIProvider,
getFirepassBaseUrl,
getOpenAIBaseUrl, getOpenAIBaseUrl,
getOpenRouterBaseUrl, getOpenRouterBaseUrl,
isFirstPartyAnthropicBaseUrl, isFirstPartyAnthropicBaseUrl,
@@ -316,6 +318,26 @@ export async function getAnthropicClient({
return new Anthropic(clientConfig) 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<typeof Anthropic>[0] = {
apiKey: firepassKey,
baseURL: getFirepassBaseUrl(),
...ARGS,
...(isDebugToStdErr() && { logger: createStderrLogger() }),
}
return new Anthropic(clientConfig)
}
if (provider === 'openai') { if (provider === 'openai') {
await refreshOpenAIAuthTokenIfNeeded() await refreshOpenAIAuthTokenIfNeeded()
const openAIKey = apiKey || getOpenAIApiKey() const openAIKey = apiKey || getOpenAIApiKey()

View File

@@ -239,6 +239,12 @@ export type OpenRouterApiKeySource =
| '/login managed OpenRouter key' | '/login managed OpenRouter key'
| 'none' | 'none'
export type FirepassApiKeySource =
| 'FIREPASS_API_KEY'
| 'FIREWORKS_API_KEY'
| '/login managed FirePass key'
| 'none'
export function getAnthropicApiKey(): null | string { export function getAnthropicApiKey(): null | string {
const { key } = getAnthropicApiKeyWithSource() const { key } = getAnthropicApiKeyWithSource()
return key return key
@@ -307,10 +313,33 @@ export function getOpenRouterApiKeyWithSource(): {
: { key: null, source: 'none' } : { 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(): export function getConfiguredAuthProvider():
| 'anthropic' | 'anthropic'
| 'openrouter' | 'openrouter'
| 'openai' { | 'openai'
| 'firepass' {
const storedProvider = getGlobalConfig().authProvider const storedProvider = getGlobalConfig().authProvider
if (storedProvider) { if (storedProvider) {
return storedProvider return storedProvider
@@ -322,6 +351,8 @@ export function getConfiguredAuthProvider():
return 'openrouter' return 'openrouter'
case 'openai': case 'openai':
return 'openai' return 'openai'
case 'firepass':
return 'firepass'
default: default:
return 'anthropic' return 'anthropic'
} }
@@ -1406,6 +1437,19 @@ export async function saveOpenRouterApiKey(apiKey: string): Promise<void> {
})) }))
} }
export async function saveFirepassApiKey(apiKey: string): Promise<void> {
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 { export function isCustomApiKeyApproved(apiKey: string): boolean {
const config = getGlobalConfig() const config = getGlobalConfig()
const normalizedKey = normalizeApiKeyForConfig(apiKey) const normalizedKey = normalizeApiKeyForConfig(apiKey)

View File

@@ -221,7 +221,7 @@ export type GlobalConfig = {
approved?: string[] approved?: string[]
rejected?: 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) primaryApiKey?: string // Primary API key for the user when no environment variable is set, set via oauth (TODO: rename)
openAiApiKey?: string openAiApiKey?: string
openAiAccessToken?: string openAiAccessToken?: string
@@ -229,6 +229,7 @@ export type GlobalConfig = {
openAiTokenExpiresAt?: number openAiTokenExpiresAt?: number
openAiWorkspaceId?: string openAiWorkspaceId?: string
openRouterApiKey?: string openRouterApiKey?: string
firepassApiKey?: string
hasAcknowledgedCostThreshold?: boolean hasAcknowledgedCostThreshold?: boolean
hasSeenUndercoverAutoNotice?: boolean // ant-only: whether the one-time auto-undercover explainer has been shown 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 hasSeenUltraplanTerms?: boolean // ant-only: whether the one-time CCR terms notice has been shown in the ultraplan launch dialog

View File

@@ -11,6 +11,7 @@ import { OAUTH_BETA_HEADER } from '../constants/oauth.js'
import { import {
getAnthropicApiKey, getAnthropicApiKey,
getClaudeAIOAuthTokens, getClaudeAIOAuthTokens,
getFirepassApiKey,
getOpenAIApiKey, getOpenAIApiKey,
getOpenRouterApiKey, getOpenRouterApiKey,
handleOAuth401Error, 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()) { if (isClaudeAISubscriber()) {
const oauthTokens = getClaudeAIOAuthTokens() const oauthTokens = getClaudeAIOAuthTokens()
if (!oauthTokens?.accessToken) { if (!oauthTokens?.accessToken) {

View File

@@ -109,6 +109,10 @@ export function getDefaultOpusModel(): ModelName {
if (process.env.ANTHROPIC_DEFAULT_OPUS_MODEL) { if (process.env.ANTHROPIC_DEFAULT_OPUS_MODEL) {
return 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 // 3P providers (Bedrock, Vertex, Foundry) — kept as a separate branch
// even when values match, since 3P availability lags firstParty and // even when values match, since 3P availability lags firstParty and
// these will diverge again at the next model launch. // these will diverge again at the next model launch.
@@ -123,6 +127,10 @@ export function getDefaultSonnetModel(): ModelName {
if (process.env.ANTHROPIC_DEFAULT_SONNET_MODEL) { if (process.env.ANTHROPIC_DEFAULT_SONNET_MODEL) {
return 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 // Default to Sonnet 4.5 for 3P since they may not have 4.6 yet
if (getAPIProvider() !== 'firstParty') { if (getAPIProvider() !== 'firstParty') {
return getModelStrings().sonnet45 return getModelStrings().sonnet45
@@ -135,7 +143,10 @@ export function getDefaultHaikuModel(): ModelName {
if (process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL) { if (process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL) {
return 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) // Haiku 4.5 is available on all platforms (first-party, Foundry, Bedrock, Vertex)
return getModelStrings().haiku45 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 // Max users get Opus as default
if (isMaxSubscriber()) { if (isMaxSubscriber()) {
return getDefaultOpusModel() + (isOpus1mMergeEnabled() ? '[1m]' : '') 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. * if the model is not recognized as a public model.
*/ */
export function getPublicModelDisplayName(model: ModelName): string | null { 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) { switch (model) {
case getModelStrings().opus46: case getModelStrings().opus46:
return 'Opus 4.6' return 'Opus 4.6'
@@ -576,6 +596,11 @@ export function getMarketingNameForModel(modelId: string): string | undefined {
return 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 has1m = modelId.toLowerCase().includes('[1m]')
const canonical = getCanonicalName(modelId) const canonical = getCanonicalName(modelId)

View File

@@ -45,6 +45,7 @@ export type ModelOption = {
export function getDefaultOptionForUser(fastMode = false): ModelOption { export function getDefaultOptionForUser(fastMode = false): ModelOption {
const provider = getAPIProvider() const provider = getAPIProvider()
const isOpenAI = provider === 'openai' const isOpenAI = provider === 'openai'
const isFirepass = provider === 'firepass'
if (process.env.USER_TYPE === 'ant') { if (process.env.USER_TYPE === 'ant') {
const currentModel = renderDefaultModelSetting( const currentModel = renderDefaultModelSetting(
getDefaultMainLoopModelSetting(), getDefaultMainLoopModelSetting(),
@@ -68,6 +69,14 @@ export function getDefaultOptionForUser(fastMode = false): ModelOption {
// PAYG // PAYG
const is3P = provider !== 'firstParty' 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 { return {
value: null, value: null,
label: 'Default (recommended)', label: 'Default (recommended)',
@@ -109,6 +118,16 @@ function getSonnet46Option(): ModelOption {
'GPT-5.4 - recommended for most coding and agentic tasks on OpenAI', '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 { return {
value: is3P ? getModelStrings().sonnet46 : 'sonnet', value: is3P ? getModelStrings().sonnet46 : 'sonnet',
label: 'Sonnet', label: 'Sonnet',
@@ -156,6 +175,16 @@ function getOpus46Option(fastMode = false): ModelOption {
'GPT-5.4 - most capable OpenAI model for complex coding work', '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 { return {
value: is3P ? getModelStrings().opus46 : 'opus', value: is3P ? getModelStrings().opus46 : 'opus',
label: 'Opus', label: 'Opus',
@@ -176,6 +205,17 @@ export function getSonnet46_1MOption(): ModelOption {
'GPT-5.4 for long-running OpenAI sessions and large codebases', '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 { return {
value: is3P ? getModelStrings().sonnet46 + '[1m]' : 'sonnet[1m]', value: is3P ? getModelStrings().sonnet46 + '[1m]' : 'sonnet[1m]',
label: 'Sonnet (1M context)', 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', '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 { return {
value: is3P ? getModelStrings().opus46 + '[1m]' : 'opus[1m]', value: is3P ? getModelStrings().opus46 + '[1m]' : 'opus[1m]',
label: 'Opus (1M context)', label: 'Opus (1M context)',
@@ -234,6 +285,16 @@ function getHaiku45Option(): ModelOption {
'GPT-5.4 Mini - fastest OpenAI option for quick answers and lightweight tasks', '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 { return {
value: 'haiku', value: 'haiku',
label: 'Haiku', label: 'Haiku',
@@ -255,6 +316,16 @@ function getHaiku35Option(): ModelOption {
'GPT-5.4 Mini - lower latency OpenAI model for simple tasks', '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 { return {
value: 'haiku', value: 'haiku',
label: 'Haiku', label: 'Haiku',
@@ -405,6 +476,19 @@ function getModelOptionsBase(fastMode = false): ModelOption[] {
return payg1POptions 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 // 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)] const payg3pOptions = [getDefaultOptionForUser(fastMode)]

View File

@@ -35,10 +35,23 @@ function getBuiltinModelStrings(provider: APIProvider): ModelStrings {
const out = getBuiltinModelStrings('firstParty') as Record<string, string> const out = getBuiltinModelStrings('firstParty') as Record<string, string>
out.sonnet46 = out.sonnet46 =
process.env.OPENROUTER_SONNET_MODEL || 'anthropic/claude-sonnet-4.6' process.env.OPENROUTER_SONNET_MODEL || 'anthropic/claude-sonnet-4.6'
out.opus46 =
process.env.OPENROUTER_OPUS_MODEL || 'anthropic/claude-opus-4.6' process.env.OPENROUTER_OPUS_MODEL || 'anthropic/claude-opus-4.6'
return out as ModelStrings 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<string, string>
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 const out = {} as ModelStrings
for (const key of MODEL_KEYS) { for (const key of MODEL_KEYS) {
out[key] = ALL_MODEL_CONFIGS[key][provider] out[key] = ALL_MODEL_CONFIGS[key][provider]

View File

@@ -5,6 +5,7 @@ export type APIProvider =
| 'firstParty' | 'firstParty'
| 'openrouter' | 'openrouter'
| 'openai' | 'openai'
| 'firepass'
| 'bedrock' | 'bedrock'
| 'vertex' | 'vertex'
| 'foundry' | 'foundry'
@@ -20,10 +21,11 @@ function getStoredProviderPreference(): APIProvider | null {
require('../env.js') as typeof import('../env.js') require('../env.js') as typeof import('../env.js')
const raw = readFileSync(getGlobalClaudeFile(), 'utf8') const raw = readFileSync(getGlobalClaudeFile(), 'utf8')
const config = JSON.parse(raw) as { const config = JSON.parse(raw) as {
authProvider?: 'anthropic' | 'openrouter' | 'openai' authProvider?: 'anthropic' | 'openrouter' | 'openai' | 'firepass'
openRouterApiKey?: string openRouterApiKey?: string
openAiApiKey?: string openAiApiKey?: string
openAiAccessToken?: string openAiAccessToken?: string
firepassApiKey?: string
} }
switch (config.authProvider) { switch (config.authProvider) {
@@ -33,6 +35,8 @@ function getStoredProviderPreference(): APIProvider | null {
return config.openAiApiKey || config.openAiAccessToken return config.openAiApiKey || config.openAiAccessToken
? 'openai' ? 'openai'
: null : null
case 'firepass':
return config.firepassApiKey ? 'firepass' : null
case 'anthropic': case 'anthropic':
return 'firstParty' return 'firstParty'
default: default:
@@ -57,6 +61,10 @@ function getExplicitProviderOverride(): APIProvider | null {
return 'openrouter' return 'openrouter'
case 'openai': case 'openai':
return 'openai' return 'openai'
case 'firepass':
case 'fire-pass':
case 'fire_pass':
return 'firepass'
case 'bedrock': case 'bedrock':
return 'bedrock' return 'bedrock'
case 'vertex': 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 { export function getOpenRouterBaseUrl(): string {
const configuredBaseUrl = process.env.OPENROUTER_BASE_URL const configuredBaseUrl = process.env.OPENROUTER_BASE_URL
const fallbackBaseUrl = 'https://openrouter.ai/api' 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' 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 { export function getAPIProvider(): APIProvider {
const explicitProvider = getExplicitProviderOverride() const explicitProvider = getExplicitProviderOverride()
if (explicitProvider) { if (explicitProvider) {
@@ -143,9 +202,11 @@ export function getAPIProvider(): APIProvider {
? 'foundry' ? 'foundry'
: isOpenAIConfigured() : isOpenAIConfigured()
? 'openai' ? 'openai'
: isOpenRouterConfigured() : isFirepassConfigured()
? 'openrouter' ? 'firepass'
: getStoredProviderPreference() ?? 'firstParty' : isOpenRouterConfigured()
? 'openrouter'
: getStoredProviderPreference() ?? 'firstParty'
} }
export function getAPIProviderForStatsig(): AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS { export function getAPIProviderForStatsig(): AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS {

View File

@@ -13,6 +13,7 @@ import { getIdeClientName, type IDEExtensionInstallationStatus, isJetBrainsIde,
import { getClaudeAiUserDefaultModelDescription, modelDisplayString } from './model/model.js'; import { getClaudeAiUserDefaultModelDescription, modelDisplayString } from './model/model.js';
import { import {
getAPIProvider, getAPIProvider,
getFirepassBaseUrl,
getOpenAIBaseUrl, getOpenAIBaseUrl,
getOpenRouterBaseUrl, getOpenRouterBaseUrl,
} from './model/providers.js'; } from './model/providers.js';
@@ -248,6 +249,7 @@ export function buildAPIProviderProperties(): Property[] {
const providerLabel = { const providerLabel = {
openrouter: 'OpenRouter', openrouter: 'OpenRouter',
openai: 'OpenAI', openai: 'OpenAI',
firepass: 'FirePass',
bedrock: 'AWS Bedrock', bedrock: 'AWS Bedrock',
vertex: 'Google Vertex AI', vertex: 'Google Vertex AI',
foundry: 'Microsoft Foundry' foundry: 'Microsoft Foundry'
@@ -275,6 +277,11 @@ export function buildAPIProviderProperties(): Property[] {
label: 'OpenAI base URL', label: 'OpenAI base URL',
value: getOpenAIBaseUrl() value: getOpenAIBaseUrl()
}); });
} else if (apiProvider === 'firepass') {
properties.push({
label: 'FirePass base URL',
value: getFirepassBaseUrl()
});
} else if (apiProvider === 'bedrock') { } else if (apiProvider === 'bedrock') {
const bedrockBaseUrl = process.env.BEDROCK_BASE_URL; const bedrockBaseUrl = process.env.BEDROCK_BASE_URL;
if (bedrockBaseUrl) { if (bedrockBaseUrl) {