Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
d35592b
merge dev into main for the v2.32.1 release
lidge-jun Aug 25, 2026
71c57ea
release: v2.32.1
lidge-jun Aug 25, 2026
d560ac6
merge dev into main for the v2.33.0 release
lidge-jun Aug 25, 2026
08ada6f
Merge pull request #2553 from lidge-jun/codex/promote-main-2330
lidge-jun Aug 25, 2026
ec51e42
release: v2.33.0
lidge-jun Aug 25, 2026
e25b653
merge dev into main for the v2.34.0 release
lidge-jun Aug 27, 2026
80fff9a
Merge pull request #2760 from lidge-jun/codex/promote-main-2340
lidge-jun Aug 27, 2026
fc4de77
Merge pull request #2826 from lidge-jun/codex/promote-main-2350
lidge-jun Aug 28, 2026
c7d8407
Merge pull request #3002 from lidge-jun/codex/promote-main-2360
lidge-jun Aug 30, 2026
54e2274
Merge pull request #3037 from lidge-jun/codex/promote-main-2370
lidge-jun Aug 31, 2026
2c4dca1
merge dev into the promotion branch for v2.38.0
lidge-jun Aug 31, 2026
a34e8b7
merge dev into the promotion branch for v2.38.0 (picks up the ReDoS fix)
lidge-jun Aug 31, 2026
ebb4d55
Merge pull request #3073 from lidge-jun/codex/promote-main-2380
lidge-jun Aug 31, 2026
682112e
Merge remote-tracking branch 'origin/dev' into codex/promote-main-2390
lidge-jun Sep 1, 2026
af6113a
merge dev into main for the v2.39.0 release
lidge-jun Sep 1, 2026
847f4f1
merge dev into main for the v2.40.0 release
Sep 2, 2026
ac78647
Merge pull request #3261 from lidge-jun/codex/promote-main-2400
lidge-jun Sep 2, 2026
aaa9eaf
fix(release): pass the bump job's permissions through the reusable-wo…
lidge-jun Sep 2, 2026
35ff3a4
Merge pull request #3263 from lidge-jun/codex/promote-main-2400-relfix
lidge-jun Sep 2, 2026
00a7947
fix(security): scope keychain references to providers
luvs01 Sep 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ jobs:
bump-dev-version:
needs: publish
if: ${{ inputs.dry-run != true }}
# A reusable-workflow CALL cannot grant the callee more than the calling job holds,
# and GitHub refuses the whole run at startup when the called workflow's own job
# declares permissions the caller did not pass down ("startup_failure", runs
# 33615174183 / 33615177849 — the first dispatches since #3129 wired this call).
# The callee's job declares exactly these two; nothing else in this file gains them.
permissions:
contents: write
pull-requests: write
uses: ./.github/workflows/dev-version-bump.yml
with:
released-version: v${{ inputs.version }}
Expand Down
2 changes: 1 addition & 1 deletion src/codex/catalog/provider-fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1283,7 +1283,7 @@ function observedModelsAuthResolver(
resolve(name, provider) {
if (provider.authMode === "forward") return { apiKey: undefined, observed: true };
if (provider.authMode !== "oauth") {
return { apiKey: resolveProviderApiKey(provider.apiKey), observed: true };
return { apiKey: resolveProviderApiKey(provider.apiKey, name), observed: true };
}

const observation = observeActiveOAuthAccessToken(name, authStoreBuffer);
Expand Down
8 changes: 4 additions & 4 deletions src/images/plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ export function findXaiProvider(config: OcxConfig): { name: string; provider: Oc
* OAuth / Grok CLI proxy transport is not used here (that path is chat-oriented and not a
* supported Images transport), so oauth-only configs deliberately do not arm the bridge.
*/
export function resolveXaiImageApiKey(provider: OcxProviderConfig): string | undefined {
export function resolveXaiImageApiKey(provider: OcxProviderConfig, providerName = "xai"): string | undefined {
if (provider.authMode === "oauth") return undefined;
const apiKey = resolveProviderApiKey(provider.apiKey)?.trim();
const apiKey = resolveProviderApiKey(provider.apiKey, providerName)?.trim();
Comment on lines +38 to +40

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Pass the custom xAI provider name through relay auth

When the image bridge discovers a custom-named xAI provider, tryXaiImageRelay still calls resolveXaiImageAuthToken(found.provider) without found.name; that function consequently reaches this helper with the default name "xai". After storing the custom provider's key in the OS keychain, its reference is keychain:<custom-name>, so the new ownership check rejects it and every direct /v1/images/generations or /v1/images/edits request returns the missing-auth response, even though the synthetic image/video bridge paths correctly pass found.name. Accept and propagate the discovered provider name through resolveXaiImageAuthToken, and cover the custom-name/keychain relay case in tests/server-images.test.ts.

AGENTS.md reference: src/AGENTS.md:L10-L10

Useful? React with 👍 / 👎.

return apiKey || undefined;
}

Expand Down Expand Up @@ -73,7 +73,7 @@ export async function planImageBridge(
if (host === "api.openai.com") return undefined;
const found = findXaiProvider(config);
if (!found) return undefined;
const token = resolveXaiImageApiKey(found.provider);
const token = resolveXaiImageApiKey(found.provider, found.name);
if (!token) return undefined;
// Pin the baseUrl to the registry entry, ignoring any config-level baseUrl override.
const registryEntry = getProviderRegistryEntry("xai");
Expand Down Expand Up @@ -138,7 +138,7 @@ export async function planVideoBridge(
if (host === "api.openai.com") return undefined;
const found = findXaiProvider(config);
if (!found) return undefined;
const token = resolveXaiImageApiKey(found.provider);
const token = resolveXaiImageApiKey(found.provider, found.name);
if (!token) return undefined;
// Pin the baseUrl to the registry entry, ignoring any config-level baseUrl override.
const registryEntry = getProviderRegistryEntry("xai");
Expand Down
2 changes: 1 addition & 1 deletion src/lib/lab-live-route-production.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ async function buildLabProviderAuthHeaders(
throw new TransportError("harness_failure", "oauth refresh unavailable");
}
} else {
const apiKey = resolveProviderApiKey(provider.apiKey)?.trim();
const apiKey = resolveProviderApiKey(provider.apiKey, routeContext.providerId)?.trim();
if (!apiKey) throw new TransportError("auth_blocked", "missing api key");
if (provider.adapter === "anthropic" && provider.apiKeyTransport === "x-api-key") {
headers["x-api-key"] = apiKey;
Expand Down
2 changes: 1 addition & 1 deletion src/oauth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1044,7 +1044,7 @@ export async function resolveModelsAuthToken(name: string, prov: OcxProviderConf
return undefined;
}
}
return resolveProviderApiKey(prov.apiKey);
return resolveProviderApiKey(prov.apiKey, name);
}

function modelDiscoveryTransportSeed(providerName: string, prov: OcxProviderConfig): OcxProviderConfig {
Expand Down
16 changes: 13 additions & 3 deletions src/providers/key-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ function keychainAccount(reference: string): string {
return reference.slice(KEYCHAIN_REFERENCE_PREFIX.length);
}

function keychainReferenceBelongsToProvider(reference: string, providerName: string): boolean {
const account = keychainAccount(reference);
return account === providerName || account.startsWith(`${providerName}/`);
}

function readKeychain(account: string): string | undefined {
const cached = resolvedCache.get(account);
if (cached !== undefined) return cached;
Expand All @@ -76,9 +81,12 @@ function readKeychain(account: string): string | undefined {
* Single resolver for provider key material: env references, keychain references, or the
* literal value. Every request-time read of `apiKey` goes through here.
*/
export function resolveProviderApiKey(value: string | undefined): string | undefined {
export function resolveProviderApiKey(value: string | undefined, providerName: string): string | undefined {
if (!value) return undefined;
if (isKeychainReference(value)) return readKeychain(keychainAccount(value));
if (isKeychainReference(value)) {
if (!keychainReferenceBelongsToProvider(value, providerName)) return undefined;
return readKeychain(keychainAccount(value));
}
return resolveEnvValue(value);
}

Expand Down Expand Up @@ -174,6 +182,9 @@ export function restoreProviderKeyFromKeychain(config: OcxConfig, name: string):
const pool = provider.apiKeyPool ?? [];
const resolved = new Map<string, string>();
const refs = [provider.apiKey, ...pool.map(e => e.key)].filter(isKeychainReference);
if (refs.some(ref => !keychainReferenceBelongsToProvider(ref, name))) {
return { ok: false, error: "provider contains a keychain reference owned by another provider", status: 400 };
}
for (const ref of refs) {
const account = keychainAccount(ref);
if (resolved.has(account)) continue;
Expand All @@ -194,4 +205,3 @@ export function restoreProviderKeyFromKeychain(config: OcxConfig, name: string):
saveConfigPreservingClaudeCode(config);
return { ok: true, restored: resolved.size };
}

4 changes: 2 additions & 2 deletions src/providers/openai-sidecar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ export function selectOpenAiImagesProvider(config: OcxConfig): OpenAiImagesProvi
&& provider.authMode !== "forward"
&& provider.baseUrl.replace(/\/+$/, "") === "https://api.openai.com/v1"
) {
const apiKey = resolveProviderApiKey(provider.apiKey)?.trim();
const apiKey = resolveProviderApiKey(provider.apiKey, OPENAI_API_PROVIDER_ID)?.trim();
if (apiKey) selection.keyed = { providerName: OPENAI_API_PROVIDER_ID, provider, apiKey };
}
return selection;
Expand Down Expand Up @@ -236,7 +236,7 @@ export function selectImagesProvider(config: OcxConfig): OpenAiImagesProviderSel
};
}

const apiKey = resolveProviderApiKey(provider.apiKey)?.trim();
const apiKey = resolveProviderApiKey(provider.apiKey, providerName)?.trim();
if (!apiKey) {
return { forwardCandidates: [], error: `images.provider "${providerName}" has no usable API key` };
}
Expand Down
38 changes: 19 additions & 19 deletions src/providers/quota.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ function cacheKey(config: OcxConfig): string {
const providers = Object.entries(config.providers)
.map(([name, provider]) => {
const resolvedKey = typeof provider.apiKey === "string"
? resolveProviderApiKey(provider.apiKey)?.trim()
? resolveProviderApiKey(provider.apiKey, name)?.trim()
: undefined;
const activeKeyId = resolvedKey ? apiKeyPoolEntryId(resolvedKey) : "none";
return `${name}:${provider.adapter}:${provider.authMode ?? "key"}:${providerCodexAccountMode(name, provider) ?? "none"}:${provider.disabled === true ? "off" : "on"}:${provider.baseUrl}:${activeKeyId}`;
Expand Down Expand Up @@ -371,7 +371,7 @@ function firstFinite(record: Record<string, unknown> | null, names: string[]): n
async function fetchA6apiQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
// Never send a configured API key to a lookalike host or through a redirect.
if (!isCanonicalA6apiBaseUrl(config.baseUrl)) return null;
const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
const apiKey = resolveProviderApiKey(config.apiKey, provider)?.trim();
if (!apiKey) return null;
const headers = { Accept: "application/json", Authorization: `Bearer ${apiKey}` } as const;
const [subscriptionResponse, tokenResponse] = await Promise.all([
Expand Down Expand Up @@ -465,7 +465,7 @@ function parseOpenCodeGoUsageWindow(value: unknown): { percent: number; resetAt?
async function fetchOpenCodeGoQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
// Never send a configured API key when the provider destination is not the built-in Go endpoint.
if (!isCanonicalOpenCodeGoBaseUrl(config.baseUrl)) return null;
const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
const apiKey = resolveProviderApiKey(config.apiKey, provider)?.trim();
if (!apiKey) return null;
const response = await fetch(OPENCODE_GO_USAGE_URL, {
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
Expand Down Expand Up @@ -511,7 +511,7 @@ async function fetchOpenCodeGoQuota(provider: string, config: OcxProviderConfig)
async function fetchOpenRouterQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
// Never send a configured API key to a lookalike host or through a redirect.
if (!isCanonicalOpenRouterBaseUrl(config.baseUrl)) return null;
const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
const apiKey = resolveProviderApiKey(config.apiKey, provider)?.trim();
if (!apiKey) return null;
const response = await fetch(`${OPENROUTER_BASE_URL}/key`, {
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
Expand Down Expand Up @@ -558,7 +558,7 @@ async function fetchOpenRouterQuota(provider: string, config: OcxProviderConfig)
*/
async function fetchDeepSeekQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
if (!isCanonicalDeepSeekBaseUrl(config.baseUrl)) return null;
const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
const apiKey = resolveProviderApiKey(config.apiKey, provider)?.trim();
if (!apiKey) return null;
const response = await fetch(`${DEEPSEEK_BASE_URL}/user/balance`, {
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
Expand Down Expand Up @@ -603,7 +603,7 @@ async function fetchDeepSeekQuota(provider: string, config: OcxProviderConfig):
*/
async function fetchClineQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
if (!isCanonicalClineBaseUrl(config.baseUrl)) return null;
const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
const apiKey = resolveProviderApiKey(config.apiKey, provider)?.trim();
if (!apiKey) return null;
const response = await fetch(`${CLINE_BASE_URL}/api/v1/users/me/plan/usage-limits`, {
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
Expand Down Expand Up @@ -751,7 +751,7 @@ function parseZaiQuotaLegacyFields(data: Record<string, unknown> | null): Provid
*/
async function fetchZaiQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
if (!isCanonicalZaiBaseUrl(config.baseUrl)) return null;
const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
const apiKey = resolveProviderApiKey(config.apiKey, provider)?.trim();
if (!apiKey) return null;
const normalized = normalizedBaseUrl(config.baseUrl);
const monitorHost = normalized === ZAI_BASE_URL || normalized === `${ZAI_BASE_URL}/api/coding/paas/v4`
Expand Down Expand Up @@ -794,7 +794,7 @@ async function fetchZaiQuota(provider: string, config: OcxProviderConfig): Promi
*/
async function fetchMinimaxQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
if (!isCanonicalMinimaxBaseUrl(config.baseUrl)) return null;
const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
const apiKey = resolveProviderApiKey(config.apiKey, provider)?.trim();
if (!apiKey) return null;
const cnHost = normalizedBaseUrl(config.baseUrl)?.startsWith("https://api.minimaxi.com");
const remainsUrl = cnHost ? "https://api.minimaxi.com/v1/token_plan/remains" : MINIMAX_REMAINS_URL;
Expand Down Expand Up @@ -838,7 +838,7 @@ async function fetchMinimaxQuota(provider: string, config: OcxProviderConfig): P
*/
async function fetchMoonshotQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
if (!isCanonicalMoonshotBaseUrl(config.baseUrl)) return null;
const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
const apiKey = resolveProviderApiKey(config.apiKey, provider)?.trim();
if (!apiKey) return null;
const host = normalizedBaseUrl(config.baseUrl)?.startsWith("https://api.moonshot.cn") ? "https://api.moonshot.cn/v1" : MOONSHOT_BASE_URL;
const response = await fetch(`${host}/users/me/balance`, {
Expand Down Expand Up @@ -882,7 +882,7 @@ async function fetchMoonshotQuota(provider: string, config: OcxProviderConfig):
*/
async function fetchVeniceQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
if (!isCanonicalVeniceBaseUrl(config.baseUrl)) return null;
const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
const apiKey = resolveProviderApiKey(config.apiKey, provider)?.trim();
if (!apiKey) return null;
const response = await fetch(`${VENICE_BASE_URL}/billing/balance`, {
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
Expand Down Expand Up @@ -925,7 +925,7 @@ async function fetchVeniceQuota(provider: string, config: OcxProviderConfig): Pr
*/
async function fetchSyntheticQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
if (!isCanonicalSyntheticBaseUrl(config.baseUrl)) return null;
const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
const apiKey = resolveProviderApiKey(config.apiKey, provider)?.trim();
if (!apiKey) return null;
const response = await fetch(`${SYNTHETIC_BASE_URL}/quotas`, {
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
Expand Down Expand Up @@ -973,7 +973,7 @@ async function fetchSyntheticQuota(provider: string, config: OcxProviderConfig):
*/
async function fetchDeepInfraQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
if (!isCanonicalDeepInfraBaseUrl(config.baseUrl)) return null;
const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
const apiKey = resolveProviderApiKey(config.apiKey, provider)?.trim();
if (!apiKey) return null;
const response = await fetch(`${DEEPINFRA_BASE_URL}/payment/checklist?compute_owed=true`, {
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
Expand Down Expand Up @@ -1015,7 +1015,7 @@ async function fetchDeepInfraQuota(provider: string, config: OcxProviderConfig):
*/
async function fetchNeuralwattQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
if (!isCanonicalNeuralwattBaseUrl(config.baseUrl)) return null;
const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
const apiKey = resolveProviderApiKey(config.apiKey, provider)?.trim();
if (!apiKey) return null;
const response = await fetch(`${NEURALWATT_BASE_URL}/quota`, {
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
Expand Down Expand Up @@ -1809,7 +1809,7 @@ function parseKimiQuotaPayload(value: unknown): ProviderQuota | null {
return hasQuotaRows(quota) ? quota : null;
}

async function resolveKimiQuotaBearer(config: OcxProviderConfig): Promise<string | null> {
async function resolveKimiQuotaBearer(provider: string, config: OcxProviderConfig): Promise<string | null> {
if (config.authMode === "oauth") {
try {
return await getValidAccessToken("kimi");
Expand All @@ -1820,14 +1820,14 @@ async function resolveKimiQuotaBearer(config: OcxProviderConfig): Promise<string
// ACTIVE key only: silently walking apiKeyPool when the primary env reference is
// unresolved would render a quota bar for a DIFFERENT account than the one routing
// requests — a wrong meter is worse than no meter.
const primary = resolveProviderApiKey(config.apiKey)?.trim();
const primary = resolveProviderApiKey(config.apiKey, provider)?.trim();
return primary || null;
}

async function fetchKimiQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaReport | null> {
// Never release credentials to a user-edited or lookalike provider host.
if (!isCanonicalKimiCodeBaseUrl(config.baseUrl)) return null;
const accessToken = await resolveKimiQuotaBearer(config);
const accessToken = await resolveKimiQuotaBearer(provider, config);
if (!accessToken) return null;
const response = await fetch(KIMI_CODE_USAGE_URL, {
headers: { Accept: "application/json", Authorization: `Bearer ${accessToken}` },
Expand Down Expand Up @@ -1917,7 +1917,7 @@ async function fetchCommandCodeSpend(
}

/** OAuth access token or ACTIVE Provider-API key for the Command Code quota probe. */
async function resolveCommandCodeQuotaBearer(config: OcxProviderConfig): Promise<string | null> {
async function resolveCommandCodeQuotaBearer(provider: string, config: OcxProviderConfig): Promise<string | null> {
if (config.authMode === "oauth") {
try {
return await getValidAccessToken("command-code");
Expand All @@ -1927,7 +1927,7 @@ async function resolveCommandCodeQuotaBearer(config: OcxProviderConfig): Promise
}
// ACTIVE key only: a quota bar for a different account than the one routing
// requests is a wrong meter, not a helpful one.
return resolveProviderApiKey(config.apiKey)?.trim() || null;
return resolveProviderApiKey(config.apiKey, provider)?.trim() || null;
}

/**
Expand All @@ -1938,7 +1938,7 @@ async function resolveCommandCodeQuotaBearer(config: OcxProviderConfig): Promise
async function fetchCommandCodeQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
// Never release credentials to a user-edited or lookalike provider host.
if (!isCanonicalCommandCodeBaseUrl(config.baseUrl)) return null;
const bearer = await resolveCommandCodeQuotaBearer(config);
const bearer = await resolveCommandCodeQuotaBearer(provider, config);
if (!bearer) return null;
const whoamiBody = await fetchCommandCodeJson(COMMAND_CODE_WHOAMI_URL, bearer);
const whoami = asRecord(whoamiBody?.data) ?? whoamiBody;
Expand Down
8 changes: 4 additions & 4 deletions src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,18 +284,18 @@ export function resetCompactionFallbackWarningsForTests(): void {
compactionFallbackWarnings.clear();
}

function usableResolvedApiKey(apiKey: string | undefined): string | undefined {
const resolved = resolveProviderApiKey(apiKey);
function usableResolvedApiKey(apiKey: string | undefined, providerName: string): string | undefined {
const resolved = resolveProviderApiKey(apiKey, providerName);
return typeof resolved === "string" && resolved.trim().length > 0 ? resolved : undefined;
}

export function routedProviderConfig(providerName: string, provider: OcxProviderConfig): OcxProviderConfig {
const registryEntry = PROVIDER_REGISTRY.find(entry => entry.id === providerName);
if (!registryEntry || !providerMatchesRegistryTransportWithStaticGuards(providerName, provider)) {
assertProviderDestinationAllowed(providerName, provider);
return { ...provider, apiKey: usableResolvedApiKey(provider.apiKey) };
return { ...provider, apiKey: usableResolvedApiKey(provider.apiKey, providerName) };
}
const resolvedApiKey = usableResolvedApiKey(provider.apiKey);
const resolvedApiKey = usableResolvedApiKey(provider.apiKey, providerName);
const staticModelCatalog = !providerSupportsLiveModelDiscovery(providerName, provider);
const repairLegacyMimoFreeAuth = providerName === "mimo-free"
&& staticModelCatalog
Expand Down
Loading
Loading