Skip to content

Commit 2a0a758

Browse files
committed
Merge upstream provider updates
2 parents 5229b66 + 4064b01 commit 2a0a758

26 files changed

Lines changed: 1076 additions & 313 deletions

src/assets/icons/claudeapi.png

17.2 KB
Loading

src/assets/icons/code0.png

12.6 KB
Loading

src/components/quota/quotaConfigs.ts

Lines changed: 68 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1428,7 +1428,6 @@ const renderKimiItems = (
14281428
'div',
14291429
{ className: styleMap.quotaMeta },
14301430
h('span', { className: styleMap.quotaPercent }, percentLabel),
1431-
limit > 0 ? h('span', { className: styleMap.quotaAmount }, `${used} / ${limit}`) : null,
14321431
resetLabel ? h('span', { className: styleMap.quotaReset }, resetLabel) : null
14331432
)
14341433
),
@@ -1471,15 +1470,32 @@ const buildXaiBillingSummary = (
14711470
return null;
14721471
}
14731472

1473+
const includedUsedCents =
1474+
usedCents === null
1475+
? null
1476+
: monthlyLimitCents !== null && monthlyLimitCents > 0
1477+
? Math.min(usedCents, monthlyLimitCents)
1478+
: usedCents;
1479+
const onDemandUsedCents =
1480+
usedCents !== null && monthlyLimitCents !== null
1481+
? Math.max(0, usedCents - monthlyLimitCents)
1482+
: null;
14741483
const usedPercent =
1475-
monthlyLimitCents !== null && monthlyLimitCents > 0 && usedCents !== null
1476-
? (usedCents / monthlyLimitCents) * 100
1484+
monthlyLimitCents !== null && monthlyLimitCents > 0 && includedUsedCents !== null
1485+
? (includedUsedCents / monthlyLimitCents) * 100
1486+
: null;
1487+
const onDemandUsedPercent =
1488+
onDemandCapCents !== null && onDemandCapCents > 0 && onDemandUsedCents !== null
1489+
? (onDemandUsedCents / onDemandCapCents) * 100
14771490
: null;
14781491

