@opengeni/config 2.1.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/config",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "description": "OpenGeni runtime configuration: settings resolution, deployment knobs, and config validation shared across the server packages.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -33,8 +33,8 @@
33
33
  "prepublishOnly": "bash ../../scripts/prepublish-guard"
34
34
  },
35
35
  "dependencies": {
36
- "@opengeni/codex": "^0.2.25",
37
- "@opengeni/contracts": "^5.1.0",
36
+ "@opengeni/codex": "^0.2.26",
37
+ "@opengeni/contracts": "^5.1.1",
38
38
  "@opengeni/xai-subscription": "^0.1.4",
39
39
  "zod": "^4.2.1"
40
40
  },
package/src/index.ts CHANGED
@@ -63,6 +63,9 @@ export const DEFAULT_MODEL_COST_POLICY_JSON = JSON.stringify({
63
63
  // a configured request timeout may never consume the entire durable claim.
64
64
  export const SANDBOX_ARCHIVE_CAPTURE_MAX_TIMEOUT_MS = 60 * 60_000;
65
65
  export const SANDBOX_ARCHIVE_CAPTURE_SETTLEMENT_GRACE_MS = 10_000;
66
+ // Deadline rotation gives retained legacy commands a bounded stop window
67
+ // before capturing the still-running sandbox's current files.
68
+ export const SANDBOX_DEADLINE_COMMAND_STOP_GRACE_MS = 120_000;
66
69
  export const SANDBOX_SNAPSHOT_MAX_TIMEOUT_MS =
67
70
  SANDBOX_ARCHIVE_CAPTURE_MAX_TIMEOUT_MS - SANDBOX_ARCHIVE_CAPTURE_SETTLEMENT_GRACE_MS;
68
71
  export const GOOGLE_DRIVE_PROVIDER_REQUEST_TIMEOUT_MAX_MS = 60_000;
@@ -353,6 +356,9 @@ const SettingsSchema = z.object({
353
356
  // variable out to disable it).
354
357
  organizationTenancyCanonicalActivationEnabled: EnvBoolean.default(false),
355
358
  billingMode: BillingMode.default("disabled"),
359
+ // Explicit launch gate for the one-time $10 verified self-service signup grant.
360
+ // A migration or deployment alone must not start issuing live credits.
361
+ verifiedSignupTrialCreditsEnabled: EnvBoolean.default(false),
356
362
  entitlementsMode: EntitlementsMode.default("none"),
357
363
  usageLimitsMode: UsageLimitsMode.default("none"),
358
364
  staticEntitlementsJson: z.string().default("{}"),
@@ -1216,6 +1222,7 @@ const SettingsSchema = z.object({
1216
1222
  // Shape: { "modal": 5, "runloop": 4, ... }. Backends absent here meter
1217
1223
  // warm-seconds but accrue NO warm_cost / debit (rate 0).
1218
1224
  sandboxWarmRateMicrosPerSecondJson: z.string().default("{}"),
1225
+ sandboxWarmBillingMode: z.enum(["usage_only", "shadow", "credits"]).default("usage_only"),
1219
1226
  // Per-workspace warm cap (cumulative warm-seconds since the start of the UTC
1220
1227
  // month, summed over sandbox.warm_seconds). 0 = unbounded. A workspace over the
1221
1228
  // cap force-drains its VIEWER-ONLY boxes (guarded AND turn_holders=0 — a paying
@@ -1251,6 +1258,13 @@ const SettingsSchema = z.object({
1251
1258
  documentEmbeddingDimensions: z.coerce.number().int().positive().default(3072),
1252
1259
  documentEmbeddingApiKey: z.string().optional(),
1253
1260
  documentEmbeddingBaseUrl: z.string().url().optional(),
1261
+ documentEmbeddingBillingMode: z.enum(["usage_only", "shadow", "credits"]).default("usage_only"),
1262
+ // Explicit commercial cutover. Existing queued revisions remain unpriced;
1263
+ // only jobs created at or after this instant may enter the paid rail.
1264
+ documentEmbeddingCreditsActivatedAt: z.iso.datetime().optional(),
1265
+ // Customer tariff for the exact UTF-8 bytes sent to the embedder. Zero leaves
1266
+ // the paid mode unavailable; no commercial rate is selected by this PR.
1267
+ documentEmbeddingRateMicrosPerMillionBytes: z.coerce.number().int().nonnegative().default(0),
1254
1268
  documentCurationProvider: z.enum(["openai", "heuristic", "none"]).default("openai"),
1255
1269
  documentCurationModel: z.string().min(1).default("gpt-4o-mini"),
1256
1270
  documentCurationApiKey: z.string().optional(),
@@ -3074,6 +3088,7 @@ export function getSettings(source: NodeJS.ProcessEnv = process.env): Settings {
3074
3088
  "OPENGENI_ORGANIZATION_TENANCY_CANONICAL_ACTIVATION_ENABLED",
3075
3089
  ),
3076
3090
  billingMode: optional("OPENGENI_BILLING_MODE"),
3091
+ verifiedSignupTrialCreditsEnabled: optional("OPENGENI_VERIFIED_SIGNUP_TRIAL_CREDITS_ENABLED"),
3077
3092
  entitlementsMode: optional("OPENGENI_ENTITLEMENTS_MODE"),
3078
3093
  usageLimitsMode: optional("OPENGENI_USAGE_LIMITS_MODE"),
3079
3094
  staticEntitlementsJson: optional("OPENGENI_STATIC_ENTITLEMENTS_JSON"),
@@ -3360,6 +3375,7 @@ export function getSettings(source: NodeJS.ProcessEnv = process.env): Settings {
3360
3375
  sandboxWarmRateMicrosPerSecondJson: optional(
3361
3376
  "OPENGENI_SANDBOX_WARM_RATE_MICROS_PER_SECOND_JSON",
3362
3377
  ),
3378
+ sandboxWarmBillingMode: optional("OPENGENI_SANDBOX_WARM_BILLING_MODE"),
3363
3379
  sandboxMaxWarmSecondsPerWorkspace: optional("OPENGENI_SANDBOX_MAX_WARM_SECONDS_PER_WORKSPACE"),
3364
3380
  sandboxPreparationProfiles: optional("OPENGENI_SANDBOX_PREPARATION_PROFILES"),
3365
3381
  sandboxEnvAllowlist: optional("OPENGENI_SANDBOX_ENV_ALLOWLIST"),
@@ -3389,6 +3405,13 @@ export function getSettings(source: NodeJS.ProcessEnv = process.env): Settings {
3389
3405
  documentEmbeddingDimensions: optional("OPENGENI_DOCUMENT_EMBEDDING_DIMENSIONS"),
3390
3406
  documentEmbeddingApiKey: optional("OPENGENI_DOCUMENT_EMBEDDING_API_KEY"),
3391
3407
  documentEmbeddingBaseUrl: optional("OPENGENI_DOCUMENT_EMBEDDING_BASE_URL"),
3408
+ documentEmbeddingBillingMode: optional("OPENGENI_DOCUMENT_EMBEDDING_BILLING_MODE"),
3409
+ documentEmbeddingCreditsActivatedAt: optional(
3410
+ "OPENGENI_DOCUMENT_EMBEDDING_CREDITS_ACTIVATED_AT",
3411
+ ),
3412
+ documentEmbeddingRateMicrosPerMillionBytes: optional(
3413
+ "OPENGENI_DOCUMENT_EMBEDDING_RATE_MICROS_PER_MILLION_BYTES",
3414
+ ),
3392
3415
  documentCurationProvider: optional("OPENGENI_DOCUMENT_CURATION_PROVIDER"),
3393
3416
  documentCurationModel: optional("OPENGENI_DOCUMENT_CURATION_MODEL"),
3394
3417
  documentCurationApiKey: optional("OPENGENI_DOCUMENT_CURATION_API_KEY"),
@@ -3447,7 +3470,21 @@ export function getSettings(source: NodeJS.ProcessEnv = process.env): Settings {
3447
3470
  : parsed.sandboxIdleGraceMs,
3448
3471
  sandboxRotationLeadMs:
3449
3472
  raw.sandboxRotationLeadMs === undefined && parsed.sandboxBackend === "modal"
3450
- ? Math.min(3_600_000, Math.floor((parsed.modalTimeoutSeconds * 1000) / 2))
3473
+ ? Math.min(
3474
+ 3_600_000,
3475
+ Math.max(
3476
+ Math.floor((parsed.modalTimeoutSeconds * 1000) / 2),
3477
+ SANDBOX_DEADLINE_COMMAND_STOP_GRACE_MS +
3478
+ sandboxArchiveCaptureTimeoutMs({
3479
+ sandboxSnapshotTimeoutMs: Math.max(
3480
+ parsed.sandboxSnapshotTimeoutMs,
3481
+ parsed.sandboxDrainSnapshotTimeoutMs ?? parsed.sandboxSnapshotTimeoutMs,
3482
+ ),
3483
+ }) +
3484
+ 2 * parsed.sandboxLeaseReaperPeriodMs +
3485
+ 1,
3486
+ ),
3487
+ )
3451
3488
  : parsed.sandboxRotationLeadMs,
3452
3489
  mcpServers: ensureBuiltInMcpServers(parsed),
3453
3490
  };
@@ -4729,6 +4766,33 @@ function legacyImplicitOpenAiDefinitionVersionFor(
4729
4766
  });
4730
4767
  }
4731
4768
 
4769
+ function legacyCodexAstraImplicitCachingDefinitionVersionFor(
4770
+ model: ConfiguredModel,
4771
+ provider: ResolvedModelProvider,
4772
+ ): string | null {
4773
+ // The GPT-6 rollout only made the already-implicit Codex cache discoverable.
4774
+ // Bound this compatibility to that product/transport and exact declaration;
4775
+ // never normalize explicit caching, another capability, or another model.
4776
+ if (
4777
+ model.id !== "codex/gpt-6-astra" ||
4778
+ model.upstreamModelId !== "gpt-6-astra" ||
4779
+ provider.id !== CODEX_PROVIDER_ID ||
4780
+ provider.kind !== "codex-subscription" ||
4781
+ provider.api !== "responses" ||
4782
+ provider.wireProfile !== "openai" ||
4783
+ canonicalJson(model.capabilities.promptCaching) !==
4784
+ canonicalJson({ upstream: "supported", runnable: true, mode: "implicit" })
4785
+ ) {
4786
+ return null;
4787
+ }
4788
+ const { definitionVersion: _definitionVersion, ...modelWithoutVersion } = model;
4789
+ const { promptCaching: _promptCaching, ...capabilities } = model.capabilities;
4790
+ // Recompute, rather than allowlisting an incident hash: all other current
4791
+ // fields must still reproduce the accepted digest. Do not compose this with
4792
+ // the older wire-profile compatibility or rewrite the accepted policy.
4793
+ return definitionVersionFor({ ...modelWithoutVersion, capabilities }, provider);
4794
+ }
4795
+
4732
4796
  /**
4733
4797
  * The built-in provider's stable id: "openai" on the OpenAI platform, "azure"
4734
4798
  * on Azure. Exported because the workspace model-policy gate must attribute
@@ -5421,6 +5485,16 @@ export function resolveTurnExecutionPolicyV1(
5421
5485
  });
5422
5486
  }
5423
5487
 
5488
+ /** Explicit accepted execution identity matches, but its definition digest differs. */
5489
+ export class TurnExecutionPolicyDefinitionMismatchError extends Error {
5490
+ readonly code = "turn_execution_policy_definition_mismatch";
5491
+
5492
+ constructor() {
5493
+ super("Turn execution policy does not match the current provider definition");
5494
+ this.name = "TurnExecutionPolicyDefinitionMismatchError";
5495
+ }
5496
+ }
5497
+
5424
5498
  /**
5425
5499
  * Parse-time validation lives in @opengeni/contracts; this verifier binds a
5426
5500
  * present snapshot to the current executable definition and exact turn row.
@@ -5474,17 +5548,23 @@ export function assertTurnExecutionPolicyMatchesConfigV1(
5474
5548
  );
5475
5549
  const definitionVersionMatches =
5476
5550
  parsed.definitionVersion === resolved.model.definitionVersion ||
5477
- parsed.definitionVersion === legacyImplicitOpenAiDefinitionVersion;
5478
- const mismatched =
5551
+ parsed.definitionVersion === legacyImplicitOpenAiDefinitionVersion ||
5552
+ parsed.definitionVersion ===
5553
+ legacyCodexAstraImplicitCachingDefinitionVersionFor(resolved.model, resolved.provider);
5554
+ const identityMismatched =
5479
5555
  parsed.providerId !== resolved.provider.id ||
5480
5556
  parsed.upstreamModelId !== resolved.model.upstreamModelId ||
5481
5557
  parsed.wireApi !== resolved.model.api ||
5482
- !definitionVersionMatches ||
5483
5558
  canonicalJson(parsed.credentialSource) !== canonicalJson(resolved.model.credentialSource) ||
5484
5559
  canonicalJson(parsed.billing) !== canonicalJson(resolved.model.billing);
5485
- if (mismatched) {
5560
+ // Identity/source changes must never enter a rollout-retry classification,
5561
+ // even when their definition digest also differs.
5562
+ if (identityMismatched) {
5486
5563
  throw new Error("Turn execution policy does not match the current provider definition");
5487
5564
  }
5565
+ if (!definitionVersionMatches) {
5566
+ throw new TurnExecutionPolicyDefinitionMismatchError();
5567
+ }
5488
5568
  return { policy: parsed, provider: resolved.provider, model: resolved.model };
5489
5569
  }
5490
5570
 
@@ -7038,7 +7118,36 @@ function validateSettings(settings: Settings, source: NodeJS.ProcessEnv = proces
7038
7118
  sandboxEnvironmentVariableNames(settings);
7039
7119
  sandboxLifecycleHookIds(settings);
7040
7120
  // Fail fast on a malformed warm-rate table (P2.1).
7041
- parseSandboxWarmRateJson(settings.sandboxWarmRateMicrosPerSecondJson);
7121
+ const warmRates = parseSandboxWarmRateJson(settings.sandboxWarmRateMicrosPerSecondJson);
7122
+ // The meter rounds each settled interval to whole USD micros. Fractional
7123
+ // micros/second would make the total depend on heartbeat frequency; only
7124
+ // integer rates have interval-independent settlement at the same elapsed time.
7125
+ if (
7126
+ settings.sandboxWarmBillingMode === "credits" &&
7127
+ Object.values(warmRates).some((rate) => !Number.isSafeInteger(rate))
7128
+ ) {
7129
+ throw new Error(
7130
+ "OPENGENI_SANDBOX_WARM_RATE_MICROS_PER_SECOND_JSON paid rates must be safe integers",
7131
+ );
7132
+ }
7133
+ if (
7134
+ settings.documentEmbeddingBillingMode === "credits" &&
7135
+ settings.documentEmbeddingProvider === "openai" &&
7136
+ settings.documentEmbeddingRateMicrosPerMillionBytes <= 0
7137
+ ) {
7138
+ throw new Error(
7139
+ "OPENGENI_DOCUMENT_EMBEDDING_RATE_MICROS_PER_MILLION_BYTES must be positive when paid OpenAI embedding billing is enabled",
7140
+ );
7141
+ }
7142
+ if (
7143
+ settings.documentEmbeddingBillingMode === "credits" &&
7144
+ settings.documentEmbeddingProvider === "openai" &&
7145
+ !settings.documentEmbeddingCreditsActivatedAt
7146
+ ) {
7147
+ throw new Error(
7148
+ "OPENGENI_DOCUMENT_EMBEDDING_CREDITS_ACTIVATED_AT is required for paid OpenAI embeddings",
7149
+ );
7150
+ }
7042
7151
  if (settings.sandboxBackend === "opensandbox") {
7043
7152
  if (!/@sha256:[0-9a-f]{64}$/i.test(settings.openSandboxImage ?? "")) {
7044
7153
  throw new Error(
@@ -7113,11 +7222,13 @@ function validateSettings(settings: Settings, source: NodeJS.ProcessEnv = proces
7113
7222
  ordinaryCaptureTimeoutMs,
7114
7223
  drainCaptureTimeoutMs,
7115
7224
  );
7116
- if (!(rotationLeadMs > providerDeadlineCaptureTimeoutMs + reaperPeriod)) {
7225
+ const requiredRotationLeadMs =
7226
+ SANDBOX_DEADLINE_COMMAND_STOP_GRACE_MS + providerDeadlineCaptureTimeoutMs + 2 * reaperPeriod;
7227
+ if (!(rotationLeadMs > requiredRotationLeadMs)) {
7117
7228
  throw new Error(
7118
7229
  `OPENGENI_SANDBOX_ROTATION_LEAD_MS (${rotationLeadMs}) must exceed the ` +
7119
- `largest durable snapshot or drain capture timeout plus one reaper period ` +
7120
- `(${providerDeadlineCaptureTimeoutMs + reaperPeriod}), including for persisted Modal ` +
7230
+ `legacy command stop grace, largest durable snapshot or drain capture timeout, and two reaper periods ` +
7231
+ `(${requiredRotationLeadMs}), including for persisted Modal ` +
7121
7232
  `leases after a default-backend rollout.`,
7122
7233
  );
7123
7234
  }