@opengeni/config 0.5.1 → 0.6.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 +47 -8
- package/dist/index.js +138 -18
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +192 -23
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/config",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "OpenGeni runtime configuration: settings resolution, deployment knobs, and config validation shared across the server packages.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"prepublishOnly": "bash ../../scripts/prepublish-guard"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@opengeni/codex": "^0.2.
|
|
37
|
-
"@opengeni/contracts": "^0.
|
|
36
|
+
"@opengeni/codex": "^0.2.5",
|
|
37
|
+
"@opengeni/contracts": "^0.15.0",
|
|
38
38
|
"zod": "^4.2.1"
|
|
39
39
|
},
|
|
40
40
|
"engines": {
|
package/src/index.ts
CHANGED
|
@@ -90,7 +90,7 @@ export const DEFAULT_AGENT_INSTRUCTIONS = [
|
|
|
90
90
|
"You are an OpenGeni workspace agent.",
|
|
91
91
|
"Follow the user's task and any enabled pack or skill instructions for the current role.",
|
|
92
92
|
"Work inside the sandbox workspace and use filesystem and shell tools when useful.",
|
|
93
|
-
"Repository resources are mounted under repos/<owner>/<repo
|
|
93
|
+
"Repository resources are mounted under repos/<host>/<owner>/<repo> unless the session specifies another collision-free mount path.",
|
|
94
94
|
"File resources are mounted under files/<file-id>/ unless the session specifies another mount path.",
|
|
95
95
|
"Attached files are mounted read-only; copy them before modifying.",
|
|
96
96
|
"Bundled skills are under .agents/ and can include infrastructure, marketing, or other role-specific guidance.",
|
|
@@ -103,14 +103,59 @@ export const DEFAULT_AGENT_INSTRUCTIONS = [
|
|
|
103
103
|
|
|
104
104
|
export const McpServerConnectionRefSchema = z
|
|
105
105
|
.object({
|
|
106
|
-
|
|
106
|
+
// Standalone ids are UUIDs; embedded hosts may use any stable opaque id.
|
|
107
|
+
connectionId: z.string().min(1).optional(),
|
|
108
|
+
provider: z.string().min(1).max(128).optional(),
|
|
107
109
|
providerDomain: z.string().min(1),
|
|
108
110
|
kind: z.enum(["oauth2", "api_key", "app_install", "delegated"]).optional(),
|
|
109
111
|
scopes: z.array(z.string().min(1)).optional(),
|
|
110
112
|
resource: z.string().min(1).optional(),
|
|
113
|
+
selectedResources: z
|
|
114
|
+
.array(
|
|
115
|
+
z
|
|
116
|
+
.object({
|
|
117
|
+
id: z.string().min(1).max(512),
|
|
118
|
+
kind: z.literal("repository"),
|
|
119
|
+
})
|
|
120
|
+
.strict(),
|
|
121
|
+
)
|
|
122
|
+
.min(1)
|
|
123
|
+
.max(256)
|
|
124
|
+
.superRefine((resources, context) => {
|
|
125
|
+
const seen = new Set<string>();
|
|
126
|
+
for (const [index, resource] of resources.entries()) {
|
|
127
|
+
const key = `${resource.kind}\0${resource.id}`;
|
|
128
|
+
if (seen.has(key)) {
|
|
129
|
+
context.addIssue({
|
|
130
|
+
code: "custom",
|
|
131
|
+
message: "selectedResources must not contain duplicates",
|
|
132
|
+
path: [index],
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
seen.add(key);
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
.optional(),
|
|
111
139
|
subjectScope: z.enum(["workspace", "subject"]).optional(),
|
|
112
140
|
})
|
|
113
|
-
.strict()
|
|
141
|
+
.strict()
|
|
142
|
+
.superRefine((reference, context) => {
|
|
143
|
+
if (!reference.selectedResources) return;
|
|
144
|
+
if (!reference.connectionId) {
|
|
145
|
+
context.addIssue({
|
|
146
|
+
code: "custom",
|
|
147
|
+
message: "selectedResources requires connectionId",
|
|
148
|
+
path: ["connectionId"],
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
if (!reference.provider) {
|
|
152
|
+
context.addIssue({
|
|
153
|
+
code: "custom",
|
|
154
|
+
message: "selectedResources requires provider",
|
|
155
|
+
path: ["provider"],
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
});
|
|
114
159
|
export type McpServerConnectionRef = z.infer<typeof McpServerConnectionRefSchema>;
|
|
115
160
|
|
|
116
161
|
const SettingsSchema = z.object({
|
|
@@ -125,7 +170,7 @@ const SettingsSchema = z.object({
|
|
|
125
170
|
// topology. Default "" → standalone: no search_path scoping, server default
|
|
126
171
|
// (`public`). When set (e.g. "opengeni"), the db handle + the managed-auth
|
|
127
172
|
// pool send `search_path = "<dbSchema>","opengeni_private","public"` so every
|
|
128
|
-
// query resolves into the dedicated schema with NO query rewrite (
|
|
173
|
+
// query resolves into the dedicated schema with NO query rewrite (schema-isolation contract F1).
|
|
129
174
|
dbSchema: z.string().default(""),
|
|
130
175
|
// Step I (§7.7). RLS posture. "force" (default) = today's FORCE-RLS via the
|
|
131
176
|
// non-owner `opengeni_app` role. "scoped" = the embedded owner-role path (the
|
|
@@ -135,6 +180,12 @@ const SettingsSchema = z.object({
|
|
|
135
180
|
temporalHost: z.string().default("127.0.0.1:7233"),
|
|
136
181
|
temporalNamespace: z.string().default("default"),
|
|
137
182
|
temporalTaskQueue: z.string().default("opengeni-runs-ts"),
|
|
183
|
+
temporalTlsEnabled: EnvBoolean.default(false),
|
|
184
|
+
temporalApiKey: z.string().optional(),
|
|
185
|
+
temporalTlsServerName: z.string().optional(),
|
|
186
|
+
temporalTlsRootCaCertificateBase64: z.string().optional(),
|
|
187
|
+
temporalTlsClientCertificateBase64: z.string().optional(),
|
|
188
|
+
temporalTlsClientPrivateKeyBase64: z.string().optional(),
|
|
138
189
|
startupDependencyRetryAttempts: z.coerce.number().int().positive().default(30),
|
|
139
190
|
startupDependencyRetryInitialDelayMs: z.coerce.number().int().positive().default(1000),
|
|
140
191
|
startupDependencyRetryMaxDelayMs: z.coerce.number().int().positive().default(5000),
|
|
@@ -157,11 +208,11 @@ const SettingsSchema = z.object({
|
|
|
157
208
|
staticEntitlementsJson: z.string().default("{}"),
|
|
158
209
|
staticUsageLimitsJson: z.string().default("{}"),
|
|
159
210
|
delegationSecret: z.string().optional(),
|
|
160
|
-
//
|
|
211
|
+
// sandbox workspace scoped stream-token HMAC secret (sandbox contract §C.3 / stream-token availability contract).
|
|
161
212
|
// When unset, the API falls back to `delegationSecret` (the same HMAC envelope
|
|
162
213
|
// family, `ogs_` vs `ogd_` prefix). REQUIRED-WHEN-DESKTOP, but the absence of
|
|
163
214
|
// BOTH while sandboxDesktopEnabled=true is a GRACEFUL DEGRADE (DesktopStream
|
|
164
|
-
// transport:null + a loud boot warning), NOT a hard boot-fail (
|
|
215
|
+
// transport:null + a loud boot warning), NOT a hard boot-fail (stream-token availability contract).
|
|
165
216
|
streamTokenSecret: z.string().optional(),
|
|
166
217
|
// The desktop input plane (raw stream:control writes) is OFF in v1: even a
|
|
167
218
|
// holder of stream:control gets 403 until this flips. Keeps stream:control a
|
|
@@ -169,6 +220,13 @@ const SettingsSchema = z.object({
|
|
|
169
220
|
streamControlEnabled: EnvBoolean.default(false),
|
|
170
221
|
toolspaceEnabled: EnvBoolean.default(false),
|
|
171
222
|
toolspaceMaxCallsPerTurn: z.coerce.number().int().positive().default(200),
|
|
223
|
+
// Optional release-coherent bootstrap hint for custom rigs/connected machines
|
|
224
|
+
// that do not carry the stock-image ogtool binary. Exact stable versions only:
|
|
225
|
+
// the agent must never guess a tag or silently install `latest`.
|
|
226
|
+
ogtoolPackageSpec: z
|
|
227
|
+
.string()
|
|
228
|
+
.regex(/^@opengeni\/ogtool@(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/u)
|
|
229
|
+
.optional(),
|
|
172
230
|
environmentsEncryptionKey: z.string().optional(),
|
|
173
231
|
integrationsEnabled: EnvBoolean.default(false),
|
|
174
232
|
integrationsStateSecret: z.string().optional(),
|
|
@@ -216,6 +274,10 @@ const SettingsSchema = z.object({
|
|
|
216
274
|
// Model-catalog auto-compact limit. When present it is clamped to
|
|
217
275
|
// 90% of the raw window, matching Codex core's auto_compact_token_limit().
|
|
218
276
|
contextAutoCompactThresholdTokens: z.coerce.number().int().positive().optional(),
|
|
277
|
+
// Provider-neutral fallback for canonical model-facing tool-result text.
|
|
278
|
+
// The current stable Codex catalog policy is 10k tokens; the truncator adds
|
|
279
|
+
// Codex's 1.2x JSON serialization allowance when applying it.
|
|
280
|
+
modelToolOutputTruncationTokens: z.coerce.number().int().positive().default(10_000),
|
|
219
281
|
authRequired: EnvBoolean.default(false),
|
|
220
282
|
accessKey: z.string().optional(),
|
|
221
283
|
authAllowHealth: EnvBoolean.default(true),
|
|
@@ -249,15 +311,11 @@ const SettingsSchema = z.object({
|
|
|
249
311
|
// tool that BM25-discloses only the matching connectors. Default OFF — a codex
|
|
250
312
|
// turn is byte-for-byte unchanged until enabled. OPENGENI_CODEX_TOOL_SEARCH_ENABLED
|
|
251
313
|
codexToolSearchEnabled: EnvBoolean.default(false),
|
|
252
|
-
//
|
|
314
|
+
// credential allocator atomic, workspace-local credential allocation. Default OFF is a
|
|
253
315
|
// deliberate rolling-deploy fence: migrate + roll every worker first, then
|
|
254
316
|
// enable. Turning it off restores the legacy sticky selector without a schema
|
|
255
317
|
// rollback; the additive lease table/cursor columns become inert.
|
|
256
318
|
codexCredentialLeasingEnabled: EnvBoolean.default(false),
|
|
257
|
-
// Multi-account P3 (auto-rotation): an account is "near exhaustion" — ineligible to be
|
|
258
|
-
// rotated TO — when EITHER usage window (5h/weekly) is at/over this percent. Default 90 to
|
|
259
|
-
// match the UI danger flip (UsageBar danger at pct >= 90). OPENGENI_CODEX_ROTATION_NEAR_EXHAUSTION_PCT.
|
|
260
|
-
codexRotationNearExhaustionPct: z.coerce.number().int().min(1).max(100).default(90),
|
|
261
319
|
openaiReasoningEffort: ReasoningEffort.default("low"),
|
|
262
320
|
openaiAllowedReasoningEfforts: z.string().default("low,medium,high,xhigh"),
|
|
263
321
|
openaiResponsesTransport: z.enum(["http", "websocket"]).default("http"),
|
|
@@ -398,7 +456,7 @@ const SettingsSchema = z.object({
|
|
|
398
456
|
// recordingMaxSeconds is the ffmpeg -t hard ceiling (bounds a multi-day turn).
|
|
399
457
|
recordingEnabled: EnvBoolean.default(true),
|
|
400
458
|
recordingDefaultCodec: z.enum(["h264-mp4", "vp9-webm"]).default("h264-mp4"),
|
|
401
|
-
// Workbench v2 turn-end workspace capture
|
|
459
|
+
// Workbench v2 turn-end workspace capture. When on, the turn
|
|
402
460
|
// activity probes the box's changed files off the live box at turn end and
|
|
403
461
|
// persists a capture revision (blobs in @opengeni/storage) so the workbench
|
|
404
462
|
// paints cold/offline sessions with zero machine round-trips. Best-effort and
|
|
@@ -477,7 +535,7 @@ const SettingsSchema = z.object({
|
|
|
477
535
|
// 404 (invisible — the surface does not exist for this deployment) and the
|
|
478
536
|
// selfhosted backend is inert; boot is unaffected. EnvBoolean (NOT
|
|
479
537
|
// z.coerce.boolean(), which coerces "false" -> true). Flipped per-environment via
|
|
480
|
-
// the deploy-staging IaC secret/configmap pattern
|
|
538
|
+
// the deploy-staging IaC secret/configmap pattern.
|
|
481
539
|
sandboxSelfhostedEnabled: EnvBoolean.default(false),
|
|
482
540
|
// Gates the op-stream (streaming exec) transport to Connected Machines. The
|
|
483
541
|
// runner must ALSO advertise Capabilities.op_stream; default off, and legacy
|
|
@@ -497,7 +555,7 @@ const SettingsSchema = z.object({
|
|
|
497
555
|
selfhostedNatsUrl: z.string().optional(),
|
|
498
556
|
selfhostedRelayUrl: z.string().optional(),
|
|
499
557
|
// The HMAC secret the control plane signs the agent's relay PRODUCER token with
|
|
500
|
-
// (the `ogr_` envelope threaded into EnrollmentCredentials.relayToken; M8b/
|
|
558
|
+
// (the `ogr_` envelope threaded into EnrollmentCredentials.relayToken; M8b/design
|
|
501
559
|
// §10.5). The relay verifies the producer token with the SAME secret. Optional:
|
|
502
560
|
// when ABSENT the poll returns an empty relayToken (graceful degrade — the stream
|
|
503
561
|
// plane is simply unavailable until configured). Falls back to streamTokenSecret /
|
|
@@ -507,7 +565,7 @@ const SettingsSchema = z.object({
|
|
|
507
565
|
// The minisign PUBLIC key the agent pins for self-update verification (handed to
|
|
508
566
|
// the agent in EnrollmentCredentials; the SECRET key lives only in CI).
|
|
509
567
|
agentUpdatePublicKey: z.string().optional(),
|
|
510
|
-
// --- NATS auth-callout tenancy boundary (bring-your-own-compute M-AUTH;
|
|
568
|
+
// --- NATS auth-callout tenancy boundary (bring-your-own-compute M-AUTH; design
|
|
511
569
|
// §10.1 NATS Accounts per workspace + §17 the isolation smoke) -------------
|
|
512
570
|
// nats-server is configured with AUTH CALLOUT: an external agent connects
|
|
513
571
|
// presenting its `oge_` enrollment bearer as the connect auth-token; the server
|
|
@@ -701,6 +759,19 @@ const SettingsSchema = z.object({
|
|
|
701
759
|
|
|
702
760
|
export type Settings = z.infer<typeof SettingsSchema>;
|
|
703
761
|
export type McpServerConfig = Settings["mcpServers"][number];
|
|
762
|
+
export type TemporalTlsConnectionConfig = {
|
|
763
|
+
serverNameOverride?: string;
|
|
764
|
+
serverRootCACertificate?: Uint8Array;
|
|
765
|
+
clientCertPair?: {
|
|
766
|
+
crt: Uint8Array;
|
|
767
|
+
key: Uint8Array;
|
|
768
|
+
};
|
|
769
|
+
};
|
|
770
|
+
export type TemporalConnectionOptions = {
|
|
771
|
+
address: string;
|
|
772
|
+
tls?: true | TemporalTlsConnectionConfig;
|
|
773
|
+
apiKey?: string;
|
|
774
|
+
};
|
|
704
775
|
export type ModelPricing = {
|
|
705
776
|
inputMicrosPerMillionTokens: number;
|
|
706
777
|
cachedInputMicrosPerMillionTokens?: number | undefined;
|
|
@@ -750,6 +821,9 @@ const RegistryModelSchema = z.object({
|
|
|
750
821
|
contextWindowTokens: z.number().int().positive().optional(),
|
|
751
822
|
effectiveContextWindowTokens: z.number().int().positive().optional(),
|
|
752
823
|
autoCompactTokenLimit: z.number().int().positive().optional(),
|
|
824
|
+
// Canonical model-facing function/tool-result policy. The runtime applies
|
|
825
|
+
// the same 1.2x serialization allowance as Codex when materializing output.
|
|
826
|
+
toolOutputTruncationTokens: z.number().int().positive().optional(),
|
|
753
827
|
reasoningEffort: z.boolean().optional(), // model accepts a reasoning-effort control
|
|
754
828
|
hostedWebSearch: z.boolean().optional(), // provider executes the hosted web_search tool for this model
|
|
755
829
|
pricing: ModelPricingSchema.optional(),
|
|
@@ -808,6 +882,7 @@ export interface ConfiguredModel {
|
|
|
808
882
|
contextWindowTokens?: number | undefined;
|
|
809
883
|
effectiveContextWindowTokens?: number | undefined;
|
|
810
884
|
autoCompactTokenLimit?: number | undefined;
|
|
885
|
+
toolOutputTruncationTokens?: number | undefined;
|
|
811
886
|
reasoningEffort: boolean;
|
|
812
887
|
hostedWebSearch: boolean;
|
|
813
888
|
}
|
|
@@ -971,6 +1046,14 @@ export function getSettings(): Settings {
|
|
|
971
1046
|
temporalHost: optional("OPENGENI_TEMPORAL_HOST"),
|
|
972
1047
|
temporalNamespace: optional("OPENGENI_TEMPORAL_NAMESPACE"),
|
|
973
1048
|
temporalTaskQueue: optional("OPENGENI_TEMPORAL_TASK_QUEUE"),
|
|
1049
|
+
temporalTlsEnabled: optional("OPENGENI_TEMPORAL_TLS_ENABLED"),
|
|
1050
|
+
temporalApiKey: optional("OPENGENI_TEMPORAL_API_KEY"),
|
|
1051
|
+
temporalTlsServerName: optional("OPENGENI_TEMPORAL_TLS_SERVER_NAME"),
|
|
1052
|
+
temporalTlsRootCaCertificateBase64: optional(
|
|
1053
|
+
"OPENGENI_TEMPORAL_TLS_ROOT_CA_CERTIFICATE_BASE64",
|
|
1054
|
+
),
|
|
1055
|
+
temporalTlsClientCertificateBase64: optional("OPENGENI_TEMPORAL_TLS_CLIENT_CERTIFICATE_BASE64"),
|
|
1056
|
+
temporalTlsClientPrivateKeyBase64: optional("OPENGENI_TEMPORAL_TLS_CLIENT_PRIVATE_KEY_BASE64"),
|
|
974
1057
|
startupDependencyRetryAttempts: optional("OPENGENI_STARTUP_DEPENDENCY_RETRY_ATTEMPTS"),
|
|
975
1058
|
startupDependencyRetryInitialDelayMs: optional(
|
|
976
1059
|
"OPENGENI_STARTUP_DEPENDENCY_RETRY_INITIAL_DELAY_MS",
|
|
@@ -995,6 +1078,7 @@ export function getSettings(): Settings {
|
|
|
995
1078
|
streamControlEnabled: optional("OPENGENI_STREAM_CONTROL_ENABLED"),
|
|
996
1079
|
toolspaceEnabled: optional("OPENGENI_TOOLSPACE_ENABLED"),
|
|
997
1080
|
toolspaceMaxCallsPerTurn: optional("OPENGENI_TOOLSPACE_MAX_CALLS_PER_TURN"),
|
|
1081
|
+
ogtoolPackageSpec: optional("OPENGENI_OGTOOL_PACKAGE_SPEC"),
|
|
998
1082
|
environmentsEncryptionKey: optional("OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY"),
|
|
999
1083
|
integrationsEnabled: optional("OPENGENI_INTEGRATIONS_ENABLED"),
|
|
1000
1084
|
integrationsStateSecret: optional("OPENGENI_INTEGRATIONS_STATE_SECRET"),
|
|
@@ -1010,6 +1094,7 @@ export function getSettings(): Settings {
|
|
|
1010
1094
|
contextCompactionThresholdRatio: optional("OPENGENI_COMPACTION_THRESHOLD_RATIO"),
|
|
1011
1095
|
contextReservedOutputTokens: optional("OPENGENI_CONTEXT_RESERVED_OUTPUT_TOKENS"),
|
|
1012
1096
|
contextAutoCompactThresholdTokens: optional("OPENGENI_CONTEXT_AUTO_COMPACT_THRESHOLD_TOKENS"),
|
|
1097
|
+
modelToolOutputTruncationTokens: optional("OPENGENI_MODEL_TOOL_OUTPUT_TRUNCATION_TOKENS"),
|
|
1013
1098
|
authRequired: optional("OPENGENI_AUTH_REQUIRED"),
|
|
1014
1099
|
accessKey: optional("OPENGENI_ACCESS_KEY"),
|
|
1015
1100
|
authAllowHealth: optional("OPENGENI_AUTH_ALLOW_HEALTH"),
|
|
@@ -1030,7 +1115,6 @@ export function getSettings(): Settings {
|
|
|
1030
1115
|
codexToolSearchEnabled: optional("OPENGENI_CODEX_TOOL_SEARCH_ENABLED"),
|
|
1031
1116
|
codexCredentialLeasingEnabled: optional("OPENGENI_CODEX_CREDENTIAL_LEASING_ENABLED"),
|
|
1032
1117
|
codexProductSku: optional("OPENGENI_CODEX_PRODUCT_SKU"),
|
|
1033
|
-
codexRotationNearExhaustionPct: optional("OPENGENI_CODEX_ROTATION_NEAR_EXHAUSTION_PCT"),
|
|
1034
1118
|
openaiReasoningEffort: optional("OPENGENI_OPENAI_REASONING_EFFORT"),
|
|
1035
1119
|
openaiAllowedReasoningEfforts: optional("OPENGENI_OPENAI_ALLOWED_REASONING_EFFORTS"),
|
|
1036
1120
|
openaiResponsesTransport: optional("OPENGENI_OPENAI_RESPONSES_TRANSPORT"),
|
|
@@ -1368,6 +1452,7 @@ export function configuredModels(settings: Settings): ConfiguredModel[] {
|
|
|
1368
1452
|
providerLabel: builtinLabel,
|
|
1369
1453
|
api: "responses" as const,
|
|
1370
1454
|
contextWindowTokens: settings.contextWindowTokens,
|
|
1455
|
+
toolOutputTruncationTokens: settings.modelToolOutputTruncationTokens,
|
|
1371
1456
|
reasoningEffort: true,
|
|
1372
1457
|
hostedWebSearch: settings.webSearchEnabled,
|
|
1373
1458
|
}));
|
|
@@ -1389,6 +1474,9 @@ export function configuredModels(settings: Settings): ConfiguredModel[] {
|
|
|
1389
1474
|
...(model.autoCompactTokenLimit === undefined
|
|
1390
1475
|
? {}
|
|
1391
1476
|
: { autoCompactTokenLimit: model.autoCompactTokenLimit }),
|
|
1477
|
+
...(model.toolOutputTruncationTokens === undefined
|
|
1478
|
+
? {}
|
|
1479
|
+
: { toolOutputTruncationTokens: model.toolOutputTruncationTokens }),
|
|
1392
1480
|
reasoningEffort: model.reasoningEffort ?? false,
|
|
1393
1481
|
hostedWebSearch: model.hostedWebSearch ?? false,
|
|
1394
1482
|
});
|
|
@@ -1485,7 +1573,10 @@ export function settingsWithResolvedModelContext(
|
|
|
1485
1573
|
settings: Settings,
|
|
1486
1574
|
model: Pick<
|
|
1487
1575
|
ConfiguredModel,
|
|
1488
|
-
|
|
1576
|
+
| "contextWindowTokens"
|
|
1577
|
+
| "effectiveContextWindowTokens"
|
|
1578
|
+
| "autoCompactTokenLimit"
|
|
1579
|
+
| "toolOutputTruncationTokens"
|
|
1489
1580
|
>,
|
|
1490
1581
|
): Settings {
|
|
1491
1582
|
const contextWindowTokens = model.contextWindowTokens ?? settings.contextWindowTokens;
|
|
@@ -1503,6 +1594,9 @@ export function settingsWithResolvedModelContext(
|
|
|
1503
1594
|
...(model.autoCompactTokenLimit === undefined
|
|
1504
1595
|
? {}
|
|
1505
1596
|
: { contextAutoCompactThresholdTokens: model.autoCompactTokenLimit }),
|
|
1597
|
+
...(model.toolOutputTruncationTokens === undefined
|
|
1598
|
+
? {}
|
|
1599
|
+
: { modelToolOutputTruncationTokens: model.toolOutputTruncationTokens }),
|
|
1506
1600
|
};
|
|
1507
1601
|
}
|
|
1508
1602
|
|
|
@@ -1573,6 +1667,77 @@ export function environmentsEncryptionKeyBytes(settings: Settings): Uint8Array |
|
|
|
1573
1667
|
return new Uint8Array(decoded);
|
|
1574
1668
|
}
|
|
1575
1669
|
|
|
1670
|
+
/**
|
|
1671
|
+
* Build one structurally compatible connection policy for both
|
|
1672
|
+
* `@temporalio/client` and `@temporalio/worker`. An API key or any custom TLS
|
|
1673
|
+
* material enables TLS automatically; the explicit flag covers server-auth TLS
|
|
1674
|
+
* without credentials. Secret values are never included in validation errors.
|
|
1675
|
+
*/
|
|
1676
|
+
export function temporalConnectionOptions(settings: Settings): TemporalConnectionOptions {
|
|
1677
|
+
const apiKey = settings.temporalApiKey?.trim() || undefined;
|
|
1678
|
+
const serverNameOverride = settings.temporalTlsServerName?.trim() || undefined;
|
|
1679
|
+
const rootCa = decodeTemporalTlsMaterial(
|
|
1680
|
+
settings.temporalTlsRootCaCertificateBase64,
|
|
1681
|
+
"OPENGENI_TEMPORAL_TLS_ROOT_CA_CERTIFICATE_BASE64",
|
|
1682
|
+
);
|
|
1683
|
+
const clientCertificate = decodeTemporalTlsMaterial(
|
|
1684
|
+
settings.temporalTlsClientCertificateBase64,
|
|
1685
|
+
"OPENGENI_TEMPORAL_TLS_CLIENT_CERTIFICATE_BASE64",
|
|
1686
|
+
);
|
|
1687
|
+
const clientPrivateKey = decodeTemporalTlsMaterial(
|
|
1688
|
+
settings.temporalTlsClientPrivateKeyBase64,
|
|
1689
|
+
"OPENGENI_TEMPORAL_TLS_CLIENT_PRIVATE_KEY_BASE64",
|
|
1690
|
+
);
|
|
1691
|
+
|
|
1692
|
+
if (Boolean(clientCertificate) !== Boolean(clientPrivateKey)) {
|
|
1693
|
+
throw new Error(
|
|
1694
|
+
"OPENGENI_TEMPORAL_TLS_CLIENT_CERTIFICATE_BASE64 and " +
|
|
1695
|
+
"OPENGENI_TEMPORAL_TLS_CLIENT_PRIVATE_KEY_BASE64 must both be set or both omitted",
|
|
1696
|
+
);
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
const tls: TemporalTlsConnectionConfig = {};
|
|
1700
|
+
if (serverNameOverride) {
|
|
1701
|
+
tls.serverNameOverride = serverNameOverride;
|
|
1702
|
+
}
|
|
1703
|
+
if (rootCa) {
|
|
1704
|
+
tls.serverRootCACertificate = rootCa;
|
|
1705
|
+
}
|
|
1706
|
+
if (clientCertificate && clientPrivateKey) {
|
|
1707
|
+
tls.clientCertPair = { crt: clientCertificate, key: clientPrivateKey };
|
|
1708
|
+
}
|
|
1709
|
+
const hasCustomTls = Object.keys(tls).length > 0;
|
|
1710
|
+
const tlsEnabled = settings.temporalTlsEnabled || Boolean(apiKey) || hasCustomTls;
|
|
1711
|
+
|
|
1712
|
+
return {
|
|
1713
|
+
address: settings.temporalHost,
|
|
1714
|
+
...(tlsEnabled ? { tls: hasCustomTls ? tls : true } : {}),
|
|
1715
|
+
...(apiKey ? { apiKey } : {}),
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
function decodeTemporalTlsMaterial(
|
|
1720
|
+
value: string | undefined,
|
|
1721
|
+
settingName: string,
|
|
1722
|
+
): Uint8Array | undefined {
|
|
1723
|
+
// RFC 2045 base64 commonly arrives wrapped at 76 columns. Kubernetes
|
|
1724
|
+
// stringData and external secret stores preserve those line breaks, so
|
|
1725
|
+
// normalize whitespace before applying the strict alphabet/canonical check.
|
|
1726
|
+
const encoded = value?.replace(/\s/g, "");
|
|
1727
|
+
if (!encoded) {
|
|
1728
|
+
return undefined;
|
|
1729
|
+
}
|
|
1730
|
+
if (!/^[A-Za-z0-9+/]+={0,2}$/.test(encoded) || encoded.length % 4 === 1) {
|
|
1731
|
+
throw new Error(`${settingName} must contain valid base64`);
|
|
1732
|
+
}
|
|
1733
|
+
const decoded = Buffer.from(encoded, "base64");
|
|
1734
|
+
const canonical = decoded.toString("base64").replace(/=+$/, "");
|
|
1735
|
+
if (decoded.length === 0 || canonical !== encoded.replace(/=+$/, "")) {
|
|
1736
|
+
throw new Error(`${settingName} must contain valid base64`);
|
|
1737
|
+
}
|
|
1738
|
+
return new Uint8Array(decoded);
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1576
1741
|
/**
|
|
1577
1742
|
* The connection `search_path` for OpenGeni's db handles + the managed-auth pool
|
|
1578
1743
|
* (Step I, §7.8 runtime half). Returns `undefined` when `dbSchema` is unset
|
|
@@ -1580,7 +1745,7 @@ export function environmentsEncryptionKeyBytes(settings: Settings): Uint8Array |
|
|
|
1580
1745
|
* default (`public`) applies — byte-for-byte today's behavior. When `dbSchema`
|
|
1581
1746
|
* is set (embedded), returns `"<schema>,opengeni_private,public"` — `public`
|
|
1582
1747
|
* stays LAST so `gen_random_uuid()` (pgcrypto) and the `vector` type still
|
|
1583
|
-
* resolve (the
|
|
1748
|
+
* resolve (the schema-isolation contract live footgun). `opengeni_private` is on the path so the
|
|
1584
1749
|
* RLS GUC-reader helpers resolve when referenced unqualified.
|
|
1585
1750
|
*/
|
|
1586
1751
|
export function dbSearchPath(settings: Pick<Settings, "dbSchema">): string | undefined {
|
|
@@ -1683,6 +1848,9 @@ export function stableSandboxEnvironmentForRun(
|
|
|
1683
1848
|
}
|
|
1684
1849
|
if (settings.toolspaceEnabled) {
|
|
1685
1850
|
environment.OPENGENI_TOOLSPACE_TOKEN_FILE ??= `${environment.HOME ?? descriptor.workspaceRoot}/.opengeni/toolspace-token`;
|
|
1851
|
+
if (settings.ogtoolPackageSpec) {
|
|
1852
|
+
environment.OPENGENI_OGTOOL_PACKAGE_SPEC ??= settings.ogtoolPackageSpec;
|
|
1853
|
+
}
|
|
1686
1854
|
if (options.workspaceId) {
|
|
1687
1855
|
environment.OPENGENI_TOOLSPACE_URL ??= firstPartyMcpWorkspaceUrl(
|
|
1688
1856
|
settings,
|
|
@@ -2196,6 +2364,7 @@ function firstPartyDocumentsMcpServerUrl(mcpUrl: string): string {
|
|
|
2196
2364
|
}
|
|
2197
2365
|
|
|
2198
2366
|
function validateSettings(settings: Settings): void {
|
|
2367
|
+
temporalConnectionOptions(settings);
|
|
2199
2368
|
if (settings.toolspaceEnabled && !settings.delegationSecret) {
|
|
2200
2369
|
throw new Error("OPENGENI_DELEGATION_SECRET is required when OPENGENI_TOOLSPACE_ENABLED=true");
|
|
2201
2370
|
}
|
|
@@ -2507,7 +2676,7 @@ function validateSettings(settings: Settings): void {
|
|
|
2507
2676
|
);
|
|
2508
2677
|
}
|
|
2509
2678
|
}
|
|
2510
|
-
// --- stream-token secret: required-when-desktop, but GRACEFULLY DEGRADE (
|
|
2679
|
+
// --- stream-token secret: required-when-desktop, but GRACEFULLY DEGRADE (stream-token availability contract) ---
|
|
2511
2680
|
// The desktop pixel plane needs an HMAC secret to mint scoped stream tokens.
|
|
2512
2681
|
// It is REQUIRED when desktop is enabled — but per OD-8 a missing secret is NOT
|
|
2513
2682
|
// a hard boot-fail: we emit a LOUD warning and the deployment ships with
|
|
@@ -2554,7 +2723,7 @@ function validateSettings(settings: Settings): void {
|
|
|
2554
2723
|
}
|
|
2555
2724
|
|
|
2556
2725
|
/**
|
|
2557
|
-
* Resolve the secret used to sign/verify scoped stream tokens (
|
|
2726
|
+
* Resolve the secret used to sign/verify scoped stream tokens (sandbox contract
|
|
2558
2727
|
* §C.3). Falls back to `delegationSecret` (the same HMAC envelope family —
|
|
2559
2728
|
* `ogs_` vs `ogd_` prefix) so a deployment that already carries a delegation
|
|
2560
2729
|
* secret does not need a second one. Returns undefined when neither is set,
|
|
@@ -2571,7 +2740,7 @@ export function resolveStreamTokenSecret(settings: Settings): string | undefined
|
|
|
2571
2740
|
|
|
2572
2741
|
/**
|
|
2573
2742
|
* True iff the desktop pixel plane must GRACEFULLY DEGRADE because desktop is
|
|
2574
|
-
* enabled but no stream-token secret is resolvable (
|
|
2743
|
+
* enabled but no stream-token secret is resolvable (stream-token availability contract). When true,
|
|
2575
2744
|
* negotiateCapabilities forces DesktopStream.transport:null.
|
|
2576
2745
|
*/
|
|
2577
2746
|
export function streamTokenDegraded(settings: Settings): boolean {
|
|
@@ -2580,7 +2749,7 @@ export function streamTokenDegraded(settings: Settings): boolean {
|
|
|
2580
2749
|
|
|
2581
2750
|
/**
|
|
2582
2751
|
* Resolve the secret the control plane signs the enrollment bearer credential
|
|
2583
|
-
* with (the `oge_` envelope the agent presents back — M5
|
|
2752
|
+
* with (the `oge_` envelope the agent presents back — M5). Falls
|
|
2584
2753
|
* back to `delegationSecret` (the same HMAC envelope family) so a deployment that
|
|
2585
2754
|
* already carries a delegation secret needs no second one. Returns undefined when
|
|
2586
2755
|
* neither is set; when selfhosted is enabled but this is undefined, the poll route
|
|
@@ -2598,7 +2767,7 @@ export function resolveEnrollmentSigningSecret(settings: Settings): string | und
|
|
|
2598
2767
|
|
|
2599
2768
|
/**
|
|
2600
2769
|
* Resolve the HMAC secret the control plane signs the agent's relay PRODUCER token
|
|
2601
|
-
* with (the `ogr_` envelope; M8b
|
|
2770
|
+
* with (the `ogr_` envelope; M8b). The RELAY verifies the producer
|
|
2602
2771
|
* token with the SAME secret (injected into the relay via env). Prefers an explicit
|
|
2603
2772
|
* `selfhostedRelayTokenSecret`, then the `streamTokenSecret` (the relay already
|
|
2604
2773
|
* needs that one to verify the viewer's `ogs_` token, so a single secret can back
|