Files
OmniRoute/open-sse/services/usage/codex.ts
Praveen K Palaniswamy 65e81158ab fix(ollama): route models by advertised capability (#11088)
Landed with the design call resolved per the owner's pick — **option 1**: the synced store is now endpoint-agnostic (persistDiscoveredModels and managedModelImport no longer drop non-chat models at write time), and chat selectability moved to read time (auto-pool expansion in autoStrategy applies filterChatSelectableModels; the models-route projection already had its chatOnly filter). Your discovery test now passes end-to-end (3/3): /api/show capabilities persist per connection and image/embedding requests route through the advertising host.

Reconciliation notes: conflicted areas merged onto the current tip (adobe discovery import, requestedModel preflight signature, resolvedProvider fast-path coexists with the synced-route override — explicit resolution wins); carried base-red drains (#10055 memoization, #11071 test variants) dropped as already-landed; the managed-model-import exclusion test was propagated to the new contract (image/video models persist; the read filter still hides them from chat pickers — pinned by a new assertion). Full battery: 205/206 focused (the one red is a confirmed periodic-timer timing flake on the loaded devbox — 20/20 isolated), autoCombo vitest 30/30, combo suites 46/46, gates + typecheck clean.

Thank you @yourspraveen — the capability probe + routing design was right; it just needed the store contract opened up. Fixes #11087.
2026-08-23 11:45:01 -03:00

81 lines
3.0 KiB
TypeScript

/**
* usage/codex.ts — Codex (OpenAI / ChatGPT backend) usage fetcher.
*
* Extracted from services/usage.ts (god-file decomposition): the Codex family — the ChatGPT
* backend usage-API config and the getCodexUsage fetcher that reads the persisted workspace
* binding and shapes quotas via buildCodexUsageQuotas. Depends only on the scalar leaf +
* codexUsageQuotas — no host coupling — so it lives as a co-located provider leaf. usage.ts
* imports getCodexUsage (dispatcher). Behavior-preserving move.
*/
import { buildCodexUsageQuotas } from "../codexUsageQuotas.ts";
import { getCodexBackendIdentityHeaders } from "../../config/codexClient.ts";
import { getFieldValue } from "./scalars.ts";
// Codex (OpenAI) API config
const CODEX_CONFIG = {
usageUrl: "https://chatgpt.com/backend-api/wham/usage",
};
/**
* Codex (OpenAI) Usage - Fetch from ChatGPT backend API
* IMPORTANT: Uses persisted workspaceId from OAuth to ensure correct workspace binding.
* No fallback to other workspaces - strict binding to user's selected workspace.
*/
export async function getCodexUsage(
accessToken?: string,
providerSpecificData: Record<string, unknown> = {}
) {
try {
// Use persisted workspace ID from OAuth - NO FALLBACK
const accountId =
typeof providerSpecificData.workspaceId === "string"
? providerSpecificData.workspaceId
: null;
const headers: Record<string, string> = {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
Accept: "application/json",
// Same UA/version identity chain as Codex inference (sub2api v0.1.178
// unified-outbound-identity): usage probes must not show up upstream as
// an anonymous half-identity next to the converged inference traffic.
...getCodexBackendIdentityHeaders(),
};
if (accountId) {
headers["chatgpt-account-id"] = accountId;
}
const response = await fetch(CODEX_CONFIG.usageUrl, {
method: "GET",
headers,
});
if (!response.ok) {
if (response.status === 401 || response.status === 403) {
return {
message: `Codex token expired or access denied. Please re-authenticate the connection.`,
};
}
throw new Error(`Codex API error: ${response.status}`);
}
const data = await response.json();
const { rateLimit, quotas, bankedResetCredits, rateLimitReachedType } =
buildCodexUsageQuotas(data);
return {
plan: String(getFieldValue(data, "plan_type", "planType") || "unknown"),
limitReached: Boolean(getFieldValue(rateLimit, "limit_reached", "limitReached")),
quotas,
// Banked reset credits (display-only, eligibility-gated — issue #5199).
// Absent for most accounts; never throws when the upstream omits it.
...(bankedResetCredits !== undefined ? { bankedResetCredits } : {}),
...(rateLimitReachedType !== undefined ? { rateLimitReachedType } : {}),
};
} catch (error) {
return { message: `Failed to fetch Codex usage: ${(error as Error).message}` };
}
}