14791492
return {
14801493
monthlyLimitCents,
14811494
usedCents,
1495+
includedUsedCents,
14821496
onDemandCapCents,
1497+
onDemandUsedCents,
1498+
onDemandUsedPercent,
14831499
billingPeriodStart,
14841500
billingPeriodEnd,
14851501
usedPercent,
@@ -1523,15 +1539,26 @@ const formatUsdFromCents = (cents: number | null): string => {
15231539

15241540
const formatXaiRemainingAmount = (billing: XaiBillingSummary): string => {
15251541
const remainingCents =
1526-
billing.monthlyLimitCents !== null && billing.usedCents !== null
1527-
? Math.max(0, billing.monthlyLimitCents - billing.usedCents)
1542+
billing.monthlyLimitCents !== null && billing.includedUsedCents !== null
1543+
? Math.max(0, billing.monthlyLimitCents - billing.includedUsedCents)
15281544
: null;
15291545
const remaining = formatUsdFromCents(remainingCents);
15301546
const limit = formatUsdFromCents(billing.monthlyLimitCents);
15311547
if (billing.monthlyLimitCents === null) return remaining;
15321548
return `${remaining} / ${limit}`;
15331549
};
15341550

1551+
const formatXaiOnDemandAmount = (billing: XaiBillingSummary): string => {
1552+
const remainingCents =
1553+
billing.onDemandCapCents !== null && billing.onDemandUsedCents !== null
1554+
? Math.max(0, billing.onDemandCapCents - billing.onDemandUsedCents)
1555+
: null;
1556+
const remaining = formatUsdFromCents(remainingCents);
1557+
const cap = formatUsdFromCents(billing.onDemandCapCents);
1558+
if (billing.onDemandCapCents === null) return remaining;
1559+
return `${remaining} / ${cap}`;
1560+
};
1561+
15351562
const XAI_SUPERGROK_LIMIT_CENTS = 15_000;
15361563
const XAI_SUPERGROK_HEAVY_LIMIT_CENTS = 150_000;
15371564

@@ -1567,11 +1594,16 @@ const renderXaiItems = (
15671594
const amountLabel = formatXaiRemainingAmount(billing);
15681595
const resetLabel = formatQuotaResetTime(billing.billingPeriodEnd);
15691596
const onDemandCap = billing.onDemandCapCents ?? 0;
1597+
const clampedOnDemandUsed =
1598+
billing.onDemandUsedPercent === null
1599+
? null
1600+
: Math.max(0, Math.min(100, billing.onDemandUsedPercent));
1601+
const onDemandRemaining =
1602+
clampedOnDemandUsed === null ? null : Math.max(0, Math.min(100, 100 - clampedOnDemandUsed));
1603+
const onDemandPercentLabel =
1604+
onDemandRemaining === null ? '--' : `${Math.round(onDemandRemaining)}%`;
1605+
const onDemandAmountLabel = formatXaiOnDemandAmount(billing);
15701606
const plan = resolveXaiPlan(billing.monthlyLimitCents);
1571-
const payAsYouGoLabel =
1572-
onDemandCap > 0
1573-
? t('xai_quota.pay_as_you_go_enabled', { cap: formatUsdFromCents(onDemandCap) })
1574-
: t('xai_quota.pay_as_you_go_disabled');
15751607

15761608
return h(
15771609
Fragment,
@@ -1588,12 +1620,33 @@ const renderXaiItems = (
15881620
)
15891621
)
15901622
: null,
1591-
h(
1592-
'div',
1593-
{ key: 'pay-as-you-go', className: styleMap.codexPlan },
1594-
h('span', { className: styleMap.codexPlanLabel }, t('xai_quota.pay_as_you_go_label')),
1595-
h('span', { className: styleMap.codexPlanValue }, payAsYouGoLabel)
1596-
),
1623+
onDemandCap > 0
1624+
? h(
1625+
'div',
1626+
{ key: 'pay-as-you-go', className: styleMap.quotaRow },
1627+
h(
1628+
'div',
1629+
{ className: styleMap.quotaRowHeader },
1630+
h('span', { className: styleMap.quotaModel }, t('xai_quota.pay_as_you_go_label')),
1631+
h(
1632+
'div',
1633+
{ className: styleMap.quotaMeta },
1634+
h('span', { className: styleMap.quotaPercent }, onDemandPercentLabel),
1635+
h('span', { className: styleMap.quotaAmount }, onDemandAmountLabel)
1636+
)
1637+
),
1638+
h(QuotaProgressBar, {
1639+
percent: onDemandRemaining,
1640+
highThreshold: QUOTA_PROGRESS_HIGH_THRESHOLD,
1641+
mediumThreshold: QUOTA_PROGRESS_MEDIUM_THRESHOLD,
1642+
})
1643+
)
1644+
: h(
1645+
'div',
1646+
{ key: 'pay-as-you-go', className: styleMap.codexPlan },
1647+
h('span', { className: styleMap.codexPlanLabel }, t('xai_quota.pay_as_you_go_label')),
1648+
h('span', { className: styleMap.codexPlanValue }, t('xai_quota.pay_as_you_go_disabled'))
1649+
),
15971650
h(
15981651
'div',
15991652
{ key: 'monthly-credits', className: styleMap.quotaRow },

src/features/providers/ProvidersWorkbenchPage.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import type { ProviderPanelControls } from './components/ProviderResourcePanel';
1818
import { SponsorQuickStartPanel } from './components/SponsorQuickStartPanel';
1919
import { ProviderSheet, type ProviderSheetHandle } from './sheets/ProviderSheet';
2020
import { APIKEY_FUN_DISPLAY_NAME } from './sponsor';
21+
import { isMultiProtocolSponsorBrand } from './sponsorDefinitions';
2122
import { useProviderWorkbench } from './useProviderWorkbench';
2223
import {
2324
getProviderFilterState,
@@ -79,16 +80,17 @@ const getResourceRecentSuccess = (
7980
resource: ProviderResource,
8081
usageByProvider: ProviderRecentUsageMap
8182
): number => {
82-
if (resource.brand === 'apikeyFun') {
83+
if (isMultiProtocolSponsorBrand(resource.brand)) {
8384
return 0;
8485
}
8586
if (resource.brand === 'openaiCompatibility') {
8687
return getOpenAIProviderRecentWindowStats(resource.raw as OpenAIProviderConfig, usageByProvider)
8788
.success;
8889
}
90+
const usageProvider = resource.brand === 'claudeApi' ? 'claude' : resource.brand;
8991
return getProviderRecentWindowStats(
9092
usageByProvider,
91-
resource.brand,
93+
usageProvider,
9294
resource.apiKey ?? undefined,
9395
resource.baseUrl ?? undefined,
9496
resource.authIndex,

src/features/providers/adapters.ts

Lines changed: 103 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,18 @@ import {
77
getApiKeyFunProtocolUrls,
88
resolveApiKeyFunBaseUrl,
99
} from './sponsor';
10+
import { CLAUDE_API_DISPLAY_NAME } from './claudeApi';
11+
import {
12+
CODE0_DISPLAY_NAME,
13+
CODE0_PROTOCOL_LABELS,
14+
getCode0ProtocolUrls,
15+
resolveCode0BaseUrl,
16+
} from './code0';
1017
import type {
1118
ProviderBrand,
1219
ProviderResource,
1320
ProviderResourceSelector,
21+
SponsorProviderBrand,
1422
SponsorProviderRaw,
1523
} from './types';
1624

@@ -40,7 +48,7 @@ const truncateForId = (value: string | undefined | null): string => {
4048
};
4149

4250
function providerKeyToResource(
43-
brand: 'gemini' | 'codex' | 'claude' | 'vertex',
51+
brand: 'gemini' | 'codex' | 'claude' | 'claudeApi' | 'vertex',
4452
config: GeminiKeyConfig | ProviderKeyConfig,
4553
index: number
4654
): ProviderResource {
@@ -50,7 +58,7 @@ function providerKeyToResource(
5058
if (brand === 'codex') {
5159
flags.websockets = (config as ProviderKeyConfig).websockets === true;
5260
}
53-
if (brand === 'claude') {
61+
if (brand === 'claude' || brand === 'claudeApi') {
5462
const cloak = (config as ProviderKeyConfig).cloak;
5563
flags.cloakEnabled = Boolean(cloak?.mode?.trim());
5664
}
@@ -99,6 +107,14 @@ export function claudeToResource(config: ProviderKeyConfig, index: number): Prov
99107
return providerKeyToResource('claude', config, index);
100108
}
101109

110+
export function claudeApiToResource(config: ProviderKeyConfig, index: number): ProviderResource {
111+
const resource = providerKeyToResource('claudeApi', config, index);
112+
return {
113+
...resource,
114+
name: CLAUDE_API_DISPLAY_NAME,
115+
};
116+
}
117+
102118
export function vertexToResource(config: ProviderKeyConfig, index: number): ProviderResource {
103119
return providerKeyToResource('vertex', config, index);
104120
}
@@ -132,22 +148,49 @@ export function openaiToResource(config: OpenAIProviderConfig, index: number): P
132148
};
133149
}
134150

135-
export function apiKeyFunToResource(raw: SponsorProviderRaw): ProviderResource | null {
136-
if (raw.openai.length === 0 && raw.claude.length === 0 && raw.codex.length === 0) {
151+
interface SponsorResourceOptions {
152+
displayName: string;
153+
protocolLabels: readonly string[];
154+
resolveBaseUrl: (value: string | undefined | null) => string;
155+
getProtocolUrls: (value: string | undefined | null) => {
156+
anthropic: string;
157+
openai: string;
158+
codex: string;
159+
gemini: string;
160+
};
161+
}
162+
163+
function sponsorRawToResource(
164+
brand: SponsorProviderBrand,
165+
raw: SponsorProviderRaw,
166+
options: SponsorResourceOptions
167+
): ProviderResource | null {
168+
if (
169+
raw.openai.length === 0 &&
170+
raw.claude.length === 0 &&
171+
raw.codex.length === 0 &&
172+
raw.gemini.length === 0
173+
) {
137174
return null;
138175
}
139176
const openaiKeyCount = raw.openai.reduce(
140177
(count, item) => count + (item.config.apiKeyEntries?.length ?? 0),
141178
0
142179
);
143180
const codexKeyCount = raw.codex.length;
181+
const geminiKeyCount = raw.gemini.length;
144182
const firstOpenAIEntry = raw.openai
145183
.flatMap((item) => item.config.apiKeyEntries ?? [])
146184
.find((entry) => entry.apiKey?.trim());
147185
const firstCodex = raw.codex.find((item) => item.config.apiKey?.trim());
148186
const firstClaude = raw.claude.find((item) => item.config.apiKey?.trim());
187+
const firstGemini = raw.gemini.find((item) => item.config.apiKey?.trim());
149188
const apiKey =
150-
firstOpenAIEntry?.apiKey ?? firstCodex?.config.apiKey ?? firstClaude?.config.apiKey ?? '';
189+
firstOpenAIEntry?.apiKey ??
190+
firstCodex?.config.apiKey ??
191+
firstClaude?.config.apiKey ??
192+
firstGemini?.config.apiKey ??
193+
'';
151194
const openaiDisabled =
152195
raw.openai.length > 0 && raw.openai.every((item) => item.config.disabled === true);
153196
const codexDisabled =
@@ -156,53 +199,70 @@ export function apiKeyFunToResource(raw: SponsorProviderRaw): ProviderResource |
156199
const claudeDisabled =
157200
raw.claude.length > 0 &&
158201
raw.claude.every((item) => hasDisableAllModelsRule(item.config.excludedModels));
202+
const geminiDisabled =
203+
raw.gemini.length > 0 &&
204+
raw.gemini.every((item) => hasDisableAllModelsRule(item.config.excludedModels));
159205
const enabledCount =
160206
(raw.openai.length > 0 && !openaiDisabled ? 1 : 0) +
161207
(raw.codex.length > 0 && !codexDisabled ? 1 : 0) +
162-
(raw.claude.length > 0 && !claudeDisabled ? 1 : 0);
208+
(raw.claude.length > 0 && !claudeDisabled ? 1 : 0) +
209+
(raw.gemini.length > 0 && !geminiDisabled ? 1 : 0);
163210
const allResourcesConfigured =
164-
raw.openai.length > 0 || raw.codex.length > 0 || raw.claude.length > 0;
211+
raw.openai.length > 0 ||
212+
raw.codex.length > 0 ||
213+
raw.claude.length > 0 ||
214+
raw.gemini.length > 0;
165215
const disabled = allResourcesConfigured && enabledCount === 0;
166216
const models = [
167217
...raw.openai.flatMap((item) => collectModelNames(item.config.models)),
168218
...raw.codex.flatMap((item) => collectModelNames(item.config.models)),
169219
...raw.claude.flatMap((item) => collectModelNames(item.config.models)),
220+
...raw.gemini.flatMap((item) => collectModelNames(item.config.models)),
170221
];
171222
const uniqueModels = Array.from(new Set(models));
172223
const headerCount =
173224
raw.openai.reduce((count, item) => count + countHeaders(item.config.headers), 0) +
174225
raw.codex.reduce((count, item) => count + countHeaders(item.config.headers), 0) +
175-
raw.claude.reduce((count, item) => count + countHeaders(item.config.headers), 0);
226+
raw.claude.reduce((count, item) => count + countHeaders(item.config.headers), 0) +
227+
raw.gemini.reduce((count, item) => count + countHeaders(item.config.headers), 0);
176228
const priority = Math.max(
177229
0,
178230
...raw.openai.map((item) => normalizePriority(item.config.priority)),
179231
...raw.codex.map((item) => normalizePriority(item.config.priority)),
180-
...raw.claude.map((item) => normalizePriority(item.config.priority))
232+
...raw.claude.map((item) => normalizePriority(item.config.priority)),
233+
...raw.gemini.map((item) => normalizePriority(item.config.priority))
181234
);
182-
const baseUrl = resolveApiKeyFunBaseUrl(
183-
raw.openai[0]?.config.baseUrl ?? raw.codex[0]?.config.baseUrl ?? raw.claude[0]?.config.baseUrl
235+
const baseUrl = options.resolveBaseUrl(
236+
raw.openai[0]?.config.baseUrl ??
237+
raw.codex[0]?.config.baseUrl ??
238+
raw.claude[0]?.config.baseUrl ??
239+
raw.gemini[0]?.config.baseUrl
184240
);
185-
const protocolUrls = getApiKeyFunProtocolUrls(baseUrl);
241+
const protocolUrls = options.getProtocolUrls(baseUrl);
186242

187243
return {
188-
id: buildId('apikeyFun', 0, 'sponsor'),
189-
brand: 'apikeyFun',
244+
id: buildId(brand, 0, 'sponsor'),
245+
brand,
190246
originalIndex: 0,
191-
name: APIKEY_FUN_DISPLAY_NAME,
192-
identifier: APIKEY_FUN_DISPLAY_NAME,
247+
name: options.displayName,
248+
identifier: options.displayName,
193249
apiKeyPreview: apiKey ? maskApiKey(apiKey) : null,
194250
apiKey: apiKey || null,
195251
authIndex: null,
196-
baseUrl: `${protocolUrls.openai} / ${protocolUrls.anthropic}`,
252+
baseUrl: [protocolUrls.openai, protocolUrls.anthropic, protocolUrls.gemini]
253+
.filter(Boolean)
254+
.join(' / '),
197255
proxyUrl:
198256
firstOpenAIEntry?.proxyUrl ??
199257
raw.codex.find((item) => item.config.proxyUrl)?.config.proxyUrl ??
200258
raw.claude.find((item) => item.config.proxyUrl)?.config.proxyUrl ??
259+
raw.gemini.find((item) => item.config.proxyUrl)?.config.proxyUrl ??
201260
null,
202261
prefix:
203262
raw.openai[0]?.config.prefix ??
204263
raw.codex[0]?.config.prefix ??
205264
raw.claude[0]?.config.prefix ??
265+
raw.gemini[0]?.config.prefix ??
206266
null,
207267
modelCount: uniqueModels.length,
208268
models: uniqueModels,
@@ -216,18 +276,41 @@ export function apiKeyFunToResource(raw: SponsorProviderRaw): ProviderResource |
216276
raw.claude.reduce(
217277
(count, item) => count + stripDisableAllModelsRule(item.config.excludedModels).length,
218278
0
279+
) +
280+
raw.gemini.reduce(
281+
(count, item) => count + stripDisableAllModelsRule(item.config.excludedModels).length,
282+
0
219283
),
220-
apiKeyEntryCount: openaiKeyCount + codexKeyCount + raw.claude.length,
284+
apiKeyEntryCount: openaiKeyCount + codexKeyCount + raw.claude.length + geminiKeyCount,
221285
disabled,
222286
flags: {
223-
protocols: [...APIKEY_FUN_PROTOCOLS],
287+
protocols: [...options.protocolLabels],
224288
},
225289
selector: {
226-
brand: 'apikeyFun',
290+
brand,
227291
openaiIndices: raw.openai.map((item) => item.index),
228292
claudeIndices: raw.claude.map((item) => item.index),
229293
codexIndices: raw.codex.map((item) => item.index),
294+
geminiIndices: raw.gemini.map((item) => item.index),
230295
} as ProviderResourceSelector,
231296
raw,
232297
};
233298
}
299+
300+
export function apiKeyFunToResource(raw: SponsorProviderRaw): ProviderResource | null {
301+
return sponsorRawToResource('apikeyFun', raw, {
302+
displayName: APIKEY_FUN_DISPLAY_NAME,
303+
protocolLabels: APIKEY_FUN_PROTOCOLS,
304+
resolveBaseUrl: resolveApiKeyFunBaseUrl,
305+
getProtocolUrls: getApiKeyFunProtocolUrls,
306+
});
307+
}
308+
309+
export function code0ToResource(raw: SponsorProviderRaw): ProviderResource | null {
310+
return sponsorRawToResource('code0', raw, {
311+
displayName: CODE0_DISPLAY_NAME,
312+
protocolLabels: CODE0_PROTOCOL_LABELS,
313+
resolveBaseUrl: resolveCode0BaseUrl,
314+
getProtocolUrls: getCode0ProtocolUrls,
315+
});
316+
}

0 commit comments

Comments
 (0)