@opengeni/config 0.9.3 → 0.10.1
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 +67 -0
- package/dist/index.js +223 -10
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +261 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/config",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
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": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@opengeni/codex": "^0.2.9",
|
|
37
|
-
"@opengeni/contracts": "^0.
|
|
37
|
+
"@opengeni/contracts": "^0.31.1",
|
|
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
|
|
@@ -480,6 +484,15 @@ const SettingsSchema = z.object({
|
|
|
480
484
|
dockerWorkspaceBaseDir: z.string().min(1).optional(),
|
|
481
485
|
modalAppName: z.string().default("opengeni-sandbox"),
|
|
482
486
|
modalImageRef: z.string().optional(),
|
|
487
|
+
// Provider-native immutable Modal image ID for the exact logical
|
|
488
|
+
// `modalImageRef`. When set, the runtime uses ModalImageSelector.fromId and
|
|
489
|
+
// never asks Modal to parse or import the registry ref. The logical ref is
|
|
490
|
+
// still persisted on the sandbox lease for provenance and conflict fencing;
|
|
491
|
+
// the Modal session envelope persists the actual image ID.
|
|
492
|
+
modalImageId: z
|
|
493
|
+
.string()
|
|
494
|
+
.regex(/^im-[A-Za-z0-9]{22}$/)
|
|
495
|
+
.optional(),
|
|
483
496
|
// Name of a Modal Secret (containing REGISTRY_USERNAME + REGISTRY_PASSWORD) used
|
|
484
497
|
// to authenticate the pull of `modalImageRef` from a PRIVATE registry. When UNSET
|
|
485
498
|
// (the default), the sandbox image is pulled UNAUTHENTICATED — i.e. it must be a
|
|
@@ -1169,6 +1182,9 @@ export const ModelCapabilitiesV1Schema = z
|
|
|
1169
1182
|
responsesWebSocket: CapabilityStateV1Schema,
|
|
1170
1183
|
realtimeAudio: CapabilityStateV1Schema,
|
|
1171
1184
|
}),
|
|
1185
|
+
promptCaching: CapabilityStateV1Schema.extend({
|
|
1186
|
+
mode: z.enum(["implicit", "automatic", "none"]),
|
|
1187
|
+
}).optional(),
|
|
1172
1188
|
latencyModes: z
|
|
1173
1189
|
.array(
|
|
1174
1190
|
z.object({
|
|
@@ -1273,7 +1289,12 @@ export type ModelProviderApi = z.infer<typeof ModelProviderApi>;
|
|
|
1273
1289
|
* "codex-subscription" providers authenticate per-request with a ChatGPT/Codex
|
|
1274
1290
|
* subscription token resolved at call time (no static key) — see @opengeni/codex.
|
|
1275
1291
|
*/
|
|
1276
|
-
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
|
+
]);
|
|
1277
1298
|
export type RegistryProviderKind = z.infer<typeof RegistryProviderKind>;
|
|
1278
1299
|
|
|
1279
1300
|
/** A single model exposed by a registry provider. */
|
|
@@ -1326,7 +1347,7 @@ const RegistryModelSchema = z
|
|
|
1326
1347
|
|
|
1327
1348
|
/** A non-built-in provider declared by the host via OPENGENI_MODEL_PROVIDERS_JSON. */
|
|
1328
1349
|
const RegistryProviderSchema = z.object({
|
|
1329
|
-
kind: RegistryProviderKind.default("api-key"),
|
|
1350
|
+
kind: RegistryProviderKind.default("api-key"),
|
|
1330
1351
|
id: z.string().min(1).regex(registryId), // stable provider id, e.g. "fireworks"
|
|
1331
1352
|
label: z.string().min(1).optional(),
|
|
1332
1353
|
api: ModelProviderApi.default("chat"),
|
|
@@ -1392,6 +1413,12 @@ export interface ConfiguredModel {
|
|
|
1392
1413
|
credentialSource: CredentialSourceV1;
|
|
1393
1414
|
billing: BillingAttributionV1;
|
|
1394
1415
|
capabilities: ModelCapabilitiesV1;
|
|
1416
|
+
requestPolicy?: {
|
|
1417
|
+
gateway: {
|
|
1418
|
+
only: [string];
|
|
1419
|
+
caching: "auto" | "none";
|
|
1420
|
+
};
|
|
1421
|
+
};
|
|
1395
1422
|
pricing?: ModelPricingScheduleV1 | undefined;
|
|
1396
1423
|
definitionVersion: string;
|
|
1397
1424
|
contextWindowTokens?: number | undefined;
|
|
@@ -1402,6 +1429,32 @@ export interface ConfiguredModel {
|
|
|
1402
1429
|
hostedWebSearch: boolean;
|
|
1403
1430
|
}
|
|
1404
1431
|
|
|
1432
|
+
export const VERCEL_AI_GATEWAY_BASE_URL = "https://ai-gateway.vercel.sh/v1" as const;
|
|
1433
|
+
export const OPENGENI_GATEWAY_PROVIDER_ID = "opengeni-gateway" as const;
|
|
1434
|
+
export const WORKSPACE_GATEWAY_PROVIDER_ID = "workspace-gateway" as const;
|
|
1435
|
+
export const WORKSPACE_GATEWAY_MODEL_ID_PREFIX = "workspace-gateway/" as const;
|
|
1436
|
+
export const VERCEL_AI_GATEWAY_CONNECTION_DOMAIN = "ai-gateway.vercel.sh" as const;
|
|
1437
|
+
export const VERCEL_AI_GATEWAY_CONNECTION_ROLE = "vercel_ai_gateway" as const;
|
|
1438
|
+
|
|
1439
|
+
export const OPENGENI_GATEWAY_MODELS = {
|
|
1440
|
+
deepseek: {
|
|
1441
|
+
productId: "deepseek-v4-flash-0731",
|
|
1442
|
+
workspaceProductId: `${WORKSPACE_GATEWAY_MODEL_ID_PREFIX}deepseek-v4-flash-0731`,
|
|
1443
|
+
upstreamModelId: "deepseek/deepseek-v4-flash-0731",
|
|
1444
|
+
label: "DeepSeek V4 Flash 0731",
|
|
1445
|
+
provider: "deepinfra",
|
|
1446
|
+
implicitCaching: true,
|
|
1447
|
+
},
|
|
1448
|
+
kimi: {
|
|
1449
|
+
productId: "kimi-k3-fast",
|
|
1450
|
+
workspaceProductId: `${WORKSPACE_GATEWAY_MODEL_ID_PREFIX}kimi-k3-fast`,
|
|
1451
|
+
upstreamModelId: "moonshotai/kimi-k3-fast",
|
|
1452
|
+
label: "Kimi K3 Fast",
|
|
1453
|
+
provider: "wafer",
|
|
1454
|
+
implicitCaching: true,
|
|
1455
|
+
},
|
|
1456
|
+
} as const;
|
|
1457
|
+
|
|
1405
1458
|
/**
|
|
1406
1459
|
* Built-in OpenGeni credit pricing schedules.
|
|
1407
1460
|
*
|
|
@@ -1476,6 +1529,27 @@ export const defaultModelPricing: Record<string, ModelPricingScheduleV1> = {
|
|
|
1476
1529
|
},
|
|
1477
1530
|
],
|
|
1478
1531
|
},
|
|
1532
|
+
// Vercel AI Gateway endpoint prices, provider-pinned in the runtime.
|
|
1533
|
+
// Snapshot: 2026-08-02. Both pinned routes returned discounted implicit
|
|
1534
|
+
// cache reads in live Gateway responses. Wafer/Kimi reported $0.45/M even
|
|
1535
|
+
// though the provider-discovery flag currently says otherwise; bill from
|
|
1536
|
+
// the response-backed rate, not that inconsistent boolean.
|
|
1537
|
+
[OPENGENI_GATEWAY_MODELS.deepseek.productId]: {
|
|
1538
|
+
default: {
|
|
1539
|
+
inputMicrosPerMillionTokens: 90_000,
|
|
1540
|
+
cachedInputMicrosPerMillionTokens: 18_000,
|
|
1541
|
+
outputMicrosPerMillionTokens: 180_000,
|
|
1542
|
+
marginBps: 2_500,
|
|
1543
|
+
},
|
|
1544
|
+
},
|
|
1545
|
+
[OPENGENI_GATEWAY_MODELS.kimi.productId]: {
|
|
1546
|
+
default: {
|
|
1547
|
+
inputMicrosPerMillionTokens: 4_500_000,
|
|
1548
|
+
cachedInputMicrosPerMillionTokens: 450_000,
|
|
1549
|
+
outputMicrosPerMillionTokens: 22_500_000,
|
|
1550
|
+
marginBps: 2_500,
|
|
1551
|
+
},
|
|
1552
|
+
},
|
|
1479
1553
|
// Fireworks AI / GLM 5.2 — the first shipped non-OpenAI registry model. A
|
|
1480
1554
|
// built-in default pricing entry makes managed billing work out of the box
|
|
1481
1555
|
// for hosts that expose this model via OPENGENI_MODEL_PROVIDERS_JSON without
|
|
@@ -1647,6 +1721,7 @@ export function getSettings(): Settings {
|
|
|
1647
1721
|
openaiBaseUrl: optional("OPENGENI_OPENAI_BASE_URL") ?? optional("OPENAI_BASE_URL"),
|
|
1648
1722
|
openaiModel: optional("OPENGENI_OPENAI_MODEL"),
|
|
1649
1723
|
openaiAllowedModels: optional("OPENGENI_OPENAI_ALLOWED_MODELS"),
|
|
1724
|
+
vercelAiGatewayApiKey: optional("OPENGENI_VERCEL_AI_GATEWAY_API_KEY"),
|
|
1650
1725
|
voiceInputMaxDurationSeconds: optional("OPENGENI_VOICE_INPUT_MAX_DURATION_SECONDS"),
|
|
1651
1726
|
voiceInputMaxSizeBytes: optional("OPENGENI_VOICE_INPUT_MAX_SIZE_BYTES"),
|
|
1652
1727
|
voiceInputProviderOrder: optional("OPENGENI_VOICE_INPUT_PROVIDER_ORDER"),
|
|
@@ -1691,6 +1766,7 @@ export function getSettings(): Settings {
|
|
|
1691
1766
|
dockerWorkspaceBaseDir: optional("OPENGENI_DOCKER_WORKSPACE_BASE_DIR"),
|
|
1692
1767
|
modalAppName: optional("OPENGENI_MODAL_APP_NAME"),
|
|
1693
1768
|
modalImageRef: optional("OPENGENI_MODAL_IMAGE_REF"),
|
|
1769
|
+
modalImageId: optional("OPENGENI_MODAL_IMAGE_ID"),
|
|
1694
1770
|
modalImageRegistrySecret: optional("OPENGENI_MODAL_IMAGE_REGISTRY_SECRET"),
|
|
1695
1771
|
modalTimeoutSeconds: optional("OPENGENI_MODAL_TIMEOUT_SECONDS"),
|
|
1696
1772
|
modalTokenId: optional("OPENGENI_MODAL_TOKEN_ID"),
|
|
@@ -2192,6 +2268,128 @@ function legacyModelCapabilities(
|
|
|
2192
2268
|
});
|
|
2193
2269
|
}
|
|
2194
2270
|
|
|
2271
|
+
export function gatewayRequestPolicyForUpstreamModel(
|
|
2272
|
+
upstreamModelId: string,
|
|
2273
|
+
): ConfiguredModel["requestPolicy"] {
|
|
2274
|
+
const model = Object.values(OPENGENI_GATEWAY_MODELS).find(
|
|
2275
|
+
(candidate) => candidate.upstreamModelId === upstreamModelId,
|
|
2276
|
+
);
|
|
2277
|
+
if (!model) {
|
|
2278
|
+
return undefined;
|
|
2279
|
+
}
|
|
2280
|
+
return {
|
|
2281
|
+
gateway: {
|
|
2282
|
+
only: [model.provider],
|
|
2283
|
+
caching: model.implicitCaching ? "auto" : "none",
|
|
2284
|
+
},
|
|
2285
|
+
};
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
function gatewayModelCapabilities(
|
|
2289
|
+
settings: Settings,
|
|
2290
|
+
input: { implicitCaching: boolean; vision: boolean },
|
|
2291
|
+
): ModelCapabilitiesV1 {
|
|
2292
|
+
const legacy = legacyModelCapabilities(settings, {
|
|
2293
|
+
reasoningEffort: true,
|
|
2294
|
+
hostedWebSearch: false,
|
|
2295
|
+
});
|
|
2296
|
+
return normalizeCapabilities({
|
|
2297
|
+
...legacy,
|
|
2298
|
+
functionCalling: { upstream: "supported", runnable: true },
|
|
2299
|
+
inputModalities: input.vision ? ["text", "image"] : ["text"],
|
|
2300
|
+
transports: {
|
|
2301
|
+
...legacy.transports,
|
|
2302
|
+
sse: { upstream: "supported", runnable: true },
|
|
2303
|
+
},
|
|
2304
|
+
promptCaching: input.implicitCaching
|
|
2305
|
+
? { upstream: "supported", runnable: true, mode: "implicit" }
|
|
2306
|
+
: { upstream: "unsupported", runnable: false, mode: "none" },
|
|
2307
|
+
// "Fast" is part of Kimi's product name, not OpenGeni's separately billed
|
|
2308
|
+
// latency mode. Both Gateway products expose only standard here.
|
|
2309
|
+
latencyModes: [{ id: "standard", upstream: "supported", runnable: true }],
|
|
2310
|
+
});
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
function gatewayRegistryProvider(
|
|
2314
|
+
settings: Settings,
|
|
2315
|
+
input:
|
|
2316
|
+
| { kind: "vercel-gateway-managed"; apiKey: string }
|
|
2317
|
+
| { kind: "vercel-gateway-workspace"; apiKey?: string },
|
|
2318
|
+
): RegistryProvider {
|
|
2319
|
+
const workspace = input.kind === "vercel-gateway-workspace";
|
|
2320
|
+
const models = [OPENGENI_GATEWAY_MODELS.deepseek, OPENGENI_GATEWAY_MODELS.kimi].map((model) => ({
|
|
2321
|
+
id: workspace ? model.workspaceProductId : model.productId,
|
|
2322
|
+
upstreamModelId: model.upstreamModelId,
|
|
2323
|
+
label: model.label,
|
|
2324
|
+
capabilities: gatewayModelCapabilities(settings, {
|
|
2325
|
+
implicitCaching: model.implicitCaching,
|
|
2326
|
+
vision: model === OPENGENI_GATEWAY_MODELS.kimi,
|
|
2327
|
+
}),
|
|
2328
|
+
contextWindowTokens: 1_000_000,
|
|
2329
|
+
effectiveContextWindowTokens: 900_000,
|
|
2330
|
+
autoCompactTokenLimit: 850_000,
|
|
2331
|
+
toolOutputTruncationTokens: settings.modelToolOutputTruncationTokens,
|
|
2332
|
+
}));
|
|
2333
|
+
return {
|
|
2334
|
+
kind: input.kind,
|
|
2335
|
+
id: workspace ? WORKSPACE_GATEWAY_PROVIDER_ID : OPENGENI_GATEWAY_PROVIDER_ID,
|
|
2336
|
+
label: workspace ? "Your Gateway" : "OpenGeni",
|
|
2337
|
+
// Responses preserves vision, reasoning items, and provider-native usage.
|
|
2338
|
+
// Model-specific compatibility stays at the reviewed request fence rather
|
|
2339
|
+
// than downgrading the whole provider wire.
|
|
2340
|
+
api: "responses",
|
|
2341
|
+
baseUrl: VERCEL_AI_GATEWAY_BASE_URL,
|
|
2342
|
+
...(input.apiKey ? { apiKey: input.apiKey } : {}),
|
|
2343
|
+
models,
|
|
2344
|
+
};
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
function configuredRegistryProviders(settings: Settings): RegistryProvider[] {
|
|
2348
|
+
const providers = parseModelProvidersJson(settings.modelProvidersJson);
|
|
2349
|
+
if (!settings.vercelAiGatewayApiKey) {
|
|
2350
|
+
return providers;
|
|
2351
|
+
}
|
|
2352
|
+
if (providers.some((provider) => provider.id === OPENGENI_GATEWAY_PROVIDER_ID)) {
|
|
2353
|
+
throw new Error(
|
|
2354
|
+
`${OPENGENI_GATEWAY_PROVIDER_ID} is reserved for OPENGENI_VERCEL_AI_GATEWAY_API_KEY`,
|
|
2355
|
+
);
|
|
2356
|
+
}
|
|
2357
|
+
return [
|
|
2358
|
+
...providers,
|
|
2359
|
+
gatewayRegistryProvider(settings, {
|
|
2360
|
+
kind: "vercel-gateway-managed",
|
|
2361
|
+
apiKey: settings.vercelAiGatewayApiKey,
|
|
2362
|
+
}),
|
|
2363
|
+
];
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2366
|
+
/** Static catalog overlay; it contains no concrete workspace credential. */
|
|
2367
|
+
export function withWorkspaceGatewayCatalogProvider(settings: Settings): Settings {
|
|
2368
|
+
const providers = parseModelProvidersJson(settings.modelProvidersJson);
|
|
2369
|
+
if (providers.some((provider) => provider.id === WORKSPACE_GATEWAY_PROVIDER_ID)) {
|
|
2370
|
+
return settings;
|
|
2371
|
+
}
|
|
2372
|
+
return {
|
|
2373
|
+
...settings,
|
|
2374
|
+
modelProvidersJson: JSON.stringify([
|
|
2375
|
+
...providers,
|
|
2376
|
+
gatewayRegistryProvider(settings, { kind: "vercel-gateway-workspace" }),
|
|
2377
|
+
]),
|
|
2378
|
+
};
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
/** Runtime overlay after the worker resolves the workspace's encrypted key. */
|
|
2382
|
+
export function withWorkspaceGatewayCredential(settings: Settings, apiKey: string): Settings {
|
|
2383
|
+
if (!apiKey.trim()) {
|
|
2384
|
+
throw new Error("workspace AI Gateway credential is empty");
|
|
2385
|
+
}
|
|
2386
|
+
const catalogSettings = withWorkspaceGatewayCatalogProvider(settings);
|
|
2387
|
+
const providers = parseModelProvidersJson(catalogSettings.modelProvidersJson).map((provider) =>
|
|
2388
|
+
provider.id === WORKSPACE_GATEWAY_PROVIDER_ID ? { ...provider, apiKey } : provider,
|
|
2389
|
+
);
|
|
2390
|
+
return { ...catalogSettings, modelProvidersJson: JSON.stringify(providers) };
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2195
2393
|
/** OpenAI GPT-5.6 Fast mode is 2× Standard list rates (service_tier fast/priority). */
|
|
2196
2394
|
const GPT56_FAST_BILLING_MULTIPLIER_BPS = 20_000;
|
|
2197
2395
|
|
|
@@ -2246,6 +2444,17 @@ function builtinLatencyModesForModel(modelId: string): Array<{
|
|
|
2246
2444
|
return [{ id: "standard", upstream: "unknown", runnable: true }];
|
|
2247
2445
|
}
|
|
2248
2446
|
|
|
2447
|
+
function builtinPromptCachingForModel(
|
|
2448
|
+
modelId: string,
|
|
2449
|
+
): NonNullable<ModelCapabilitiesV1["promptCaching"]> | undefined {
|
|
2450
|
+
const slug = modelId.startsWith(CODEX_MODEL_ID_PREFIX)
|
|
2451
|
+
? modelId.slice(CODEX_MODEL_ID_PREFIX.length)
|
|
2452
|
+
: modelId;
|
|
2453
|
+
return slug.startsWith("gpt-5.6-")
|
|
2454
|
+
? { upstream: "supported", runnable: true, mode: "implicit" }
|
|
2455
|
+
: undefined;
|
|
2456
|
+
}
|
|
2457
|
+
|
|
2249
2458
|
/**
|
|
2250
2459
|
* Map OpenGeni latency mode to the provider `service_tier` wire value.
|
|
2251
2460
|
* Azure and Codex ChatGPT accept `priority`; OpenAI API accepts `fast` (alias of priority).
|
|
@@ -2302,15 +2511,23 @@ function assertLatencyModeRunnable(
|
|
|
2302
2511
|
}
|
|
2303
2512
|
|
|
2304
2513
|
function registryCredentialSource(provider: RegistryProvider): CredentialSourceV1 {
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2514
|
+
if (provider.kind === "codex-subscription") {
|
|
2515
|
+
return { kind: "connected_subscription", provider: "codex" };
|
|
2516
|
+
}
|
|
2517
|
+
if (provider.kind === "vercel-gateway-workspace") {
|
|
2518
|
+
return { kind: "workspace_connection", mechanism: "api_key" };
|
|
2519
|
+
}
|
|
2520
|
+
return { kind: "deployment", mechanism: "api_key" };
|
|
2308
2521
|
}
|
|
2309
2522
|
|
|
2310
2523
|
function registryBilling(provider: RegistryProvider): BillingAttributionV1 {
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2524
|
+
if (provider.kind === "codex-subscription") {
|
|
2525
|
+
return { upstreamPayer: "connected_subscription", metering: "external" };
|
|
2526
|
+
}
|
|
2527
|
+
if (provider.kind === "vercel-gateway-workspace") {
|
|
2528
|
+
return { upstreamPayer: "workspace", metering: "external" };
|
|
2529
|
+
}
|
|
2530
|
+
return { upstreamPayer: "deployment", metering: "opengeni_credits" };
|
|
2314
2531
|
}
|
|
2315
2532
|
|
|
2316
2533
|
function builtinCredentialSource(settings: Settings): CredentialSourceV1 {
|
|
@@ -2393,6 +2610,7 @@ function definitionVersionFor(
|
|
|
2393
2610
|
billing: model.billing,
|
|
2394
2611
|
executionLimits: model.executionLimits,
|
|
2395
2612
|
capabilities: model.capabilities,
|
|
2613
|
+
...(model.requestPolicy ? { requestPolicy: model.requestPolicy } : {}),
|
|
2396
2614
|
pricing: model.pricing ?? null,
|
|
2397
2615
|
});
|
|
2398
2616
|
return `sha256:${createHash("sha256")
|
|
@@ -2445,7 +2663,7 @@ export function configuredProviders(settings: Settings): ResolvedModelProvider[]
|
|
|
2445
2663
|
: undefined;
|
|
2446
2664
|
builtin.apiKey = settings.openaiApiKey;
|
|
2447
2665
|
}
|
|
2448
|
-
const registry =
|
|
2666
|
+
const registry = configuredRegistryProviders(settings).map(
|
|
2449
2667
|
(provider): ResolvedModelProvider => ({
|
|
2450
2668
|
id: provider.id,
|
|
2451
2669
|
label: provider.label ?? provider.id,
|
|
@@ -2488,6 +2706,11 @@ export function withCodexCatalogProvider(settings: Settings): Settings {
|
|
|
2488
2706
|
reasoningEffort: true,
|
|
2489
2707
|
hostedWebSearch: true,
|
|
2490
2708
|
}),
|
|
2709
|
+
...(builtinPromptCachingForModel(`${CODEX_MODEL_ID_PREFIX}${slug}`)
|
|
2710
|
+
? {
|
|
2711
|
+
promptCaching: builtinPromptCachingForModel(`${CODEX_MODEL_ID_PREFIX}${slug}`)!,
|
|
2712
|
+
}
|
|
2713
|
+
: {}),
|
|
2491
2714
|
latencyModes: builtinLatencyModesForModel(`${CODEX_MODEL_ID_PREFIX}${slug}`),
|
|
2492
2715
|
};
|
|
2493
2716
|
return {
|
|
@@ -2532,6 +2755,9 @@ export function policyProviderIdForModel(settings: Settings, modelId: string): s
|
|
|
2532
2755
|
if (canonicalModelId.startsWith(CODEX_MODEL_ID_PREFIX)) {
|
|
2533
2756
|
return CODEX_PROVIDER_ID;
|
|
2534
2757
|
}
|
|
2758
|
+
if (canonicalModelId.startsWith(WORKSPACE_GATEWAY_MODEL_ID_PREFIX)) {
|
|
2759
|
+
return WORKSPACE_GATEWAY_PROVIDER_ID;
|
|
2760
|
+
}
|
|
2535
2761
|
const configured = configuredModels(settings).find((model) => model.id === canonicalModelId);
|
|
2536
2762
|
return configured?.providerId ?? builtinProviderId(settings);
|
|
2537
2763
|
}
|
|
@@ -2561,9 +2787,14 @@ function finalizeConfiguredModel(
|
|
|
2561
2787
|
provider: ResolvedModelProvider,
|
|
2562
2788
|
input: Omit<ConfiguredModel, "schemaVersion" | "definitionVersion" | "executionLimits">,
|
|
2563
2789
|
): ConfiguredModel {
|
|
2790
|
+
const requestPolicy =
|
|
2791
|
+
provider.kind === "vercel-gateway-managed" || provider.kind === "vercel-gateway-workspace"
|
|
2792
|
+
? gatewayRequestPolicyForUpstreamModel(input.upstreamModelId)
|
|
2793
|
+
: undefined;
|
|
2564
2794
|
const modelWithoutVersion: Omit<ConfiguredModel, "definitionVersion"> = {
|
|
2565
2795
|
schemaVersion: 1,
|
|
2566
2796
|
...input,
|
|
2797
|
+
...(requestPolicy ? { requestPolicy } : {}),
|
|
2567
2798
|
executionLimits: resolvedExecutionLimits(settings, input),
|
|
2568
2799
|
};
|
|
2569
2800
|
return {
|
|
@@ -2635,7 +2866,7 @@ export function configuredModels(settings: Settings): ConfiguredModel[] {
|
|
|
2635
2866
|
// a codex/ id has NO codex provider injected (no active subscription) it then
|
|
2636
2867
|
// resolves to nothing and getModel fails loud with
|
|
2637
2868
|
// CodexSubscriptionUnavailableError instead of mis-routing to Azure.
|
|
2638
|
-
const parsedRegistry =
|
|
2869
|
+
const parsedRegistry = configuredRegistryProviders(settings);
|
|
2639
2870
|
const registryOwnedIds = new Set(
|
|
2640
2871
|
parsedRegistry.flatMap((provider) => provider.models.map((model) => model.id)),
|
|
2641
2872
|
);
|
|
@@ -2661,6 +2892,9 @@ export function configuredModels(settings: Settings): ConfiguredModel[] {
|
|
|
2661
2892
|
reasoningEffort: true,
|
|
2662
2893
|
hostedWebSearch: settings.webSearchEnabled,
|
|
2663
2894
|
}),
|
|
2895
|
+
...(builtinPromptCachingForModel(id)
|
|
2896
|
+
? { promptCaching: builtinPromptCachingForModel(id)! }
|
|
2897
|
+
: {}),
|
|
2664
2898
|
latencyModes: builtinLatencyModesForModel(id),
|
|
2665
2899
|
};
|
|
2666
2900
|
return finalizeConfiguredModel(settings, builtinProvider, {
|
|
@@ -2794,9 +3028,13 @@ export type ResolveTurnExecutionPolicyV1Input = {
|
|
|
2794
3028
|
};
|
|
2795
3029
|
|
|
2796
3030
|
function settingsForTurnExecutionPolicy(settings: Settings, modelId: string): Settings {
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
3031
|
+
if (settings.codexSubscriptionEnabled && modelId.startsWith(CODEX_MODEL_ID_PREFIX)) {
|
|
3032
|
+
return withCodexCatalogProvider(settings);
|
|
3033
|
+
}
|
|
3034
|
+
if (modelId.startsWith(WORKSPACE_GATEWAY_MODEL_ID_PREFIX)) {
|
|
3035
|
+
return withWorkspaceGatewayCatalogProvider(settings);
|
|
3036
|
+
}
|
|
3037
|
+
return settings;
|
|
2800
3038
|
}
|
|
2801
3039
|
|
|
2802
3040
|
/**
|
|
@@ -2914,7 +3152,7 @@ export function configuredModelPricingSchedules(
|
|
|
2914
3152
|
]),
|
|
2915
3153
|
);
|
|
2916
3154
|
const registry: Record<string, ModelPricingScheduleV1> = {};
|
|
2917
|
-
for (const provider of
|
|
3155
|
+
for (const provider of configuredRegistryProviders(settings)) {
|
|
2918
3156
|
for (const model of provider.models) {
|
|
2919
3157
|
if (model.pricing) {
|
|
2920
3158
|
registry[model.id] = normalizeModelPricingSchedule(model.pricing);
|
|
@@ -4288,6 +4526,14 @@ function validateSettings(settings: Settings): void {
|
|
|
4288
4526
|
const builtinId = builtinProviderId(settings);
|
|
4289
4527
|
const providerIds = new Set<string>();
|
|
4290
4528
|
for (const provider of registryProviders) {
|
|
4529
|
+
if (
|
|
4530
|
+
provider.kind === "vercel-gateway-managed" ||
|
|
4531
|
+
provider.kind === "vercel-gateway-workspace"
|
|
4532
|
+
) {
|
|
4533
|
+
throw new Error(
|
|
4534
|
+
`OPENGENI_MODEL_PROVIDERS_JSON provider kind ${provider.kind} is reserved for the reviewed AI Gateway broker`,
|
|
4535
|
+
);
|
|
4536
|
+
}
|
|
4291
4537
|
if (provider.id === builtinId) {
|
|
4292
4538
|
throw new Error(
|
|
4293
4539
|
`OPENGENI_MODEL_PROVIDERS_JSON provider id ${provider.id} collides with the built-in provider id`,
|
|
@@ -4299,7 +4545,7 @@ function validateSettings(settings: Settings): void {
|
|
|
4299
4545
|
);
|
|
4300
4546
|
}
|
|
4301
4547
|
providerIds.add(provider.id);
|
|
4302
|
-
if (!resolveProviderApiKey(provider)) {
|
|
4548
|
+
if (provider.kind !== "codex-subscription" && !resolveProviderApiKey(provider)) {
|
|
4303
4549
|
throw new Error(
|
|
4304
4550
|
`OPENGENI_MODEL_PROVIDERS_JSON provider ${provider.id} requires a resolvable API key (set apiKey or apiKeyEnv)`,
|
|
4305
4551
|
);
|