@newrelic/preflight 1.6.12 → 1.6.14
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/config.d.ts +49 -24
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +220 -85
- package/dist/config.js.map +1 -1
- package/dist/hooks/subagent-watcher.d.ts +1 -0
- package/dist/hooks/subagent-watcher.d.ts.map +1 -1
- package/dist/hooks/subagent-watcher.js +24 -2
- package/dist/hooks/subagent-watcher.js.map +1 -1
- package/dist/index.js +14 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/config.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ import type { PersonalAlertThresholds } from './alerts/types.js';
|
|
|
6
6
|
export interface McpServerConfig {
|
|
7
7
|
readonly licenseKey?: string;
|
|
8
8
|
readonly accountId?: string;
|
|
9
|
+
/** Config-file schema version. Defaults to 1; see CURRENT_CONFIG_VERSION. */
|
|
10
|
+
readonly configVersion: number;
|
|
9
11
|
readonly appName: string;
|
|
10
12
|
readonly developer: string;
|
|
11
13
|
readonly teamId: string | null;
|
|
@@ -35,29 +37,37 @@ export interface McpServerConfig {
|
|
|
35
37
|
/** Default: 90. `null` disables retention (only reachable via an explicit `null` in config.json). */
|
|
36
38
|
readonly retainSessionsDays: number | null;
|
|
37
39
|
readonly personalAlertThresholds: PersonalAlertThresholds;
|
|
38
|
-
readonly otlpEndpoint: string | null;
|
|
39
|
-
readonly otlpHeaders: Readonly<Record<string, string>>;
|
|
40
|
-
readonly transport: 'nr-events-api' | 'otlp' | 'both';
|
|
41
40
|
readonly mode: 'cloud' | 'local' | 'both';
|
|
42
41
|
readonly platformTarget?: PlatformTarget;
|
|
43
|
-
/** Enable the local OTLP/HTTP receiver. Default: false. */
|
|
44
|
-
readonly otlpReceiverEnabled: boolean;
|
|
45
|
-
/** Port for the local OTLP/HTTP receiver. Default: 4318. */
|
|
46
|
-
readonly otlpReceiverPort: number;
|
|
47
|
-
/** Bind address for the local OTLP/HTTP receiver. Default: '127.0.0.1'. */
|
|
48
|
-
readonly otlpReceiverBindAddress: string;
|
|
49
42
|
/**
|
|
50
|
-
* OTLP
|
|
51
|
-
*
|
|
52
|
-
*
|
|
43
|
+
* OTLP-related config, grouped to match the `dashboard`/`alerts` nesting
|
|
44
|
+
* precedent. The config-file schema also still accepts the legacy flat
|
|
45
|
+
* top-level keys (`otlpEndpoint`, `otlpReceiverPort`, etc.) for backward
|
|
46
|
+
* compatibility — see `pickOtlpValue()` in loadMcpConfig().
|
|
53
47
|
*/
|
|
54
|
-
readonly
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
48
|
+
readonly otlp: {
|
|
49
|
+
readonly endpoint: string | null;
|
|
50
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
51
|
+
readonly transport: 'nr-events-api' | 'otlp' | 'both';
|
|
52
|
+
/** Enable the local OTLP/HTTP receiver. Default: false. */
|
|
53
|
+
readonly receiverEnabled: boolean;
|
|
54
|
+
/** Port for the local OTLP/HTTP receiver. Default: 4318. */
|
|
55
|
+
readonly receiverPort: number;
|
|
56
|
+
/** Bind address for the local OTLP/HTTP receiver. Default: '127.0.0.1'. */
|
|
57
|
+
readonly receiverBindAddress: string;
|
|
58
|
+
/**
|
|
59
|
+
* OTLP forward endpoint — where to relay received spans.
|
|
60
|
+
* Defaults to New Relic US OTLP endpoint when licenseKey is present.
|
|
61
|
+
* Set to null to disable forwarding (receive and enrich only, then drop).
|
|
62
|
+
*/
|
|
63
|
+
readonly forwardEndpoint: string | null;
|
|
64
|
+
/**
|
|
65
|
+
* HTTP headers added to every OTLP forward request (e.g. `{ 'api-key': licenseKey }`).
|
|
66
|
+
* Defaults to `{ 'api-key': licenseKey }` when licenseKey is present.
|
|
67
|
+
* Configurable via NR_AI_OTLP_FORWARD_HEADERS (comma-separated key=value pairs).
|
|
68
|
+
*/
|
|
69
|
+
readonly forwardHeaders: Readonly<Record<string, string>>;
|
|
70
|
+
};
|
|
61
71
|
readonly dashboard: {
|
|
62
72
|
readonly port: number;
|
|
63
73
|
readonly host: string;
|
|
@@ -79,6 +89,7 @@ export interface McpServerConfig {
|
|
|
79
89
|
export declare const DEFAULT_STORAGE_PATH: string;
|
|
80
90
|
export type PlatformTarget = 'native' | 'wsl-windows-cc' | 'wsl-linux-cc';
|
|
81
91
|
export declare const ConfigFileSchema: z.ZodObject<{
|
|
92
|
+
configVersion: z.ZodOptional<z.ZodNumber>;
|
|
82
93
|
licenseKey: z.ZodOptional<z.ZodString>;
|
|
83
94
|
accountId: z.ZodOptional<z.ZodString>;
|
|
84
95
|
appName: z.ZodOptional<z.ZodString>;
|
|
@@ -117,6 +128,11 @@ export declare const ConfigFileSchema: z.ZodObject<{
|
|
|
117
128
|
otlp: "otlp";
|
|
118
129
|
both: "both";
|
|
119
130
|
}>>;
|
|
131
|
+
otlpReceiverEnabled: z.ZodOptional<z.ZodBoolean>;
|
|
132
|
+
otlpReceiverPort: z.ZodOptional<z.ZodNumber>;
|
|
133
|
+
otlpReceiverBindAddress: z.ZodOptional<z.ZodString>;
|
|
134
|
+
otlpForwardEndpoint: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
135
|
+
otlpForwardHeaders: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
120
136
|
mode: z.ZodOptional<z.ZodEnum<{
|
|
121
137
|
both: "both";
|
|
122
138
|
cloud: "cloud";
|
|
@@ -127,11 +143,20 @@ export declare const ConfigFileSchema: z.ZodObject<{
|
|
|
127
143
|
"wsl-windows-cc": "wsl-windows-cc";
|
|
128
144
|
"wsl-linux-cc": "wsl-linux-cc";
|
|
129
145
|
}>>;
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
146
|
+
otlp: z.ZodOptional<z.ZodObject<{
|
|
147
|
+
endpoint: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
148
|
+
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
149
|
+
transport: z.ZodOptional<z.ZodEnum<{
|
|
150
|
+
"nr-events-api": "nr-events-api";
|
|
151
|
+
otlp: "otlp";
|
|
152
|
+
both: "both";
|
|
153
|
+
}>>;
|
|
154
|
+
receiverEnabled: z.ZodOptional<z.ZodBoolean>;
|
|
155
|
+
receiverPort: z.ZodOptional<z.ZodNumber>;
|
|
156
|
+
receiverBindAddress: z.ZodOptional<z.ZodString>;
|
|
157
|
+
forwardEndpoint: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
158
|
+
forwardHeaders: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
159
|
+
}, z.core.$loose>>;
|
|
135
160
|
alerts: z.ZodOptional<z.ZodObject<{
|
|
136
161
|
personal: z.ZodOptional<z.ZodObject<{
|
|
137
162
|
dailyCostUsd: z.ZodOptional<z.ZodNumber>;
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAOjE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9C,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,iBAAiB,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAClF,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,QAAQ,CAAC,cAAc,EAAE,SAAS,cAAc,EAAE,CAAC;IACnD,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,qGAAqG;IACrG,QAAQ,CAAC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,QAAQ,CAAC,uBAAuB,EAAE,uBAAuB,CAAC;IAC1D,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/config.js
CHANGED
|
@@ -13,8 +13,17 @@ export const DEFAULT_STORAGE_PATH = resolve(homedir(), '.newrelic-preflight');
|
|
|
13
13
|
// retainSessionsDays at all — an explicit `null` in config.json still means
|
|
14
14
|
// "retention disabled" (see the resolution logic below).
|
|
15
15
|
const DEFAULT_RETAIN_SESSIONS_DAYS = 90;
|
|
16
|
+
/**
|
|
17
|
+
* Bump when a change to config.json's shape is non-additive (a field is
|
|
18
|
+
* renamed, moved, or removed) so a future migration path has something to
|
|
19
|
+
* branch on. Every change to date has been additive — including the `otlp`
|
|
20
|
+
* nested object below, which was introduced as an accept-both addition
|
|
21
|
+
* rather than a breaking rename — so this has never moved past 1.
|
|
22
|
+
*/
|
|
23
|
+
const CURRENT_CONFIG_VERSION = 1;
|
|
16
24
|
export const ConfigFileSchema = z
|
|
17
25
|
.object({
|
|
26
|
+
configVersion: z.number().int().optional(),
|
|
18
27
|
licenseKey: z.string().optional(),
|
|
19
28
|
accountId: z.string().optional(),
|
|
20
29
|
appName: z.string().optional(),
|
|
@@ -41,16 +50,32 @@ export const ConfigFileSchema = z
|
|
|
41
50
|
digestWebhookUrl: z.string().nullable().optional(),
|
|
42
51
|
digestSchedule: z.string().optional(),
|
|
43
52
|
retainSessionsDays: z.number().nullable().optional(),
|
|
53
|
+
// Legacy flat OTLP fields — deprecated in favor of the nested `otlp`
|
|
54
|
+
// object below, but still accepted for backward compatibility (using
|
|
55
|
+
// one logs a deprecation warning; see pickOtlpValue() in loadMcpConfig).
|
|
44
56
|
otlpEndpoint: z.string().nullable().optional(),
|
|
45
57
|
otlpHeaders: z.record(z.string(), z.string()).optional(),
|
|
46
58
|
transport: z.enum(['nr-events-api', 'otlp', 'both']).optional(),
|
|
47
|
-
mode: z.enum(['cloud', 'local', 'both']).optional(),
|
|
48
|
-
platformTarget: z.enum(['native', 'wsl-windows-cc', 'wsl-linux-cc']).optional(),
|
|
49
59
|
otlpReceiverEnabled: z.boolean().optional(),
|
|
50
60
|
otlpReceiverPort: z.number().optional(),
|
|
51
61
|
otlpReceiverBindAddress: z.string().optional(),
|
|
52
62
|
otlpForwardEndpoint: z.string().nullable().optional(),
|
|
53
63
|
otlpForwardHeaders: z.record(z.string(), z.string()).optional(),
|
|
64
|
+
mode: z.enum(['cloud', 'local', 'both']).optional(),
|
|
65
|
+
platformTarget: z.enum(['native', 'wsl-windows-cc', 'wsl-linux-cc']).optional(),
|
|
66
|
+
otlp: z
|
|
67
|
+
.object({
|
|
68
|
+
endpoint: z.string().nullable().optional(),
|
|
69
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
70
|
+
transport: z.enum(['nr-events-api', 'otlp', 'both']).optional(),
|
|
71
|
+
receiverEnabled: z.boolean().optional(),
|
|
72
|
+
receiverPort: z.number().optional(),
|
|
73
|
+
receiverBindAddress: z.string().optional(),
|
|
74
|
+
forwardEndpoint: z.string().nullable().optional(),
|
|
75
|
+
forwardHeaders: z.record(z.string(), z.string()).optional(),
|
|
76
|
+
})
|
|
77
|
+
.passthrough()
|
|
78
|
+
.optional(),
|
|
54
79
|
alerts: z
|
|
55
80
|
.object({
|
|
56
81
|
personal: z
|
|
@@ -165,18 +190,25 @@ function envBool(key, defaultValue) {
|
|
|
165
190
|
return false;
|
|
166
191
|
return defaultValue;
|
|
167
192
|
}
|
|
193
|
+
// Applied to every return path of envInt (not just the parsed-env-var one) so
|
|
194
|
+
// a bounds-checked call site is protected uniformly regardless of whether the
|
|
195
|
+
// value actually came from the env var or from its (often config-file- or
|
|
196
|
+
// CLI-derived) `defaultValue` argument.
|
|
197
|
+
function clampNumber(value, bounds) {
|
|
198
|
+
if (bounds?.min !== undefined && value < bounds.min)
|
|
199
|
+
return bounds.min;
|
|
200
|
+
if (bounds?.max !== undefined && value > bounds.max)
|
|
201
|
+
return bounds.max;
|
|
202
|
+
return value;
|
|
203
|
+
}
|
|
168
204
|
function envInt(key, defaultValue, bounds) {
|
|
169
205
|
const val = process.env[key];
|
|
170
206
|
if (val === undefined)
|
|
171
|
-
return defaultValue;
|
|
207
|
+
return clampNumber(defaultValue, bounds);
|
|
172
208
|
const parsed = parseInt(val, 10);
|
|
173
209
|
if (Number.isNaN(parsed))
|
|
174
|
-
return defaultValue;
|
|
175
|
-
|
|
176
|
-
return bounds.min;
|
|
177
|
-
if (bounds?.max !== undefined && parsed > bounds.max)
|
|
178
|
-
return bounds.max;
|
|
179
|
-
return parsed;
|
|
210
|
+
return clampNumber(defaultValue, bounds);
|
|
211
|
+
return clampNumber(parsed, bounds);
|
|
180
212
|
}
|
|
181
213
|
function envLogLevel(key, defaultValue) {
|
|
182
214
|
const val = process.env[key]?.toLowerCase();
|
|
@@ -259,6 +291,20 @@ function parseOtlpHeaders(headerString) {
|
|
|
259
291
|
}
|
|
260
292
|
return result;
|
|
261
293
|
}
|
|
294
|
+
// Resolves a config-file OTLP field: the new nested `otlp.X` value wins when
|
|
295
|
+
// present; otherwise falls back to the legacy flat top-level key, recording
|
|
296
|
+
// its name in `used` so the caller can warn about exactly the legacy keys
|
|
297
|
+
// that were actually consulted (not merely present-but-shadowed by a nested
|
|
298
|
+
// value for the same field).
|
|
299
|
+
function pickOtlpValue(nested, flat, legacyKey, used) {
|
|
300
|
+
if (nested !== undefined)
|
|
301
|
+
return nested;
|
|
302
|
+
if (flat !== undefined) {
|
|
303
|
+
used.push(legacyKey);
|
|
304
|
+
return flat;
|
|
305
|
+
}
|
|
306
|
+
return undefined;
|
|
307
|
+
}
|
|
262
308
|
/**
|
|
263
309
|
* Validate the alerts.rulesPath value. The path is read from the user's
|
|
264
310
|
* config file or NR_AI_ALERTS_RULES_PATH env var; both are user-controlled
|
|
@@ -298,6 +344,21 @@ function validateRulesPath(rawPath, storagePath) {
|
|
|
298
344
|
}
|
|
299
345
|
return resolved;
|
|
300
346
|
}
|
|
347
|
+
// Config-file-sourced numeric fields (budgets, retention) validate positivity
|
|
348
|
+
// only on the env-var path today — a negative/zero value in config.json
|
|
349
|
+
// silently disables the guardrail with no warning, unlike every other
|
|
350
|
+
// malformed-input path in this file (see validateRulesPath, dashboard.host,
|
|
351
|
+
// parseProxyUpstreams). Callers decide their own fallback value; this only
|
|
352
|
+
// validates and logs.
|
|
353
|
+
function validateFilePositiveNumber(value, fieldName) {
|
|
354
|
+
if (Number.isFinite(value) && value > 0)
|
|
355
|
+
return true;
|
|
356
|
+
logger.warn(`${fieldName} in config file must be a positive number — ignoring value`, {
|
|
357
|
+
fieldName,
|
|
358
|
+
value,
|
|
359
|
+
});
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
301
362
|
function parseProxyUpstreams(envValue, fileValue) {
|
|
302
363
|
// Env var takes precedence (JSON string)
|
|
303
364
|
if (envValue) {
|
|
@@ -367,6 +428,16 @@ export function loadMcpConfig(cliOptions) {
|
|
|
367
428
|
'antiPatternCountMax',
|
|
368
429
|
]);
|
|
369
430
|
const knownDashboardKeys = new Set(['port', 'host', 'openOnStart']);
|
|
431
|
+
const knownOtlpKeys = new Set([
|
|
432
|
+
'endpoint',
|
|
433
|
+
'headers',
|
|
434
|
+
'transport',
|
|
435
|
+
'receiverEnabled',
|
|
436
|
+
'receiverPort',
|
|
437
|
+
'receiverBindAddress',
|
|
438
|
+
'forwardEndpoint',
|
|
439
|
+
'forwardHeaders',
|
|
440
|
+
]);
|
|
370
441
|
if (file.alerts && typeof file.alerts === 'object') {
|
|
371
442
|
const alerts = file.alerts;
|
|
372
443
|
const unknownAlertsKeys = Object.keys(alerts).filter((k) => !knownAlertsKeys.has(k));
|
|
@@ -397,6 +468,15 @@ export function loadMcpConfig(cliOptions) {
|
|
|
397
468
|
});
|
|
398
469
|
}
|
|
399
470
|
}
|
|
471
|
+
if (file.otlp && typeof file.otlp === 'object') {
|
|
472
|
+
const unknownOtlpKeys = Object.keys(file.otlp).filter((k) => !knownOtlpKeys.has(k));
|
|
473
|
+
if (unknownOtlpKeys.length > 0) {
|
|
474
|
+
logger.warn('Unknown keys in otlp config (ignored)', {
|
|
475
|
+
unknownFields: unknownOtlpKeys,
|
|
476
|
+
path: configFilePath,
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
}
|
|
400
480
|
// --- Resolve mode early so we can gate licenseKey/accountId requirements ---
|
|
401
481
|
// File mode is already validated by the zod schema in loadConfigFile.
|
|
402
482
|
const VALID_MODES = ['cloud', 'local', 'both'];
|
|
@@ -446,6 +526,7 @@ export function loadMcpConfig(cliOptions) {
|
|
|
446
526
|
const config = {
|
|
447
527
|
licenseKey,
|
|
448
528
|
accountId,
|
|
529
|
+
configVersion: typeof file.configVersion === 'number' ? file.configVersion : CURRENT_CONFIG_VERSION,
|
|
449
530
|
appName: process.env.NEW_RELIC_AI_MCP_APP_NAME ??
|
|
450
531
|
(typeof file.appName === 'string' ? file.appName : 'preflight'),
|
|
451
532
|
model: process.env.NEW_RELIC_AI_MODEL ??
|
|
@@ -476,7 +557,11 @@ export function loadMcpConfig(cliOptions) {
|
|
|
476
557
|
if (Number.isFinite(v) && v > 0)
|
|
477
558
|
return v;
|
|
478
559
|
}
|
|
479
|
-
|
|
560
|
+
if (typeof file.sessionBudgetUsd !== 'number')
|
|
561
|
+
return null;
|
|
562
|
+
return validateFilePositiveNumber(file.sessionBudgetUsd, 'sessionBudgetUsd')
|
|
563
|
+
? file.sessionBudgetUsd
|
|
564
|
+
: null;
|
|
480
565
|
})(),
|
|
481
566
|
dailyBudgetUsd: (() => {
|
|
482
567
|
const raw = process.env.NEW_RELIC_AI_DAILY_BUDGET_USD;
|
|
@@ -485,7 +570,11 @@ export function loadMcpConfig(cliOptions) {
|
|
|
485
570
|
if (Number.isFinite(v) && v > 0)
|
|
486
571
|
return v;
|
|
487
572
|
}
|
|
488
|
-
|
|
573
|
+
if (typeof file.dailyBudgetUsd !== 'number')
|
|
574
|
+
return null;
|
|
575
|
+
return validateFilePositiveNumber(file.dailyBudgetUsd, 'dailyBudgetUsd')
|
|
576
|
+
? file.dailyBudgetUsd
|
|
577
|
+
: null;
|
|
489
578
|
})(),
|
|
490
579
|
weeklyBudgetUsd: (() => {
|
|
491
580
|
const raw = process.env.NEW_RELIC_AI_WEEKLY_BUDGET_USD;
|
|
@@ -494,13 +583,20 @@ export function loadMcpConfig(cliOptions) {
|
|
|
494
583
|
if (Number.isFinite(v) && v > 0)
|
|
495
584
|
return v;
|
|
496
585
|
}
|
|
497
|
-
|
|
586
|
+
if (typeof file.weeklyBudgetUsd !== 'number')
|
|
587
|
+
return null;
|
|
588
|
+
return validateFilePositiveNumber(file.weeklyBudgetUsd, 'weeklyBudgetUsd')
|
|
589
|
+
? file.weeklyBudgetUsd
|
|
590
|
+
: null;
|
|
498
591
|
})(),
|
|
499
|
-
|
|
592
|
+
// clampNumber wraps the whole expression, not just envInt's own bounds
|
|
593
|
+
// param, because cliOptions?.port short-circuits envInt (and its
|
|
594
|
+
// clamping) entirely when a CLI value is supplied.
|
|
595
|
+
port: clampNumber(cliOptions?.port ??
|
|
500
596
|
envInt('NEW_RELIC_AI_MCP_PORT', typeof file.port === 'number' ? file.port : 9847, {
|
|
501
597
|
min: 1,
|
|
502
598
|
max: 65535,
|
|
503
|
-
}),
|
|
599
|
+
}), { min: 1, max: 65535 }),
|
|
504
600
|
logLevel: cliOptions?.logLevel ??
|
|
505
601
|
envLogLevel('NEW_RELIC_AI_MCP_LOG_LEVEL', typeof file.logLevel === 'string' &&
|
|
506
602
|
['debug', 'info', 'warn', 'error'].includes(file.logLevel)
|
|
@@ -521,89 +617,128 @@ export function loadMcpConfig(cliOptions) {
|
|
|
521
617
|
if (Number.isFinite(v) && v > 0)
|
|
522
618
|
return v;
|
|
523
619
|
}
|
|
524
|
-
if (typeof file.retainSessionsDays === 'number')
|
|
525
|
-
|
|
620
|
+
if (typeof file.retainSessionsDays === 'number') {
|
|
621
|
+
// An invalid (non-positive) value falls back to the default rather
|
|
622
|
+
// than null — null already has a distinct, intentional meaning
|
|
623
|
+
// (explicit opt-out, handled below); a typo'd negative number is a
|
|
624
|
+
// mistake, not an opt-out.
|
|
625
|
+
return validateFilePositiveNumber(file.retainSessionsDays, 'retainSessionsDays')
|
|
626
|
+
? file.retainSessionsDays
|
|
627
|
+
: DEFAULT_RETAIN_SESSIONS_DAYS;
|
|
628
|
+
}
|
|
526
629
|
// Explicit `null` in the config file is a deliberate opt-out — distinct
|
|
527
630
|
// from the key being absent entirely, which falls through to the default.
|
|
528
631
|
if (file.retainSessionsDays === null)
|
|
529
632
|
return null;
|
|
530
633
|
return DEFAULT_RETAIN_SESSIONS_DAYS;
|
|
531
634
|
})(),
|
|
532
|
-
otlpEndpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT ??
|
|
533
|
-
(typeof file.otlpEndpoint === 'string' ? file.otlpEndpoint : null),
|
|
534
|
-
otlpHeaders: (() => {
|
|
535
|
-
const envValue = process.env.OTEL_EXPORTER_OTLP_HEADERS;
|
|
536
|
-
if (envValue)
|
|
537
|
-
return parseOtlpHeaders(envValue);
|
|
538
|
-
return typeof file.otlpHeaders === 'object' && file.otlpHeaders !== null
|
|
539
|
-
? file.otlpHeaders
|
|
540
|
-
: {};
|
|
541
|
-
})(),
|
|
542
|
-
transport: process.env.NEW_RELIC_AI_TRANSPORT === 'otlp'
|
|
543
|
-
? 'otlp'
|
|
544
|
-
: process.env.NEW_RELIC_AI_TRANSPORT === 'both'
|
|
545
|
-
? 'both'
|
|
546
|
-
: typeof file.transport === 'string' &&
|
|
547
|
-
(file.transport === 'otlp' || file.transport === 'both')
|
|
548
|
-
? file.transport
|
|
549
|
-
: 'nr-events-api',
|
|
550
635
|
mode,
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
636
|
+
// The 8 OTLP-related fields are grouped here (matching the dashboard/alerts
|
|
637
|
+
// nesting precedent). Each still accepts the legacy flat top-level key for
|
|
638
|
+
// backward compatibility — pickOtlpValue() prefers the nested `otlp.X`
|
|
639
|
+
// value and records which legacy keys were actually consulted so exactly
|
|
640
|
+
// those can be named in a single deprecation warning below.
|
|
641
|
+
otlp: (() => {
|
|
642
|
+
const fileOtlp = file.otlp ?? {};
|
|
643
|
+
const usedLegacyOtlpKeys = [];
|
|
644
|
+
const endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT ??
|
|
645
|
+
pickOtlpValue(typeof fileOtlp.endpoint === 'string' ? fileOtlp.endpoint : undefined, typeof file.otlpEndpoint === 'string' ? file.otlpEndpoint : undefined, 'otlpEndpoint', usedLegacyOtlpKeys) ??
|
|
646
|
+
null;
|
|
647
|
+
const headers = (() => {
|
|
648
|
+
const envValue = process.env.OTEL_EXPORTER_OTLP_HEADERS;
|
|
649
|
+
if (envValue)
|
|
650
|
+
return parseOtlpHeaders(envValue);
|
|
651
|
+
return (pickOtlpValue(typeof fileOtlp.headers === 'object' && fileOtlp.headers !== null
|
|
652
|
+
? fileOtlp.headers
|
|
653
|
+
: undefined, typeof file.otlpHeaders === 'object' && file.otlpHeaders !== null
|
|
654
|
+
? file.otlpHeaders
|
|
655
|
+
: undefined, 'otlpHeaders', usedLegacyOtlpKeys) ?? {});
|
|
656
|
+
})();
|
|
657
|
+
const transportValue = process.env.NEW_RELIC_AI_TRANSPORT === 'otlp'
|
|
658
|
+
? 'otlp'
|
|
659
|
+
: process.env.NEW_RELIC_AI_TRANSPORT === 'both'
|
|
660
|
+
? 'both'
|
|
661
|
+
: (pickOtlpValue(fileOtlp.transport === 'otlp' || fileOtlp.transport === 'both'
|
|
662
|
+
? fileOtlp.transport
|
|
663
|
+
: undefined, file.transport === 'otlp' || file.transport === 'both' ? file.transport : undefined, 'transport', usedLegacyOtlpKeys) ?? 'nr-events-api');
|
|
664
|
+
const receiverEnabled = envBool('NR_AI_OTLP_RECEIVER_ENABLED', pickOtlpValue(typeof fileOtlp.receiverEnabled === 'boolean' ? fileOtlp.receiverEnabled : undefined, typeof file.otlpReceiverEnabled === 'boolean' ? file.otlpReceiverEnabled : undefined, 'otlpReceiverEnabled', usedLegacyOtlpKeys) ?? false);
|
|
665
|
+
const receiverPort = envInt('NR_AI_OTLP_RECEIVER_PORT', pickOtlpValue(typeof fileOtlp.receiverPort === 'number' ? fileOtlp.receiverPort : undefined, typeof file.otlpReceiverPort === 'number' ? file.otlpReceiverPort : undefined, 'otlpReceiverPort', usedLegacyOtlpKeys) ?? 4318, { min: 1, max: 65535 });
|
|
666
|
+
const receiverBindAddress = (() => {
|
|
667
|
+
const envVal = process.env.NR_AI_OTLP_RECEIVER_BIND_ADDRESS;
|
|
668
|
+
if (envVal !== undefined && envVal !== '')
|
|
669
|
+
return envVal;
|
|
670
|
+
return (pickOtlpValue(typeof fileOtlp.receiverBindAddress === 'string' && fileOtlp.receiverBindAddress !== ''
|
|
671
|
+
? fileOtlp.receiverBindAddress
|
|
672
|
+
: undefined, typeof file.otlpReceiverBindAddress === 'string' && file.otlpReceiverBindAddress !== ''
|
|
673
|
+
? file.otlpReceiverBindAddress
|
|
674
|
+
: undefined, 'otlpReceiverBindAddress', usedLegacyOtlpKeys) ?? '127.0.0.1');
|
|
675
|
+
})();
|
|
676
|
+
const forwardEndpoint = (() => {
|
|
677
|
+
const envVal = process.env.NR_AI_OTLP_FORWARD_ENDPOINT;
|
|
678
|
+
// Default to the NR OTLP endpoint only when not in local mode. Local users
|
|
679
|
+
// may still set the value explicitly via env or config file (e.g. to point
|
|
680
|
+
// at a self-hosted collector); we just don't synthesize a NR default for them.
|
|
681
|
+
const defaultEndpoint = mode !== 'local' && licenseKey !== undefined ? 'https://otlp.nr-data.net' : null;
|
|
682
|
+
const fileValue = pickOtlpValue(typeof fileOtlp.forwardEndpoint === 'string' ? fileOtlp.forwardEndpoint : undefined, typeof file.otlpForwardEndpoint === 'string' ? file.otlpForwardEndpoint : undefined, 'otlpForwardEndpoint', usedLegacyOtlpKeys);
|
|
683
|
+
const endpoint = envVal !== undefined
|
|
684
|
+
? envVal || null
|
|
685
|
+
: fileValue !== undefined
|
|
686
|
+
? fileValue || null
|
|
687
|
+
: defaultEndpoint;
|
|
688
|
+
if (endpoint === null)
|
|
689
|
+
return null;
|
|
690
|
+
try {
|
|
691
|
+
const url = new URL(endpoint);
|
|
692
|
+
if (url.protocol === 'https:')
|
|
693
|
+
return endpoint;
|
|
694
|
+
if (url.protocol === 'http:') {
|
|
695
|
+
logger.warn('OTLP forward endpoint uses http:// instead of https:// — TLS not enabled', { endpoint });
|
|
696
|
+
return endpoint;
|
|
697
|
+
}
|
|
698
|
+
logger.warn('OTLP forward endpoint has unexpected protocol', {
|
|
581
699
|
endpoint,
|
|
700
|
+
protocol: url.protocol,
|
|
582
701
|
});
|
|
583
702
|
return endpoint;
|
|
584
703
|
}
|
|
585
|
-
|
|
586
|
-
endpoint,
|
|
587
|
-
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
704
|
+
catch (_err) {
|
|
705
|
+
logger.error('OTLP forward endpoint is not a valid URL', { endpoint });
|
|
706
|
+
return null;
|
|
707
|
+
}
|
|
708
|
+
})();
|
|
709
|
+
const forwardHeaders = (() => {
|
|
710
|
+
const envValue = process.env.NR_AI_OTLP_FORWARD_HEADERS;
|
|
711
|
+
if (envValue !== undefined)
|
|
712
|
+
return parseOtlpHeaders(envValue);
|
|
713
|
+
const fileValue = pickOtlpValue(typeof fileOtlp.forwardHeaders === 'object' && fileOtlp.forwardHeaders !== null
|
|
714
|
+
? fileOtlp.forwardHeaders
|
|
715
|
+
: undefined, typeof file.otlpForwardHeaders === 'object' && file.otlpForwardHeaders !== null
|
|
716
|
+
? file.otlpForwardHeaders
|
|
717
|
+
: undefined, 'otlpForwardHeaders', usedLegacyOtlpKeys);
|
|
718
|
+
if (fileValue !== undefined)
|
|
719
|
+
return fileValue;
|
|
720
|
+
// Don't synthesize a NR api-key header default in local mode — the licenseKey
|
|
721
|
+
// may be present in the env from another tool, and we must not leak it to a
|
|
722
|
+
// forward target the user didn't explicitly configure.
|
|
723
|
+
return mode !== 'local' && licenseKey !== undefined ? { 'api-key': licenseKey } : {};
|
|
724
|
+
})();
|
|
725
|
+
if (usedLegacyOtlpKeys.length > 0) {
|
|
726
|
+
logger.warn('Config file uses deprecated flat OTLP fields — move these under a nested "otlp" object',
|
|
727
|
+
// Field name deliberately avoids "key"/"keys" — the logger's redact()
|
|
728
|
+
// treats any key-shaped property name as secret-bearing and would
|
|
729
|
+
// replace the whole array with '***' (see SECRET_KEY_RE).
|
|
730
|
+
{ legacyFieldNames: usedLegacyOtlpKeys, path: configFilePath });
|
|
602
731
|
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
732
|
+
return {
|
|
733
|
+
endpoint,
|
|
734
|
+
headers,
|
|
735
|
+
transport: transportValue,
|
|
736
|
+
receiverEnabled,
|
|
737
|
+
receiverPort,
|
|
738
|
+
receiverBindAddress,
|
|
739
|
+
forwardEndpoint,
|
|
740
|
+
forwardHeaders,
|
|
741
|
+
};
|
|
607
742
|
})(),
|
|
608
743
|
personalAlertThresholds: (() => {
|
|
609
744
|
const fileThresholds = file.alerts?.personal;
|