@opengeni/config 0.11.0 → 0.13.2
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 +70 -3
- package/dist/index.js +237 -69
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +351 -77
package/src/index.ts
CHANGED
|
@@ -9,12 +9,14 @@ import {
|
|
|
9
9
|
ReasoningEffort,
|
|
10
10
|
SandboxBackend,
|
|
11
11
|
SessionMcpApprovalPolicy,
|
|
12
|
+
SEEDANCE_2_5_MODEL_ID,
|
|
12
13
|
StaticUsageLimits,
|
|
13
14
|
TurnExecutionPolicyV1,
|
|
14
15
|
UsageLimitsMode,
|
|
15
16
|
type TurnExecutionLatencyModeSourceV1,
|
|
16
17
|
type TurnExecutionModelSourceV1,
|
|
17
18
|
type TurnExecutionReasoningSourceV1,
|
|
19
|
+
type VideoGenerationResolution,
|
|
18
20
|
} from "@opengeni/contracts";
|
|
19
21
|
import { CODEX_MODEL_TOOL_OUTPUT_TRUNCATION_TOKENS } from "@opengeni/codex";
|
|
20
22
|
import {
|
|
@@ -31,6 +33,24 @@ import { z } from "zod";
|
|
|
31
33
|
|
|
32
34
|
const envName = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
33
35
|
const registryId = /^[A-Za-z0-9_-]+$/;
|
|
36
|
+
|
|
37
|
+
// Archive capture claims are also the admission/teardown fence around a
|
|
38
|
+
// provider snapshot. Keep a real settlement window after the provider request;
|
|
39
|
+
// a configured request timeout may never consume the entire durable claim.
|
|
40
|
+
export const SANDBOX_ARCHIVE_CAPTURE_MAX_TIMEOUT_MS = 60 * 60_000;
|
|
41
|
+
export const SANDBOX_ARCHIVE_CAPTURE_SETTLEMENT_GRACE_MS = 10_000;
|
|
42
|
+
export const SANDBOX_SNAPSHOT_MAX_TIMEOUT_MS =
|
|
43
|
+
SANDBOX_ARCHIVE_CAPTURE_MAX_TIMEOUT_MS - SANDBOX_ARCHIVE_CAPTURE_SETTLEMENT_GRACE_MS;
|
|
44
|
+
// Admission waits are observational: successful capture/teardown returns as
|
|
45
|
+
// soon as the DB fence clears. This ceiling only covers the unhealthy path. It
|
|
46
|
+
// allows one scheduled inventory and one complete successor claim without
|
|
47
|
+
// turning a recoverable lifecycle transition into a visible caller error. A
|
|
48
|
+
// dead holder's TTL is deliberately NOT included: admission cannot accelerate
|
|
49
|
+
// that proof, and the turn moves to durable recovery if holder quiescence takes
|
|
50
|
+
// longer than this observational wait. The outer cap remains an explicit
|
|
51
|
+
// request-resource boundary; successful transitions return immediately.
|
|
52
|
+
export const SANDBOX_LIFECYCLE_TRANSITION_MAX_WAIT_MS = 60 * 60_000;
|
|
53
|
+
export const SANDBOX_LIFECYCLE_RETRY_HANDOFF_GRACE_MS = 10_000;
|
|
34
54
|
const EnvBoolean = z.preprocess((value) => {
|
|
35
55
|
if (typeof value !== "string") {
|
|
36
56
|
return value;
|
|
@@ -214,6 +234,12 @@ const SettingsSchema = z.object({
|
|
|
214
234
|
turnWorkerMaxConcurrentTurns: z.coerce.number().int().positive().max(2_000).default(16),
|
|
215
235
|
turnWorkerTargetCpuUsage: z.coerce.number().positive().max(1).default(0.8),
|
|
216
236
|
turnWorkerTargetMemoryUsage: z.coerce.number().positive().max(0.8).default(0.75),
|
|
237
|
+
// Admission and emergency recovery are deliberately separate control loops.
|
|
238
|
+
// The Temporal tuner stops polling at the lower target; only genuine danger
|
|
239
|
+
// may invoke the disruptive graceful-drain fallback.
|
|
240
|
+
turnWorkerEmergencyMemoryUsage: z.coerce.number().min(0.85).max(0.95).default(0.9),
|
|
241
|
+
turnWorkerMemoryGuardIntervalMs: z.coerce.number().int().min(1_000).max(60_000).default(5_000),
|
|
242
|
+
turnWorkerMemoryGuardSustainMs: z.coerce.number().int().min(5_000).max(300_000).default(30_000),
|
|
217
243
|
observabilityStructuredLogs: EnvBoolean.default(false),
|
|
218
244
|
observabilityMetricsEnabled: EnvBoolean.default(true),
|
|
219
245
|
observabilityOtlpEndpoint: z.string().url().optional(),
|
|
@@ -249,7 +275,13 @@ const SettingsSchema = z.object({
|
|
|
249
275
|
agentStableVersion: z
|
|
250
276
|
.string()
|
|
251
277
|
.regex(/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/u)
|
|
252
|
-
.default("0.1.
|
|
278
|
+
.default("0.1.14"),
|
|
279
|
+
// Optional independent beta-channel pointer. When unset, the beta update
|
|
280
|
+
// manifest route is unavailable rather than silently serving stable.
|
|
281
|
+
agentBetaVersion: z
|
|
282
|
+
.string()
|
|
283
|
+
.regex(/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/u)
|
|
284
|
+
.optional(),
|
|
253
285
|
productAccessMode: ProductAccessMode.default("local"),
|
|
254
286
|
billingMode: BillingMode.default("disabled"),
|
|
255
287
|
entitlementsMode: EntitlementsMode.default("none"),
|
|
@@ -267,8 +299,7 @@ const SettingsSchema = z.object({
|
|
|
267
299
|
// holder of stream:control gets 403 until this flips. Keeps stream:control a
|
|
268
300
|
// declared-but-inert permission so later hardening is a flag flip.
|
|
269
301
|
streamControlEnabled: EnvBoolean.default(false),
|
|
270
|
-
|
|
271
|
-
toolspaceMaxCallsPerTurn: z.coerce.number().int().positive().default(200),
|
|
302
|
+
codemodeMaxCallsPerTurn: z.coerce.number().int().positive().default(200),
|
|
272
303
|
// Optional release-coherent bootstrap hint for custom rigs/connected machines
|
|
273
304
|
// that do not carry the stock-image ogtool binary. Exact stable versions only:
|
|
274
305
|
// the agent must never guess a tag or silently install `latest`.
|
|
@@ -281,11 +312,15 @@ const SettingsSchema = z.object({
|
|
|
281
312
|
integrationsStateSecret: z.string().optional(),
|
|
282
313
|
integrationsAllowPrivateNetworkTargets: EnvBoolean.default(false),
|
|
283
314
|
integrationsOauthClientsJson: z.string().default("{}"),
|
|
315
|
+
gmailRestAdapterEnabled: EnvBoolean.default(false),
|
|
284
316
|
slackClientId: z.string().optional(),
|
|
285
317
|
slackClientSecret: z.string().optional(),
|
|
286
318
|
slackSigningSecret: z.string().optional(),
|
|
287
319
|
googleDriveClientId: z.string().optional(),
|
|
288
320
|
googleDriveClientSecret: z.string().optional(),
|
|
321
|
+
googleDriveWorkspaceEventsEnabled: EnvBoolean.optional(),
|
|
322
|
+
atlassianClientId: z.string().optional(),
|
|
323
|
+
atlassianClientSecret: z.string().optional(),
|
|
289
324
|
// Undefined is meaningful: the migration boundary persists the product
|
|
290
325
|
// default of 3 when no deployment override is supplied.
|
|
291
326
|
maxNestedAgentDepth: z.coerce.number().int().nonnegative().max(MAX_NESTED_AGENT_DEPTH).optional(),
|
|
@@ -359,6 +394,45 @@ const SettingsSchema = z.object({
|
|
|
359
394
|
// Gateway models below are added to the managed-credit catalog. Workspace
|
|
360
395
|
// Gateway keys use the encrypted connection broker and never this secret.
|
|
361
396
|
vercelAiGatewayApiKey: z.string().optional(),
|
|
397
|
+
/** Image adapter route; native hosted providers ignore this model. */
|
|
398
|
+
imageGenerationModel: z.string().trim().min(1).max(256).default("openai/gpt-image-2"),
|
|
399
|
+
/** Durable video generation uses the workspace-owned Gateway credential. */
|
|
400
|
+
videoGenerationPollIntervalMs: z.coerce.number().int().min(1_000).max(60_000).default(5_000),
|
|
401
|
+
videoGenerationRecoveryDeadlineMs: z.coerce
|
|
402
|
+
.number()
|
|
403
|
+
.int()
|
|
404
|
+
.min(60_000)
|
|
405
|
+
.max(24 * 60 * 60_000)
|
|
406
|
+
.default(2 * 60 * 60_000),
|
|
407
|
+
videoGenerationReferenceUrlTtlSeconds: z.coerce
|
|
408
|
+
.number()
|
|
409
|
+
.int()
|
|
410
|
+
.min(300)
|
|
411
|
+
.max(6 * 60 * 60)
|
|
412
|
+
.default(60 * 60),
|
|
413
|
+
videoGenerationMaxConcurrentPerWorkspace: z.coerce.number().int().min(1).max(16).default(2),
|
|
414
|
+
videoGenerationWorkspaceQuotaBytes: z.coerce
|
|
415
|
+
.number()
|
|
416
|
+
.int()
|
|
417
|
+
.positive()
|
|
418
|
+
.max(Number.MAX_SAFE_INTEGER)
|
|
419
|
+
.default(20 * 1024 * 1024 * 1024),
|
|
420
|
+
videoGenerationTempDirectory: z.string().trim().min(1).max(1_024).default("/tmp/opengeni-video"),
|
|
421
|
+
videoGenerationFfprobePath: z.string().trim().min(1).max(1_024).default("ffprobe"),
|
|
422
|
+
// OpenGeni's customer price, not a claim about the provider's delayed cost report.
|
|
423
|
+
// The durable operation freezes the exact resulting price before provider submit.
|
|
424
|
+
videoGenerationCredit480pMicrosPerSecond: z.coerce
|
|
425
|
+
.number()
|
|
426
|
+
.int()
|
|
427
|
+
.positive()
|
|
428
|
+
.max(10_000_000)
|
|
429
|
+
.default(155_000),
|
|
430
|
+
videoGenerationCredit720pMicrosPerSecond: z.coerce
|
|
431
|
+
.number()
|
|
432
|
+
.int()
|
|
433
|
+
.positive()
|
|
434
|
+
.max(10_000_000)
|
|
435
|
+
.default(350_000),
|
|
362
436
|
// Native composer voice input (browser MediaRecorder → API transcription).
|
|
363
437
|
// Provider credentials stay server-side; ClientConfig only projects availability
|
|
364
438
|
// and hard ceilings. Selection happens once before audio is sent — never retry
|
|
@@ -449,6 +523,11 @@ const SettingsSchema = z.object({
|
|
|
449
523
|
// compatibility diagnosis.
|
|
450
524
|
// OPENGENI_CODEX_TOOL_SEARCH_ENABLED
|
|
451
525
|
codexToolSearchEnabled: EnvBoolean.default(true),
|
|
526
|
+
// Provider-neutral progressive disclosure for direct OpenAI/Azure native
|
|
527
|
+
// client search and ordinary-function generic dispatch. Kept separate from
|
|
528
|
+
// the Codex rollout so an emergency Codex opt-out cannot disable every model.
|
|
529
|
+
// OPENGENI_LAZY_TOOL_SEARCH_ENABLED
|
|
530
|
+
lazyToolSearchEnabled: EnvBoolean.default(true),
|
|
452
531
|
// credential allocator atomic, workspace-local credential allocation. Default OFF is a
|
|
453
532
|
// deliberate rolling-deploy fence: migrate + roll every worker first, then
|
|
454
533
|
// enable. Turning it off restores the legacy sticky selector without a schema
|
|
@@ -506,6 +585,13 @@ const SettingsSchema = z.object({
|
|
|
506
585
|
disableOpenaiTracing: EnvBoolean.default(false),
|
|
507
586
|
sandboxBackend: SandboxBackend.default("docker"),
|
|
508
587
|
dockerImage: z.string().default("opengeni-sandbox:local"),
|
|
588
|
+
// Explicit deployment contract: the configured base sandbox image contains
|
|
589
|
+
// the verified, self-contained native artifact runtime at its fixed image
|
|
590
|
+
// paths. Disabled by default so arbitrary/custom provider images never make
|
|
591
|
+
// document/spreadsheet/presentation skills appear when their runtime is
|
|
592
|
+
// absent. Per-pack/per-rig image overrides fail closed in the worker even
|
|
593
|
+
// when this base-image contract is enabled.
|
|
594
|
+
sandboxArtifactRuntimeEnabled: EnvBoolean.default(false),
|
|
509
595
|
dockerExposedPorts: z.string().default(""),
|
|
510
596
|
dockerNetwork: z.string().optional(),
|
|
511
597
|
// When the worker itself runs in a container and talks to a host Docker daemon,
|
|
@@ -565,17 +651,16 @@ const SettingsSchema = z.object({
|
|
|
565
651
|
// SOONER than the hard lifetime; the boot invariant forbids a value that would
|
|
566
652
|
// reap before reaperPeriod + idleGrace elapses.
|
|
567
653
|
modalIdleTimeoutSeconds: z.coerce.number().int().positive().optional(),
|
|
568
|
-
// /workspace FILE PERSISTENCE across warm/cold cycles.
|
|
569
|
-
//
|
|
570
|
-
//
|
|
571
|
-
//
|
|
572
|
-
//
|
|
573
|
-
//
|
|
574
|
-
//
|
|
575
|
-
// the reaper persists a tar archive — same store+hydrate plumbing, slower).
|
|
654
|
+
// /workspace FILE PERSISTENCE across warm/cold cycles. Directory snapshots
|
|
655
|
+
// preserve only the durable user workspace, so provider recovery does not
|
|
656
|
+
// restore an entire machine image or replace the selected rig/base image.
|
|
657
|
+
// Existing serialized sessions retain their original persistence mode and
|
|
658
|
+
// remain recoverable; this default governs newly created Modal sandboxes.
|
|
659
|
+
// `snapshot_filesystem` remains available for explicit compatibility and
|
|
660
|
+
// immutable rig-image materialization. `tar` is the portable fallback.
|
|
576
661
|
modalWorkspacePersistence: z
|
|
577
662
|
.enum(["tar", "snapshot_filesystem", "snapshot_directory"])
|
|
578
|
-
.default("
|
|
663
|
+
.default("snapshot_directory"),
|
|
579
664
|
// Shared desktop toggle: this module reads it for the 6080 port-merge; the
|
|
580
665
|
// owner module (P4.x) acts on it to launch the display stack.
|
|
581
666
|
sandboxDesktopEnabled: EnvBoolean.default(false),
|
|
@@ -700,10 +785,13 @@ const SettingsSchema = z.object({
|
|
|
700
785
|
// the deploy-staging IaC secret/configmap pattern.
|
|
701
786
|
sandboxSelfhostedEnabled: EnvBoolean.default(false),
|
|
702
787
|
// Gates the op-stream (streaming exec) transport to Connected Machines. The
|
|
703
|
-
// runner must ALSO advertise Capabilities.op_stream
|
|
704
|
-
//
|
|
788
|
+
// runner must ALSO advertise Capabilities.op_stream. Streaming is the default
|
|
789
|
+
// because it is the only transport that can keep a command alive without an
|
|
790
|
+
// arbitrary request/reply wall while still supporting replay and cancellation.
|
|
791
|
+
// Older runners remain usable when an explicit positive exec timeout is set.
|
|
792
|
+
// EnvBoolean (NOT
|
|
705
793
|
// z.coerce.boolean(), which coerces "false" -> true).
|
|
706
|
-
agentOpStreamEnabled: EnvBoolean.default(
|
|
794
|
+
agentOpStreamEnabled: EnvBoolean.default(true),
|
|
707
795
|
// The HMAC secret the control plane signs the enrollment bearer credential with
|
|
708
796
|
// (the `oge_` envelope the agent presents back to the control plane). Optional:
|
|
709
797
|
// when ABSENT and sandboxSelfhostedEnabled is on, the poll route reports the
|
|
@@ -761,23 +849,15 @@ const SettingsSchema = z.object({
|
|
|
761
849
|
selfhostedNatsControlUser: z.string().optional(),
|
|
762
850
|
selfhostedNatsControlPassword: z.string().optional(),
|
|
763
851
|
// --- selfhosted (Connected Machine) control/exec op deadlines ---------------
|
|
764
|
-
//
|
|
765
|
-
//
|
|
766
|
-
//
|
|
767
|
-
//
|
|
768
|
-
//
|
|
769
|
-
//
|
|
770
|
-
//
|
|
771
|
-
//
|
|
772
|
-
// The exec default is a DELIBERATELY MODEST 2min (not 5): the agent-side admission
|
|
773
|
-
// pool is (until a later agent release) a FLAT 8 permits with no per-class split,
|
|
774
|
-
// so 8 slow execs holding a permit for 5 minutes would blanket-DRAIN every fs/git
|
|
775
|
-
// op — shipping the amplifier before the class-aware-admission fix. 2min still
|
|
776
|
-
// clears the large majority of the observed >30s exec tail; genuinely long jobs run
|
|
777
|
-
// in the background (see the exec-deadline hint) or raise the knob per deployment.
|
|
778
|
-
// Knobs: OPENGENI_SANDBOX_SELFHOSTED_EXEC_TIMEOUT_MS (default 2min) and
|
|
852
|
+
// CONTROL ops (ping / fs / git / desktop / pty) keep a short request timeout so
|
|
853
|
+
// liveness failures surface promptly. EXEC duration is a different concern: by
|
|
854
|
+
// default it is unbounded (0), exactly like a command launched by an unrestricted
|
|
855
|
+
// local agent. The op-stream transport keeps control liveness, replay, and explicit
|
|
856
|
+
// cancellation independent of command duration. A deployment may opt into a hard
|
|
857
|
+
// process deadline by setting a positive value; 0 never schedules a process kill.
|
|
858
|
+
// Knobs: OPENGENI_SANDBOX_SELFHOSTED_EXEC_TIMEOUT_MS (default 0 = none) and
|
|
779
859
|
// OPENGENI_SANDBOX_SELFHOSTED_CONTROL_TIMEOUT_MS (default 30s).
|
|
780
|
-
sandboxSelfhostedExecTimeoutMs: z.coerce.number().int().
|
|
860
|
+
sandboxSelfhostedExecTimeoutMs: z.coerce.number().int().nonnegative().default(0),
|
|
781
861
|
sandboxSelfhostedControlTimeoutMs: z.coerce.number().int().positive().default(30_000),
|
|
782
862
|
// --- sandbox lease cadences (cadence invariant validated at boot below) ---
|
|
783
863
|
// reaperPeriod < viewerHolderTTL, and reaperPeriod + idleGrace < the EFFECTIVE
|
|
@@ -788,6 +868,10 @@ const SettingsSchema = z.object({
|
|
|
788
868
|
// snapshotted before the box dies (sandbox-file-persistence).
|
|
789
869
|
sandboxLeaseReaperPeriodMs: z.coerce.number().int().positive().default(30_000),
|
|
790
870
|
sandboxViewerHolderTtlMs: z.coerce.number().int().positive().default(90_000),
|
|
871
|
+
// A BrowserSession controller refreshes its durable resource and exact
|
|
872
|
+
// interaction lease holder together. This longer crash horizon tolerates API
|
|
873
|
+
// replacement while still releasing a placement whose controller died.
|
|
874
|
+
sandboxInteractionHolderTtlMs: z.coerce.number().int().positive().default(180_000),
|
|
791
875
|
// The DRAIN grace: how long a refcount-0 (draining) lease stays WARM before the
|
|
792
876
|
// reaper resume-by-ids the box and terminates it. This is the cost-vs-snappiness
|
|
793
877
|
// dial — when the user navigates away the box keeps refcount 0, but it survives
|
|
@@ -813,7 +897,12 @@ const SettingsSchema = z.object({
|
|
|
813
897
|
// graceful shutdown, or become permission to GC an older archive. Timeout is
|
|
814
898
|
// treated exactly like a failed best-effort snapshot. Knob:
|
|
815
899
|
// OPENGENI_SANDBOX_SNAPSHOT_TIMEOUT_MS. Default 60s.
|
|
816
|
-
sandboxSnapshotTimeoutMs: z.coerce
|
|
900
|
+
sandboxSnapshotTimeoutMs: z.coerce
|
|
901
|
+
.number()
|
|
902
|
+
.int()
|
|
903
|
+
.positive()
|
|
904
|
+
.max(SANDBOX_SNAPSHOT_MAX_TIMEOUT_MS)
|
|
905
|
+
.default(60_000),
|
|
817
906
|
// Begin a controlled snapshot/quiesce/drain/rematerialize transition this far
|
|
818
907
|
// ahead of a finite provider deadline. Modal's 24h creation clock cannot be
|
|
819
908
|
// extended; the logical sandbox outlives it by moving to one successor box.
|
|
@@ -823,13 +912,12 @@ const SettingsSchema = z.object({
|
|
|
823
912
|
// an operator deliberately wants more rotation headroom; the boot invariant
|
|
824
913
|
// still requires it to remain below the provider lifetime.
|
|
825
914
|
sandboxRotationLeadMs: z.coerce.number().int().positive().default(3_600_000),
|
|
826
|
-
// Bound
|
|
827
|
-
//
|
|
828
|
-
//
|
|
829
|
-
//
|
|
830
|
-
//
|
|
831
|
-
|
|
832
|
-
sandboxRotationBatchSize: z.coerce.number().int().positive().max(500).default(1),
|
|
915
|
+
// Bound provider-deadline rotation admission independently of execution.
|
|
916
|
+
// Every admitted box receives its own durable drain child; the control worker
|
|
917
|
+
// limits provider I/O to 32 concurrent activities. Matching that bound avoids
|
|
918
|
+
// both the old one-box-per-tick deadline backlog and an unbounded provider/API
|
|
919
|
+
// burst. Operators may tune this for a differently-sized worker pool.
|
|
920
|
+
sandboxRotationBatchSize: z.coerce.number().int().positive().max(500).default(32),
|
|
833
921
|
// expires_at refresh window for a held lease (>> the turn 10s heartbeat so a
|
|
834
922
|
// single missed heartbeat never TTL-reaps a live turn). The warming TTL is the
|
|
835
923
|
// window a cold->warming spawner has to commit warm before a reaper resets it.
|
|
@@ -1134,6 +1222,13 @@ export type ModelUsageInput = {
|
|
|
1134
1222
|
requestUsageEntries?: ModelUsageInput[] | undefined;
|
|
1135
1223
|
};
|
|
1136
1224
|
|
|
1225
|
+
export type ModelUsageCostBreakdown = {
|
|
1226
|
+
/** Provider-rate cost basis for the exact usage, before OpenGeni margin. */
|
|
1227
|
+
providerCostMicros: number;
|
|
1228
|
+
/** OpenGeni credit price after configured margin and latency-mode multiplier. */
|
|
1229
|
+
creditCostMicros: number;
|
|
1230
|
+
};
|
|
1231
|
+
|
|
1137
1232
|
export type StaticUsageLimitsConfig = StaticUsageLimits;
|
|
1138
1233
|
export type EntitlementsConfig = Entitlements;
|
|
1139
1234
|
|
|
@@ -1205,6 +1300,10 @@ export const ModelCapabilitiesV1Schema = z
|
|
|
1205
1300
|
webSearch: CapabilityStateV1Schema,
|
|
1206
1301
|
xSearch: CapabilityStateV1Schema,
|
|
1207
1302
|
codeExecution: CapabilityStateV1Schema,
|
|
1303
|
+
imageGeneration: CapabilityStateV1Schema.default({
|
|
1304
|
+
upstream: "unknown",
|
|
1305
|
+
runnable: false,
|
|
1306
|
+
}),
|
|
1208
1307
|
}),
|
|
1209
1308
|
inputModalities: z.array(ModelModalityV1).min(1),
|
|
1210
1309
|
/** Exact MIME types accepted as typed `input_file`; `text/*` is allowed. */
|
|
@@ -1741,6 +1840,9 @@ export function getSettings(): Settings {
|
|
|
1741
1840
|
turnWorkerMaxConcurrentTurns: optional("OPENGENI_TURN_WORKER_MAX_CONCURRENT_TURNS"),
|
|
1742
1841
|
turnWorkerTargetCpuUsage: optional("OPENGENI_TURN_WORKER_TARGET_CPU_USAGE"),
|
|
1743
1842
|
turnWorkerTargetMemoryUsage: optional("OPENGENI_TURN_WORKER_TARGET_MEMORY_USAGE"),
|
|
1843
|
+
turnWorkerEmergencyMemoryUsage: optional("OPENGENI_TURN_WORKER_EMERGENCY_MEMORY_USAGE"),
|
|
1844
|
+
turnWorkerMemoryGuardIntervalMs: optional("OPENGENI_TURN_WORKER_MEMORY_GUARD_INTERVAL_MS"),
|
|
1845
|
+
turnWorkerMemoryGuardSustainMs: optional("OPENGENI_TURN_WORKER_MEMORY_GUARD_SUSTAIN_MS"),
|
|
1744
1846
|
observabilityStructuredLogs: optional("OPENGENI_OBSERVABILITY_STRUCTURED_LOGS"),
|
|
1745
1847
|
observabilityMetricsEnabled: optional("OPENGENI_OBSERVABILITY_METRICS_ENABLED"),
|
|
1746
1848
|
observabilityOtlpEndpoint:
|
|
@@ -1757,6 +1859,7 @@ export function getSettings(): Settings {
|
|
|
1757
1859
|
webBaseUrl: optional("OPENGENI_WEB_BASE_URL"),
|
|
1758
1860
|
agentReleasesBaseUrl: optional("OPENGENI_AGENT_RELEASES_BASE_URL"),
|
|
1759
1861
|
agentStableVersion: optional("OPENGENI_AGENT_STABLE_VERSION"),
|
|
1862
|
+
agentBetaVersion: optional("OPENGENI_AGENT_BETA_VERSION"),
|
|
1760
1863
|
productAccessMode: optional("OPENGENI_PRODUCT_ACCESS_MODE"),
|
|
1761
1864
|
billingMode: optional("OPENGENI_BILLING_MODE"),
|
|
1762
1865
|
entitlementsMode: optional("OPENGENI_ENTITLEMENTS_MODE"),
|
|
@@ -1766,8 +1869,7 @@ export function getSettings(): Settings {
|
|
|
1766
1869
|
delegationSecret: optional("OPENGENI_DELEGATION_SECRET"),
|
|
1767
1870
|
streamTokenSecret: optional("OPENGENI_STREAM_TOKEN_SECRET"),
|
|
1768
1871
|
streamControlEnabled: optional("OPENGENI_STREAM_CONTROL_ENABLED"),
|
|
1769
|
-
|
|
1770
|
-
toolspaceMaxCallsPerTurn: optional("OPENGENI_TOOLSPACE_MAX_CALLS_PER_TURN"),
|
|
1872
|
+
codemodeMaxCallsPerTurn: optional("OPENGENI_CODEMODE_MAX_CALLS_PER_TURN"),
|
|
1771
1873
|
ogtoolPackageSpec: optional("OPENGENI_OGTOOL_PACKAGE_SPEC"),
|
|
1772
1874
|
environmentsEncryptionKey: optional("OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY"),
|
|
1773
1875
|
integrationsEnabled: optional("OPENGENI_INTEGRATIONS_ENABLED"),
|
|
@@ -1776,11 +1878,15 @@ export function getSettings(): Settings {
|
|
|
1776
1878
|
"OPENGENI_INTEGRATIONS_ALLOW_PRIVATE_NETWORK_TARGETS",
|
|
1777
1879
|
),
|
|
1778
1880
|
integrationsOauthClientsJson: optional("OPENGENI_INTEGRATIONS_OAUTH_CLIENTS_JSON"),
|
|
1881
|
+
gmailRestAdapterEnabled: optional("OPENGENI_GMAIL_REST_ADAPTER_ENABLED"),
|
|
1779
1882
|
slackClientId: optional("OPENGENI_SLACK_CLIENT_ID"),
|
|
1780
1883
|
slackClientSecret: optional("OPENGENI_SLACK_CLIENT_SECRET"),
|
|
1781
1884
|
slackSigningSecret: optional("OPENGENI_SLACK_SIGNING_SECRET"),
|
|
1782
1885
|
googleDriveClientId: optional("OPENGENI_GOOGLE_DRIVE_CLIENT_ID"),
|
|
1783
1886
|
googleDriveClientSecret: optional("OPENGENI_GOOGLE_DRIVE_CLIENT_SECRET"),
|
|
1887
|
+
googleDriveWorkspaceEventsEnabled: optional("OPENGENI_GOOGLE_DRIVE_WORKSPACE_EVENTS_ENABLED"),
|
|
1888
|
+
atlassianClientId: optional("OPENGENI_ATLASSIAN_CLIENT_ID"),
|
|
1889
|
+
atlassianClientSecret: optional("OPENGENI_ATLASSIAN_CLIENT_SECRET"),
|
|
1784
1890
|
maxNestedAgentDepth: optional("OPENGENI_MAX_NESTED_AGENT_DEPTH"),
|
|
1785
1891
|
socialOauthClientsJson: optional("OPENGENI_SOCIAL_OAUTH_CLIENTS_JSON"),
|
|
1786
1892
|
goalMaxAutoContinuations: optional("OPENGENI_GOAL_MAX_AUTO_CONTINUATIONS"),
|
|
@@ -1807,6 +1913,24 @@ export function getSettings(): Settings {
|
|
|
1807
1913
|
openaiModel: optional("OPENGENI_OPENAI_MODEL"),
|
|
1808
1914
|
openaiAllowedModels: optional("OPENGENI_OPENAI_ALLOWED_MODELS"),
|
|
1809
1915
|
vercelAiGatewayApiKey: optional("OPENGENI_VERCEL_AI_GATEWAY_API_KEY"),
|
|
1916
|
+
imageGenerationModel: optional("OPENGENI_IMAGE_GENERATION_MODEL"),
|
|
1917
|
+
videoGenerationPollIntervalMs: optional("OPENGENI_VIDEO_GENERATION_POLL_INTERVAL_MS"),
|
|
1918
|
+
videoGenerationRecoveryDeadlineMs: optional("OPENGENI_VIDEO_GENERATION_RECOVERY_DEADLINE_MS"),
|
|
1919
|
+
videoGenerationReferenceUrlTtlSeconds: optional(
|
|
1920
|
+
"OPENGENI_VIDEO_GENERATION_REFERENCE_URL_TTL_SECONDS",
|
|
1921
|
+
),
|
|
1922
|
+
videoGenerationMaxConcurrentPerWorkspace: optional(
|
|
1923
|
+
"OPENGENI_VIDEO_GENERATION_MAX_CONCURRENT_PER_WORKSPACE",
|
|
1924
|
+
),
|
|
1925
|
+
videoGenerationWorkspaceQuotaBytes: optional("OPENGENI_VIDEO_GENERATION_WORKSPACE_QUOTA_BYTES"),
|
|
1926
|
+
videoGenerationTempDirectory: optional("OPENGENI_VIDEO_GENERATION_TEMP_DIRECTORY"),
|
|
1927
|
+
videoGenerationFfprobePath: optional("OPENGENI_VIDEO_GENERATION_FFPROBE_PATH"),
|
|
1928
|
+
videoGenerationCredit480pMicrosPerSecond: optional(
|
|
1929
|
+
"OPENGENI_VIDEO_GENERATION_CREDIT_480P_MICROS_PER_SECOND",
|
|
1930
|
+
),
|
|
1931
|
+
videoGenerationCredit720pMicrosPerSecond: optional(
|
|
1932
|
+
"OPENGENI_VIDEO_GENERATION_CREDIT_720P_MICROS_PER_SECOND",
|
|
1933
|
+
),
|
|
1810
1934
|
voiceInputMaxDurationSeconds: optional("OPENGENI_VOICE_INPUT_MAX_DURATION_SECONDS"),
|
|
1811
1935
|
voiceInputMaxSizeBytes: optional("OPENGENI_VOICE_INPUT_MAX_SIZE_BYTES"),
|
|
1812
1936
|
voiceInputResumableEnabled: optional("OPENGENI_VOICE_INPUT_RESUMABLE_ENABLED"),
|
|
@@ -1838,6 +1962,7 @@ export function getSettings(): Settings {
|
|
|
1838
1962
|
codexSubscriptionEnabled: optional("OPENGENI_CODEX_SUBSCRIPTION_ENABLED"),
|
|
1839
1963
|
codexConnectedAppsEnabled: optional("OPENGENI_CODEX_CONNECTED_APPS_ENABLED"),
|
|
1840
1964
|
codexToolSearchEnabled: optional("OPENGENI_CODEX_TOOL_SEARCH_ENABLED"),
|
|
1965
|
+
lazyToolSearchEnabled: optional("OPENGENI_LAZY_TOOL_SEARCH_ENABLED"),
|
|
1841
1966
|
codexCredentialLeasingEnabled: optional("OPENGENI_CODEX_CREDENTIAL_LEASING_ENABLED"),
|
|
1842
1967
|
codexFleetPolicyShadowEnabled: optional("OPENGENI_CODEX_FLEET_POLICY_SHADOW_ENABLED"),
|
|
1843
1968
|
codexProductSku: optional("OPENGENI_CODEX_PRODUCT_SKU"),
|
|
@@ -1858,6 +1983,7 @@ export function getSettings(): Settings {
|
|
|
1858
1983
|
disableOpenaiTracing: optional("OPENGENI_DISABLE_OPENAI_TRACING"),
|
|
1859
1984
|
sandboxBackend: optional("OPENGENI_SANDBOX_BACKEND"),
|
|
1860
1985
|
dockerImage: optional("OPENGENI_DOCKER_IMAGE"),
|
|
1986
|
+
sandboxArtifactRuntimeEnabled: optional("OPENGENI_SANDBOX_ARTIFACT_RUNTIME_ENABLED"),
|
|
1861
1987
|
dockerExposedPorts: optional("OPENGENI_DOCKER_EXPOSED_PORTS"),
|
|
1862
1988
|
dockerNetwork: optional("OPENGENI_DOCKER_NETWORK"),
|
|
1863
1989
|
dockerWorkspaceBaseDir: optional("OPENGENI_DOCKER_WORKSPACE_BASE_DIR"),
|
|
@@ -1940,6 +2066,7 @@ export function getSettings(): Settings {
|
|
|
1940
2066
|
sandboxSelfhostedControlTimeoutMs: optional("OPENGENI_SANDBOX_SELFHOSTED_CONTROL_TIMEOUT_MS"),
|
|
1941
2067
|
sandboxLeaseReaperPeriodMs: optional("OPENGENI_SANDBOX_LEASE_REAPER_PERIOD_MS"),
|
|
1942
2068
|
sandboxViewerHolderTtlMs: optional("OPENGENI_SANDBOX_VIEWER_HOLDER_TTL_MS"),
|
|
2069
|
+
sandboxInteractionHolderTtlMs: optional("OPENGENI_SANDBOX_INTERACTION_HOLDER_TTL_MS"),
|
|
1943
2070
|
sandboxIdleGraceMs: optional("OPENGENI_SANDBOX_IDLE_GRACE_MS"),
|
|
1944
2071
|
sandboxSnapshotIntervalMs: optional("OPENGENI_SANDBOX_SNAPSHOT_INTERVAL_MS"),
|
|
1945
2072
|
sandboxSnapshotTimeoutMs: optional("OPENGENI_SANDBOX_SNAPSHOT_TIMEOUT_MS"),
|
|
@@ -2070,8 +2197,20 @@ export function sandboxArchiveCaptureTimeoutMs(
|
|
|
2070
2197
|
settings: Pick<Settings, "sandboxSnapshotTimeoutMs">,
|
|
2071
2198
|
): number {
|
|
2072
2199
|
return Math.min(
|
|
2073
|
-
|
|
2074
|
-
|
|
2200
|
+
SANDBOX_ARCHIVE_CAPTURE_MAX_TIMEOUT_MS,
|
|
2201
|
+
settings.sandboxSnapshotTimeoutMs + SANDBOX_ARCHIVE_CAPTURE_SETTLEMENT_GRACE_MS,
|
|
2202
|
+
);
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
export function sandboxLifecycleTransitionWaitMs(
|
|
2206
|
+
settings: Pick<Settings, "sandboxSnapshotTimeoutMs" | "sandboxLeaseReaperPeriodMs">,
|
|
2207
|
+
): number {
|
|
2208
|
+
const captureTimeoutMs = sandboxArchiveCaptureTimeoutMs(settings);
|
|
2209
|
+
return Math.min(
|
|
2210
|
+
SANDBOX_LIFECYCLE_TRANSITION_MAX_WAIT_MS,
|
|
2211
|
+
settings.sandboxLeaseReaperPeriodMs +
|
|
2212
|
+
captureTimeoutMs +
|
|
2213
|
+
SANDBOX_LIFECYCLE_RETRY_HANDOFF_GRACE_MS,
|
|
2075
2214
|
);
|
|
2076
2215
|
}
|
|
2077
2216
|
|
|
@@ -2334,7 +2473,12 @@ function normalizeCapabilities(capabilities: ModelCapabilitiesV1): ModelCapabili
|
|
|
2334
2473
|
|
|
2335
2474
|
function legacyModelCapabilities(
|
|
2336
2475
|
settings: Settings,
|
|
2337
|
-
input: {
|
|
2476
|
+
input: {
|
|
2477
|
+
reasoningEffort: boolean;
|
|
2478
|
+
hostedWebSearch: boolean;
|
|
2479
|
+
hostedImageGeneration?: boolean;
|
|
2480
|
+
vision?: boolean;
|
|
2481
|
+
},
|
|
2338
2482
|
): ModelCapabilitiesV1 {
|
|
2339
2483
|
const reasoningEfforts = input.reasoningEffort ? configuredAllowedReasoningEfforts(settings) : [];
|
|
2340
2484
|
return normalizeCapabilities({
|
|
@@ -2354,6 +2498,10 @@ function legacyModelCapabilities(
|
|
|
2354
2498
|
},
|
|
2355
2499
|
xSearch: { upstream: "unknown", runnable: false },
|
|
2356
2500
|
codeExecution: { upstream: "unknown", runnable: false },
|
|
2501
|
+
imageGeneration: {
|
|
2502
|
+
upstream: input.hostedImageGeneration ? "supported" : "unknown",
|
|
2503
|
+
runnable: input.hostedImageGeneration ?? false,
|
|
2504
|
+
},
|
|
2357
2505
|
},
|
|
2358
2506
|
inputModalities: input.vision ? ["text", "image"] : ["text"],
|
|
2359
2507
|
inputFileMediaTypes: [
|
|
@@ -2586,6 +2734,35 @@ function builtinPromptCachingForModel(
|
|
|
2586
2734
|
: undefined;
|
|
2587
2735
|
}
|
|
2588
2736
|
|
|
2737
|
+
/** Reviewed direct-OpenAI text models that accept the hosted image tool. */
|
|
2738
|
+
function builtinHostedImageGenerationForModel(settings: Settings, modelId: string): boolean {
|
|
2739
|
+
return (
|
|
2740
|
+
settings.openaiProvider === "openai" &&
|
|
2741
|
+
isDirectOpenAiApiBaseUrl(settings.openaiBaseUrl) &&
|
|
2742
|
+
["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"].includes(modelId)
|
|
2743
|
+
);
|
|
2744
|
+
}
|
|
2745
|
+
|
|
2746
|
+
/** Undefined and the exact public OpenAI v1 endpoint are the same direct route. */
|
|
2747
|
+
export function isDirectOpenAiApiBaseUrl(baseUrl: string | undefined): boolean {
|
|
2748
|
+
if (baseUrl === undefined) return true;
|
|
2749
|
+
try {
|
|
2750
|
+
const parsed = new URL(baseUrl);
|
|
2751
|
+
return (
|
|
2752
|
+
parsed.protocol === "https:" &&
|
|
2753
|
+
parsed.hostname === "api.openai.com" &&
|
|
2754
|
+
parsed.port === "" &&
|
|
2755
|
+
parsed.username === "" &&
|
|
2756
|
+
parsed.password === "" &&
|
|
2757
|
+
parsed.search === "" &&
|
|
2758
|
+
parsed.hash === "" &&
|
|
2759
|
+
parsed.pathname.replace(/\/+$/, "") === "/v1"
|
|
2760
|
+
);
|
|
2761
|
+
} catch {
|
|
2762
|
+
return false;
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
|
|
2589
2766
|
/**
|
|
2590
2767
|
* Map OpenGeni latency mode to the provider `service_tier` wire value.
|
|
2591
2768
|
* Azure and Codex ChatGPT accept `priority`; OpenAI API accepts `fast` (alias of priority).
|
|
@@ -3026,6 +3203,7 @@ export function configuredModels(settings: Settings): ConfiguredModel[] {
|
|
|
3026
3203
|
...legacyModelCapabilities(settings, {
|
|
3027
3204
|
reasoningEffort: true,
|
|
3028
3205
|
hostedWebSearch: settings.webSearchEnabled,
|
|
3206
|
+
hostedImageGeneration: builtinHostedImageGenerationForModel(settings, id),
|
|
3029
3207
|
vision: id.startsWith("gpt-5.6-"),
|
|
3030
3208
|
}),
|
|
3031
3209
|
...(builtinPromptCachingForModel(id)
|
|
@@ -3422,6 +3600,15 @@ export function calculateModelUsageCostMicros(
|
|
|
3422
3600
|
usage: ModelUsageInput,
|
|
3423
3601
|
options?: { latencyMode?: LatencyMode },
|
|
3424
3602
|
): number {
|
|
3603
|
+
return calculateModelUsageCostBreakdown(settings, model, usage, options).creditCostMicros;
|
|
3604
|
+
}
|
|
3605
|
+
|
|
3606
|
+
export function calculateModelUsageCostBreakdown(
|
|
3607
|
+
settings: Settings,
|
|
3608
|
+
model: string,
|
|
3609
|
+
usage: ModelUsageInput,
|
|
3610
|
+
options?: { latencyMode?: LatencyMode },
|
|
3611
|
+
): ModelUsageCostBreakdown {
|
|
3425
3612
|
const schedule = configuredModelPricingSchedules(settings)[model];
|
|
3426
3613
|
if (!schedule) {
|
|
3427
3614
|
throw new Error(`Missing model pricing for ${model}`);
|
|
@@ -3438,10 +3625,12 @@ export function calculateModelUsageCostMicros(
|
|
|
3438
3625
|
(rawCostByPricing.get(pricing) ?? 0) + calculateEntryCostMicros(pricing, entry),
|
|
3439
3626
|
);
|
|
3440
3627
|
}
|
|
3441
|
-
let
|
|
3628
|
+
let providerCostMicros = 0;
|
|
3629
|
+
let creditCostMicros = 0;
|
|
3442
3630
|
for (const [pricing, rawCost] of rawCostByPricing) {
|
|
3443
3631
|
const marginBps = pricing.marginBps ?? 0;
|
|
3444
|
-
|
|
3632
|
+
providerCostMicros += rawCost;
|
|
3633
|
+
creditCostMicros += Math.ceil((rawCost * (10_000 + marginBps)) / 10_000);
|
|
3445
3634
|
}
|
|
3446
3635
|
const latencyMode = options?.latencyMode ?? "standard";
|
|
3447
3636
|
if (latencyMode !== "standard") {
|
|
@@ -3454,10 +3643,11 @@ export function calculateModelUsageCostMicros(
|
|
|
3454
3643
|
(mode) => mode.id === latencyMode && mode.runnable,
|
|
3455
3644
|
)?.billingMultiplierBps;
|
|
3456
3645
|
if (multiplierBps && multiplierBps > 0) {
|
|
3457
|
-
|
|
3646
|
+
providerCostMicros = Math.ceil((providerCostMicros * multiplierBps) / 10_000);
|
|
3647
|
+
creditCostMicros = Math.ceil((creditCostMicros * multiplierBps) / 10_000);
|
|
3458
3648
|
}
|
|
3459
3649
|
}
|
|
3460
|
-
return
|
|
3650
|
+
return { providerCostMicros, creditCostMicros };
|
|
3461
3651
|
}
|
|
3462
3652
|
|
|
3463
3653
|
/**
|
|
@@ -3471,6 +3661,16 @@ export function calculateGatewayReportedCostMicros(
|
|
|
3471
3661
|
inferenceCostUsd: string,
|
|
3472
3662
|
options?: { inputTokens?: number },
|
|
3473
3663
|
): number {
|
|
3664
|
+
return calculateGatewayReportedCostBreakdown(settings, model, inferenceCostUsd, options)
|
|
3665
|
+
.creditCostMicros;
|
|
3666
|
+
}
|
|
3667
|
+
|
|
3668
|
+
export function calculateGatewayReportedCostBreakdown(
|
|
3669
|
+
settings: Settings,
|
|
3670
|
+
model: string,
|
|
3671
|
+
inferenceCostUsd: string,
|
|
3672
|
+
options?: { inputTokens?: number },
|
|
3673
|
+
): ModelUsageCostBreakdown {
|
|
3474
3674
|
const schedule = configuredModelPricingSchedules(settings)[model];
|
|
3475
3675
|
if (!schedule) {
|
|
3476
3676
|
throw new Error(`Missing model pricing for ${model}`);
|
|
@@ -3483,14 +3683,52 @@ export function calculateGatewayReportedCostMicros(
|
|
|
3483
3683
|
const fraction = match[2] ?? "";
|
|
3484
3684
|
const decimalDigits = BigInt(`${match[1]}${fraction}`);
|
|
3485
3685
|
const decimalScale = 10n ** BigInt(fraction.length);
|
|
3686
|
+
const providerNumerator = decimalDigits * 1_000_000n;
|
|
3687
|
+
const providerMicros = (providerNumerator + decimalScale - 1n) / decimalScale;
|
|
3486
3688
|
const marginBps = BigInt(10_000 + (pricing.marginBps ?? 0));
|
|
3487
|
-
const numerator =
|
|
3689
|
+
const numerator = providerNumerator * marginBps;
|
|
3488
3690
|
const denominator = decimalScale * 10_000n;
|
|
3489
|
-
const
|
|
3490
|
-
if (
|
|
3691
|
+
const creditMicros = (numerator + denominator - 1n) / denominator;
|
|
3692
|
+
if (
|
|
3693
|
+
providerMicros > BigInt(Number.MAX_SAFE_INTEGER) ||
|
|
3694
|
+
creditMicros > BigInt(Number.MAX_SAFE_INTEGER)
|
|
3695
|
+
) {
|
|
3491
3696
|
throw new Error("AI Gateway inference cost exceeds the supported billing range");
|
|
3492
3697
|
}
|
|
3493
|
-
return
|
|
3698
|
+
return {
|
|
3699
|
+
providerCostMicros: Number(providerMicros),
|
|
3700
|
+
creditCostMicros: Number(creditMicros),
|
|
3701
|
+
};
|
|
3702
|
+
}
|
|
3703
|
+
|
|
3704
|
+
/**
|
|
3705
|
+
* Exact OpenGeni product price frozen before a managed video request starts.
|
|
3706
|
+
* Gateway reporting is delayed for asynchronous video, so this deliberately
|
|
3707
|
+
* does not masquerade as provider-reported cost.
|
|
3708
|
+
*/
|
|
3709
|
+
export function calculateVideoGenerationCreditCostMicros(
|
|
3710
|
+
settings: Settings,
|
|
3711
|
+
input: {
|
|
3712
|
+
modelId: string;
|
|
3713
|
+
resolution: VideoGenerationResolution;
|
|
3714
|
+
durationSeconds: number;
|
|
3715
|
+
},
|
|
3716
|
+
): number {
|
|
3717
|
+
if (input.modelId !== SEEDANCE_2_5_MODEL_ID) {
|
|
3718
|
+
throw new Error(`Missing video generation credit pricing for ${input.modelId}`);
|
|
3719
|
+
}
|
|
3720
|
+
if (!Number.isSafeInteger(input.durationSeconds) || input.durationSeconds < 1) {
|
|
3721
|
+
throw new Error("Video generation duration is invalid for credit pricing");
|
|
3722
|
+
}
|
|
3723
|
+
const rate =
|
|
3724
|
+
input.resolution === "480p"
|
|
3725
|
+
? settings.videoGenerationCredit480pMicrosPerSecond
|
|
3726
|
+
: settings.videoGenerationCredit720pMicrosPerSecond;
|
|
3727
|
+
const cost = rate * input.durationSeconds;
|
|
3728
|
+
if (!Number.isSafeInteger(cost) || cost <= 0 || cost > 1_000_000_000) {
|
|
3729
|
+
throw new Error("Video generation credit price exceeds the supported range");
|
|
3730
|
+
}
|
|
3731
|
+
return cost;
|
|
3494
3732
|
}
|
|
3495
3733
|
|
|
3496
3734
|
export function configuredAllowedReasoningEfforts(
|
|
@@ -3704,24 +3942,13 @@ export function stableSandboxEnvironmentForRun(
|
|
|
3704
3942
|
environment.OPENGENI_GIT_CLI_WRAPPER_DIR ??= `${home}/.opengeni/bin`;
|
|
3705
3943
|
environment.PATH = prependPathEntry(environment.PATH, environment.OPENGENI_GIT_CLI_WRAPPER_DIR);
|
|
3706
3944
|
}
|
|
3707
|
-
if (settings.
|
|
3708
|
-
|
|
3709
|
-
// stable shell-resolved pointer in the manifest; runtime expands this trusted
|
|
3710
|
-
// marker against the machine's own HOME for seed, renewal, and every command.
|
|
3711
|
-
// Never derive it from the selfhosted descriptor root (`/`), which would try
|
|
3712
|
-
// to write `/.opengeni` as an ordinary machine user.
|
|
3713
|
-
environment.OPENGENI_TOOLSPACE_TOKEN_FILE ??=
|
|
3714
|
-
settings.sandboxBackend === "selfhosted"
|
|
3715
|
-
? "$HOME/.opengeni/toolspace-token"
|
|
3716
|
-
: `${environment.HOME ?? descriptor.workspaceRoot}/.opengeni/toolspace-token`;
|
|
3945
|
+
if (settings.sandboxBackend !== "selfhosted" && resolveFirstPartyDelegationSecret(settings)) {
|
|
3946
|
+
environment.OPENGENI_CODEMODE_TOKEN_FILE ??= `${environment.HOME ?? descriptor.workspaceRoot}/.opengeni/codemode-token`;
|
|
3717
3947
|
if (settings.ogtoolPackageSpec) {
|
|
3718
3948
|
environment.OPENGENI_OGTOOL_PACKAGE_SPEC ??= settings.ogtoolPackageSpec;
|
|
3719
3949
|
}
|
|
3720
3950
|
if (options.workspaceId) {
|
|
3721
|
-
environment.
|
|
3722
|
-
settings,
|
|
3723
|
-
options.workspaceId,
|
|
3724
|
-
);
|
|
3951
|
+
environment.OPENGENI_CODEMODE_URL ??= codemodeWorkspaceUrl(settings, options.workspaceId);
|
|
3725
3952
|
}
|
|
3726
3953
|
}
|
|
3727
3954
|
return environment;
|
|
@@ -4226,6 +4453,7 @@ function ensureBuiltInMcpServers(settings: Settings): Settings["mcpServers"] {
|
|
|
4226
4453
|
"search_documents",
|
|
4227
4454
|
"fetch_document_chunk",
|
|
4228
4455
|
"list_document_bases",
|
|
4456
|
+
"list_indexed_documents",
|
|
4229
4457
|
"knowledge_search",
|
|
4230
4458
|
"knowledge_fetch",
|
|
4231
4459
|
"memory_search",
|
|
@@ -4278,6 +4506,15 @@ export function firstPartyMcpWorkspaceUrl(settings: Settings, workspaceId: strin
|
|
|
4278
4506
|
return url.toString();
|
|
4279
4507
|
}
|
|
4280
4508
|
|
|
4509
|
+
export function codemodeWorkspaceUrl(settings: Settings, workspaceId: string): string {
|
|
4510
|
+
const url = new URL(firstPartyMcpWorkspaceUrl(settings, workspaceId));
|
|
4511
|
+
if (!url.pathname.endsWith("/mcp")) {
|
|
4512
|
+
throw new Error("First-party MCP URL cannot be projected to the Codemode endpoint");
|
|
4513
|
+
}
|
|
4514
|
+
url.pathname = `${url.pathname.slice(0, -4)}/codemode`;
|
|
4515
|
+
return url.toString();
|
|
4516
|
+
}
|
|
4517
|
+
|
|
4281
4518
|
function firstPartyMcpServerUrl(settings: Settings): string {
|
|
4282
4519
|
return firstPartyMcpBaseUrl(settings);
|
|
4283
4520
|
}
|
|
@@ -4292,9 +4529,6 @@ function firstPartyFilesMcpServerUrl(mcpUrl: string): string {
|
|
|
4292
4529
|
|
|
4293
4530
|
function validateSettings(settings: Settings): void {
|
|
4294
4531
|
temporalConnectionOptions(settings);
|
|
4295
|
-
if (settings.toolspaceEnabled && !settings.delegationSecret) {
|
|
4296
|
-
throw new Error("OPENGENI_DELEGATION_SECRET is required when OPENGENI_TOOLSPACE_ENABLED=true");
|
|
4297
|
-
}
|
|
4298
4532
|
if (settings.productAccessMode === "managed") {
|
|
4299
4533
|
if (!settings.publicBaseUrl) {
|
|
4300
4534
|
throw new Error(
|
|
@@ -4348,11 +4582,6 @@ function validateSettings(settings: Settings): void {
|
|
|
4348
4582
|
);
|
|
4349
4583
|
}
|
|
4350
4584
|
if (settings.slackClientId) {
|
|
4351
|
-
if (!settings.slackSigningSecret) {
|
|
4352
|
-
throw new Error(
|
|
4353
|
-
"OPENGENI_SLACK_SIGNING_SECRET is required when the OpenGeni Slack app is configured",
|
|
4354
|
-
);
|
|
4355
|
-
}
|
|
4356
4585
|
if (!settings.publicBaseUrl) {
|
|
4357
4586
|
throw new Error(
|
|
4358
4587
|
"OPENGENI_PUBLIC_BASE_URL is required when the OpenGeni Slack app is configured",
|
|
@@ -4397,6 +4626,31 @@ function validateSettings(settings: Settings): void {
|
|
|
4397
4626
|
);
|
|
4398
4627
|
}
|
|
4399
4628
|
}
|
|
4629
|
+
if (Boolean(settings.atlassianClientId) !== Boolean(settings.atlassianClientSecret)) {
|
|
4630
|
+
throw new Error(
|
|
4631
|
+
"OPENGENI_ATLASSIAN_CLIENT_ID and OPENGENI_ATLASSIAN_CLIENT_SECRET must be configured together",
|
|
4632
|
+
);
|
|
4633
|
+
}
|
|
4634
|
+
if (settings.atlassianClientId) {
|
|
4635
|
+
if (!settings.publicBaseUrl) {
|
|
4636
|
+
throw new Error(
|
|
4637
|
+
"OPENGENI_PUBLIC_BASE_URL is required when the Atlassian integration is configured",
|
|
4638
|
+
);
|
|
4639
|
+
}
|
|
4640
|
+
if (
|
|
4641
|
+
!settings.publicBaseUrl.startsWith("https://") &&
|
|
4642
|
+
!["local", "test"].includes(settings.environment)
|
|
4643
|
+
) {
|
|
4644
|
+
throw new Error(
|
|
4645
|
+
"OPENGENI_PUBLIC_BASE_URL must use https when the Atlassian integration is configured outside local/test",
|
|
4646
|
+
);
|
|
4647
|
+
}
|
|
4648
|
+
if (!settings.integrationsStateSecret) {
|
|
4649
|
+
throw new Error(
|
|
4650
|
+
"OPENGENI_INTEGRATIONS_STATE_SECRET is required when the Atlassian integration is configured",
|
|
4651
|
+
);
|
|
4652
|
+
}
|
|
4653
|
+
}
|
|
4400
4654
|
parseIntegrationsOauthClientsJson(settings.integrationsOauthClientsJson);
|
|
4401
4655
|
parseSocialOauthClientsJson(settings.socialOauthClientsJson);
|
|
4402
4656
|
if (
|
|
@@ -4620,6 +4874,7 @@ function validateSettings(settings: Settings): void {
|
|
|
4620
4874
|
{
|
|
4621
4875
|
const reaperPeriod = settings.sandboxLeaseReaperPeriodMs;
|
|
4622
4876
|
const viewerTtl = settings.sandboxViewerHolderTtlMs;
|
|
4877
|
+
const interactionTtl = settings.sandboxInteractionHolderTtlMs;
|
|
4623
4878
|
const idleGraceMs = settings.sandboxIdleGraceMs;
|
|
4624
4879
|
const providerLifetimeMs = settings.modalTimeoutSeconds * 1000;
|
|
4625
4880
|
const rotationLeadMs = settings.sandboxRotationLeadMs;
|
|
@@ -4638,6 +4893,13 @@ function validateSettings(settings: Settings): void {
|
|
|
4638
4893
|
`than the TTL it polices, or stale viewer holders outlive a full reaper period.`,
|
|
4639
4894
|
);
|
|
4640
4895
|
}
|
|
4896
|
+
if (!(reaperPeriod < interactionTtl)) {
|
|
4897
|
+
throw new Error(
|
|
4898
|
+
`OPENGENI_SANDBOX_LEASE_REAPER_PERIOD_MS (${reaperPeriod}) must be strictly less than ` +
|
|
4899
|
+
`OPENGENI_SANDBOX_INTERACTION_HOLDER_TTL_MS (${interactionTtl}): the reaper must run ` +
|
|
4900
|
+
`more often than the controller-heartbeat horizon.`,
|
|
4901
|
+
);
|
|
4902
|
+
}
|
|
4641
4903
|
if (!(idleTimeoutMs <= providerLifetimeMs)) {
|
|
4642
4904
|
throw new Error(
|
|
4643
4905
|
`OPENGENI_MODAL_IDLE_TIMEOUT_SECONDS*1000 (${idleTimeoutMs}) must not exceed the hard provider ` +
|
|
@@ -4651,10 +4913,15 @@ function validateSettings(settings: Settings): void {
|
|
|
4651
4913
|
`OPENGENI_MODAL_TIMEOUT_SECONDS*1000 (${providerLifetimeMs}).`,
|
|
4652
4914
|
);
|
|
4653
4915
|
}
|
|
4654
|
-
|
|
4916
|
+
// This is provider-hard-deadline headroom, not a retry delay. Rotation is
|
|
4917
|
+
// admitted immediately before the same sweep's drain inventory, so only the
|
|
4918
|
+
// worst-case time until that sweep plus the complete durable capture window
|
|
4919
|
+
// is required. No second schedule period belongs in the availability path.
|
|
4920
|
+
const captureTimeoutMs = sandboxArchiveCaptureTimeoutMs(settings);
|
|
4921
|
+
if (!(rotationLeadMs > captureTimeoutMs + reaperPeriod)) {
|
|
4655
4922
|
throw new Error(
|
|
4656
|
-
`OPENGENI_SANDBOX_ROTATION_LEAD_MS (${rotationLeadMs}) must exceed the
|
|
4657
|
-
`plus
|
|
4923
|
+
`OPENGENI_SANDBOX_ROTATION_LEAD_MS (${rotationLeadMs}) must exceed the durable capture ` +
|
|
4924
|
+
`timeout plus one reaper period (${captureTimeoutMs + reaperPeriod}).`,
|
|
4658
4925
|
);
|
|
4659
4926
|
}
|
|
4660
4927
|
if (!(viewerTtl < idleTimeoutMs)) {
|
|
@@ -4664,6 +4931,13 @@ function validateSettings(settings: Settings): void {
|
|
|
4664
4931
|
`under it (the provider idle-timeout is the backstop).`,
|
|
4665
4932
|
);
|
|
4666
4933
|
}
|
|
4934
|
+
if (!(interactionTtl < idleTimeoutMs)) {
|
|
4935
|
+
throw new Error(
|
|
4936
|
+
`OPENGENI_SANDBOX_INTERACTION_HOLDER_TTL_MS (${interactionTtl}) must be strictly less than ` +
|
|
4937
|
+
`the effective box idle timeout (${idleTimeoutMs}): a dead browser controller must be ` +
|
|
4938
|
+
`reapable before the provider reclaims its placement.`,
|
|
4939
|
+
);
|
|
4940
|
+
}
|
|
4667
4941
|
if (!(reaperPeriod + idleGraceMs < idleTimeoutMs)) {
|
|
4668
4942
|
throw new Error(
|
|
4669
4943
|
`OPENGENI_SANDBOX_LEASE_REAPER_PERIOD_MS + OPENGENI_SANDBOX_IDLE_GRACE_MS ` +
|