mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-14 10:52:17 +03:00
feat(models): live account catalog for Claude, Codex, Copilot, AGY (#12866)
Validado numa worktree combinada com as 16 PRs desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-file-size e check-changelog-integrity OK, complexity 2788/3218 e cognitive 1261/1437, ESLint 0 erros nos 152 arquivos alterados, 771 testes unitários focados, 49 de integração e a suíte vitest:ui completa (2149) verdes. O ponto que sustenta a PR é o `models.dev` virar overlay de preço em vez de fonte de catálogo. Um catálogo estático que sobrevive à conta já ter listado ids mais novos é o tipo de defeito que só aparece quando o modelo novo é justamente o que se quer usar. Nota de integração: `activeSyncedCatalog.ts` colidiu com o #12934 (união dos `customModels` do picker no catálogo de despacho). Como você extraiu o bloco original para `loadConnectionCatalog`, os dois se compõem: a união dos irmãos agy/antigravity primeiro, o `unionCustomModels` por cima. Revalidei com `custom-models-live-catalog-12597`, `live-model-catalog-reconciliation-8926`, `sync-models-degraded-cached-catalog-9683`, `models-dev-catalog-read-gate`, `discovery-class`, `reactive-model-sync` e `l1-oauth-autosync-default` juntos — 48/48 — mais typecheck:core limpo. Sobre o `autoSync` padrão em Claude/Codex/Copilot com scheduler de 6h: passei isso pelo dono antes de mergear e a decisão foi manter como está.
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
} from "@omniroute/open-sse/config/grokBuild.ts";
|
||||
import { getAntigravityContentHeaders } from "@omniroute/open-sse/services/antigravityHeaders.ts";
|
||||
import { parseGeminiModelsList } from "@/lib/providerModels/geminiModelsParser";
|
||||
import { buildClaudeModelsHeaders } from "@/lib/providerModels/claudeModelsHeaders";
|
||||
import {
|
||||
CLINE_MODELS_ENDPOINT,
|
||||
CLINEPASS_MODELS_ENDPOINT,
|
||||
@@ -103,10 +104,12 @@ export function parsePerplexitySonarModels(data: any): any[] {
|
||||
(model: any) => typeof model?.id === "string" && /^sonar(-|$)/.test(model.id)
|
||||
);
|
||||
}
|
||||
type ProviderModelsHeaderContext = {
|
||||
export type ProviderModelsHeaderContext = {
|
||||
authType?: string;
|
||||
providerSpecificData?: unknown;
|
||||
email?: string | null;
|
||||
accessToken?: string | null;
|
||||
apiKey?: string | null;
|
||||
};
|
||||
|
||||
export type ProviderModelsConfigEntry = {
|
||||
@@ -124,6 +127,20 @@ export type ProviderModelsConfigEntry = {
|
||||
parseResponse: (data: any) => any;
|
||||
};
|
||||
|
||||
export function assembleProviderModelsHeaders(
|
||||
config: ProviderModelsConfigEntry,
|
||||
token: string,
|
||||
context?: ProviderModelsHeaderContext,
|
||||
): Record<string, string> {
|
||||
const headers = config.buildHeaders
|
||||
? config.buildHeaders(token, context)
|
||||
: { ...config.headers };
|
||||
if (!config.buildHeaders && config.authHeader && !config.authQuery) {
|
||||
headers[config.authHeader] = (config.authPrefix || "") + token;
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
const DASHSCOPE_TEXT_MODELS_CONFIG: ProviderModelsConfigEntry = {
|
||||
url: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/models",
|
||||
method: "GET",
|
||||
@@ -387,10 +404,14 @@ export const PROVIDER_MODELS_CONFIG: Record<string, ProviderModelsConfigEntry> =
|
||||
url: "https://api.anthropic.com/v1/models",
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Anthropic-Version": "2023-06-01",
|
||||
"anthropic-version": "2023-06-01",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
authHeader: "x-api-key",
|
||||
buildHeaders: (_token, context) =>
|
||||
buildClaudeModelsHeaders({
|
||||
accessToken: context?.accessToken,
|
||||
apiKey: context?.apiKey,
|
||||
}),
|
||||
parseResponse: (data) => data.data || [],
|
||||
},
|
||||
gemini: {
|
||||
|
||||
@@ -92,6 +92,7 @@ import { isConnectionUnavailableToAuxiliaryActivity } from "@/lib/exclusiveLease
|
||||
import { fetchCursorAgentModels } from "@/lib/providerModels/cursorAgent";
|
||||
import { fetchCursorAvailableModels } from "@/lib/providerModels/cursorAvailableModels";
|
||||
import { ensureCursorAutoCatalogEntry } from "@/lib/providerModels/cursorAutoCatalog";
|
||||
import { resolveCopilotDiscoveryToken } from "@/lib/providerModels/copilotDiscoveryToken";
|
||||
import {
|
||||
type JsonRecord,
|
||||
asRecord,
|
||||
@@ -117,6 +118,7 @@ import { isNamedOpenAIStyleProvider } from "./discovery/providerSets";
|
||||
import { buildStaleEncryptionKeyResponse } from "./staleEncryptionGuard";
|
||||
import {
|
||||
type ProviderModelsConfigEntry,
|
||||
assembleProviderModelsHeaders,
|
||||
PROVIDER_MODELS_CONFIG,
|
||||
} from "./discovery/providerModelsConfig";
|
||||
import {
|
||||
@@ -1336,14 +1338,6 @@ export async function GET(
|
||||
return buildApiDiscoveryResponse(normalizeSapModelsResponse(await response.json()));
|
||||
}
|
||||
|
||||
if (provider === "claude") {
|
||||
return buildResponse({
|
||||
provider,
|
||||
connectionId,
|
||||
models: getStaticModelsForProvider("claude") || [],
|
||||
});
|
||||
}
|
||||
|
||||
if (provider === "cursor") {
|
||||
const cachedResponse = maybeReturnCachedDiscovery();
|
||||
if (cachedResponse) return cachedResponse;
|
||||
@@ -1650,8 +1644,10 @@ export async function GET(
|
||||
// the exchanged token; only DISCOVERY needs the raw token.) This mirrors the
|
||||
// Copilot CLI + Hermes "de-gate model discovery" fix. Exchanged token stays
|
||||
// as a fallback for connections that only captured that.
|
||||
const copilotToken =
|
||||
toNonEmptyString(accessToken) || toNonEmptyString(psd.copilotToken) || null;
|
||||
const copilotToken = resolveCopilotDiscoveryToken({
|
||||
accessToken,
|
||||
copilotToken: psd.copilotToken,
|
||||
});
|
||||
|
||||
const discovery = await fetchGitHubCopilotModels({
|
||||
token: copilotToken,
|
||||
@@ -1697,8 +1693,10 @@ export async function GET(
|
||||
if (autoFetchDisabledResponse) return autoFetchDisabledResponse;
|
||||
|
||||
const psd = asRecord(connection.providerSpecificData);
|
||||
const copilotToken =
|
||||
toNonEmptyString(psd.copilotToken) || toNonEmptyString(accessToken) || null;
|
||||
const copilotToken = resolveCopilotDiscoveryToken({
|
||||
accessToken,
|
||||
copilotToken: psd.copilotToken,
|
||||
});
|
||||
// endpoints.api serves the real chat model catalog; endpoints.proxy only
|
||||
// has NES/autocomplete models. Prefer the api host, fall back to proxy for
|
||||
// legacy connections that predate copilotApiUrl capture.
|
||||
@@ -2157,10 +2155,13 @@ export async function GET(
|
||||
}
|
||||
|
||||
if (githubCatalogModels && githubCatalogModels.length > 0) {
|
||||
return buildApiDiscoveryResponse(
|
||||
finalizeCodexCatalog(githubCatalogModels),
|
||||
"Codex live catalog unavailable — using GitHub model catalog"
|
||||
);
|
||||
return buildResponse({
|
||||
provider,
|
||||
connectionId,
|
||||
models: finalizeCodexCatalog(githubCatalogModels),
|
||||
source: "github_catalog",
|
||||
warning: "Codex live catalog unavailable — using GitHub model catalog",
|
||||
});
|
||||
}
|
||||
|
||||
if (cachedDiscoveryModels.length > 0) {
|
||||
@@ -2292,12 +2293,8 @@ export async function GET(
|
||||
}
|
||||
|
||||
// Build headers
|
||||
const headers = config.buildHeaders
|
||||
? config.buildHeaders(token, connection)
|
||||
: { ...config.headers };
|
||||
if (!config.buildHeaders && config.authHeader && !config.authQuery) {
|
||||
headers[config.authHeader] = (config.authPrefix || "") + token;
|
||||
}
|
||||
const headerContext = { ...connection, accessToken, apiKey };
|
||||
const headers = assembleProviderModelsHeaders(config, token, headerContext);
|
||||
|
||||
// Make request (with pagination for providers that use nextPageToken, e.g. Gemini)
|
||||
const fetchOptions: any = {
|
||||
|
||||
@@ -48,6 +48,23 @@ export function isDegradedCachedCatalog(modelsData: {
|
||||
return typeof modelsData?.warning === "string" && modelsData.warning.trim().length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Codex live-empty GET returns `{ source: "github_catalog", warning: "…" }`
|
||||
* via `buildResponse`. That is a display fallback of public models.json, not
|
||||
* an authoritative discovery. Same discriminator as cache: source match + a
|
||||
* non-empty warning. Without this, Import/Sync and boot ModelSync would persist
|
||||
* the public catalog as the synced source of truth (spec 3.7).
|
||||
*/
|
||||
export function isDegradedGithubCatalog(modelsData: {
|
||||
source?: unknown;
|
||||
warning?: unknown;
|
||||
}): boolean {
|
||||
const source =
|
||||
typeof modelsData?.source === "string" ? modelsData.source.trim().toLowerCase() : "";
|
||||
if (source !== "github_catalog") return false;
|
||||
return typeof modelsData?.warning === "string" && modelsData.warning.trim().length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Either degraded shape. Model-sync must refuse to treat these as a successful
|
||||
* discovery: persisting them would silently pin a stale catalog and hide the
|
||||
@@ -58,5 +75,9 @@ export function isDegradedDiscovery(modelsData: {
|
||||
intentional?: unknown;
|
||||
warning?: unknown;
|
||||
}): boolean {
|
||||
return isDegradedLocalCatalog(modelsData) || isDegradedCachedCatalog(modelsData);
|
||||
return (
|
||||
isDegradedLocalCatalog(modelsData) ||
|
||||
isDegradedCachedCatalog(modelsData) ||
|
||||
isDegradedGithubCatalog(modelsData)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -58,6 +58,34 @@ function resolveStoredProviderId(aliasOrId: string): string {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Distinct stored provider ids that share an account family.
|
||||
* Credential lookup already pairs these in PROVIDER_SEARCH_PAIRS (#8779);
|
||||
* live catalogs are keyed `provider:connectionId`, so the same pair must
|
||||
* union here. parseModel folds `agy/` → `antigravity`, but CLI-card rows
|
||||
* persist catalogs under `agy:` and the IDE card under `antigravity:`.
|
||||
*/
|
||||
const CATALOG_SIBLING_IDS: Record<string, string[]> = {
|
||||
antigravity: ["agy"],
|
||||
agy: ["antigravity"],
|
||||
};
|
||||
|
||||
function catalogLookupIds(storedProviderId: string): string[] {
|
||||
const siblings = CATALOG_SIBLING_IDS[storedProviderId] || [];
|
||||
return [storedProviderId, ...siblings.filter((id) => id !== storedProviderId)];
|
||||
}
|
||||
|
||||
function unionModels(groups: SyncedAvailableModel[][]): SyncedAvailableModel[] {
|
||||
const models = new Map<string, SyncedAvailableModel>();
|
||||
for (const group of groups) {
|
||||
for (const model of group) {
|
||||
if (!model?.id || models.has(model.id)) continue;
|
||||
models.set(model.id, model);
|
||||
}
|
||||
}
|
||||
return Array.from(models.values());
|
||||
}
|
||||
|
||||
function readConnectionRef(connection: unknown): ProviderConnectionRef | null {
|
||||
if (!connection || typeof connection !== "object") return null;
|
||||
|
||||
@@ -154,6 +182,23 @@ async function unionCustomModels(
|
||||
* non-empty usable catalog. Missing, empty, malformed, or unavailable state
|
||||
* fails open to the static registry.
|
||||
*/
|
||||
async function loadConnectionCatalog(storedProviderId: string): Promise<SyncedAvailableModel[]> {
|
||||
const [connections, modelsByConnection] = await Promise.all([
|
||||
getRawProviderConnections({ provider: storedProviderId, isActive: true }, undefined, undefined, [
|
||||
"id",
|
||||
"provider",
|
||||
]),
|
||||
getSyncedAvailableModelsByConnection(storedProviderId),
|
||||
]);
|
||||
|
||||
const activeConnectionIds = connections
|
||||
.map(readConnectionRef)
|
||||
.filter((connection): connection is ProviderConnectionRef => connection !== null)
|
||||
.map((connection) => connection.id);
|
||||
|
||||
return collectModelsForConnections(modelsByConnection, activeConnectionIds);
|
||||
}
|
||||
|
||||
export async function getActiveSyncedCatalog(providerId: string): Promise<ActiveSyncedCatalog> {
|
||||
const storedProviderId = resolveStoredProviderId(providerId);
|
||||
if (!storedProviderId) {
|
||||
@@ -161,27 +206,13 @@ export async function getActiveSyncedCatalog(providerId: string): Promise<Active
|
||||
}
|
||||
|
||||
try {
|
||||
const [connections, modelsByConnection] = await Promise.all([
|
||||
getRawProviderConnections(
|
||||
{ provider: storedProviderId, isActive: true },
|
||||
undefined,
|
||||
undefined,
|
||||
["id", "provider"]
|
||||
),
|
||||
getSyncedAvailableModelsByConnection(storedProviderId),
|
||||
]);
|
||||
|
||||
const activeConnectionIds = connections
|
||||
.map(readConnectionRef)
|
||||
.filter((connection): connection is ProviderConnectionRef => connection !== null)
|
||||
.map((connection) => connection.id);
|
||||
|
||||
const lookupIds = catalogLookupIds(storedProviderId);
|
||||
const siblingCatalogs = await Promise.all(lookupIds.map(loadConnectionCatalog));
|
||||
// #12866 unions the agy/antigravity sibling catalogs; #12934 then overlays the
|
||||
// picker-added customModels so dispatch admits the same rows the picker REST shows.
|
||||
const models = enrichCursorCatalog(
|
||||
storedProviderId,
|
||||
await unionCustomModels(
|
||||
storedProviderId,
|
||||
collectModelsForConnections(modelsByConnection, activeConnectionIds)
|
||||
)
|
||||
await unionCustomModels(storedProviderId, unionModels(siblingCatalogs))
|
||||
);
|
||||
if (models.length > 0) {
|
||||
return {
|
||||
|
||||
@@ -143,6 +143,7 @@ export const claude = {
|
||||
const providerSpecificData: any = {
|
||||
// Generated once at provisioning; preserved across token refresh.
|
||||
cliUserID: crypto.randomBytes(32).toString("hex"),
|
||||
autoSync: true,
|
||||
};
|
||||
if (bs.account_uuid) providerSpecificData.accountUUID = bs.account_uuid;
|
||||
if (bs.organization_uuid) providerSpecificData.organizationUUID = bs.organization_uuid;
|
||||
|
||||
@@ -194,6 +194,7 @@ export const codex = {
|
||||
}
|
||||
|
||||
const providerSpecificData = {
|
||||
autoSync: true,
|
||||
workspaceId,
|
||||
workspacePlanType: planType,
|
||||
// Also store the full authInfo for future reference
|
||||
|
||||
@@ -102,6 +102,7 @@ export const gheCopilot = {
|
||||
refreshToken: tokens.refresh_token,
|
||||
expiresIn: tokens.expires_in,
|
||||
providerSpecificData: {
|
||||
autoSync: true,
|
||||
gheUrl: extra?.gheUrl,
|
||||
copilotApiUrl: extra?.copilotApiUrl || extra?.copilotToken?.endpoints?.api,
|
||||
copilotProxyUrl: extra?.copilotProxyUrl || extra?.copilotToken?.endpoints?.proxy,
|
||||
|
||||
@@ -79,6 +79,7 @@ export const github = {
|
||||
refreshToken: tokens.refresh_token,
|
||||
expiresIn: tokens.expires_in,
|
||||
providerSpecificData: {
|
||||
autoSync: true,
|
||||
copilotToken: extra?.copilotToken?.token,
|
||||
copilotTokenExpiresAt: extra?.copilotToken?.expires_at,
|
||||
githubUserId: extra?.userInfo?.id,
|
||||
|
||||
26
src/lib/providerModels/claudeModelsHeaders.ts
Normal file
26
src/lib/providerModels/claudeModelsHeaders.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { getClaudeCodeVersion } from "@omniroute/open-sse/executors/claudeIdentity.ts";
|
||||
|
||||
export function buildClaudeModelsHeaders(input: {
|
||||
accessToken?: string | null;
|
||||
apiKey?: string | null;
|
||||
}): Record<string, string> {
|
||||
const accessToken = typeof input.accessToken === "string" ? input.accessToken.trim() : "";
|
||||
const apiKey = typeof input.apiKey === "string" ? input.apiKey.trim() : "";
|
||||
const common = {
|
||||
"Content-Type": "application/json",
|
||||
"anthropic-version": "2023-06-01",
|
||||
};
|
||||
if (accessToken) {
|
||||
const scheme = "Bearer";
|
||||
return {
|
||||
...common,
|
||||
Authorization: [scheme, accessToken].join(" "),
|
||||
"anthropic-beta": "oauth-2025-04-20",
|
||||
"User-Agent": `claude-cli/${getClaudeCodeVersion()} (external, cli)`,
|
||||
};
|
||||
}
|
||||
return {
|
||||
...common,
|
||||
"x-api-key": apiKey,
|
||||
};
|
||||
}
|
||||
12
src/lib/providerModels/copilotDiscoveryToken.ts
Normal file
12
src/lib/providerModels/copilotDiscoveryToken.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
function nonEmpty(value: unknown): string | null {
|
||||
if (typeof value !== "string") return null;
|
||||
const trimmed = value.trim();
|
||||
return trimmed ? trimmed : null;
|
||||
}
|
||||
|
||||
export function resolveCopilotDiscoveryToken(input: {
|
||||
accessToken?: unknown;
|
||||
copilotToken?: unknown;
|
||||
}): string | null {
|
||||
return nonEmpty(input.accessToken) || nonEmpty(input.copilotToken);
|
||||
}
|
||||
31
src/lib/providerModels/discoveryClass.ts
Normal file
31
src/lib/providerModels/discoveryClass.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { getRegistryEntry } from "@omniroute/open-sse/config/providerRegistry";
|
||||
import { providerUsesCuratedModelsOnly } from "@/lib/providers/modelListingCapability";
|
||||
import { HARDCODED_MODELS_CONFIG_IDS } from "./hardcodedModelsConfigIds.ts";
|
||||
|
||||
export type DiscoveryClass = "account-live" | "openai-compat" | "static-only";
|
||||
|
||||
export const ACCOUNT_LIVE_PROVIDER_IDS = [
|
||||
"claude",
|
||||
"codex",
|
||||
"github",
|
||||
"ghe-copilot",
|
||||
"agy",
|
||||
"antigravity",
|
||||
"gemini",
|
||||
"cursor",
|
||||
"cu",
|
||||
"grok-cli",
|
||||
] as const;
|
||||
|
||||
const ACCOUNT_LIVE = new Set<string>(ACCOUNT_LIVE_PROVIDER_IDS);
|
||||
|
||||
export function getDiscoveryClass(providerId: string): DiscoveryClass {
|
||||
const id = providerId.trim().toLowerCase();
|
||||
if (!id) return "static-only";
|
||||
if (providerUsesCuratedModelsOnly(id)) return "static-only";
|
||||
if (ACCOUNT_LIVE.has(id)) return "account-live";
|
||||
if (HARDCODED_MODELS_CONFIG_IDS.has(id) || Boolean(getRegistryEntry(id)?.modelsUrl)) {
|
||||
return "openai-compat";
|
||||
}
|
||||
return "static-only";
|
||||
}
|
||||
51
src/lib/providerModels/hardcodedModelsConfigIds.ts
Normal file
51
src/lib/providerModels/hardcodedModelsConfigIds.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
/** Keys of PROVIDER_MODELS_CONFIG. Lockstep test in discovery-class.test.ts. */
|
||||
export const HARDCODED_MODELS_CONFIG_IDS: ReadonlySet<string> = new Set([
|
||||
"agentrouter",
|
||||
"aimlapi",
|
||||
"alibaba",
|
||||
"alibaba-cn",
|
||||
"anthropic",
|
||||
"antigravity",
|
||||
"blackbox",
|
||||
"cerebras",
|
||||
"chutes",
|
||||
"clarifai",
|
||||
"claude",
|
||||
"cline",
|
||||
"clinepass",
|
||||
"cloudflare-ai",
|
||||
"cohere",
|
||||
"command-code",
|
||||
"deepseek",
|
||||
"fenayai",
|
||||
"fireworks",
|
||||
"gemini",
|
||||
"gitlawb",
|
||||
"gitlawb-gmi",
|
||||
"glm-cn",
|
||||
"grok-cli",
|
||||
"groq",
|
||||
"huggingface",
|
||||
"kilo-gateway",
|
||||
"kilocode",
|
||||
"kimi",
|
||||
"kimi-coding",
|
||||
"kimi-coding-apikey",
|
||||
"mistral",
|
||||
"nebius",
|
||||
"nvidia",
|
||||
"ollama-cloud",
|
||||
"openai",
|
||||
"opencode-go",
|
||||
"opencode-zen",
|
||||
"openference",
|
||||
"openference-api",
|
||||
"openrouter",
|
||||
"openvecta",
|
||||
"perplexity",
|
||||
"qwen-cloud",
|
||||
"synthetic",
|
||||
"thebai",
|
||||
"together",
|
||||
"xai",
|
||||
]);
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
isUserCallableAntigravityModelId,
|
||||
toClientAntigravityModelId,
|
||||
} from "@omniroute/open-sse/config/antigravityModelAliases.ts";
|
||||
import { isUserCallableAgyModelId } from "@omniroute/open-sse/config/agyModels.ts";
|
||||
import { isDiscoverableAgyModelId } from "@omniroute/open-sse/config/agyModels.ts";
|
||||
|
||||
type JsonRecord = Record<string, unknown>;
|
||||
|
||||
@@ -13,7 +13,7 @@ export function isRecord(value: unknown): value is JsonRecord {
|
||||
export function isUsageQuotaKeyAllowed(provider: string, quotaKey: string): boolean {
|
||||
if (quotaKey === "credits" || quotaKey === "models") return true;
|
||||
if (provider === "antigravity") return isUserCallableAntigravityModelId(quotaKey);
|
||||
if (provider === "agy") return isUserCallableAgyModelId(quotaKey);
|
||||
if (provider === "agy") return isDiscoverableAgyModelId(quotaKey);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Kept in lockstep with the codex CLI actually installed in the OmniRoute image
|
||||
// (bin/omniroute-fix.Containerfile installs `codex` latest; app-server runtime is
|
||||
// 0.149.0 as of 2026-08-22). When the image's codex is bumped, refresh this so the
|
||||
// fingerprint OpenAI sees from the OAuth/Responses face matches the real client
|
||||
// version. Overridable per-deployment via the CODEX_CLIENT_VERSION env.
|
||||
export const DEFAULT_CODEX_CLIENT_VERSION = "0.149.0";
|
||||
// Kept in lockstep with the `@openai/codex@x.y.z` pin in the root Dockerfile
|
||||
// (the CLI installed in the OmniRoute image). When that image pin is bumped,
|
||||
// refresh this so the fingerprint OpenAI sees from the OAuth/Responses face
|
||||
// matches the real client version. Overridable per-deployment via
|
||||
// CODEX_CLIENT_VERSION.
|
||||
export const DEFAULT_CODEX_CLIENT_VERSION = "0.153.2";
|
||||
export const CODEX_CLI_RS_ORIGINATOR = "codex_cli_rs";
|
||||
|
||||
export function getCodexCliRsHeaders(
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*
|
||||
* Automatically refreshes model lists for provider connections that have
|
||||
* autoSync enabled in their providerSpecificData, at a configurable
|
||||
* interval (default: 24h).
|
||||
* interval (default: 6h).
|
||||
*
|
||||
* Pattern mirrors cloudSyncScheduler.ts for consistency.
|
||||
*/
|
||||
@@ -14,7 +14,7 @@ import { getSettings, updateSettings } from "@/lib/db/settings";
|
||||
import { isConnectionUnavailableToAuxiliaryActivity } from "@/lib/exclusiveLeaseIsolation";
|
||||
import { getRuntimePorts } from "@/lib/runtime/ports";
|
||||
|
||||
const DEFAULT_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
||||
export const DEFAULT_INTERVAL_MS = 6 * 60 * 60 * 1000; // 6 hours
|
||||
const MODEL_SYNC_SETTING_KEY = "model_sync_last_run";
|
||||
const MODEL_SYNC_INTERNAL_AUTH_HEADER = "x-model-sync-internal-auth";
|
||||
|
||||
@@ -270,7 +270,7 @@ async function runSyncCycle(apiBaseUrl: string): Promise<void> {
|
||||
/**
|
||||
* Start the model sync scheduler.
|
||||
* @param apiBaseUrl — internal base URL to call OmniRoute's own API
|
||||
* @param intervalMs — sync interval in milliseconds (default: 24h)
|
||||
* @param intervalMs — sync interval in milliseconds (default: 6h)
|
||||
*/
|
||||
export function startModelSyncScheduler(
|
||||
apiBaseUrl = getModelSyncInternalBaseUrl(),
|
||||
|
||||
Reference in New Issue
Block a user