@opengeni/config 0.22.5 → 1.0.0-canary.0
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 +724 -23
- package/dist/index.js +1048 -134
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +1330 -165
package/src/index.ts
CHANGED
|
@@ -50,6 +50,11 @@ import { z } from "zod";
|
|
|
50
50
|
|
|
51
51
|
const envName = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
52
52
|
const registryId = /^[A-Za-z0-9_-]+$/;
|
|
53
|
+
export const DEFAULT_OPENROUTER_MODEL_ID =
|
|
54
|
+
"openrouter/nvidia/nemotron-3-super-120b-a12b:free" as const;
|
|
55
|
+
export const DEFAULT_MODEL_COST_POLICY_JSON = JSON.stringify({
|
|
56
|
+
[DEFAULT_OPENROUTER_MODEL_ID]: "free",
|
|
57
|
+
});
|
|
53
58
|
|
|
54
59
|
// Archive capture claims are also the admission/teardown fence around a
|
|
55
60
|
// provider snapshot. Keep a real settlement window after the provider request;
|
|
@@ -238,10 +243,18 @@ export const McpServerConnectionRefSchema = z
|
|
|
238
243
|
}
|
|
239
244
|
})
|
|
240
245
|
.optional(),
|
|
246
|
+
authoritySource: z.literal("host").optional(),
|
|
241
247
|
subjectScope: z.enum(["workspace", "subject"]).optional(),
|
|
242
248
|
})
|
|
243
249
|
.strict()
|
|
244
250
|
.superRefine((reference, context) => {
|
|
251
|
+
if (reference.authoritySource === "host" && !reference.connectionId) {
|
|
252
|
+
context.addIssue({
|
|
253
|
+
code: "custom",
|
|
254
|
+
message: "host authority requires connectionId",
|
|
255
|
+
path: ["connectionId"],
|
|
256
|
+
});
|
|
257
|
+
}
|
|
245
258
|
if (!reference.selectedResources) return;
|
|
246
259
|
if (!reference.connectionId) {
|
|
247
260
|
context.addIssue({
|
|
@@ -260,6 +273,10 @@ export const McpServerConnectionRefSchema = z
|
|
|
260
273
|
});
|
|
261
274
|
export type McpServerConnectionRef = z.infer<typeof McpServerConnectionRefSchema>;
|
|
262
275
|
|
|
276
|
+
/** Public, digest-pinned desktop image used by Modal unless the operator overrides it. */
|
|
277
|
+
export const DEFAULT_MODAL_IMAGE_REF =
|
|
278
|
+
"opengenipublicneuacr.azurecr.io/opengeni-desktop@sha256:c3bd17b8841de1bff9bb2777aad422cf8c75de78e2c30f9ac81d0cd6810a1b78";
|
|
279
|
+
|
|
263
280
|
const SettingsSchema = z.object({
|
|
264
281
|
serviceName: z.string().default("opengeni"),
|
|
265
282
|
environment: z.string().default("local"),
|
|
@@ -324,6 +341,12 @@ const SettingsSchema = z.object({
|
|
|
324
341
|
.regex(/^G-[A-Z0-9]+$/u)
|
|
325
342
|
.optional(),
|
|
326
343
|
publicBaseUrl: z.string().url().optional(),
|
|
344
|
+
// Standards-based OAuth authorization server for external workspace MCP
|
|
345
|
+
// clients. Opt-in because it creates a new public authentication surface.
|
|
346
|
+
mcpOauthEnabled: EnvBoolean.default(false),
|
|
347
|
+
// Forwarded client addresses are ignored by default. Operators may trust an
|
|
348
|
+
// exact number of proxy hops only when direct access to the API is blocked.
|
|
349
|
+
mcpOauthTrustedProxyHops: z.coerce.number().int().min(0).max(16).default(0),
|
|
327
350
|
// Browser origin when the web app and API use separate origins in local
|
|
328
351
|
// development. Production normally leaves this unset and uses publicBaseUrl.
|
|
329
352
|
webBaseUrl: z.string().url().optional(),
|
|
@@ -502,6 +525,13 @@ const SettingsSchema = z.object({
|
|
|
502
525
|
// into @opengeni/db once at boot.
|
|
503
526
|
// Env: OPENGENI_CHILD_LIFECYCLE_NOTICES_ENABLED.
|
|
504
527
|
childLifecycleNoticesEnabled: EnvBoolean.default(false),
|
|
528
|
+
// Explicit host-owned MCP connection authority is a rolling protocol
|
|
529
|
+
// activation. Keep it off while any API, worker, or browser bundle predates
|
|
530
|
+
// the authority discriminator; enable it only after the whole fleet runs an
|
|
531
|
+
// image that understands host refs. Legacy markerless non-UUID refs remain a
|
|
532
|
+
// separate compatibility lane for already-persisted embedding integrations.
|
|
533
|
+
// Env: OPENGENI_HOST_MCP_AUTHORITY_SOURCE_ADMISSION_ENABLED.
|
|
534
|
+
hostMcpAuthoritySourceAdmissionEnabled: EnvBoolean.default(false),
|
|
505
535
|
// Per-channel and per-DM Slack workspace routing. Default ON. A channel does
|
|
506
536
|
// not count a personal workspace as a candidate, so an organization with one
|
|
507
537
|
// shared workspace resolves it as the sole candidate and never asks; the
|
|
@@ -684,6 +714,24 @@ const SettingsSchema = z.object({
|
|
|
684
714
|
// keep subscription model routing while disabling Codex voice input.
|
|
685
715
|
voiceInputCodexExperimentalEnabled: EnvBoolean.default(false),
|
|
686
716
|
modelPricingJson: z.string().default("{}"),
|
|
717
|
+
// Supported-model membership source. Database mode is resolved by the async
|
|
718
|
+
// core overlay; getSettings remains synchronous and env-only.
|
|
719
|
+
modelCatalogSource: z.enum(["code", "database"]).default("code"),
|
|
720
|
+
// Deployment-owned workspace-facing price policy. This is deliberately
|
|
721
|
+
// separate from catalog membership and upstream credential ownership.
|
|
722
|
+
// Shape: { "product/model-id": "free" | "credits" }.
|
|
723
|
+
modelCostPolicyJson: z.string().default("{}"),
|
|
724
|
+
// Optional per-product agent guidance. Database mode replaces this with the
|
|
725
|
+
// singleton document's validated modelNotes map.
|
|
726
|
+
modelNotesJson: z.string().default("{}"),
|
|
727
|
+
// Managed OpenRouter credential. The curated model table is injected in
|
|
728
|
+
// code/catalog-document resolution and never read from host provider JSON.
|
|
729
|
+
openrouterApiKey: z.string().optional(),
|
|
730
|
+
// Internal, secret-free catalog overlays populated only by
|
|
731
|
+
// applyModelCatalogDocument. They intentionally have no OPENGENI_* env
|
|
732
|
+
// binding so database mode cannot be bypassed with a second source.
|
|
733
|
+
resolvedGatewayModelsJson: z.string().optional(),
|
|
734
|
+
resolvedOpenRouterModelsJson: z.string().optional(),
|
|
687
735
|
// Extra (non-built-in) model providers, declared by the host as a JSON
|
|
688
736
|
// provider registry. Each entry carries its own base URL, API key, wire API
|
|
689
737
|
// ("responses" | "chat") and the models it exposes. The models a client may
|
|
@@ -726,11 +774,6 @@ const SettingsSchema = z.object({
|
|
|
726
774
|
// the Codex rollout so an emergency Codex opt-out cannot disable every model.
|
|
727
775
|
// OPENGENI_LAZY_TOOL_SEARCH_ENABLED
|
|
728
776
|
lazyToolSearchEnabled: EnvBoolean.default(true),
|
|
729
|
-
// credential allocator atomic, workspace-local credential allocation. Default OFF is a
|
|
730
|
-
// deliberate rolling-deploy fence: migrate + roll every worker first, then
|
|
731
|
-
// enable. Turning it off restores the legacy sticky selector without a schema
|
|
732
|
-
// rollback; the additive lease table/cursor columns become inert.
|
|
733
|
-
codexCredentialLeasingEnabled: EnvBoolean.default(false),
|
|
734
777
|
// Decision-observability fence. When enabled, the worker emits one
|
|
735
778
|
// bounded, metadata-only adaptive-policy replay record alongside the unchanged
|
|
736
779
|
// sticky-sharded decision. It never changes placement/admission/failover.
|
|
@@ -1151,6 +1194,18 @@ const SettingsSchema = z.object({
|
|
|
1151
1194
|
.positive()
|
|
1152
1195
|
.max(SANDBOX_SNAPSHOT_MAX_TIMEOUT_MS)
|
|
1153
1196
|
.default(60_000),
|
|
1197
|
+
// A zero-holder drain may need substantially longer than a best-effort
|
|
1198
|
+
// mid-turn/turn-end snapshot for a very large workspace. Keep that provider
|
|
1199
|
+
// budget independent so increasing drain recovery headroom cannot pin an
|
|
1200
|
+
// ordinary turn finalizer for the same duration. Unset preserves the legacy
|
|
1201
|
+
// single-budget behavior. Knob:
|
|
1202
|
+
// OPENGENI_SANDBOX_DRAIN_SNAPSHOT_TIMEOUT_MS.
|
|
1203
|
+
sandboxDrainSnapshotTimeoutMs: z.coerce
|
|
1204
|
+
.number()
|
|
1205
|
+
.int()
|
|
1206
|
+
.positive()
|
|
1207
|
+
.max(SANDBOX_SNAPSHOT_MAX_TIMEOUT_MS)
|
|
1208
|
+
.optional(),
|
|
1154
1209
|
// Begin a controlled snapshot/quiesce/drain/rematerialize transition this far
|
|
1155
1210
|
// ahead of a finite provider deadline. Modal's 24h creation clock cannot be
|
|
1156
1211
|
// extended; the logical sandbox outlives it by moving to one successor box.
|
|
@@ -1274,6 +1329,15 @@ const SettingsSchema = z.object({
|
|
|
1274
1329
|
// Rolling browser login-slot compatibility. Repository/deployment default is
|
|
1275
1330
|
// deliberately legacy; changing to broker is an operator-authorized rollout.
|
|
1276
1331
|
managedAuthSessionSetMode: z.enum(["legacy", "dual", "broker"]).default("legacy"),
|
|
1332
|
+
// Query transport is an explicit second-stage rollout. A pre-compatibility
|
|
1333
|
+
// web image understands only fragment bearers, so API replicas must keep
|
|
1334
|
+
// generating fragment links until the compatible web fleet has converged.
|
|
1335
|
+
organizationUserSetupEmailTokenTransport: z.enum(["fragment", "query"]).default("fragment"),
|
|
1336
|
+
// Query-bearing setup links may appear in controller/edge error logs even
|
|
1337
|
+
// when access logs and request tracing are disabled. This explicit operator
|
|
1338
|
+
// confirmation keeps query transport fail closed until that separate sink is
|
|
1339
|
+
// proven sanitized.
|
|
1340
|
+
organizationUserSetupQueryEdgeSanitizationConfirmed: EnvBoolean.default(false),
|
|
1277
1341
|
resendApiKey: z.string().optional(),
|
|
1278
1342
|
emailFrom: z.string().default("OpenGeni <auth@mail.opengeni.ai>"),
|
|
1279
1343
|
stripeSecretKey: z.string().optional(),
|
|
@@ -1575,6 +1639,7 @@ export type TemporalConnectionOptions = {
|
|
|
1575
1639
|
export type ModelPricing = {
|
|
1576
1640
|
inputMicrosPerMillionTokens: number;
|
|
1577
1641
|
cachedInputMicrosPerMillionTokens?: number | undefined;
|
|
1642
|
+
cacheWriteMicrosPerMillionTokens?: number | undefined;
|
|
1578
1643
|
outputMicrosPerMillionTokens: number;
|
|
1579
1644
|
marginBps?: number | undefined;
|
|
1580
1645
|
};
|
|
@@ -1608,6 +1673,7 @@ export type EntitlementsConfig = Entitlements;
|
|
|
1608
1673
|
const ModelPricingSchema = z.object({
|
|
1609
1674
|
inputMicrosPerMillionTokens: z.number().int().nonnegative(),
|
|
1610
1675
|
cachedInputMicrosPerMillionTokens: z.number().int().nonnegative().optional(),
|
|
1676
|
+
cacheWriteMicrosPerMillionTokens: z.number().int().nonnegative().optional(),
|
|
1611
1677
|
outputMicrosPerMillionTokens: z.number().int().nonnegative(),
|
|
1612
1678
|
marginBps: z.number().int().min(0).max(100_000).optional(),
|
|
1613
1679
|
});
|
|
@@ -1772,10 +1838,11 @@ export type ModelExecutionLimitsV1 = {
|
|
|
1772
1838
|
export type CredentialSourceV1 =
|
|
1773
1839
|
| { kind: "deployment"; mechanism: "api_key" | "azure_ad_bearer" | "none" }
|
|
1774
1840
|
| { kind: "connected_subscription"; provider: "codex" | "xai" }
|
|
1775
|
-
| { kind: "workspace_connection"; mechanism: "api_key" }
|
|
1841
|
+
| { kind: "workspace_connection"; mechanism: "api_key" }
|
|
1842
|
+
| { kind: "organization_connection"; mechanism: "api_key" };
|
|
1776
1843
|
|
|
1777
1844
|
export type BillingAttributionV1 = {
|
|
1778
|
-
upstreamPayer: "deployment" | "workspace" | "connected_subscription";
|
|
1845
|
+
upstreamPayer: "deployment" | "workspace" | "organization" | "connected_subscription";
|
|
1779
1846
|
metering: "opengeni_credits" | "external";
|
|
1780
1847
|
};
|
|
1781
1848
|
|
|
@@ -1810,6 +1877,9 @@ export const RegistryProviderKind = z.enum([
|
|
|
1810
1877
|
"xai-subscription",
|
|
1811
1878
|
"vercel-gateway-managed",
|
|
1812
1879
|
"vercel-gateway-workspace",
|
|
1880
|
+
"vercel-gateway-organization",
|
|
1881
|
+
"openrouter-workspace",
|
|
1882
|
+
"openrouter-organization",
|
|
1813
1883
|
]);
|
|
1814
1884
|
export type RegistryProviderKind = z.infer<typeof RegistryProviderKind>;
|
|
1815
1885
|
|
|
@@ -1932,6 +2002,350 @@ const RegistryProviderSchema = z
|
|
|
1932
2002
|
});
|
|
1933
2003
|
export type RegistryProvider = z.infer<typeof RegistryProviderSchema>;
|
|
1934
2004
|
|
|
2005
|
+
export const OPENGENI_GATEWAY_PROVIDER_ID = "opengeni-gateway" as const;
|
|
2006
|
+
export const WORKSPACE_GATEWAY_PROVIDER_ID = "workspace-gateway" as const;
|
|
2007
|
+
export const WORKSPACE_GATEWAY_MODEL_ID_PREFIX = "workspace-gateway/" as const;
|
|
2008
|
+
export const ORGANIZATION_GATEWAY_PROVIDER_ID = "organization-gateway" as const;
|
|
2009
|
+
export const ORGANIZATION_GATEWAY_MODEL_ID_PREFIX = "organization-gateway/" as const;
|
|
2010
|
+
export const OPENROUTER_PROVIDER_ID = "openrouter" as const;
|
|
2011
|
+
export const OPENROUTER_MODEL_ID_PREFIX = "openrouter/" as const;
|
|
2012
|
+
export const WORKSPACE_OPENROUTER_PROVIDER_ID = "workspace-openrouter" as const;
|
|
2013
|
+
export const WORKSPACE_OPENROUTER_MODEL_ID_PREFIX = "workspace-openrouter/" as const;
|
|
2014
|
+
export const ORGANIZATION_OPENROUTER_PROVIDER_ID = "organization-openrouter" as const;
|
|
2015
|
+
export const ORGANIZATION_OPENROUTER_MODEL_ID_PREFIX = "organization-openrouter/" as const;
|
|
2016
|
+
export const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1" as const;
|
|
2017
|
+
|
|
2018
|
+
const RESERVED_MODEL_PROVIDER_IDS = new Set<string>([
|
|
2019
|
+
"openai",
|
|
2020
|
+
"azure",
|
|
2021
|
+
CODEX_PROVIDER_ID,
|
|
2022
|
+
XAI_SUBSCRIPTION_PROVIDER_ID,
|
|
2023
|
+
OPENGENI_GATEWAY_PROVIDER_ID,
|
|
2024
|
+
WORKSPACE_GATEWAY_PROVIDER_ID,
|
|
2025
|
+
ORGANIZATION_GATEWAY_PROVIDER_ID,
|
|
2026
|
+
OPENROUTER_PROVIDER_ID,
|
|
2027
|
+
WORKSPACE_OPENROUTER_PROVIDER_ID,
|
|
2028
|
+
ORGANIZATION_OPENROUTER_PROVIDER_ID,
|
|
2029
|
+
]);
|
|
2030
|
+
|
|
2031
|
+
export const ModelCostClass = z.enum(["free", "credits"]);
|
|
2032
|
+
export type ModelCostClass = z.infer<typeof ModelCostClass>;
|
|
2033
|
+
|
|
2034
|
+
export const ConfiguredModelCostClass = z.enum([
|
|
2035
|
+
"free",
|
|
2036
|
+
"credits",
|
|
2037
|
+
"subscription",
|
|
2038
|
+
"workspace",
|
|
2039
|
+
"organization",
|
|
2040
|
+
]);
|
|
2041
|
+
export type ConfiguredModelCostClass = z.infer<typeof ConfiguredModelCostClass>;
|
|
2042
|
+
|
|
2043
|
+
const ModelNote = z
|
|
2044
|
+
.string()
|
|
2045
|
+
.max(500)
|
|
2046
|
+
.refine((value) => !/[\r\n|]/u.test(value), {
|
|
2047
|
+
message: "model notes must not contain newlines or the | field separator",
|
|
2048
|
+
});
|
|
2049
|
+
|
|
2050
|
+
export function parseModelCostPolicyJson(raw: string): Record<string, ModelCostClass> {
|
|
2051
|
+
let parsed: unknown;
|
|
2052
|
+
try {
|
|
2053
|
+
parsed = JSON.parse(raw);
|
|
2054
|
+
} catch (error) {
|
|
2055
|
+
throw new Error(
|
|
2056
|
+
`OPENGENI_MODEL_COST_POLICY_JSON must be valid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
2057
|
+
{ cause: error },
|
|
2058
|
+
);
|
|
2059
|
+
}
|
|
2060
|
+
return z.record(z.string().min(1), ModelCostClass).parse(parsed);
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
export function parseModelNotesJson(raw: string): Record<string, string> {
|
|
2064
|
+
let parsed: unknown;
|
|
2065
|
+
try {
|
|
2066
|
+
parsed = JSON.parse(raw);
|
|
2067
|
+
} catch (error) {
|
|
2068
|
+
throw new Error(
|
|
2069
|
+
`OPENGENI_MODEL_NOTES_JSON must be valid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
2070
|
+
{ cause: error },
|
|
2071
|
+
);
|
|
2072
|
+
}
|
|
2073
|
+
return z.record(z.string().min(1), ModelNote).parse(parsed);
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
export function configuredModelNotes(
|
|
2077
|
+
settings: Pick<Settings, "modelNotesJson">,
|
|
2078
|
+
): Record<string, string> {
|
|
2079
|
+
return parseModelNotesJson(settings.modelNotesJson);
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
export const GatewayCatalogModel = z
|
|
2083
|
+
.object({
|
|
2084
|
+
productId: z.string().min(1),
|
|
2085
|
+
workspaceProductId: z.string().min(1).startsWith(WORKSPACE_GATEWAY_MODEL_ID_PREFIX),
|
|
2086
|
+
upstreamModelId: z.string().min(1),
|
|
2087
|
+
label: z.string().min(1),
|
|
2088
|
+
shortLabel: z.string().min(1).max(64).optional(),
|
|
2089
|
+
providers: z.array(z.string().min(1)).min(1),
|
|
2090
|
+
implicitCaching: z.boolean().default(false),
|
|
2091
|
+
vision: z.boolean().default(false),
|
|
2092
|
+
inputFileMediaTypes: z.array(z.string().min(1)).default([]),
|
|
2093
|
+
contextWindowTokens: z.number().int().positive().default(1_000_000),
|
|
2094
|
+
effectiveContextWindowTokens: z.number().int().positive().default(900_000),
|
|
2095
|
+
autoCompactTokenLimit: z.number().int().positive().default(850_000),
|
|
2096
|
+
pricing: z.union([ModelPricingSchema, ModelPricingScheduleSchema]).optional(),
|
|
2097
|
+
credentialSource: z.never().optional(),
|
|
2098
|
+
billing: z.never().optional(),
|
|
2099
|
+
apiKey: z.never().optional(),
|
|
2100
|
+
})
|
|
2101
|
+
.strict();
|
|
2102
|
+
export type GatewayCatalogModel = z.infer<typeof GatewayCatalogModel>;
|
|
2103
|
+
|
|
2104
|
+
export const OpenRouterCatalogModel = z
|
|
2105
|
+
.object({
|
|
2106
|
+
upstreamModelId: z.string().min(1).endsWith(":free"),
|
|
2107
|
+
label: z.string().min(1),
|
|
2108
|
+
shortLabel: z.string().min(1).max(64).optional(),
|
|
2109
|
+
aliases: z.array(z.string().min(1)).default([]),
|
|
2110
|
+
capabilities: ModelCapabilitiesV1Schema,
|
|
2111
|
+
contextWindowTokens: z.number().int().positive().optional(),
|
|
2112
|
+
effectiveContextWindowTokens: z.number().int().positive().optional(),
|
|
2113
|
+
autoCompactTokenLimit: z.number().int().positive().optional(),
|
|
2114
|
+
toolOutputTruncationTokens: z.number().int().positive().optional(),
|
|
2115
|
+
credentialSource: z.never().optional(),
|
|
2116
|
+
billing: z.never().optional(),
|
|
2117
|
+
pricing: z.never().optional(),
|
|
2118
|
+
apiKey: z.never().optional(),
|
|
2119
|
+
})
|
|
2120
|
+
.strict();
|
|
2121
|
+
export type OpenRouterCatalogModel = z.infer<typeof OpenRouterCatalogModel>;
|
|
2122
|
+
|
|
2123
|
+
const DeploymentRegistryBaseUrl = z
|
|
2124
|
+
.string()
|
|
2125
|
+
.url()
|
|
2126
|
+
.superRefine((value, context) => {
|
|
2127
|
+
const url = new URL(value);
|
|
2128
|
+
if (url.username || url.password) {
|
|
2129
|
+
context.addIssue({
|
|
2130
|
+
code: "custom",
|
|
2131
|
+
message: "database catalog provider baseUrl must not contain userinfo",
|
|
2132
|
+
});
|
|
2133
|
+
}
|
|
2134
|
+
if (url.search) {
|
|
2135
|
+
context.addIssue({
|
|
2136
|
+
code: "custom",
|
|
2137
|
+
message: "database catalog provider baseUrl must not contain a query",
|
|
2138
|
+
});
|
|
2139
|
+
}
|
|
2140
|
+
if (url.hash) {
|
|
2141
|
+
context.addIssue({
|
|
2142
|
+
code: "custom",
|
|
2143
|
+
message: "database catalog provider baseUrl must not contain a fragment",
|
|
2144
|
+
});
|
|
2145
|
+
}
|
|
2146
|
+
});
|
|
2147
|
+
|
|
2148
|
+
const DeploymentRegistryProviderKind = z.enum(["api-key", "anonymous"]);
|
|
2149
|
+
|
|
2150
|
+
const DeploymentRegistryModelSchema = RegistryModelSchema.safeExtend({
|
|
2151
|
+
pricing: z.never().optional(),
|
|
2152
|
+
}).strict();
|
|
2153
|
+
|
|
2154
|
+
const DeploymentRegistryProviderSchema = RegistryProviderSchema.safeExtend({
|
|
2155
|
+
kind: DeploymentRegistryProviderKind.default("api-key"),
|
|
2156
|
+
baseUrl: DeploymentRegistryBaseUrl,
|
|
2157
|
+
models: z.array(DeploymentRegistryModelSchema).min(1),
|
|
2158
|
+
apiKey: z.never().optional(),
|
|
2159
|
+
apiKeyEnv: z.never().optional(),
|
|
2160
|
+
defaultHeaders: z.never().optional(),
|
|
2161
|
+
defaultQuery: z.never().optional(),
|
|
2162
|
+
publicDefaultHeaderNames: z.never().optional(),
|
|
2163
|
+
publicDefaultQueryNames: z.never().optional(),
|
|
2164
|
+
}).strict();
|
|
2165
|
+
|
|
2166
|
+
const DeploymentGatewayCatalogModelSchema = GatewayCatalogModel.safeExtend({
|
|
2167
|
+
pricing: z.never().optional(),
|
|
2168
|
+
}).strict();
|
|
2169
|
+
|
|
2170
|
+
export const ModelCatalogDocument = z
|
|
2171
|
+
.object({
|
|
2172
|
+
schemaVersion: z.literal(1),
|
|
2173
|
+
/** Canonical deployment default. Omission preserves the V1 first-built-in
|
|
2174
|
+
* fallback for existing documents; operators should set this explicitly
|
|
2175
|
+
* when cutting over a registry or connected-subscription default. */
|
|
2176
|
+
defaultModel: z.string().min(1).optional(),
|
|
2177
|
+
builtInModels: z.array(z.string().min(1)).min(1),
|
|
2178
|
+
registryProviders: z.array(DeploymentRegistryProviderSchema).default([]),
|
|
2179
|
+
gatewayModels: z.array(DeploymentGatewayCatalogModelSchema).default([]),
|
|
2180
|
+
openrouterModels: z.array(OpenRouterCatalogModel).default([]),
|
|
2181
|
+
modelNotes: z.record(z.string().min(1), ModelNote).default({}),
|
|
2182
|
+
billing: z.never().optional(),
|
|
2183
|
+
enabled: z.never().optional(),
|
|
2184
|
+
apiKey: z.never().optional(),
|
|
2185
|
+
bands: z.never().optional(),
|
|
2186
|
+
})
|
|
2187
|
+
.strict()
|
|
2188
|
+
.superRefine((document, context) => {
|
|
2189
|
+
const productIds = new Set<string>();
|
|
2190
|
+
const providerIds = new Set<string>();
|
|
2191
|
+
const gatewayUpstreamIds = new Set<string>();
|
|
2192
|
+
const add = (id: string, path: Array<string | number>): void => {
|
|
2193
|
+
if (/[\u000A\u000D|]/u.test(id)) {
|
|
2194
|
+
context.addIssue({
|
|
2195
|
+
code: "custom",
|
|
2196
|
+
path,
|
|
2197
|
+
message: "catalog product ids must not contain newlines or the | field separator",
|
|
2198
|
+
});
|
|
2199
|
+
}
|
|
2200
|
+
if (productIds.has(id)) {
|
|
2201
|
+
context.addIssue({
|
|
2202
|
+
code: "custom",
|
|
2203
|
+
path,
|
|
2204
|
+
message: `duplicate product id ${id}`,
|
|
2205
|
+
});
|
|
2206
|
+
}
|
|
2207
|
+
productIds.add(id);
|
|
2208
|
+
};
|
|
2209
|
+
document.builtInModels.forEach((id, index) => add(id, ["builtInModels", index]));
|
|
2210
|
+
document.registryProviders.forEach((provider, providerIndex) => {
|
|
2211
|
+
if (RESERVED_MODEL_PROVIDER_IDS.has(provider.id)) {
|
|
2212
|
+
context.addIssue({
|
|
2213
|
+
code: "custom",
|
|
2214
|
+
path: ["registryProviders", providerIndex, "id"],
|
|
2215
|
+
message: `provider id ${provider.id} is reserved for a reviewed OpenGeni provider`,
|
|
2216
|
+
});
|
|
2217
|
+
}
|
|
2218
|
+
if (providerIds.has(provider.id)) {
|
|
2219
|
+
context.addIssue({
|
|
2220
|
+
code: "custom",
|
|
2221
|
+
path: ["registryProviders", providerIndex, "id"],
|
|
2222
|
+
message: `duplicate provider id ${provider.id}`,
|
|
2223
|
+
});
|
|
2224
|
+
}
|
|
2225
|
+
providerIds.add(provider.id);
|
|
2226
|
+
provider.models.forEach((model, modelIndex) =>
|
|
2227
|
+
add(model.id, ["registryProviders", providerIndex, "models", modelIndex, "id"]),
|
|
2228
|
+
);
|
|
2229
|
+
});
|
|
2230
|
+
document.gatewayModels.forEach((model, index) => {
|
|
2231
|
+
if (gatewayUpstreamIds.has(model.upstreamModelId)) {
|
|
2232
|
+
context.addIssue({
|
|
2233
|
+
code: "custom",
|
|
2234
|
+
path: ["gatewayModels", index, "upstreamModelId"],
|
|
2235
|
+
message: `duplicate Gateway upstream model id ${model.upstreamModelId}`,
|
|
2236
|
+
});
|
|
2237
|
+
}
|
|
2238
|
+
gatewayUpstreamIds.add(model.upstreamModelId);
|
|
2239
|
+
add(model.productId, ["gatewayModels", index, "productId"]);
|
|
2240
|
+
add(model.workspaceProductId, ["gatewayModels", index, "workspaceProductId"]);
|
|
2241
|
+
});
|
|
2242
|
+
document.openrouterModels.forEach((model, index) =>
|
|
2243
|
+
add(`${OPENROUTER_MODEL_ID_PREFIX}${model.upstreamModelId}`, [
|
|
2244
|
+
"openrouterModels",
|
|
2245
|
+
index,
|
|
2246
|
+
"upstreamModelId",
|
|
2247
|
+
]),
|
|
2248
|
+
);
|
|
2249
|
+
if (document.defaultModel && /[\u000A\u000D|]/u.test(document.defaultModel)) {
|
|
2250
|
+
context.addIssue({
|
|
2251
|
+
code: "custom",
|
|
2252
|
+
path: ["defaultModel"],
|
|
2253
|
+
message: "catalog default model must not contain newlines or the | field separator",
|
|
2254
|
+
});
|
|
2255
|
+
}
|
|
2256
|
+
if (
|
|
2257
|
+
document.defaultModel &&
|
|
2258
|
+
!productIds.has(document.defaultModel) &&
|
|
2259
|
+
!document.defaultModel.startsWith(CODEX_MODEL_ID_PREFIX) &&
|
|
2260
|
+
!document.defaultModel.startsWith(XAI_SUBSCRIPTION_MODEL_ID_PREFIX)
|
|
2261
|
+
) {
|
|
2262
|
+
context.addIssue({
|
|
2263
|
+
code: "custom",
|
|
2264
|
+
path: ["defaultModel"],
|
|
2265
|
+
message:
|
|
2266
|
+
"catalog default model must reference deployment catalog membership or a connected-subscription product",
|
|
2267
|
+
});
|
|
2268
|
+
}
|
|
2269
|
+
for (const productId of Object.keys(document.modelNotes)) {
|
|
2270
|
+
if (!productIds.has(productId)) {
|
|
2271
|
+
context.addIssue({
|
|
2272
|
+
code: "custom",
|
|
2273
|
+
path: ["modelNotes", productId],
|
|
2274
|
+
message: "model note references a product id outside the deployment catalog",
|
|
2275
|
+
});
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
});
|
|
2279
|
+
export type ModelCatalogDocument = z.infer<typeof ModelCatalogDocument>;
|
|
2280
|
+
|
|
2281
|
+
export function parseModelCatalogDocument(value: unknown): ModelCatalogDocument {
|
|
2282
|
+
return ModelCatalogDocument.parse(value);
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
function deploymentRegistryProvidersWithHostCredentials(
|
|
2286
|
+
settings: Settings,
|
|
2287
|
+
providers: readonly z.infer<typeof DeploymentRegistryProviderSchema>[],
|
|
2288
|
+
): RegistryProvider[] {
|
|
2289
|
+
const hostProviders = new Map(
|
|
2290
|
+
parseModelProvidersJson(settings.modelProvidersJson).map((provider) => [provider.id, provider]),
|
|
2291
|
+
);
|
|
2292
|
+
return providers.map((provider) => {
|
|
2293
|
+
if (provider.kind !== "api-key") return provider;
|
|
2294
|
+
const host = hostProviders.get(provider.id);
|
|
2295
|
+
if (!host || host.kind !== "api-key") {
|
|
2296
|
+
throw new Error(
|
|
2297
|
+
`database model catalog provider ${provider.id} has no matching host-authorized api-key transport`,
|
|
2298
|
+
);
|
|
2299
|
+
}
|
|
2300
|
+
const transportIdentity = (candidate: typeof provider | RegistryProvider) => ({
|
|
2301
|
+
kind: candidate.kind,
|
|
2302
|
+
baseUrl: candidate.baseUrl,
|
|
2303
|
+
api: candidate.api,
|
|
2304
|
+
wireProfile: candidate.wireProfile,
|
|
2305
|
+
});
|
|
2306
|
+
if (canonicalJson(transportIdentity(provider)) !== canonicalJson(transportIdentity(host))) {
|
|
2307
|
+
throw new Error(
|
|
2308
|
+
`database model catalog provider ${provider.id} does not match its host-authorized transport`,
|
|
2309
|
+
);
|
|
2310
|
+
}
|
|
2311
|
+
return {
|
|
2312
|
+
...provider,
|
|
2313
|
+
...(host.defaultHeaders === undefined ? {} : { defaultHeaders: host.defaultHeaders }),
|
|
2314
|
+
...(host.defaultQuery === undefined ? {} : { defaultQuery: host.defaultQuery }),
|
|
2315
|
+
...(host.publicDefaultHeaderNames === undefined
|
|
2316
|
+
? {}
|
|
2317
|
+
: { publicDefaultHeaderNames: host.publicDefaultHeaderNames }),
|
|
2318
|
+
...(host.publicDefaultQueryNames === undefined
|
|
2319
|
+
? {}
|
|
2320
|
+
: { publicDefaultQueryNames: host.publicDefaultQueryNames }),
|
|
2321
|
+
...(host.apiKey === undefined ? {} : { apiKey: host.apiKey }),
|
|
2322
|
+
...(host.apiKeyEnv === undefined ? {} : { apiKeyEnv: host.apiKeyEnv }),
|
|
2323
|
+
};
|
|
2324
|
+
});
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
/** Pure secret-free database catalog overlay. getSettings remains env-only. */
|
|
2328
|
+
export function applyModelCatalogDocument(settings: Settings, rawDocument: unknown): Settings {
|
|
2329
|
+
const document = parseModelCatalogDocument(rawDocument);
|
|
2330
|
+
const defaultModel = document.defaultModel ?? document.builtInModels[0]!;
|
|
2331
|
+
const resolved = {
|
|
2332
|
+
...settings,
|
|
2333
|
+
openaiModel: defaultModel,
|
|
2334
|
+
// Keep the complete built-in membership, including the default. The worker
|
|
2335
|
+
// replaces openaiModel with the exact turn model; the run-scoped router
|
|
2336
|
+
// needs one stable built-in id in this allow-list so a bare provider model
|
|
2337
|
+
// is not temporarily claimed by OpenAI/Azure during name re-resolution.
|
|
2338
|
+
openaiAllowedModels: document.builtInModels.join(","),
|
|
2339
|
+
modelProvidersJson: JSON.stringify(
|
|
2340
|
+
deploymentRegistryProvidersWithHostCredentials(settings, document.registryProviders),
|
|
2341
|
+
),
|
|
2342
|
+
resolvedGatewayModelsJson: JSON.stringify(document.gatewayModels),
|
|
2343
|
+
resolvedOpenRouterModelsJson: JSON.stringify(document.openrouterModels),
|
|
2344
|
+
modelNotesJson: JSON.stringify(document.modelNotes),
|
|
2345
|
+
};
|
|
2346
|
+
return resolved;
|
|
2347
|
+
}
|
|
2348
|
+
|
|
1935
2349
|
export const IntegrationOAuthClientConfigSchema = z.object({
|
|
1936
2350
|
clientId: z.string().min(1),
|
|
1937
2351
|
clientSecret: z.string().min(1).optional(),
|
|
@@ -1951,7 +2365,7 @@ export type IntegrationOAuthClientConfig = z.infer<typeof IntegrationOAuthClient
|
|
|
1951
2365
|
export interface ResolvedModelProvider {
|
|
1952
2366
|
id: string; // "openai" | "azure" | registry id
|
|
1953
2367
|
label: string;
|
|
1954
|
-
kind: RegistryProviderKind
|
|
2368
|
+
kind: RegistryProviderKind | "openrouter-managed";
|
|
1955
2369
|
api: ModelProviderApi;
|
|
1956
2370
|
wireProfile: ModelProviderWireProfile;
|
|
1957
2371
|
builtin: boolean;
|
|
@@ -1965,6 +2379,10 @@ export interface ResolvedModelProvider {
|
|
|
1965
2379
|
billing: BillingAttributionV1;
|
|
1966
2380
|
}
|
|
1967
2381
|
|
|
2382
|
+
type InternalRegistryProvider = Omit<RegistryProvider, "kind"> & {
|
|
2383
|
+
kind: RegistryProviderKind | "openrouter-managed";
|
|
2384
|
+
};
|
|
2385
|
+
|
|
1968
2386
|
/** A single exposed model + the provider that serves it. */
|
|
1969
2387
|
export interface ConfiguredModel {
|
|
1970
2388
|
schemaVersion: 1;
|
|
@@ -1981,6 +2399,8 @@ export interface ConfiguredModel {
|
|
|
1981
2399
|
executionLimits: ModelExecutionLimitsV1;
|
|
1982
2400
|
credentialSource: CredentialSourceV1;
|
|
1983
2401
|
billing: BillingAttributionV1;
|
|
2402
|
+
/** Workspace-facing funding policy, independent of upstream settlement. */
|
|
2403
|
+
cost: ConfiguredModelCostClass;
|
|
1984
2404
|
capabilities: ModelCapabilitiesV1;
|
|
1985
2405
|
requestPolicy?: {
|
|
1986
2406
|
gateway: {
|
|
@@ -2000,11 +2420,10 @@ export interface ConfiguredModel {
|
|
|
2000
2420
|
|
|
2001
2421
|
export const VERCEL_AI_GATEWAY_BASE_URL = "https://ai-gateway.vercel.sh/v1" as const;
|
|
2002
2422
|
export const VERCEL_AI_GATEWAY_AI_SDK_BASE_URL = "https://ai-gateway.vercel.sh/v4/ai" as const;
|
|
2003
|
-
export const OPENGENI_GATEWAY_PROVIDER_ID = "opengeni-gateway" as const;
|
|
2004
|
-
export const WORKSPACE_GATEWAY_PROVIDER_ID = "workspace-gateway" as const;
|
|
2005
|
-
export const WORKSPACE_GATEWAY_MODEL_ID_PREFIX = "workspace-gateway/" as const;
|
|
2006
2423
|
export const VERCEL_AI_GATEWAY_CONNECTION_DOMAIN = "ai-gateway.vercel.sh" as const;
|
|
2007
2424
|
export const VERCEL_AI_GATEWAY_CONNECTION_ROLE = "vercel_ai_gateway" as const;
|
|
2425
|
+
export const WORKSPACE_OPENROUTER_CONNECTION_DOMAIN = "openrouter.ai" as const;
|
|
2426
|
+
export const WORKSPACE_OPENROUTER_CONNECTION_ROLE = "openrouter" as const;
|
|
2008
2427
|
|
|
2009
2428
|
export const CODEX_REALTIME_MODEL_ID = "gpt-live-1-boulder-alpha" as const;
|
|
2010
2429
|
export const SUPERGROK_REALTIME_MODEL_ID = "supergrok/grok-voice-think-fast-2.0" as const;
|
|
@@ -2074,16 +2493,133 @@ export const OPENGENI_GATEWAY_MODELS = {
|
|
|
2074
2493
|
},
|
|
2075
2494
|
} as const;
|
|
2076
2495
|
|
|
2496
|
+
export const OPENGENI_OPENROUTER_MODELS: readonly OpenRouterCatalogModel[] = [
|
|
2497
|
+
OpenRouterCatalogModel.parse({
|
|
2498
|
+
upstreamModelId: "nvidia/nemotron-3-super-120b-a12b:free",
|
|
2499
|
+
label: "Nemotron 3 Super 120B",
|
|
2500
|
+
shortLabel: "Nemotron 3 Super",
|
|
2501
|
+
aliases: [],
|
|
2502
|
+
capabilities: {
|
|
2503
|
+
reasoning: {
|
|
2504
|
+
upstream: "supported",
|
|
2505
|
+
// OpenRouter advertises the reasoning controls, but the catalogue does
|
|
2506
|
+
// not publish this model's accepted effort vocabulary. Preserve that
|
|
2507
|
+
// upstream fact without exposing an unverified runnable selector.
|
|
2508
|
+
runnable: false,
|
|
2509
|
+
efforts: [],
|
|
2510
|
+
defaultEffort: null,
|
|
2511
|
+
required: false,
|
|
2512
|
+
},
|
|
2513
|
+
functionCalling: { upstream: "supported", runnable: true },
|
|
2514
|
+
structuredOutput: { upstream: "supported", runnable: true },
|
|
2515
|
+
hostedTools: {
|
|
2516
|
+
webSearch: { upstream: "unknown", runnable: false },
|
|
2517
|
+
xSearch: { upstream: "unknown", runnable: false },
|
|
2518
|
+
codeExecution: { upstream: "unknown", runnable: false },
|
|
2519
|
+
imageGeneration: { upstream: "unknown", runnable: false },
|
|
2520
|
+
},
|
|
2521
|
+
inputModalities: ["text"],
|
|
2522
|
+
inputFileMediaTypes: [],
|
|
2523
|
+
outputModalities: ["text"],
|
|
2524
|
+
transports: {
|
|
2525
|
+
sse: { upstream: "supported", runnable: true },
|
|
2526
|
+
responsesWebSocket: { upstream: "unknown", runnable: false },
|
|
2527
|
+
realtimeAudio: { upstream: "unsupported", runnable: false },
|
|
2528
|
+
},
|
|
2529
|
+
latencyModes: [{ id: "standard", upstream: "unknown", runnable: true }],
|
|
2530
|
+
},
|
|
2531
|
+
contextWindowTokens: 262_144,
|
|
2532
|
+
effectiveContextWindowTokens: 235_929,
|
|
2533
|
+
autoCompactTokenLimit: 220_000,
|
|
2534
|
+
}),
|
|
2535
|
+
];
|
|
2536
|
+
|
|
2537
|
+
function defaultGatewayCatalogModels(): GatewayCatalogModel[] {
|
|
2538
|
+
return [
|
|
2539
|
+
{
|
|
2540
|
+
...OPENGENI_GATEWAY_MODELS.deepseek,
|
|
2541
|
+
vision: false,
|
|
2542
|
+
inputFileMediaTypes: [],
|
|
2543
|
+
contextWindowTokens: 1_000_000,
|
|
2544
|
+
effectiveContextWindowTokens: 900_000,
|
|
2545
|
+
autoCompactTokenLimit: 850_000,
|
|
2546
|
+
},
|
|
2547
|
+
{
|
|
2548
|
+
...OPENGENI_GATEWAY_MODELS.kimi,
|
|
2549
|
+
vision: true,
|
|
2550
|
+
inputFileMediaTypes: ["application/pdf"],
|
|
2551
|
+
contextWindowTokens: 1_000_000,
|
|
2552
|
+
effectiveContextWindowTokens: 900_000,
|
|
2553
|
+
autoCompactTokenLimit: 850_000,
|
|
2554
|
+
},
|
|
2555
|
+
].map((model) => GatewayCatalogModel.parse(model));
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2558
|
+
function configuredGatewayCatalogModels(settings: Settings): GatewayCatalogModel[] {
|
|
2559
|
+
if (settings.resolvedGatewayModelsJson === undefined) {
|
|
2560
|
+
return defaultGatewayCatalogModels();
|
|
2561
|
+
}
|
|
2562
|
+
return z.array(GatewayCatalogModel).parse(JSON.parse(settings.resolvedGatewayModelsJson));
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
export function configuredGatewayUpstreamModelIds(settings: Settings): string[] {
|
|
2566
|
+
return configuredGatewayCatalogModels(settings).map((model) => model.upstreamModelId);
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
export function configuredGatewayWorkspaceProductModelIds(settings: Settings): string[] {
|
|
2570
|
+
return configuredGatewayCatalogModels(settings).map((model) => model.workspaceProductId);
|
|
2571
|
+
}
|
|
2572
|
+
|
|
2573
|
+
export function configuredGatewayOrganizationProductModelIds(settings: Settings): string[] {
|
|
2574
|
+
void settings;
|
|
2575
|
+
return [];
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2578
|
+
export function configuredModelInputIdentities(settings: Settings): string[] {
|
|
2579
|
+
return configuredModels(settings).flatMap((model) => [model.id, ...model.aliases]);
|
|
2580
|
+
}
|
|
2581
|
+
|
|
2582
|
+
function configuredOpenRouterCatalogModels(settings: Settings): OpenRouterCatalogModel[] {
|
|
2583
|
+
if (settings.resolvedOpenRouterModelsJson === undefined) {
|
|
2584
|
+
return [...OPENGENI_OPENROUTER_MODELS];
|
|
2585
|
+
}
|
|
2586
|
+
return z.array(OpenRouterCatalogModel).parse(JSON.parse(settings.resolvedOpenRouterModelsJson));
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2589
|
+
export function configuredOpenRouterUpstreamModelIds(settings: Settings): string[] {
|
|
2590
|
+
return configuredOpenRouterCatalogModels(settings).map((model) => model.upstreamModelId);
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
function workspaceOpenRouterProductId(modelId: string): string {
|
|
2594
|
+
return `${WORKSPACE_OPENROUTER_MODEL_ID_PREFIX}${
|
|
2595
|
+
modelId.startsWith(OPENROUTER_MODEL_ID_PREFIX)
|
|
2596
|
+
? modelId.slice(OPENROUTER_MODEL_ID_PREFIX.length)
|
|
2597
|
+
: modelId
|
|
2598
|
+
}`;
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
export function configuredOpenRouterWorkspaceProductModelIds(settings: Settings): string[] {
|
|
2602
|
+
return configuredOpenRouterCatalogModels(settings).flatMap((model) =>
|
|
2603
|
+
[model.upstreamModelId, ...model.aliases].map(workspaceOpenRouterProductId),
|
|
2604
|
+
);
|
|
2605
|
+
}
|
|
2606
|
+
|
|
2607
|
+
export function configuredOpenRouterOrganizationProductModelIds(settings: Settings): string[] {
|
|
2608
|
+
void settings;
|
|
2609
|
+
return [];
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2077
2612
|
/**
|
|
2078
2613
|
* Built-in OpenGeni credit pricing schedules.
|
|
2079
2614
|
*
|
|
2080
2615
|
* Rates are provider list prices in USD micros per 1M tokens. Debit applies
|
|
2081
|
-
* `marginBps` (
|
|
2616
|
+
* `marginBps` (500 = +5%) on top. Long-context tiers follow OpenAI's
|
|
2082
2617
|
* ">272K input tokens" rule (threshold exclusive of 272_000).
|
|
2083
2618
|
*
|
|
2084
2619
|
* GPT-5.4 and older families are intentionally omitted — they are no longer
|
|
2085
|
-
* offered. Codex / connected-subscription turns use `metering: external
|
|
2086
|
-
* never
|
|
2620
|
+
* offered. Codex / connected-subscription turns use `metering: external`, so
|
|
2621
|
+
* this map never debits them, but it does provide their equivalent OpenGeni
|
|
2622
|
+
* credit price when a matching product model is configured.
|
|
2087
2623
|
*
|
|
2088
2624
|
* When adding or changing a billed model, run `bun run check:model-pricing`
|
|
2089
2625
|
* (see docs/model-providers.md § Price audit). That compares this map to
|
|
@@ -2092,20 +2628,23 @@ export const OPENGENI_GATEWAY_MODELS = {
|
|
|
2092
2628
|
export const defaultModelPricing: Record<string, ModelPricingScheduleV1> = {
|
|
2093
2629
|
"gpt-5.6-sol": {
|
|
2094
2630
|
default: {
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2631
|
+
// Promotional OpenAI pricing, guaranteed through at least 2026-11-21.
|
|
2632
|
+
inputMicrosPerMillionTokens: 4_000_000,
|
|
2633
|
+
cachedInputMicrosPerMillionTokens: 400_000,
|
|
2634
|
+
cacheWriteMicrosPerMillionTokens: 5_000_000,
|
|
2635
|
+
outputMicrosPerMillionTokens: 20_000_000,
|
|
2636
|
+
marginBps: 500,
|
|
2099
2637
|
},
|
|
2100
2638
|
inputTokenTiers: [
|
|
2101
2639
|
{
|
|
2102
2640
|
// OpenAI: prompts with >272K input tokens use the long-context rate.
|
|
2103
2641
|
minimumInputTokens: 272_001,
|
|
2104
2642
|
pricing: {
|
|
2105
|
-
inputMicrosPerMillionTokens:
|
|
2106
|
-
cachedInputMicrosPerMillionTokens:
|
|
2107
|
-
|
|
2108
|
-
|
|
2643
|
+
inputMicrosPerMillionTokens: 8_000_000,
|
|
2644
|
+
cachedInputMicrosPerMillionTokens: 800_000,
|
|
2645
|
+
cacheWriteMicrosPerMillionTokens: 10_000_000,
|
|
2646
|
+
outputMicrosPerMillionTokens: 30_000_000,
|
|
2647
|
+
marginBps: 500,
|
|
2109
2648
|
},
|
|
2110
2649
|
},
|
|
2111
2650
|
],
|
|
@@ -2114,8 +2653,9 @@ export const defaultModelPricing: Record<string, ModelPricingScheduleV1> = {
|
|
|
2114
2653
|
default: {
|
|
2115
2654
|
inputMicrosPerMillionTokens: 2_000_000,
|
|
2116
2655
|
cachedInputMicrosPerMillionTokens: 200_000,
|
|
2656
|
+
cacheWriteMicrosPerMillionTokens: 2_500_000,
|
|
2117
2657
|
outputMicrosPerMillionTokens: 12_000_000,
|
|
2118
|
-
marginBps:
|
|
2658
|
+
marginBps: 500,
|
|
2119
2659
|
},
|
|
2120
2660
|
inputTokenTiers: [
|
|
2121
2661
|
{
|
|
@@ -2123,8 +2663,9 @@ export const defaultModelPricing: Record<string, ModelPricingScheduleV1> = {
|
|
|
2123
2663
|
pricing: {
|
|
2124
2664
|
inputMicrosPerMillionTokens: 4_000_000,
|
|
2125
2665
|
cachedInputMicrosPerMillionTokens: 400_000,
|
|
2666
|
+
cacheWriteMicrosPerMillionTokens: 5_000_000,
|
|
2126
2667
|
outputMicrosPerMillionTokens: 18_000_000,
|
|
2127
|
-
marginBps:
|
|
2668
|
+
marginBps: 500,
|
|
2128
2669
|
},
|
|
2129
2670
|
},
|
|
2130
2671
|
],
|
|
@@ -2133,8 +2674,9 @@ export const defaultModelPricing: Record<string, ModelPricingScheduleV1> = {
|
|
|
2133
2674
|
default: {
|
|
2134
2675
|
inputMicrosPerMillionTokens: 200_000,
|
|
2135
2676
|
cachedInputMicrosPerMillionTokens: 20_000,
|
|
2677
|
+
cacheWriteMicrosPerMillionTokens: 250_000,
|
|
2136
2678
|
outputMicrosPerMillionTokens: 1_200_000,
|
|
2137
|
-
marginBps:
|
|
2679
|
+
marginBps: 500,
|
|
2138
2680
|
},
|
|
2139
2681
|
inputTokenTiers: [
|
|
2140
2682
|
{
|
|
@@ -2142,8 +2684,9 @@ export const defaultModelPricing: Record<string, ModelPricingScheduleV1> = {
|
|
|
2142
2684
|
pricing: {
|
|
2143
2685
|
inputMicrosPerMillionTokens: 400_000,
|
|
2144
2686
|
cachedInputMicrosPerMillionTokens: 40_000,
|
|
2687
|
+
cacheWriteMicrosPerMillionTokens: 500_000,
|
|
2145
2688
|
outputMicrosPerMillionTokens: 1_800_000,
|
|
2146
|
-
marginBps:
|
|
2689
|
+
marginBps: 500,
|
|
2147
2690
|
},
|
|
2148
2691
|
},
|
|
2149
2692
|
],
|
|
@@ -2158,7 +2701,7 @@ export const defaultModelPricing: Record<string, ModelPricingScheduleV1> = {
|
|
|
2158
2701
|
inputMicrosPerMillionTokens: 140_000,
|
|
2159
2702
|
cachedInputMicrosPerMillionTokens: 28_000,
|
|
2160
2703
|
outputMicrosPerMillionTokens: 280_000,
|
|
2161
|
-
marginBps:
|
|
2704
|
+
marginBps: 500,
|
|
2162
2705
|
},
|
|
2163
2706
|
},
|
|
2164
2707
|
[OPENGENI_GATEWAY_MODELS.kimi.productId]: {
|
|
@@ -2166,7 +2709,7 @@ export const defaultModelPricing: Record<string, ModelPricingScheduleV1> = {
|
|
|
2166
2709
|
inputMicrosPerMillionTokens: 3_000_000,
|
|
2167
2710
|
cachedInputMicrosPerMillionTokens: 300_000,
|
|
2168
2711
|
outputMicrosPerMillionTokens: 15_000_000,
|
|
2169
|
-
marginBps:
|
|
2712
|
+
marginBps: 500,
|
|
2170
2713
|
},
|
|
2171
2714
|
},
|
|
2172
2715
|
// Fireworks AI / GLM 5.2 — the first shipped non-OpenAI registry model. A
|
|
@@ -2178,7 +2721,7 @@ export const defaultModelPricing: Record<string, ModelPricingScheduleV1> = {
|
|
|
2178
2721
|
inputMicrosPerMillionTokens: 1_400_000,
|
|
2179
2722
|
cachedInputMicrosPerMillionTokens: 140_000,
|
|
2180
2723
|
outputMicrosPerMillionTokens: 4_400_000,
|
|
2181
|
-
marginBps:
|
|
2724
|
+
marginBps: 500,
|
|
2182
2725
|
},
|
|
2183
2726
|
},
|
|
2184
2727
|
};
|
|
@@ -2269,12 +2812,17 @@ function objectStorageConfiguredForWorkspaceArchives(settings: Settings): boolea
|
|
|
2269
2812
|
}
|
|
2270
2813
|
}
|
|
2271
2814
|
|
|
2272
|
-
function
|
|
2273
|
-
const value =
|
|
2815
|
+
function optionalEnvironmentValue(name: string, source: NodeJS.ProcessEnv): string | undefined {
|
|
2816
|
+
const value = source[name];
|
|
2274
2817
|
return value && value.trim().length > 0 ? value : undefined;
|
|
2275
2818
|
}
|
|
2276
2819
|
|
|
2277
|
-
export function getSettings(): Settings {
|
|
2820
|
+
export function getSettings(source: NodeJS.ProcessEnv = process.env): Settings {
|
|
2821
|
+
const optional = (name: string): string | undefined => optionalEnvironmentValue(name, source);
|
|
2822
|
+
const modelCatalogSource = optional("OPENGENI_MODEL_CATALOG_SOURCE");
|
|
2823
|
+
const modelCostPolicyJson =
|
|
2824
|
+
optional("OPENGENI_MODEL_COST_POLICY_JSON") ??
|
|
2825
|
+
(modelCatalogSource === "database" ? "{}" : DEFAULT_MODEL_COST_POLICY_JSON);
|
|
2278
2826
|
const raw = {
|
|
2279
2827
|
serviceName: optional("OPENGENI_SERVICE_NAME"),
|
|
2280
2828
|
environment: optional("OPENGENI_ENVIRONMENT"),
|
|
@@ -2324,6 +2872,8 @@ export function getSettings(): Settings {
|
|
|
2324
2872
|
analyticsPosthogHost: optional("OPENGENI_ANALYTICS_POSTHOG_HOST"),
|
|
2325
2873
|
analyticsGa4MeasurementId: optional("OPENGENI_ANALYTICS_GA4_MEASUREMENT_ID"),
|
|
2326
2874
|
publicBaseUrl: optional("OPENGENI_PUBLIC_BASE_URL"),
|
|
2875
|
+
mcpOauthEnabled: optional("OPENGENI_MCP_OAUTH_ENABLED"),
|
|
2876
|
+
mcpOauthTrustedProxyHops: optional("OPENGENI_MCP_OAUTH_TRUSTED_PROXY_HOPS"),
|
|
2327
2877
|
webBaseUrl: optional("OPENGENI_WEB_BASE_URL"),
|
|
2328
2878
|
agentReleasesBaseUrl: optional("OPENGENI_AGENT_RELEASES_BASE_URL"),
|
|
2329
2879
|
agentStableVersion: optional("OPENGENI_AGENT_STABLE_VERSION"),
|
|
@@ -2395,6 +2945,9 @@ export function getSettings(): Settings {
|
|
|
2395
2945
|
goalIdleBackoffMs: optional("OPENGENI_GOAL_IDLE_BACKOFF_MS"),
|
|
2396
2946
|
goalIdleBackoffMaxMs: optional("OPENGENI_GOAL_IDLE_BACKOFF_MAX_MS"),
|
|
2397
2947
|
childLifecycleNoticesEnabled: optional("OPENGENI_CHILD_LIFECYCLE_NOTICES_ENABLED"),
|
|
2948
|
+
hostMcpAuthoritySourceAdmissionEnabled: optional(
|
|
2949
|
+
"OPENGENI_HOST_MCP_AUTHORITY_SOURCE_ADMISSION_ENABLED",
|
|
2950
|
+
),
|
|
2398
2951
|
slackWorkspaceRoutingEnabled: optional("OPENGENI_SLACK_WORKSPACE_ROUTING_ENABLED"),
|
|
2399
2952
|
agentMaxModelCallsPerTurn: optional("OPENGENI_AGENT_MAX_MODEL_CALLS_PER_TURN"),
|
|
2400
2953
|
contextWindowTokens: optional("OPENGENI_CONTEXT_WINDOW_TOKENS"),
|
|
@@ -2464,6 +3017,10 @@ export function getSettings(): Settings {
|
|
|
2464
3017
|
voiceInputAzureAdToken: optional("OPENGENI_VOICE_INPUT_AZURE_AD_TOKEN"),
|
|
2465
3018
|
voiceInputCodexExperimentalEnabled: optional("OPENGENI_VOICE_INPUT_CODEX_EXPERIMENTAL"),
|
|
2466
3019
|
modelPricingJson: optional("OPENGENI_MODEL_PRICING_JSON"),
|
|
3020
|
+
modelCatalogSource,
|
|
3021
|
+
modelCostPolicyJson,
|
|
3022
|
+
modelNotesJson: optional("OPENGENI_MODEL_NOTES_JSON"),
|
|
3023
|
+
openrouterApiKey: optional("OPENGENI_OPENROUTER_API_KEY"),
|
|
2467
3024
|
modelProvidersJson: optional("OPENGENI_MODEL_PROVIDERS_JSON"),
|
|
2468
3025
|
codexSubscriptionEnabled: optional("OPENGENI_CODEX_SUBSCRIPTION_ENABLED"),
|
|
2469
3026
|
supergrokSubscriptionEnabled: optional("OPENGENI_SUPERGROK_SUBSCRIPTION_ENABLED"),
|
|
@@ -2473,7 +3030,6 @@ export function getSettings(): Settings {
|
|
|
2473
3030
|
codexConnectedAppsEnabled: optional("OPENGENI_CODEX_CONNECTED_APPS_ENABLED"),
|
|
2474
3031
|
codexToolSearchEnabled: optional("OPENGENI_CODEX_TOOL_SEARCH_ENABLED"),
|
|
2475
3032
|
lazyToolSearchEnabled: optional("OPENGENI_LAZY_TOOL_SEARCH_ENABLED"),
|
|
2476
|
-
codexCredentialLeasingEnabled: optional("OPENGENI_CODEX_CREDENTIAL_LEASING_ENABLED"),
|
|
2477
3033
|
codexFleetPolicyShadowEnabled: optional("OPENGENI_CODEX_FLEET_POLICY_SHADOW_ENABLED"),
|
|
2478
3034
|
codexProductSku: optional("OPENGENI_CODEX_PRODUCT_SKU"),
|
|
2479
3035
|
openaiReasoningEffort: optional("OPENGENI_OPENAI_REASONING_EFFORT"),
|
|
@@ -2498,7 +3054,7 @@ export function getSettings(): Settings {
|
|
|
2498
3054
|
dockerNetwork: optional("OPENGENI_DOCKER_NETWORK"),
|
|
2499
3055
|
dockerWorkspaceBaseDir: optional("OPENGENI_DOCKER_WORKSPACE_BASE_DIR"),
|
|
2500
3056
|
modalAppName: optional("OPENGENI_MODAL_APP_NAME"),
|
|
2501
|
-
modalImageRef: optional("OPENGENI_MODAL_IMAGE_REF"),
|
|
3057
|
+
modalImageRef: optional("OPENGENI_MODAL_IMAGE_REF") ?? DEFAULT_MODAL_IMAGE_REF,
|
|
2502
3058
|
modalImageId: optional("OPENGENI_MODAL_IMAGE_ID"),
|
|
2503
3059
|
modalImageRegistrySecret: optional("OPENGENI_MODAL_IMAGE_REGISTRY_SECRET"),
|
|
2504
3060
|
modalTimeoutSeconds: optional("OPENGENI_MODAL_TIMEOUT_SECONDS"),
|
|
@@ -2600,6 +3156,7 @@ export function getSettings(): Settings {
|
|
|
2600
3156
|
sandboxIdleGraceMs: optional("OPENGENI_SANDBOX_IDLE_GRACE_MS"),
|
|
2601
3157
|
sandboxSnapshotIntervalMs: optional("OPENGENI_SANDBOX_SNAPSHOT_INTERVAL_MS"),
|
|
2602
3158
|
sandboxSnapshotTimeoutMs: optional("OPENGENI_SANDBOX_SNAPSHOT_TIMEOUT_MS"),
|
|
3159
|
+
sandboxDrainSnapshotTimeoutMs: optional("OPENGENI_SANDBOX_DRAIN_SNAPSHOT_TIMEOUT_MS"),
|
|
2603
3160
|
sandboxRotationLeadMs: optional("OPENGENI_SANDBOX_ROTATION_LEAD_MS"),
|
|
2604
3161
|
sandboxRotationBatchSize: optional("OPENGENI_SANDBOX_ROTATION_BATCH_SIZE"),
|
|
2605
3162
|
sandboxLeaseTtlMs: optional("OPENGENI_SANDBOX_LEASE_TTL_MS"),
|
|
@@ -2674,6 +3231,12 @@ export function getSettings(): Settings {
|
|
|
2674
3231
|
managedAuthGithubClientId: optional("OPENGENI_MANAGED_AUTH_GITHUB_CLIENT_ID"),
|
|
2675
3232
|
managedAuthGithubClientSecret: optional("OPENGENI_MANAGED_AUTH_GITHUB_CLIENT_SECRET"),
|
|
2676
3233
|
managedAuthSessionSetMode: optional("OPENGENI_MANAGED_AUTH_SESSION_SET_MODE"),
|
|
3234
|
+
organizationUserSetupEmailTokenTransport: optional(
|
|
3235
|
+
"OPENGENI_ORGANIZATION_USER_SETUP_EMAIL_TOKEN_TRANSPORT",
|
|
3236
|
+
),
|
|
3237
|
+
organizationUserSetupQueryEdgeSanitizationConfirmed: optional(
|
|
3238
|
+
"OPENGENI_ORGANIZATION_USER_SETUP_QUERY_EDGE_SANITIZATION_CONFIRMED",
|
|
3239
|
+
),
|
|
2677
3240
|
resendApiKey: optional("OPENGENI_RESEND_API_KEY"),
|
|
2678
3241
|
emailFrom: optional("OPENGENI_EMAIL_FROM"),
|
|
2679
3242
|
stripeSecretKey: optional("OPENGENI_STRIPE_SECRET_KEY"),
|
|
@@ -2695,7 +3258,7 @@ export function getSettings(): Settings {
|
|
|
2695
3258
|
: parsed.sandboxRotationLeadMs,
|
|
2696
3259
|
mcpServers: ensureBuiltInMcpServers(parsed),
|
|
2697
3260
|
};
|
|
2698
|
-
validateSettings(settings);
|
|
3261
|
+
validateSettings(settings, source);
|
|
2699
3262
|
return settings;
|
|
2700
3263
|
}
|
|
2701
3264
|
|
|
@@ -2819,10 +3382,23 @@ export function sandboxArchiveCaptureTimeoutMs(
|
|
|
2819
3382
|
);
|
|
2820
3383
|
}
|
|
2821
3384
|
|
|
3385
|
+
/** Provider operation budget used only by zero-holder drain/rotation capture.
|
|
3386
|
+
* Unset preserves the historical shared snapshot budget exactly. */
|
|
3387
|
+
export function effectiveSandboxDrainSnapshotTimeoutMs(
|
|
3388
|
+
settings: Pick<Settings, "sandboxSnapshotTimeoutMs" | "sandboxDrainSnapshotTimeoutMs">,
|
|
3389
|
+
): number {
|
|
3390
|
+
return settings.sandboxDrainSnapshotTimeoutMs ?? settings.sandboxSnapshotTimeoutMs;
|
|
3391
|
+
}
|
|
3392
|
+
|
|
2822
3393
|
export function sandboxLifecycleTransitionWaitMs(
|
|
2823
|
-
settings: Pick<
|
|
3394
|
+
settings: Pick<
|
|
3395
|
+
Settings,
|
|
3396
|
+
"sandboxSnapshotTimeoutMs" | "sandboxDrainSnapshotTimeoutMs" | "sandboxLeaseReaperPeriodMs"
|
|
3397
|
+
>,
|
|
2824
3398
|
): number {
|
|
2825
|
-
const captureTimeoutMs = sandboxArchiveCaptureTimeoutMs(
|
|
3399
|
+
const captureTimeoutMs = sandboxArchiveCaptureTimeoutMs({
|
|
3400
|
+
sandboxSnapshotTimeoutMs: effectiveSandboxDrainSnapshotTimeoutMs(settings),
|
|
3401
|
+
});
|
|
2826
3402
|
return Math.min(
|
|
2827
3403
|
SANDBOX_LIFECYCLE_TRANSITION_MAX_WAIT_MS,
|
|
2828
3404
|
settings.sandboxLeaseReaperPeriodMs +
|
|
@@ -3140,10 +3716,9 @@ function legacyModelCapabilities(
|
|
|
3140
3716
|
|
|
3141
3717
|
export function gatewayRequestPolicyForUpstreamModel(
|
|
3142
3718
|
upstreamModelId: string,
|
|
3719
|
+
models: readonly GatewayCatalogModel[] = defaultGatewayCatalogModels(),
|
|
3143
3720
|
): ConfiguredModel["requestPolicy"] {
|
|
3144
|
-
const model =
|
|
3145
|
-
(candidate) => candidate.upstreamModelId === upstreamModelId,
|
|
3146
|
-
);
|
|
3721
|
+
const model = models.find((candidate) => candidate.upstreamModelId === upstreamModelId);
|
|
3147
3722
|
if (!model) {
|
|
3148
3723
|
return undefined;
|
|
3149
3724
|
}
|
|
@@ -3157,7 +3732,11 @@ export function gatewayRequestPolicyForUpstreamModel(
|
|
|
3157
3732
|
|
|
3158
3733
|
function gatewayModelCapabilities(
|
|
3159
3734
|
settings: Settings,
|
|
3160
|
-
input: {
|
|
3735
|
+
input: {
|
|
3736
|
+
implicitCaching: boolean;
|
|
3737
|
+
vision: boolean;
|
|
3738
|
+
inputFileMediaTypes?: string[];
|
|
3739
|
+
},
|
|
3161
3740
|
): ModelCapabilitiesV1 {
|
|
3162
3741
|
const legacy = legacyModelCapabilities(settings, {
|
|
3163
3742
|
reasoningEffort: true,
|
|
@@ -3181,35 +3760,112 @@ function gatewayModelCapabilities(
|
|
|
3181
3760
|
});
|
|
3182
3761
|
}
|
|
3183
3762
|
|
|
3763
|
+
function openRouterCustomModelCapabilities(settings: Settings): ModelCapabilitiesV1 {
|
|
3764
|
+
const legacy = legacyModelCapabilities(settings, {
|
|
3765
|
+
reasoningEffort: false,
|
|
3766
|
+
hostedWebSearch: false,
|
|
3767
|
+
});
|
|
3768
|
+
return normalizeCapabilities({
|
|
3769
|
+
...legacy,
|
|
3770
|
+
functionCalling: { upstream: "supported", runnable: true },
|
|
3771
|
+
inputModalities: ["text"],
|
|
3772
|
+
inputFileMediaTypes: [],
|
|
3773
|
+
transports: {
|
|
3774
|
+
...legacy.transports,
|
|
3775
|
+
sse: { upstream: "supported", runnable: true },
|
|
3776
|
+
},
|
|
3777
|
+
promptCaching: { upstream: "unsupported", runnable: false, mode: "none" },
|
|
3778
|
+
latencyModes: [{ id: "standard", upstream: "supported", runnable: true }],
|
|
3779
|
+
});
|
|
3780
|
+
}
|
|
3781
|
+
|
|
3184
3782
|
function gatewayRegistryProvider(
|
|
3185
3783
|
settings: Settings,
|
|
3186
3784
|
input:
|
|
3187
3785
|
| { kind: "vercel-gateway-managed"; apiKey: string }
|
|
3188
|
-
| {
|
|
3189
|
-
|
|
3786
|
+
| {
|
|
3787
|
+
kind: "vercel-gateway-workspace" | "vercel-gateway-organization";
|
|
3788
|
+
apiKey?: string;
|
|
3789
|
+
customModels?: readonly {
|
|
3790
|
+
upstreamModelId: string;
|
|
3791
|
+
label?: string | null;
|
|
3792
|
+
}[];
|
|
3793
|
+
},
|
|
3794
|
+
): InternalRegistryProvider {
|
|
3190
3795
|
const workspace = input.kind === "vercel-gateway-workspace";
|
|
3191
|
-
const
|
|
3192
|
-
|
|
3796
|
+
const organization = input.kind === "vercel-gateway-organization";
|
|
3797
|
+
const scoped = workspace || organization;
|
|
3798
|
+
const curated = organization ? [] : configuredGatewayCatalogModels(settings);
|
|
3799
|
+
const upstreamIds = new Set(curated.map((model) => model.upstreamModelId));
|
|
3800
|
+
const productIds = new Set(
|
|
3801
|
+
parseModelProvidersJson(settings.modelProvidersJson)
|
|
3802
|
+
.filter(
|
|
3803
|
+
(provider) =>
|
|
3804
|
+
provider.id !== WORKSPACE_GATEWAY_PROVIDER_ID &&
|
|
3805
|
+
provider.id !== ORGANIZATION_GATEWAY_PROVIDER_ID,
|
|
3806
|
+
)
|
|
3807
|
+
.flatMap((provider) =>
|
|
3808
|
+
provider.models.flatMap((model) => [model.id, ...(model.aliases ?? [])]),
|
|
3809
|
+
),
|
|
3810
|
+
);
|
|
3811
|
+
const models = curated.map((model) => {
|
|
3812
|
+
const id = workspace
|
|
3813
|
+
? model.workspaceProductId
|
|
3814
|
+
: organization
|
|
3815
|
+
? `${ORGANIZATION_GATEWAY_MODEL_ID_PREFIX}${model.upstreamModelId}`
|
|
3816
|
+
: model.productId;
|
|
3817
|
+
productIds.add(id);
|
|
3193
3818
|
return {
|
|
3194
|
-
id
|
|
3819
|
+
id,
|
|
3195
3820
|
upstreamModelId: model.upstreamModelId,
|
|
3196
3821
|
label: model.label,
|
|
3197
|
-
shortLabel: model.shortLabel,
|
|
3822
|
+
...(model.shortLabel ? { shortLabel: model.shortLabel } : {}),
|
|
3198
3823
|
capabilities: gatewayModelCapabilities(settings, {
|
|
3199
3824
|
implicitCaching: model.implicitCaching,
|
|
3200
|
-
vision:
|
|
3201
|
-
inputFileMediaTypes:
|
|
3825
|
+
vision: model.vision,
|
|
3826
|
+
inputFileMediaTypes: model.inputFileMediaTypes,
|
|
3202
3827
|
}),
|
|
3203
|
-
contextWindowTokens:
|
|
3204
|
-
effectiveContextWindowTokens:
|
|
3205
|
-
autoCompactTokenLimit:
|
|
3828
|
+
contextWindowTokens: model.contextWindowTokens,
|
|
3829
|
+
effectiveContextWindowTokens: model.effectiveContextWindowTokens,
|
|
3830
|
+
autoCompactTokenLimit: model.autoCompactTokenLimit,
|
|
3206
3831
|
toolOutputTruncationTokens: settings.modelToolOutputTruncationTokens,
|
|
3832
|
+
...(model.pricing === undefined ? {} : { pricing: model.pricing }),
|
|
3207
3833
|
};
|
|
3208
3834
|
});
|
|
3835
|
+
if (scoped) {
|
|
3836
|
+
for (const custom of input.customModels ?? []) {
|
|
3837
|
+
const productId = `${workspace ? WORKSPACE_GATEWAY_MODEL_ID_PREFIX : ORGANIZATION_GATEWAY_MODEL_ID_PREFIX}${custom.upstreamModelId}`;
|
|
3838
|
+
// Deployment membership wins over an older or concurrently-created
|
|
3839
|
+
// workspace row with the same upstream identity or generated product id.
|
|
3840
|
+
// This keeps runtime routing deterministic and prevents a legacy/admin
|
|
3841
|
+
// row from making the entire workspace catalog fail uniqueness checks.
|
|
3842
|
+
if (upstreamIds.has(custom.upstreamModelId) || productIds.has(productId)) continue;
|
|
3843
|
+
upstreamIds.add(custom.upstreamModelId);
|
|
3844
|
+
productIds.add(productId);
|
|
3845
|
+
models.push({
|
|
3846
|
+
id: productId,
|
|
3847
|
+
upstreamModelId: custom.upstreamModelId,
|
|
3848
|
+
label: custom.label?.trim() || custom.upstreamModelId,
|
|
3849
|
+
capabilities: gatewayModelCapabilities(settings, {
|
|
3850
|
+
implicitCaching: false,
|
|
3851
|
+
vision: false,
|
|
3852
|
+
inputFileMediaTypes: [],
|
|
3853
|
+
}),
|
|
3854
|
+
contextWindowTokens: 1_000_000,
|
|
3855
|
+
effectiveContextWindowTokens: 900_000,
|
|
3856
|
+
autoCompactTokenLimit: 850_000,
|
|
3857
|
+
toolOutputTruncationTokens: settings.modelToolOutputTruncationTokens,
|
|
3858
|
+
});
|
|
3859
|
+
}
|
|
3860
|
+
}
|
|
3209
3861
|
return {
|
|
3210
3862
|
kind: input.kind,
|
|
3211
|
-
id: workspace
|
|
3212
|
-
|
|
3863
|
+
id: workspace
|
|
3864
|
+
? WORKSPACE_GATEWAY_PROVIDER_ID
|
|
3865
|
+
: organization
|
|
3866
|
+
? ORGANIZATION_GATEWAY_PROVIDER_ID
|
|
3867
|
+
: OPENGENI_GATEWAY_PROVIDER_ID,
|
|
3868
|
+
label: workspace ? "Your Gateway" : organization ? "Organization Gateway" : "OpenGeni",
|
|
3213
3869
|
// Responses preserves vision, reasoning items, and provider-native usage.
|
|
3214
3870
|
// Model-specific compatibility stays at the reviewed request fence rather
|
|
3215
3871
|
// than downgrading the whole provider wire.
|
|
@@ -3221,52 +3877,274 @@ function gatewayRegistryProvider(
|
|
|
3221
3877
|
};
|
|
3222
3878
|
}
|
|
3223
3879
|
|
|
3224
|
-
function
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3880
|
+
function openRouterRegistryProvider(
|
|
3881
|
+
settings: Settings,
|
|
3882
|
+
input:
|
|
3883
|
+
| { kind: "openrouter-managed"; apiKey: string }
|
|
3884
|
+
| {
|
|
3885
|
+
kind: "openrouter-workspace" | "openrouter-organization";
|
|
3886
|
+
apiKey?: string;
|
|
3887
|
+
customModels?: readonly {
|
|
3888
|
+
upstreamModelId: string;
|
|
3889
|
+
label?: string | null;
|
|
3890
|
+
}[];
|
|
3891
|
+
},
|
|
3892
|
+
): InternalRegistryProvider | null {
|
|
3893
|
+
const workspace = input.kind === "openrouter-workspace";
|
|
3894
|
+
const organization = input.kind === "openrouter-organization";
|
|
3895
|
+
const scoped = workspace || organization;
|
|
3896
|
+
const curated = organization ? [] : configuredOpenRouterCatalogModels(settings);
|
|
3897
|
+
const upstreamIds = new Set(curated.map((model) => model.upstreamModelId));
|
|
3898
|
+
const productIds = new Set(
|
|
3899
|
+
parseModelProvidersJson(settings.modelProvidersJson)
|
|
3900
|
+
.filter(
|
|
3901
|
+
(provider) =>
|
|
3902
|
+
provider.id !== WORKSPACE_OPENROUTER_PROVIDER_ID &&
|
|
3903
|
+
provider.id !== ORGANIZATION_OPENROUTER_PROVIDER_ID,
|
|
3904
|
+
)
|
|
3905
|
+
.flatMap((provider) =>
|
|
3906
|
+
provider.models.flatMap((model) => [model.id, ...(model.aliases ?? [])]),
|
|
3907
|
+
),
|
|
3908
|
+
);
|
|
3909
|
+
const models: RegistryProvider["models"] = curated.map((model) => {
|
|
3910
|
+
const id = workspace
|
|
3911
|
+
? workspaceOpenRouterProductId(model.upstreamModelId)
|
|
3912
|
+
: organization
|
|
3913
|
+
? `${ORGANIZATION_OPENROUTER_MODEL_ID_PREFIX}${model.upstreamModelId}`
|
|
3914
|
+
: `${OPENROUTER_MODEL_ID_PREFIX}${model.upstreamModelId}`;
|
|
3915
|
+
const aliases = workspace
|
|
3916
|
+
? model.aliases.map(workspaceOpenRouterProductId)
|
|
3917
|
+
: organization
|
|
3918
|
+
? model.aliases.map((alias) => `${ORGANIZATION_OPENROUTER_MODEL_ID_PREFIX}${alias}`)
|
|
3919
|
+
: model.aliases;
|
|
3920
|
+
productIds.add(id);
|
|
3921
|
+
for (const alias of aliases) productIds.add(alias);
|
|
3922
|
+
return {
|
|
3923
|
+
id,
|
|
3924
|
+
upstreamModelId: model.upstreamModelId,
|
|
3925
|
+
aliases,
|
|
3926
|
+
label: model.label,
|
|
3927
|
+
...(model.shortLabel ? { shortLabel: model.shortLabel } : {}),
|
|
3928
|
+
capabilities: model.capabilities,
|
|
3929
|
+
...(model.contextWindowTokens === undefined
|
|
3930
|
+
? {}
|
|
3931
|
+
: { contextWindowTokens: model.contextWindowTokens }),
|
|
3932
|
+
...(model.effectiveContextWindowTokens === undefined
|
|
3933
|
+
? {}
|
|
3934
|
+
: { effectiveContextWindowTokens: model.effectiveContextWindowTokens }),
|
|
3935
|
+
...(model.autoCompactTokenLimit === undefined
|
|
3936
|
+
? {}
|
|
3937
|
+
: { autoCompactTokenLimit: model.autoCompactTokenLimit }),
|
|
3938
|
+
toolOutputTruncationTokens:
|
|
3939
|
+
model.toolOutputTruncationTokens ?? settings.modelToolOutputTruncationTokens,
|
|
3940
|
+
};
|
|
3941
|
+
});
|
|
3942
|
+
if (scoped) {
|
|
3943
|
+
for (const custom of input.customModels ?? []) {
|
|
3944
|
+
const productId = `${workspace ? WORKSPACE_OPENROUTER_MODEL_ID_PREFIX : ORGANIZATION_OPENROUTER_MODEL_ID_PREFIX}${custom.upstreamModelId}`;
|
|
3945
|
+
if (upstreamIds.has(custom.upstreamModelId) || productIds.has(productId)) continue;
|
|
3946
|
+
upstreamIds.add(custom.upstreamModelId);
|
|
3947
|
+
productIds.add(productId);
|
|
3948
|
+
models.push({
|
|
3949
|
+
id: productId,
|
|
3950
|
+
upstreamModelId: custom.upstreamModelId,
|
|
3951
|
+
aliases: [],
|
|
3952
|
+
label: custom.label?.trim() || custom.upstreamModelId,
|
|
3953
|
+
capabilities: openRouterCustomModelCapabilities(settings),
|
|
3954
|
+
toolOutputTruncationTokens: settings.modelToolOutputTruncationTokens,
|
|
3955
|
+
});
|
|
3956
|
+
}
|
|
3228
3957
|
}
|
|
3229
|
-
if (
|
|
3230
|
-
|
|
3231
|
-
|
|
3958
|
+
if (models.length === 0) return null;
|
|
3959
|
+
const defaultHeaders: Record<string, string> = {
|
|
3960
|
+
"x-title": "OpenGeni",
|
|
3961
|
+
...(settings.publicBaseUrl ? { "http-referer": settings.publicBaseUrl } : {}),
|
|
3962
|
+
};
|
|
3963
|
+
return {
|
|
3964
|
+
kind: input.kind,
|
|
3965
|
+
id: workspace
|
|
3966
|
+
? WORKSPACE_OPENROUTER_PROVIDER_ID
|
|
3967
|
+
: organization
|
|
3968
|
+
? ORGANIZATION_OPENROUTER_PROVIDER_ID
|
|
3969
|
+
: OPENROUTER_PROVIDER_ID,
|
|
3970
|
+
label: workspace ? "Your OpenRouter" : organization ? "Organization OpenRouter" : "OpenRouter",
|
|
3971
|
+
api: "chat",
|
|
3972
|
+
wireProfile: "openai",
|
|
3973
|
+
baseUrl: OPENROUTER_BASE_URL,
|
|
3974
|
+
...(input.apiKey ? { apiKey: input.apiKey } : {}),
|
|
3975
|
+
defaultHeaders,
|
|
3976
|
+
publicDefaultHeaderNames: Object.keys(defaultHeaders),
|
|
3977
|
+
models,
|
|
3978
|
+
};
|
|
3979
|
+
}
|
|
3980
|
+
|
|
3981
|
+
function configuredRegistryProviders(settings: Settings): InternalRegistryProvider[] {
|
|
3982
|
+
const providers = parseModelProvidersJson(settings.modelProvidersJson);
|
|
3983
|
+
const injected: InternalRegistryProvider[] = [...providers];
|
|
3984
|
+
if (settings.vercelAiGatewayApiKey && configuredGatewayCatalogModels(settings).length > 0) {
|
|
3985
|
+
injected.push(
|
|
3986
|
+
gatewayRegistryProvider(settings, {
|
|
3987
|
+
kind: "vercel-gateway-managed",
|
|
3988
|
+
apiKey: settings.vercelAiGatewayApiKey,
|
|
3989
|
+
}),
|
|
3232
3990
|
);
|
|
3233
3991
|
}
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3992
|
+
const openrouter = settings.openrouterApiKey
|
|
3993
|
+
? openRouterRegistryProvider(settings, {
|
|
3994
|
+
kind: "openrouter-managed",
|
|
3995
|
+
apiKey: settings.openrouterApiKey,
|
|
3996
|
+
})
|
|
3997
|
+
: null;
|
|
3998
|
+
if (openrouter) injected.push(openrouter);
|
|
3999
|
+
return injected;
|
|
3241
4000
|
}
|
|
3242
4001
|
|
|
3243
4002
|
/** Static catalog overlay; it contains no concrete workspace credential. */
|
|
3244
|
-
export function withWorkspaceGatewayCatalogProvider(
|
|
4003
|
+
export function withWorkspaceGatewayCatalogProvider(
|
|
4004
|
+
settings: Settings,
|
|
4005
|
+
customModels: readonly {
|
|
4006
|
+
upstreamModelId: string;
|
|
4007
|
+
label?: string | null;
|
|
4008
|
+
}[] = [],
|
|
4009
|
+
): Settings {
|
|
3245
4010
|
const providers = parseModelProvidersJson(settings.modelProvidersJson);
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
4011
|
+
const withoutWorkspace = providers.filter(
|
|
4012
|
+
(provider) => provider.id !== WORKSPACE_GATEWAY_PROVIDER_ID,
|
|
4013
|
+
);
|
|
4014
|
+
const curatedCount = configuredGatewayCatalogModels(settings).length;
|
|
4015
|
+
if (curatedCount === 0 && customModels.length === 0) return settings;
|
|
3249
4016
|
return {
|
|
3250
4017
|
...settings,
|
|
3251
4018
|
modelProvidersJson: JSON.stringify([
|
|
3252
|
-
...
|
|
3253
|
-
gatewayRegistryProvider(settings, {
|
|
4019
|
+
...withoutWorkspace,
|
|
4020
|
+
gatewayRegistryProvider(settings, {
|
|
4021
|
+
kind: "vercel-gateway-workspace",
|
|
4022
|
+
customModels,
|
|
4023
|
+
}),
|
|
3254
4024
|
]),
|
|
3255
4025
|
};
|
|
3256
4026
|
}
|
|
3257
4027
|
|
|
3258
4028
|
/** Runtime overlay after the worker resolves the workspace's encrypted key. */
|
|
3259
|
-
export function withWorkspaceGatewayCredential(
|
|
4029
|
+
export function withWorkspaceGatewayCredential(
|
|
4030
|
+
settings: Settings,
|
|
4031
|
+
apiKey: string,
|
|
4032
|
+
customModels: readonly {
|
|
4033
|
+
upstreamModelId: string;
|
|
4034
|
+
label?: string | null;
|
|
4035
|
+
}[] = [],
|
|
4036
|
+
): Settings {
|
|
3260
4037
|
if (!apiKey.trim()) {
|
|
3261
4038
|
throw new Error("workspace AI Gateway credential is empty");
|
|
3262
4039
|
}
|
|
3263
|
-
const catalogSettings = withWorkspaceGatewayCatalogProvider(settings);
|
|
4040
|
+
const catalogSettings = withWorkspaceGatewayCatalogProvider(settings, customModels);
|
|
3264
4041
|
const providers = parseModelProvidersJson(catalogSettings.modelProvidersJson).map((provider) =>
|
|
3265
4042
|
provider.id === WORKSPACE_GATEWAY_PROVIDER_ID ? { ...provider, apiKey } : provider,
|
|
3266
4043
|
);
|
|
3267
4044
|
return { ...catalogSettings, modelProvidersJson: JSON.stringify(providers) };
|
|
3268
4045
|
}
|
|
3269
4046
|
|
|
4047
|
+
/** Static OpenRouter catalog overlay; it contains no concrete workspace credential. */
|
|
4048
|
+
export function withWorkspaceOpenRouterCatalogProvider(
|
|
4049
|
+
settings: Settings,
|
|
4050
|
+
customModels: readonly {
|
|
4051
|
+
upstreamModelId: string;
|
|
4052
|
+
label?: string | null;
|
|
4053
|
+
}[] = [],
|
|
4054
|
+
): Settings {
|
|
4055
|
+
const providers = parseModelProvidersJson(settings.modelProvidersJson);
|
|
4056
|
+
const withoutWorkspace = providers.filter(
|
|
4057
|
+
(provider) => provider.id !== WORKSPACE_OPENROUTER_PROVIDER_ID,
|
|
4058
|
+
);
|
|
4059
|
+
const provider = openRouterRegistryProvider(settings, {
|
|
4060
|
+
kind: "openrouter-workspace",
|
|
4061
|
+
customModels,
|
|
4062
|
+
});
|
|
4063
|
+
if (!provider) return settings;
|
|
4064
|
+
return {
|
|
4065
|
+
...settings,
|
|
4066
|
+
modelProvidersJson: JSON.stringify([...withoutWorkspace, provider]),
|
|
4067
|
+
};
|
|
4068
|
+
}
|
|
4069
|
+
|
|
4070
|
+
/** Runtime overlay after the worker resolves the workspace's encrypted OpenRouter key. */
|
|
4071
|
+
export function withWorkspaceOpenRouterCredential(
|
|
4072
|
+
settings: Settings,
|
|
4073
|
+
apiKey: string,
|
|
4074
|
+
customModels: readonly {
|
|
4075
|
+
upstreamModelId: string;
|
|
4076
|
+
label?: string | null;
|
|
4077
|
+
}[] = [],
|
|
4078
|
+
): Settings {
|
|
4079
|
+
if (!apiKey.trim()) {
|
|
4080
|
+
throw new Error("workspace OpenRouter credential is empty");
|
|
4081
|
+
}
|
|
4082
|
+
const catalogSettings = withWorkspaceOpenRouterCatalogProvider(settings, customModels);
|
|
4083
|
+
const providers = parseModelProvidersJson(catalogSettings.modelProvidersJson).map((provider) =>
|
|
4084
|
+
provider.id === WORKSPACE_OPENROUTER_PROVIDER_ID ? { ...provider, apiKey } : provider,
|
|
4085
|
+
);
|
|
4086
|
+
return { ...catalogSettings, modelProvidersJson: JSON.stringify(providers) };
|
|
4087
|
+
}
|
|
4088
|
+
|
|
4089
|
+
/** Secret-free organization Vercel AI Gateway catalog overlay. */
|
|
4090
|
+
export function withOrganizationGatewayCatalogProvider(
|
|
4091
|
+
settings: Settings,
|
|
4092
|
+
customModels: readonly { upstreamModelId: string; label?: string | null }[] = [],
|
|
4093
|
+
): Settings {
|
|
4094
|
+
if (customModels.length === 0) return settings;
|
|
4095
|
+
const providers = parseModelProvidersJson(settings.modelProvidersJson).filter(
|
|
4096
|
+
(provider) => provider.id !== ORGANIZATION_GATEWAY_PROVIDER_ID,
|
|
4097
|
+
);
|
|
4098
|
+
const provider = gatewayRegistryProvider(settings, {
|
|
4099
|
+
kind: "vercel-gateway-organization",
|
|
4100
|
+
customModels,
|
|
4101
|
+
});
|
|
4102
|
+
return { ...settings, modelProvidersJson: JSON.stringify([...providers, provider]) };
|
|
4103
|
+
}
|
|
4104
|
+
|
|
4105
|
+
export function withOrganizationGatewayCredential(
|
|
4106
|
+
settings: Settings,
|
|
4107
|
+
apiKey: string,
|
|
4108
|
+
customModels: readonly { upstreamModelId: string; label?: string | null }[] = [],
|
|
4109
|
+
): Settings {
|
|
4110
|
+
if (!apiKey.trim()) throw new Error("organization AI Gateway credential is empty");
|
|
4111
|
+
const catalog = withOrganizationGatewayCatalogProvider(settings, customModels);
|
|
4112
|
+
const providers = parseModelProvidersJson(catalog.modelProvidersJson).map((provider) =>
|
|
4113
|
+
provider.id === ORGANIZATION_GATEWAY_PROVIDER_ID ? { ...provider, apiKey } : provider,
|
|
4114
|
+
);
|
|
4115
|
+
return { ...catalog, modelProvidersJson: JSON.stringify(providers) };
|
|
4116
|
+
}
|
|
4117
|
+
|
|
4118
|
+
/** Secret-free organization OpenRouter catalog overlay. */
|
|
4119
|
+
export function withOrganizationOpenRouterCatalogProvider(
|
|
4120
|
+
settings: Settings,
|
|
4121
|
+
customModels: readonly { upstreamModelId: string; label?: string | null }[] = [],
|
|
4122
|
+
): Settings {
|
|
4123
|
+
const providers = parseModelProvidersJson(settings.modelProvidersJson).filter(
|
|
4124
|
+
(provider) => provider.id !== ORGANIZATION_OPENROUTER_PROVIDER_ID,
|
|
4125
|
+
);
|
|
4126
|
+
const provider = openRouterRegistryProvider(settings, {
|
|
4127
|
+
kind: "openrouter-organization",
|
|
4128
|
+
customModels,
|
|
4129
|
+
});
|
|
4130
|
+
return provider
|
|
4131
|
+
? { ...settings, modelProvidersJson: JSON.stringify([...providers, provider]) }
|
|
4132
|
+
: settings;
|
|
4133
|
+
}
|
|
4134
|
+
|
|
4135
|
+
export function withOrganizationOpenRouterCredential(
|
|
4136
|
+
settings: Settings,
|
|
4137
|
+
apiKey: string,
|
|
4138
|
+
customModels: readonly { upstreamModelId: string; label?: string | null }[] = [],
|
|
4139
|
+
): Settings {
|
|
4140
|
+
if (!apiKey.trim()) throw new Error("organization OpenRouter credential is empty");
|
|
4141
|
+
const catalog = withOrganizationOpenRouterCatalogProvider(settings, customModels);
|
|
4142
|
+
const providers = parseModelProvidersJson(catalog.modelProvidersJson).map((provider) =>
|
|
4143
|
+
provider.id === ORGANIZATION_OPENROUTER_PROVIDER_ID ? { ...provider, apiKey } : provider,
|
|
4144
|
+
);
|
|
4145
|
+
return { ...catalog, modelProvidersJson: JSON.stringify(providers) };
|
|
4146
|
+
}
|
|
4147
|
+
|
|
3270
4148
|
/** OpenAI GPT-5.6 Fast mode is 2× Standard list rates (service_tier fast/priority). */
|
|
3271
4149
|
const GPT56_FAST_BILLING_MULTIPLIER_BPS = 20_000;
|
|
3272
4150
|
|
|
@@ -3318,6 +4196,8 @@ export function productShortLabelForModelId(modelId: string): string | null {
|
|
|
3318
4196
|
return "5.6 Terra";
|
|
3319
4197
|
case "gpt-5.6-luna":
|
|
3320
4198
|
return "5.6 Luna";
|
|
4199
|
+
case "gpt-6-astra":
|
|
4200
|
+
return "6 Astra";
|
|
3321
4201
|
default:
|
|
3322
4202
|
return null;
|
|
3323
4203
|
}
|
|
@@ -3353,7 +4233,11 @@ function builtinLatencyModesForModel(modelId: string): Array<{
|
|
|
3353
4233
|
runnable: boolean;
|
|
3354
4234
|
billingMultiplierBps?: number;
|
|
3355
4235
|
}> {
|
|
3356
|
-
if (
|
|
4236
|
+
if (
|
|
4237
|
+
isBuiltinGpt56ModelId(modelId) ||
|
|
4238
|
+
modelId.startsWith("codex/gpt-5.6-") ||
|
|
4239
|
+
modelId === "codex/gpt-6-astra"
|
|
4240
|
+
) {
|
|
3357
4241
|
return [
|
|
3358
4242
|
{ id: "standard", upstream: "supported", runnable: true },
|
|
3359
4243
|
{
|
|
@@ -3466,33 +4350,71 @@ function assertLatencyModeRunnable(
|
|
|
3466
4350
|
}
|
|
3467
4351
|
}
|
|
3468
4352
|
|
|
3469
|
-
function registryCredentialSource(provider:
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
4353
|
+
function registryCredentialSource(provider: InternalRegistryProvider): CredentialSourceV1 {
|
|
4354
|
+
switch (provider.kind) {
|
|
4355
|
+
case "anonymous":
|
|
4356
|
+
return { kind: "deployment", mechanism: "none" };
|
|
4357
|
+
case "codex-subscription":
|
|
4358
|
+
return { kind: "connected_subscription", provider: "codex" };
|
|
4359
|
+
case "xai-subscription":
|
|
4360
|
+
return { kind: "connected_subscription", provider: "xai" };
|
|
4361
|
+
case "vercel-gateway-workspace":
|
|
4362
|
+
case "openrouter-workspace":
|
|
4363
|
+
return { kind: "workspace_connection", mechanism: "api_key" };
|
|
4364
|
+
case "vercel-gateway-organization":
|
|
4365
|
+
case "openrouter-organization":
|
|
4366
|
+
return { kind: "organization_connection", mechanism: "api_key" };
|
|
4367
|
+
case "api-key":
|
|
4368
|
+
case "vercel-gateway-managed":
|
|
4369
|
+
case "openrouter-managed":
|
|
4370
|
+
return { kind: "deployment", mechanism: "api_key" };
|
|
4371
|
+
default: {
|
|
4372
|
+
const _exhaustive: never = provider.kind;
|
|
4373
|
+
return _exhaustive;
|
|
4374
|
+
}
|
|
3481
4375
|
}
|
|
3482
|
-
return { kind: "deployment", mechanism: "api_key" };
|
|
3483
4376
|
}
|
|
3484
4377
|
|
|
3485
|
-
function registryBilling(provider:
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
4378
|
+
function registryBilling(provider: InternalRegistryProvider): BillingAttributionV1 {
|
|
4379
|
+
switch (provider.kind) {
|
|
4380
|
+
case "anonymous":
|
|
4381
|
+
case "openrouter-managed":
|
|
4382
|
+
return { upstreamPayer: "deployment", metering: "external" };
|
|
4383
|
+
case "codex-subscription":
|
|
4384
|
+
case "xai-subscription":
|
|
4385
|
+
return { upstreamPayer: "connected_subscription", metering: "external" };
|
|
4386
|
+
case "vercel-gateway-workspace":
|
|
4387
|
+
case "openrouter-workspace":
|
|
4388
|
+
return { upstreamPayer: "workspace", metering: "external" };
|
|
4389
|
+
case "vercel-gateway-organization":
|
|
4390
|
+
case "openrouter-organization":
|
|
4391
|
+
return { upstreamPayer: "organization", metering: "external" };
|
|
4392
|
+
case "api-key":
|
|
4393
|
+
case "vercel-gateway-managed":
|
|
4394
|
+
return { upstreamPayer: "deployment", metering: "opengeni_credits" };
|
|
4395
|
+
default: {
|
|
4396
|
+
const _exhaustive: never = provider.kind;
|
|
4397
|
+
return _exhaustive;
|
|
4398
|
+
}
|
|
3494
4399
|
}
|
|
3495
|
-
|
|
4400
|
+
}
|
|
4401
|
+
|
|
4402
|
+
function configuredCostForModel(
|
|
4403
|
+
settings: Settings,
|
|
4404
|
+
productModelId: string,
|
|
4405
|
+
credentialSource: CredentialSourceV1,
|
|
4406
|
+
): ConfiguredModelCostClass {
|
|
4407
|
+
if (credentialSource.kind === "workspace_connection") return "workspace";
|
|
4408
|
+
if (credentialSource.kind === "organization_connection") return "organization";
|
|
4409
|
+
if (credentialSource.kind === "connected_subscription") return "subscription";
|
|
4410
|
+
return parseModelCostPolicyJson(settings.modelCostPolicyJson)[productModelId] ?? "credits";
|
|
4411
|
+
}
|
|
4412
|
+
|
|
4413
|
+
export function modelCostClassForConfiguredModel(
|
|
4414
|
+
_settings: Settings,
|
|
4415
|
+
model: Pick<ConfiguredModel, "cost">,
|
|
4416
|
+
): ConfiguredModelCostClass {
|
|
4417
|
+
return model.cost;
|
|
3496
4418
|
}
|
|
3497
4419
|
|
|
3498
4420
|
function builtinCredentialSource(settings: Settings): CredentialSourceV1 {
|
|
@@ -3578,6 +4500,10 @@ function definitionVersionFor(
|
|
|
3578
4500
|
billing: model.billing,
|
|
3579
4501
|
executionLimits: model.executionLimits,
|
|
3580
4502
|
capabilities: model.capabilities,
|
|
4503
|
+
// Workspace-facing free/credits classification is a separate live
|
|
4504
|
+
// deployment policy. Operators must drain/fence accepted turns before
|
|
4505
|
+
// changing it; it is intentionally not a second executable-definition
|
|
4506
|
+
// freeze inside TurnExecutionPolicyV1.
|
|
3581
4507
|
...(model.requestPolicy ? { requestPolicy: model.requestPolicy } : {}),
|
|
3582
4508
|
pricing: model.pricing ?? null,
|
|
3583
4509
|
});
|
|
@@ -3593,7 +4519,9 @@ function legacyImplicitOpenAiDefinitionVersionFor(
|
|
|
3593
4519
|
): string | null {
|
|
3594
4520
|
if (provider.wireProfile !== "openai") return null;
|
|
3595
4521
|
const { definitionVersion: _definitionVersion, ...modelWithoutVersion } = model;
|
|
3596
|
-
return definitionVersionFor(modelWithoutVersion, provider, {
|
|
4522
|
+
return definitionVersionFor(modelWithoutVersion, provider, {
|
|
4523
|
+
includeWireProfile: false,
|
|
4524
|
+
});
|
|
3597
4525
|
}
|
|
3598
4526
|
|
|
3599
4527
|
/**
|
|
@@ -3619,7 +4547,10 @@ function builtinProviderLabel(settings: Pick<Settings, "openaiProvider">): strin
|
|
|
3619
4547
|
* registry entry for the rest. Registry ids may not collide with the built-in
|
|
3620
4548
|
* id — validateSettings rejects that at boot.
|
|
3621
4549
|
*/
|
|
3622
|
-
export function configuredProviders(
|
|
4550
|
+
export function configuredProviders(
|
|
4551
|
+
settings: Settings,
|
|
4552
|
+
source: NodeJS.ProcessEnv = process.env,
|
|
4553
|
+
): ResolvedModelProvider[] {
|
|
3623
4554
|
const credentialSource = builtinCredentialSource(settings);
|
|
3624
4555
|
const builtin: ResolvedModelProvider = {
|
|
3625
4556
|
id: builtinProviderId(settings),
|
|
@@ -3650,7 +4581,7 @@ export function configuredProviders(settings: Settings): ResolvedModelProvider[]
|
|
|
3650
4581
|
wireProfile: provider.wireProfile,
|
|
3651
4582
|
builtin: false,
|
|
3652
4583
|
baseUrl: provider.baseUrl,
|
|
3653
|
-
apiKey: resolveProviderApiKey(provider),
|
|
4584
|
+
apiKey: resolveProviderApiKey(provider, source),
|
|
3654
4585
|
defaultQuery: provider.defaultQuery,
|
|
3655
4586
|
defaultHeaders: provider.defaultHeaders,
|
|
3656
4587
|
publicDefaultQueryNames: provider.publicDefaultQueryNames,
|
|
@@ -3685,7 +4616,7 @@ export function withCodexCatalogProvider(settings: Settings): Settings {
|
|
|
3685
4616
|
...legacyModelCapabilities(settings, {
|
|
3686
4617
|
reasoningEffort: true,
|
|
3687
4618
|
hostedWebSearch: true,
|
|
3688
|
-
vision: slug.startsWith("gpt-5.6-"),
|
|
4619
|
+
vision: slug.startsWith("gpt-5.6-") || slug === "gpt-6-astra",
|
|
3689
4620
|
}),
|
|
3690
4621
|
...(builtinPromptCachingForModel(`${CODEX_MODEL_ID_PREFIX}${slug}`)
|
|
3691
4622
|
? {
|
|
@@ -3750,8 +4681,14 @@ export function withXaiSubscriptionCatalogProvider(settings: Settings): Settings
|
|
|
3750
4681
|
{ id: "standard", upstream: "supported", runnable: true },
|
|
3751
4682
|
{ id: "fast", upstream: "supported", runnable: true },
|
|
3752
4683
|
];
|
|
3753
|
-
capabilities.hostedTools.xSearch = {
|
|
3754
|
-
|
|
4684
|
+
capabilities.hostedTools.xSearch = {
|
|
4685
|
+
upstream: "supported",
|
|
4686
|
+
runnable: true,
|
|
4687
|
+
};
|
|
4688
|
+
capabilities.hostedTools.imageGeneration = {
|
|
4689
|
+
upstream: "supported",
|
|
4690
|
+
runnable: true,
|
|
4691
|
+
};
|
|
3755
4692
|
return {
|
|
3756
4693
|
id: `${XAI_SUBSCRIPTION_MODEL_ID_PREFIX}${slug}`,
|
|
3757
4694
|
upstreamModelId: slug,
|
|
@@ -3769,7 +4706,10 @@ export function withXaiSubscriptionCatalogProvider(settings: Settings): Settings
|
|
|
3769
4706
|
};
|
|
3770
4707
|
}),
|
|
3771
4708
|
};
|
|
3772
|
-
return {
|
|
4709
|
+
return {
|
|
4710
|
+
...settings,
|
|
4711
|
+
modelProvidersJson: JSON.stringify([...providers, provider]),
|
|
4712
|
+
};
|
|
3773
4713
|
}
|
|
3774
4714
|
|
|
3775
4715
|
/**
|
|
@@ -3796,6 +4736,9 @@ export function policyProviderIdForModel(settings: Settings, modelId: string): s
|
|
|
3796
4736
|
if (canonicalModelId.startsWith(WORKSPACE_GATEWAY_MODEL_ID_PREFIX)) {
|
|
3797
4737
|
return WORKSPACE_GATEWAY_PROVIDER_ID;
|
|
3798
4738
|
}
|
|
4739
|
+
if (canonicalModelId.startsWith(WORKSPACE_OPENROUTER_MODEL_ID_PREFIX)) {
|
|
4740
|
+
return WORKSPACE_OPENROUTER_PROVIDER_ID;
|
|
4741
|
+
}
|
|
3799
4742
|
const configured = configuredModels(settings).find((model) => model.id === canonicalModelId);
|
|
3800
4743
|
return configured?.providerId ?? builtinProviderId(settings);
|
|
3801
4744
|
}
|
|
@@ -3823,15 +4766,21 @@ function resolvedExecutionLimits(
|
|
|
3823
4766
|
function finalizeConfiguredModel(
|
|
3824
4767
|
settings: Settings,
|
|
3825
4768
|
provider: ResolvedModelProvider,
|
|
3826
|
-
input: Omit<ConfiguredModel, "schemaVersion" | "definitionVersion" | "executionLimits">,
|
|
4769
|
+
input: Omit<ConfiguredModel, "schemaVersion" | "definitionVersion" | "executionLimits" | "cost">,
|
|
3827
4770
|
): ConfiguredModel {
|
|
3828
4771
|
const requestPolicy =
|
|
3829
|
-
provider.kind === "vercel-gateway-managed" ||
|
|
3830
|
-
|
|
4772
|
+
provider.kind === "vercel-gateway-managed" ||
|
|
4773
|
+
provider.kind === "vercel-gateway-workspace" ||
|
|
4774
|
+
provider.kind === "vercel-gateway-organization"
|
|
4775
|
+
? gatewayRequestPolicyForUpstreamModel(
|
|
4776
|
+
input.upstreamModelId,
|
|
4777
|
+
configuredGatewayCatalogModels(settings),
|
|
4778
|
+
)
|
|
3831
4779
|
: undefined;
|
|
3832
4780
|
const modelWithoutVersion: Omit<ConfiguredModel, "definitionVersion"> = {
|
|
3833
4781
|
schemaVersion: 1,
|
|
3834
4782
|
...input,
|
|
4783
|
+
cost: configuredCostForModel(settings, input.id, input.credentialSource),
|
|
3835
4784
|
...(requestPolicy ? { requestPolicy } : {}),
|
|
3836
4785
|
executionLimits: resolvedExecutionLimits(settings, input),
|
|
3837
4786
|
};
|
|
@@ -3882,10 +4831,13 @@ function assertUniqueModelIdentities(models: ConfiguredModel[]): void {
|
|
|
3882
4831
|
* default false). De-duplicated by id (first wins) so the default model stays
|
|
3883
4832
|
* first and the built-in allow-list takes precedence over registry entries.
|
|
3884
4833
|
*/
|
|
3885
|
-
export function configuredModels(
|
|
4834
|
+
export function configuredModels(
|
|
4835
|
+
settings: Settings,
|
|
4836
|
+
source: NodeJS.ProcessEnv = process.env,
|
|
4837
|
+
): ConfiguredModel[] {
|
|
3886
4838
|
const builtinId = builtinProviderId(settings);
|
|
3887
4839
|
const builtinLabel = builtinProviderLabel(settings);
|
|
3888
|
-
const providers = configuredProviders(settings);
|
|
4840
|
+
const providers = configuredProviders(settings, source);
|
|
3889
4841
|
const providerById = new Map(providers.map((provider) => [provider.id, provider]));
|
|
3890
4842
|
const pricingSchedules = configuredModelPricingSchedules(settings);
|
|
3891
4843
|
// The built-in (OpenAI/Azure) provider must NEVER claim a registry-namespaced
|
|
@@ -4016,7 +4968,12 @@ export function configuredModels(settings: Settings): ConfiguredModel[] {
|
|
|
4016
4968
|
}
|
|
4017
4969
|
}
|
|
4018
4970
|
assertUniqueModelIdentities(out);
|
|
4019
|
-
|
|
4971
|
+
const defaultIndex = out.findIndex(
|
|
4972
|
+
(model) => model.id === settings.openaiModel || model.aliases.includes(settings.openaiModel),
|
|
4973
|
+
);
|
|
4974
|
+
return defaultIndex > 0
|
|
4975
|
+
? [out[defaultIndex]!, ...out.slice(0, defaultIndex), ...out.slice(defaultIndex + 1)]
|
|
4976
|
+
: out;
|
|
4020
4977
|
}
|
|
4021
4978
|
|
|
4022
4979
|
/** Resolve a known canonical id or alias. Unknown strings are returned unchanged. */
|
|
@@ -4087,11 +5044,50 @@ function settingsForTurnExecutionPolicy(settings: Settings, modelId: string): Se
|
|
|
4087
5044
|
return withXaiSubscriptionCatalogProvider(settings);
|
|
4088
5045
|
}
|
|
4089
5046
|
if (modelId.startsWith(WORKSPACE_GATEWAY_MODEL_ID_PREFIX)) {
|
|
5047
|
+
// API/worker workspace boundaries may already have overlaid durable custom
|
|
5048
|
+
// rows and, at execution time, the decrypted workspace key. Re-applying an
|
|
5049
|
+
// empty static overlay would silently discard both. Only synthesize the
|
|
5050
|
+
// curated fallback when this exact model is not already executable.
|
|
5051
|
+
if (resolveModelProvider(settings, modelId)) {
|
|
5052
|
+
return settings;
|
|
5053
|
+
}
|
|
4090
5054
|
return withWorkspaceGatewayCatalogProvider(settings);
|
|
4091
5055
|
}
|
|
5056
|
+
if (modelId.startsWith(WORKSPACE_OPENROUTER_MODEL_ID_PREFIX)) {
|
|
5057
|
+
if (resolveModelProvider(settings, modelId)) {
|
|
5058
|
+
return settings;
|
|
5059
|
+
}
|
|
5060
|
+
return withWorkspaceOpenRouterCatalogProvider(settings);
|
|
5061
|
+
}
|
|
5062
|
+
if (modelId.startsWith(ORGANIZATION_GATEWAY_MODEL_ID_PREFIX)) {
|
|
5063
|
+
return resolveModelProvider(settings, modelId)
|
|
5064
|
+
? settings
|
|
5065
|
+
: withOrganizationGatewayCatalogProvider(settings);
|
|
5066
|
+
}
|
|
5067
|
+
if (modelId.startsWith(ORGANIZATION_OPENROUTER_MODEL_ID_PREFIX)) {
|
|
5068
|
+
return resolveModelProvider(settings, modelId)
|
|
5069
|
+
? settings
|
|
5070
|
+
: withOrganizationOpenRouterCatalogProvider(settings);
|
|
5071
|
+
}
|
|
4092
5072
|
return settings;
|
|
4093
5073
|
}
|
|
4094
5074
|
|
|
5075
|
+
/**
|
|
5076
|
+
* Resolve the static catalog identity used by an accepted turn. Subscription
|
|
5077
|
+
* overlays contain no account or bearer and do not prove connection readiness;
|
|
5078
|
+
* callers must keep their live credential/readiness gate authoritative.
|
|
5079
|
+
*/
|
|
5080
|
+
export function resolveModelProviderForTurn(
|
|
5081
|
+
settings: Settings,
|
|
5082
|
+
modelId: string,
|
|
5083
|
+
): ReturnType<typeof resolveModelProvider> {
|
|
5084
|
+
const catalogSettings = settingsForTurnExecutionPolicy(settings, modelId);
|
|
5085
|
+
return resolveModelProvider(
|
|
5086
|
+
catalogSettings,
|
|
5087
|
+
canonicalizeConfiguredModelId(catalogSettings, modelId),
|
|
5088
|
+
);
|
|
5089
|
+
}
|
|
5090
|
+
|
|
4095
5091
|
/**
|
|
4096
5092
|
* Build a trusted, secret-safe execution policy from the normalized catalog.
|
|
4097
5093
|
* The Codex overlay here contains static product/provider identity only; it
|
|
@@ -4203,10 +5199,9 @@ export function assertTurnExecutionPolicyMatchesConfigV1(
|
|
|
4203
5199
|
|
|
4204
5200
|
/**
|
|
4205
5201
|
* Effective per-model pricing schedules. Merge order (later wins): built-in
|
|
4206
|
-
* flat defaults → registry model flat/scheduled pricing → explicit
|
|
4207
|
-
* OPENGENI_MODEL_PRICING_JSON.
|
|
4208
|
-
*
|
|
4209
|
-
* exact.
|
|
5202
|
+
* flat defaults → registry model flat/scheduled pricing → explicit
|
|
5203
|
+
* OPENGENI_MODEL_PRICING_JSON. Explicit entries may be legacy flat prices or a
|
|
5204
|
+
* complete schedule, and always replace the lower-precedence schedule.
|
|
4210
5205
|
*/
|
|
4211
5206
|
export function configuredModelPricingSchedules(
|
|
4212
5207
|
settings: Settings,
|
|
@@ -4228,7 +5223,7 @@ export function configuredModelPricingSchedules(
|
|
|
4228
5223
|
const configured = Object.fromEntries(
|
|
4229
5224
|
Object.entries(parseModelPricingJson(settings.modelPricingJson)).map(([model, pricing]) => [
|
|
4230
5225
|
model,
|
|
4231
|
-
|
|
5226
|
+
normalizeModelPricingSchedule(pricing),
|
|
4232
5227
|
]),
|
|
4233
5228
|
);
|
|
4234
5229
|
return {
|
|
@@ -4409,6 +5404,37 @@ export function calculateGatewayReportedCostMicros(
|
|
|
4409
5404
|
.creditCostMicros;
|
|
4410
5405
|
}
|
|
4411
5406
|
|
|
5407
|
+
type GatewayReportedCostDecimal = {
|
|
5408
|
+
providerNumerator: bigint;
|
|
5409
|
+
decimalScale: bigint;
|
|
5410
|
+
providerCostMicros: number;
|
|
5411
|
+
};
|
|
5412
|
+
|
|
5413
|
+
function parseGatewayReportedCostDecimal(inferenceCostUsd: string): GatewayReportedCostDecimal {
|
|
5414
|
+
const match = /^(0|[1-9]\d*)(?:\.(\d{1,18}))?$/.exec(inferenceCostUsd);
|
|
5415
|
+
if (!match) {
|
|
5416
|
+
throw new Error("Invalid AI Gateway inference cost");
|
|
5417
|
+
}
|
|
5418
|
+
const fraction = match[2] ?? "";
|
|
5419
|
+
const decimalDigits = BigInt(`${match[1]}${fraction}`);
|
|
5420
|
+
const decimalScale = 10n ** BigInt(fraction.length);
|
|
5421
|
+
const providerNumerator = decimalDigits * 1_000_000n;
|
|
5422
|
+
const providerCostMicros = (providerNumerator + decimalScale - 1n) / decimalScale;
|
|
5423
|
+
if (providerCostMicros > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
5424
|
+
throw new Error("AI Gateway inference cost exceeds the supported billing range");
|
|
5425
|
+
}
|
|
5426
|
+
return {
|
|
5427
|
+
providerNumerator,
|
|
5428
|
+
decimalScale,
|
|
5429
|
+
providerCostMicros: Number(providerCostMicros),
|
|
5430
|
+
};
|
|
5431
|
+
}
|
|
5432
|
+
|
|
5433
|
+
/** Exact provider-reported Gateway cost without requiring an OpenGeni price schedule. */
|
|
5434
|
+
export function calculateGatewayReportedProviderCostMicros(inferenceCostUsd: string): number {
|
|
5435
|
+
return parseGatewayReportedCostDecimal(inferenceCostUsd).providerCostMicros;
|
|
5436
|
+
}
|
|
5437
|
+
|
|
4412
5438
|
export function calculateGatewayReportedCostBreakdown(
|
|
4413
5439
|
settings: Settings,
|
|
4414
5440
|
model: string,
|
|
@@ -4420,27 +5446,17 @@ export function calculateGatewayReportedCostBreakdown(
|
|
|
4420
5446
|
throw new Error(`Missing model pricing for ${model}`);
|
|
4421
5447
|
}
|
|
4422
5448
|
const pricing = selectModelPricing(schedule, positiveInt(options?.inputTokens));
|
|
4423
|
-
const
|
|
4424
|
-
|
|
4425
|
-
throw new Error("Invalid AI Gateway inference cost");
|
|
4426
|
-
}
|
|
4427
|
-
const fraction = match[2] ?? "";
|
|
4428
|
-
const decimalDigits = BigInt(`${match[1]}${fraction}`);
|
|
4429
|
-
const decimalScale = 10n ** BigInt(fraction.length);
|
|
4430
|
-
const providerNumerator = decimalDigits * 1_000_000n;
|
|
4431
|
-
const providerMicros = (providerNumerator + decimalScale - 1n) / decimalScale;
|
|
5449
|
+
const { providerNumerator, decimalScale, providerCostMicros } =
|
|
5450
|
+
parseGatewayReportedCostDecimal(inferenceCostUsd);
|
|
4432
5451
|
const marginBps = BigInt(10_000 + (pricing.marginBps ?? 0));
|
|
4433
5452
|
const numerator = providerNumerator * marginBps;
|
|
4434
5453
|
const denominator = decimalScale * 10_000n;
|
|
4435
5454
|
const creditMicros = (numerator + denominator - 1n) / denominator;
|
|
4436
|
-
if (
|
|
4437
|
-
providerMicros > BigInt(Number.MAX_SAFE_INTEGER) ||
|
|
4438
|
-
creditMicros > BigInt(Number.MAX_SAFE_INTEGER)
|
|
4439
|
-
) {
|
|
5455
|
+
if (creditMicros > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
4440
5456
|
throw new Error("AI Gateway inference cost exceeds the supported billing range");
|
|
4441
5457
|
}
|
|
4442
5458
|
return {
|
|
4443
|
-
providerCostMicros
|
|
5459
|
+
providerCostMicros,
|
|
4444
5460
|
creditCostMicros: Number(creditMicros),
|
|
4445
5461
|
};
|
|
4446
5462
|
}
|
|
@@ -4890,7 +5906,9 @@ export function parseMcpServers(raw: string | undefined): unknown[] | undefined
|
|
|
4890
5906
|
}
|
|
4891
5907
|
}
|
|
4892
5908
|
|
|
4893
|
-
export function parseModelPricingJson(
|
|
5909
|
+
export function parseModelPricingJson(
|
|
5910
|
+
raw: string,
|
|
5911
|
+
): Record<string, ModelPricing | ModelPricingScheduleV1> {
|
|
4894
5912
|
if (!raw.trim() || raw.trim() === "{}") {
|
|
4895
5913
|
return {};
|
|
4896
5914
|
}
|
|
@@ -4904,12 +5922,12 @@ export function parseModelPricingJson(raw: string): Record<string, ModelPricing>
|
|
|
4904
5922
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
4905
5923
|
throw new Error("OPENGENI_MODEL_PRICING_JSON must be a JSON object keyed by model name");
|
|
4906
5924
|
}
|
|
4907
|
-
const out: Record<string, ModelPricing> = {};
|
|
5925
|
+
const out: Record<string, ModelPricing | ModelPricingScheduleV1> = {};
|
|
4908
5926
|
for (const [model, value] of Object.entries(parsed)) {
|
|
4909
5927
|
if (!model.trim()) {
|
|
4910
5928
|
throw new Error("OPENGENI_MODEL_PRICING_JSON contains an empty model name");
|
|
4911
5929
|
}
|
|
4912
|
-
out[model] = ModelPricingSchema.parse(value);
|
|
5930
|
+
out[model] = z.union([ModelPricingSchema, ModelPricingScheduleSchema]).parse(value);
|
|
4913
5931
|
}
|
|
4914
5932
|
return out;
|
|
4915
5933
|
}
|
|
@@ -5120,12 +6138,19 @@ function calculateEntryCostMicros(pricing: ModelPricing, entry: ModelUsageInput)
|
|
|
5120
6138
|
const inputTokens = positiveInt(entry.inputTokens);
|
|
5121
6139
|
const outputTokens = positiveInt(entry.outputTokens);
|
|
5122
6140
|
const cachedTokens = Math.min(inputTokens, cachedInputTokens(entry));
|
|
5123
|
-
const
|
|
6141
|
+
const cacheWriteTokens = Math.min(
|
|
6142
|
+
Math.max(0, inputTokens - cachedTokens),
|
|
6143
|
+
cacheWriteInputTokens(entry),
|
|
6144
|
+
);
|
|
6145
|
+
const uncachedInputTokens = Math.max(0, inputTokens - cachedTokens - cacheWriteTokens);
|
|
5124
6146
|
const cachedInputRate =
|
|
5125
6147
|
pricing.cachedInputMicrosPerMillionTokens ?? pricing.inputMicrosPerMillionTokens;
|
|
6148
|
+
const cacheWriteRate =
|
|
6149
|
+
pricing.cacheWriteMicrosPerMillionTokens ?? pricing.inputMicrosPerMillionTokens;
|
|
5126
6150
|
return (
|
|
5127
6151
|
Math.ceil((uncachedInputTokens * pricing.inputMicrosPerMillionTokens) / 1_000_000) +
|
|
5128
6152
|
Math.ceil((cachedTokens * cachedInputRate) / 1_000_000) +
|
|
6153
|
+
Math.ceil((cacheWriteTokens * cacheWriteRate) / 1_000_000) +
|
|
5129
6154
|
Math.ceil((outputTokens * pricing.outputMicrosPerMillionTokens) / 1_000_000)
|
|
5130
6155
|
);
|
|
5131
6156
|
}
|
|
@@ -5146,6 +6171,19 @@ function cachedInputTokens(entry: ModelUsageInput): number {
|
|
|
5146
6171
|
return total;
|
|
5147
6172
|
}
|
|
5148
6173
|
|
|
6174
|
+
function cacheWriteInputTokens(entry: ModelUsageInput): number {
|
|
6175
|
+
const details = Array.isArray(entry.inputTokensDetails)
|
|
6176
|
+
? entry.inputTokensDetails
|
|
6177
|
+
: entry.inputTokensDetails
|
|
6178
|
+
? [entry.inputTokensDetails]
|
|
6179
|
+
: [];
|
|
6180
|
+
let total = 0;
|
|
6181
|
+
for (const detail of details) {
|
|
6182
|
+
total += positiveInt(detail.cache_write_tokens ?? detail.cacheWriteTokens);
|
|
6183
|
+
}
|
|
6184
|
+
return total;
|
|
6185
|
+
}
|
|
6186
|
+
|
|
5149
6187
|
function positiveInt(value: unknown): number {
|
|
5150
6188
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
|
|
5151
6189
|
}
|
|
@@ -5314,8 +6352,16 @@ function isDigestPinnedModalDesktopImage(settings: Settings): boolean {
|
|
|
5314
6352
|
);
|
|
5315
6353
|
}
|
|
5316
6354
|
|
|
5317
|
-
function validateSettings(settings: Settings): void {
|
|
6355
|
+
function validateSettings(settings: Settings, source: NodeJS.ProcessEnv = process.env): void {
|
|
5318
6356
|
temporalConnectionOptions(settings);
|
|
6357
|
+
if (
|
|
6358
|
+
settings.organizationUserSetupEmailTokenTransport === "query" &&
|
|
6359
|
+
!settings.organizationUserSetupQueryEdgeSanitizationConfirmed
|
|
6360
|
+
) {
|
|
6361
|
+
throw new Error(
|
|
6362
|
+
"OPENGENI_ORGANIZATION_USER_SETUP_QUERY_EDGE_SANITIZATION_CONFIRMED=true is required when OPENGENI_ORGANIZATION_USER_SETUP_EMAIL_TOKEN_TRANSPORT=query",
|
|
6363
|
+
);
|
|
6364
|
+
}
|
|
5319
6365
|
if (settings.goalIdleBackoffMs.some((delayMs) => delayMs > settings.goalIdleBackoffMaxMs)) {
|
|
5320
6366
|
throw new Error(
|
|
5321
6367
|
`OPENGENI_GOAL_IDLE_BACKOFF_MS entries must not exceed OPENGENI_GOAL_IDLE_BACKOFF_MAX_MS (${settings.goalIdleBackoffMaxMs})`,
|
|
@@ -5401,6 +6447,24 @@ function validateSettings(settings: Settings): void {
|
|
|
5401
6447
|
);
|
|
5402
6448
|
}
|
|
5403
6449
|
}
|
|
6450
|
+
if (settings.mcpOauthEnabled) {
|
|
6451
|
+
if (settings.productAccessMode === "configured") {
|
|
6452
|
+
throw new Error(
|
|
6453
|
+
"OPENGENI_MCP_OAUTH_ENABLED=true requires managed or local product access mode",
|
|
6454
|
+
);
|
|
6455
|
+
}
|
|
6456
|
+
const publicOrigin = canonicalPublicOrigin(settings.publicBaseUrl);
|
|
6457
|
+
if (!publicOrigin) {
|
|
6458
|
+
throw new Error(
|
|
6459
|
+
"OPENGENI_PUBLIC_BASE_URL must be a credential-free HTTP(S) origin when OPENGENI_MCP_OAUTH_ENABLED=true",
|
|
6460
|
+
);
|
|
6461
|
+
}
|
|
6462
|
+
if (!publicOrigin.startsWith("https://") && !["local", "test"].includes(settings.environment)) {
|
|
6463
|
+
throw new Error(
|
|
6464
|
+
"OPENGENI_PUBLIC_BASE_URL must use https when OPENGENI_MCP_OAUTH_ENABLED=true outside local/test",
|
|
6465
|
+
);
|
|
6466
|
+
}
|
|
6467
|
+
}
|
|
5404
6468
|
environmentsEncryptionKeyBytes(settings);
|
|
5405
6469
|
if (settings.integrationsEnabled) {
|
|
5406
6470
|
if (settings.productAccessMode === "managed" && !settings.publicBaseUrl) {
|
|
@@ -5620,15 +6684,6 @@ function validateSettings(settings: Settings): void {
|
|
|
5620
6684
|
if (settings.productAccessMode !== "managed" && settings.billingMode === "stripe") {
|
|
5621
6685
|
throw new Error("OPENGENI_BILLING_MODE=stripe requires OPENGENI_PRODUCT_ACCESS_MODE=managed");
|
|
5622
6686
|
}
|
|
5623
|
-
if (settings.billingMode === "stripe" || settings.usageLimitsMode === "managed") {
|
|
5624
|
-
const pricing = configuredModelPricing(settings);
|
|
5625
|
-
const missing = configuredAllowedModels(settings).filter((model) => !pricing[model]);
|
|
5626
|
-
if (missing.length > 0) {
|
|
5627
|
-
throw new Error(
|
|
5628
|
-
`Missing model pricing for managed billing model(s): ${missing.join(", ")}. Set OPENGENI_MODEL_PRICING_JSON.`,
|
|
5629
|
-
);
|
|
5630
|
-
}
|
|
5631
|
-
}
|
|
5632
6687
|
if (settings.usageLimitsMode === "static") {
|
|
5633
6688
|
const limits = configuredStaticUsageLimits(settings);
|
|
5634
6689
|
if (Object.keys(limits).length === 0) {
|
|
@@ -5819,6 +6874,14 @@ function validateSettings(settings: Settings): void {
|
|
|
5819
6874
|
throw new Error(`OPENGENI_MCP_SERVERS contains duplicate id ${server.id}`);
|
|
5820
6875
|
}
|
|
5821
6876
|
serverIds.add(server.id);
|
|
6877
|
+
if (
|
|
6878
|
+
server.connectionRef?.authoritySource === "host" &&
|
|
6879
|
+
!settings.hostMcpAuthoritySourceAdmissionEnabled
|
|
6880
|
+
) {
|
|
6881
|
+
throw new Error(
|
|
6882
|
+
"OPENGENI_MCP_SERVERS host-owned connection refs require OPENGENI_HOST_MCP_AUTHORITY_SOURCE_ADMISSION_ENABLED=true after the whole API/worker fleet is upgraded",
|
|
6883
|
+
);
|
|
6884
|
+
}
|
|
5822
6885
|
}
|
|
5823
6886
|
// --- sandbox lease cadence invariant (fail fast at boot) ---
|
|
5824
6887
|
// Holder TTLs are provider-neutral. Modal's finite hard/idle clocks and
|
|
@@ -5842,11 +6905,46 @@ function validateSettings(settings: Settings): void {
|
|
|
5842
6905
|
`more often than the controller-heartbeat horizon.`,
|
|
5843
6906
|
);
|
|
5844
6907
|
}
|
|
6908
|
+
if (settings.sandboxDrainSnapshotTimeoutMs !== undefined) {
|
|
6909
|
+
const drainCaptureTimeoutMs = sandboxArchiveCaptureTimeoutMs({
|
|
6910
|
+
sandboxSnapshotTimeoutMs: effectiveSandboxDrainSnapshotTimeoutMs(settings),
|
|
6911
|
+
});
|
|
6912
|
+
const requiredTransitionWaitMs =
|
|
6913
|
+
reaperPeriod + drainCaptureTimeoutMs + SANDBOX_LIFECYCLE_RETRY_HANDOFF_GRACE_MS;
|
|
6914
|
+
if (requiredTransitionWaitMs > SANDBOX_LIFECYCLE_TRANSITION_MAX_WAIT_MS) {
|
|
6915
|
+
throw new Error(
|
|
6916
|
+
`OPENGENI_SANDBOX_DRAIN_SNAPSHOT_TIMEOUT_MS (${settings.sandboxDrainSnapshotTimeoutMs}) ` +
|
|
6917
|
+
`requires a sandbox lifecycle transition wait of ${requiredTransitionWaitMs}ms after ` +
|
|
6918
|
+
`one reaper period and provider settlement, exceeding the ` +
|
|
6919
|
+
`${SANDBOX_LIFECYCLE_TRANSITION_MAX_WAIT_MS}ms limit. Lower the drain snapshot timeout ` +
|
|
6920
|
+
`or OPENGENI_SANDBOX_LEASE_REAPER_PERIOD_MS.`,
|
|
6921
|
+
);
|
|
6922
|
+
}
|
|
6923
|
+
}
|
|
6924
|
+
// A backend rollout does not rewrite or synchronously drain existing
|
|
6925
|
+
// leases. Preserve enough deadline-rotation headroom for historical Modal
|
|
6926
|
+
// leases even when the deployment default has moved to another backend.
|
|
6927
|
+
const rotationLeadMs = settings.sandboxRotationLeadMs;
|
|
6928
|
+
const ordinaryCaptureTimeoutMs = sandboxArchiveCaptureTimeoutMs(settings);
|
|
6929
|
+
const drainCaptureTimeoutMs = sandboxArchiveCaptureTimeoutMs({
|
|
6930
|
+
sandboxSnapshotTimeoutMs: effectiveSandboxDrainSnapshotTimeoutMs(settings),
|
|
6931
|
+
});
|
|
6932
|
+
const providerDeadlineCaptureTimeoutMs = Math.max(
|
|
6933
|
+
ordinaryCaptureTimeoutMs,
|
|
6934
|
+
drainCaptureTimeoutMs,
|
|
6935
|
+
);
|
|
6936
|
+
if (!(rotationLeadMs > providerDeadlineCaptureTimeoutMs + reaperPeriod)) {
|
|
6937
|
+
throw new Error(
|
|
6938
|
+
`OPENGENI_SANDBOX_ROTATION_LEAD_MS (${rotationLeadMs}) must exceed the ` +
|
|
6939
|
+
`largest durable snapshot or drain capture timeout plus one reaper period ` +
|
|
6940
|
+
`(${providerDeadlineCaptureTimeoutMs + reaperPeriod}), including for persisted Modal ` +
|
|
6941
|
+
`leases after a default-backend rollout.`,
|
|
6942
|
+
);
|
|
6943
|
+
}
|
|
5845
6944
|
if (settings.sandboxBackend === "modal") {
|
|
5846
6945
|
const idleGraceMs = settings.sandboxIdleGraceMs;
|
|
5847
6946
|
const lifecycle = effectiveSandboxLifecycle(settings, "modal");
|
|
5848
6947
|
const providerLifetimeMs = lifecycle.hardLifetimeMs!;
|
|
5849
|
-
const rotationLeadMs = lifecycle.rotationLeadMs!;
|
|
5850
6948
|
const idleTimeoutMs = lifecycle.providerIdleTimeoutMs!;
|
|
5851
6949
|
if (!(idleTimeoutMs <= providerLifetimeMs)) {
|
|
5852
6950
|
throw new Error(
|
|
@@ -5861,13 +6959,6 @@ function validateSettings(settings: Settings): void {
|
|
|
5861
6959
|
`OPENGENI_MODAL_TIMEOUT_SECONDS*1000 (${providerLifetimeMs}).`,
|
|
5862
6960
|
);
|
|
5863
6961
|
}
|
|
5864
|
-
const captureTimeoutMs = sandboxArchiveCaptureTimeoutMs(settings);
|
|
5865
|
-
if (!(rotationLeadMs > captureTimeoutMs + reaperPeriod)) {
|
|
5866
|
-
throw new Error(
|
|
5867
|
-
`OPENGENI_SANDBOX_ROTATION_LEAD_MS (${rotationLeadMs}) must exceed the durable capture ` +
|
|
5868
|
-
`timeout plus one reaper period (${captureTimeoutMs + reaperPeriod}).`,
|
|
5869
|
-
);
|
|
5870
|
-
}
|
|
5871
6962
|
if (!(viewerTtl < idleTimeoutMs)) {
|
|
5872
6963
|
throw new Error(
|
|
5873
6964
|
`OPENGENI_SANDBOX_VIEWER_HOLDER_TTL_MS (${viewerTtl}) must be strictly less than the effective box ` +
|
|
@@ -5925,15 +7016,23 @@ function validateSettings(settings: Settings): void {
|
|
|
5925
7016
|
"OPENGENI_STREAM_TOKEN_SECRET to enable the live desktop stream.",
|
|
5926
7017
|
);
|
|
5927
7018
|
}
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
7019
|
+
if (settings.modelCatalogSource === "code") {
|
|
7020
|
+
validateModelCatalogSettings(settings, source);
|
|
7021
|
+
} else {
|
|
7022
|
+
// Database mode resolves membership asynchronously. Only the independent
|
|
7023
|
+
// deployment funding JSON is parsed here; env catalog and note inputs are
|
|
7024
|
+
// intentionally ignored until resolveCatalogSettings applies the singleton.
|
|
7025
|
+
parseModelCostPolicyJson(settings.modelCostPolicyJson);
|
|
7026
|
+
}
|
|
7027
|
+
}
|
|
7028
|
+
|
|
7029
|
+
/** Validate one fully resolved, secret-bearing executable catalog. */
|
|
7030
|
+
export function validateModelCatalogSettings(
|
|
7031
|
+
settings: Settings,
|
|
7032
|
+
source: NodeJS.ProcessEnv = process.env,
|
|
7033
|
+
): ConfiguredModel[] {
|
|
7034
|
+
const costPolicy = parseModelCostPolicyJson(settings.modelCostPolicyJson);
|
|
7035
|
+
const notes = parseModelNotesJson(settings.modelNotesJson);
|
|
5937
7036
|
const registryProviders = parseModelProvidersJson(settings.modelProvidersJson);
|
|
5938
7037
|
const builtinId = builtinProviderId(settings);
|
|
5939
7038
|
const providerIds = new Set<string>();
|
|
@@ -5941,12 +7040,20 @@ function validateSettings(settings: Settings): void {
|
|
|
5941
7040
|
if (
|
|
5942
7041
|
provider.kind === "vercel-gateway-managed" ||
|
|
5943
7042
|
provider.kind === "vercel-gateway-workspace" ||
|
|
7043
|
+
provider.kind === "vercel-gateway-organization" ||
|
|
7044
|
+
provider.kind === "openrouter-workspace" ||
|
|
7045
|
+
provider.kind === "openrouter-organization" ||
|
|
5944
7046
|
provider.kind === "xai-subscription"
|
|
5945
7047
|
) {
|
|
5946
7048
|
throw new Error(
|
|
5947
7049
|
`OPENGENI_MODEL_PROVIDERS_JSON provider kind ${provider.kind} is reserved for a reviewed OpenGeni credential broker`,
|
|
5948
7050
|
);
|
|
5949
7051
|
}
|
|
7052
|
+
if (RESERVED_MODEL_PROVIDER_IDS.has(provider.id)) {
|
|
7053
|
+
throw new Error(
|
|
7054
|
+
`OPENGENI_MODEL_PROVIDERS_JSON provider id ${provider.id} is reserved for a reviewed OpenGeni provider`,
|
|
7055
|
+
);
|
|
7056
|
+
}
|
|
5950
7057
|
if (provider.id === builtinId) {
|
|
5951
7058
|
throw new Error(
|
|
5952
7059
|
`OPENGENI_MODEL_PROVIDERS_JSON provider id ${provider.id} collides with the built-in provider id`,
|
|
@@ -5961,7 +7068,7 @@ function validateSettings(settings: Settings): void {
|
|
|
5961
7068
|
if (
|
|
5962
7069
|
provider.kind !== "codex-subscription" &&
|
|
5963
7070
|
provider.kind !== "anonymous" &&
|
|
5964
|
-
!resolveProviderApiKey(provider)
|
|
7071
|
+
!resolveProviderApiKey(provider, source)
|
|
5965
7072
|
) {
|
|
5966
7073
|
throw new Error(
|
|
5967
7074
|
`OPENGENI_MODEL_PROVIDERS_JSON provider ${provider.id} requires a resolvable API key (set apiKey or apiKeyEnv)`,
|
|
@@ -5971,7 +7078,65 @@ function validateSettings(settings: Settings): void {
|
|
|
5971
7078
|
// Materialize the normalized catalog at boot so canonical product ids,
|
|
5972
7079
|
// aliases, definition digests, and capability/pricing normalization are
|
|
5973
7080
|
// validated even when managed billing is disabled.
|
|
5974
|
-
configuredModels(settings);
|
|
7081
|
+
const models = configuredModels(settings, source);
|
|
7082
|
+
const defaultCatalogSettings = settingsForTurnExecutionPolicy(settings, settings.openaiModel);
|
|
7083
|
+
const defaultCatalogModels =
|
|
7084
|
+
defaultCatalogSettings === settings ? models : configuredModels(defaultCatalogSettings, source);
|
|
7085
|
+
if (models.length === 0 && defaultCatalogModels.length === 0) {
|
|
7086
|
+
throw new Error("The resolved model catalog contains no executable models");
|
|
7087
|
+
}
|
|
7088
|
+
const defaultModelId = canonicalizeConfiguredModelId(
|
|
7089
|
+
defaultCatalogSettings,
|
|
7090
|
+
settings.openaiModel,
|
|
7091
|
+
);
|
|
7092
|
+
if (!defaultCatalogModels.some((model) => model.id === defaultModelId)) {
|
|
7093
|
+
throw new Error(
|
|
7094
|
+
`The default model ${settings.openaiModel} is not executable in the resolved model catalog`,
|
|
7095
|
+
);
|
|
7096
|
+
}
|
|
7097
|
+
|
|
7098
|
+
const deploymentProductIds = new Set(
|
|
7099
|
+
models.filter((model) => model.credentialSource.kind === "deployment").map((model) => model.id),
|
|
7100
|
+
);
|
|
7101
|
+
const noteProductIds = new Set(models.map((model) => model.id));
|
|
7102
|
+
for (const model of configuredGatewayCatalogModels(settings)) {
|
|
7103
|
+
deploymentProductIds.add(model.productId);
|
|
7104
|
+
noteProductIds.add(model.productId);
|
|
7105
|
+
noteProductIds.add(model.workspaceProductId);
|
|
7106
|
+
}
|
|
7107
|
+
for (const model of configuredOpenRouterCatalogModels(settings)) {
|
|
7108
|
+
const productId = `${OPENROUTER_MODEL_ID_PREFIX}${model.upstreamModelId}`;
|
|
7109
|
+
deploymentProductIds.add(productId);
|
|
7110
|
+
noteProductIds.add(productId);
|
|
7111
|
+
noteProductIds.add(`${WORKSPACE_OPENROUTER_MODEL_ID_PREFIX}${model.upstreamModelId}`);
|
|
7112
|
+
}
|
|
7113
|
+
if (settings.modelCatalogSource === "code") {
|
|
7114
|
+
for (const productId of Object.keys(costPolicy)) {
|
|
7115
|
+
if (!deploymentProductIds.has(productId)) {
|
|
7116
|
+
throw new Error(
|
|
7117
|
+
`OPENGENI_MODEL_COST_POLICY_JSON references unknown deployment model ${productId}`,
|
|
7118
|
+
);
|
|
7119
|
+
}
|
|
7120
|
+
}
|
|
7121
|
+
}
|
|
7122
|
+
for (const productId of Object.keys(notes)) {
|
|
7123
|
+
if (!noteProductIds.has(productId)) {
|
|
7124
|
+
throw new Error(`OPENGENI_MODEL_NOTES_JSON references unknown catalog model ${productId}`);
|
|
7125
|
+
}
|
|
7126
|
+
}
|
|
7127
|
+
|
|
7128
|
+
if (settings.billingMode === "stripe" || settings.usageLimitsMode === "managed") {
|
|
7129
|
+
const pricing = configuredModelPricing(settings);
|
|
7130
|
+
const missing = models
|
|
7131
|
+
.filter((model) => model.cost === "credits" && !pricing[model.id])
|
|
7132
|
+
.map((model) => model.id);
|
|
7133
|
+
if (missing.length > 0) {
|
|
7134
|
+
throw new Error(
|
|
7135
|
+
`Missing model pricing for managed billing model(s): ${missing.join(", ")}. Set OPENGENI_MODEL_PRICING_JSON.`,
|
|
7136
|
+
);
|
|
7137
|
+
}
|
|
7138
|
+
}
|
|
7139
|
+
return models;
|
|
5975
7140
|
}
|
|
5976
7141
|
|
|
5977
7142
|
/**
|