@opengeni/config 0.7.13 → 0.7.22

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.js CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  CAPABILITY_DESCRIPTORS,
5
5
  Entitlements,
6
6
  EntitlementsMode,
7
+ LatencyMode,
7
8
  MAX_NESTED_AGENT_DEPTH,
8
9
  ProductAccessMode,
9
10
  ReasoningEffort,
@@ -175,6 +176,9 @@ var SettingsSchema = z.object({
175
176
  observabilityOtlpEndpoint: z.string().url().optional(),
176
177
  observabilityOtlpHeaders: z.string().default(""),
177
178
  publicBaseUrl: z.string().url().optional(),
179
+ // Browser origin when the web app and API use separate origins in local
180
+ // development. Production normally leaves this unset and uses publicBaseUrl.
181
+ webBaseUrl: z.string().url().optional(),
178
182
  // Base URL for the bring-your-own-compute agent release assets the get.<domain>
179
183
  // install routes redirect to. Defaults to this repo's GitHub Releases. The route
180
184
  // appends `/download/agent-v<ver>/<asset>`.
@@ -213,6 +217,8 @@ var SettingsSchema = z.object({
213
217
  integrationsOauthClientsJson: z.string().default("{}"),
214
218
  slackClientId: z.string().optional(),
215
219
  slackClientSecret: z.string().optional(),
220
+ googleDriveClientId: z.string().optional(),
221
+ googleDriveClientSecret: z.string().optional(),
216
222
  // Undefined is meaningful: the migration boundary persists the product
217
223
  // default of 3 when no deployment override is supplied.
218
224
  maxNestedAgentDepth: z.coerce.number().int().nonnegative().max(MAX_NESTED_AGENT_DEPTH).optional(),
@@ -273,6 +279,36 @@ var SettingsSchema = z.object({
273
279
  openaiBaseUrl: z.string().optional(),
274
280
  openaiModel: z.string().default("gpt-5.6-sol"),
275
281
  openaiAllowedModels: z.string().default("gpt-5.6-sol,gpt-5.6-terra,gpt-5.6-luna"),
282
+ // Native composer voice input (browser MediaRecorder → API transcription).
283
+ // Provider credentials stay server-side; ClientConfig only projects availability
284
+ // and hard ceilings. Selection happens once before audio is sent — never retry
285
+ // the same clip across vendors after an upstream request may have started.
286
+ voiceInputMaxDurationSeconds: z.coerce.number().int().positive().max(600).default(60),
287
+ voiceInputMaxSizeBytes: z.coerce.number().int().positive().max(100 * 1024 * 1024).default(25 * 1024 * 1024),
288
+ // Preferred provider order (comma-separated ids). First configured+ready wins.
289
+ // Codex subscription STT is preferred by default when subscription routing is
290
+ // enabled; operators can put openai/azure-openai first explicitly.
291
+ // Supported: openai, azure-openai, codex-subscription.
292
+ voiceInputProviderOrder: z.string().default("codex-subscription,openai,azure-openai"),
293
+ // OpenAI public /v1/audio/transcriptions path. Reuses OPENGENI_OPENAI_API_KEY
294
+ // when voiceInputOpenaiApiKey is unset. Default model is gpt-transcribe.
295
+ voiceInputOpenaiEnabled: EnvBoolean.default(true),
296
+ voiceInputOpenaiApiKey: z.string().optional(),
297
+ voiceInputOpenaiBaseUrl: z.string().optional(),
298
+ voiceInputOpenaiModel: z.string().default("gpt-transcribe"),
299
+ // Azure OpenAI deployment-scoped audio transcriptions. Reuses the turn-model
300
+ // Azure endpoint/key/AD token when voice-specific overrides are unset.
301
+ voiceInputAzureEnabled: EnvBoolean.default(true),
302
+ voiceInputAzureEndpoint: z.string().optional(),
303
+ voiceInputAzureDeployment: z.string().optional(),
304
+ voiceInputAzureApiVersion: z.string().optional(),
305
+ voiceInputAzureApiKey: z.string().optional(),
306
+ voiceInputAzureAdToken: z.string().optional(),
307
+ // Legacy opt-in for undocumented ChatGPT /backend-api/transcribe. When
308
+ // OPENGENI_CODEX_SUBSCRIPTION_ENABLED is true, Codex STT is included without
309
+ // this flag. Set false and omit codex-subscription from PROVIDER_ORDER to
310
+ // keep subscription model routing while disabling Codex voice input.
311
+ voiceInputCodexExperimentalEnabled: EnvBoolean.default(false),
276
312
  modelPricingJson: z.string().default("{}"),
277
313
  // Extra (non-built-in) model providers, declared by the host as a JSON
278
314
  // provider registry. Each entry carries its own base URL, API key, wire API
@@ -375,17 +411,17 @@ var SettingsSchema = z.object({
375
411
  // the named Secret and builds the image via `fromRegistry(tag, secret)` before the
376
412
  // first sandbox is created. Knob: OPENGENI_MODAL_IMAGE_REGISTRY_SECRET.
377
413
  modalImageRegistrySecret: z.string().optional(),
378
- // Modal's hard sandbox lifetime (timeoutMs = this * 1000), counted from each
379
- // create/resume it is the BACKSTOP that reclaims a box if the reaper/worker is
380
- // down, NOT the warm-window controller (that's sandboxIdleGraceMs). It must
381
- // comfortably exceed reaperPeriod + idleGrace so the reaper terminates a
382
- // genuinely-idle box FIRST; the boot invariant below enforces that. Default 1h
383
- // (was 900s/15min): the 15-min drain grace counts from the user's LAST release,
384
- // but Modal's clock starts at the preceding turn's resume so a 15-min grace on
385
- // top of a 900s lifetime would let Modal kill the box mid-warm-window. 3600s
386
- // leaves ~45min of headroom for the active turn before the warm window opens.
414
+ // Modal's hard sandbox lifetime (timeoutMs = this * 1000), counted from box
415
+ // creation. A resume-by-id does NOT reset that provider clock. It is the
416
+ // BACKSTOP that reclaims a box if the reaper/worker is down, NOT the warm-window
417
+ // controller (that's sandboxIdleGraceMs). It must comfortably exceed
418
+ // reaperPeriod + idleGrace so the reaper terminates a genuinely-idle box FIRST;
419
+ // the boot invariant below enforces that. Default 24h, Modal's documented
420
+ // maximum, to reduce premature active-box loss and leave headroom for the
421
+ // deadline-aware snapshot/rematerialization transition. The transition—not a
422
+ // larger timeout—is what lets a session outlive one finite provider box.
387
423
  // Knob: OPENGENI_MODAL_TIMEOUT_SECONDS.
388
- modalTimeoutSeconds: z.coerce.number().int().positive().default(3600),
424
+ modalTimeoutSeconds: z.coerce.number().int().positive().max(86400).default(86400),
389
425
  modalTokenId: z.string().optional(),
390
426
  modalTokenSecret: z.string().optional(),
391
427
  modalEnvironment: z.string().optional(),
@@ -416,13 +452,6 @@ var SettingsSchema = z.object({
416
452
  // OPENGENI_MODAL_WORKSPACE_PERSISTENCE=tar to opt back out (no native snapshot;
417
453
  // the reaper persists a tar archive — same store+hydrate plumbing, slower).
418
454
  modalWorkspacePersistence: z.enum(["tar", "snapshot_filesystem", "snapshot_directory"]).default("snapshot_filesystem"),
419
- // Snapshot GC backstop (sandbox-file-persistence): the reaper keeps ONE latest
420
- // filesystem snapshot per lease (delete-prior-on-supersede + delete-on-teardown).
421
- // This is the TTL retention floor for the periodic orphan sweep — a snapshot
422
- // whose lease is cold and older than this is best-effort deleted so a crashed
423
- // persist-then-no-restore never leaks a Modal image. 0 disables the TTL sweep
424
- // (delete-on-supersede/teardown still run). Default 7 days.
425
- modalSnapshotRetentionSeconds: z.coerce.number().int().nonnegative().default(604800),
426
455
  // Shared desktop toggle: this module reads it for the 6080 port-merge; the
427
456
  // owner module (P4.x) acts on it to launch the display stack.
428
457
  sandboxDesktopEnabled: EnvBoolean.default(false),
@@ -644,8 +673,9 @@ var SettingsSchema = z.object({
644
673
  // this whole window so a "glanced away then came back" re-arms the SAME warm box
645
674
  // (acquireLease re-arms draining->warm; the reaper's BEFORE-terminate re-read
646
675
  // skips a re-armed box). Default 15min so a brief detour never cold-creates a
647
- // fresh EMPTY box; lower it to trade warm cost for a snappier reclaim. Knob:
648
- // OPENGENI_SANDBOX_IDLE_GRACE_MS.
676
+ // fresh EMPTY box; lower it to trade warm cost for a snappier reclaim.
677
+ // getSettings caps the default at half a shorter configured Modal lifetime so
678
+ // the entire reaper window always fits. Knob: OPENGENI_SANDBOX_IDLE_GRACE_MS.
649
679
  sandboxIdleGraceMs: z.coerce.number().int().positive().default(9e5),
650
680
  // MID-SESSION /workspace snapshot cadence (sandbox-file-persistence). The
651
681
  // reaper's drain-persist only protects boxes the reaper itself kills; a box
@@ -663,6 +693,22 @@ var SettingsSchema = z.object({
663
693
  // treated exactly like a failed best-effort snapshot. Knob:
664
694
  // OPENGENI_SANDBOX_SNAPSHOT_TIMEOUT_MS. Default 60s.
665
695
  sandboxSnapshotTimeoutMs: z.coerce.number().int().positive().default(6e4),
696
+ // Begin a controlled snapshot/quiesce/drain/rematerialize transition this far
697
+ // ahead of a finite provider deadline. Modal's 24h creation clock cannot be
698
+ // extended; the logical sandbox outlives it by moving to one successor box.
699
+ // getSettings derives the actual default as min(1h, half the configured
700
+ // provider lifetime) so short-lived test/canary boxes remain bootable without
701
+ // an extra coupled environment override. An explicit value may be larger when
702
+ // an operator deliberately wants more rotation headroom; the boot invariant
703
+ // still requires it to remain below the provider lifetime.
704
+ sandboxRotationLeadMs: z.coerce.number().int().positive().default(36e5),
705
+ // Bound each global reaper pass so a rollout that discovers many legacy boxes
706
+ // with unknown creation clocks cannot create a provider/API thundering herd.
707
+ // One is the safe admission default: the reaper services provider transitions
708
+ // sequentially, so claiming a wider batch would fence boxes before the same
709
+ // sweep can service them. Larger fleets may raise this only as an explicit,
710
+ // observed deployment choice.
711
+ sandboxRotationBatchSize: z.coerce.number().int().positive().max(500).default(1),
666
712
  // expires_at refresh window for a held lease (>> the turn 10s heartbeat so a
667
713
  // single missed heartbeat never TTL-reaps a live turn). The warming TTL is the
668
714
  // window a cold->warming spawner has to commit warm before a reaper resets it.
@@ -691,6 +737,7 @@ var SettingsSchema = z.object({
691
737
  sandboxPreparationProfiles: z.string().default("none"),
692
738
  sandboxEnvAllowlist: z.string().default(""),
693
739
  objectStorageEndpoint: z.string().url().optional(),
740
+ objectStorageInternalEndpoint: z.string().url().optional(),
694
741
  objectStorageSandboxEndpoint: z.string().url().optional(),
695
742
  objectStorageBackend: z.enum(["s3-compatible", "aws-s3", "azure-blob", "gcs"]).default("s3-compatible"),
696
743
  objectStorageBucket: z.string().min(1).default("opengeni-files"),
@@ -762,6 +809,77 @@ var SettingsSchema = z.object({
762
809
  })
763
810
  ).default([])
764
811
  });
812
+ function isUsableVoiceInputSecret(value) {
813
+ if (value == null) return false;
814
+ const trimmed = value.trim();
815
+ if (!trimmed) return false;
816
+ const normalized = trimmed.toLowerCase();
817
+ if (normalized === "your-key" || normalized === "your_key" || normalized === "changeme" || normalized === "replace-me" || normalized === "xxx" || normalized.startsWith("your-") || normalized.startsWith("your_")) {
818
+ return false;
819
+ }
820
+ return true;
821
+ }
822
+ function resolveVoiceInputProviderRegistry(settings) {
823
+ const order = settings.voiceInputProviderOrder.split(",").map((part) => part.trim()).filter(
824
+ (part) => part === "openai" || part === "azure-openai" || part === "codex-subscription"
825
+ );
826
+ const seen = /* @__PURE__ */ new Set();
827
+ const providers = [];
828
+ for (const id of order) {
829
+ if (seen.has(id)) continue;
830
+ seen.add(id);
831
+ if (id === "openai") {
832
+ if (!settings.voiceInputOpenaiEnabled) continue;
833
+ const apiKey = settings.voiceInputOpenaiApiKey ?? settings.openaiApiKey;
834
+ if (!isUsableVoiceInputSecret(apiKey)) continue;
835
+ if (settings.openaiProvider === "azure" && !settings.voiceInputOpenaiApiKey && !settings.voiceInputOpenaiBaseUrl) {
836
+ continue;
837
+ }
838
+ providers.push({
839
+ id: "openai",
840
+ kind: "openai",
841
+ apiKey,
842
+ baseUrl: (settings.voiceInputOpenaiBaseUrl ?? settings.openaiBaseUrl ?? "https://api.openai.com/v1").replace(/\/+$/, ""),
843
+ model: settings.voiceInputOpenaiModel
844
+ });
845
+ continue;
846
+ }
847
+ if (id === "azure-openai") {
848
+ if (!settings.voiceInputAzureEnabled) continue;
849
+ const endpoint = (settings.voiceInputAzureEndpoint ?? settings.azureOpenaiEndpoint ?? "").replace(/\/+$/, "");
850
+ const deployment = settings.voiceInputAzureDeployment ?? settings.azureOpenaiDeployment ?? "";
851
+ const apiVersion = settings.voiceInputAzureApiVersion ?? settings.azureOpenaiApiVersion ?? "2025-04-01-preview";
852
+ const apiKey = settings.voiceInputAzureApiKey ?? settings.azureOpenaiApiKey ?? null;
853
+ const adToken = settings.voiceInputAzureAdToken ?? settings.azureOpenaiAdToken ?? null;
854
+ if (!endpoint || !deployment || !isUsableVoiceInputSecret(apiKey) && !isUsableVoiceInputSecret(adToken)) {
855
+ continue;
856
+ }
857
+ if (settings.openaiProvider !== "azure" && !settings.voiceInputAzureEndpoint && !settings.voiceInputAzureDeployment && !settings.voiceInputAzureApiKey && !settings.voiceInputAzureAdToken) {
858
+ continue;
859
+ }
860
+ providers.push({
861
+ id: "azure-openai",
862
+ kind: "azure-openai",
863
+ endpoint,
864
+ deployment,
865
+ apiVersion,
866
+ apiKey: isUsableVoiceInputSecret(apiKey) ? apiKey : null,
867
+ adToken: isUsableVoiceInputSecret(adToken) ? adToken : null
868
+ });
869
+ continue;
870
+ }
871
+ if (id === "codex-subscription") {
872
+ if (!settings.codexSubscriptionEnabled) continue;
873
+ providers.push({ id: "codex-subscription", kind: "codex-subscription", experimental: true });
874
+ }
875
+ }
876
+ return providers;
877
+ }
878
+ function voiceInputDeploymentConfigured(settings) {
879
+ return resolveVoiceInputProviderRegistry(settings).some(
880
+ (provider) => provider.kind !== "codex-subscription"
881
+ );
882
+ }
765
883
  var ModelPricingSchema = z.object({
766
884
  inputMicrosPerMillionTokens: z.number().int().nonnegative(),
767
885
  cachedInputMicrosPerMillionTokens: z.number().int().nonnegative().optional(),
@@ -955,86 +1073,74 @@ var IntegrationOAuthClientConfigSchema = z.object({
955
1073
  });
956
1074
  var defaultModelPricing = {
957
1075
  "gpt-5.6-sol": {
958
- inputMicrosPerMillionTokens: 5e6,
959
- cachedInputMicrosPerMillionTokens: 5e5,
960
- outputMicrosPerMillionTokens: 3e7,
961
- marginBps: 2500
1076
+ default: {
1077
+ inputMicrosPerMillionTokens: 5e6,
1078
+ cachedInputMicrosPerMillionTokens: 5e5,
1079
+ outputMicrosPerMillionTokens: 3e7,
1080
+ marginBps: 2500
1081
+ },
1082
+ inputTokenTiers: [
1083
+ {
1084
+ // OpenAI: prompts with >272K input tokens use the long-context rate.
1085
+ minimumInputTokens: 272001,
1086
+ pricing: {
1087
+ inputMicrosPerMillionTokens: 1e7,
1088
+ cachedInputMicrosPerMillionTokens: 1e6,
1089
+ outputMicrosPerMillionTokens: 45e6,
1090
+ marginBps: 2500
1091
+ }
1092
+ }
1093
+ ]
962
1094
  },
963
1095
  "gpt-5.6-terra": {
964
- inputMicrosPerMillionTokens: 25e5,
965
- cachedInputMicrosPerMillionTokens: 25e4,
966
- outputMicrosPerMillionTokens: 15e6,
967
- marginBps: 2500
1096
+ default: {
1097
+ inputMicrosPerMillionTokens: 2e6,
1098
+ cachedInputMicrosPerMillionTokens: 2e5,
1099
+ outputMicrosPerMillionTokens: 12e6,
1100
+ marginBps: 2500
1101
+ },
1102
+ inputTokenTiers: [
1103
+ {
1104
+ minimumInputTokens: 272001,
1105
+ pricing: {
1106
+ inputMicrosPerMillionTokens: 4e6,
1107
+ cachedInputMicrosPerMillionTokens: 4e5,
1108
+ outputMicrosPerMillionTokens: 18e6,
1109
+ marginBps: 2500
1110
+ }
1111
+ }
1112
+ ]
968
1113
  },
969
1114
  "gpt-5.6-luna": {
970
- inputMicrosPerMillionTokens: 1e6,
971
- cachedInputMicrosPerMillionTokens: 1e5,
972
- outputMicrosPerMillionTokens: 6e6,
973
- marginBps: 2500
974
- },
975
- "gpt-5.4": {
976
- inputMicrosPerMillionTokens: 25e5,
977
- cachedInputMicrosPerMillionTokens: 25e4,
978
- outputMicrosPerMillionTokens: 15e6,
979
- marginBps: 2500
980
- },
981
- "gpt-5.4-mini": {
982
- inputMicrosPerMillionTokens: 75e4,
983
- cachedInputMicrosPerMillionTokens: 75e3,
984
- outputMicrosPerMillionTokens: 45e5,
985
- marginBps: 2500
986
- },
987
- "gpt-5.2": {
988
- inputMicrosPerMillionTokens: 175e4,
989
- cachedInputMicrosPerMillionTokens: 175e3,
990
- outputMicrosPerMillionTokens: 14e6,
991
- marginBps: 2500
992
- },
993
- "gpt-5.2-chat-latest": {
994
- inputMicrosPerMillionTokens: 175e4,
995
- cachedInputMicrosPerMillionTokens: 175e3,
996
- outputMicrosPerMillionTokens: 14e6,
997
- marginBps: 2500
998
- },
999
- "gpt-5.2-codex": {
1000
- inputMicrosPerMillionTokens: 175e4,
1001
- cachedInputMicrosPerMillionTokens: 175e3,
1002
- outputMicrosPerMillionTokens: 14e6,
1003
- marginBps: 2500
1004
- },
1005
- "gpt-5.1": {
1006
- inputMicrosPerMillionTokens: 125e4,
1007
- cachedInputMicrosPerMillionTokens: 125e3,
1008
- outputMicrosPerMillionTokens: 1e7,
1009
- marginBps: 2500
1010
- },
1011
- "gpt-5": {
1012
- inputMicrosPerMillionTokens: 125e4,
1013
- cachedInputMicrosPerMillionTokens: 125e3,
1014
- outputMicrosPerMillionTokens: 1e7,
1015
- marginBps: 2500
1016
- },
1017
- "gpt-5-mini": {
1018
- inputMicrosPerMillionTokens: 25e4,
1019
- cachedInputMicrosPerMillionTokens: 25e3,
1020
- outputMicrosPerMillionTokens: 2e6,
1021
- marginBps: 2500
1022
- },
1023
- "gpt-5-nano": {
1024
- inputMicrosPerMillionTokens: 5e4,
1025
- cachedInputMicrosPerMillionTokens: 5e3,
1026
- outputMicrosPerMillionTokens: 4e5,
1027
- marginBps: 2500
1115
+ default: {
1116
+ inputMicrosPerMillionTokens: 2e5,
1117
+ cachedInputMicrosPerMillionTokens: 2e4,
1118
+ outputMicrosPerMillionTokens: 12e5,
1119
+ marginBps: 2500
1120
+ },
1121
+ inputTokenTiers: [
1122
+ {
1123
+ minimumInputTokens: 272001,
1124
+ pricing: {
1125
+ inputMicrosPerMillionTokens: 4e5,
1126
+ cachedInputMicrosPerMillionTokens: 4e4,
1127
+ outputMicrosPerMillionTokens: 18e5,
1128
+ marginBps: 2500
1129
+ }
1130
+ }
1131
+ ]
1028
1132
  },
1029
1133
  // Fireworks AI / GLM 5.2 — the first shipped non-OpenAI registry model. A
1030
1134
  // built-in default pricing entry makes managed billing work out of the box
1031
1135
  // for hosts that expose this model via OPENGENI_MODEL_PROVIDERS_JSON without
1032
1136
  // also setting OPENGENI_MODEL_PRICING_JSON.
1033
1137
  "accounts/fireworks/models/glm-5p2": {
1034
- inputMicrosPerMillionTokens: 14e5,
1035
- cachedInputMicrosPerMillionTokens: 26e4,
1036
- outputMicrosPerMillionTokens: 44e5,
1037
- marginBps: 2500
1138
+ default: {
1139
+ inputMicrosPerMillionTokens: 14e5,
1140
+ cachedInputMicrosPerMillionTokens: 14e4,
1141
+ outputMicrosPerMillionTokens: 44e5,
1142
+ marginBps: 2500
1143
+ }
1038
1144
  }
1039
1145
  };
1040
1146
  var SANDBOX_REQUIRED_ENV = {
@@ -1105,6 +1211,7 @@ function getSettings() {
1105
1211
  observabilityOtlpEndpoint: optional("OPENGENI_OTEL_EXPORTER_OTLP_ENDPOINT") ?? optional("OTEL_EXPORTER_OTLP_ENDPOINT"),
1106
1212
  observabilityOtlpHeaders: optional("OPENGENI_OTEL_EXPORTER_OTLP_HEADERS") ?? optional("OTEL_EXPORTER_OTLP_HEADERS"),
1107
1213
  publicBaseUrl: optional("OPENGENI_PUBLIC_BASE_URL"),
1214
+ webBaseUrl: optional("OPENGENI_WEB_BASE_URL"),
1108
1215
  agentReleasesBaseUrl: optional("OPENGENI_AGENT_RELEASES_BASE_URL"),
1109
1216
  agentStableVersion: optional("OPENGENI_AGENT_STABLE_VERSION"),
1110
1217
  productAccessMode: optional("OPENGENI_PRODUCT_ACCESS_MODE"),
@@ -1128,6 +1235,8 @@ function getSettings() {
1128
1235
  integrationsOauthClientsJson: optional("OPENGENI_INTEGRATIONS_OAUTH_CLIENTS_JSON"),
1129
1236
  slackClientId: optional("OPENGENI_SLACK_CLIENT_ID"),
1130
1237
  slackClientSecret: optional("OPENGENI_SLACK_CLIENT_SECRET"),
1238
+ googleDriveClientId: optional("OPENGENI_GOOGLE_DRIVE_CLIENT_ID"),
1239
+ googleDriveClientSecret: optional("OPENGENI_GOOGLE_DRIVE_CLIENT_SECRET"),
1131
1240
  maxNestedAgentDepth: optional("OPENGENI_MAX_NESTED_AGENT_DEPTH"),
1132
1241
  goalMaxAutoContinuations: optional("OPENGENI_GOAL_MAX_AUTO_CONTINUATIONS"),
1133
1242
  goalNoProgressLimit: optional("OPENGENI_GOAL_NO_PROGRESS_LIMIT"),
@@ -1152,6 +1261,20 @@ function getSettings() {
1152
1261
  openaiBaseUrl: optional("OPENGENI_OPENAI_BASE_URL") ?? optional("OPENAI_BASE_URL"),
1153
1262
  openaiModel: optional("OPENGENI_OPENAI_MODEL"),
1154
1263
  openaiAllowedModels: optional("OPENGENI_OPENAI_ALLOWED_MODELS"),
1264
+ voiceInputMaxDurationSeconds: optional("OPENGENI_VOICE_INPUT_MAX_DURATION_SECONDS"),
1265
+ voiceInputMaxSizeBytes: optional("OPENGENI_VOICE_INPUT_MAX_SIZE_BYTES"),
1266
+ voiceInputProviderOrder: optional("OPENGENI_VOICE_INPUT_PROVIDER_ORDER"),
1267
+ voiceInputOpenaiEnabled: optional("OPENGENI_VOICE_INPUT_OPENAI_ENABLED"),
1268
+ voiceInputOpenaiApiKey: optional("OPENGENI_VOICE_INPUT_OPENAI_API_KEY"),
1269
+ voiceInputOpenaiBaseUrl: optional("OPENGENI_VOICE_INPUT_OPENAI_BASE_URL"),
1270
+ voiceInputOpenaiModel: optional("OPENGENI_VOICE_INPUT_OPENAI_MODEL"),
1271
+ voiceInputAzureEnabled: optional("OPENGENI_VOICE_INPUT_AZURE_ENABLED"),
1272
+ voiceInputAzureEndpoint: optional("OPENGENI_VOICE_INPUT_AZURE_ENDPOINT"),
1273
+ voiceInputAzureDeployment: optional("OPENGENI_VOICE_INPUT_AZURE_DEPLOYMENT"),
1274
+ voiceInputAzureApiVersion: optional("OPENGENI_VOICE_INPUT_AZURE_API_VERSION"),
1275
+ voiceInputAzureApiKey: optional("OPENGENI_VOICE_INPUT_AZURE_API_KEY"),
1276
+ voiceInputAzureAdToken: optional("OPENGENI_VOICE_INPUT_AZURE_AD_TOKEN"),
1277
+ voiceInputCodexExperimentalEnabled: optional("OPENGENI_VOICE_INPUT_CODEX_EXPERIMENTAL"),
1155
1278
  modelPricingJson: optional("OPENGENI_MODEL_PRICING_JSON"),
1156
1279
  modelProvidersJson: optional("OPENGENI_MODEL_PROVIDERS_JSON"),
1157
1280
  codexSubscriptionEnabled: optional("OPENGENI_CODEX_SUBSCRIPTION_ENABLED"),
@@ -1189,7 +1312,6 @@ function getSettings() {
1189
1312
  modalEnvironment: optional("OPENGENI_MODAL_ENVIRONMENT"),
1190
1313
  modalIdleTimeoutSeconds: optional("OPENGENI_MODAL_IDLE_TIMEOUT_SECONDS"),
1191
1314
  modalWorkspacePersistence: optional("OPENGENI_MODAL_WORKSPACE_PERSISTENCE"),
1192
- modalSnapshotRetentionSeconds: optional("OPENGENI_MODAL_SNAPSHOT_RETENTION_SECONDS"),
1193
1315
  sandboxDesktopEnabled: optional("OPENGENI_SANDBOX_DESKTOP_ENABLED"),
1194
1316
  sandboxDesktopInteractive: optional("OPENGENI_SANDBOX_DESKTOP_INTERACTIVE"),
1195
1317
  sandboxTerminalEnabled: optional("OPENGENI_SANDBOX_TERMINAL_ENABLED"),
@@ -1262,6 +1384,8 @@ function getSettings() {
1262
1384
  sandboxIdleGraceMs: optional("OPENGENI_SANDBOX_IDLE_GRACE_MS"),
1263
1385
  sandboxSnapshotIntervalMs: optional("OPENGENI_SANDBOX_SNAPSHOT_INTERVAL_MS"),
1264
1386
  sandboxSnapshotTimeoutMs: optional("OPENGENI_SANDBOX_SNAPSHOT_TIMEOUT_MS"),
1387
+ sandboxRotationLeadMs: optional("OPENGENI_SANDBOX_ROTATION_LEAD_MS"),
1388
+ sandboxRotationBatchSize: optional("OPENGENI_SANDBOX_ROTATION_BATCH_SIZE"),
1265
1389
  sandboxLeaseTtlMs: optional("OPENGENI_SANDBOX_LEASE_TTL_MS"),
1266
1390
  sandboxLeaseWarmingTtlMs: optional("OPENGENI_SANDBOX_LEASE_WARMING_TTL_MS"),
1267
1391
  sandboxWarmingTimeoutMs: optional("OPENGENI_SANDBOX_WARMING_TIMEOUT_MS"),
@@ -1273,6 +1397,7 @@ function getSettings() {
1273
1397
  sandboxPreparationProfiles: optional("OPENGENI_SANDBOX_PREPARATION_PROFILES"),
1274
1398
  sandboxEnvAllowlist: optional("OPENGENI_SANDBOX_ENV_ALLOWLIST"),
1275
1399
  objectStorageEndpoint: optional("OPENGENI_OBJECT_STORAGE_ENDPOINT"),
1400
+ objectStorageInternalEndpoint: optional("OPENGENI_OBJECT_STORAGE_INTERNAL_ENDPOINT"),
1276
1401
  objectStorageSandboxEndpoint: optional("OPENGENI_OBJECT_STORAGE_SANDBOX_ENDPOINT"),
1277
1402
  objectStorageBackend: optional("OPENGENI_OBJECT_STORAGE_BACKEND"),
1278
1403
  objectStorageBucket: optional("OPENGENI_OBJECT_STORAGE_BUCKET"),
@@ -1328,6 +1453,8 @@ function getSettings() {
1328
1453
  const parsed = SettingsSchema.parse(raw);
1329
1454
  const settings = {
1330
1455
  ...parsed,
1456
+ sandboxIdleGraceMs: raw.sandboxIdleGraceMs === void 0 ? Math.min(9e5, Math.floor(parsed.modalTimeoutSeconds * 1e3 / 2)) : parsed.sandboxIdleGraceMs,
1457
+ sandboxRotationLeadMs: raw.sandboxRotationLeadMs === void 0 ? Math.min(36e5, Math.floor(parsed.modalTimeoutSeconds * 1e3 / 2)) : parsed.sandboxRotationLeadMs,
1331
1458
  mcpServers: ensureBuiltInMcpServers(parsed)
1332
1459
  };
1333
1460
  validateSettings(settings);
@@ -1336,6 +1463,12 @@ function getSettings() {
1336
1463
  function effectiveModalIdleTimeoutSeconds(settings) {
1337
1464
  return settings.modalIdleTimeoutSeconds ?? settings.modalTimeoutSeconds;
1338
1465
  }
1466
+ function sandboxArchiveCaptureTimeoutMs(settings) {
1467
+ return Math.min(
1468
+ 60 * 6e4,
1469
+ Math.max(settings.sandboxSnapshotTimeoutMs + 3e4, settings.sandboxSnapshotTimeoutMs * 2)
1470
+ );
1471
+ }
1339
1472
  function collectSandboxEnvironment(settings, source = process.env) {
1340
1473
  const out = {};
1341
1474
  for (const name of sandboxEnvironmentVariableNames(settings)) {
@@ -1572,6 +1705,68 @@ function legacyModelCapabilities(settings, input) {
1572
1705
  latencyModes: [{ id: "standard", upstream: "unknown", runnable: true }]
1573
1706
  });
1574
1707
  }
1708
+ var GPT56_FAST_BILLING_MULTIPLIER_BPS = 2e4;
1709
+ function productLabelForModelId(modelId) {
1710
+ const slug = modelId.startsWith(CODEX_MODEL_ID_PREFIX) ? modelId.slice(CODEX_MODEL_ID_PREFIX.length) : modelId;
1711
+ const match = /^(gpt-\d+(?:\.\d+)?)(?:-(.+))?$/i.exec(slug);
1712
+ if (!match) {
1713
+ return slug;
1714
+ }
1715
+ const family = match[1].replace(/^gpt/i, "GPT");
1716
+ const rest = match[2];
1717
+ if (!rest) {
1718
+ return family;
1719
+ }
1720
+ const suffix = rest.split("-").filter((part) => part.length > 0).map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join(" ");
1721
+ return suffix.length > 0 ? `${family} ${suffix}` : family;
1722
+ }
1723
+ function builtinLatencyModesForModel(modelId) {
1724
+ if (modelId === "gpt-5.6-sol" || modelId === "gpt-5.6-terra" || modelId === "gpt-5.6-luna" || modelId.startsWith("codex/gpt-5.6-")) {
1725
+ return [
1726
+ { id: "standard", upstream: "supported", runnable: true },
1727
+ {
1728
+ id: "fast",
1729
+ upstream: "supported",
1730
+ runnable: true,
1731
+ billingMultiplierBps: GPT56_FAST_BILLING_MULTIPLIER_BPS
1732
+ }
1733
+ ];
1734
+ }
1735
+ return [{ id: "standard", upstream: "unknown", runnable: true }];
1736
+ }
1737
+ function serviceTierForLatencyMode(providerId, latencyMode) {
1738
+ if (latencyMode === "standard") {
1739
+ return void 0;
1740
+ }
1741
+ if (providerId === "azure" || providerId === CODEX_PROVIDER_ID) {
1742
+ return "priority";
1743
+ }
1744
+ return "fast";
1745
+ }
1746
+ function responseSatisfiesLatencyMode(requested, responseServiceTier) {
1747
+ if (requested === "standard") {
1748
+ return true;
1749
+ }
1750
+ return responseServiceTier === "priority" || responseServiceTier === "fast";
1751
+ }
1752
+ function runnableLatencyModesForModel(settings, modelId) {
1753
+ const resolved = resolveModelProvider(
1754
+ settingsForTurnExecutionPolicy(settings, modelId),
1755
+ canonicalizeConfiguredModelId(settings, modelId)
1756
+ );
1757
+ if (!resolved) {
1758
+ return ["standard"];
1759
+ }
1760
+ return resolved.model.capabilities.latencyModes.filter((mode) => mode.runnable).map((mode) => LatencyMode.parse(mode.id));
1761
+ }
1762
+ function assertLatencyModeRunnable(settings, modelId, latencyMode) {
1763
+ const runnable = runnableLatencyModesForModel(settings, modelId);
1764
+ if (!runnable.includes(latencyMode)) {
1765
+ throw new Error(
1766
+ `latency mode ${latencyMode} is not runnable for model ${modelId} (allowed: ${runnable.join(", ")})`
1767
+ );
1768
+ }
1769
+ }
1575
1770
  function registryCredentialSource(provider) {
1576
1771
  return provider.kind === "codex-subscription" ? { kind: "connected_subscription", provider: "codex" } : { kind: "deployment", mechanism: "api_key" };
1577
1772
  }
@@ -1692,21 +1887,31 @@ function withCodexCatalogProvider(settings) {
1692
1887
  label: "Codex (ChatGPT subscription)",
1693
1888
  api: "responses",
1694
1889
  baseUrl: CODEX_PROVIDER_BASE_URL,
1695
- models: CODEX_FALLBACK_MODEL_SLUGS.map((slug) => ({
1696
- id: `${CODEX_MODEL_ID_PREFIX}${slug}`,
1697
- upstreamModelId: slug,
1698
- label: slug,
1699
- reasoningEffort: true,
1700
- // The ChatGPT/Codex Responses backend accepts the native web_search
1701
- // hosted tool (unlike hosted apply_patch/computer transports). Declaring
1702
- // this here makes provider resolution truthful; the worker still applies
1703
- // the durable session/turn policy gate before attaching it.
1704
- hostedWebSearch: true,
1705
- contextWindowTokens: CODEX_MODEL_CONTEXT_WINDOW_TOKENS,
1706
- effectiveContextWindowTokens: CODEX_MODEL_EFFECTIVE_CONTEXT_WINDOW_TOKENS,
1707
- autoCompactTokenLimit: CODEX_MODEL_AUTO_COMPACT_TOKEN_LIMIT,
1708
- toolOutputTruncationTokens: CODEX_MODEL_TOOL_OUTPUT_TRUNCATION_TOKENS
1709
- }))
1890
+ models: CODEX_FALLBACK_MODEL_SLUGS.map((slug) => {
1891
+ const capabilities = {
1892
+ ...legacyModelCapabilities(settings, {
1893
+ reasoningEffort: true,
1894
+ hostedWebSearch: true
1895
+ }),
1896
+ latencyModes: builtinLatencyModesForModel(`${CODEX_MODEL_ID_PREFIX}${slug}`)
1897
+ };
1898
+ return {
1899
+ id: `${CODEX_MODEL_ID_PREFIX}${slug}`,
1900
+ upstreamModelId: slug,
1901
+ label: productLabelForModelId(slug),
1902
+ reasoningEffort: true,
1903
+ // The ChatGPT/Codex Responses backend accepts the native web_search
1904
+ // hosted tool (unlike hosted apply_patch/computer transports). Declaring
1905
+ // this here makes provider resolution truthful; the worker still applies
1906
+ // the durable session/turn policy gate before attaching it.
1907
+ hostedWebSearch: true,
1908
+ capabilities,
1909
+ contextWindowTokens: CODEX_MODEL_CONTEXT_WINDOW_TOKENS,
1910
+ effectiveContextWindowTokens: CODEX_MODEL_EFFECTIVE_CONTEXT_WINDOW_TOKENS,
1911
+ autoCompactTokenLimit: CODEX_MODEL_AUTO_COMPACT_TOKEN_LIMIT,
1912
+ toolOutputTruncationTokens: CODEX_MODEL_TOOL_OUTPUT_TRUNCATION_TOKENS
1913
+ };
1914
+ })
1710
1915
  };
1711
1916
  return { ...settings, modelProvidersJson: JSON.stringify([...providers, provider]) };
1712
1917
  }
@@ -1790,14 +1995,17 @@ function configuredModels(settings) {
1790
1995
  settings.openaiModel,
1791
1996
  ...splitCsv(settings.openaiAllowedModels)
1792
1997
  ]).filter((id) => !isRegistryNamespaced(id)).map((id) => {
1793
- const capabilities = legacyModelCapabilities(settings, {
1794
- reasoningEffort: true,
1795
- hostedWebSearch: settings.webSearchEnabled
1796
- });
1998
+ const capabilities = {
1999
+ ...legacyModelCapabilities(settings, {
2000
+ reasoningEffort: true,
2001
+ hostedWebSearch: settings.webSearchEnabled
2002
+ }),
2003
+ latencyModes: builtinLatencyModesForModel(id)
2004
+ };
1797
2005
  return finalizeConfiguredModel(settings, builtinProvider, {
1798
2006
  id,
1799
2007
  aliases: [],
1800
- label: id,
2008
+ label: productLabelForModelId(id),
1801
2009
  providerId: builtinId,
1802
2010
  providerLabel: builtinLabel,
1803
2011
  api: "responses",
@@ -1829,7 +2037,7 @@ function configuredModels(settings) {
1829
2037
  finalizeConfiguredModel(settings, resolvedProvider, {
1830
2038
  id: model.id,
1831
2039
  aliases: [...model.aliases ?? []],
1832
- label: model.label ?? model.id,
2040
+ label: model.label ?? productLabelForModelId(model.id),
1833
2041
  providerId: provider.id,
1834
2042
  providerLabel,
1835
2043
  api: provider.api,
@@ -1890,6 +2098,9 @@ function resolveTurnExecutionPolicyV1(settings, input) {
1890
2098
  if (input.requestedModelId !== null && canonicalizeConfiguredModelId(catalogSettings, input.requestedModelId) !== productModelId) {
1891
2099
  throw new Error("Turn execution policy requested model does not canonicalize to its product");
1892
2100
  }
2101
+ const latencyMode = LatencyMode.parse(input.latencyMode ?? "standard");
2102
+ const latencyModeSource = input.latencyModeSource ?? "deployment";
2103
+ assertLatencyModeRunnable(catalogSettings, productModelId, latencyMode);
1893
2104
  return TurnExecutionPolicyV1.parse({
1894
2105
  schemaVersion: 1,
1895
2106
  productModelId,
@@ -1897,6 +2108,8 @@ function resolveTurnExecutionPolicyV1(settings, input) {
1897
2108
  modelSource: input.modelSource,
1898
2109
  reasoningEffort: input.reasoningEffort,
1899
2110
  reasoningSource: input.reasoningSource,
2111
+ latencyMode,
2112
+ latencyModeSource,
1900
2113
  providerId: resolved.provider.id,
1901
2114
  upstreamModelId: resolved.model.upstreamModelId,
1902
2115
  wireApi: resolved.model.api,
@@ -1909,9 +2122,13 @@ function assertTurnExecutionPolicyMatchesConfigV1(settings, policy, expected) {
1909
2122
  const parsed = TurnExecutionPolicyV1.parse(policy);
1910
2123
  const catalogSettings = settingsForTurnExecutionPolicy(settings, parsed.productModelId);
1911
2124
  const canonicalExpectedModel = canonicalizeConfiguredModelId(catalogSettings, expected.modelId);
1912
- if (parsed.productModelId !== canonicalExpectedModel || parsed.reasoningEffort !== expected.reasoningEffort) {
1913
- throw new Error("Turn execution policy does not match the accepted turn model/reasoning");
2125
+ const expectedLatencyMode = expected.latencyMode ?? parsed.latencyMode;
2126
+ if (parsed.productModelId !== canonicalExpectedModel || parsed.reasoningEffort !== expected.reasoningEffort || parsed.latencyMode !== expectedLatencyMode) {
2127
+ throw new Error(
2128
+ "Turn execution policy does not match the accepted turn model/reasoning/latency"
2129
+ );
1914
2130
  }
2131
+ assertLatencyModeRunnable(catalogSettings, parsed.productModelId, parsed.latencyMode);
1915
2132
  if (parsed.requestedModelId !== null && canonicalizeConfiguredModelId(catalogSettings, parsed.requestedModelId) !== parsed.productModelId) {
1916
2133
  throw new Error("Turn execution policy requested model does not match its product model");
1917
2134
  }
@@ -1927,7 +2144,10 @@ function assertTurnExecutionPolicyMatchesConfigV1(settings, policy, expected) {
1927
2144
  }
1928
2145
  function configuredModelPricingSchedules(settings) {
1929
2146
  const defaults = Object.fromEntries(
1930
- Object.entries(defaultModelPricing).map(([model, pricing]) => [model, { default: pricing }])
2147
+ Object.entries(defaultModelPricing).map(([model, pricing]) => [
2148
+ model,
2149
+ normalizeModelPricingSchedule(pricing)
2150
+ ])
1931
2151
  );
1932
2152
  const registry = {};
1933
2153
  for (const provider of parseModelProvidersJson(settings.modelProvidersJson)) {
@@ -2009,7 +2229,7 @@ function configuredEntitlements(settings) {
2009
2229
  ...configured
2010
2230
  };
2011
2231
  }
2012
- function calculateModelUsageCostMicros(settings, model, usage) {
2232
+ function calculateModelUsageCostMicros(settings, model, usage, options) {
2013
2233
  const schedule = configuredModelPricingSchedules(settings)[model];
2014
2234
  if (!schedule) {
2015
2235
  throw new Error(`Missing model pricing for ${model}`);
@@ -2028,6 +2248,20 @@ function calculateModelUsageCostMicros(settings, model, usage) {
2028
2248
  const marginBps = pricing.marginBps ?? 0;
2029
2249
  total += Math.ceil(rawCost * (1e4 + marginBps) / 1e4);
2030
2250
  }
2251
+ const latencyMode = options?.latencyMode ?? "standard";
2252
+ if (latencyMode !== "standard") {
2253
+ const catalogSettings = settingsForTurnExecutionPolicy(settings, model);
2254
+ const resolved = resolveModelProvider(
2255
+ catalogSettings,
2256
+ canonicalizeConfiguredModelId(catalogSettings, model)
2257
+ );
2258
+ const multiplierBps = resolved?.model.capabilities.latencyModes.find(
2259
+ (mode) => mode.id === latencyMode && mode.runnable
2260
+ )?.billingMultiplierBps;
2261
+ if (multiplierBps && multiplierBps > 0) {
2262
+ total = Math.ceil(total * multiplierBps / 1e4);
2263
+ }
2264
+ }
2031
2265
  return total;
2032
2266
  }
2033
2267
  function configuredAllowedReasoningEfforts(settings) {
@@ -2591,6 +2825,28 @@ function validateSettings(settings) {
2591
2825
  );
2592
2826
  }
2593
2827
  }
2828
+ if (Boolean(settings.googleDriveClientId) !== Boolean(settings.googleDriveClientSecret)) {
2829
+ throw new Error(
2830
+ "OPENGENI_GOOGLE_DRIVE_CLIENT_ID and OPENGENI_GOOGLE_DRIVE_CLIENT_SECRET must be configured together"
2831
+ );
2832
+ }
2833
+ if (settings.googleDriveClientId) {
2834
+ if (!settings.publicBaseUrl) {
2835
+ throw new Error(
2836
+ "OPENGENI_PUBLIC_BASE_URL is required when the Google Drive integration is configured"
2837
+ );
2838
+ }
2839
+ if (!settings.publicBaseUrl.startsWith("https://") && !["local", "test"].includes(settings.environment)) {
2840
+ throw new Error(
2841
+ "OPENGENI_PUBLIC_BASE_URL must use https when the Google Drive integration is configured outside local/test"
2842
+ );
2843
+ }
2844
+ if (!settings.integrationsStateSecret) {
2845
+ throw new Error(
2846
+ "OPENGENI_INTEGRATIONS_STATE_SECRET is required when the Google Drive integration is configured"
2847
+ );
2848
+ }
2849
+ }
2594
2850
  parseIntegrationsOauthClientsJson(settings.integrationsOauthClientsJson);
2595
2851
  if (settings.productAccessMode === "configured" && !["local", "test"].includes(settings.environment) && !settings.delegationSecret && !settings.authRequired) {
2596
2852
  throw new Error(
@@ -2674,7 +2930,7 @@ function validateSettings(settings) {
2674
2930
  "OPENGENI_OBJECT_STORAGE_ACCESS_KEY_ID and OPENGENI_OBJECT_STORAGE_SECRET_ACCESS_KEY must both be set or both omitted"
2675
2931
  );
2676
2932
  }
2677
- if (settings.objectStorageBackend === "s3-compatible" && (settings.objectStorageEndpoint || settings.objectStorageSandboxEndpoint) && (!settings.objectStorageAccessKeyId || !settings.objectStorageSecretAccessKey)) {
2933
+ if (settings.objectStorageBackend === "s3-compatible" && (settings.objectStorageEndpoint || settings.objectStorageInternalEndpoint || settings.objectStorageSandboxEndpoint) && (!settings.objectStorageAccessKeyId || !settings.objectStorageSecretAccessKey)) {
2678
2934
  throw new Error(
2679
2935
  "S3-compatible object storage endpoints require OPENGENI_OBJECT_STORAGE_ACCESS_KEY_ID and OPENGENI_OBJECT_STORAGE_SECRET_ACCESS_KEY"
2680
2936
  );
@@ -2690,7 +2946,7 @@ function validateSettings(settings) {
2690
2946
  );
2691
2947
  }
2692
2948
  } else if (settings.objectStorageBackend === "azure-blob") {
2693
- if (settings.objectStorageEndpoint || settings.objectStorageSandboxEndpoint || settings.objectStorageAccessKeyId || settings.objectStorageSecretAccessKey) {
2949
+ if (settings.objectStorageEndpoint || settings.objectStorageInternalEndpoint || settings.objectStorageSandboxEndpoint || settings.objectStorageAccessKeyId || settings.objectStorageSecretAccessKey) {
2694
2950
  throw new Error(
2695
2951
  "Azure Blob storage uses OPENGENI_OBJECT_STORAGE_AZURE_* settings, not S3-compatible object storage settings"
2696
2952
  );
@@ -2708,7 +2964,7 @@ function validateSettings(settings) {
2708
2964
  );
2709
2965
  }
2710
2966
  } else {
2711
- if (settings.objectStorageEndpoint || settings.objectStorageSandboxEndpoint || settings.objectStorageAccessKeyId || settings.objectStorageSecretAccessKey) {
2967
+ if (settings.objectStorageEndpoint || settings.objectStorageInternalEndpoint || settings.objectStorageSandboxEndpoint || settings.objectStorageAccessKeyId || settings.objectStorageSecretAccessKey) {
2712
2968
  throw new Error(
2713
2969
  "GCS object storage uses OPENGENI_OBJECT_STORAGE_GCS_* settings, not S3-compatible object storage settings"
2714
2970
  );
@@ -2743,6 +2999,7 @@ function validateSettings(settings) {
2743
2999
  const viewerTtl = settings.sandboxViewerHolderTtlMs;
2744
3000
  const idleGraceMs = settings.sandboxIdleGraceMs;
2745
3001
  const providerLifetimeMs = settings.modalTimeoutSeconds * 1e3;
3002
+ const rotationLeadMs = settings.sandboxRotationLeadMs;
2746
3003
  const idleTimeoutMs = effectiveModalIdleTimeoutSeconds(settings) * 1e3;
2747
3004
  if (!(reaperPeriod < viewerTtl)) {
2748
3005
  throw new Error(
@@ -2754,6 +3011,16 @@ function validateSettings(settings) {
2754
3011
  `OPENGENI_MODAL_IDLE_TIMEOUT_SECONDS*1000 (${idleTimeoutMs}) must not exceed the hard provider lifetime (OPENGENI_MODAL_TIMEOUT_SECONDS*1000 = ${providerLifetimeMs}): the idle timeout is a floor under the hard lifetime, not above it.`
2755
3012
  );
2756
3013
  }
3014
+ if (!(rotationLeadMs < providerLifetimeMs)) {
3015
+ throw new Error(
3016
+ `OPENGENI_SANDBOX_ROTATION_LEAD_MS (${rotationLeadMs}) must be strictly less than OPENGENI_MODAL_TIMEOUT_SECONDS*1000 (${providerLifetimeMs}).`
3017
+ );
3018
+ }
3019
+ if (!(rotationLeadMs > settings.sandboxSnapshotTimeoutMs + 2 * reaperPeriod)) {
3020
+ throw new Error(
3021
+ `OPENGENI_SANDBOX_ROTATION_LEAD_MS (${rotationLeadMs}) must exceed the snapshot timeout plus two reaper periods (${settings.sandboxSnapshotTimeoutMs + 2 * reaperPeriod}).`
3022
+ );
3023
+ }
2757
3024
  if (!(viewerTtl < idleTimeoutMs)) {
2758
3025
  throw new Error(
2759
3026
  `OPENGENI_SANDBOX_VIEWER_HOLDER_TTL_MS (${viewerTtl}) must be strictly less than the effective box idle timeout (${idleTimeoutMs}): a viewer holder must be reapable before the box idles out from under it (the provider idle-timeout is the backstop).`
@@ -2911,6 +3178,7 @@ export {
2911
3178
  getSettings,
2912
3179
  hasGitCredentialRepositorySelection,
2913
3180
  hasGitHubRepositorySelection,
3181
+ isUsableVoiceInputSecret,
2914
3182
  parseExposedPorts,
2915
3183
  parseIntegrationsOauthClientsJson,
2916
3184
  parseMcpServers,
@@ -2920,6 +3188,7 @@ export {
2920
3188
  parseStaticEntitlementsJson,
2921
3189
  parseStaticUsageLimitsJson,
2922
3190
  policyProviderIdForModel,
3191
+ productLabelForModelId,
2923
3192
  requiredSandboxEnvForBackend,
2924
3193
  resolveEnrollmentSigningSecret,
2925
3194
  resolveModelProvider,
@@ -2929,17 +3198,23 @@ export {
2929
3198
  resolveRelayTokenSecret,
2930
3199
  resolveStreamTokenSecret,
2931
3200
  resolveTurnExecutionPolicyV1,
3201
+ resolveVoiceInputProviderRegistry,
3202
+ responseSatisfiesLatencyMode,
2932
3203
  retryStartupDependency,
3204
+ runnableLatencyModesForModel,
3205
+ sandboxArchiveCaptureTimeoutMs,
2933
3206
  sandboxEnvironmentVariableNames,
2934
3207
  sandboxLifecycleHookIds,
2935
3208
  sandboxPreparationProfiles,
2936
3209
  sandboxWarmRateMicrosPerSecond,
2937
3210
  selectModelPricing,
3211
+ serviceTierForLatencyMode,
2938
3212
  settingsWithResolvedModelContext,
2939
3213
  stableSandboxEnvironmentForRun,
2940
3214
  startupRetryOptions,
2941
3215
  streamTokenDegraded,
2942
3216
  temporalConnectionOptions,
3217
+ voiceInputDeploymentConfigured,
2943
3218
  withCodexCatalogProvider
2944
3219
  };
2945
3220
  //# sourceMappingURL=index.js.map