@opengeni/config 0.2.4 → 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 +60 -3
- package/dist/index.js +123 -8
- package/dist/index.js.map +1 -1
- package/package.json +4 -7
- package/src/index.ts +125 -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,10 +101,23 @@ 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"),
|
|
107
117
|
deploymentRevision: z.string().default("dev"),
|
|
118
|
+
// The release-train version baked into official images (OPENGENI_SERVER_VERSION).
|
|
119
|
+
// Absent on dev/source builds — consumers must treat it as optional.
|
|
120
|
+
serverVersion: z.string().optional(),
|
|
108
121
|
databaseUrl: z.string().default("postgres://opengeni:opengeni@127.0.0.1:5432/opengeni"),
|
|
109
122
|
// Step I (§7.8 runtime half). Dedicated Postgres schema for the EMBEDDED
|
|
110
123
|
// topology. Default "" → standalone: no search_path scoping, server default
|
|
@@ -149,7 +162,13 @@ const SettingsSchema = z.object({
|
|
|
149
162
|
// holder of stream:control gets 403 until this flips. Keeps stream:control a
|
|
150
163
|
// declared-but-inert permission so later hardening is a flag flip.
|
|
151
164
|
streamControlEnabled: EnvBoolean.default(false),
|
|
165
|
+
toolspaceEnabled: EnvBoolean.default(false),
|
|
166
|
+
toolspaceMaxCallsPerTurn: z.coerce.number().int().positive().default(200),
|
|
152
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("{}"),
|
|
153
172
|
// Session goal guard rails. Goals are designed for runs that legitimately
|
|
154
173
|
// span days, so length is bounded by pathology detection (no-progress
|
|
155
174
|
// streaks, budget exhaustion), never by count. goalMaxAutoContinuations is
|
|
@@ -186,15 +205,26 @@ const SettingsSchema = z.object({
|
|
|
186
205
|
// derived from these settings on the server path, and use the same numbers to
|
|
187
206
|
// budget the client path.
|
|
188
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
|
+
}),
|
|
189
218
|
// Tokens reserved for model output; subtracted from the window to get the
|
|
190
219
|
// usable input budget B = contextWindowTokens - contextReservedOutputTokens.
|
|
191
220
|
contextReservedOutputTokens: z.coerce.number().int().nonnegative().default(128_000),
|
|
192
221
|
// Server path only: explicit compact_threshold (tokens) handed to the SDK's
|
|
193
|
-
// StaticCompactionPolicy. Defaults to floor(
|
|
194
|
-
// when unset.
|
|
222
|
+
// StaticCompactionPolicy. Defaults to floor(contextWindowTokens *
|
|
223
|
+
// contextCompactionThresholdRatio) when unset.
|
|
195
224
|
contextServerCompactThresholdTokens: z.coerce.number().int().positive().optional(),
|
|
196
|
-
//
|
|
197
|
-
//
|
|
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.
|
|
198
228
|
contextCompactSoftFraction: z.coerce.number().positive().max(1).default(0.70),
|
|
199
229
|
contextCompactHardFraction: z.coerce.number().positive().max(1).default(0.85),
|
|
200
230
|
// Deprecated for the client path; parsed for env/back-compat only.
|
|
@@ -208,6 +238,7 @@ const SettingsSchema = z.object({
|
|
|
208
238
|
authAllowMetrics: EnvBoolean.default(false),
|
|
209
239
|
apiHost: z.string().default("0.0.0.0"),
|
|
210
240
|
apiPort: z.coerce.number().int().positive().default(8000),
|
|
241
|
+
workerHttpPort: z.coerce.number().int().positive().default(8001),
|
|
211
242
|
opengeniMcpUrl: z.string().url().optional(),
|
|
212
243
|
corsAllowOriginRegex: z.string().default(String.raw`^https?://(localhost|127\.0\.0\.1)(:\d+)?$`),
|
|
213
244
|
openaiProvider: z.enum(["openai", "azure"]).default("openai"),
|
|
@@ -580,6 +611,14 @@ const SettingsSchema = z.object({
|
|
|
580
611
|
allowedTools: z.array(z.string().min(1)).optional(),
|
|
581
612
|
timeoutMs: z.number().int().positive().optional(),
|
|
582
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(),
|
|
583
622
|
/**
|
|
584
623
|
* Extra request headers sent to this MCP server (credential injection
|
|
585
624
|
* for workspace-enabled capability MCPs). Populated at runtime from
|
|
@@ -587,6 +626,7 @@ const SettingsSchema = z.object({
|
|
|
587
626
|
* OPENGENI_MCP_SERVERS.
|
|
588
627
|
*/
|
|
589
628
|
headers: z.record(z.string(), z.string()).optional(),
|
|
629
|
+
connectionRef: McpServerConnectionRefSchema.optional(),
|
|
590
630
|
})).default([]),
|
|
591
631
|
});
|
|
592
632
|
|
|
@@ -659,6 +699,13 @@ const RegistryProviderSchema = z.object({
|
|
|
659
699
|
});
|
|
660
700
|
export type RegistryProvider = z.infer<typeof RegistryProviderSchema>;
|
|
661
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
|
+
|
|
662
709
|
/**
|
|
663
710
|
* Runtime-resolved provider (built-in or registry), client-construction-ready.
|
|
664
711
|
* The built-in OpenAI/Azure provider is always present and always "responses";
|
|
@@ -834,6 +881,7 @@ export function getSettings(): Settings {
|
|
|
834
881
|
serviceName: optional("OPENGENI_SERVICE_NAME"),
|
|
835
882
|
environment: optional("OPENGENI_ENVIRONMENT"),
|
|
836
883
|
deploymentRevision: optional("OPENGENI_DEPLOYMENT_REVISION") ?? optional("SOURCE_VERSION") ?? optional("GITHUB_SHA"),
|
|
884
|
+
serverVersion: optional("OPENGENI_SERVER_VERSION"),
|
|
837
885
|
databaseUrl: optional("OPENGENI_DATABASE_URL"),
|
|
838
886
|
dbSchema: optional("OPENGENI_DB_SCHEMA"),
|
|
839
887
|
rlsStrategy: optional("OPENGENI_RLS_STRATEGY"),
|
|
@@ -859,13 +907,20 @@ export function getSettings(): Settings {
|
|
|
859
907
|
delegationSecret: optional("OPENGENI_DELEGATION_SECRET"),
|
|
860
908
|
streamTokenSecret: optional("OPENGENI_STREAM_TOKEN_SECRET"),
|
|
861
909
|
streamControlEnabled: optional("OPENGENI_STREAM_CONTROL_ENABLED"),
|
|
910
|
+
toolspaceEnabled: optional("OPENGENI_TOOLSPACE_ENABLED"),
|
|
911
|
+
toolspaceMaxCallsPerTurn: optional("OPENGENI_TOOLSPACE_MAX_CALLS_PER_TURN"),
|
|
862
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"),
|
|
863
917
|
goalMaxAutoContinuations: optional("OPENGENI_GOAL_MAX_AUTO_CONTINUATIONS"),
|
|
864
918
|
goalNoProgressLimit: optional("OPENGENI_GOAL_NO_PROGRESS_LIMIT"),
|
|
865
919
|
agentMaxModelCallsPerTurn: optional("OPENGENI_AGENT_MAX_MODEL_CALLS_PER_TURN"),
|
|
866
920
|
sessionHistorySource: optional("OPENGENI_SESSION_HISTORY_SOURCE"),
|
|
867
921
|
contextCompactionMode: optional("OPENGENI_CONTEXT_COMPACTION_MODE"),
|
|
868
922
|
contextWindowTokens: optional("OPENGENI_CONTEXT_WINDOW_TOKENS"),
|
|
923
|
+
contextCompactionThresholdRatio: optional("OPENGENI_COMPACTION_THRESHOLD_RATIO"),
|
|
869
924
|
contextReservedOutputTokens: optional("OPENGENI_CONTEXT_RESERVED_OUTPUT_TOKENS"),
|
|
870
925
|
contextServerCompactThresholdTokens: optional("OPENGENI_CONTEXT_SERVER_COMPACT_THRESHOLD_TOKENS"),
|
|
871
926
|
contextCompactSoftFraction: optional("OPENGENI_CONTEXT_COMPACT_SOFT_FRACTION"),
|
|
@@ -878,6 +933,7 @@ export function getSettings(): Settings {
|
|
|
878
933
|
authAllowMetrics: optional("OPENGENI_AUTH_ALLOW_METRICS"),
|
|
879
934
|
apiHost: optional("OPENGENI_API_HOST"),
|
|
880
935
|
apiPort: optional("OPENGENI_API_PORT"),
|
|
936
|
+
workerHttpPort: optional("OPENGENI_WORKER_HTTP_PORT"),
|
|
881
937
|
opengeniMcpUrl: optional("OPENGENI_MCP_URL"),
|
|
882
938
|
corsAllowOriginRegex: optional("OPENGENI_CORS_ALLOW_ORIGIN_REGEX"),
|
|
883
939
|
openaiProvider: optional("OPENGENI_OPENAI_PROVIDER"),
|
|
@@ -1304,11 +1360,11 @@ export function contextInputBudgetTokens(settings: Pick<Settings, "contextWindow
|
|
|
1304
1360
|
* floor(B * softFraction). This is what sidesteps the SDK's wrong 240k
|
|
1305
1361
|
* fallback for gpt-5.5 (which is absent from its hardcoded window map).
|
|
1306
1362
|
*/
|
|
1307
|
-
export function contextServerCompactThreshold(settings: Pick<Settings, "contextWindowTokens" | "contextReservedOutputTokens" | "contextServerCompactThresholdTokens" | "contextCompactSoftFraction">): number {
|
|
1363
|
+
export function contextServerCompactThreshold(settings: Pick<Settings, "contextWindowTokens" | "contextReservedOutputTokens" | "contextServerCompactThresholdTokens" | "contextCompactSoftFraction" | "contextCompactionThresholdRatio">): number {
|
|
1308
1364
|
if (settings.contextServerCompactThresholdTokens) {
|
|
1309
1365
|
return settings.contextServerCompactThresholdTokens;
|
|
1310
1366
|
}
|
|
1311
|
-
return Math.floor(
|
|
1367
|
+
return Math.floor(settings.contextWindowTokens * settings.contextCompactionThresholdRatio);
|
|
1312
1368
|
}
|
|
1313
1369
|
|
|
1314
1370
|
export function configuredStaticUsageLimits(settings: Settings): StaticUsageLimitsConfig {
|
|
@@ -1428,6 +1484,7 @@ export function collectGitIdentityEnvironment(settings: Settings): Record<string
|
|
|
1428
1484
|
export function stableSandboxEnvironmentForRun(
|
|
1429
1485
|
settings: Settings,
|
|
1430
1486
|
workspaceEnvironment: Record<string, string> = {},
|
|
1487
|
+
options: { workspaceId?: string } = {},
|
|
1431
1488
|
): Record<string, string> {
|
|
1432
1489
|
const environment: Record<string, string> = {
|
|
1433
1490
|
...collectSandboxEnvironment(settings),
|
|
@@ -1449,6 +1506,12 @@ export function stableSandboxEnvironmentForRun(
|
|
|
1449
1506
|
// VALUE lives exclusively in the file (agent-managed, refreshable mid-turn), never
|
|
1450
1507
|
// the manifest env.
|
|
1451
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
|
+
}
|
|
1452
1515
|
return environment;
|
|
1453
1516
|
}
|
|
1454
1517
|
|
|
@@ -1693,6 +1756,34 @@ export function parseModelProvidersJson(raw: string): RegistryProvider[] {
|
|
|
1693
1756
|
});
|
|
1694
1757
|
}
|
|
1695
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
|
+
|
|
1696
1787
|
export function parseStaticUsageLimitsJson(raw: string): StaticUsageLimitsConfig {
|
|
1697
1788
|
if (!raw.trim() || raw.trim() === "{}") {
|
|
1698
1789
|
return {};
|
|
@@ -1786,7 +1877,7 @@ function ensureBuiltInMcpServers(settings: Settings): Settings["mcpServers"] {
|
|
|
1786
1877
|
id: "docs",
|
|
1787
1878
|
name: "Document Search",
|
|
1788
1879
|
url: firstPartyDocsMcpUrl,
|
|
1789
|
-
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"],
|
|
1790
1881
|
cacheToolsList: false,
|
|
1791
1882
|
}]),
|
|
1792
1883
|
...existing,
|
|
@@ -1818,6 +1909,18 @@ export function firstPartyMcpBaseUrl(settings: Settings): string {
|
|
|
1818
1909
|
return settings.opengeniMcpUrl ?? `http://127.0.0.1:${settings.apiPort}/v1/workspaces/{workspaceId}/mcp`;
|
|
1819
1910
|
}
|
|
1820
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
|
+
|
|
1821
1924
|
function firstPartyMcpServerUrl(settings: Settings): string {
|
|
1822
1925
|
return firstPartyMcpBaseUrl(settings);
|
|
1823
1926
|
}
|
|
@@ -1827,6 +1930,9 @@ function firstPartyDocumentsMcpServerUrl(mcpUrl: string): string {
|
|
|
1827
1930
|
}
|
|
1828
1931
|
|
|
1829
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
|
+
}
|
|
1830
1936
|
if (settings.productAccessMode === "managed") {
|
|
1831
1937
|
if (!settings.publicBaseUrl) {
|
|
1832
1938
|
throw new Error("OPENGENI_PUBLIC_BASE_URL is required when OPENGENI_PRODUCT_ACCESS_MODE=managed");
|
|
@@ -1845,6 +1951,18 @@ function validateSettings(settings: Settings): void {
|
|
|
1845
1951
|
}
|
|
1846
1952
|
}
|
|
1847
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);
|
|
1848
1966
|
if (
|
|
1849
1967
|
settings.productAccessMode === "configured"
|
|
1850
1968
|
&& !["local", "test"].includes(settings.environment)
|