@opengeni/config 0.3.0 → 0.5.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 +79 -58
- package/dist/index.js +425 -204
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
- package/src/index.ts +725 -340
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
StaticUsageLimits,
|
|
11
11
|
UsageLimitsMode
|
|
12
12
|
} from "@opengeni/contracts";
|
|
13
|
-
import { CODEX_MODEL_ID_PREFIX } from "@opengeni/codex/constants";
|
|
13
|
+
import { CODEX_MODEL_ID_PREFIX, CODEX_PROVIDER_ID } from "@opengeni/codex/constants";
|
|
14
14
|
import { z } from "zod";
|
|
15
15
|
var envName = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
16
16
|
var registryId = /^[A-Za-z0-9_-]+$/;
|
|
@@ -67,9 +67,9 @@ var DEFAULT_AGENT_INSTRUCTIONS = [
|
|
|
67
67
|
"File resources are mounted under files/<file-id>/ unless the session specifies another mount path.",
|
|
68
68
|
"Attached files are mounted read-only; copy them before modifying.",
|
|
69
69
|
"Bundled skills are under .agents/ and can include infrastructure, marketing, or other role-specific guidance.",
|
|
70
|
-
"Use Checkov, Terraform, Azure CLI,
|
|
70
|
+
"Use Checkov, Terraform, Azure CLI, git provider CLIs, and repository tools when relevant; gh, glab, and az repos are pre-authenticated when the host brokers matching git credentials.",
|
|
71
71
|
"When the Azure sandbox preparation profile is enabled and service-principal variables are present, the sandbox is pre-authenticated with normal Azure CLI before work starts.",
|
|
72
|
-
"Treat code-changing work as GitOps work: create a focused branch/commit/PR when
|
|
72
|
+
"Treat code-changing work as GitOps work: create a focused branch/commit/PR when git provider credentials are available; otherwise report exact commands and blockers.",
|
|
73
73
|
"Return concise, factual summaries with files changed, commands run, and remaining blockers.",
|
|
74
74
|
AGENT_INSTRUCTIONS_CORE_PLACEHOLDER
|
|
75
75
|
].join(" ");
|
|
@@ -146,62 +146,42 @@ var SettingsSchema = z.object({
|
|
|
146
146
|
// it then acts as a hard ceiling that per-goal overrides can only lower.
|
|
147
147
|
goalMaxAutoContinuations: z.coerce.number().int().positive().optional(),
|
|
148
148
|
goalNoProgressLimit: z.coerce.number().int().positive().default(3),
|
|
149
|
+
// Temporary safety valve: spawned child sessions still run and retain their
|
|
150
|
+
// own durable events/goals, but terminal child episodes do not create parent
|
|
151
|
+
// system updates or inference work unless explicitly enabled.
|
|
152
|
+
childCompletionParentWakeEnabled: EnvBoolean.default(false),
|
|
149
153
|
// Per-segment ceiling on agent loop turns (model calls) within a single
|
|
150
154
|
// session turn. Effectively unbounded by default for the same reason as
|
|
151
155
|
// above; the graceful max-turns valve (idle + goal continuation, never a
|
|
152
156
|
// session failure) remains as inert safety should a deployment set a cap.
|
|
153
157
|
agentMaxModelCallsPerTurn: z.coerce.number().int().positive().default(1e6),
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
// RunState blob. Items and the sandbox envelope are dual-written
|
|
158
|
-
// unconditionally; this flag governs the read path only, so flipping back to
|
|
159
|
-
// "run_state" remains a safe rollback at any time.
|
|
160
|
-
sessionHistorySource: z.enum(["run_state", "items"]).default("items"),
|
|
161
|
-
// Provider-aware conversation context management (long-lived sessions
|
|
162
|
-
// otherwise grow unbounded until they overflow the model context window and
|
|
163
|
-
// hard-fail every turn). Resolution (see resolveContextCompactionMode):
|
|
164
|
-
// "auto" (default) -> "server" when openaiProvider === "openai" (the
|
|
165
|
-
// OpenAI platform Responses API honors server-side context_management),
|
|
166
|
-
// else "client" (Azure rejects context_management with a 400, so we run
|
|
167
|
-
// our own client-side compaction).
|
|
168
|
-
// "server" / "client" -> force that path regardless of provider.
|
|
169
|
-
// "off" -> neither path (legacy unbounded growth; escape hatch only).
|
|
170
|
-
contextCompactionMode: z.enum(["auto", "server", "client", "off"]).default("auto"),
|
|
171
|
-
// The model's real context window in tokens. gpt-5.5's true window is
|
|
172
|
-
// 1,050,000; it is absent from the SDK's hardcoded compaction window map (it
|
|
173
|
-
// knows only up to gpt-5.4), so the SDK's DynamicCompactionPolicy would fall
|
|
174
|
-
// back to a wrong 240k. We pass an explicit StaticCompactionPolicy threshold
|
|
175
|
-
// derived from these settings on the server path, and use the same numbers to
|
|
176
|
-
// budget the client path.
|
|
158
|
+
// The model family's real context window in tokens. OpenGeni always performs
|
|
159
|
+
// one durable, portable plaintext compaction transition; there is no
|
|
160
|
+
// provider/server/off mode ladder.
|
|
177
161
|
contextWindowTokens: z.coerce.number().int().positive().default(105e4),
|
|
162
|
+
// Optional model-catalog effective input ceiling. Codex models expose this as
|
|
163
|
+
// raw context_window * effective_context_window_percent; when absent, retain
|
|
164
|
+
// the deployment-level window-minus-reserved-output behavior.
|
|
165
|
+
contextEffectiveWindowTokens: z.coerce.number().int().positive().optional(),
|
|
178
166
|
// Proactive compaction threshold as a ratio of the model context window.
|
|
179
|
-
// Defaults to
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
|
|
167
|
+
// Defaults to 90%: compact as late as possible — retained context beats early
|
|
168
|
+
// headroom now that per-model windows are declared honestly (input-effective,
|
|
169
|
+
// empirically measured), and the fail-closed reactive compact-on-reject path
|
|
170
|
+
// absorbs any overshoot as one retried call rather than a dead session.
|
|
171
|
+
// Clamped to [0.3, 0.9] so deployments can tune the trigger without
|
|
172
|
+
// accidentally disabling compaction.
|
|
173
|
+
contextCompactionThresholdRatio: z.coerce.number().default(0.9).transform((value) => {
|
|
183
174
|
if (!Number.isFinite(value)) {
|
|
184
|
-
return 0.
|
|
175
|
+
return 0.9;
|
|
185
176
|
}
|
|
186
177
|
return Math.min(0.9, Math.max(0.3, value));
|
|
187
178
|
}),
|
|
188
179
|
// Tokens reserved for model output; subtracted from the window to get the
|
|
189
180
|
// usable input budget B = contextWindowTokens - contextReservedOutputTokens.
|
|
190
181
|
contextReservedOutputTokens: z.coerce.number().int().nonnegative().default(128e3),
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
|
|
194
|
-
contextServerCompactThresholdTokens: z.coerce.number().int().positive().optional(),
|
|
195
|
-
// Deprecated back-compat knobs. The threshold is now controlled by
|
|
196
|
-
// contextCompactionThresholdRatio; these remain parsed so older deployments do
|
|
197
|
-
// not fail boot when their env still contains them.
|
|
198
|
-
contextCompactSoftFraction: z.coerce.number().positive().max(1).default(0.7),
|
|
199
|
-
contextCompactHardFraction: z.coerce.number().positive().max(1).default(0.85),
|
|
200
|
-
// Deprecated for the client path; parsed for env/back-compat only.
|
|
201
|
-
contextKeepRecentTokens: z.coerce.number().int().positive().default(32e3),
|
|
202
|
-
// Parsed for back-compat. Client compaction uses the fixed 20k Codex summary
|
|
203
|
-
// buffer as its generated-summary output ceiling.
|
|
204
|
-
contextSummaryMaxTokens: z.coerce.number().int().positive().default(2e4),
|
|
182
|
+
// Model-catalog auto-compact limit. When present it is clamped to
|
|
183
|
+
// 90% of the raw window, matching Codex core's auto_compact_token_limit().
|
|
184
|
+
contextAutoCompactThresholdTokens: z.coerce.number().int().positive().optional(),
|
|
205
185
|
authRequired: EnvBoolean.default(false),
|
|
206
186
|
accessKey: z.string().optional(),
|
|
207
187
|
authAllowHealth: EnvBoolean.default(true),
|
|
@@ -214,8 +194,8 @@ var SettingsSchema = z.object({
|
|
|
214
194
|
openaiProvider: z.enum(["openai", "azure"]).default("openai"),
|
|
215
195
|
openaiApiKey: z.string().optional(),
|
|
216
196
|
openaiBaseUrl: z.string().optional(),
|
|
217
|
-
openaiModel: z.string().default("gpt-5.
|
|
218
|
-
openaiAllowedModels: z.string().default("gpt-5.
|
|
197
|
+
openaiModel: z.string().default("gpt-5.6-sol"),
|
|
198
|
+
openaiAllowedModels: z.string().default("gpt-5.6-sol,gpt-5.6-terra,gpt-5.6-luna"),
|
|
219
199
|
modelPricingJson: z.string().default("{}"),
|
|
220
200
|
// Extra (non-built-in) model providers, declared by the host as a JSON
|
|
221
201
|
// provider registry. Each entry carries its own base URL, API key, wire API
|
|
@@ -237,6 +217,11 @@ var SettingsSchema = z.object({
|
|
|
237
217
|
// tool that BM25-discloses only the matching connectors. Default OFF — a codex
|
|
238
218
|
// turn is byte-for-byte unchanged until enabled. OPENGENI_CODEX_TOOL_SEARCH_ENABLED
|
|
239
219
|
codexToolSearchEnabled: EnvBoolean.default(false),
|
|
220
|
+
// OPE-21 atomic, workspace-local credential allocation. Default OFF is a
|
|
221
|
+
// deliberate rolling-deploy fence: migrate + roll every worker first, then
|
|
222
|
+
// enable. Turning it off restores the legacy sticky selector without a schema
|
|
223
|
+
// rollback; the additive lease table/cursor columns become inert.
|
|
224
|
+
codexCredentialLeasingEnabled: EnvBoolean.default(false),
|
|
240
225
|
// Multi-account P3 (auto-rotation): an account is "near exhaustion" — ineligible to be
|
|
241
226
|
// rotated TO — when EITHER usage window (5h/weekly) is at/over this percent. Default 90 to
|
|
242
227
|
// match the UI danger flip (UsageBar danger at pct >= 90). OPENGENI_CODEX_ROTATION_NEAR_EXHAUSTION_PCT.
|
|
@@ -274,7 +259,7 @@ var SettingsSchema = z.object({
|
|
|
274
259
|
// the non-bypassable CORE at AGENT_INSTRUCTIONS_CORE_PLACEHOLDER (or appends
|
|
275
260
|
// it when the template omits the marker), and uses the result as the agent's
|
|
276
261
|
// instructions. Defaulting to DEFAULT_AGENT_INSTRUCTIONS keeps the composed
|
|
277
|
-
// default
|
|
262
|
+
// default pinned by runtime tests.
|
|
278
263
|
agentInstructionsTemplate: z.string().default(DEFAULT_AGENT_INSTRUCTIONS),
|
|
279
264
|
azureOpenaiBaseUrl: z.string().optional(),
|
|
280
265
|
azureOpenaiEndpoint: z.string().optional(),
|
|
@@ -289,6 +274,15 @@ var SettingsSchema = z.object({
|
|
|
289
274
|
dockerNetwork: z.string().optional(),
|
|
290
275
|
modalAppName: z.string().default("opengeni-sandbox"),
|
|
291
276
|
modalImageRef: z.string().optional(),
|
|
277
|
+
// Name of a Modal Secret (containing REGISTRY_USERNAME + REGISTRY_PASSWORD) used
|
|
278
|
+
// to authenticate the pull of `modalImageRef` from a PRIVATE registry. When UNSET
|
|
279
|
+
// (the default), the sandbox image is pulled UNAUTHENTICATED — i.e. it must be a
|
|
280
|
+
// PUBLIC registry tag, which is the only shape the Agents-extension Modal backend
|
|
281
|
+
// supports out of the box (`Image.fromRegistry(tag)` with no secret). Set this to
|
|
282
|
+
// run a private image (e.g. a cloud-hosted ACR/ECR/GCR digest): the runtime resolves
|
|
283
|
+
// the named Secret and builds the image via `fromRegistry(tag, secret)` before the
|
|
284
|
+
// first sandbox is created. Knob: OPENGENI_MODAL_IMAGE_REGISTRY_SECRET.
|
|
285
|
+
modalImageRegistrySecret: z.string().optional(),
|
|
292
286
|
// Modal's hard sandbox lifetime (timeoutMs = this * 1000), counted from each
|
|
293
287
|
// create/resume — it is the BACKSTOP that reclaims a box if the reaper/worker is
|
|
294
288
|
// down, NOT the warm-window controller (that's sandboxIdleGraceMs). It must
|
|
@@ -370,6 +364,13 @@ var SettingsSchema = z.object({
|
|
|
370
364
|
// recordingMaxSeconds is the ffmpeg -t hard ceiling (bounds a multi-day turn).
|
|
371
365
|
recordingEnabled: EnvBoolean.default(true),
|
|
372
366
|
recordingDefaultCodec: z.enum(["h264-mp4", "vp9-webm"]).default("h264-mp4"),
|
|
367
|
+
// Workbench v2 turn-end workspace capture (dossier §10.1). When on, the turn
|
|
368
|
+
// activity probes the box's changed files off the live box at turn end and
|
|
369
|
+
// persists a capture revision (blobs in @opengeni/storage) so the workbench
|
|
370
|
+
// paints cold/offline sessions with zero machine round-trips. Best-effort and
|
|
371
|
+
// fully behind this flag: off ⇒ capture is skipped and reads fall back to the
|
|
372
|
+
// live/wake path (status-quo behavior). Default on; explicit per environment.
|
|
373
|
+
workspaceCaptureEnabled: EnvBoolean.default(true),
|
|
373
374
|
recordingFramerate: z.coerce.number().int().positive().default(15),
|
|
374
375
|
recordingMaxSeconds: z.coerce.number().int().positive().default(600),
|
|
375
376
|
recordingMaxBytes: z.coerce.number().int().positive().default(268435456),
|
|
@@ -426,6 +427,19 @@ var SettingsSchema = z.object({
|
|
|
426
427
|
// EnvBoolean (NOT z.coerce.boolean(), which would coerce "false" -> true and
|
|
427
428
|
// turn the flag ON the moment anyone set the env var to disable it).
|
|
428
429
|
sandboxOwnershipEnabled: EnvBoolean.default(false),
|
|
430
|
+
// --- lazy sandbox provisioning rollout flag, default OFF ---
|
|
431
|
+
// Only effective when sandboxOwnershipEnabled is ALSO on (lazy provisioning is a
|
|
432
|
+
// property of the owned path — the SDK never creates/resumes an injected session,
|
|
433
|
+
// so we control when the box is established). When TRUE, a turn does NOT provision
|
|
434
|
+
// its box at turn start: the lease acquire + resume-by-id + hooks + downloads +
|
|
435
|
+
// heartbeat + recording are deferred to an in-process single-flight provisioner
|
|
436
|
+
// that runs the FIRST time a sandbox op is dispatched (via the routing proxy's
|
|
437
|
+
// resolveActiveBackend). A turn whose model never calls a sandbox-backed tool ends
|
|
438
|
+
// with NO lease row and ZERO warm-seconds. When FALSE (or ownership off) the turn
|
|
439
|
+
// provisions eagerly exactly as today — byte-for-byte. EnvBoolean (NOT
|
|
440
|
+
// z.coerce.boolean(), which coerces "false" -> true and would turn the flag ON the
|
|
441
|
+
// moment anyone set the env var to disable it).
|
|
442
|
+
sandboxLazyProvisionEnabled: EnvBoolean.default(false),
|
|
429
443
|
// --- bring-your-own-compute (selfhosted 11th backend) rollout flag, default OFF ---
|
|
430
444
|
// The keystone flag for the whole selfhosted feature (the enrollment device-flow,
|
|
431
445
|
// the NATS control plane, the relay stream tier). When FALSE the enrollment routes
|
|
@@ -434,6 +448,11 @@ var SettingsSchema = z.object({
|
|
|
434
448
|
// z.coerce.boolean(), which coerces "false" -> true). Flipped per-environment via
|
|
435
449
|
// the deploy-staging IaC secret/configmap pattern (dossier §17/§25.1).
|
|
436
450
|
sandboxSelfhostedEnabled: EnvBoolean.default(false),
|
|
451
|
+
// Gates the op-stream (streaming exec) transport to Connected Machines. The
|
|
452
|
+
// runner must ALSO advertise Capabilities.op_stream; default off, and legacy
|
|
453
|
+
// request/reply exec is the permanent fallback. EnvBoolean (NOT
|
|
454
|
+
// z.coerce.boolean(), which coerces "false" -> true).
|
|
455
|
+
agentOpStreamEnabled: EnvBoolean.default(false),
|
|
437
456
|
// The HMAC secret the control plane signs the enrollment bearer credential with
|
|
438
457
|
// (the `oge_` envelope the agent presents back to the control plane). Optional:
|
|
439
458
|
// when ABSENT and sandboxSelfhostedEnabled is on, the poll route reports the
|
|
@@ -490,6 +509,25 @@ var SettingsSchema = z.object({
|
|
|
490
509
|
// bus connects anonymously (local dev / a NATS with no auth_callout).
|
|
491
510
|
selfhostedNatsControlUser: z.string().optional(),
|
|
492
511
|
selfhostedNatsControlPassword: z.string().optional(),
|
|
512
|
+
// --- selfhosted (Connected Machine) control/exec op deadlines ---------------
|
|
513
|
+
// The control plane splits its op deadline in two. CONTROL ops (ping / fs / git /
|
|
514
|
+
// desktop / pty) must stay responsive so a machine's liveness is never masked by a
|
|
515
|
+
// slow op, so they use the short control timeout. EXEC gets its OWN, larger budget:
|
|
516
|
+
// a real command (compile, test run, dependency install) routinely outlives the
|
|
517
|
+
// control timeout, and before the split a long command was killed at the ~30s
|
|
518
|
+
// control wall. The agent kills the exec child at this deadline; the wire waits
|
|
519
|
+
// slightly longer (SELFHOSTED_EXEC_REPLY_GRACE_MS) for the typed timed-out reply.
|
|
520
|
+
//
|
|
521
|
+
// The exec default is a DELIBERATELY MODEST 2min (not 5): the agent-side admission
|
|
522
|
+
// pool is (until a later agent release) a FLAT 8 permits with no per-class split,
|
|
523
|
+
// so 8 slow execs holding a permit for 5 minutes would blanket-DRAIN every fs/git
|
|
524
|
+
// op — shipping the amplifier before the class-aware-admission fix. 2min still
|
|
525
|
+
// clears the large majority of the observed >30s exec tail; genuinely long jobs run
|
|
526
|
+
// in the background (see the exec-deadline hint) or raise the knob per deployment.
|
|
527
|
+
// Knobs: OPENGENI_SANDBOX_SELFHOSTED_EXEC_TIMEOUT_MS (default 2min) and
|
|
528
|
+
// OPENGENI_SANDBOX_SELFHOSTED_CONTROL_TIMEOUT_MS (default 30s).
|
|
529
|
+
sandboxSelfhostedExecTimeoutMs: z.coerce.number().int().positive().default(12e4),
|
|
530
|
+
sandboxSelfhostedControlTimeoutMs: z.coerce.number().int().positive().default(3e4),
|
|
493
531
|
// --- sandbox lease cadences (cadence invariant validated at boot below) ---
|
|
494
532
|
// reaperPeriod < viewerHolderTTL, and reaperPeriod + idleGrace < the EFFECTIVE
|
|
495
533
|
// box idle timeout (effectiveModalIdleTimeoutSeconds, which defaults to the hard
|
|
@@ -508,6 +546,22 @@ var SettingsSchema = z.object({
|
|
|
508
546
|
// fresh EMPTY box; lower it to trade warm cost for a snappier reclaim. Knob:
|
|
509
547
|
// OPENGENI_SANDBOX_IDLE_GRACE_MS.
|
|
510
548
|
sandboxIdleGraceMs: z.coerce.number().int().positive().default(9e5),
|
|
549
|
+
// MID-SESSION /workspace snapshot cadence (sandbox-file-persistence). The
|
|
550
|
+
// reaper's drain-persist only protects boxes the reaper itself kills; a box
|
|
551
|
+
// that dies any other way (Modal's hard creation-time timeout on a session
|
|
552
|
+
// busy past it, provider OOM/infra death) loses everything since the last
|
|
553
|
+
// clean drain. While a turn holds the box, the turn heartbeat and turn-end
|
|
554
|
+
// both take a snapshot when at least this interval has passed since the last
|
|
555
|
+
// one (same epoch-fenced fold-onto-lease seam as the drain), bounding the
|
|
556
|
+
// worst-case loss of ANY unclean box death to this window. 0 disables.
|
|
557
|
+
// Knob: OPENGENI_SANDBOX_SNAPSHOT_INTERVAL_MS. Default 15min.
|
|
558
|
+
sandboxSnapshotIntervalMs: z.coerce.number().int().min(0).default(9e5),
|
|
559
|
+
// Maximum time a best-effort /workspace snapshot capture may hold turn/reaper
|
|
560
|
+
// cleanup. A hung provider snapshot must never pin a lease holder, block
|
|
561
|
+
// graceful shutdown, or become permission to GC an older archive. Timeout is
|
|
562
|
+
// treated exactly like a failed best-effort snapshot. Knob:
|
|
563
|
+
// OPENGENI_SANDBOX_SNAPSHOT_TIMEOUT_MS. Default 60s.
|
|
564
|
+
sandboxSnapshotTimeoutMs: z.coerce.number().int().positive().default(6e4),
|
|
511
565
|
// expires_at refresh window for a held lease (>> the turn 10s heartbeat so a
|
|
512
566
|
// single missed heartbeat never TTL-reaps a live turn). The warming TTL is the
|
|
513
567
|
// window a cold->warming spawner has to commit warm before a reaper resets it.
|
|
@@ -517,6 +571,11 @@ var SettingsSchema = z.object({
|
|
|
517
571
|
// (a liveness/reaper cadence), this bounds how long one turn waits for capacity
|
|
518
572
|
// or provider creation before surfacing a clear turn.failed error.
|
|
519
573
|
sandboxWarmingTimeoutMs: z.coerce.number().int().positive().default(6e5),
|
|
574
|
+
// Rig setup-script budget (M3): the wall-clock timeout the rig-setup lifecycle
|
|
575
|
+
// hook runs its script under, distinct from the 120s per-command lifecycle
|
|
576
|
+
// default (a rig may compile/install heavy tooling on first cold create).
|
|
577
|
+
// Env: OPENGENI_RIG_SETUP_TIMEOUT_MS. Default 10min.
|
|
578
|
+
rigSetupTimeoutMs: z.coerce.number().int().positive().default(6e5),
|
|
520
579
|
// --- sandbox warm-time billing (P2.1) ---
|
|
521
580
|
// Per-backend warm rate (usd_micros/sec), like modelPricingJson: an empty {}
|
|
522
581
|
// means warm-cost is not debited (warm-seconds are still metered for audit).
|
|
@@ -577,30 +636,32 @@ var SettingsSchema = z.object({
|
|
|
577
636
|
stripePublishableKey: z.string().optional(),
|
|
578
637
|
stripeWebhookSecret: z.string().optional(),
|
|
579
638
|
stripeCreditsProductId: z.string().optional(),
|
|
580
|
-
mcpServers: z.array(
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
639
|
+
mcpServers: z.array(
|
|
640
|
+
z.object({
|
|
641
|
+
id: z.string().min(1).regex(registryId),
|
|
642
|
+
name: z.string().min(1).optional(),
|
|
643
|
+
url: z.string().url(),
|
|
644
|
+
allowedTools: z.array(z.string().min(1)).optional(),
|
|
645
|
+
timeoutMs: z.number().int().positive().optional(),
|
|
646
|
+
cacheToolsList: z.boolean().default(false),
|
|
647
|
+
/**
|
|
648
|
+
* Human-approval policy for this server's tools, overlaid per-run from a
|
|
649
|
+
* session MCP server row (never from OPENGENI_MCP_SERVERS). `true` = all
|
|
650
|
+
* tools require approval; a string[] = only the listed UNPREFIXED tool
|
|
651
|
+
* names do; absent = auto-run (the historical default). Enforced in the
|
|
652
|
+
* runtime by attaching `needsApproval` to the matching MCP tools.
|
|
653
|
+
*/
|
|
654
|
+
requireApproval: z.union([z.boolean(), z.array(z.string().min(1))]).optional(),
|
|
655
|
+
/**
|
|
656
|
+
* Extra request headers sent to this MCP server (credential injection
|
|
657
|
+
* for workspace-enabled capability MCPs). Populated at runtime from
|
|
658
|
+
* encrypted capability-installation credentials; do not put secrets in
|
|
659
|
+
* OPENGENI_MCP_SERVERS.
|
|
660
|
+
*/
|
|
661
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
662
|
+
connectionRef: McpServerConnectionRefSchema.optional()
|
|
663
|
+
})
|
|
664
|
+
).default([])
|
|
604
665
|
});
|
|
605
666
|
var ModelPricingSchema = z.object({
|
|
606
667
|
inputMicrosPerMillionTokens: z.number().int().nonnegative(),
|
|
@@ -616,6 +677,8 @@ var RegistryModelSchema = z.object({
|
|
|
616
677
|
label: z.string().min(1).optional(),
|
|
617
678
|
// display name; defaults to id
|
|
618
679
|
contextWindowTokens: z.number().int().positive().optional(),
|
|
680
|
+
effectiveContextWindowTokens: z.number().int().positive().optional(),
|
|
681
|
+
autoCompactTokenLimit: z.number().int().positive().optional(),
|
|
619
682
|
reasoningEffort: z.boolean().optional(),
|
|
620
683
|
// model accepts a reasoning-effort control
|
|
621
684
|
hostedWebSearch: z.boolean().optional(),
|
|
@@ -644,12 +707,24 @@ var IntegrationOAuthClientConfigSchema = z.object({
|
|
|
644
707
|
tokenEndpointAuthMethod: z.enum(["none", "client_secret_post", "client_secret_basic"]).default("none")
|
|
645
708
|
});
|
|
646
709
|
var defaultModelPricing = {
|
|
647
|
-
"gpt-5.
|
|
710
|
+
"gpt-5.6-sol": {
|
|
648
711
|
inputMicrosPerMillionTokens: 5e6,
|
|
649
712
|
cachedInputMicrosPerMillionTokens: 5e5,
|
|
650
713
|
outputMicrosPerMillionTokens: 3e7,
|
|
651
714
|
marginBps: 2500
|
|
652
715
|
},
|
|
716
|
+
"gpt-5.6-terra": {
|
|
717
|
+
inputMicrosPerMillionTokens: 25e5,
|
|
718
|
+
cachedInputMicrosPerMillionTokens: 25e4,
|
|
719
|
+
outputMicrosPerMillionTokens: 15e6,
|
|
720
|
+
marginBps: 2500
|
|
721
|
+
},
|
|
722
|
+
"gpt-5.6-luna": {
|
|
723
|
+
inputMicrosPerMillionTokens: 1e6,
|
|
724
|
+
cachedInputMicrosPerMillionTokens: 1e5,
|
|
725
|
+
outputMicrosPerMillionTokens: 6e6,
|
|
726
|
+
marginBps: 2500
|
|
727
|
+
},
|
|
653
728
|
"gpt-5.4": {
|
|
654
729
|
inputMicrosPerMillionTokens: 25e5,
|
|
655
730
|
cachedInputMicrosPerMillionTokens: 25e4,
|
|
@@ -725,21 +800,11 @@ var SANDBOX_REQUIRED_ENV = {
|
|
|
725
800
|
{ field: "modalTokenId", env: "OPENGENI_MODAL_TOKEN_ID" },
|
|
726
801
|
{ field: "modalTokenSecret", env: "OPENGENI_MODAL_TOKEN_SECRET" }
|
|
727
802
|
],
|
|
728
|
-
daytona: [
|
|
729
|
-
|
|
730
|
-
],
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
],
|
|
734
|
-
e2b: [
|
|
735
|
-
{ field: "e2bApiKey", env: "OPENGENI_E2B_API_KEY" }
|
|
736
|
-
],
|
|
737
|
-
blaxel: [
|
|
738
|
-
{ field: "blaxelApiKey", env: "OPENGENI_BLAXEL_API_KEY" }
|
|
739
|
-
],
|
|
740
|
-
cloudflare: [
|
|
741
|
-
{ field: "cloudflareWorkerUrl", env: "OPENGENI_CLOUDFLARE_WORKER_URL" }
|
|
742
|
-
],
|
|
803
|
+
daytona: [{ field: "daytonaApiKey", env: "OPENGENI_DAYTONA_API_KEY" }],
|
|
804
|
+
runloop: [{ field: "runloopApiKey", env: "OPENGENI_RUNLOOP_API_KEY" }],
|
|
805
|
+
e2b: [{ field: "e2bApiKey", env: "OPENGENI_E2B_API_KEY" }],
|
|
806
|
+
blaxel: [{ field: "blaxelApiKey", env: "OPENGENI_BLAXEL_API_KEY" }],
|
|
807
|
+
cloudflare: [{ field: "cloudflareWorkerUrl", env: "OPENGENI_CLOUDFLARE_WORKER_URL" }],
|
|
743
808
|
vercel: [
|
|
744
809
|
{ field: "vercelToken", env: "OPENGENI_VERCEL_TOKEN" },
|
|
745
810
|
{ field: "vercelProjectId", env: "OPENGENI_VERCEL_PROJECT_ID" }
|
|
@@ -771,7 +836,9 @@ function getSettings() {
|
|
|
771
836
|
temporalNamespace: optional("OPENGENI_TEMPORAL_NAMESPACE"),
|
|
772
837
|
temporalTaskQueue: optional("OPENGENI_TEMPORAL_TASK_QUEUE"),
|
|
773
838
|
startupDependencyRetryAttempts: optional("OPENGENI_STARTUP_DEPENDENCY_RETRY_ATTEMPTS"),
|
|
774
|
-
startupDependencyRetryInitialDelayMs: optional(
|
|
839
|
+
startupDependencyRetryInitialDelayMs: optional(
|
|
840
|
+
"OPENGENI_STARTUP_DEPENDENCY_RETRY_INITIAL_DELAY_MS"
|
|
841
|
+
),
|
|
775
842
|
startupDependencyRetryMaxDelayMs: optional("OPENGENI_STARTUP_DEPENDENCY_RETRY_MAX_DELAY_MS"),
|
|
776
843
|
observabilityStructuredLogs: optional("OPENGENI_OBSERVABILITY_STRUCTURED_LOGS"),
|
|
777
844
|
observabilityMetricsEnabled: optional("OPENGENI_OBSERVABILITY_METRICS_ENABLED"),
|
|
@@ -793,21 +860,19 @@ function getSettings() {
|
|
|
793
860
|
environmentsEncryptionKey: optional("OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY"),
|
|
794
861
|
integrationsEnabled: optional("OPENGENI_INTEGRATIONS_ENABLED"),
|
|
795
862
|
integrationsStateSecret: optional("OPENGENI_INTEGRATIONS_STATE_SECRET"),
|
|
796
|
-
integrationsAllowPrivateNetworkTargets: optional(
|
|
863
|
+
integrationsAllowPrivateNetworkTargets: optional(
|
|
864
|
+
"OPENGENI_INTEGRATIONS_ALLOW_PRIVATE_NETWORK_TARGETS"
|
|
865
|
+
),
|
|
797
866
|
integrationsOauthClientsJson: optional("OPENGENI_INTEGRATIONS_OAUTH_CLIENTS_JSON"),
|
|
798
867
|
goalMaxAutoContinuations: optional("OPENGENI_GOAL_MAX_AUTO_CONTINUATIONS"),
|
|
799
868
|
goalNoProgressLimit: optional("OPENGENI_GOAL_NO_PROGRESS_LIMIT"),
|
|
869
|
+
childCompletionParentWakeEnabled: optional("OPENGENI_CHILD_COMPLETION_PARENT_WAKE_ENABLED"),
|
|
800
870
|
agentMaxModelCallsPerTurn: optional("OPENGENI_AGENT_MAX_MODEL_CALLS_PER_TURN"),
|
|
801
|
-
sessionHistorySource: optional("OPENGENI_SESSION_HISTORY_SOURCE"),
|
|
802
|
-
contextCompactionMode: optional("OPENGENI_CONTEXT_COMPACTION_MODE"),
|
|
803
871
|
contextWindowTokens: optional("OPENGENI_CONTEXT_WINDOW_TOKENS"),
|
|
872
|
+
contextEffectiveWindowTokens: optional("OPENGENI_CONTEXT_EFFECTIVE_WINDOW_TOKENS"),
|
|
804
873
|
contextCompactionThresholdRatio: optional("OPENGENI_COMPACTION_THRESHOLD_RATIO"),
|
|
805
874
|
contextReservedOutputTokens: optional("OPENGENI_CONTEXT_RESERVED_OUTPUT_TOKENS"),
|
|
806
|
-
|
|
807
|
-
contextCompactSoftFraction: optional("OPENGENI_CONTEXT_COMPACT_SOFT_FRACTION"),
|
|
808
|
-
contextCompactHardFraction: optional("OPENGENI_CONTEXT_COMPACT_HARD_FRACTION"),
|
|
809
|
-
contextKeepRecentTokens: optional("OPENGENI_CONTEXT_KEEP_RECENT_TOKENS"),
|
|
810
|
-
contextSummaryMaxTokens: optional("OPENGENI_CONTEXT_SUMMARY_MAX_TOKENS"),
|
|
875
|
+
contextAutoCompactThresholdTokens: optional("OPENGENI_CONTEXT_AUTO_COMPACT_THRESHOLD_TOKENS"),
|
|
811
876
|
authRequired: optional("OPENGENI_AUTH_REQUIRED"),
|
|
812
877
|
accessKey: optional("OPENGENI_ACCESS_KEY"),
|
|
813
878
|
authAllowHealth: optional("OPENGENI_AUTH_ALLOW_HEALTH"),
|
|
@@ -826,6 +891,7 @@ function getSettings() {
|
|
|
826
891
|
modelProvidersJson: optional("OPENGENI_MODEL_PROVIDERS_JSON"),
|
|
827
892
|
codexSubscriptionEnabled: optional("OPENGENI_CODEX_SUBSCRIPTION_ENABLED"),
|
|
828
893
|
codexToolSearchEnabled: optional("OPENGENI_CODEX_TOOL_SEARCH_ENABLED"),
|
|
894
|
+
codexCredentialLeasingEnabled: optional("OPENGENI_CODEX_CREDENTIAL_LEASING_ENABLED"),
|
|
829
895
|
codexProductSku: optional("OPENGENI_CODEX_PRODUCT_SKU"),
|
|
830
896
|
codexRotationNearExhaustionPct: optional("OPENGENI_CODEX_ROTATION_NEAR_EXHAUSTION_PCT"),
|
|
831
897
|
openaiReasoningEffort: optional("OPENGENI_OPENAI_REASONING_EFFORT"),
|
|
@@ -849,6 +915,7 @@ function getSettings() {
|
|
|
849
915
|
dockerNetwork: optional("OPENGENI_DOCKER_NETWORK"),
|
|
850
916
|
modalAppName: optional("OPENGENI_MODAL_APP_NAME"),
|
|
851
917
|
modalImageRef: optional("OPENGENI_MODAL_IMAGE_REF"),
|
|
918
|
+
modalImageRegistrySecret: optional("OPENGENI_MODAL_IMAGE_REGISTRY_SECRET"),
|
|
852
919
|
modalTimeoutSeconds: optional("OPENGENI_MODAL_TIMEOUT_SECONDS"),
|
|
853
920
|
modalTokenId: optional("OPENGENI_MODAL_TOKEN_ID"),
|
|
854
921
|
modalTokenSecret: optional("OPENGENI_MODAL_TOKEN_SECRET"),
|
|
@@ -864,6 +931,7 @@ function getSettings() {
|
|
|
864
931
|
computerUseEnabled: optional("OPENGENI_COMPUTER_USE_ENABLED"),
|
|
865
932
|
computerUseReadOnly: optional("OPENGENI_COMPUTER_USE_READONLY"),
|
|
866
933
|
recordingEnabled: optional("OPENGENI_RECORDING_ENABLED"),
|
|
934
|
+
workspaceCaptureEnabled: optional("OPENGENI_WORKSPACE_CAPTURE"),
|
|
867
935
|
recordingDefaultCodec: optional("OPENGENI_RECORDING_DEFAULT_CODEC"),
|
|
868
936
|
recordingFramerate: optional("OPENGENI_RECORDING_FRAMERATE"),
|
|
869
937
|
recordingMaxSeconds: optional("OPENGENI_RECORDING_MAX_SECONDS"),
|
|
@@ -903,7 +971,9 @@ function getSettings() {
|
|
|
903
971
|
vercelTeamId: optional("OPENGENI_VERCEL_TEAM_ID"),
|
|
904
972
|
vercelRuntime: optional("OPENGENI_VERCEL_RUNTIME"),
|
|
905
973
|
sandboxOwnershipEnabled: optional("OPENGENI_SANDBOX_OWNERSHIP_ENABLED"),
|
|
974
|
+
sandboxLazyProvisionEnabled: optional("OPENGENI_SANDBOX_LAZY_PROVISION"),
|
|
906
975
|
sandboxSelfhostedEnabled: optional("OPENGENI_SANDBOX_SELFHOSTED_ENABLED"),
|
|
976
|
+
agentOpStreamEnabled: optional("OPENGENI_AGENT_OP_STREAM_ENABLED"),
|
|
907
977
|
enrollmentSigningSecret: optional("OPENGENI_ENROLLMENT_SIGNING_SECRET"),
|
|
908
978
|
selfhostedNatsUrl: optional("OPENGENI_SELFHOSTED_NATS_URL"),
|
|
909
979
|
selfhostedRelayUrl: optional("OPENGENI_SELFHOSTED_RELAY_URL"),
|
|
@@ -915,13 +985,20 @@ function getSettings() {
|
|
|
915
985
|
selfhostedNatsCalloutPassword: optional("OPENGENI_SELFHOSTED_NATS_CALLOUT_PASSWORD"),
|
|
916
986
|
selfhostedNatsControlUser: optional("OPENGENI_SELFHOSTED_NATS_CONTROL_USER"),
|
|
917
987
|
selfhostedNatsControlPassword: optional("OPENGENI_SELFHOSTED_NATS_CONTROL_PASSWORD"),
|
|
988
|
+
sandboxSelfhostedExecTimeoutMs: optional("OPENGENI_SANDBOX_SELFHOSTED_EXEC_TIMEOUT_MS"),
|
|
989
|
+
sandboxSelfhostedControlTimeoutMs: optional("OPENGENI_SANDBOX_SELFHOSTED_CONTROL_TIMEOUT_MS"),
|
|
918
990
|
sandboxLeaseReaperPeriodMs: optional("OPENGENI_SANDBOX_LEASE_REAPER_PERIOD_MS"),
|
|
919
991
|
sandboxViewerHolderTtlMs: optional("OPENGENI_SANDBOX_VIEWER_HOLDER_TTL_MS"),
|
|
920
992
|
sandboxIdleGraceMs: optional("OPENGENI_SANDBOX_IDLE_GRACE_MS"),
|
|
993
|
+
sandboxSnapshotIntervalMs: optional("OPENGENI_SANDBOX_SNAPSHOT_INTERVAL_MS"),
|
|
994
|
+
sandboxSnapshotTimeoutMs: optional("OPENGENI_SANDBOX_SNAPSHOT_TIMEOUT_MS"),
|
|
921
995
|
sandboxLeaseTtlMs: optional("OPENGENI_SANDBOX_LEASE_TTL_MS"),
|
|
922
996
|
sandboxLeaseWarmingTtlMs: optional("OPENGENI_SANDBOX_LEASE_WARMING_TTL_MS"),
|
|
923
997
|
sandboxWarmingTimeoutMs: optional("OPENGENI_SANDBOX_WARMING_TIMEOUT_MS"),
|
|
924
|
-
|
|
998
|
+
rigSetupTimeoutMs: optional("OPENGENI_RIG_SETUP_TIMEOUT_MS"),
|
|
999
|
+
sandboxWarmRateMicrosPerSecondJson: optional(
|
|
1000
|
+
"OPENGENI_SANDBOX_WARM_RATE_MICROS_PER_SECOND_JSON"
|
|
1001
|
+
),
|
|
925
1002
|
sandboxMaxWarmSecondsPerWorkspace: optional("OPENGENI_SANDBOX_MAX_WARM_SECONDS_PER_WORKSPACE"),
|
|
926
1003
|
sandboxPreparationProfiles: optional("OPENGENI_SANDBOX_PREPARATION_PROFILES"),
|
|
927
1004
|
sandboxEnvAllowlist: optional("OPENGENI_SANDBOX_ENV_ALLOWLIST"),
|
|
@@ -1017,8 +1094,7 @@ function configuredProviders(settings) {
|
|
|
1017
1094
|
label: builtinProviderLabel(settings),
|
|
1018
1095
|
kind: "api-key",
|
|
1019
1096
|
api: "responses",
|
|
1020
|
-
builtin: true
|
|
1021
|
-
compactionMode: resolveContextCompactionMode(settings)
|
|
1097
|
+
builtin: true
|
|
1022
1098
|
};
|
|
1023
1099
|
if (settings.openaiProvider === "azure") {
|
|
1024
1100
|
builtin.baseUrl = settings.azureOpenaiBaseUrl ?? settings.azureOpenaiEndpoint;
|
|
@@ -1027,28 +1103,41 @@ function configuredProviders(settings) {
|
|
|
1027
1103
|
builtin.baseUrl = settings.openaiBaseUrl;
|
|
1028
1104
|
builtin.apiKey = settings.openaiApiKey;
|
|
1029
1105
|
}
|
|
1030
|
-
const registry = parseModelProvidersJson(settings.modelProvidersJson).map(
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1106
|
+
const registry = parseModelProvidersJson(settings.modelProvidersJson).map(
|
|
1107
|
+
(provider) => ({
|
|
1108
|
+
id: provider.id,
|
|
1109
|
+
label: provider.label ?? provider.id,
|
|
1110
|
+
kind: provider.kind,
|
|
1111
|
+
api: provider.api,
|
|
1112
|
+
builtin: false,
|
|
1113
|
+
baseUrl: provider.baseUrl,
|
|
1114
|
+
apiKey: resolveProviderApiKey(provider),
|
|
1115
|
+
defaultQuery: provider.defaultQuery,
|
|
1116
|
+
defaultHeaders: provider.defaultHeaders
|
|
1117
|
+
})
|
|
1118
|
+
);
|
|
1042
1119
|
return [builtin, ...registry];
|
|
1043
1120
|
}
|
|
1121
|
+
function policyProviderIdForModel(settings, modelId) {
|
|
1122
|
+
if (modelId.startsWith(CODEX_MODEL_ID_PREFIX)) {
|
|
1123
|
+
return CODEX_PROVIDER_ID;
|
|
1124
|
+
}
|
|
1125
|
+
const configured = configuredModels(settings).find((model) => model.id === modelId);
|
|
1126
|
+
return configured?.providerId ?? builtinProviderId(settings);
|
|
1127
|
+
}
|
|
1044
1128
|
function configuredModels(settings) {
|
|
1045
1129
|
const builtinId = builtinProviderId(settings);
|
|
1046
1130
|
const builtinLabel = builtinProviderLabel(settings);
|
|
1047
1131
|
const registryOwnedIds = new Set(
|
|
1048
|
-
parseModelProvidersJson(settings.modelProvidersJson).flatMap(
|
|
1132
|
+
parseModelProvidersJson(settings.modelProvidersJson).flatMap(
|
|
1133
|
+
(provider) => provider.models.map((model) => model.id)
|
|
1134
|
+
)
|
|
1049
1135
|
);
|
|
1050
1136
|
const isRegistryNamespaced = (id) => id.startsWith(CODEX_MODEL_ID_PREFIX) || id.includes("/") && registryOwnedIds.has(id);
|
|
1051
|
-
const out = uniqueValues([
|
|
1137
|
+
const out = uniqueValues([
|
|
1138
|
+
settings.openaiModel,
|
|
1139
|
+
...splitCsv(settings.openaiAllowedModels)
|
|
1140
|
+
]).filter((id) => !isRegistryNamespaced(id)).map((id) => ({
|
|
1052
1141
|
id,
|
|
1053
1142
|
label: id,
|
|
1054
1143
|
providerId: builtinId,
|
|
@@ -1068,6 +1157,8 @@ function configuredModels(settings) {
|
|
|
1068
1157
|
providerLabel,
|
|
1069
1158
|
api: provider.api,
|
|
1070
1159
|
...model.contextWindowTokens === void 0 ? {} : { contextWindowTokens: model.contextWindowTokens },
|
|
1160
|
+
...model.effectiveContextWindowTokens === void 0 ? {} : { effectiveContextWindowTokens: model.effectiveContextWindowTokens },
|
|
1161
|
+
...model.autoCompactTokenLimit === void 0 ? {} : { autoCompactTokenLimit: model.autoCompactTokenLimit },
|
|
1071
1162
|
reasoningEffort: model.reasoningEffort ?? false,
|
|
1072
1163
|
hostedWebSearch: model.hostedWebSearch ?? false
|
|
1073
1164
|
});
|
|
@@ -1090,7 +1181,9 @@ function resolveModelProvider(settings, modelId) {
|
|
|
1090
1181
|
if (!model) {
|
|
1091
1182
|
return void 0;
|
|
1092
1183
|
}
|
|
1093
|
-
const provider = configuredProviders(settings).find(
|
|
1184
|
+
const provider = configuredProviders(settings).find(
|
|
1185
|
+
(candidate) => candidate.id === model.providerId
|
|
1186
|
+
);
|
|
1094
1187
|
if (!provider) {
|
|
1095
1188
|
return void 0;
|
|
1096
1189
|
}
|
|
@@ -1112,27 +1205,25 @@ function configuredModelPricing(settings) {
|
|
|
1112
1205
|
...configured
|
|
1113
1206
|
};
|
|
1114
1207
|
}
|
|
1115
|
-
function resolveContextCompactionMode(settings) {
|
|
1116
|
-
switch (settings.contextCompactionMode) {
|
|
1117
|
-
case "server":
|
|
1118
|
-
return "server";
|
|
1119
|
-
case "client":
|
|
1120
|
-
return "client";
|
|
1121
|
-
case "off":
|
|
1122
|
-
return "off";
|
|
1123
|
-
case "auto":
|
|
1124
|
-
default:
|
|
1125
|
-
return settings.openaiProvider === "openai" ? "server" : "client";
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
1208
|
function contextInputBudgetTokens(settings) {
|
|
1209
|
+
if (settings.contextEffectiveWindowTokens !== void 0) {
|
|
1210
|
+
return Math.min(settings.contextWindowTokens, settings.contextEffectiveWindowTokens);
|
|
1211
|
+
}
|
|
1129
1212
|
return Math.max(0, settings.contextWindowTokens - settings.contextReservedOutputTokens);
|
|
1130
1213
|
}
|
|
1131
|
-
function
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1214
|
+
function settingsWithResolvedModelContext(settings, model) {
|
|
1215
|
+
const contextWindowTokens = model.contextWindowTokens ?? settings.contextWindowTokens;
|
|
1216
|
+
return {
|
|
1217
|
+
...settings,
|
|
1218
|
+
contextWindowTokens,
|
|
1219
|
+
...model.effectiveContextWindowTokens === void 0 ? {} : {
|
|
1220
|
+
contextEffectiveWindowTokens: Math.min(
|
|
1221
|
+
contextWindowTokens,
|
|
1222
|
+
model.effectiveContextWindowTokens
|
|
1223
|
+
)
|
|
1224
|
+
},
|
|
1225
|
+
...model.autoCompactTokenLimit === void 0 ? {} : { contextAutoCompactThresholdTokens: model.autoCompactTokenLimit }
|
|
1226
|
+
};
|
|
1136
1227
|
}
|
|
1137
1228
|
function configuredStaticUsageLimits(settings) {
|
|
1138
1229
|
return parseStaticUsageLimitsJson(settings.staticUsageLimitsJson);
|
|
@@ -1165,7 +1256,10 @@ function calculateModelUsageCostMicros(settings, model, usage) {
|
|
|
1165
1256
|
return Math.ceil(rawCost * (1e4 + marginBps) / 1e4);
|
|
1166
1257
|
}
|
|
1167
1258
|
function configuredAllowedReasoningEfforts(settings) {
|
|
1168
|
-
return uniqueValues([
|
|
1259
|
+
return uniqueValues([
|
|
1260
|
+
settings.openaiReasoningEffort,
|
|
1261
|
+
...splitCsv(settings.openaiAllowedReasoningEfforts)
|
|
1262
|
+
]).map((value) => ReasoningEffort.parse(value));
|
|
1169
1263
|
}
|
|
1170
1264
|
function environmentsEncryptionKeyBytes(settings) {
|
|
1171
1265
|
if (!settings.environmentsEncryptionKey) {
|
|
@@ -1173,7 +1267,9 @@ function environmentsEncryptionKeyBytes(settings) {
|
|
|
1173
1267
|
}
|
|
1174
1268
|
const decoded = Buffer.from(settings.environmentsEncryptionKey, "base64");
|
|
1175
1269
|
if (decoded.length !== 32) {
|
|
1176
|
-
throw new Error(
|
|
1270
|
+
throw new Error(
|
|
1271
|
+
"OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY must be base64 for exactly 32 bytes (generate with: openssl rand -base64 32)"
|
|
1272
|
+
);
|
|
1177
1273
|
}
|
|
1178
1274
|
return new Uint8Array(decoded);
|
|
1179
1275
|
}
|
|
@@ -1188,12 +1284,21 @@ function dbSearchPath(settings) {
|
|
|
1188
1284
|
return `${schema},opengeni_private,public`;
|
|
1189
1285
|
}
|
|
1190
1286
|
function collectGitIdentityEnvironment(settings) {
|
|
1191
|
-
return Object.fromEntries(
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1287
|
+
return Object.fromEntries(
|
|
1288
|
+
Object.entries({
|
|
1289
|
+
GIT_AUTHOR_NAME: settings.gitAuthorName,
|
|
1290
|
+
GIT_AUTHOR_EMAIL: settings.gitAuthorEmail,
|
|
1291
|
+
GIT_COMMITTER_NAME: settings.gitCommitterName ?? settings.gitAuthorName,
|
|
1292
|
+
GIT_COMMITTER_EMAIL: settings.gitCommitterEmail ?? settings.gitAuthorEmail
|
|
1293
|
+
}).filter(
|
|
1294
|
+
(entry) => typeof entry[1] === "string" && entry[1].trim().length > 0
|
|
1295
|
+
)
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
var DEFAULT_SANDBOX_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
1299
|
+
function prependPathEntry(pathValue, entry) {
|
|
1300
|
+
const parts = (pathValue ?? DEFAULT_SANDBOX_PATH).split(":").filter(Boolean);
|
|
1301
|
+
return [entry, ...parts.filter((part) => part !== entry)].join(":");
|
|
1197
1302
|
}
|
|
1198
1303
|
function stableSandboxEnvironmentForRun(settings, workspaceEnvironment = {}, options = {}) {
|
|
1199
1304
|
const environment = {
|
|
@@ -1205,18 +1310,35 @@ function stableSandboxEnvironmentForRun(settings, workspaceEnvironment = {}, opt
|
|
|
1205
1310
|
if (settings.sandboxBackend !== "none" && settings.sandboxBackend !== "local") {
|
|
1206
1311
|
environment.HOME ??= descriptor.workspaceRoot;
|
|
1207
1312
|
}
|
|
1208
|
-
|
|
1313
|
+
const provisionedGitHelperBackend = settings.sandboxBackend !== "none" && settings.sandboxBackend !== "local" && settings.sandboxBackend !== "selfhosted";
|
|
1314
|
+
if (provisionedGitHelperBackend) {
|
|
1315
|
+
const home = environment.HOME ?? descriptor.workspaceRoot;
|
|
1316
|
+
environment.OPENGENI_GIT_CREDENTIALS_DIR ??= `${home}/.opengeni/git-credentials`;
|
|
1317
|
+
environment.OPENGENI_GIT_TOKEN_FILE ??= `${home}/.opengeni/git-token`;
|
|
1318
|
+
environment.OPENGENI_GIT_CLI_WRAPPER_DIR ??= `${home}/.opengeni/bin`;
|
|
1319
|
+
environment.PATH = prependPathEntry(environment.PATH, environment.OPENGENI_GIT_CLI_WRAPPER_DIR);
|
|
1320
|
+
}
|
|
1209
1321
|
if (settings.toolspaceEnabled) {
|
|
1210
1322
|
environment.OPENGENI_TOOLSPACE_TOKEN_FILE ??= `${environment.HOME ?? descriptor.workspaceRoot}/.opengeni/toolspace-token`;
|
|
1211
1323
|
if (options.workspaceId) {
|
|
1212
|
-
environment.OPENGENI_TOOLSPACE_URL ??= firstPartyMcpWorkspaceUrl(
|
|
1324
|
+
environment.OPENGENI_TOOLSPACE_URL ??= firstPartyMcpWorkspaceUrl(
|
|
1325
|
+
settings,
|
|
1326
|
+
options.workspaceId
|
|
1327
|
+
);
|
|
1213
1328
|
}
|
|
1214
1329
|
}
|
|
1215
1330
|
return environment;
|
|
1216
1331
|
}
|
|
1217
1332
|
function hasGitHubRepositorySelection(resources) {
|
|
1218
1333
|
const positive = (value) => typeof value === "number" && Number.isInteger(value) && value > 0 || typeof value === "string" && /^\d+$/.test(value) && Number(value) > 0;
|
|
1219
|
-
return resources.some(
|
|
1334
|
+
return resources.some(
|
|
1335
|
+
(resource) => resource.kind === "repository" && (positive(resource.githubInstallationId) && positive(resource.githubRepositoryId) || resource.provider === "github" && positive(resource.installationId) && positive(resource.repositoryId))
|
|
1336
|
+
);
|
|
1337
|
+
}
|
|
1338
|
+
function hasGitCredentialRepositorySelection(resources) {
|
|
1339
|
+
return resources.some(
|
|
1340
|
+
(resource) => resource.kind === "repository" && (resource.provider === "github" || resource.provider === "gitlab" || resource.provider === "azure_devops" || hasGitHubRepositorySelection([resource]))
|
|
1341
|
+
);
|
|
1220
1342
|
}
|
|
1221
1343
|
function applyGitAuthPointerEnvironment(environment, identity) {
|
|
1222
1344
|
environment.GIT_ASKPASS = `${environment.HOME ?? "/workspace"}/.opengeni/askpass`;
|
|
@@ -1271,10 +1393,14 @@ function sandboxLifecycleHookIds(settings) {
|
|
|
1271
1393
|
return uniqueValues(ids);
|
|
1272
1394
|
}
|
|
1273
1395
|
function sandboxPreparationProfileNames(settings) {
|
|
1274
|
-
const profiles = splitCsv(settings.sandboxPreparationProfiles).map(
|
|
1396
|
+
const profiles = splitCsv(settings.sandboxPreparationProfiles).map(
|
|
1397
|
+
(value) => value.toLowerCase()
|
|
1398
|
+
);
|
|
1275
1399
|
if (profiles.includes("none")) {
|
|
1276
1400
|
if (profiles.length > 1) {
|
|
1277
|
-
throw new Error(
|
|
1401
|
+
throw new Error(
|
|
1402
|
+
"OPENGENI_SANDBOX_PREPARATION_PROFILES cannot combine none with other profiles"
|
|
1403
|
+
);
|
|
1278
1404
|
}
|
|
1279
1405
|
return ["none"];
|
|
1280
1406
|
}
|
|
@@ -1306,7 +1432,7 @@ function parseMcpServers(raw) {
|
|
|
1306
1432
|
return parsed;
|
|
1307
1433
|
} catch (error) {
|
|
1308
1434
|
const message = error instanceof Error ? error.message : String(error);
|
|
1309
|
-
throw new Error(`OPENGENI_MCP_SERVERS must be a JSON array: ${message}
|
|
1435
|
+
throw new Error(`OPENGENI_MCP_SERVERS must be a JSON array: ${message}`, { cause: error });
|
|
1310
1436
|
}
|
|
1311
1437
|
}
|
|
1312
1438
|
function parseModelPricingJson(raw) {
|
|
@@ -1318,7 +1444,7 @@ function parseModelPricingJson(raw) {
|
|
|
1318
1444
|
parsed = JSON.parse(raw);
|
|
1319
1445
|
} catch (error) {
|
|
1320
1446
|
const message = error instanceof Error ? error.message : String(error);
|
|
1321
|
-
throw new Error(`OPENGENI_MODEL_PRICING_JSON must be valid JSON: ${message}
|
|
1447
|
+
throw new Error(`OPENGENI_MODEL_PRICING_JSON must be valid JSON: ${message}`, { cause: error });
|
|
1322
1448
|
}
|
|
1323
1449
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1324
1450
|
throw new Error("OPENGENI_MODEL_PRICING_JSON must be a JSON object keyed by model name");
|
|
@@ -1341,19 +1467,28 @@ function parseSandboxWarmRateJson(raw) {
|
|
|
1341
1467
|
parsed = JSON.parse(raw);
|
|
1342
1468
|
} catch (error) {
|
|
1343
1469
|
const message = error instanceof Error ? error.message : String(error);
|
|
1344
|
-
throw new Error(
|
|
1470
|
+
throw new Error(
|
|
1471
|
+
`OPENGENI_SANDBOX_WARM_RATE_MICROS_PER_SECOND_JSON must be valid JSON: ${message}`,
|
|
1472
|
+
{ cause: error }
|
|
1473
|
+
);
|
|
1345
1474
|
}
|
|
1346
1475
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1347
|
-
throw new Error(
|
|
1476
|
+
throw new Error(
|
|
1477
|
+
"OPENGENI_SANDBOX_WARM_RATE_MICROS_PER_SECOND_JSON must be a JSON object keyed by backend name"
|
|
1478
|
+
);
|
|
1348
1479
|
}
|
|
1349
1480
|
const out = {};
|
|
1350
1481
|
for (const [backend, value] of Object.entries(parsed)) {
|
|
1351
1482
|
if (!backend.trim()) {
|
|
1352
|
-
throw new Error(
|
|
1483
|
+
throw new Error(
|
|
1484
|
+
"OPENGENI_SANDBOX_WARM_RATE_MICROS_PER_SECOND_JSON contains an empty backend name"
|
|
1485
|
+
);
|
|
1353
1486
|
}
|
|
1354
1487
|
const rate = typeof value === "number" ? value : Number(value);
|
|
1355
1488
|
if (!Number.isFinite(rate) || rate < 0) {
|
|
1356
|
-
throw new Error(
|
|
1489
|
+
throw new Error(
|
|
1490
|
+
`OPENGENI_SANDBOX_WARM_RATE_MICROS_PER_SECOND_JSON rate for ${backend} must be a non-negative number`
|
|
1491
|
+
);
|
|
1357
1492
|
}
|
|
1358
1493
|
out[backend] = rate;
|
|
1359
1494
|
}
|
|
@@ -1372,7 +1507,9 @@ function parseModelProvidersJson(raw) {
|
|
|
1372
1507
|
parsed = JSON.parse(raw);
|
|
1373
1508
|
} catch (error) {
|
|
1374
1509
|
const message = error instanceof Error ? error.message : String(error);
|
|
1375
|
-
throw new Error(`OPENGENI_MODEL_PROVIDERS_JSON must be valid JSON: ${message}
|
|
1510
|
+
throw new Error(`OPENGENI_MODEL_PROVIDERS_JSON must be valid JSON: ${message}`, {
|
|
1511
|
+
cause: error
|
|
1512
|
+
});
|
|
1376
1513
|
}
|
|
1377
1514
|
if (!Array.isArray(parsed)) {
|
|
1378
1515
|
throw new Error("OPENGENI_MODEL_PROVIDERS_JSON must be a JSON array of providers");
|
|
@@ -1380,7 +1517,9 @@ function parseModelProvidersJson(raw) {
|
|
|
1380
1517
|
return parsed.map((entry, index) => {
|
|
1381
1518
|
const result = RegistryProviderSchema.safeParse(entry);
|
|
1382
1519
|
if (!result.success) {
|
|
1383
|
-
throw new Error(
|
|
1520
|
+
throw new Error(
|
|
1521
|
+
`OPENGENI_MODEL_PROVIDERS_JSON provider[${index}] is invalid: ${result.error.message}`
|
|
1522
|
+
);
|
|
1384
1523
|
}
|
|
1385
1524
|
return result.data;
|
|
1386
1525
|
});
|
|
@@ -1394,10 +1533,14 @@ function parseIntegrationsOauthClientsJson(raw) {
|
|
|
1394
1533
|
parsed = JSON.parse(raw);
|
|
1395
1534
|
} catch (error) {
|
|
1396
1535
|
const message = error instanceof Error ? error.message : String(error);
|
|
1397
|
-
throw new Error(`OPENGENI_INTEGRATIONS_OAUTH_CLIENTS_JSON must be valid JSON: ${message}
|
|
1536
|
+
throw new Error(`OPENGENI_INTEGRATIONS_OAUTH_CLIENTS_JSON must be valid JSON: ${message}`, {
|
|
1537
|
+
cause: error
|
|
1538
|
+
});
|
|
1398
1539
|
}
|
|
1399
1540
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1400
|
-
throw new Error(
|
|
1541
|
+
throw new Error(
|
|
1542
|
+
"OPENGENI_INTEGRATIONS_OAUTH_CLIENTS_JSON must be a JSON object keyed by authorization-server issuer or URL"
|
|
1543
|
+
);
|
|
1401
1544
|
}
|
|
1402
1545
|
const out = {};
|
|
1403
1546
|
for (const [key, value] of Object.entries(parsed)) {
|
|
@@ -1406,7 +1549,9 @@ function parseIntegrationsOauthClientsJson(raw) {
|
|
|
1406
1549
|
}
|
|
1407
1550
|
const result = IntegrationOAuthClientConfigSchema.safeParse(value);
|
|
1408
1551
|
if (!result.success) {
|
|
1409
|
-
throw new Error(
|
|
1552
|
+
throw new Error(
|
|
1553
|
+
`OPENGENI_INTEGRATIONS_OAUTH_CLIENTS_JSON client for ${key} is invalid: ${result.error.message}`
|
|
1554
|
+
);
|
|
1410
1555
|
}
|
|
1411
1556
|
out[key] = result.data;
|
|
1412
1557
|
}
|
|
@@ -1421,7 +1566,9 @@ function parseStaticUsageLimitsJson(raw) {
|
|
|
1421
1566
|
parsed = JSON.parse(raw);
|
|
1422
1567
|
} catch (error) {
|
|
1423
1568
|
const message = error instanceof Error ? error.message : String(error);
|
|
1424
|
-
throw new Error(`OPENGENI_STATIC_USAGE_LIMITS_JSON must be valid JSON: ${message}
|
|
1569
|
+
throw new Error(`OPENGENI_STATIC_USAGE_LIMITS_JSON must be valid JSON: ${message}`, {
|
|
1570
|
+
cause: error
|
|
1571
|
+
});
|
|
1425
1572
|
}
|
|
1426
1573
|
return StaticUsageLimits.parse(parsed);
|
|
1427
1574
|
}
|
|
@@ -1434,7 +1581,9 @@ function parseStaticEntitlementsJson(raw) {
|
|
|
1434
1581
|
parsed = JSON.parse(raw);
|
|
1435
1582
|
} catch (error) {
|
|
1436
1583
|
const message = error instanceof Error ? error.message : String(error);
|
|
1437
|
-
throw new Error(`OPENGENI_STATIC_ENTITLEMENTS_JSON must be valid JSON: ${message}
|
|
1584
|
+
throw new Error(`OPENGENI_STATIC_ENTITLEMENTS_JSON must be valid JSON: ${message}`, {
|
|
1585
|
+
cause: error
|
|
1586
|
+
});
|
|
1438
1587
|
}
|
|
1439
1588
|
return Entitlements.parse(parsed);
|
|
1440
1589
|
}
|
|
@@ -1481,20 +1630,32 @@ function ensureBuiltInMcpServers(settings) {
|
|
|
1481
1630
|
// safe to cache / leave as-is.)
|
|
1482
1631
|
cacheToolsList: false
|
|
1483
1632
|
},
|
|
1484
|
-
...hasFiles ? [] : [
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1633
|
+
...hasFiles ? [] : [
|
|
1634
|
+
{
|
|
1635
|
+
id: "files",
|
|
1636
|
+
name: "Files",
|
|
1637
|
+
url: firstPartyMcpUrl,
|
|
1638
|
+
allowedTools: ["files_get_download_url"],
|
|
1639
|
+
cacheToolsList: true
|
|
1640
|
+
}
|
|
1641
|
+
],
|
|
1642
|
+
...hasDocs ? [] : [
|
|
1643
|
+
{
|
|
1644
|
+
id: "docs",
|
|
1645
|
+
name: "Document Search",
|
|
1646
|
+
url: firstPartyDocsMcpUrl,
|
|
1647
|
+
allowedTools: [
|
|
1648
|
+
"search_documents",
|
|
1649
|
+
"fetch_document_chunk",
|
|
1650
|
+
"list_document_bases",
|
|
1651
|
+
"knowledge_search",
|
|
1652
|
+
"knowledge_fetch",
|
|
1653
|
+
"memory_search",
|
|
1654
|
+
"memory_propose"
|
|
1655
|
+
],
|
|
1656
|
+
cacheToolsList: false
|
|
1657
|
+
}
|
|
1658
|
+
],
|
|
1498
1659
|
...existing
|
|
1499
1660
|
];
|
|
1500
1661
|
}
|
|
@@ -1524,40 +1685,58 @@ function validateSettings(settings) {
|
|
|
1524
1685
|
}
|
|
1525
1686
|
if (settings.productAccessMode === "managed") {
|
|
1526
1687
|
if (!settings.publicBaseUrl) {
|
|
1527
|
-
throw new Error(
|
|
1688
|
+
throw new Error(
|
|
1689
|
+
"OPENGENI_PUBLIC_BASE_URL is required when OPENGENI_PRODUCT_ACCESS_MODE=managed"
|
|
1690
|
+
);
|
|
1528
1691
|
}
|
|
1529
1692
|
if (!settings.betterAuthSecret) {
|
|
1530
|
-
throw new Error(
|
|
1693
|
+
throw new Error(
|
|
1694
|
+
"OPENGENI_BETTER_AUTH_SECRET is required when OPENGENI_PRODUCT_ACCESS_MODE=managed"
|
|
1695
|
+
);
|
|
1531
1696
|
}
|
|
1532
1697
|
if (!settings.delegationSecret) {
|
|
1533
|
-
throw new Error(
|
|
1698
|
+
throw new Error(
|
|
1699
|
+
"OPENGENI_DELEGATION_SECRET is required when OPENGENI_PRODUCT_ACCESS_MODE=managed"
|
|
1700
|
+
);
|
|
1534
1701
|
}
|
|
1535
1702
|
if (!["local", "test"].includes(settings.environment) && !settings.resendApiKey) {
|
|
1536
1703
|
throw new Error("OPENGENI_RESEND_API_KEY is required for managed mode outside local/test");
|
|
1537
1704
|
}
|
|
1538
1705
|
if (!["local", "test"].includes(settings.environment) && !settings.environmentsEncryptionKey) {
|
|
1539
|
-
throw new Error(
|
|
1706
|
+
throw new Error(
|
|
1707
|
+
"OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY is required for managed mode outside local/test"
|
|
1708
|
+
);
|
|
1540
1709
|
}
|
|
1541
1710
|
}
|
|
1542
1711
|
environmentsEncryptionKeyBytes(settings);
|
|
1543
1712
|
if (settings.integrationsEnabled) {
|
|
1544
1713
|
if (settings.productAccessMode === "managed" && !settings.publicBaseUrl) {
|
|
1545
|
-
throw new Error(
|
|
1714
|
+
throw new Error(
|
|
1715
|
+
"OPENGENI_PUBLIC_BASE_URL is required when OPENGENI_INTEGRATIONS_ENABLED=true and OPENGENI_PRODUCT_ACCESS_MODE=managed"
|
|
1716
|
+
);
|
|
1546
1717
|
}
|
|
1547
1718
|
if (settings.publicBaseUrl && !settings.publicBaseUrl.startsWith("https://") && !["local", "test"].includes(settings.environment)) {
|
|
1548
|
-
throw new Error(
|
|
1719
|
+
throw new Error(
|
|
1720
|
+
"OPENGENI_PUBLIC_BASE_URL must use https when OPENGENI_INTEGRATIONS_ENABLED=true outside local/test"
|
|
1721
|
+
);
|
|
1549
1722
|
}
|
|
1550
1723
|
if (!settings.integrationsStateSecret && !["local", "test"].includes(settings.environment)) {
|
|
1551
|
-
throw new Error(
|
|
1724
|
+
throw new Error(
|
|
1725
|
+
"OPENGENI_INTEGRATIONS_STATE_SECRET is required when OPENGENI_INTEGRATIONS_ENABLED=true outside local/test"
|
|
1726
|
+
);
|
|
1552
1727
|
}
|
|
1553
1728
|
}
|
|
1554
1729
|
parseIntegrationsOauthClientsJson(settings.integrationsOauthClientsJson);
|
|
1555
1730
|
if (settings.productAccessMode === "configured" && !["local", "test"].includes(settings.environment) && !settings.delegationSecret && !settings.authRequired) {
|
|
1556
|
-
throw new Error(
|
|
1731
|
+
throw new Error(
|
|
1732
|
+
"OPENGENI_PRODUCT_ACCESS_MODE=configured requires OPENGENI_DELEGATION_SECRET or OPENGENI_AUTH_REQUIRED=true outside local/test"
|
|
1733
|
+
);
|
|
1557
1734
|
}
|
|
1558
1735
|
if (settings.billingMode === "stripe") {
|
|
1559
1736
|
if (!settings.stripeSecretKey || !settings.stripeWebhookSecret) {
|
|
1560
|
-
throw new Error(
|
|
1737
|
+
throw new Error(
|
|
1738
|
+
"OPENGENI_STRIPE_SECRET_KEY and OPENGENI_STRIPE_WEBHOOK_SECRET are required when OPENGENI_BILLING_MODE=stripe"
|
|
1739
|
+
);
|
|
1561
1740
|
}
|
|
1562
1741
|
}
|
|
1563
1742
|
if (settings.productAccessMode !== "managed" && settings.billingMode === "stripe") {
|
|
@@ -1567,13 +1746,17 @@ function validateSettings(settings) {
|
|
|
1567
1746
|
const pricing = configuredModelPricing(settings);
|
|
1568
1747
|
const missing = configuredAllowedModels(settings).filter((model) => !pricing[model]);
|
|
1569
1748
|
if (missing.length > 0) {
|
|
1570
|
-
throw new Error(
|
|
1749
|
+
throw new Error(
|
|
1750
|
+
`Missing model pricing for managed billing model(s): ${missing.join(", ")}. Set OPENGENI_MODEL_PRICING_JSON.`
|
|
1751
|
+
);
|
|
1571
1752
|
}
|
|
1572
1753
|
}
|
|
1573
1754
|
if (settings.usageLimitsMode === "static") {
|
|
1574
1755
|
const limits = configuredStaticUsageLimits(settings);
|
|
1575
1756
|
if (Object.keys(limits).length === 0) {
|
|
1576
|
-
throw new Error(
|
|
1757
|
+
throw new Error(
|
|
1758
|
+
"OPENGENI_STATIC_USAGE_LIMITS_JSON must define at least one cap when OPENGENI_USAGE_LIMITS_MODE=static"
|
|
1759
|
+
);
|
|
1577
1760
|
}
|
|
1578
1761
|
} else {
|
|
1579
1762
|
parseStaticUsageLimitsJson(settings.staticUsageLimitsJson);
|
|
@@ -1581,7 +1764,9 @@ function validateSettings(settings) {
|
|
|
1581
1764
|
if (settings.entitlementsMode === "static") {
|
|
1582
1765
|
const entitlements = parseStaticEntitlementsJson(settings.staticEntitlementsJson);
|
|
1583
1766
|
if (Object.keys(entitlements).length === 0) {
|
|
1584
|
-
throw new Error(
|
|
1767
|
+
throw new Error(
|
|
1768
|
+
"OPENGENI_STATIC_ENTITLEMENTS_JSON must define at least one feature when OPENGENI_ENTITLEMENTS_MODE=static"
|
|
1769
|
+
);
|
|
1585
1770
|
}
|
|
1586
1771
|
} else {
|
|
1587
1772
|
parseStaticEntitlementsJson(settings.staticEntitlementsJson);
|
|
@@ -1591,7 +1776,9 @@ function validateSettings(settings) {
|
|
|
1591
1776
|
}
|
|
1592
1777
|
if (settings.openaiProvider === "azure") {
|
|
1593
1778
|
if (!settings.azureOpenaiBaseUrl && !settings.azureOpenaiEndpoint) {
|
|
1594
|
-
throw new Error(
|
|
1779
|
+
throw new Error(
|
|
1780
|
+
"Azure OpenAI requires OPENGENI_AZURE_OPENAI_BASE_URL or OPENGENI_AZURE_OPENAI_ENDPOINT"
|
|
1781
|
+
);
|
|
1595
1782
|
}
|
|
1596
1783
|
if (!settings.azureOpenaiBaseUrl && !settings.azureOpenaiDeployment) {
|
|
1597
1784
|
throw new Error("Azure OpenAI endpoint mode requires OPENGENI_AZURE_OPENAI_DEPLOYMENT");
|
|
@@ -1604,52 +1791,76 @@ function validateSettings(settings) {
|
|
|
1604
1791
|
}
|
|
1605
1792
|
}
|
|
1606
1793
|
if (Boolean(settings.modalTokenId) !== Boolean(settings.modalTokenSecret)) {
|
|
1607
|
-
throw new Error(
|
|
1794
|
+
throw new Error(
|
|
1795
|
+
"OPENGENI_MODAL_TOKEN_ID and OPENGENI_MODAL_TOKEN_SECRET must both be set or both omitted"
|
|
1796
|
+
);
|
|
1608
1797
|
}
|
|
1609
1798
|
for (const required of SANDBOX_REQUIRED_ENV[settings.sandboxBackend] ?? []) {
|
|
1610
1799
|
const value = settings[required.field];
|
|
1611
1800
|
if (value === void 0 || value === null || typeof value === "string" && value.trim().length === 0) {
|
|
1612
|
-
throw new Error(
|
|
1801
|
+
throw new Error(
|
|
1802
|
+
`${required.env} is required when OPENGENI_SANDBOX_BACKEND=${settings.sandboxBackend}`
|
|
1803
|
+
);
|
|
1613
1804
|
}
|
|
1614
1805
|
}
|
|
1615
1806
|
if (settings.objectStorageBackend === "s3-compatible" || settings.objectStorageBackend === "aws-s3") {
|
|
1616
1807
|
if (Boolean(settings.objectStorageAccessKeyId) !== Boolean(settings.objectStorageSecretAccessKey)) {
|
|
1617
|
-
throw new Error(
|
|
1808
|
+
throw new Error(
|
|
1809
|
+
"OPENGENI_OBJECT_STORAGE_ACCESS_KEY_ID and OPENGENI_OBJECT_STORAGE_SECRET_ACCESS_KEY must both be set or both omitted"
|
|
1810
|
+
);
|
|
1618
1811
|
}
|
|
1619
1812
|
if (settings.objectStorageBackend === "s3-compatible" && (settings.objectStorageEndpoint || settings.objectStorageSandboxEndpoint) && (!settings.objectStorageAccessKeyId || !settings.objectStorageSecretAccessKey)) {
|
|
1620
|
-
throw new Error(
|
|
1813
|
+
throw new Error(
|
|
1814
|
+
"S3-compatible object storage endpoints require OPENGENI_OBJECT_STORAGE_ACCESS_KEY_ID and OPENGENI_OBJECT_STORAGE_SECRET_ACCESS_KEY"
|
|
1815
|
+
);
|
|
1621
1816
|
}
|
|
1622
1817
|
if (settings.objectStorageAzureConnectionString || settings.objectStorageAzureAccountName || settings.objectStorageAzureAccountKey || settings.objectStorageAzureEndpoint) {
|
|
1623
|
-
throw new Error(
|
|
1818
|
+
throw new Error(
|
|
1819
|
+
"S3 object storage uses OPENGENI_OBJECT_STORAGE_* S3 settings, not OPENGENI_OBJECT_STORAGE_AZURE_* settings"
|
|
1820
|
+
);
|
|
1624
1821
|
}
|
|
1625
1822
|
if (settings.objectStorageGcsProjectId || settings.objectStorageGcsCredentialsJson || settings.objectStorageGcsKeyFilename || settings.objectStorageGcsApiEndpoint) {
|
|
1626
|
-
throw new Error(
|
|
1823
|
+
throw new Error(
|
|
1824
|
+
"S3 object storage uses OPENGENI_OBJECT_STORAGE_* S3 settings, not OPENGENI_OBJECT_STORAGE_GCS_* settings"
|
|
1825
|
+
);
|
|
1627
1826
|
}
|
|
1628
1827
|
} else if (settings.objectStorageBackend === "azure-blob") {
|
|
1629
1828
|
if (settings.objectStorageEndpoint || settings.objectStorageSandboxEndpoint || settings.objectStorageAccessKeyId || settings.objectStorageSecretAccessKey) {
|
|
1630
|
-
throw new Error(
|
|
1829
|
+
throw new Error(
|
|
1830
|
+
"Azure Blob storage uses OPENGENI_OBJECT_STORAGE_AZURE_* settings, not S3-compatible object storage settings"
|
|
1831
|
+
);
|
|
1631
1832
|
}
|
|
1632
1833
|
if (settings.objectStorageGcsProjectId || settings.objectStorageGcsCredentialsJson || settings.objectStorageGcsKeyFilename || settings.objectStorageGcsApiEndpoint) {
|
|
1633
|
-
throw new Error(
|
|
1834
|
+
throw new Error(
|
|
1835
|
+
"Azure Blob storage uses OPENGENI_OBJECT_STORAGE_AZURE_* settings, not OPENGENI_OBJECT_STORAGE_GCS_* settings"
|
|
1836
|
+
);
|
|
1634
1837
|
}
|
|
1635
1838
|
const hasConnectionString = Boolean(settings.objectStorageAzureConnectionString);
|
|
1636
1839
|
const hasSharedKey = Boolean(settings.objectStorageAzureAccountName) && Boolean(settings.objectStorageAzureAccountKey);
|
|
1637
1840
|
if (!hasConnectionString && !hasSharedKey) {
|
|
1638
|
-
throw new Error(
|
|
1841
|
+
throw new Error(
|
|
1842
|
+
"Azure Blob storage requires OPENGENI_OBJECT_STORAGE_AZURE_CONNECTION_STRING or OPENGENI_OBJECT_STORAGE_AZURE_ACCOUNT_NAME plus OPENGENI_OBJECT_STORAGE_AZURE_ACCOUNT_KEY"
|
|
1843
|
+
);
|
|
1639
1844
|
}
|
|
1640
1845
|
} else {
|
|
1641
1846
|
if (settings.objectStorageEndpoint || settings.objectStorageSandboxEndpoint || settings.objectStorageAccessKeyId || settings.objectStorageSecretAccessKey) {
|
|
1642
|
-
throw new Error(
|
|
1847
|
+
throw new Error(
|
|
1848
|
+
"GCS object storage uses OPENGENI_OBJECT_STORAGE_GCS_* settings, not S3-compatible object storage settings"
|
|
1849
|
+
);
|
|
1643
1850
|
}
|
|
1644
1851
|
if (settings.objectStorageAzureConnectionString || settings.objectStorageAzureAccountName || settings.objectStorageAzureAccountKey || settings.objectStorageAzureEndpoint) {
|
|
1645
|
-
throw new Error(
|
|
1852
|
+
throw new Error(
|
|
1853
|
+
"GCS object storage uses OPENGENI_OBJECT_STORAGE_GCS_* settings, not OPENGENI_OBJECT_STORAGE_AZURE_* settings"
|
|
1854
|
+
);
|
|
1646
1855
|
}
|
|
1647
1856
|
if (settings.objectStorageGcsCredentialsJson) {
|
|
1648
1857
|
parseGcsCredentialsJson(settings.objectStorageGcsCredentialsJson);
|
|
1649
1858
|
}
|
|
1650
1859
|
}
|
|
1651
1860
|
if (settings.documentChunkOverlap >= settings.documentChunkSize) {
|
|
1652
|
-
throw new Error(
|
|
1861
|
+
throw new Error(
|
|
1862
|
+
"OPENGENI_DOCUMENT_CHUNK_OVERLAP must be smaller than OPENGENI_DOCUMENT_CHUNK_SIZE"
|
|
1863
|
+
);
|
|
1653
1864
|
}
|
|
1654
1865
|
parseExposedPorts(settings.dockerExposedPorts);
|
|
1655
1866
|
sandboxEnvironmentVariableNames(settings);
|
|
@@ -1699,14 +1910,20 @@ function validateSettings(settings) {
|
|
|
1699
1910
|
const providerIds = /* @__PURE__ */ new Set();
|
|
1700
1911
|
for (const provider of registryProviders) {
|
|
1701
1912
|
if (provider.id === builtinId) {
|
|
1702
|
-
throw new Error(
|
|
1913
|
+
throw new Error(
|
|
1914
|
+
`OPENGENI_MODEL_PROVIDERS_JSON provider id ${provider.id} collides with the built-in provider id`
|
|
1915
|
+
);
|
|
1703
1916
|
}
|
|
1704
1917
|
if (providerIds.has(provider.id)) {
|
|
1705
|
-
throw new Error(
|
|
1918
|
+
throw new Error(
|
|
1919
|
+
`OPENGENI_MODEL_PROVIDERS_JSON contains duplicate provider id ${provider.id}`
|
|
1920
|
+
);
|
|
1706
1921
|
}
|
|
1707
1922
|
providerIds.add(provider.id);
|
|
1708
1923
|
if (!resolveProviderApiKey(provider)) {
|
|
1709
|
-
throw new Error(
|
|
1924
|
+
throw new Error(
|
|
1925
|
+
`OPENGENI_MODEL_PROVIDERS_JSON provider ${provider.id} requires a resolvable API key (set apiKey or apiKeyEnv)`
|
|
1926
|
+
);
|
|
1710
1927
|
}
|
|
1711
1928
|
}
|
|
1712
1929
|
}
|
|
@@ -1784,7 +2001,9 @@ function parseGcsCredentialsJson(raw) {
|
|
|
1784
2001
|
return JSON.parse(raw);
|
|
1785
2002
|
} catch (error) {
|
|
1786
2003
|
const message = error instanceof Error ? error.message : String(error);
|
|
1787
|
-
throw new Error(`OPENGENI_OBJECT_STORAGE_GCS_CREDENTIALS_JSON must be valid JSON: ${message}
|
|
2004
|
+
throw new Error(`OPENGENI_OBJECT_STORAGE_GCS_CREDENTIALS_JSON must be valid JSON: ${message}`, {
|
|
2005
|
+
cause: error
|
|
2006
|
+
});
|
|
1788
2007
|
}
|
|
1789
2008
|
}
|
|
1790
2009
|
function delay(ms) {
|
|
@@ -1799,6 +2018,7 @@ export {
|
|
|
1799
2018
|
RegistryProviderKind,
|
|
1800
2019
|
SANDBOX_REQUIRED_ENV,
|
|
1801
2020
|
applyGitAuthPointerEnvironment,
|
|
2021
|
+
builtinProviderId,
|
|
1802
2022
|
calculateModelUsageCostMicros,
|
|
1803
2023
|
collectGitIdentityEnvironment,
|
|
1804
2024
|
collectSandboxEnvironment,
|
|
@@ -1810,7 +2030,6 @@ export {
|
|
|
1810
2030
|
configuredProviders,
|
|
1811
2031
|
configuredStaticUsageLimits,
|
|
1812
2032
|
contextInputBudgetTokens,
|
|
1813
|
-
contextServerCompactThreshold,
|
|
1814
2033
|
dbSearchPath,
|
|
1815
2034
|
defaultModelPricing,
|
|
1816
2035
|
effectiveModalIdleTimeoutSeconds,
|
|
@@ -1818,6 +2037,7 @@ export {
|
|
|
1818
2037
|
firstPartyMcpBaseUrl,
|
|
1819
2038
|
firstPartyMcpWorkspaceUrl,
|
|
1820
2039
|
getSettings,
|
|
2040
|
+
hasGitCredentialRepositorySelection,
|
|
1821
2041
|
hasGitHubRepositorySelection,
|
|
1822
2042
|
parseExposedPorts,
|
|
1823
2043
|
parseIntegrationsOauthClientsJson,
|
|
@@ -1827,8 +2047,8 @@ export {
|
|
|
1827
2047
|
parseSandboxWarmRateJson,
|
|
1828
2048
|
parseStaticEntitlementsJson,
|
|
1829
2049
|
parseStaticUsageLimitsJson,
|
|
2050
|
+
policyProviderIdForModel,
|
|
1830
2051
|
requiredSandboxEnvForBackend,
|
|
1831
|
-
resolveContextCompactionMode,
|
|
1832
2052
|
resolveEnrollmentSigningSecret,
|
|
1833
2053
|
resolveModelProvider,
|
|
1834
2054
|
resolveNatsCalloutConfig,
|
|
@@ -1841,6 +2061,7 @@ export {
|
|
|
1841
2061
|
sandboxLifecycleHookIds,
|
|
1842
2062
|
sandboxPreparationProfiles,
|
|
1843
2063
|
sandboxWarmRateMicrosPerSecond,
|
|
2064
|
+
settingsWithResolvedModelContext,
|
|
1844
2065
|
stableSandboxEnvironmentForRun,
|
|
1845
2066
|
startupRetryOptions,
|
|
1846
2067
|
streamTokenDegraded
|