monora-ai 2.1.0 → 2.1.3
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/README.md +333 -159
- package/dist/aims_governance.d.ts +238 -0
- package/dist/aims_governance.d.ts.map +1 -0
- package/dist/aims_governance.js +922 -0
- package/dist/alerts.d.ts +16 -0
- package/dist/alerts.d.ts.map +1 -1
- package/dist/alerts.js +16 -0
- package/dist/api.d.ts +6 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +6 -0
- package/dist/assessment.d.ts +85 -0
- package/dist/assessment.d.ts.map +1 -1
- package/dist/assessment.js +506 -13
- package/dist/attribution.d.ts +44 -3
- package/dist/attribution.d.ts.map +1 -1
- package/dist/attribution.js +197 -10
- package/dist/autodetect.d.ts +68 -0
- package/dist/autodetect.d.ts.map +1 -1
- package/dist/autodetect.js +639 -0
- package/dist/bias.d.ts +130 -0
- package/dist/bias.d.ts.map +1 -0
- package/dist/bias.js +223 -0
- package/dist/cli/diagnostics.d.ts +5 -1
- package/dist/cli/diagnostics.d.ts.map +1 -1
- package/dist/cli/diagnostics.js +23 -6
- package/dist/cli/doctor.d.ts +25 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +381 -0
- package/dist/cli/fix.d.ts +16 -0
- package/dist/cli/fix.d.ts.map +1 -0
- package/dist/cli/fix.js +284 -0
- package/dist/cli/init.d.ts +57 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +205 -0
- package/dist/cli.js +1550 -176
- package/dist/complianceTargets.d.ts +111 -0
- package/dist/complianceTargets.d.ts.map +1 -0
- package/dist/complianceTargets.js +521 -0
- package/dist/config.d.ts +261 -16
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +381 -32
- package/dist/config_migrations.d.ts.map +1 -1
- package/dist/config_migrations.js +38 -1
- package/dist/config_schema.d.ts +2490 -1035
- package/dist/config_schema.d.ts.map +1 -1
- package/dist/config_schema.js +233 -64
- package/dist/context.d.ts +34 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +118 -7
- package/dist/control_backbone.d.ts +122 -0
- package/dist/control_backbone.d.ts.map +1 -0
- package/dist/control_backbone.js +698 -0
- package/dist/data-governance.d.ts +187 -0
- package/dist/data-governance.d.ts.map +1 -0
- package/dist/data-governance.js +424 -0
- package/dist/dataResidency.d.ts +44 -0
- package/dist/dataResidency.d.ts.map +1 -0
- package/dist/dataResidency.js +203 -0
- package/dist/dispatcher.d.ts.map +1 -1
- package/dist/dispatcher.js +17 -5
- package/dist/evidence_store.d.ts +103 -0
- package/dist/evidence_store.d.ts.map +1 -0
- package/dist/evidence_store.js +459 -0
- package/dist/executiveSummary.d.ts +15 -0
- package/dist/executiveSummary.d.ts.map +1 -1
- package/dist/executiveSummary.js +135 -22
- package/dist/identity.d.ts +143 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +231 -0
- package/dist/impact-assessment.d.ts +350 -0
- package/dist/impact-assessment.d.ts.map +1 -0
- package/dist/impact-assessment.js +580 -0
- package/dist/index.d.ts +20 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +247 -5
- package/dist/instrumentation.d.ts +1 -1
- package/dist/instrumentation.d.ts.map +1 -1
- package/dist/instrumentation.js +123 -22
- package/dist/integrations/anthropic.d.ts +3 -0
- package/dist/integrations/anthropic.d.ts.map +1 -1
- package/dist/integrations/anthropic.js +282 -80
- package/dist/integrations/governance.d.ts +33 -0
- package/dist/integrations/governance.d.ts.map +1 -0
- package/dist/integrations/governance.js +208 -0
- package/dist/integrations/langchain.d.ts +4 -0
- package/dist/integrations/langchain.d.ts.map +1 -1
- package/dist/integrations/langchain.js +362 -142
- package/dist/integrations/openai.d.ts +9 -0
- package/dist/integrations/openai.d.ts.map +1 -1
- package/dist/integrations/openai.js +673 -73
- package/dist/iso42001_consolidation.d.ts +16 -0
- package/dist/iso42001_consolidation.d.ts.map +1 -0
- package/dist/iso42001_consolidation.js +413 -0
- package/dist/iso42001_workflows.d.ts +263 -0
- package/dist/iso42001_workflows.d.ts.map +1 -0
- package/dist/iso42001_workflows.js +781 -0
- package/dist/lifecycle.d.ts +299 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +624 -0
- package/dist/lineage.d.ts +2 -2
- package/dist/lineage.d.ts.map +1 -1
- package/dist/lineage.js +9 -16
- package/dist/middleware/express.d.ts.map +1 -1
- package/dist/middleware/express.js +18 -3
- package/dist/middleware/nextjs.js +2 -2
- package/dist/model.d.ts +143 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +371 -0
- package/dist/onboarding.d.ts +42 -0
- package/dist/onboarding.d.ts.map +1 -0
- package/dist/onboarding.js +1022 -0
- package/dist/oversight.d.ts +264 -0
- package/dist/oversight.d.ts.map +1 -0
- package/dist/oversight.js +497 -0
- package/dist/presets.js +7 -7
- package/dist/quotas.d.ts +171 -0
- package/dist/quotas.d.ts.map +1 -0
- package/dist/quotas.js +259 -0
- package/dist/register.d.ts +13 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +99 -0
- package/dist/registry.d.ts +1 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +7 -0
- package/dist/registryData.json +43 -6
- package/dist/report.d.ts +2 -1
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +189 -2
- package/dist/reporting.d.ts +125 -0
- package/dist/reporting.d.ts.map +1 -1
- package/dist/reporting.js +192 -2
- package/dist/resources.d.ts +285 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +643 -0
- package/dist/risk.d.ts +120 -0
- package/dist/risk.d.ts.map +1 -0
- package/dist/risk.js +220 -0
- package/dist/runtime.d.ts +73 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +415 -18
- package/dist/schemaInference.d.ts +92 -0
- package/dist/schemaInference.d.ts.map +1 -0
- package/dist/schemaInference.js +466 -0
- package/dist/schema_validation.js +2 -2
- package/dist/schemas/config.schema.json +118 -4
- package/dist/security_report.js +4 -4
- package/dist/signing.d.ts +1 -1
- package/dist/signing.d.ts.map +1 -1
- package/dist/signing.js +4 -0
- package/dist/sinks/file.d.ts +19 -1
- package/dist/sinks/file.d.ts.map +1 -1
- package/dist/sinks/file.js +82 -13
- package/dist/sinks/https.d.ts +10 -0
- package/dist/sinks/https.d.ts.map +1 -1
- package/dist/sinks/https.js +76 -16
- package/dist/sinks/stdout.d.ts +1 -0
- package/dist/sinks/stdout.d.ts.map +1 -1
- package/dist/sinks/stdout.js +12 -1
- package/dist/spec.d.ts +159 -0
- package/dist/spec.d.ts.map +1 -0
- package/dist/spec.js +391 -0
- package/dist/stakeholders.d.ts +199 -0
- package/dist/stakeholders.d.ts.map +1 -0
- package/dist/stakeholders.js +398 -0
- package/dist/standards.d.ts.map +1 -1
- package/dist/standards.js +160 -2
- package/dist/standards_ingest.d.ts.map +1 -1
- package/dist/standards_ingest.js +1 -4
- package/dist/telemetry.d.ts +16 -2
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +77 -14
- package/dist/templates/controls/iso42001_control_catalog.json +1443 -0
- package/dist/traced_emitter.d.ts.map +1 -1
- package/dist/traced_emitter.js +19 -9
- package/dist/trust_package.d.ts +19 -1
- package/dist/trust_package.d.ts.map +1 -1
- package/dist/trust_package.js +89 -2
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +9 -2
- package/dist/wal.d.ts.map +1 -1
- package/dist/wal.js +2 -1
- package/package.json +14 -1
- package/scripts/postinstall.js +105 -210
- package/templates/controls/iso42001_control_catalog.json +1443 -0
package/dist/runtime.js
CHANGED
|
@@ -12,11 +12,17 @@ exports.llmCall = llmCall;
|
|
|
12
12
|
exports.toolCall = toolCall;
|
|
13
13
|
exports.agentStep = agentStep;
|
|
14
14
|
exports.ensureState = ensureState;
|
|
15
|
+
exports.getState = getState;
|
|
15
16
|
exports.emitEvent = emitEvent;
|
|
17
|
+
exports.emitInternal = emitInternal;
|
|
18
|
+
exports.notifyViolation = notifyViolation;
|
|
16
19
|
exports.executeCall = executeCall;
|
|
20
|
+
exports.applyQuotaLimits = applyQuotaLimits;
|
|
17
21
|
exports.buildLlmBody = buildLlmBody;
|
|
18
22
|
exports.buildToolBody = buildToolBody;
|
|
19
23
|
exports.buildAgentBody = buildAgentBody;
|
|
24
|
+
exports.extractUsageMetrics = extractUsageMetrics;
|
|
25
|
+
exports.estimateUsageCost = estimateUsageCost;
|
|
20
26
|
exports.isPromise = isPromise;
|
|
21
27
|
const perf_hooks_1 = require("perf_hooks");
|
|
22
28
|
const logger_1 = require("./logger");
|
|
@@ -39,7 +45,26 @@ const instrumentation_1 = require("./instrumentation");
|
|
|
39
45
|
const telemetry_1 = require("./telemetry");
|
|
40
46
|
const attribution_1 = require("./attribution");
|
|
41
47
|
const config_migrations_1 = require("./config_migrations");
|
|
48
|
+
const quotas_1 = require("./quotas");
|
|
42
49
|
let state = null;
|
|
50
|
+
let cachedVerifyEventHash = null;
|
|
51
|
+
let verifyEventHashResolved = false;
|
|
52
|
+
function getVerifyEventHash() {
|
|
53
|
+
if (verifyEventHashResolved) {
|
|
54
|
+
return cachedVerifyEventHash;
|
|
55
|
+
}
|
|
56
|
+
verifyEventHashResolved = true;
|
|
57
|
+
try {
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
59
|
+
const verifyModule = require('./verify');
|
|
60
|
+
cachedVerifyEventHash = verifyModule.verifyEventHash;
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
logger_1.logger.error('Real-time verification unavailable: %s', error);
|
|
64
|
+
cachedVerifyEventHash = null;
|
|
65
|
+
}
|
|
66
|
+
return cachedVerifyEventHash;
|
|
67
|
+
}
|
|
43
68
|
/**
|
|
44
69
|
* Initialize Monora with simplified configuration.
|
|
45
70
|
*
|
|
@@ -56,14 +81,25 @@ let state = null;
|
|
|
56
81
|
* init({ configPath: 'monora.yml' }); // Load from config file
|
|
57
82
|
* init({ configDict: {...} }); // Provide full config dict
|
|
58
83
|
* ```
|
|
84
|
+
*
|
|
85
|
+
* @remarks
|
|
86
|
+
* Cross-SDK Parity: This function is async (returns Promise) in Node.js but
|
|
87
|
+
* synchronous in Python. This is an intentional language difference:
|
|
88
|
+
* - Node.js: `await monora.init({ preset: 'production' })`
|
|
89
|
+
* - Python: `monora.init("production")`
|
|
59
90
|
*/
|
|
60
91
|
async function init(options) {
|
|
61
|
-
const { preset, serviceName, sink, policies, configPath, configDict, envPrefix = 'MONORA_', failFast = false, } = options || {};
|
|
92
|
+
const { preset, serviceName, sink, policies, configPath, configDict, envPrefix = 'MONORA_', failFast = false, onEvent: eventCallback, promptAttribution = false, } = options || {};
|
|
62
93
|
if (state) {
|
|
63
94
|
if (state.traceCompletionUnregister) {
|
|
64
95
|
state.traceCompletionUnregister();
|
|
65
96
|
}
|
|
97
|
+
if (state.traceStartUnregister) {
|
|
98
|
+
state.traceStartUnregister();
|
|
99
|
+
}
|
|
66
100
|
state.dispatcher.close();
|
|
101
|
+
// Clear any previously registered callbacks to avoid duplicates on re-init
|
|
102
|
+
(0, dispatcher_1.clearAuditCallbacks)();
|
|
67
103
|
state = null;
|
|
68
104
|
}
|
|
69
105
|
// Auto-detect environment and service info
|
|
@@ -109,7 +145,23 @@ async function init(options) {
|
|
|
109
145
|
}
|
|
110
146
|
// Check for production readiness warnings
|
|
111
147
|
checkProductionReadiness(config, effectivePreset);
|
|
112
|
-
|
|
148
|
+
enforceOnboardingGate(config);
|
|
149
|
+
// Register user-provided event callback before init
|
|
150
|
+
if (eventCallback) {
|
|
151
|
+
(0, dispatcher_1.onEvent)(eventCallback);
|
|
152
|
+
}
|
|
153
|
+
await initWithConfig(config, failFast, { promptAttribution });
|
|
154
|
+
// Send startup telemetry event (opt-in only)
|
|
155
|
+
if ((0, attribution_1.isTelemetryEnabled)()) {
|
|
156
|
+
(0, attribution_1.sendTelemetryEvent)({
|
|
157
|
+
eventType: 'startup',
|
|
158
|
+
environment: detectedEnv,
|
|
159
|
+
eventData: {
|
|
160
|
+
preset: effectivePreset,
|
|
161
|
+
service_name: config.defaults?.service_name,
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
}
|
|
113
165
|
}
|
|
114
166
|
/**
|
|
115
167
|
* Check configuration for production readiness and log warnings.
|
|
@@ -130,10 +182,32 @@ function checkProductionReadiness(config, presetName) {
|
|
|
130
182
|
(0, config_migrations_1.logProductionWarnings)(warnings);
|
|
131
183
|
}
|
|
132
184
|
}
|
|
185
|
+
function enforceOnboardingGate(config) {
|
|
186
|
+
const environment = String(config.defaults?.environment || '').toLowerCase();
|
|
187
|
+
if (environment !== 'production') {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const onboarding = config.onboarding;
|
|
191
|
+
if (!onboarding || typeof onboarding !== 'object') {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (!onboarding.enabled) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (onboarding.required_in_production === false) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const status = String(onboarding.status || 'draft').toLowerCase();
|
|
201
|
+
if (status === 'completed') {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
throw new Error('Onboarding is required for production but is not completed. ' +
|
|
205
|
+
'Run `npx monora-ai onboard validate` and `npx monora-ai onboard complete` first.');
|
|
206
|
+
}
|
|
133
207
|
/**
|
|
134
208
|
* Initialize Monora with a pre-built configuration.
|
|
135
209
|
*/
|
|
136
|
-
async function initWithConfig(config, failFast) {
|
|
210
|
+
async function initWithConfig(config, failFast, options) {
|
|
137
211
|
const sinks = buildSinks(config.sinks || [], failFast);
|
|
138
212
|
const dispatcher = new dispatcher_1.EventDispatcher(sinks, config);
|
|
139
213
|
const registry = new registry_1.ModelRegistry(config.registry || {});
|
|
@@ -153,7 +227,9 @@ async function initWithConfig(config, failFast) {
|
|
|
153
227
|
reportManager,
|
|
154
228
|
wal,
|
|
155
229
|
signer,
|
|
230
|
+
quotaManager: new quotas_1.QuotaManager(config.quotas || {}),
|
|
156
231
|
};
|
|
232
|
+
state.emitInternal = emitInternal;
|
|
157
233
|
// Recover uncommitted events from WAL
|
|
158
234
|
if (config.wal?.enabled && config.wal?.recovery_on_startup !== false) {
|
|
159
235
|
try {
|
|
@@ -176,8 +252,11 @@ async function initWithConfig(config, failFast) {
|
|
|
176
252
|
}
|
|
177
253
|
}
|
|
178
254
|
}
|
|
255
|
+
state.traceStartUnregister = (0, context_1.registerTraceStartHandler)((span) => {
|
|
256
|
+
handleTraceStart(span);
|
|
257
|
+
});
|
|
179
258
|
state.traceCompletionUnregister = (0, context_1.registerTraceCompletionHandler)((span) => {
|
|
180
|
-
handleTraceCompletion(span
|
|
259
|
+
handleTraceCompletion(span);
|
|
181
260
|
});
|
|
182
261
|
// Initialize telemetry/metrics collection
|
|
183
262
|
await (0, telemetry_1.initMetrics)({ telemetry: config.telemetry });
|
|
@@ -200,6 +279,18 @@ async function initWithConfig(config, failFast) {
|
|
|
200
279
|
}
|
|
201
280
|
dispatcher.start();
|
|
202
281
|
applyAttributionConfig(config);
|
|
282
|
+
const envValue = config.defaults?.environment || undefined;
|
|
283
|
+
if (options?.promptAttribution) {
|
|
284
|
+
await (0, attribution_1.promptForRegistration)({
|
|
285
|
+
environment: envValue,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
// Show first-run registration prompt if needed (one-time only)
|
|
290
|
+
(0, attribution_1.showFirstRunPromptIfNeeded)({
|
|
291
|
+
environment: envValue,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
203
294
|
emitSdkInit(state);
|
|
204
295
|
}
|
|
205
296
|
function getSdkVersion() {
|
|
@@ -251,9 +342,9 @@ function applyAttributionConfig(config) {
|
|
|
251
342
|
}
|
|
252
343
|
: undefined,
|
|
253
344
|
apiKeyEnv: telemetry.api_key_env ?? undefined,
|
|
254
|
-
|
|
345
|
+
timeoutSec: typeof telemetry.timeout_sec === 'number' ? telemetry.timeout_sec : undefined,
|
|
255
346
|
retryAttempts: typeof telemetry.retry_attempts === 'number' ? telemetry.retry_attempts : undefined,
|
|
256
|
-
|
|
347
|
+
backoffBaseSec: typeof telemetry.backoff_base_sec === 'number' ? telemetry.backoff_base_sec : undefined,
|
|
257
348
|
});
|
|
258
349
|
if (telemetry.enabled || telemetry.send_data || attribution.enabled) {
|
|
259
350
|
(0, attribution_1.enableTelemetry)({
|
|
@@ -407,11 +498,26 @@ function recordFeatureFlags(config, attribution) {
|
|
|
407
498
|
* 1. Verifies all pending trace chains (if verify_on_shutdown enabled)
|
|
408
499
|
* 2. Finalizes all pending traces and emits report summaries
|
|
409
500
|
* 3. Closes all sinks and dispatchers
|
|
501
|
+
*
|
|
502
|
+
* @remarks
|
|
503
|
+
* Cross-SDK Parity: This function is async (returns Promise) in Node.js but
|
|
504
|
+
* synchronous in Python. This is an intentional language difference:
|
|
505
|
+
* - Node.js: `await monora.shutdown()`
|
|
506
|
+
* - Python: `monora.shutdown()`
|
|
410
507
|
*/
|
|
411
508
|
async function shutdown() {
|
|
412
509
|
if (!state) {
|
|
413
510
|
return;
|
|
414
511
|
}
|
|
512
|
+
// Send shutdown telemetry event (opt-in only)
|
|
513
|
+
if ((0, attribution_1.isTelemetryEnabled)()) {
|
|
514
|
+
(0, attribution_1.sendTelemetryEvent)({
|
|
515
|
+
eventType: 'shutdown',
|
|
516
|
+
eventData: {
|
|
517
|
+
service_name: state.config.defaults?.service_name,
|
|
518
|
+
},
|
|
519
|
+
});
|
|
520
|
+
}
|
|
415
521
|
// AUTOMATIC VERIFICATION ON SHUTDOWN
|
|
416
522
|
const immutability = state.config.immutability || {};
|
|
417
523
|
if (immutability.verify_on_shutdown !== false) {
|
|
@@ -420,6 +526,9 @@ async function shutdown() {
|
|
|
420
526
|
if (state.traceCompletionUnregister) {
|
|
421
527
|
state.traceCompletionUnregister();
|
|
422
528
|
}
|
|
529
|
+
if (state.traceStartUnregister) {
|
|
530
|
+
state.traceStartUnregister();
|
|
531
|
+
}
|
|
423
532
|
state.dispatcher.close();
|
|
424
533
|
// Cleanup and close WAL
|
|
425
534
|
if (state.config.wal?.enabled) {
|
|
@@ -456,7 +565,22 @@ function exportTrustPackage(traceId, options) {
|
|
|
456
565
|
if (!events) {
|
|
457
566
|
throw new Error('No events found for trust package; provide events, inputPath, or configure a file sink.');
|
|
458
567
|
}
|
|
459
|
-
let trustPackage = (0, trust_package_1.buildTrustPackage)(traceId, events, config
|
|
568
|
+
let trustPackage = (0, trust_package_1.buildTrustPackage)(traceId, events, config, {
|
|
569
|
+
evidenceManifest: options?.evidenceManifest,
|
|
570
|
+
evidenceManifestPath: options?.evidenceManifestPath,
|
|
571
|
+
evidenceManifestAuto: options?.evidenceManifestAuto,
|
|
572
|
+
evidenceManifestStandard: options?.evidenceManifestStandard,
|
|
573
|
+
evidenceManifestIncludeLineage: options?.evidenceManifestIncludeLineage,
|
|
574
|
+
evidenceManifestIncludeHashChain: options?.evidenceManifestIncludeHashChain,
|
|
575
|
+
evidenceManifestIncludeWorkflowState: options?.evidenceManifestIncludeWorkflowState,
|
|
576
|
+
evidenceManifestIncludeAimsState: options?.evidenceManifestIncludeAimsState,
|
|
577
|
+
controlCatalog: options?.controlCatalog,
|
|
578
|
+
controlCatalogPath: options?.controlCatalogPath,
|
|
579
|
+
controlWorkflowState: options?.controlWorkflowState,
|
|
580
|
+
controlWorkflowStatePath: options?.controlWorkflowStatePath,
|
|
581
|
+
controlCoverageTarget: options?.controlCoverageTarget,
|
|
582
|
+
controlCoveragePath: options?.controlCoveragePath,
|
|
583
|
+
});
|
|
460
584
|
if (options?.sign || options?.gpgKey || options?.gpgHome) {
|
|
461
585
|
trustPackage = (0, trust_package_1.applyGpgSignature)(trustPackage, {
|
|
462
586
|
gpgKey: options?.gpgKey,
|
|
@@ -549,6 +673,13 @@ function ensureState() {
|
|
|
549
673
|
}
|
|
550
674
|
return state;
|
|
551
675
|
}
|
|
676
|
+
/**
|
|
677
|
+
* Get the current Monora state, or null if not initialized.
|
|
678
|
+
* Use this for optional event emission where initialization isn't required.
|
|
679
|
+
*/
|
|
680
|
+
function getState() {
|
|
681
|
+
return state;
|
|
682
|
+
}
|
|
552
683
|
function isRecoveredEventValid(event, config) {
|
|
553
684
|
if (!event || typeof event !== 'object') {
|
|
554
685
|
return false;
|
|
@@ -640,7 +771,7 @@ function emitEvent(event, options) {
|
|
|
640
771
|
}
|
|
641
772
|
return;
|
|
642
773
|
}
|
|
643
|
-
if ((0, context_1.getCurrentSpan)()
|
|
774
|
+
if ((0, context_1.getCurrentSpan)()) {
|
|
644
775
|
try {
|
|
645
776
|
current.reportManager.recordEvent(event);
|
|
646
777
|
}
|
|
@@ -650,6 +781,38 @@ function emitEvent(event, options) {
|
|
|
650
781
|
}
|
|
651
782
|
current.dispatcher.emit(event);
|
|
652
783
|
}
|
|
784
|
+
function emitInternal(payload, body, options) {
|
|
785
|
+
const current = getState();
|
|
786
|
+
if (!current) {
|
|
787
|
+
return null;
|
|
788
|
+
}
|
|
789
|
+
const raw = typeof payload === 'object' && payload ? payload : {};
|
|
790
|
+
const eventType = (typeof payload === 'string' ? payload : undefined) ||
|
|
791
|
+
raw.event_type ||
|
|
792
|
+
'custom';
|
|
793
|
+
const eventBody = body !== undefined ? body : (raw.body || {});
|
|
794
|
+
const normalizedBody = eventBody && typeof eventBody === 'object' && !Array.isArray(eventBody)
|
|
795
|
+
? eventBody
|
|
796
|
+
: { value: eventBody };
|
|
797
|
+
const built = current.eventBuilder.build(eventType, normalizedBody, {
|
|
798
|
+
dataClassification: options?.dataClassification ||
|
|
799
|
+
raw.data_classification ||
|
|
800
|
+
current.config.defaults?.data_classification ||
|
|
801
|
+
'internal',
|
|
802
|
+
purpose: options?.purpose ||
|
|
803
|
+
raw.purpose ||
|
|
804
|
+
current.config.defaults?.purpose ||
|
|
805
|
+
'governance',
|
|
806
|
+
reason: options?.reason || raw.reason || undefined,
|
|
807
|
+
parentEventId: options?.parentEventId || raw.parent_event_id || undefined,
|
|
808
|
+
});
|
|
809
|
+
const traceId = options?.traceId || raw.trace_id;
|
|
810
|
+
if (traceId) {
|
|
811
|
+
built.trace_id = traceId;
|
|
812
|
+
}
|
|
813
|
+
emitEvent(built);
|
|
814
|
+
return built;
|
|
815
|
+
}
|
|
653
816
|
/**
|
|
654
817
|
* Verify event hash in real-time as it's emitted.
|
|
655
818
|
*
|
|
@@ -657,8 +820,10 @@ function emitEvent(event, options) {
|
|
|
657
820
|
* Failures are logged as warnings (user decision: don't block).
|
|
658
821
|
*/
|
|
659
822
|
function verifyEventRealTime(event, expectedPrevHash) {
|
|
660
|
-
|
|
661
|
-
|
|
823
|
+
const verifyEventHash = getVerifyEventHash();
|
|
824
|
+
if (!verifyEventHash) {
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
662
827
|
try {
|
|
663
828
|
if (!verifyEventHash(event)) {
|
|
664
829
|
logger_1.logger.error('Real-time verification FAILED: event %s has invalid hash', event.event_id);
|
|
@@ -673,8 +838,8 @@ function verifyEventRealTime(event, expectedPrevHash) {
|
|
|
673
838
|
}
|
|
674
839
|
function notifyViolation(violation) {
|
|
675
840
|
const current = ensureState();
|
|
676
|
-
// Record violation metric
|
|
677
|
-
const policyType = violation.
|
|
841
|
+
// Record violation metric (policyType matches Python SDK's policy_type attribute)
|
|
842
|
+
const policyType = violation.policyType || 'model_policy';
|
|
678
843
|
const model = violation.model || 'unknown';
|
|
679
844
|
(0, telemetry_1.recordViolation)(policyType, model);
|
|
680
845
|
if (!current.violationHandler) {
|
|
@@ -687,14 +852,43 @@ function notifyViolation(violation) {
|
|
|
687
852
|
logger_1.logger.error('Violation handler error: %s', error);
|
|
688
853
|
}
|
|
689
854
|
}
|
|
690
|
-
function
|
|
855
|
+
function emitSpanEvent(current, span, eventType, durationMs) {
|
|
856
|
+
const tracingConfig = current.config.tracing || {};
|
|
857
|
+
if (tracingConfig.emit_span_events === false) {
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
const body = {
|
|
861
|
+
name: span.name,
|
|
862
|
+
metadata: span.metadata,
|
|
863
|
+
};
|
|
864
|
+
if (durationMs !== undefined) {
|
|
865
|
+
body.duration_ms = durationMs;
|
|
866
|
+
}
|
|
867
|
+
const event = current.eventBuilder.build(eventType, body);
|
|
868
|
+
emitEvent(event);
|
|
869
|
+
}
|
|
870
|
+
function handleTraceStart(span) {
|
|
871
|
+
const current = state;
|
|
872
|
+
if (!current) {
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
emitSpanEvent(current, span, 'span_start');
|
|
876
|
+
}
|
|
877
|
+
function handleTraceCompletion(span) {
|
|
691
878
|
const current = state;
|
|
692
|
-
if (!current
|
|
879
|
+
if (!current) {
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
const traceStart = (0, context_1.getTraceStartTime)(span.traceId);
|
|
883
|
+
const durationMs = traceStart !== undefined ? Math.max(0, Date.now() - traceStart) : undefined;
|
|
884
|
+
emitSpanEvent(current, span, 'span_end', durationMs);
|
|
885
|
+
current.quotaManager.removeTrace(span.traceId);
|
|
886
|
+
if (!current.reportManager.isEnabled()) {
|
|
693
887
|
return;
|
|
694
888
|
}
|
|
695
889
|
try {
|
|
696
890
|
const registryMetadata = buildRegistryMetadata(current.registry);
|
|
697
|
-
const result = current.reportManager.finalizeTrace(traceId, registryMetadata);
|
|
891
|
+
const result = current.reportManager.finalizeTrace(span.traceId, registryMetadata);
|
|
698
892
|
if (!result) {
|
|
699
893
|
return;
|
|
700
894
|
}
|
|
@@ -755,6 +949,8 @@ function executeCall(current, fn, args, config, context) {
|
|
|
755
949
|
const purpose = options.purpose || current.config.defaults?.purpose || 'general';
|
|
756
950
|
const reason = options.reason;
|
|
757
951
|
const resolvedModel = options.model || resolveModelFromArgs(args, current.registry);
|
|
952
|
+
const span = (0, context_1.getCurrentSpan)();
|
|
953
|
+
const traceId = span ? span.traceId : null;
|
|
758
954
|
if (config.eventType === 'tool_call' && options.toolName) {
|
|
759
955
|
try {
|
|
760
956
|
const violation = current.toolPolicyEngine.checkTool(options.toolName);
|
|
@@ -870,6 +1066,9 @@ function executeCall(current, fn, args, config, context) {
|
|
|
870
1066
|
throw violation;
|
|
871
1067
|
}
|
|
872
1068
|
}
|
|
1069
|
+
if (config.eventType === 'llm_call') {
|
|
1070
|
+
applyQuotaLimits(current, traceId, resolvedModel, dataClassification, purpose, reason);
|
|
1071
|
+
}
|
|
873
1072
|
const startTime = perf_hooks_1.performance.now();
|
|
874
1073
|
const stepNumber = config.eventType === 'agent_step' ? (0, context_1.nextStepNumber)() : undefined;
|
|
875
1074
|
try {
|
|
@@ -899,9 +1098,17 @@ function executeCall(current, fn, args, config, context) {
|
|
|
899
1098
|
reason,
|
|
900
1099
|
});
|
|
901
1100
|
emitEvent(event);
|
|
1101
|
+
if (config.eventType === 'llm_call') {
|
|
1102
|
+
const { tokens, usage } = extractUsageMetrics(response);
|
|
1103
|
+
const cost = estimateUsageCost(current, resolvedModel, usage, tokens);
|
|
1104
|
+
applyQuotaLimits(current, traceId, resolvedModel, dataClassification, purpose, reason, tokens || [0, 0], cost ?? undefined);
|
|
1105
|
+
}
|
|
902
1106
|
return response;
|
|
903
1107
|
})
|
|
904
1108
|
.catch((error) => {
|
|
1109
|
+
if (error instanceof quotas_1.QuotaExceededError) {
|
|
1110
|
+
throw error;
|
|
1111
|
+
}
|
|
905
1112
|
const durationMs = perf_hooks_1.performance.now() - startTime;
|
|
906
1113
|
const logErrors = current.config.error_handling?.log_user_exceptions !== false;
|
|
907
1114
|
const body = config.buildBody({
|
|
@@ -925,6 +1132,9 @@ function executeCall(current, fn, args, config, context) {
|
|
|
925
1132
|
reason,
|
|
926
1133
|
});
|
|
927
1134
|
emitEvent(event);
|
|
1135
|
+
if (config.eventType === 'llm_call') {
|
|
1136
|
+
applyQuotaLimits(current, traceId, resolvedModel, dataClassification, purpose, reason, [0, 0], undefined, false);
|
|
1137
|
+
}
|
|
928
1138
|
throw error;
|
|
929
1139
|
});
|
|
930
1140
|
}
|
|
@@ -950,10 +1160,17 @@ function executeCall(current, fn, args, config, context) {
|
|
|
950
1160
|
reason,
|
|
951
1161
|
});
|
|
952
1162
|
emitEvent(event);
|
|
1163
|
+
if (config.eventType === 'llm_call') {
|
|
1164
|
+
const { tokens, usage } = extractUsageMetrics(result);
|
|
1165
|
+
const cost = estimateUsageCost(current, resolvedModel, usage, tokens);
|
|
1166
|
+
applyQuotaLimits(current, traceId, resolvedModel, dataClassification, purpose, reason, tokens || [0, 0], cost ?? undefined);
|
|
1167
|
+
}
|
|
953
1168
|
return result;
|
|
954
1169
|
}
|
|
955
1170
|
catch (error) {
|
|
956
|
-
if (error instanceof data_handling_1.DataHandlingViolation ||
|
|
1171
|
+
if (error instanceof data_handling_1.DataHandlingViolation ||
|
|
1172
|
+
error instanceof policy_1.PolicyViolation ||
|
|
1173
|
+
error instanceof quotas_1.QuotaExceededError) {
|
|
957
1174
|
throw error;
|
|
958
1175
|
}
|
|
959
1176
|
const durationMs = perf_hooks_1.performance.now() - startTime;
|
|
@@ -979,6 +1196,9 @@ function executeCall(current, fn, args, config, context) {
|
|
|
979
1196
|
reason,
|
|
980
1197
|
});
|
|
981
1198
|
emitEvent(event);
|
|
1199
|
+
if (config.eventType === 'llm_call') {
|
|
1200
|
+
applyQuotaLimits(current, traceId, resolvedModel, dataClassification, purpose, reason, [0, 0], undefined, false);
|
|
1201
|
+
}
|
|
982
1202
|
throw error;
|
|
983
1203
|
}
|
|
984
1204
|
}
|
|
@@ -1030,6 +1250,48 @@ function emitRegistryOverride(current, model, dataClassification, purpose, reaso
|
|
|
1030
1250
|
emitEvent(event);
|
|
1031
1251
|
return event.event_id || null;
|
|
1032
1252
|
}
|
|
1253
|
+
function applyQuotaLimits(current, traceId, model, dataClassification, purpose, reason, tokens, cost, raiseOnBlock = true) {
|
|
1254
|
+
if (!traceId || !current.quotaManager.isEnabled()) {
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
const tracker = current.quotaManager.getTracker(traceId);
|
|
1258
|
+
if (tokens || cost !== undefined) {
|
|
1259
|
+
const [promptTokens, completionTokens] = tokens || [0, 0];
|
|
1260
|
+
tracker.recordTokenUsage(promptTokens, completionTokens, cost);
|
|
1261
|
+
}
|
|
1262
|
+
const result = tracker.checkQuotas();
|
|
1263
|
+
if (result.allowed) {
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
emitQuotaViolation(current.eventBuilder, model, result, dataClassification, purpose, reason);
|
|
1267
|
+
const violation = new quotas_1.QuotaExceededError(result.exceededQuota || 'unknown', result.currentValue || 0, result.limitValue || 0, model);
|
|
1268
|
+
notifyViolation(violation);
|
|
1269
|
+
const action = tracker.getConfig().action;
|
|
1270
|
+
if (action === 'block' && raiseOnBlock) {
|
|
1271
|
+
throw violation;
|
|
1272
|
+
}
|
|
1273
|
+
logger_1.logger.warning('Quota warning: %s', result.message);
|
|
1274
|
+
}
|
|
1275
|
+
function emitQuotaViolation(builder, model, result, dataClassification, purpose, reason) {
|
|
1276
|
+
const body = {
|
|
1277
|
+
model,
|
|
1278
|
+
policy_name: `quota.${result.exceededQuota || 'unknown'}`,
|
|
1279
|
+
message: result.message,
|
|
1280
|
+
status: 'policy_violation',
|
|
1281
|
+
error: null,
|
|
1282
|
+
quota: {
|
|
1283
|
+
type: result.exceededQuota ?? null,
|
|
1284
|
+
current_value: result.currentValue ?? null,
|
|
1285
|
+
limit_value: result.limitValue ?? null,
|
|
1286
|
+
},
|
|
1287
|
+
};
|
|
1288
|
+
const event = builder.build('llm_call', body, {
|
|
1289
|
+
dataClassification,
|
|
1290
|
+
purpose,
|
|
1291
|
+
reason,
|
|
1292
|
+
});
|
|
1293
|
+
emitEvent(event);
|
|
1294
|
+
}
|
|
1033
1295
|
function buildLlmBody(params) {
|
|
1034
1296
|
const responseDetails = params.response ? extractResponseDetails(params.response) : null;
|
|
1035
1297
|
let explanation;
|
|
@@ -1101,6 +1363,132 @@ function extractResponseDetails(response) {
|
|
|
1101
1363
|
}
|
|
1102
1364
|
return { content, finish_reason: finishReason, usage };
|
|
1103
1365
|
}
|
|
1366
|
+
function extractUsageMetrics(response) {
|
|
1367
|
+
if (!response) {
|
|
1368
|
+
return { tokens: null, usage: null };
|
|
1369
|
+
}
|
|
1370
|
+
const details = extractResponseDetails(response);
|
|
1371
|
+
const usage = details && typeof details === 'object' ? details.usage : null;
|
|
1372
|
+
return { tokens: normalizeTokenUsage(usage), usage };
|
|
1373
|
+
}
|
|
1374
|
+
function estimateUsageCost(current, model, usage, tokens) {
|
|
1375
|
+
if (!usage || typeof usage !== 'object') {
|
|
1376
|
+
return null;
|
|
1377
|
+
}
|
|
1378
|
+
for (const key of ['total_cost', 'cost', 'estimated_cost', 'total_cost_usd', 'cost_usd']) {
|
|
1379
|
+
const value = usage[key];
|
|
1380
|
+
const cost = coerceCost(value);
|
|
1381
|
+
if (cost !== null) {
|
|
1382
|
+
return cost;
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
if (!model || !tokens) {
|
|
1386
|
+
return null;
|
|
1387
|
+
}
|
|
1388
|
+
const metadata = current.registry.getModelMetadata(model);
|
|
1389
|
+
const pricing = metadata && typeof metadata === 'object' ? metadata.pricing : null;
|
|
1390
|
+
return estimateCostFromPricing(pricing, tokens);
|
|
1391
|
+
}
|
|
1392
|
+
function estimateCostFromPricing(pricing, tokens) {
|
|
1393
|
+
if (!pricing || typeof pricing !== 'object') {
|
|
1394
|
+
return null;
|
|
1395
|
+
}
|
|
1396
|
+
const [promptTokens, completionTokens] = tokens;
|
|
1397
|
+
const pickRatePerToken = (candidates) => {
|
|
1398
|
+
for (const candidate of candidates) {
|
|
1399
|
+
if (!(candidate.key in pricing)) {
|
|
1400
|
+
continue;
|
|
1401
|
+
}
|
|
1402
|
+
const cost = coerceCost(pricing[candidate.key]);
|
|
1403
|
+
if (cost === null) {
|
|
1404
|
+
continue;
|
|
1405
|
+
}
|
|
1406
|
+
return cost / candidate.scale;
|
|
1407
|
+
}
|
|
1408
|
+
return null;
|
|
1409
|
+
};
|
|
1410
|
+
const totalRate = pickRatePerToken([
|
|
1411
|
+
{ key: 'cost_per_token', scale: 1 },
|
|
1412
|
+
{ key: 'cost_per_1k_tokens', scale: 1000 },
|
|
1413
|
+
{ key: 'total_cost_per_1k', scale: 1000 },
|
|
1414
|
+
{ key: 'cost_per_1m_tokens', scale: 1000000 },
|
|
1415
|
+
{ key: 'total_cost_per_1m', scale: 1000000 },
|
|
1416
|
+
]);
|
|
1417
|
+
const promptRate = pickRatePerToken([
|
|
1418
|
+
{ key: 'prompt_cost_per_token', scale: 1 },
|
|
1419
|
+
{ key: 'input_cost_per_token', scale: 1 },
|
|
1420
|
+
{ key: 'prompt_cost_per_1k', scale: 1000 },
|
|
1421
|
+
{ key: 'input_cost_per_1k', scale: 1000 },
|
|
1422
|
+
{ key: 'prompt_cost_per_1m', scale: 1000000 },
|
|
1423
|
+
{ key: 'input_cost_per_1m', scale: 1000000 },
|
|
1424
|
+
]);
|
|
1425
|
+
const completionRate = pickRatePerToken([
|
|
1426
|
+
{ key: 'completion_cost_per_token', scale: 1 },
|
|
1427
|
+
{ key: 'output_cost_per_token', scale: 1 },
|
|
1428
|
+
{ key: 'completion_cost_per_1k', scale: 1000 },
|
|
1429
|
+
{ key: 'output_cost_per_1k', scale: 1000 },
|
|
1430
|
+
{ key: 'completion_cost_per_1m', scale: 1000000 },
|
|
1431
|
+
{ key: 'output_cost_per_1m', scale: 1000000 },
|
|
1432
|
+
]);
|
|
1433
|
+
if (totalRate !== null) {
|
|
1434
|
+
return (promptTokens + completionTokens) * totalRate;
|
|
1435
|
+
}
|
|
1436
|
+
if (promptRate === null && completionRate === null) {
|
|
1437
|
+
return null;
|
|
1438
|
+
}
|
|
1439
|
+
let cost = 0;
|
|
1440
|
+
if (promptRate !== null) {
|
|
1441
|
+
cost += promptTokens * promptRate;
|
|
1442
|
+
}
|
|
1443
|
+
if (completionRate !== null) {
|
|
1444
|
+
cost += completionTokens * completionRate;
|
|
1445
|
+
}
|
|
1446
|
+
return cost;
|
|
1447
|
+
}
|
|
1448
|
+
function normalizeTokenUsage(usage) {
|
|
1449
|
+
if (!usage || typeof usage !== 'object') {
|
|
1450
|
+
return null;
|
|
1451
|
+
}
|
|
1452
|
+
let prompt = coerceToken(usage.prompt_tokens);
|
|
1453
|
+
let completion = coerceToken(usage.completion_tokens);
|
|
1454
|
+
if (prompt === null) {
|
|
1455
|
+
prompt = coerceToken(usage.input_tokens);
|
|
1456
|
+
}
|
|
1457
|
+
if (completion === null) {
|
|
1458
|
+
completion = coerceToken(usage.output_tokens);
|
|
1459
|
+
}
|
|
1460
|
+
const total = coerceToken(usage.total_tokens);
|
|
1461
|
+
if (prompt === null && completion === null && total === null) {
|
|
1462
|
+
return null;
|
|
1463
|
+
}
|
|
1464
|
+
if (prompt === null) {
|
|
1465
|
+
prompt = Math.max(0, (total || 0) - (completion || 0));
|
|
1466
|
+
}
|
|
1467
|
+
if (completion === null) {
|
|
1468
|
+
completion = Math.max(0, (total || 0) - (prompt || 0));
|
|
1469
|
+
}
|
|
1470
|
+
return [prompt || 0, completion || 0];
|
|
1471
|
+
}
|
|
1472
|
+
function coerceToken(value) {
|
|
1473
|
+
if (value === null || value === undefined) {
|
|
1474
|
+
return null;
|
|
1475
|
+
}
|
|
1476
|
+
const number = Number(value);
|
|
1477
|
+
if (!Number.isFinite(number)) {
|
|
1478
|
+
return null;
|
|
1479
|
+
}
|
|
1480
|
+
return Math.trunc(number);
|
|
1481
|
+
}
|
|
1482
|
+
function coerceCost(value) {
|
|
1483
|
+
if (value === null || value === undefined) {
|
|
1484
|
+
return null;
|
|
1485
|
+
}
|
|
1486
|
+
const number = Number(value);
|
|
1487
|
+
if (!Number.isFinite(number)) {
|
|
1488
|
+
return null;
|
|
1489
|
+
}
|
|
1490
|
+
return number;
|
|
1491
|
+
}
|
|
1104
1492
|
function formatError(error) {
|
|
1105
1493
|
return { type: error.name || 'Error', message: error.message || String(error) };
|
|
1106
1494
|
}
|
|
@@ -1180,7 +1568,7 @@ function buildSinks(sinkConfigs, failFast) {
|
|
|
1180
1568
|
const sinkType = String(config.type || '').toLowerCase();
|
|
1181
1569
|
try {
|
|
1182
1570
|
if (sinkType === 'stdout') {
|
|
1183
|
-
sinks.push(new sinks_1.StdoutSink(config.format || '
|
|
1571
|
+
sinks.push(new sinks_1.StdoutSink(config.format || 'pretty'));
|
|
1184
1572
|
}
|
|
1185
1573
|
else if (sinkType === 'file') {
|
|
1186
1574
|
sinks.push(new sinks_1.FileSink(config.path, {
|
|
@@ -1188,6 +1576,7 @@ function buildSinks(sinkConfigs, failFast) {
|
|
|
1188
1576
|
flushIntervalSec: config.flush_interval_sec || 5.0,
|
|
1189
1577
|
rotation: config.rotation || 'none',
|
|
1190
1578
|
maxSizeMb: config.max_size_mb,
|
|
1579
|
+
symlink: config.symlink,
|
|
1191
1580
|
}));
|
|
1192
1581
|
}
|
|
1193
1582
|
else if (sinkType === 'https' || sinkType === 'http') {
|
|
@@ -1206,6 +1595,12 @@ function buildSinks(sinkConfigs, failFast) {
|
|
|
1206
1595
|
headerName: config.idempotency.header_name,
|
|
1207
1596
|
}
|
|
1208
1597
|
: undefined;
|
|
1598
|
+
const dataResidencyConfig = config.data_residency
|
|
1599
|
+
? {
|
|
1600
|
+
residency: config.data_residency,
|
|
1601
|
+
action: config.data_residency_action,
|
|
1602
|
+
}
|
|
1603
|
+
: undefined;
|
|
1209
1604
|
sinks.push(new sinks_1.HttpSink(config.endpoint, headers, {
|
|
1210
1605
|
batchSize: config.batch_size || 50,
|
|
1211
1606
|
timeoutSec: config.timeout_sec || 10.0,
|
|
@@ -1214,6 +1609,7 @@ function buildSinks(sinkConfigs, failFast) {
|
|
|
1214
1609
|
circuitBreaker: config.circuit_breaker,
|
|
1215
1610
|
retryQueue: retryQueueConfig,
|
|
1216
1611
|
idempotency: idempotencyConfig,
|
|
1612
|
+
dataResidency: dataResidencyConfig,
|
|
1217
1613
|
}));
|
|
1218
1614
|
}
|
|
1219
1615
|
else if (sinkType) {
|
|
@@ -1252,5 +1648,6 @@ function findEventSource(config) {
|
|
|
1252
1648
|
return String(sink.path);
|
|
1253
1649
|
}
|
|
1254
1650
|
}
|
|
1255
|
-
|
|
1651
|
+
const fallback = config.error_handling?.fallback_path;
|
|
1652
|
+
return fallback ? String(fallback) : undefined;
|
|
1256
1653
|
}
|