@opengeni/config 0.2.5 → 0.4.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/LICENSE +190 -0
- package/dist/index.d.ts +59 -3
- package/dist/index.js +127 -8
- package/dist/index.js.map +1 -1
- package/package.json +4 -7
- package/src/index.ts +129 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/config",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
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": {
|
|
@@ -32,15 +32,12 @@
|
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
34
|
"typecheck": "tsc --noEmit",
|
|
35
|
-
"build": "tsup"
|
|
35
|
+
"build": "tsup",
|
|
36
|
+
"prepublishOnly": "bash ../../scripts/prepublish-guard"
|
|
36
37
|
},
|
|
37
38
|
"dependencies": {
|
|
38
39
|
"@opengeni/codex": "^0.2.1",
|
|
39
|
-
"@opengeni/contracts": "^0.
|
|
40
|
+
"@opengeni/contracts": "^0.9.0",
|
|
40
41
|
"zod": "^4.2.1"
|
|
41
|
-
},
|
|
42
|
-
"devDependencies": {
|
|
43
|
-
"tsup": "^8.5.0",
|
|
44
|
-
"typescript": "^6.0.3"
|
|
45
42
|
}
|
|
46
43
|
}
|
package/src/index.ts
CHANGED
|
@@ -101,6 +101,16 @@ export const DEFAULT_AGENT_INSTRUCTIONS = [
|
|
|
101
101
|
AGENT_INSTRUCTIONS_CORE_PLACEHOLDER,
|
|
102
102
|
].join(" ");
|
|
103
103
|
|
|
104
|
+
export const McpServerConnectionRefSchema = z.object({
|
|
105
|
+
connectionId: z.string().uuid().optional(),
|
|
106
|
+
providerDomain: z.string().min(1),
|
|
107
|
+
kind: z.enum(["oauth2", "api_key", "app_install", "delegated"]).optional(),
|
|
108
|
+
scopes: z.array(z.string().min(1)).optional(),
|
|
109
|
+
resource: z.string().min(1).optional(),
|
|
110
|
+
subjectScope: z.enum(["workspace", "subject"]).optional(),
|
|
111
|
+
}).strict();
|
|
112
|
+
export type McpServerConnectionRef = z.infer<typeof McpServerConnectionRefSchema>;
|
|
113
|
+
|
|
104
114
|
const SettingsSchema = z.object({
|
|
105
115
|
serviceName: z.string().default("opengeni"),
|
|
106
116
|
environment: z.string().default("local"),
|
|
@@ -152,7 +162,13 @@ const SettingsSchema = z.object({
|
|
|
152
162
|
// holder of stream:control gets 403 until this flips. Keeps stream:control a
|
|
153
163
|
// declared-but-inert permission so later hardening is a flag flip.
|
|
154
164
|
streamControlEnabled: EnvBoolean.default(false),
|
|
165
|
+
toolspaceEnabled: EnvBoolean.default(false),
|
|
166
|
+
toolspaceMaxCallsPerTurn: z.coerce.number().int().positive().default(200),
|
|
155
167
|
environmentsEncryptionKey: z.string().optional(),
|
|
168
|
+
integrationsEnabled: EnvBoolean.default(false),
|
|
169
|
+
integrationsStateSecret: z.string().optional(),
|
|
170
|
+
integrationsAllowPrivateNetworkTargets: EnvBoolean.default(false),
|
|
171
|
+
integrationsOauthClientsJson: z.string().default("{}"),
|
|
156
172
|
// Session goal guard rails. Goals are designed for runs that legitimately
|
|
157
173
|
// span days, so length is bounded by pathology detection (no-progress
|
|
158
174
|
// streaks, budget exhaustion), never by count. goalMaxAutoContinuations is
|
|
@@ -189,15 +205,26 @@ const SettingsSchema = z.object({
|
|
|
189
205
|
// derived from these settings on the server path, and use the same numbers to
|
|
190
206
|
// budget the client path.
|
|
191
207
|
contextWindowTokens: z.coerce.number().int().positive().default(1_050_000),
|
|
208
|
+
// Proactive compaction threshold as a ratio of the model context window.
|
|
209
|
+
// Defaults to 60% and is clamped to [0.3, 0.9] so deployments can tune the
|
|
210
|
+
// trigger without accidentally disabling compaction or waiting until the
|
|
211
|
+
// provider is already at the cliff.
|
|
212
|
+
contextCompactionThresholdRatio: z.coerce.number().default(0.6).transform((value) => {
|
|
213
|
+
if (!Number.isFinite(value)) {
|
|
214
|
+
return 0.6;
|
|
215
|
+
}
|
|
216
|
+
return Math.min(0.9, Math.max(0.3, value));
|
|
217
|
+
}),
|
|
192
218
|
// Tokens reserved for model output; subtracted from the window to get the
|
|
193
219
|
// usable input budget B = contextWindowTokens - contextReservedOutputTokens.
|
|
194
220
|
contextReservedOutputTokens: z.coerce.number().int().nonnegative().default(128_000),
|
|
195
221
|
// Server path only: explicit compact_threshold (tokens) handed to the SDK's
|
|
196
|
-
// StaticCompactionPolicy. Defaults to floor(
|
|
197
|
-
// when unset.
|
|
222
|
+
// StaticCompactionPolicy. Defaults to floor(contextWindowTokens *
|
|
223
|
+
// contextCompactionThresholdRatio) when unset.
|
|
198
224
|
contextServerCompactThresholdTokens: z.coerce.number().int().positive().optional(),
|
|
199
|
-
//
|
|
200
|
-
//
|
|
225
|
+
// Deprecated back-compat knobs. The threshold is now controlled by
|
|
226
|
+
// contextCompactionThresholdRatio; these remain parsed so older deployments do
|
|
227
|
+
// not fail boot when their env still contains them.
|
|
201
228
|
contextCompactSoftFraction: z.coerce.number().positive().max(1).default(0.70),
|
|
202
229
|
contextCompactHardFraction: z.coerce.number().positive().max(1).default(0.85),
|
|
203
230
|
// Deprecated for the client path; parsed for env/back-compat only.
|
|
@@ -290,6 +317,15 @@ const SettingsSchema = z.object({
|
|
|
290
317
|
dockerNetwork: z.string().optional(),
|
|
291
318
|
modalAppName: z.string().default("opengeni-sandbox"),
|
|
292
319
|
modalImageRef: z.string().optional(),
|
|
320
|
+
// Name of a Modal Secret (containing REGISTRY_USERNAME + REGISTRY_PASSWORD) used
|
|
321
|
+
// to authenticate the pull of `modalImageRef` from a PRIVATE registry. When UNSET
|
|
322
|
+
// (the default), the sandbox image is pulled UNAUTHENTICATED — i.e. it must be a
|
|
323
|
+
// PUBLIC registry tag, which is the only shape the Agents-extension Modal backend
|
|
324
|
+
// supports out of the box (`Image.fromRegistry(tag)` with no secret). Set this to
|
|
325
|
+
// run a private image (e.g. a cloud-hosted ACR/ECR/GCR digest): the runtime resolves
|
|
326
|
+
// the named Secret and builds the image via `fromRegistry(tag, secret)` before the
|
|
327
|
+
// first sandbox is created. Knob: OPENGENI_MODAL_IMAGE_REGISTRY_SECRET.
|
|
328
|
+
modalImageRegistrySecret: z.string().optional(),
|
|
293
329
|
// Modal's hard sandbox lifetime (timeoutMs = this * 1000), counted from each
|
|
294
330
|
// create/resume — it is the BACKSTOP that reclaims a box if the reaper/worker is
|
|
295
331
|
// down, NOT the warm-window controller (that's sandboxIdleGraceMs). It must
|
|
@@ -584,6 +620,14 @@ const SettingsSchema = z.object({
|
|
|
584
620
|
allowedTools: z.array(z.string().min(1)).optional(),
|
|
585
621
|
timeoutMs: z.number().int().positive().optional(),
|
|
586
622
|
cacheToolsList: z.boolean().default(false),
|
|
623
|
+
/**
|
|
624
|
+
* Human-approval policy for this server's tools, overlaid per-run from a
|
|
625
|
+
* session MCP server row (never from OPENGENI_MCP_SERVERS). `true` = all
|
|
626
|
+
* tools require approval; a string[] = only the listed UNPREFIXED tool
|
|
627
|
+
* names do; absent = auto-run (the historical default). Enforced in the
|
|
628
|
+
* runtime by attaching `needsApproval` to the matching MCP tools.
|
|
629
|
+
*/
|
|
630
|
+
requireApproval: z.union([z.boolean(), z.array(z.string().min(1))]).optional(),
|
|
587
631
|
/**
|
|
588
632
|
* Extra request headers sent to this MCP server (credential injection
|
|
589
633
|
* for workspace-enabled capability MCPs). Populated at runtime from
|
|
@@ -591,6 +635,7 @@ const SettingsSchema = z.object({
|
|
|
591
635
|
* OPENGENI_MCP_SERVERS.
|
|
592
636
|
*/
|
|
593
637
|
headers: z.record(z.string(), z.string()).optional(),
|
|
638
|
+
connectionRef: McpServerConnectionRefSchema.optional(),
|
|
594
639
|
})).default([]),
|
|
595
640
|
});
|
|
596
641
|
|
|
@@ -663,6 +708,13 @@ const RegistryProviderSchema = z.object({
|
|
|
663
708
|
});
|
|
664
709
|
export type RegistryProvider = z.infer<typeof RegistryProviderSchema>;
|
|
665
710
|
|
|
711
|
+
export const IntegrationOAuthClientConfigSchema = z.object({
|
|
712
|
+
clientId: z.string().min(1),
|
|
713
|
+
clientSecret: z.string().min(1).optional(),
|
|
714
|
+
tokenEndpointAuthMethod: z.enum(["none", "client_secret_post", "client_secret_basic"]).default("none"),
|
|
715
|
+
});
|
|
716
|
+
export type IntegrationOAuthClientConfig = z.infer<typeof IntegrationOAuthClientConfigSchema>;
|
|
717
|
+
|
|
666
718
|
/**
|
|
667
719
|
* Runtime-resolved provider (built-in or registry), client-construction-ready.
|
|
668
720
|
* The built-in OpenAI/Azure provider is always present and always "responses";
|
|
@@ -864,13 +916,20 @@ export function getSettings(): Settings {
|
|
|
864
916
|
delegationSecret: optional("OPENGENI_DELEGATION_SECRET"),
|
|
865
917
|
streamTokenSecret: optional("OPENGENI_STREAM_TOKEN_SECRET"),
|
|
866
918
|
streamControlEnabled: optional("OPENGENI_STREAM_CONTROL_ENABLED"),
|
|
919
|
+
toolspaceEnabled: optional("OPENGENI_TOOLSPACE_ENABLED"),
|
|
920
|
+
toolspaceMaxCallsPerTurn: optional("OPENGENI_TOOLSPACE_MAX_CALLS_PER_TURN"),
|
|
867
921
|
environmentsEncryptionKey: optional("OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY"),
|
|
922
|
+
integrationsEnabled: optional("OPENGENI_INTEGRATIONS_ENABLED"),
|
|
923
|
+
integrationsStateSecret: optional("OPENGENI_INTEGRATIONS_STATE_SECRET"),
|
|
924
|
+
integrationsAllowPrivateNetworkTargets: optional("OPENGENI_INTEGRATIONS_ALLOW_PRIVATE_NETWORK_TARGETS"),
|
|
925
|
+
integrationsOauthClientsJson: optional("OPENGENI_INTEGRATIONS_OAUTH_CLIENTS_JSON"),
|
|
868
926
|
goalMaxAutoContinuations: optional("OPENGENI_GOAL_MAX_AUTO_CONTINUATIONS"),
|
|
869
927
|
goalNoProgressLimit: optional("OPENGENI_GOAL_NO_PROGRESS_LIMIT"),
|
|
870
928
|
agentMaxModelCallsPerTurn: optional("OPENGENI_AGENT_MAX_MODEL_CALLS_PER_TURN"),
|
|
871
929
|
sessionHistorySource: optional("OPENGENI_SESSION_HISTORY_SOURCE"),
|
|
872
930
|
contextCompactionMode: optional("OPENGENI_CONTEXT_COMPACTION_MODE"),
|
|
873
931
|
contextWindowTokens: optional("OPENGENI_CONTEXT_WINDOW_TOKENS"),
|
|
932
|
+
contextCompactionThresholdRatio: optional("OPENGENI_COMPACTION_THRESHOLD_RATIO"),
|
|
874
933
|
contextReservedOutputTokens: optional("OPENGENI_CONTEXT_RESERVED_OUTPUT_TOKENS"),
|
|
875
934
|
contextServerCompactThresholdTokens: optional("OPENGENI_CONTEXT_SERVER_COMPACT_THRESHOLD_TOKENS"),
|
|
876
935
|
contextCompactSoftFraction: optional("OPENGENI_CONTEXT_COMPACT_SOFT_FRACTION"),
|
|
@@ -918,6 +977,7 @@ export function getSettings(): Settings {
|
|
|
918
977
|
dockerNetwork: optional("OPENGENI_DOCKER_NETWORK"),
|
|
919
978
|
modalAppName: optional("OPENGENI_MODAL_APP_NAME"),
|
|
920
979
|
modalImageRef: optional("OPENGENI_MODAL_IMAGE_REF"),
|
|
980
|
+
modalImageRegistrySecret: optional("OPENGENI_MODAL_IMAGE_REGISTRY_SECRET"),
|
|
921
981
|
modalTimeoutSeconds: optional("OPENGENI_MODAL_TIMEOUT_SECONDS"),
|
|
922
982
|
modalTokenId: optional("OPENGENI_MODAL_TOKEN_ID"),
|
|
923
983
|
modalTokenSecret: optional("OPENGENI_MODAL_TOKEN_SECRET"),
|
|
@@ -1310,11 +1370,11 @@ export function contextInputBudgetTokens(settings: Pick<Settings, "contextWindow
|
|
|
1310
1370
|
* floor(B * softFraction). This is what sidesteps the SDK's wrong 240k
|
|
1311
1371
|
* fallback for gpt-5.5 (which is absent from its hardcoded window map).
|
|
1312
1372
|
*/
|
|
1313
|
-
export function contextServerCompactThreshold(settings: Pick<Settings, "contextWindowTokens" | "contextReservedOutputTokens" | "contextServerCompactThresholdTokens" | "contextCompactSoftFraction">): number {
|
|
1373
|
+
export function contextServerCompactThreshold(settings: Pick<Settings, "contextWindowTokens" | "contextReservedOutputTokens" | "contextServerCompactThresholdTokens" | "contextCompactSoftFraction" | "contextCompactionThresholdRatio">): number {
|
|
1314
1374
|
if (settings.contextServerCompactThresholdTokens) {
|
|
1315
1375
|
return settings.contextServerCompactThresholdTokens;
|
|
1316
1376
|
}
|
|
1317
|
-
return Math.floor(
|
|
1377
|
+
return Math.floor(settings.contextWindowTokens * settings.contextCompactionThresholdRatio);
|
|
1318
1378
|
}
|
|
1319
1379
|
|
|
1320
1380
|
export function configuredStaticUsageLimits(settings: Settings): StaticUsageLimitsConfig {
|
|
@@ -1434,6 +1494,7 @@ export function collectGitIdentityEnvironment(settings: Settings): Record<string
|
|
|
1434
1494
|
export function stableSandboxEnvironmentForRun(
|
|
1435
1495
|
settings: Settings,
|
|
1436
1496
|
workspaceEnvironment: Record<string, string> = {},
|
|
1497
|
+
options: { workspaceId?: string } = {},
|
|
1437
1498
|
): Record<string, string> {
|
|
1438
1499
|
const environment: Record<string, string> = {
|
|
1439
1500
|
...collectSandboxEnvironment(settings),
|
|
@@ -1455,6 +1516,12 @@ export function stableSandboxEnvironmentForRun(
|
|
|
1455
1516
|
// VALUE lives exclusively in the file (agent-managed, refreshable mid-turn), never
|
|
1456
1517
|
// the manifest env.
|
|
1457
1518
|
environment.OPENGENI_GIT_TOKEN_FILE ??= `${environment.HOME ?? descriptor.workspaceRoot}/.opengeni/git-token`;
|
|
1519
|
+
if (settings.toolspaceEnabled) {
|
|
1520
|
+
environment.OPENGENI_TOOLSPACE_TOKEN_FILE ??= `${environment.HOME ?? descriptor.workspaceRoot}/.opengeni/toolspace-token`;
|
|
1521
|
+
if (options.workspaceId) {
|
|
1522
|
+
environment.OPENGENI_TOOLSPACE_URL ??= firstPartyMcpWorkspaceUrl(settings, options.workspaceId);
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1458
1525
|
return environment;
|
|
1459
1526
|
}
|
|
1460
1527
|
|
|
@@ -1699,6 +1766,34 @@ export function parseModelProvidersJson(raw: string): RegistryProvider[] {
|
|
|
1699
1766
|
});
|
|
1700
1767
|
}
|
|
1701
1768
|
|
|
1769
|
+
export function parseIntegrationsOauthClientsJson(raw: string | undefined): Record<string, IntegrationOAuthClientConfig> {
|
|
1770
|
+
if (!raw?.trim() || raw.trim() === "{}") {
|
|
1771
|
+
return {};
|
|
1772
|
+
}
|
|
1773
|
+
let parsed: unknown;
|
|
1774
|
+
try {
|
|
1775
|
+
parsed = JSON.parse(raw);
|
|
1776
|
+
} catch (error) {
|
|
1777
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1778
|
+
throw new Error(`OPENGENI_INTEGRATIONS_OAUTH_CLIENTS_JSON must be valid JSON: ${message}`);
|
|
1779
|
+
}
|
|
1780
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1781
|
+
throw new Error("OPENGENI_INTEGRATIONS_OAUTH_CLIENTS_JSON must be a JSON object keyed by authorization-server issuer or URL");
|
|
1782
|
+
}
|
|
1783
|
+
const out: Record<string, IntegrationOAuthClientConfig> = {};
|
|
1784
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
1785
|
+
if (!key.trim()) {
|
|
1786
|
+
throw new Error("OPENGENI_INTEGRATIONS_OAUTH_CLIENTS_JSON contains an empty issuer key");
|
|
1787
|
+
}
|
|
1788
|
+
const result = IntegrationOAuthClientConfigSchema.safeParse(value);
|
|
1789
|
+
if (!result.success) {
|
|
1790
|
+
throw new Error(`OPENGENI_INTEGRATIONS_OAUTH_CLIENTS_JSON client for ${key} is invalid: ${result.error.message}`);
|
|
1791
|
+
}
|
|
1792
|
+
out[key] = result.data;
|
|
1793
|
+
}
|
|
1794
|
+
return out;
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1702
1797
|
export function parseStaticUsageLimitsJson(raw: string): StaticUsageLimitsConfig {
|
|
1703
1798
|
if (!raw.trim() || raw.trim() === "{}") {
|
|
1704
1799
|
return {};
|
|
@@ -1792,7 +1887,7 @@ function ensureBuiltInMcpServers(settings: Settings): Settings["mcpServers"] {
|
|
|
1792
1887
|
id: "docs",
|
|
1793
1888
|
name: "Document Search",
|
|
1794
1889
|
url: firstPartyDocsMcpUrl,
|
|
1795
|
-
allowedTools: ["search_documents", "fetch_document_chunk", "list_document_bases"],
|
|
1890
|
+
allowedTools: ["search_documents", "fetch_document_chunk", "list_document_bases", "knowledge_search", "knowledge_fetch", "memory_search", "memory_propose"],
|
|
1796
1891
|
cacheToolsList: false,
|
|
1797
1892
|
}]),
|
|
1798
1893
|
...existing,
|
|
@@ -1824,6 +1919,18 @@ export function firstPartyMcpBaseUrl(settings: Settings): string {
|
|
|
1824
1919
|
return settings.opengeniMcpUrl ?? `http://127.0.0.1:${settings.apiPort}/v1/workspaces/{workspaceId}/mcp`;
|
|
1825
1920
|
}
|
|
1826
1921
|
|
|
1922
|
+
export function firstPartyMcpWorkspaceUrl(settings: Settings, workspaceId: string): string {
|
|
1923
|
+
const raw = firstPartyMcpBaseUrl(settings);
|
|
1924
|
+
if (raw.includes("{workspaceId}")) {
|
|
1925
|
+
return raw.replaceAll("{workspaceId}", workspaceId);
|
|
1926
|
+
}
|
|
1927
|
+
const url = new URL(raw);
|
|
1928
|
+
url.pathname = `/v1/workspaces/${workspaceId}/mcp`;
|
|
1929
|
+
url.search = "";
|
|
1930
|
+
url.hash = "";
|
|
1931
|
+
return url.toString();
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1827
1934
|
function firstPartyMcpServerUrl(settings: Settings): string {
|
|
1828
1935
|
return firstPartyMcpBaseUrl(settings);
|
|
1829
1936
|
}
|
|
@@ -1833,6 +1940,9 @@ function firstPartyDocumentsMcpServerUrl(mcpUrl: string): string {
|
|
|
1833
1940
|
}
|
|
1834
1941
|
|
|
1835
1942
|
function validateSettings(settings: Settings): void {
|
|
1943
|
+
if (settings.toolspaceEnabled && !settings.delegationSecret) {
|
|
1944
|
+
throw new Error("OPENGENI_DELEGATION_SECRET is required when OPENGENI_TOOLSPACE_ENABLED=true");
|
|
1945
|
+
}
|
|
1836
1946
|
if (settings.productAccessMode === "managed") {
|
|
1837
1947
|
if (!settings.publicBaseUrl) {
|
|
1838
1948
|
throw new Error("OPENGENI_PUBLIC_BASE_URL is required when OPENGENI_PRODUCT_ACCESS_MODE=managed");
|
|
@@ -1851,6 +1961,18 @@ function validateSettings(settings: Settings): void {
|
|
|
1851
1961
|
}
|
|
1852
1962
|
}
|
|
1853
1963
|
environmentsEncryptionKeyBytes(settings);
|
|
1964
|
+
if (settings.integrationsEnabled) {
|
|
1965
|
+
if (settings.productAccessMode === "managed" && !settings.publicBaseUrl) {
|
|
1966
|
+
throw new Error("OPENGENI_PUBLIC_BASE_URL is required when OPENGENI_INTEGRATIONS_ENABLED=true and OPENGENI_PRODUCT_ACCESS_MODE=managed");
|
|
1967
|
+
}
|
|
1968
|
+
if (settings.publicBaseUrl && !settings.publicBaseUrl.startsWith("https://") && !["local", "test"].includes(settings.environment)) {
|
|
1969
|
+
throw new Error("OPENGENI_PUBLIC_BASE_URL must use https when OPENGENI_INTEGRATIONS_ENABLED=true outside local/test");
|
|
1970
|
+
}
|
|
1971
|
+
if (!settings.integrationsStateSecret && !["local", "test"].includes(settings.environment)) {
|
|
1972
|
+
throw new Error("OPENGENI_INTEGRATIONS_STATE_SECRET is required when OPENGENI_INTEGRATIONS_ENABLED=true outside local/test");
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
parseIntegrationsOauthClientsJson(settings.integrationsOauthClientsJson);
|
|
1854
1976
|
if (
|
|
1855
1977
|
settings.productAccessMode === "configured"
|
|
1856
1978
|
&& !["local", "test"].includes(settings.environment)
|