@opengeni/config 0.10.0 → 0.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +112 -0
- package/dist/index.js +287 -11
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +335 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/config",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.3",
|
|
4
4
|
"description": "OpenGeni runtime configuration: settings resolution, deployment knobs, and config validation shared across the server packages.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"prepublishOnly": "bash ../../scripts/prepublish-guard"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@opengeni/codex": "^0.2.
|
|
37
|
-
"@opengeni/contracts": "^0.
|
|
36
|
+
"@opengeni/codex": "^0.2.10",
|
|
37
|
+
"@opengeni/contracts": "^0.32.0",
|
|
38
38
|
"zod": "^4.2.1"
|
|
39
39
|
},
|
|
40
40
|
"engines": {
|
package/src/index.ts
CHANGED
|
@@ -355,6 +355,10 @@ const SettingsSchema = z.object({
|
|
|
355
355
|
openaiBaseUrl: z.string().optional(),
|
|
356
356
|
openaiModel: z.string().default("gpt-5.6-sol"),
|
|
357
357
|
openaiAllowedModels: z.string().default("gpt-5.6-sol,gpt-5.6-terra,gpt-5.6-luna"),
|
|
358
|
+
// OpenGeni-managed Vercel AI Gateway. When configured, the two reviewed
|
|
359
|
+
// Gateway models below are added to the managed-credit catalog. Workspace
|
|
360
|
+
// Gateway keys use the encrypted connection broker and never this secret.
|
|
361
|
+
vercelAiGatewayApiKey: z.string().optional(),
|
|
358
362
|
// Native composer voice input (browser MediaRecorder → API transcription).
|
|
359
363
|
// Provider credentials stay server-side; ClientConfig only projects availability
|
|
360
364
|
// and hard ceilings. Selection happens once before audio is sent — never retry
|
|
@@ -428,7 +432,7 @@ const SettingsSchema = z.object({
|
|
|
428
432
|
// match the UI danger flip (UsageBar danger at pct >= 90). OPENGENI_CODEX_ROTATION_NEAR_EXHAUSTION_PCT.
|
|
429
433
|
codexRotationNearExhaustionPct: z.coerce.number().int().min(1).max(100).default(90),
|
|
430
434
|
openaiReasoningEffort: ReasoningEffort.default("low"),
|
|
431
|
-
openaiAllowedReasoningEfforts: z.string().default("low,medium,high,xhigh"),
|
|
435
|
+
openaiAllowedReasoningEfforts: z.string().default("low,medium,high,xhigh,max"),
|
|
432
436
|
openaiResponsesTransport: z.enum(["http", "websocket"]).default("http"),
|
|
433
437
|
// Provider-assigned item ids (rs_/msg_/fc_…) in Responses API input are
|
|
434
438
|
// resolved against the provider's server-side response store. That store is
|
|
@@ -1178,6 +1182,9 @@ export const ModelCapabilitiesV1Schema = z
|
|
|
1178
1182
|
responsesWebSocket: CapabilityStateV1Schema,
|
|
1179
1183
|
realtimeAudio: CapabilityStateV1Schema,
|
|
1180
1184
|
}),
|
|
1185
|
+
promptCaching: CapabilityStateV1Schema.extend({
|
|
1186
|
+
mode: z.enum(["implicit", "automatic", "none"]),
|
|
1187
|
+
}).optional(),
|
|
1181
1188
|
latencyModes: z
|
|
1182
1189
|
.array(
|
|
1183
1190
|
z.object({
|
|
@@ -1282,7 +1289,12 @@ export type ModelProviderApi = z.infer<typeof ModelProviderApi>;
|
|
|
1282
1289
|
* "codex-subscription" providers authenticate per-request with a ChatGPT/Codex
|
|
1283
1290
|
* subscription token resolved at call time (no static key) — see @opengeni/codex.
|
|
1284
1291
|
*/
|
|
1285
|
-
export const RegistryProviderKind = z.enum([
|
|
1292
|
+
export const RegistryProviderKind = z.enum([
|
|
1293
|
+
"api-key",
|
|
1294
|
+
"codex-subscription",
|
|
1295
|
+
"vercel-gateway-managed",
|
|
1296
|
+
"vercel-gateway-workspace",
|
|
1297
|
+
]);
|
|
1286
1298
|
export type RegistryProviderKind = z.infer<typeof RegistryProviderKind>;
|
|
1287
1299
|
|
|
1288
1300
|
/** A single model exposed by a registry provider. */
|
|
@@ -1335,7 +1347,7 @@ const RegistryModelSchema = z
|
|
|
1335
1347
|
|
|
1336
1348
|
/** A non-built-in provider declared by the host via OPENGENI_MODEL_PROVIDERS_JSON. */
|
|
1337
1349
|
const RegistryProviderSchema = z.object({
|
|
1338
|
-
kind: RegistryProviderKind.default("api-key"),
|
|
1350
|
+
kind: RegistryProviderKind.default("api-key"),
|
|
1339
1351
|
id: z.string().min(1).regex(registryId), // stable provider id, e.g. "fireworks"
|
|
1340
1352
|
label: z.string().min(1).optional(),
|
|
1341
1353
|
api: ModelProviderApi.default("chat"),
|
|
@@ -1401,6 +1413,12 @@ export interface ConfiguredModel {
|
|
|
1401
1413
|
credentialSource: CredentialSourceV1;
|
|
1402
1414
|
billing: BillingAttributionV1;
|
|
1403
1415
|
capabilities: ModelCapabilitiesV1;
|
|
1416
|
+
requestPolicy?: {
|
|
1417
|
+
gateway: {
|
|
1418
|
+
only: [string, ...string[]];
|
|
1419
|
+
caching: "auto" | "none";
|
|
1420
|
+
};
|
|
1421
|
+
};
|
|
1404
1422
|
pricing?: ModelPricingScheduleV1 | undefined;
|
|
1405
1423
|
definitionVersion: string;
|
|
1406
1424
|
contextWindowTokens?: number | undefined;
|
|
@@ -1411,6 +1429,79 @@ export interface ConfiguredModel {
|
|
|
1411
1429
|
hostedWebSearch: boolean;
|
|
1412
1430
|
}
|
|
1413
1431
|
|
|
1432
|
+
export const VERCEL_AI_GATEWAY_BASE_URL = "https://ai-gateway.vercel.sh/v1" as const;
|
|
1433
|
+
export const VERCEL_AI_GATEWAY_AI_SDK_BASE_URL = "https://ai-gateway.vercel.sh/v4/ai" as const;
|
|
1434
|
+
export const OPENGENI_GATEWAY_PROVIDER_ID = "opengeni-gateway" as const;
|
|
1435
|
+
export const WORKSPACE_GATEWAY_PROVIDER_ID = "workspace-gateway" as const;
|
|
1436
|
+
export const WORKSPACE_GATEWAY_MODEL_ID_PREFIX = "workspace-gateway/" as const;
|
|
1437
|
+
export const VERCEL_AI_GATEWAY_CONNECTION_DOMAIN = "ai-gateway.vercel.sh" as const;
|
|
1438
|
+
export const VERCEL_AI_GATEWAY_CONNECTION_ROLE = "vercel_ai_gateway" as const;
|
|
1439
|
+
|
|
1440
|
+
export const CODEX_REALTIME_MODEL_ID = "gpt-live-1-boulder-alpha" as const;
|
|
1441
|
+
export const OPENGENI_REALTIME_MODEL_ID_PREFIX = "opengeni-gateway/" as const;
|
|
1442
|
+
export const WORKSPACE_REALTIME_MODEL_ID_PREFIX = "workspace-gateway/" as const;
|
|
1443
|
+
|
|
1444
|
+
/** Curated voice models exposed through AI Gateway's normalized realtime API. */
|
|
1445
|
+
export const AI_GATEWAY_REALTIME_MODELS = {
|
|
1446
|
+
openaiRealtime21: {
|
|
1447
|
+
upstreamModelId: "openai/gpt-realtime-2.1",
|
|
1448
|
+
managedModelId: `${OPENGENI_REALTIME_MODEL_ID_PREFIX}openai/gpt-realtime-2.1`,
|
|
1449
|
+
workspaceModelId: `${WORKSPACE_REALTIME_MODEL_ID_PREFIX}openai/gpt-realtime-2.1`,
|
|
1450
|
+
label: "GPT Realtime 2.1",
|
|
1451
|
+
description: "Best overall voice intelligence",
|
|
1452
|
+
},
|
|
1453
|
+
openaiRealtimeMini: {
|
|
1454
|
+
upstreamModelId: "openai/gpt-realtime-mini",
|
|
1455
|
+
managedModelId: `${OPENGENI_REALTIME_MODEL_ID_PREFIX}openai/gpt-realtime-mini`,
|
|
1456
|
+
workspaceModelId: `${WORKSPACE_REALTIME_MODEL_ID_PREFIX}openai/gpt-realtime-mini`,
|
|
1457
|
+
label: "GPT Realtime Mini",
|
|
1458
|
+
description: "Faster, lighter live voice",
|
|
1459
|
+
},
|
|
1460
|
+
grokVoiceThinkFast20: {
|
|
1461
|
+
upstreamModelId: "xai/grok-voice-think-fast-2.0",
|
|
1462
|
+
managedModelId: `${OPENGENI_REALTIME_MODEL_ID_PREFIX}xai/grok-voice-think-fast-2.0`,
|
|
1463
|
+
workspaceModelId: `${WORKSPACE_REALTIME_MODEL_ID_PREFIX}xai/grok-voice-think-fast-2.0`,
|
|
1464
|
+
label: "Grok Voice Think Fast 2.0",
|
|
1465
|
+
description: "Fast, natural xAI voice",
|
|
1466
|
+
},
|
|
1467
|
+
} as const;
|
|
1468
|
+
|
|
1469
|
+
export type AiGatewayRealtimeModel =
|
|
1470
|
+
(typeof AI_GATEWAY_REALTIME_MODELS)[keyof typeof AI_GATEWAY_REALTIME_MODELS];
|
|
1471
|
+
|
|
1472
|
+
export function resolveAiGatewayRealtimeModel(
|
|
1473
|
+
modelId: string,
|
|
1474
|
+
): { source: "managed" | "workspace"; upstreamModelId: string } | null {
|
|
1475
|
+
for (const model of Object.values(AI_GATEWAY_REALTIME_MODELS)) {
|
|
1476
|
+
if (model.managedModelId === modelId) {
|
|
1477
|
+
return { source: "managed", upstreamModelId: model.upstreamModelId };
|
|
1478
|
+
}
|
|
1479
|
+
if (model.workspaceModelId === modelId) {
|
|
1480
|
+
return { source: "workspace", upstreamModelId: model.upstreamModelId };
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
return null;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
export const OPENGENI_GATEWAY_MODELS = {
|
|
1487
|
+
deepseek: {
|
|
1488
|
+
productId: "deepseek-v4-flash-0731",
|
|
1489
|
+
workspaceProductId: `${WORKSPACE_GATEWAY_MODEL_ID_PREFIX}deepseek-v4-flash-0731`,
|
|
1490
|
+
upstreamModelId: "deepseek/deepseek-v4-flash-0731",
|
|
1491
|
+
label: "DeepSeek V4 Flash 0731",
|
|
1492
|
+
providers: ["baseten", "novita", "deepinfra"],
|
|
1493
|
+
implicitCaching: true,
|
|
1494
|
+
},
|
|
1495
|
+
kimi: {
|
|
1496
|
+
productId: "kimi-k3",
|
|
1497
|
+
workspaceProductId: `${WORKSPACE_GATEWAY_MODEL_ID_PREFIX}kimi-k3`,
|
|
1498
|
+
upstreamModelId: "moonshotai/kimi-k3",
|
|
1499
|
+
label: "Kimi K3",
|
|
1500
|
+
providers: ["baseten", "fireworks"],
|
|
1501
|
+
implicitCaching: true,
|
|
1502
|
+
},
|
|
1503
|
+
} as const;
|
|
1504
|
+
|
|
1414
1505
|
/**
|
|
1415
1506
|
* Built-in OpenGeni credit pricing schedules.
|
|
1416
1507
|
*
|
|
@@ -1485,6 +1576,27 @@ export const defaultModelPricing: Record<string, ModelPricingScheduleV1> = {
|
|
|
1485
1576
|
},
|
|
1486
1577
|
],
|
|
1487
1578
|
},
|
|
1579
|
+
// Conservative Vercel AI Gateway fallback prices. Normal managed Gateway
|
|
1580
|
+
// billing uses the exact response Gateway `cost` / `inferenceCost` and applies
|
|
1581
|
+
// the same margin. These token rates are used only if that
|
|
1582
|
+
// metadata is absent. DeepSeek therefore carries the highest approved route
|
|
1583
|
+
// (Novita); both approved Kimi routes have the same list price.
|
|
1584
|
+
[OPENGENI_GATEWAY_MODELS.deepseek.productId]: {
|
|
1585
|
+
default: {
|
|
1586
|
+
inputMicrosPerMillionTokens: 140_000,
|
|
1587
|
+
cachedInputMicrosPerMillionTokens: 28_000,
|
|
1588
|
+
outputMicrosPerMillionTokens: 280_000,
|
|
1589
|
+
marginBps: 2_500,
|
|
1590
|
+
},
|
|
1591
|
+
},
|
|
1592
|
+
[OPENGENI_GATEWAY_MODELS.kimi.productId]: {
|
|
1593
|
+
default: {
|
|
1594
|
+
inputMicrosPerMillionTokens: 3_000_000,
|
|
1595
|
+
cachedInputMicrosPerMillionTokens: 300_000,
|
|
1596
|
+
outputMicrosPerMillionTokens: 15_000_000,
|
|
1597
|
+
marginBps: 2_500,
|
|
1598
|
+
},
|
|
1599
|
+
},
|
|
1488
1600
|
// Fireworks AI / GLM 5.2 — the first shipped non-OpenAI registry model. A
|
|
1489
1601
|
// built-in default pricing entry makes managed billing work out of the box
|
|
1490
1602
|
// for hosts that expose this model via OPENGENI_MODEL_PROVIDERS_JSON without
|
|
@@ -1656,6 +1768,7 @@ export function getSettings(): Settings {
|
|
|
1656
1768
|
openaiBaseUrl: optional("OPENGENI_OPENAI_BASE_URL") ?? optional("OPENAI_BASE_URL"),
|
|
1657
1769
|
openaiModel: optional("OPENGENI_OPENAI_MODEL"),
|
|
1658
1770
|
openaiAllowedModels: optional("OPENGENI_OPENAI_ALLOWED_MODELS"),
|
|
1771
|
+
vercelAiGatewayApiKey: optional("OPENGENI_VERCEL_AI_GATEWAY_API_KEY"),
|
|
1659
1772
|
voiceInputMaxDurationSeconds: optional("OPENGENI_VOICE_INPUT_MAX_DURATION_SECONDS"),
|
|
1660
1773
|
voiceInputMaxSizeBytes: optional("OPENGENI_VOICE_INPUT_MAX_SIZE_BYTES"),
|
|
1661
1774
|
voiceInputProviderOrder: optional("OPENGENI_VOICE_INPUT_PROVIDER_ORDER"),
|
|
@@ -2202,6 +2315,131 @@ function legacyModelCapabilities(
|
|
|
2202
2315
|
});
|
|
2203
2316
|
}
|
|
2204
2317
|
|
|
2318
|
+
export function gatewayRequestPolicyForUpstreamModel(
|
|
2319
|
+
upstreamModelId: string,
|
|
2320
|
+
): ConfiguredModel["requestPolicy"] {
|
|
2321
|
+
const model = Object.values(OPENGENI_GATEWAY_MODELS).find(
|
|
2322
|
+
(candidate) => candidate.upstreamModelId === upstreamModelId,
|
|
2323
|
+
);
|
|
2324
|
+
if (!model) {
|
|
2325
|
+
return undefined;
|
|
2326
|
+
}
|
|
2327
|
+
return {
|
|
2328
|
+
gateway: {
|
|
2329
|
+
only: [...model.providers] as [string, ...string[]],
|
|
2330
|
+
caching: model.implicitCaching ? "auto" : "none",
|
|
2331
|
+
},
|
|
2332
|
+
};
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
function gatewayModelCapabilities(
|
|
2336
|
+
settings: Settings,
|
|
2337
|
+
input: { implicitCaching: boolean; vision: boolean },
|
|
2338
|
+
): ModelCapabilitiesV1 {
|
|
2339
|
+
const legacy = legacyModelCapabilities(settings, {
|
|
2340
|
+
reasoningEffort: true,
|
|
2341
|
+
hostedWebSearch: false,
|
|
2342
|
+
});
|
|
2343
|
+
return normalizeCapabilities({
|
|
2344
|
+
...legacy,
|
|
2345
|
+
functionCalling: { upstream: "supported", runnable: true },
|
|
2346
|
+
inputModalities: input.vision ? ["text", "image"] : ["text"],
|
|
2347
|
+
transports: {
|
|
2348
|
+
...legacy.transports,
|
|
2349
|
+
sse: { upstream: "supported", runnable: true },
|
|
2350
|
+
},
|
|
2351
|
+
promptCaching: input.implicitCaching
|
|
2352
|
+
? { upstream: "supported", runnable: true, mode: "implicit" }
|
|
2353
|
+
: { upstream: "unsupported", runnable: false, mode: "none" },
|
|
2354
|
+
// Both Gateway products expose one reviewed route policy and no separately
|
|
2355
|
+
// billed latency mode.
|
|
2356
|
+
latencyModes: [{ id: "standard", upstream: "supported", runnable: true }],
|
|
2357
|
+
});
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
function gatewayRegistryProvider(
|
|
2361
|
+
settings: Settings,
|
|
2362
|
+
input:
|
|
2363
|
+
| { kind: "vercel-gateway-managed"; apiKey: string }
|
|
2364
|
+
| { kind: "vercel-gateway-workspace"; apiKey?: string },
|
|
2365
|
+
): RegistryProvider {
|
|
2366
|
+
const workspace = input.kind === "vercel-gateway-workspace";
|
|
2367
|
+
const models = [OPENGENI_GATEWAY_MODELS.deepseek, OPENGENI_GATEWAY_MODELS.kimi].map((model) => {
|
|
2368
|
+
const kimi = model === OPENGENI_GATEWAY_MODELS.kimi;
|
|
2369
|
+
return {
|
|
2370
|
+
id: workspace ? model.workspaceProductId : model.productId,
|
|
2371
|
+
upstreamModelId: model.upstreamModelId,
|
|
2372
|
+
label: model.label,
|
|
2373
|
+
capabilities: gatewayModelCapabilities(settings, {
|
|
2374
|
+
implicitCaching: model.implicitCaching,
|
|
2375
|
+
vision: kimi,
|
|
2376
|
+
}),
|
|
2377
|
+
contextWindowTokens: 1_000_000,
|
|
2378
|
+
effectiveContextWindowTokens: 900_000,
|
|
2379
|
+
autoCompactTokenLimit: 850_000,
|
|
2380
|
+
toolOutputTruncationTokens: settings.modelToolOutputTruncationTokens,
|
|
2381
|
+
};
|
|
2382
|
+
});
|
|
2383
|
+
return {
|
|
2384
|
+
kind: input.kind,
|
|
2385
|
+
id: workspace ? WORKSPACE_GATEWAY_PROVIDER_ID : OPENGENI_GATEWAY_PROVIDER_ID,
|
|
2386
|
+
label: workspace ? "Your Gateway" : "OpenGeni",
|
|
2387
|
+
// Responses preserves vision, reasoning items, and provider-native usage.
|
|
2388
|
+
// Model-specific compatibility stays at the reviewed request fence rather
|
|
2389
|
+
// than downgrading the whole provider wire.
|
|
2390
|
+
api: "responses",
|
|
2391
|
+
baseUrl: VERCEL_AI_GATEWAY_BASE_URL,
|
|
2392
|
+
...(input.apiKey ? { apiKey: input.apiKey } : {}),
|
|
2393
|
+
models,
|
|
2394
|
+
};
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
function configuredRegistryProviders(settings: Settings): RegistryProvider[] {
|
|
2398
|
+
const providers = parseModelProvidersJson(settings.modelProvidersJson);
|
|
2399
|
+
if (!settings.vercelAiGatewayApiKey) {
|
|
2400
|
+
return providers;
|
|
2401
|
+
}
|
|
2402
|
+
if (providers.some((provider) => provider.id === OPENGENI_GATEWAY_PROVIDER_ID)) {
|
|
2403
|
+
throw new Error(
|
|
2404
|
+
`${OPENGENI_GATEWAY_PROVIDER_ID} is reserved for OPENGENI_VERCEL_AI_GATEWAY_API_KEY`,
|
|
2405
|
+
);
|
|
2406
|
+
}
|
|
2407
|
+
return [
|
|
2408
|
+
...providers,
|
|
2409
|
+
gatewayRegistryProvider(settings, {
|
|
2410
|
+
kind: "vercel-gateway-managed",
|
|
2411
|
+
apiKey: settings.vercelAiGatewayApiKey,
|
|
2412
|
+
}),
|
|
2413
|
+
];
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
/** Static catalog overlay; it contains no concrete workspace credential. */
|
|
2417
|
+
export function withWorkspaceGatewayCatalogProvider(settings: Settings): Settings {
|
|
2418
|
+
const providers = parseModelProvidersJson(settings.modelProvidersJson);
|
|
2419
|
+
if (providers.some((provider) => provider.id === WORKSPACE_GATEWAY_PROVIDER_ID)) {
|
|
2420
|
+
return settings;
|
|
2421
|
+
}
|
|
2422
|
+
return {
|
|
2423
|
+
...settings,
|
|
2424
|
+
modelProvidersJson: JSON.stringify([
|
|
2425
|
+
...providers,
|
|
2426
|
+
gatewayRegistryProvider(settings, { kind: "vercel-gateway-workspace" }),
|
|
2427
|
+
]),
|
|
2428
|
+
};
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
/** Runtime overlay after the worker resolves the workspace's encrypted key. */
|
|
2432
|
+
export function withWorkspaceGatewayCredential(settings: Settings, apiKey: string): Settings {
|
|
2433
|
+
if (!apiKey.trim()) {
|
|
2434
|
+
throw new Error("workspace AI Gateway credential is empty");
|
|
2435
|
+
}
|
|
2436
|
+
const catalogSettings = withWorkspaceGatewayCatalogProvider(settings);
|
|
2437
|
+
const providers = parseModelProvidersJson(catalogSettings.modelProvidersJson).map((provider) =>
|
|
2438
|
+
provider.id === WORKSPACE_GATEWAY_PROVIDER_ID ? { ...provider, apiKey } : provider,
|
|
2439
|
+
);
|
|
2440
|
+
return { ...catalogSettings, modelProvidersJson: JSON.stringify(providers) };
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2205
2443
|
/** OpenAI GPT-5.6 Fast mode is 2× Standard list rates (service_tier fast/priority). */
|
|
2206
2444
|
const GPT56_FAST_BILLING_MULTIPLIER_BPS = 20_000;
|
|
2207
2445
|
|
|
@@ -2256,6 +2494,17 @@ function builtinLatencyModesForModel(modelId: string): Array<{
|
|
|
2256
2494
|
return [{ id: "standard", upstream: "unknown", runnable: true }];
|
|
2257
2495
|
}
|
|
2258
2496
|
|
|
2497
|
+
function builtinPromptCachingForModel(
|
|
2498
|
+
modelId: string,
|
|
2499
|
+
): NonNullable<ModelCapabilitiesV1["promptCaching"]> | undefined {
|
|
2500
|
+
const slug = modelId.startsWith(CODEX_MODEL_ID_PREFIX)
|
|
2501
|
+
? modelId.slice(CODEX_MODEL_ID_PREFIX.length)
|
|
2502
|
+
: modelId;
|
|
2503
|
+
return slug.startsWith("gpt-5.6-")
|
|
2504
|
+
? { upstream: "supported", runnable: true, mode: "implicit" }
|
|
2505
|
+
: undefined;
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2259
2508
|
/**
|
|
2260
2509
|
* Map OpenGeni latency mode to the provider `service_tier` wire value.
|
|
2261
2510
|
* Azure and Codex ChatGPT accept `priority`; OpenAI API accepts `fast` (alias of priority).
|
|
@@ -2312,15 +2561,23 @@ function assertLatencyModeRunnable(
|
|
|
2312
2561
|
}
|
|
2313
2562
|
|
|
2314
2563
|
function registryCredentialSource(provider: RegistryProvider): CredentialSourceV1 {
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2564
|
+
if (provider.kind === "codex-subscription") {
|
|
2565
|
+
return { kind: "connected_subscription", provider: "codex" };
|
|
2566
|
+
}
|
|
2567
|
+
if (provider.kind === "vercel-gateway-workspace") {
|
|
2568
|
+
return { kind: "workspace_connection", mechanism: "api_key" };
|
|
2569
|
+
}
|
|
2570
|
+
return { kind: "deployment", mechanism: "api_key" };
|
|
2318
2571
|
}
|
|
2319
2572
|
|
|
2320
2573
|
function registryBilling(provider: RegistryProvider): BillingAttributionV1 {
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2574
|
+
if (provider.kind === "codex-subscription") {
|
|
2575
|
+
return { upstreamPayer: "connected_subscription", metering: "external" };
|
|
2576
|
+
}
|
|
2577
|
+
if (provider.kind === "vercel-gateway-workspace") {
|
|
2578
|
+
return { upstreamPayer: "workspace", metering: "external" };
|
|
2579
|
+
}
|
|
2580
|
+
return { upstreamPayer: "deployment", metering: "opengeni_credits" };
|
|
2324
2581
|
}
|
|
2325
2582
|
|
|
2326
2583
|
function builtinCredentialSource(settings: Settings): CredentialSourceV1 {
|
|
@@ -2403,6 +2660,7 @@ function definitionVersionFor(
|
|
|
2403
2660
|
billing: model.billing,
|
|
2404
2661
|
executionLimits: model.executionLimits,
|
|
2405
2662
|
capabilities: model.capabilities,
|
|
2663
|
+
...(model.requestPolicy ? { requestPolicy: model.requestPolicy } : {}),
|
|
2406
2664
|
pricing: model.pricing ?? null,
|
|
2407
2665
|
});
|
|
2408
2666
|
return `sha256:${createHash("sha256")
|
|
@@ -2455,7 +2713,7 @@ export function configuredProviders(settings: Settings): ResolvedModelProvider[]
|
|
|
2455
2713
|
: undefined;
|
|
2456
2714
|
builtin.apiKey = settings.openaiApiKey;
|
|
2457
2715
|
}
|
|
2458
|
-
const registry =
|
|
2716
|
+
const registry = configuredRegistryProviders(settings).map(
|
|
2459
2717
|
(provider): ResolvedModelProvider => ({
|
|
2460
2718
|
id: provider.id,
|
|
2461
2719
|
label: provider.label ?? provider.id,
|
|
@@ -2498,6 +2756,11 @@ export function withCodexCatalogProvider(settings: Settings): Settings {
|
|
|
2498
2756
|
reasoningEffort: true,
|
|
2499
2757
|
hostedWebSearch: true,
|
|
2500
2758
|
}),
|
|
2759
|
+
...(builtinPromptCachingForModel(`${CODEX_MODEL_ID_PREFIX}${slug}`)
|
|
2760
|
+
? {
|
|
2761
|
+
promptCaching: builtinPromptCachingForModel(`${CODEX_MODEL_ID_PREFIX}${slug}`)!,
|
|
2762
|
+
}
|
|
2763
|
+
: {}),
|
|
2501
2764
|
latencyModes: builtinLatencyModesForModel(`${CODEX_MODEL_ID_PREFIX}${slug}`),
|
|
2502
2765
|
};
|
|
2503
2766
|
return {
|
|
@@ -2542,6 +2805,9 @@ export function policyProviderIdForModel(settings: Settings, modelId: string): s
|
|
|
2542
2805
|
if (canonicalModelId.startsWith(CODEX_MODEL_ID_PREFIX)) {
|
|
2543
2806
|
return CODEX_PROVIDER_ID;
|
|
2544
2807
|
}
|
|
2808
|
+
if (canonicalModelId.startsWith(WORKSPACE_GATEWAY_MODEL_ID_PREFIX)) {
|
|
2809
|
+
return WORKSPACE_GATEWAY_PROVIDER_ID;
|
|
2810
|
+
}
|
|
2545
2811
|
const configured = configuredModels(settings).find((model) => model.id === canonicalModelId);
|
|
2546
2812
|
return configured?.providerId ?? builtinProviderId(settings);
|
|
2547
2813
|
}
|
|
@@ -2571,9 +2837,14 @@ function finalizeConfiguredModel(
|
|
|
2571
2837
|
provider: ResolvedModelProvider,
|
|
2572
2838
|
input: Omit<ConfiguredModel, "schemaVersion" | "definitionVersion" | "executionLimits">,
|
|
2573
2839
|
): ConfiguredModel {
|
|
2840
|
+
const requestPolicy =
|
|
2841
|
+
provider.kind === "vercel-gateway-managed" || provider.kind === "vercel-gateway-workspace"
|
|
2842
|
+
? gatewayRequestPolicyForUpstreamModel(input.upstreamModelId)
|
|
2843
|
+
: undefined;
|
|
2574
2844
|
const modelWithoutVersion: Omit<ConfiguredModel, "definitionVersion"> = {
|
|
2575
2845
|
schemaVersion: 1,
|
|
2576
2846
|
...input,
|
|
2847
|
+
...(requestPolicy ? { requestPolicy } : {}),
|
|
2577
2848
|
executionLimits: resolvedExecutionLimits(settings, input),
|
|
2578
2849
|
};
|
|
2579
2850
|
return {
|
|
@@ -2645,7 +2916,7 @@ export function configuredModels(settings: Settings): ConfiguredModel[] {
|
|
|
2645
2916
|
// a codex/ id has NO codex provider injected (no active subscription) it then
|
|
2646
2917
|
// resolves to nothing and getModel fails loud with
|
|
2647
2918
|
// CodexSubscriptionUnavailableError instead of mis-routing to Azure.
|
|
2648
|
-
const parsedRegistry =
|
|
2919
|
+
const parsedRegistry = configuredRegistryProviders(settings);
|
|
2649
2920
|
const registryOwnedIds = new Set(
|
|
2650
2921
|
parsedRegistry.flatMap((provider) => provider.models.map((model) => model.id)),
|
|
2651
2922
|
);
|
|
@@ -2671,6 +2942,9 @@ export function configuredModels(settings: Settings): ConfiguredModel[] {
|
|
|
2671
2942
|
reasoningEffort: true,
|
|
2672
2943
|
hostedWebSearch: settings.webSearchEnabled,
|
|
2673
2944
|
}),
|
|
2945
|
+
...(builtinPromptCachingForModel(id)
|
|
2946
|
+
? { promptCaching: builtinPromptCachingForModel(id)! }
|
|
2947
|
+
: {}),
|
|
2674
2948
|
latencyModes: builtinLatencyModesForModel(id),
|
|
2675
2949
|
};
|
|
2676
2950
|
return finalizeConfiguredModel(settings, builtinProvider, {
|
|
@@ -2804,9 +3078,13 @@ export type ResolveTurnExecutionPolicyV1Input = {
|
|
|
2804
3078
|
};
|
|
2805
3079
|
|
|
2806
3080
|
function settingsForTurnExecutionPolicy(settings: Settings, modelId: string): Settings {
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
3081
|
+
if (settings.codexSubscriptionEnabled && modelId.startsWith(CODEX_MODEL_ID_PREFIX)) {
|
|
3082
|
+
return withCodexCatalogProvider(settings);
|
|
3083
|
+
}
|
|
3084
|
+
if (modelId.startsWith(WORKSPACE_GATEWAY_MODEL_ID_PREFIX)) {
|
|
3085
|
+
return withWorkspaceGatewayCatalogProvider(settings);
|
|
3086
|
+
}
|
|
3087
|
+
return settings;
|
|
2810
3088
|
}
|
|
2811
3089
|
|
|
2812
3090
|
/**
|
|
@@ -2924,7 +3202,7 @@ export function configuredModelPricingSchedules(
|
|
|
2924
3202
|
]),
|
|
2925
3203
|
);
|
|
2926
3204
|
const registry: Record<string, ModelPricingScheduleV1> = {};
|
|
2927
|
-
for (const provider of
|
|
3205
|
+
for (const provider of configuredRegistryProviders(settings)) {
|
|
2928
3206
|
for (const model of provider.models) {
|
|
2929
3207
|
if (model.pricing) {
|
|
2930
3208
|
registry[model.id] = normalizeModelPricingSchedule(model.pricing);
|
|
@@ -3088,6 +3366,39 @@ export function calculateModelUsageCostMicros(
|
|
|
3088
3366
|
return total;
|
|
3089
3367
|
}
|
|
3090
3368
|
|
|
3369
|
+
/**
|
|
3370
|
+
* Convert AI Gateway's exact USD inference cost to OpenGeni credit micros and
|
|
3371
|
+
* apply the configured model margin. Decimal arithmetic is integer-only so a
|
|
3372
|
+
* sub-micro provider charge cannot be lost to floating-point rounding.
|
|
3373
|
+
*/
|
|
3374
|
+
export function calculateGatewayReportedCostMicros(
|
|
3375
|
+
settings: Settings,
|
|
3376
|
+
model: string,
|
|
3377
|
+
inferenceCostUsd: string,
|
|
3378
|
+
options?: { inputTokens?: number },
|
|
3379
|
+
): number {
|
|
3380
|
+
const schedule = configuredModelPricingSchedules(settings)[model];
|
|
3381
|
+
if (!schedule) {
|
|
3382
|
+
throw new Error(`Missing model pricing for ${model}`);
|
|
3383
|
+
}
|
|
3384
|
+
const pricing = selectModelPricing(schedule, positiveInt(options?.inputTokens));
|
|
3385
|
+
const match = /^(0|[1-9]\d*)(?:\.(\d{1,18}))?$/.exec(inferenceCostUsd);
|
|
3386
|
+
if (!match) {
|
|
3387
|
+
throw new Error("Invalid AI Gateway inference cost");
|
|
3388
|
+
}
|
|
3389
|
+
const fraction = match[2] ?? "";
|
|
3390
|
+
const decimalDigits = BigInt(`${match[1]}${fraction}`);
|
|
3391
|
+
const decimalScale = 10n ** BigInt(fraction.length);
|
|
3392
|
+
const marginBps = BigInt(10_000 + (pricing.marginBps ?? 0));
|
|
3393
|
+
const numerator = decimalDigits * 1_000_000n * marginBps;
|
|
3394
|
+
const denominator = decimalScale * 10_000n;
|
|
3395
|
+
const micros = (numerator + denominator - 1n) / denominator;
|
|
3396
|
+
if (micros > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
3397
|
+
throw new Error("AI Gateway inference cost exceeds the supported billing range");
|
|
3398
|
+
}
|
|
3399
|
+
return Number(micros);
|
|
3400
|
+
}
|
|
3401
|
+
|
|
3091
3402
|
export function configuredAllowedReasoningEfforts(
|
|
3092
3403
|
settings: Settings,
|
|
3093
3404
|
): Array<z.infer<typeof ReasoningEffort>> {
|
|
@@ -4298,6 +4609,14 @@ function validateSettings(settings: Settings): void {
|
|
|
4298
4609
|
const builtinId = builtinProviderId(settings);
|
|
4299
4610
|
const providerIds = new Set<string>();
|
|
4300
4611
|
for (const provider of registryProviders) {
|
|
4612
|
+
if (
|
|
4613
|
+
provider.kind === "vercel-gateway-managed" ||
|
|
4614
|
+
provider.kind === "vercel-gateway-workspace"
|
|
4615
|
+
) {
|
|
4616
|
+
throw new Error(
|
|
4617
|
+
`OPENGENI_MODEL_PROVIDERS_JSON provider kind ${provider.kind} is reserved for the reviewed AI Gateway broker`,
|
|
4618
|
+
);
|
|
4619
|
+
}
|
|
4301
4620
|
if (provider.id === builtinId) {
|
|
4302
4621
|
throw new Error(
|
|
4303
4622
|
`OPENGENI_MODEL_PROVIDERS_JSON provider id ${provider.id} collides with the built-in provider id`,
|
|
@@ -4309,7 +4628,7 @@ function validateSettings(settings: Settings): void {
|
|
|
4309
4628
|
);
|
|
4310
4629
|
}
|
|
4311
4630
|
providerIds.add(provider.id);
|
|
4312
|
-
if (!resolveProviderApiKey(provider)) {
|
|
4631
|
+
if (provider.kind !== "codex-subscription" && !resolveProviderApiKey(provider)) {
|
|
4313
4632
|
throw new Error(
|
|
4314
4633
|
`OPENGENI_MODEL_PROVIDERS_JSON provider ${provider.id} requires a resolvable API key (set apiKey or apiKeyEnv)`,
|
|
4315
4634
|
);
|