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:
@@ -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: (
|
||||
<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}
|
||||
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
|
||||
onDone={onFlowDone}
|
||||
|
||||
89
src/components/FirepassLoginFlow.tsx
Normal file
89
src/components/FirepassLoginFlow.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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<typeof Anthropic>[0] = {
|
||||
apiKey: firepassKey,
|
||||
baseURL: getFirepassBaseUrl(),
|
||||
...ARGS,
|
||||
...(isDebugToStdErr() && { logger: createStderrLogger() }),
|
||||
}
|
||||
|
||||
return new Anthropic(clientConfig)
|
||||
}
|
||||
|
||||
if (provider === 'openai') {
|
||||
await refreshOpenAIAuthTokenIfNeeded()
|
||||
const openAIKey = apiKey || getOpenAIApiKey()
|
||||
|
||||
@@ -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<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 {
|
||||
const config = getGlobalConfig()
|
||||
const normalizedKey = normalizeApiKeyForConfig(apiKey)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)]
|
||||
|
||||
|
||||
@@ -35,10 +35,23 @@ function getBuiltinModelStrings(provider: APIProvider): ModelStrings {
|
||||
const out = getBuiltinModelStrings('firstParty') as Record<string, string>
|
||||
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<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
|
||||
for (const key of MODEL_KEYS) {
|
||||
out[key] = ALL_MODEL_CONFIGS[key][provider]
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user