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 { 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}
|
||||||
|
|||||||
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,
|
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()
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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)]
|
||||||
|
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user