@opengeni/config 0.2.5 → 0.3.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 +58 -3
- package/dist/index.js +117 -8
- package/dist/index.js.map +1 -1
- package/package.json +4 -7
- package/src/index.ts +119 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/config",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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.
|
|
@@ -584,6 +611,14 @@ const SettingsSchema = z.object({
|
|
|
584
611
|
allowedTools: z.array(z.string().min(1)).optional(),
|
|
585
612
|
timeoutMs: z.number().int().positive().optional(),
|
|
586
613
|
cacheToolsList: z.boolean().default(false),
|
|
614
|
+
/**
|
|
615
|
+
* Human-approval policy for this server's tools, overlaid per-run from a
|
|
616
|
+
* session MCP server row (never from OPENGENI_MCP_SERVERS). `true` = all
|
|
617
|
+
* tools require approval; a string[] = only the listed UNPREFIXED tool
|
|
618
|
+
* names do; absent = auto-run (the historical default). Enforced in the
|
|
619
|
+
* runtime by attaching `needsApproval` to the matching MCP tools.
|
|
620
|
+
*/
|
|
621
|
+
requireApproval: z.union([z.boolean(), z.array(z.string().min(1))]).optional(),
|
|
587
622
|
/**
|
|
588
623
|
* Extra request headers sent to this MCP server (credential injection
|
|
589
624
|
* for workspace-enabled capability MCPs). Populated at runtime from
|
|
@@ -591,6 +626,7 @@ const SettingsSchema = z.object({
|
|
|
591
626
|
* OPENGENI_MCP_SERVERS.
|
|
592
627
|
*/
|
|
593
628
|
headers: z.record(z.string(), z.string()).optional(),
|
|
629
|
+
connectionRef: McpServerConnectionRefSchema.optional(),
|
|
594
630
|
})).default([]),
|
|
595
631
|
});
|
|
596
632
|
|
|
@@ -663,6 +699,13 @@ const RegistryProviderSchema = z.object({
|
|
|
663
699
|
});
|
|
664
700
|
export type RegistryProvider = z.infer<typeof RegistryProviderSchema>;
|
|
665
701
|
|
|
702
|
+
export const IntegrationOAuthClientConfigSchema = z.object({
|
|
703
|
+
clientId: z.string().min(1),
|
|
704
|
+
clientSecret: z.string().min(1).optional(),
|
|
705
|
+
tokenEndpointAuthMethod: z.enum(["none", "client_secret_post", "client_secret_basic"]).default("none"),
|
|
706
|
+
});
|
|
707
|
+
export type IntegrationOAuthClientConfig = z.infer<typeof IntegrationOAuthClientConfigSchema>;
|
|
708
|
+
|
|
666
709
|
/**
|
|
667
710
|
* Runtime-resolved provider (built-in or registry), client-construction-ready.
|
|
668
711
|
* The built-in OpenAI/Azure provider is always present and always "responses";
|
|
@@ -864,13 +907,20 @@ export function getSettings(): Settings {
|
|
|
864
907
|
delegationSecret: optional("OPENGENI_DELEGATION_SECRET"),
|
|
865
908
|
streamTokenSecret: optional("OPENGENI_STREAM_TOKEN_SECRET"),
|
|
866
909
|
streamControlEnabled: optional("OPENGENI_STREAM_CONTROL_ENABLED"),
|
|
910
|
+
toolspaceEnabled: optional("OPENGENI_TOOLSPACE_ENABLED"),
|
|
911
|
+
toolspaceMaxCallsPerTurn: optional("OPENGENI_TOOLSPACE_MAX_CALLS_PER_TURN"),
|
|
867
912
|
environmentsEncryptionKey: optional("OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY"),
|
|
913
|
+
integrationsEnabled: optional("OPENGENI_INTEGRATIONS_ENABLED"),
|
|
914
|
+
integrationsStateSecret: optional("OPENGENI_INTEGRATIONS_STATE_SECRET"),
|
|
915
|
+
integrationsAllowPrivateNetworkTargets: optional("OPENGENI_INTEGRATIONS_ALLOW_PRIVATE_NETWORK_TARGETS"),
|
|
916
|
+
integrationsOauthClientsJson: optional("OPENGENI_INTEGRATIONS_OAUTH_CLIENTS_JSON"),
|
|
868
917
|
goalMaxAutoContinuations: optional("OPENGENI_GOAL_MAX_AUTO_CONTINUATIONS"),
|
|
869
918
|
goalNoProgressLimit: optional("OPENGENI_GOAL_NO_PROGRESS_LIMIT"),
|
|
870
919
|
agentMaxModelCallsPerTurn: optional("OPENGENI_AGENT_MAX_MODEL_CALLS_PER_TURN"),
|
|
871
920
|
sessionHistorySource: optional("OPENGENI_SESSION_HISTORY_SOURCE"),
|
|
872
921
|
contextCompactionMode: optional("OPENGENI_CONTEXT_COMPACTION_MODE"),
|
|
873
922
|
contextWindowTokens: optional("OPENGENI_CONTEXT_WINDOW_TOKENS"),
|
|
923
|
+
contextCompactionThresholdRatio: optional("OPENGENI_COMPACTION_THRESHOLD_RATIO"),
|
|
874
924
|
contextReservedOutputTokens: optional("OPENGENI_CONTEXT_RESERVED_OUTPUT_TOKENS"),
|
|
875
925
|
contextServerCompactThresholdTokens: optional("OPENGENI_CONTEXT_SERVER_COMPACT_THRESHOLD_TOKENS"),
|
|
876
926
|
contextCompactSoftFraction: optional("OPENGENI_CONTEXT_COMPACT_SOFT_FRACTION"),
|
|
@@ -1310,11 +1360,11 @@ export function contextInputBudgetTokens(settings: Pick<Settings, "contextWindow
|
|
|
1310
1360
|
* floor(B * softFraction). This is what sidesteps the SDK's wrong 240k
|
|
1311
1361
|
* fallback for gpt-5.5 (which is absent from its hardcoded window map).
|
|
1312
1362
|
*/
|
|
1313
|
-
export function contextServerCompactThreshold(settings: Pick<Settings, "contextWindowTokens" | "contextReservedOutputTokens" | "contextServerCompactThresholdTokens" | "contextCompactSoftFraction">): number {
|
|
1363
|
+
export function contextServerCompactThreshold(settings: Pick<Settings, "contextWindowTokens" | "contextReservedOutputTokens" | "contextServerCompactThresholdTokens" | "contextCompactSoftFraction" | "contextCompactionThresholdRatio">): number {
|
|
1314
1364
|
if (settings.contextServerCompactThresholdTokens) {
|
|
1315
1365
|
return settings.contextServerCompactThresholdTokens;
|
|
1316
1366
|
}
|
|
1317
|
-
return Math.floor(
|
|
1367
|
+
return Math.floor(settings.contextWindowTokens * settings.contextCompactionThresholdRatio);
|
|
1318
1368
|
}
|
|
1319
1369
|
|
|
1320
1370
|
export function configuredStaticUsageLimits(settings: Settings): StaticUsageLimitsConfig {
|
|
@@ -1434,6 +1484,7 @@ export function collectGitIdentityEnvironment(settings: Settings): Record<string
|
|
|
1434
1484
|
export function stableSandboxEnvironmentForRun(
|
|
1435
1485
|
settings: Settings,
|
|
1436
1486
|
workspaceEnvironment: Record<string, string> = {},
|
|
1487
|
+
options: { workspaceId?: string } = {},
|
|
1437
1488
|
): Record<string, string> {
|
|
1438
1489
|
const environment: Record<string, string> = {
|
|
1439
1490
|
...collectSandboxEnvironment(settings),
|
|
@@ -1455,6 +1506,12 @@ export function stableSandboxEnvironmentForRun(
|
|
|
1455
1506
|
// VALUE lives exclusively in the file (agent-managed, refreshable mid-turn), never
|
|
1456
1507
|
// the manifest env.
|
|
1457
1508
|
environment.OPENGENI_GIT_TOKEN_FILE ??= `${environment.HOME ?? descriptor.workspaceRoot}/.opengeni/git-token`;
|
|
1509
|
+
if (settings.toolspaceEnabled) {
|
|
1510
|
+
environment.OPENGENI_TOOLSPACE_TOKEN_FILE ??= `${environment.HOME ?? descriptor.workspaceRoot}/.opengeni/toolspace-token`;
|
|
1511
|
+
if (options.workspaceId) {
|
|
1512
|
+
environment.OPENGENI_TOOLSPACE_URL ??= firstPartyMcpWorkspaceUrl(settings, options.workspaceId);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1458
1515
|
return environment;
|
|
1459
1516
|
}
|
|
1460
1517
|
|
|
@@ -1699,6 +1756,34 @@ export function parseModelProvidersJson(raw: string): RegistryProvider[] {
|
|
|
1699
1756
|
});
|
|
1700
1757
|
}
|
|
1701
1758
|
|
|
1759
|
+
export function parseIntegrationsOauthClientsJson(raw: string | undefined): Record<string, IntegrationOAuthClientConfig> {
|
|
1760
|
+
if (!raw?.trim() || raw.trim() === "{}") {
|
|
1761
|
+
return {};
|
|
1762
|
+
}
|
|
1763
|
+
let parsed: unknown;
|
|
1764
|
+
try {
|
|
1765
|
+
parsed = JSON.parse(raw);
|
|
1766
|
+
} catch (error) {
|
|
1767
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1768
|
+
throw new Error(`OPENGENI_INTEGRATIONS_OAUTH_CLIENTS_JSON must be valid JSON: ${message}`);
|
|
1769
|
+
}
|
|
1770
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1771
|
+
throw new Error("OPENGENI_INTEGRATIONS_OAUTH_CLIENTS_JSON must be a JSON object keyed by authorization-server issuer or URL");
|
|
1772
|
+
}
|
|
1773
|
+
const out: Record<string, IntegrationOAuthClientConfig> = {};
|
|
1774
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
1775
|
+
if (!key.trim()) {
|
|
1776
|
+
throw new Error("OPENGENI_INTEGRATIONS_OAUTH_CLIENTS_JSON contains an empty issuer key");
|
|
1777
|
+
}
|
|
1778
|
+
const result = IntegrationOAuthClientConfigSchema.safeParse(value);
|
|
1779
|
+
if (!result.success) {
|
|
1780
|
+
throw new Error(`OPENGENI_INTEGRATIONS_OAUTH_CLIENTS_JSON client for ${key} is invalid: ${result.error.message}`);
|
|
1781
|
+
}
|
|
1782
|
+
out[key] = result.data;
|
|
1783
|
+
}
|
|
1784
|
+
return out;
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1702
1787
|
export function parseStaticUsageLimitsJson(raw: string): StaticUsageLimitsConfig {
|
|
1703
1788
|
if (!raw.trim() || raw.trim() === "{}") {
|
|
1704
1789
|
return {};
|
|
@@ -1792,7 +1877,7 @@ function ensureBuiltInMcpServers(settings: Settings): Settings["mcpServers"] {
|
|
|
1792
1877
|
id: "docs",
|
|
1793
1878
|
name: "Document Search",
|
|
1794
1879
|
url: firstPartyDocsMcpUrl,
|
|
1795
|
-
allowedTools: ["search_documents", "fetch_document_chunk", "list_document_bases"],
|
|
1880
|
+
allowedTools: ["search_documents", "fetch_document_chunk", "list_document_bases", "knowledge_search", "knowledge_fetch", "memory_search", "memory_propose"],
|
|
1796
1881
|
cacheToolsList: false,
|
|
1797
1882
|
}]),
|
|
1798
1883
|
...existing,
|
|
@@ -1824,6 +1909,18 @@ export function firstPartyMcpBaseUrl(settings: Settings): string {
|
|
|
1824
1909
|
return settings.opengeniMcpUrl ?? `http://127.0.0.1:${settings.apiPort}/v1/workspaces/{workspaceId}/mcp`;
|
|
1825
1910
|
}
|
|
1826
1911
|
|
|
1912
|
+
export function firstPartyMcpWorkspaceUrl(settings: Settings, workspaceId: string): string {
|
|
1913
|
+
const raw = firstPartyMcpBaseUrl(settings);
|
|
1914
|
+
if (raw.includes("{workspaceId}")) {
|
|
1915
|
+
return raw.replaceAll("{workspaceId}", workspaceId);
|
|
1916
|
+
}
|
|
1917
|
+
const url = new URL(raw);
|
|
1918
|
+
url.pathname = `/v1/workspaces/${workspaceId}/mcp`;
|
|
1919
|
+
url.search = "";
|
|
1920
|
+
url.hash = "";
|
|
1921
|
+
return url.toString();
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1827
1924
|
function firstPartyMcpServerUrl(settings: Settings): string {
|
|
1828
1925
|
return firstPartyMcpBaseUrl(settings);
|
|
1829
1926
|
}
|
|
@@ -1833,6 +1930,9 @@ function firstPartyDocumentsMcpServerUrl(mcpUrl: string): string {
|
|
|
1833
1930
|
}
|
|
1834
1931
|
|
|
1835
1932
|
function validateSettings(settings: Settings): void {
|
|
1933
|
+
if (settings.toolspaceEnabled && !settings.delegationSecret) {
|
|
1934
|
+
throw new Error("OPENGENI_DELEGATION_SECRET is required when OPENGENI_TOOLSPACE_ENABLED=true");
|
|
1935
|
+
}
|
|
1836
1936
|
if (settings.productAccessMode === "managed") {
|
|
1837
1937
|
if (!settings.publicBaseUrl) {
|
|
1838
1938
|
throw new Error("OPENGENI_PUBLIC_BASE_URL is required when OPENGENI_PRODUCT_ACCESS_MODE=managed");
|
|
@@ -1851,6 +1951,18 @@ function validateSettings(settings: Settings): void {
|
|
|
1851
1951
|
}
|
|
1852
1952
|
}
|
|
1853
1953
|
environmentsEncryptionKeyBytes(settings);
|
|
1954
|
+
if (settings.integrationsEnabled) {
|
|
1955
|
+
if (settings.productAccessMode === "managed" && !settings.publicBaseUrl) {
|
|
1956
|
+
throw new Error("OPENGENI_PUBLIC_BASE_URL is required when OPENGENI_INTEGRATIONS_ENABLED=true and OPENGENI_PRODUCT_ACCESS_MODE=managed");
|
|
1957
|
+
}
|
|
1958
|
+
if (settings.publicBaseUrl && !settings.publicBaseUrl.startsWith("https://") && !["local", "test"].includes(settings.environment)) {
|
|
1959
|
+
throw new Error("OPENGENI_PUBLIC_BASE_URL must use https when OPENGENI_INTEGRATIONS_ENABLED=true outside local/test");
|
|
1960
|
+
}
|
|
1961
|
+
if (!settings.integrationsStateSecret && !["local", "test"].includes(settings.environment)) {
|
|
1962
|
+
throw new Error("OPENGENI_INTEGRATIONS_STATE_SECRET is required when OPENGENI_INTEGRATIONS_ENABLED=true outside local/test");
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
parseIntegrationsOauthClientsJson(settings.integrationsOauthClientsJson);
|
|
1854
1966
|
if (
|
|
1855
1967
|
settings.productAccessMode === "configured"
|
|
1856
1968
|
&& !["local", "test"].includes(settings.environment)
|