@axonflow/sdk 2.2.0 → 3.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/README.md +185 -8
- package/dist/cjs/client.d.ts +530 -8
- package/dist/cjs/client.d.ts.map +1 -1
- package/dist/cjs/client.js +1760 -45
- package/dist/cjs/client.js.map +1 -1
- package/dist/cjs/errors.d.ts +22 -0
- package/dist/cjs/errors.d.ts.map +1 -1
- package/dist/cjs/errors.js +32 -1
- package/dist/cjs/errors.js.map +1 -1
- package/dist/cjs/index.d.ts +10 -14
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +11 -20
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/connector.d.ts +82 -0
- package/dist/cjs/types/connector.d.ts.map +1 -1
- package/dist/cjs/types/connector.js +7 -0
- package/dist/cjs/types/connector.js.map +1 -1
- package/dist/cjs/types/execution.d.ts +227 -0
- package/dist/cjs/types/execution.d.ts.map +1 -0
- package/dist/cjs/types/execution.js +73 -0
- package/dist/cjs/types/execution.js.map +1 -0
- package/dist/cjs/types/index.d.ts +3 -0
- package/dist/cjs/types/index.d.ts.map +1 -1
- package/dist/cjs/types/index.js +3 -0
- package/dist/cjs/types/index.js.map +1 -1
- package/dist/cjs/types/masfeat.d.ts +238 -0
- package/dist/cjs/types/masfeat.d.ts.map +1 -0
- package/dist/cjs/types/masfeat.js +11 -0
- package/dist/cjs/types/masfeat.js.map +1 -0
- package/dist/cjs/types/planning.d.ts +126 -1
- package/dist/cjs/types/planning.d.ts.map +1 -1
- package/dist/cjs/types/policies.d.ts +19 -1
- package/dist/cjs/types/policies.d.ts.map +1 -1
- package/dist/cjs/types/proxy.d.ts +29 -3
- package/dist/cjs/types/proxy.d.ts.map +1 -1
- package/dist/cjs/types/workflows.d.ts +318 -0
- package/dist/cjs/types/workflows.d.ts.map +1 -0
- package/dist/cjs/types/workflows.js +61 -0
- package/dist/cjs/types/workflows.js.map +1 -0
- package/dist/esm/client.d.ts +530 -8
- package/dist/esm/client.d.ts.map +1 -1
- package/dist/esm/client.js +1761 -46
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/errors.d.ts +22 -0
- package/dist/esm/errors.d.ts.map +1 -1
- package/dist/esm/errors.js +30 -0
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.d.ts +10 -14
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +7 -15
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/connector.d.ts +82 -0
- package/dist/esm/types/connector.d.ts.map +1 -1
- package/dist/esm/types/connector.js +6 -1
- package/dist/esm/types/connector.js.map +1 -1
- package/dist/esm/types/execution.d.ts +227 -0
- package/dist/esm/types/execution.d.ts.map +1 -0
- package/dist/esm/types/execution.js +70 -0
- package/dist/esm/types/execution.js.map +1 -0
- package/dist/esm/types/index.d.ts +3 -0
- package/dist/esm/types/index.d.ts.map +1 -1
- package/dist/esm/types/index.js +3 -0
- package/dist/esm/types/index.js.map +1 -1
- package/dist/esm/types/masfeat.d.ts +238 -0
- package/dist/esm/types/masfeat.d.ts.map +1 -0
- package/dist/esm/types/masfeat.js +10 -0
- package/dist/esm/types/masfeat.js.map +1 -0
- package/dist/esm/types/planning.d.ts +126 -1
- package/dist/esm/types/planning.d.ts.map +1 -1
- package/dist/esm/types/policies.d.ts +19 -1
- package/dist/esm/types/policies.d.ts.map +1 -1
- package/dist/esm/types/proxy.d.ts +29 -3
- package/dist/esm/types/proxy.d.ts.map +1 -1
- package/dist/esm/types/workflows.d.ts +318 -0
- package/dist/esm/types/workflows.d.ts.map +1 -0
- package/dist/esm/types/workflows.js +58 -0
- package/dist/esm/types/workflows.js.map +1 -0
- package/package.json +7 -2
- package/dist/cjs/interceptors/anthropic.d.ts +0 -40
- package/dist/cjs/interceptors/anthropic.d.ts.map +0 -1
- package/dist/cjs/interceptors/anthropic.js +0 -101
- package/dist/cjs/interceptors/anthropic.js.map +0 -1
- package/dist/cjs/interceptors/base.d.ts +0 -23
- package/dist/cjs/interceptors/base.d.ts.map +0 -1
- package/dist/cjs/interceptors/base.js +0 -10
- package/dist/cjs/interceptors/base.js.map +0 -1
- package/dist/cjs/interceptors/bedrock.d.ts +0 -142
- package/dist/cjs/interceptors/bedrock.d.ts.map +0 -1
- package/dist/cjs/interceptors/bedrock.js +0 -263
- package/dist/cjs/interceptors/bedrock.js.map +0 -1
- package/dist/cjs/interceptors/gemini.d.ts +0 -89
- package/dist/cjs/interceptors/gemini.d.ts.map +0 -1
- package/dist/cjs/interceptors/gemini.js +0 -121
- package/dist/cjs/interceptors/gemini.js.map +0 -1
- package/dist/cjs/interceptors/ollama.d.ts +0 -143
- package/dist/cjs/interceptors/ollama.d.ts.map +0 -1
- package/dist/cjs/interceptors/ollama.js +0 -153
- package/dist/cjs/interceptors/ollama.js.map +0 -1
- package/dist/cjs/interceptors/openai.d.ts +0 -40
- package/dist/cjs/interceptors/openai.d.ts.map +0 -1
- package/dist/cjs/interceptors/openai.js +0 -100
- package/dist/cjs/interceptors/openai.js.map +0 -1
- package/dist/esm/interceptors/anthropic.d.ts +0 -40
- package/dist/esm/interceptors/anthropic.d.ts.map +0 -1
- package/dist/esm/interceptors/anthropic.js +0 -96
- package/dist/esm/interceptors/anthropic.js.map +0 -1
- package/dist/esm/interceptors/base.d.ts +0 -23
- package/dist/esm/interceptors/base.d.ts.map +0 -1
- package/dist/esm/interceptors/base.js +0 -6
- package/dist/esm/interceptors/base.js.map +0 -1
- package/dist/esm/interceptors/bedrock.d.ts +0 -142
- package/dist/esm/interceptors/bedrock.d.ts.map +0 -1
- package/dist/esm/interceptors/bedrock.js +0 -224
- package/dist/esm/interceptors/bedrock.js.map +0 -1
- package/dist/esm/interceptors/gemini.d.ts +0 -89
- package/dist/esm/interceptors/gemini.d.ts.map +0 -1
- package/dist/esm/interceptors/gemini.js +0 -116
- package/dist/esm/interceptors/gemini.js.map +0 -1
- package/dist/esm/interceptors/ollama.d.ts +0 -143
- package/dist/esm/interceptors/ollama.d.ts.map +0 -1
- package/dist/esm/interceptors/ollama.js +0 -147
- package/dist/esm/interceptors/ollama.js.map +0 -1
- package/dist/esm/interceptors/openai.d.ts +0 -40
- package/dist/esm/interceptors/openai.d.ts.map +0 -1
- package/dist/esm/interceptors/openai.js +0 -95
- package/dist/esm/interceptors/openai.js.map +0 -1
package/dist/cjs/client.js
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AxonFlow = void 0;
|
|
4
4
|
const errors_1 = require("./errors");
|
|
5
|
-
const openai_1 = require("./interceptors/openai");
|
|
6
|
-
const anthropic_1 = require("./interceptors/anthropic");
|
|
7
5
|
const helpers_1 = require("./utils/helpers");
|
|
8
6
|
/**
|
|
9
7
|
* Main AxonFlow client for invisible AI governance
|
|
@@ -27,7 +25,7 @@ class AxonFlow {
|
|
|
27
25
|
clientSecret: config.clientSecret,
|
|
28
26
|
endpoint,
|
|
29
27
|
mode: config.mode || (hasCredentials ? 'production' : 'sandbox'),
|
|
30
|
-
tenant: config.tenant || '
|
|
28
|
+
tenant: config.tenant || '',
|
|
31
29
|
debug: config.debug || false,
|
|
32
30
|
timeout: config.timeout || 30000,
|
|
33
31
|
mapTimeout: config.mapTimeout || 120000, // 2 minutes for MAP operations
|
|
@@ -41,8 +39,8 @@ class AxonFlow {
|
|
|
41
39
|
ttl: config.cache?.ttl || 60000,
|
|
42
40
|
},
|
|
43
41
|
};
|
|
44
|
-
//
|
|
45
|
-
this.interceptors = [
|
|
42
|
+
// Interceptors removed in v3.0.0 (deprecated wrapOpenAIClient/wrapAnthropicClient)
|
|
43
|
+
this.interceptors = [];
|
|
46
44
|
if (this.config.debug) {
|
|
47
45
|
// Determine auth method for logging
|
|
48
46
|
const authMethod = hasCredentials ? 'client-credentials' : 'community (no auth)';
|
|
@@ -67,10 +65,25 @@ class AxonFlow {
|
|
|
67
65
|
if (this.config.clientId && this.config.clientSecret) {
|
|
68
66
|
const credentials = Buffer.from(`${this.config.clientId}:${this.config.clientSecret}`).toString('base64');
|
|
69
67
|
headers['Authorization'] = `Basic ${credentials}`;
|
|
68
|
+
}
|
|
69
|
+
// Always add X-Tenant-ID when clientId is set (required for multi-tenant APIs)
|
|
70
|
+
if (this.config.clientId) {
|
|
70
71
|
headers['X-Tenant-ID'] = this.config.clientId;
|
|
71
72
|
}
|
|
72
73
|
return headers;
|
|
73
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Get the effective clientId, using smart default for community mode.
|
|
77
|
+
*
|
|
78
|
+
* Returns the configured clientId if set, otherwise returns "community"
|
|
79
|
+
* as a smart default. This enables zero-config usage for community/self-hosted
|
|
80
|
+
* deployments while still supporting enterprise deployments with explicit credentials.
|
|
81
|
+
*
|
|
82
|
+
* @returns The clientId to use in requests
|
|
83
|
+
*/
|
|
84
|
+
getEffectiveClientId() {
|
|
85
|
+
return this.config.clientId || this.config.tenant || 'community';
|
|
86
|
+
}
|
|
74
87
|
/**
|
|
75
88
|
* Main method to protect AI calls with governance
|
|
76
89
|
* @param aiCall The AI call to protect
|
|
@@ -108,7 +121,7 @@ class AxonFlow {
|
|
|
108
121
|
*
|
|
109
122
|
* **Proxy Mode:**
|
|
110
123
|
* ```typescript
|
|
111
|
-
* const response = await axonflow.
|
|
124
|
+
* const response = await axonflow.proxyLLMCall({
|
|
112
125
|
* userToken: 'user-123',
|
|
113
126
|
* query: 'Your prompt here',
|
|
114
127
|
* requestType: 'chat'
|
|
@@ -118,8 +131,8 @@ class AxonFlow {
|
|
|
118
131
|
* See: https://docs.getaxonflow.com/sdk/gateway-mode
|
|
119
132
|
*/
|
|
120
133
|
async protect(aiCall) {
|
|
121
|
-
console.warn('[AxonFlow] protect() is deprecated and will be removed in
|
|
122
|
-
'Use Gateway Mode (getPolicyApprovedContext + auditLLMCall) or Proxy Mode (
|
|
134
|
+
console.warn('[AxonFlow] protect() is deprecated and will be removed in a future version. ' +
|
|
135
|
+
'Use Gateway Mode (getPolicyApprovedContext + auditLLMCall) or Proxy Mode (proxyLLMCall) instead. ' +
|
|
123
136
|
'See: https://docs.getaxonflow.com/sdk/gateway-mode');
|
|
124
137
|
try {
|
|
125
138
|
// Extract request details from the AI call
|
|
@@ -380,10 +393,19 @@ class AxonFlow {
|
|
|
380
393
|
}
|
|
381
394
|
}
|
|
382
395
|
/**
|
|
383
|
-
*
|
|
396
|
+
* Send a query through AxonFlow with full policy enforcement (Proxy Mode).
|
|
397
|
+
*
|
|
398
|
+
* This is Proxy Mode - AxonFlow acts as an intermediary, making the LLM call on your behalf.
|
|
384
399
|
*
|
|
385
|
-
*
|
|
386
|
-
*
|
|
400
|
+
* Use this when you want AxonFlow to:
|
|
401
|
+
* - Evaluate policies before the LLM call
|
|
402
|
+
* - Make the LLM call to the configured provider
|
|
403
|
+
* - Filter/redact sensitive data from responses
|
|
404
|
+
* - Automatically track costs and audit the interaction
|
|
405
|
+
*
|
|
406
|
+
* For Gateway Mode (lower latency, you make the LLM call), use:
|
|
407
|
+
* - getPolicyApprovedContext() before your LLM call
|
|
408
|
+
* - auditLLMCall() after your LLM call
|
|
387
409
|
*
|
|
388
410
|
* @param options - Query execution options
|
|
389
411
|
* @returns ExecuteQueryResponse with results or error information
|
|
@@ -393,7 +415,7 @@ class AxonFlow {
|
|
|
393
415
|
*
|
|
394
416
|
* @example
|
|
395
417
|
* ```typescript
|
|
396
|
-
* const response = await axonflow.
|
|
418
|
+
* const response = await axonflow.proxyLLMCall({
|
|
397
419
|
* userToken: 'user-123',
|
|
398
420
|
* query: 'Explain quantum computing',
|
|
399
421
|
* requestType: 'chat',
|
|
@@ -405,13 +427,13 @@ class AxonFlow {
|
|
|
405
427
|
* }
|
|
406
428
|
* ```
|
|
407
429
|
*/
|
|
408
|
-
async
|
|
430
|
+
async proxyLLMCall(options) {
|
|
409
431
|
// Default to "anonymous" if userToken is empty/undefined (community mode)
|
|
410
432
|
const effectiveUserToken = options.userToken || 'anonymous';
|
|
411
433
|
const agentRequest = {
|
|
412
434
|
query: options.query,
|
|
413
435
|
user_token: effectiveUserToken,
|
|
414
|
-
client_id: this.config.tenant,
|
|
436
|
+
client_id: this.config.clientId || this.config.tenant,
|
|
415
437
|
request_type: options.requestType,
|
|
416
438
|
context: options.context || {},
|
|
417
439
|
};
|
|
@@ -421,7 +443,7 @@ class AxonFlow {
|
|
|
421
443
|
...this.getAuthHeaders(),
|
|
422
444
|
};
|
|
423
445
|
if (this.config.debug) {
|
|
424
|
-
(0, helpers_1.debugLog)('Proxy Mode:
|
|
446
|
+
(0, helpers_1.debugLog)('Proxy Mode: proxyLLMCall', {
|
|
425
447
|
requestType: options.requestType,
|
|
426
448
|
query: options.query.substring(0, 50),
|
|
427
449
|
});
|
|
@@ -432,9 +454,28 @@ class AxonFlow {
|
|
|
432
454
|
body: JSON.stringify(agentRequest),
|
|
433
455
|
signal: AbortSignal.timeout(this.config.timeout),
|
|
434
456
|
});
|
|
457
|
+
let data;
|
|
435
458
|
if (!response.ok) {
|
|
436
459
|
const errorText = await response.text();
|
|
437
|
-
|
|
460
|
+
// Handle HTTP 402 (Payment Required) for budget exceeded - parse as blocked response with budgetInfo
|
|
461
|
+
if (response.status === 402) {
|
|
462
|
+
try {
|
|
463
|
+
data = JSON.parse(errorText);
|
|
464
|
+
// If it has budget_info, treat as valid blocked response (fall through to normal processing)
|
|
465
|
+
if (data.budget_info) {
|
|
466
|
+
// Fall through to normal response processing below
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
throw new errors_1.APIError(response.status, 'Payment Required', errorText);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
catch (e) {
|
|
473
|
+
if (e instanceof errors_1.APIError)
|
|
474
|
+
throw e;
|
|
475
|
+
throw new errors_1.APIError(response.status, 'Payment Required', errorText);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
else if (response.status === 401 || response.status === 403) {
|
|
438
479
|
// Try to parse as JSON for policy violation info
|
|
439
480
|
try {
|
|
440
481
|
const errorJson = JSON.parse(errorText);
|
|
@@ -448,11 +489,17 @@ class AxonFlow {
|
|
|
448
489
|
}
|
|
449
490
|
throw new errors_1.AuthenticationError(`Request failed: ${errorText}`);
|
|
450
491
|
}
|
|
451
|
-
|
|
492
|
+
else {
|
|
493
|
+
throw new errors_1.APIError(response.status, response.statusText, errorText);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
// Parse response if not already parsed (from 402 handling)
|
|
497
|
+
if (!data) {
|
|
498
|
+
data = await response.json();
|
|
452
499
|
}
|
|
453
|
-
const data = await response.json();
|
|
454
500
|
// Check for policy violation in successful response (some blocked responses return 200)
|
|
455
|
-
|
|
501
|
+
// Note: Don't throw for budget blocks (402 responses) - return with budgetInfo instead
|
|
502
|
+
if (data.blocked && !data.budget_info) {
|
|
456
503
|
throw new errors_1.PolicyViolationError(data.block_reason || 'Request blocked by policy', data.policy_info?.policies_evaluated);
|
|
457
504
|
}
|
|
458
505
|
// Transform snake_case response to camelCase
|
|
@@ -477,8 +524,20 @@ class AxonFlow {
|
|
|
477
524
|
codeArtifact: data.policy_info.code_artifact,
|
|
478
525
|
};
|
|
479
526
|
}
|
|
527
|
+
// Parse budget info if present (Issue #1082)
|
|
528
|
+
if (data.budget_info) {
|
|
529
|
+
result.budgetInfo = {
|
|
530
|
+
budgetId: data.budget_info.budget_id,
|
|
531
|
+
budgetName: data.budget_info.budget_name,
|
|
532
|
+
usedUsd: data.budget_info.used_usd || 0,
|
|
533
|
+
limitUsd: data.budget_info.limit_usd || 0,
|
|
534
|
+
percentage: data.budget_info.percentage || 0,
|
|
535
|
+
exceeded: data.budget_info.exceeded || false,
|
|
536
|
+
action: data.budget_info.action,
|
|
537
|
+
};
|
|
538
|
+
}
|
|
480
539
|
if (this.config.debug) {
|
|
481
|
-
(0, helpers_1.debugLog)('Proxy Mode:
|
|
540
|
+
(0, helpers_1.debugLog)('Proxy Mode: proxyLLMCall result', {
|
|
482
541
|
success: result.success,
|
|
483
542
|
blocked: result.blocked,
|
|
484
543
|
hasData: !!result.data,
|
|
@@ -578,19 +637,115 @@ class AxonFlow {
|
|
|
578
637
|
meta: agentResponse.metadata,
|
|
579
638
|
};
|
|
580
639
|
}
|
|
640
|
+
/**
|
|
641
|
+
* Execute a query directly against the MCP connector endpoint.
|
|
642
|
+
*
|
|
643
|
+
* This method calls the agent's /mcp/resources/query endpoint which provides:
|
|
644
|
+
* - Request-phase policy evaluation (SQLi blocking, PII blocking)
|
|
645
|
+
* - Response-phase policy evaluation (PII redaction)
|
|
646
|
+
* - PolicyInfo metadata in responses
|
|
647
|
+
*
|
|
648
|
+
* @example
|
|
649
|
+
* ```typescript
|
|
650
|
+
* const response = await axonflow.mcpQuery({
|
|
651
|
+
* connector: 'postgres',
|
|
652
|
+
* statement: 'SELECT * FROM customers LIMIT 10',
|
|
653
|
+
* });
|
|
654
|
+
*
|
|
655
|
+
* if (response.redacted) {
|
|
656
|
+
* console.log('Fields redacted:', response.redacted_fields);
|
|
657
|
+
* }
|
|
658
|
+
* console.log('Policies evaluated:', response.policy_info?.policies_evaluated);
|
|
659
|
+
* ```
|
|
660
|
+
*
|
|
661
|
+
* @param options - Query options including connector name and SQL statement
|
|
662
|
+
* @returns ConnectorResponse with data, redaction info, and policy_info
|
|
663
|
+
* @throws ConnectorError if the request is blocked by policy or fails
|
|
664
|
+
*/
|
|
665
|
+
async mcpQuery(options) {
|
|
666
|
+
if (!options.connector) {
|
|
667
|
+
throw new errors_1.ConnectorError('connector name is required', undefined, 'mcpQuery');
|
|
668
|
+
}
|
|
669
|
+
if (!options.statement) {
|
|
670
|
+
throw new errors_1.ConnectorError('statement is required', undefined, 'mcpQuery');
|
|
671
|
+
}
|
|
672
|
+
const url = `${this.config.endpoint}/mcp/resources/query`;
|
|
673
|
+
const headers = {
|
|
674
|
+
'Content-Type': 'application/json',
|
|
675
|
+
...this.getAuthHeaders(),
|
|
676
|
+
};
|
|
677
|
+
const body = {
|
|
678
|
+
connector: options.connector,
|
|
679
|
+
statement: options.statement,
|
|
680
|
+
options: options.options || {},
|
|
681
|
+
};
|
|
682
|
+
if (this.config.debug) {
|
|
683
|
+
(0, helpers_1.debugLog)('MCP Query', {
|
|
684
|
+
connector: options.connector,
|
|
685
|
+
statement: options.statement.substring(0, 50),
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
const response = await fetch(url, {
|
|
689
|
+
method: 'POST',
|
|
690
|
+
headers,
|
|
691
|
+
body: JSON.stringify(body),
|
|
692
|
+
signal: AbortSignal.timeout(this.config.timeout),
|
|
693
|
+
});
|
|
694
|
+
const responseData = await response.json();
|
|
695
|
+
// Handle policy blocks (403 responses)
|
|
696
|
+
if (!response.ok) {
|
|
697
|
+
throw new errors_1.ConnectorError(responseData.error || `MCP query failed: ${response.status} ${response.statusText}`, options.connector, 'mcpQuery');
|
|
698
|
+
}
|
|
699
|
+
if (this.config.debug) {
|
|
700
|
+
(0, helpers_1.debugLog)('MCP Query result', {
|
|
701
|
+
connector: options.connector,
|
|
702
|
+
success: responseData.success,
|
|
703
|
+
redacted: responseData.redacted,
|
|
704
|
+
policiesEvaluated: responseData.policy_info?.policies_evaluated,
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
return {
|
|
708
|
+
success: responseData.success,
|
|
709
|
+
data: responseData.data,
|
|
710
|
+
error: responseData.error,
|
|
711
|
+
meta: responseData.meta,
|
|
712
|
+
redacted: responseData.redacted,
|
|
713
|
+
redacted_fields: responseData.redacted_fields,
|
|
714
|
+
policy_info: responseData.policy_info,
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Execute a statement against an MCP connector (alias for mcpQuery).
|
|
719
|
+
*
|
|
720
|
+
* Same as mcpQuery but follows the naming convention of other execute* methods.
|
|
721
|
+
*
|
|
722
|
+
* @param options - Query options including connector name and SQL statement
|
|
723
|
+
* @returns ConnectorResponse with data, redaction info, and policy_info
|
|
724
|
+
*/
|
|
725
|
+
async mcpExecute(options) {
|
|
726
|
+
return this.mcpQuery(options);
|
|
727
|
+
}
|
|
581
728
|
/**
|
|
582
729
|
* Generate a multi-agent execution plan from a natural language query
|
|
583
730
|
* @param query - Natural language query describing the task
|
|
584
731
|
* @param domain - Optional domain hint (travel, healthcare, etc.)
|
|
585
732
|
* @param userToken - Optional user token for authentication (defaults to tenant/client_id)
|
|
733
|
+
* @param options - Optional plan generation options (execution mode, etc.)
|
|
586
734
|
*/
|
|
587
|
-
async generatePlan(query, domain, userToken) {
|
|
735
|
+
async generatePlan(query, domain, userToken, options) {
|
|
736
|
+
const context = {};
|
|
737
|
+
if (domain) {
|
|
738
|
+
context.domain = domain;
|
|
739
|
+
}
|
|
740
|
+
if (options?.executionMode) {
|
|
741
|
+
context.execution_mode = options.executionMode;
|
|
742
|
+
}
|
|
588
743
|
const agentRequest = {
|
|
589
744
|
query,
|
|
590
|
-
user_token: userToken || this.config.tenant,
|
|
591
|
-
client_id: this.config.tenant,
|
|
745
|
+
user_token: userToken || this.config.clientId || this.config.tenant,
|
|
746
|
+
client_id: this.config.clientId || this.config.tenant,
|
|
592
747
|
request_type: 'multi-agent-plan',
|
|
593
|
-
context
|
|
748
|
+
context,
|
|
594
749
|
};
|
|
595
750
|
const url = `${this.config.endpoint}/api/request`;
|
|
596
751
|
const headers = {
|
|
@@ -634,8 +789,8 @@ class AxonFlow {
|
|
|
634
789
|
async executePlan(planId, userToken) {
|
|
635
790
|
const agentRequest = {
|
|
636
791
|
query: '',
|
|
637
|
-
user_token: userToken || this.config.tenant,
|
|
638
|
-
client_id: this.config.tenant,
|
|
792
|
+
user_token: userToken || this.config.clientId || this.config.tenant,
|
|
793
|
+
client_id: this.config.clientId || this.config.tenant,
|
|
639
794
|
request_type: 'execute-plan',
|
|
640
795
|
context: { plan_id: planId },
|
|
641
796
|
};
|
|
@@ -656,16 +811,43 @@ class AxonFlow {
|
|
|
656
811
|
throw new errors_1.PlanExecutionError(`Plan execution failed: ${response.status} ${response.statusText} - ${errorText}`, planId, 'execution');
|
|
657
812
|
}
|
|
658
813
|
const agentResponse = await response.json();
|
|
814
|
+
// Detect nested data.success=false (agent wraps orchestrator errors)
|
|
815
|
+
let success = agentResponse.success;
|
|
816
|
+
let error = agentResponse.error;
|
|
817
|
+
let result = agentResponse.result;
|
|
818
|
+
const data = agentResponse.data;
|
|
819
|
+
if (data && typeof data === 'object' && data.success === false) {
|
|
820
|
+
success = false;
|
|
821
|
+
if (data.error && !error)
|
|
822
|
+
error = data.error;
|
|
823
|
+
// Throw on nested failure (e.g., cancelled plan execution)
|
|
824
|
+
throw new errors_1.PlanExecutionError(error || 'Plan execution failed', planId, 'execution');
|
|
825
|
+
}
|
|
826
|
+
if (!result && data?.result)
|
|
827
|
+
result = data.result;
|
|
659
828
|
if (this.config.debug) {
|
|
660
|
-
(0, helpers_1.debugLog)('Plan executed', { planId, success
|
|
829
|
+
(0, helpers_1.debugLog)('Plan executed', { planId, success });
|
|
830
|
+
}
|
|
831
|
+
// Read status from response data if available (e.g., "awaiting_approval" for confirm mode)
|
|
832
|
+
let status = success ? 'completed' : 'failed';
|
|
833
|
+
if (data &&
|
|
834
|
+
typeof data === 'object' &&
|
|
835
|
+
'status' in data &&
|
|
836
|
+
typeof data.status === 'string' &&
|
|
837
|
+
data.status) {
|
|
838
|
+
status = data.status;
|
|
839
|
+
}
|
|
840
|
+
else if (agentResponse.metadata?.status) {
|
|
841
|
+
status = agentResponse.metadata.status;
|
|
661
842
|
}
|
|
662
843
|
return {
|
|
663
844
|
planId,
|
|
664
|
-
status
|
|
665
|
-
result
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
845
|
+
status,
|
|
846
|
+
result,
|
|
847
|
+
workflowId: data?.workflow_id,
|
|
848
|
+
stepResults: agentResponse.metadata?.step_results ?? data?.metadata?.step_results,
|
|
849
|
+
error,
|
|
850
|
+
duration: agentResponse.metadata?.duration ?? data?.metadata?.duration,
|
|
669
851
|
};
|
|
670
852
|
}
|
|
671
853
|
/**
|
|
@@ -691,6 +873,150 @@ class AxonFlow {
|
|
|
691
873
|
duration: status.duration,
|
|
692
874
|
};
|
|
693
875
|
}
|
|
876
|
+
/**
|
|
877
|
+
* Cancel a running or pending plan
|
|
878
|
+
* @param planId - ID of the plan to cancel
|
|
879
|
+
* @param reason - Optional reason for cancellation
|
|
880
|
+
*/
|
|
881
|
+
async cancelPlan(planId, reason) {
|
|
882
|
+
const url = `${this.config.endpoint}/api/v1/plan/${planId}/cancel`;
|
|
883
|
+
const headers = {
|
|
884
|
+
'Content-Type': 'application/json',
|
|
885
|
+
...this.getAuthHeaders(),
|
|
886
|
+
};
|
|
887
|
+
const body = {};
|
|
888
|
+
if (reason) {
|
|
889
|
+
body.reason = reason;
|
|
890
|
+
}
|
|
891
|
+
const response = await fetch(url, {
|
|
892
|
+
method: 'POST',
|
|
893
|
+
headers,
|
|
894
|
+
body: JSON.stringify(body),
|
|
895
|
+
signal: AbortSignal.timeout(this.config.mapTimeout),
|
|
896
|
+
});
|
|
897
|
+
if (!response.ok) {
|
|
898
|
+
const errorText = await response.text();
|
|
899
|
+
throw new errors_1.PlanExecutionError(`Plan cancellation failed: ${response.status} ${response.statusText} - ${errorText}`, planId, 'cancel');
|
|
900
|
+
}
|
|
901
|
+
const data = await response.json();
|
|
902
|
+
if (this.config.debug) {
|
|
903
|
+
(0, helpers_1.debugLog)('Plan cancelled', { planId, status: data.status });
|
|
904
|
+
}
|
|
905
|
+
return {
|
|
906
|
+
planId: data.plan_id || planId,
|
|
907
|
+
status: data.status,
|
|
908
|
+
message: data.message,
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Update a plan with optimistic concurrency control.
|
|
913
|
+
* Throws VersionConflictError on 409 (version mismatch).
|
|
914
|
+
* @param planId - ID of the plan to update
|
|
915
|
+
* @param request - Update request with version and fields to change
|
|
916
|
+
*/
|
|
917
|
+
async updatePlan(planId, request) {
|
|
918
|
+
const url = `${this.config.endpoint}/api/v1/plan/${planId}`;
|
|
919
|
+
const headers = {
|
|
920
|
+
'Content-Type': 'application/json',
|
|
921
|
+
...this.getAuthHeaders(),
|
|
922
|
+
};
|
|
923
|
+
const body = {
|
|
924
|
+
version: request.version,
|
|
925
|
+
};
|
|
926
|
+
if (request.executionMode) {
|
|
927
|
+
body.execution_mode = request.executionMode;
|
|
928
|
+
}
|
|
929
|
+
if (request.domain) {
|
|
930
|
+
body.domain = request.domain;
|
|
931
|
+
}
|
|
932
|
+
const response = await fetch(url, {
|
|
933
|
+
method: 'PUT',
|
|
934
|
+
headers,
|
|
935
|
+
body: JSON.stringify(body),
|
|
936
|
+
signal: AbortSignal.timeout(this.config.mapTimeout),
|
|
937
|
+
});
|
|
938
|
+
if (response.status === 409) {
|
|
939
|
+
const errorData = await response.json().catch(() => ({}));
|
|
940
|
+
throw new errors_1.VersionConflictError(planId, request.version, errorData.current_version);
|
|
941
|
+
}
|
|
942
|
+
if (!response.ok) {
|
|
943
|
+
const errorText = await response.text();
|
|
944
|
+
throw new errors_1.PlanExecutionError(`Plan update failed: ${response.status} ${response.statusText} - ${errorText}`, planId, 'update');
|
|
945
|
+
}
|
|
946
|
+
const data = await response.json();
|
|
947
|
+
if (this.config.debug) {
|
|
948
|
+
(0, helpers_1.debugLog)('Plan updated', { planId, version: data.version });
|
|
949
|
+
}
|
|
950
|
+
return {
|
|
951
|
+
planId: data.plan_id || planId,
|
|
952
|
+
version: data.version,
|
|
953
|
+
status: data.status,
|
|
954
|
+
success: data.success ?? true,
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Get version history for a plan
|
|
959
|
+
* @param planId - ID of the plan
|
|
960
|
+
*/
|
|
961
|
+
async getPlanVersions(planId) {
|
|
962
|
+
const url = `${this.config.endpoint}/api/v1/plan/${planId}/versions`;
|
|
963
|
+
const headers = {
|
|
964
|
+
...this.getAuthHeaders(),
|
|
965
|
+
};
|
|
966
|
+
const response = await fetch(url, {
|
|
967
|
+
method: 'GET',
|
|
968
|
+
headers,
|
|
969
|
+
signal: AbortSignal.timeout(this.config.timeout),
|
|
970
|
+
});
|
|
971
|
+
if (!response.ok) {
|
|
972
|
+
const errorText = await response.text();
|
|
973
|
+
throw new errors_1.PlanExecutionError(`Get plan versions failed: ${response.status} ${response.statusText} - ${errorText}`, planId, 'versions');
|
|
974
|
+
}
|
|
975
|
+
const data = await response.json();
|
|
976
|
+
const versions = (data.versions || []).map((v) => ({
|
|
977
|
+
version: v.version,
|
|
978
|
+
changedAt: v.changed_at,
|
|
979
|
+
changedBy: v.changed_by,
|
|
980
|
+
changeType: v.change_type,
|
|
981
|
+
changeSummary: v.change_summary,
|
|
982
|
+
}));
|
|
983
|
+
return {
|
|
984
|
+
planId: data.plan_id || planId,
|
|
985
|
+
versions,
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Resume a paused plan (e.g., after approval gate or confirm mode)
|
|
990
|
+
* @param planId - ID of the plan to resume
|
|
991
|
+
* @param approved - Whether the plan is approved to proceed (defaults to true)
|
|
992
|
+
*/
|
|
993
|
+
async resumePlan(planId, approved) {
|
|
994
|
+
const url = `${this.config.endpoint}/api/v1/plan/${planId}/resume`;
|
|
995
|
+
const headers = {
|
|
996
|
+
'Content-Type': 'application/json',
|
|
997
|
+
...this.getAuthHeaders(),
|
|
998
|
+
};
|
|
999
|
+
const response = await fetch(url, {
|
|
1000
|
+
method: 'POST',
|
|
1001
|
+
headers,
|
|
1002
|
+
body: JSON.stringify({ approved: approved ?? true }),
|
|
1003
|
+
signal: AbortSignal.timeout(this.config.mapTimeout),
|
|
1004
|
+
});
|
|
1005
|
+
if (!response.ok) {
|
|
1006
|
+
const errorText = await response.text();
|
|
1007
|
+
throw new errors_1.PlanExecutionError(`Plan resume failed: ${response.status} ${response.statusText} - ${errorText}`, planId, 'resume');
|
|
1008
|
+
}
|
|
1009
|
+
const data = await response.json();
|
|
1010
|
+
if (this.config.debug) {
|
|
1011
|
+
(0, helpers_1.debugLog)('Plan resumed', { planId, approved: data.approved });
|
|
1012
|
+
}
|
|
1013
|
+
return {
|
|
1014
|
+
planId: data.plan_id || planId,
|
|
1015
|
+
status: data.status,
|
|
1016
|
+
approved: data.approved,
|
|
1017
|
+
message: data.message,
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
694
1020
|
// ============================================================================
|
|
695
1021
|
// Gateway Mode Methods
|
|
696
1022
|
// ============================================================================
|
|
@@ -743,12 +1069,12 @@ class AxonFlow {
|
|
|
743
1069
|
* ```
|
|
744
1070
|
*/
|
|
745
1071
|
async getPolicyApprovedContext(options) {
|
|
746
|
-
//
|
|
747
|
-
|
|
1072
|
+
// Use smart default for clientId - enables zero-config community mode
|
|
1073
|
+
const clientId = this.getEffectiveClientId();
|
|
748
1074
|
const url = `${this.config.endpoint}/api/policy/pre-check`;
|
|
749
1075
|
const requestBody = {
|
|
750
1076
|
user_token: options.userToken,
|
|
751
|
-
client_id:
|
|
1077
|
+
client_id: clientId,
|
|
752
1078
|
query: options.query,
|
|
753
1079
|
data_sources: options.dataSources || [],
|
|
754
1080
|
context: options.context || {},
|
|
@@ -828,12 +1154,12 @@ class AxonFlow {
|
|
|
828
1154
|
* ```
|
|
829
1155
|
*/
|
|
830
1156
|
async auditLLMCall(options) {
|
|
831
|
-
//
|
|
832
|
-
|
|
1157
|
+
// Use smart default for clientId - enables zero-config community mode
|
|
1158
|
+
const clientId = this.getEffectiveClientId();
|
|
833
1159
|
const url = `${this.config.endpoint}/api/audit/llm-call`;
|
|
834
1160
|
const requestBody = {
|
|
835
1161
|
context_id: options.contextId,
|
|
836
|
-
client_id:
|
|
1162
|
+
client_id: clientId,
|
|
837
1163
|
response_summary: options.responseSummary,
|
|
838
1164
|
provider: options.provider,
|
|
839
1165
|
model: options.model,
|
|
@@ -1039,11 +1365,9 @@ class AxonFlow {
|
|
|
1039
1365
|
'Content-Type': 'application/json',
|
|
1040
1366
|
...this.getAuthHeaders(),
|
|
1041
1367
|
};
|
|
1042
|
-
//
|
|
1043
|
-
//
|
|
1044
|
-
|
|
1045
|
-
headers['X-Org-ID'] = this.config.tenant;
|
|
1046
|
-
}
|
|
1368
|
+
// Note: X-Tenant-ID is set by getAuthHeaders() from clientId
|
|
1369
|
+
// Do NOT set X-Org-ID here - the server derives org from tenant context
|
|
1370
|
+
// Setting X-Org-ID to 'default' breaks budget queries which expect org_id to match client.OrgID
|
|
1047
1371
|
return headers;
|
|
1048
1372
|
}
|
|
1049
1373
|
/**
|
|
@@ -1403,6 +1727,10 @@ class AxonFlow {
|
|
|
1403
1727
|
const params = new URLSearchParams();
|
|
1404
1728
|
if (options?.type)
|
|
1405
1729
|
params.set('type', options.type);
|
|
1730
|
+
if (options?.tier)
|
|
1731
|
+
params.set('tier', options.tier);
|
|
1732
|
+
if (options?.organizationId)
|
|
1733
|
+
params.set('organization_id', options.organizationId);
|
|
1406
1734
|
if (options?.enabled !== undefined)
|
|
1407
1735
|
params.set('enabled', String(options.enabled));
|
|
1408
1736
|
if (options?.limit)
|
|
@@ -1463,8 +1791,27 @@ class AxonFlow {
|
|
|
1463
1791
|
if (this.config.debug) {
|
|
1464
1792
|
(0, helpers_1.debugLog)('Creating dynamic policy', { name: policy.name });
|
|
1465
1793
|
}
|
|
1794
|
+
// Convert camelCase to snake_case for API compatibility
|
|
1795
|
+
const requestBody = {
|
|
1796
|
+
name: policy.name,
|
|
1797
|
+
type: policy.type,
|
|
1798
|
+
conditions: policy.conditions,
|
|
1799
|
+
actions: policy.actions,
|
|
1800
|
+
};
|
|
1801
|
+
if (policy.description)
|
|
1802
|
+
requestBody.description = policy.description;
|
|
1803
|
+
if (policy.category)
|
|
1804
|
+
requestBody.category = policy.category;
|
|
1805
|
+
if (policy.priority !== undefined)
|
|
1806
|
+
requestBody.priority = policy.priority;
|
|
1807
|
+
if (policy.enabled !== undefined)
|
|
1808
|
+
requestBody.enabled = policy.enabled;
|
|
1809
|
+
requestBody.tier = policy.tier || 'tenant';
|
|
1810
|
+
if (policy.organizationId) {
|
|
1811
|
+
requestBody.organization_id = policy.organizationId;
|
|
1812
|
+
}
|
|
1466
1813
|
// API returns {"policy": {...}} wrapper via Agent proxy
|
|
1467
|
-
const response = await this.orchestratorRequest('POST', '/api/v1/dynamic-policies',
|
|
1814
|
+
const response = await this.orchestratorRequest('POST', '/api/v1/dynamic-policies', requestBody);
|
|
1468
1815
|
// Handle both wrapped and unwrapped responses for compatibility
|
|
1469
1816
|
return 'policy' in response ? response.policy : response;
|
|
1470
1817
|
}
|
|
@@ -1479,8 +1826,30 @@ class AxonFlow {
|
|
|
1479
1826
|
if (this.config.debug) {
|
|
1480
1827
|
(0, helpers_1.debugLog)('Updating dynamic policy', { id, updates: Object.keys(policy) });
|
|
1481
1828
|
}
|
|
1829
|
+
// Convert camelCase to snake_case for API compatibility
|
|
1830
|
+
const requestBody = {};
|
|
1831
|
+
if (policy.name !== undefined)
|
|
1832
|
+
requestBody.name = policy.name;
|
|
1833
|
+
if (policy.description !== undefined)
|
|
1834
|
+
requestBody.description = policy.description;
|
|
1835
|
+
if (policy.type !== undefined)
|
|
1836
|
+
requestBody.type = policy.type;
|
|
1837
|
+
if (policy.category !== undefined)
|
|
1838
|
+
requestBody.category = policy.category;
|
|
1839
|
+
if (policy.tier !== undefined)
|
|
1840
|
+
requestBody.tier = policy.tier;
|
|
1841
|
+
if (policy.organizationId !== undefined)
|
|
1842
|
+
requestBody.organization_id = policy.organizationId;
|
|
1843
|
+
if (policy.conditions !== undefined)
|
|
1844
|
+
requestBody.conditions = policy.conditions;
|
|
1845
|
+
if (policy.actions !== undefined)
|
|
1846
|
+
requestBody.actions = policy.actions;
|
|
1847
|
+
if (policy.priority !== undefined)
|
|
1848
|
+
requestBody.priority = policy.priority;
|
|
1849
|
+
if (policy.enabled !== undefined)
|
|
1850
|
+
requestBody.enabled = policy.enabled;
|
|
1482
1851
|
// API returns {"policy": {...}} wrapper via Agent proxy
|
|
1483
|
-
const response = await this.orchestratorRequest('PUT', `/api/v1/dynamic-policies/${id}`,
|
|
1852
|
+
const response = await this.orchestratorRequest('PUT', `/api/v1/dynamic-policies/${id}`, requestBody);
|
|
1484
1853
|
// Handle both wrapped and unwrapped responses for compatibility
|
|
1485
1854
|
return 'policy' in response ? response.policy : response;
|
|
1486
1855
|
}
|
|
@@ -2789,6 +3158,1352 @@ class AxonFlow {
|
|
|
2789
3158
|
}
|
|
2790
3159
|
return response.text();
|
|
2791
3160
|
}
|
|
3161
|
+
// =============================================================================
|
|
3162
|
+
// Workflow Control Plane (Issue #834)
|
|
3163
|
+
// =============================================================================
|
|
3164
|
+
/**
|
|
3165
|
+
* Create a new workflow for governance tracking.
|
|
3166
|
+
*
|
|
3167
|
+
* Call this at the start of your external orchestrator workflow (LangChain, LangGraph, CrewAI, etc.)
|
|
3168
|
+
* to register it with AxonFlow for governance tracking.
|
|
3169
|
+
*
|
|
3170
|
+
* @example
|
|
3171
|
+
* ```typescript
|
|
3172
|
+
* const workflow = await client.createWorkflow({
|
|
3173
|
+
* workflow_name: 'customer-support-agent',
|
|
3174
|
+
* source: 'langgraph',
|
|
3175
|
+
* total_steps: 5,
|
|
3176
|
+
* metadata: { customer_id: 'cust-123' }
|
|
3177
|
+
* });
|
|
3178
|
+
* console.log(`Workflow created: ${workflow.workflow_id}`);
|
|
3179
|
+
* ```
|
|
3180
|
+
*/
|
|
3181
|
+
async createWorkflow(request) {
|
|
3182
|
+
const response = await this.orchestratorRequest('POST', '/api/v1/workflows', request);
|
|
3183
|
+
return response;
|
|
3184
|
+
}
|
|
3185
|
+
/**
|
|
3186
|
+
* Get the status of a workflow.
|
|
3187
|
+
*
|
|
3188
|
+
* @example
|
|
3189
|
+
* ```typescript
|
|
3190
|
+
* const status = await client.getWorkflow('wf_123');
|
|
3191
|
+
* console.log(`Status: ${status.status}, Step: ${status.current_step_index}`);
|
|
3192
|
+
* ```
|
|
3193
|
+
*/
|
|
3194
|
+
async getWorkflow(workflowId) {
|
|
3195
|
+
if (!workflowId) {
|
|
3196
|
+
throw new errors_1.ConfigurationError('Workflow ID is required');
|
|
3197
|
+
}
|
|
3198
|
+
const response = await this.orchestratorRequest('GET', `/api/v1/workflows/${workflowId}`);
|
|
3199
|
+
return response;
|
|
3200
|
+
}
|
|
3201
|
+
/**
|
|
3202
|
+
* Check if a workflow step is allowed to proceed (step gate).
|
|
3203
|
+
*
|
|
3204
|
+
* This is the core governance method. Call this before executing each step
|
|
3205
|
+
* in your workflow to check if the step is allowed based on policies.
|
|
3206
|
+
*
|
|
3207
|
+
* @example
|
|
3208
|
+
* ```typescript
|
|
3209
|
+
* const gate = await client.stepGate('wf_123', 'step-generate-code', {
|
|
3210
|
+
* step_name: 'Generate Code',
|
|
3211
|
+
* step_type: 'llm_call',
|
|
3212
|
+
* model: 'gpt-4',
|
|
3213
|
+
* provider: 'openai',
|
|
3214
|
+
* step_input: { prompt: 'Generate a hello world function' }
|
|
3215
|
+
* });
|
|
3216
|
+
*
|
|
3217
|
+
* if (gate.decision === 'block') {
|
|
3218
|
+
* throw new Error(`Step blocked: ${gate.reason}`);
|
|
3219
|
+
* }
|
|
3220
|
+
* if (gate.decision === 'require_approval') {
|
|
3221
|
+
* console.log(`Approval required: ${gate.approval_url}`);
|
|
3222
|
+
* return;
|
|
3223
|
+
* }
|
|
3224
|
+
* // Step is allowed, proceed with execution
|
|
3225
|
+
* ```
|
|
3226
|
+
*/
|
|
3227
|
+
async stepGate(workflowId, stepId, request) {
|
|
3228
|
+
if (!workflowId) {
|
|
3229
|
+
throw new errors_1.ConfigurationError('Workflow ID is required');
|
|
3230
|
+
}
|
|
3231
|
+
if (!stepId) {
|
|
3232
|
+
throw new errors_1.ConfigurationError('Step ID is required');
|
|
3233
|
+
}
|
|
3234
|
+
const response = await this.orchestratorRequest('POST', `/api/v1/workflows/${workflowId}/steps/${stepId}/gate`, request);
|
|
3235
|
+
return response;
|
|
3236
|
+
}
|
|
3237
|
+
/**
|
|
3238
|
+
* Complete a workflow successfully.
|
|
3239
|
+
*
|
|
3240
|
+
* Call this when your workflow has completed all steps successfully.
|
|
3241
|
+
*
|
|
3242
|
+
* @example
|
|
3243
|
+
* ```typescript
|
|
3244
|
+
* await client.completeWorkflow('wf_123');
|
|
3245
|
+
* ```
|
|
3246
|
+
*/
|
|
3247
|
+
async completeWorkflow(workflowId) {
|
|
3248
|
+
if (!workflowId) {
|
|
3249
|
+
throw new errors_1.ConfigurationError('Workflow ID is required');
|
|
3250
|
+
}
|
|
3251
|
+
await this.orchestratorRequest('POST', `/api/v1/workflows/${workflowId}/complete`, {});
|
|
3252
|
+
}
|
|
3253
|
+
/**
|
|
3254
|
+
* Abort a workflow.
|
|
3255
|
+
*
|
|
3256
|
+
* Call this when you need to stop a workflow due to an error or user request.
|
|
3257
|
+
*
|
|
3258
|
+
* @example
|
|
3259
|
+
* ```typescript
|
|
3260
|
+
* await client.abortWorkflow('wf_123', 'User cancelled the operation');
|
|
3261
|
+
* ```
|
|
3262
|
+
*/
|
|
3263
|
+
async abortWorkflow(workflowId, reason) {
|
|
3264
|
+
if (!workflowId) {
|
|
3265
|
+
throw new errors_1.ConfigurationError('Workflow ID is required');
|
|
3266
|
+
}
|
|
3267
|
+
const request = reason ? { reason } : {};
|
|
3268
|
+
await this.orchestratorRequest('POST', `/api/v1/workflows/${workflowId}/abort`, request);
|
|
3269
|
+
}
|
|
3270
|
+
/**
|
|
3271
|
+
* Mark a workflow step as completed.
|
|
3272
|
+
*
|
|
3273
|
+
* Call this after a step has been executed successfully.
|
|
3274
|
+
*
|
|
3275
|
+
* @example
|
|
3276
|
+
* ```typescript
|
|
3277
|
+
* await client.markStepCompleted('wf_123', 'step-1', {
|
|
3278
|
+
* output: { result: 'success' }
|
|
3279
|
+
* });
|
|
3280
|
+
* ```
|
|
3281
|
+
*/
|
|
3282
|
+
async markStepCompleted(workflowId, stepId, request) {
|
|
3283
|
+
if (!workflowId) {
|
|
3284
|
+
throw new errors_1.ConfigurationError('Workflow ID is required');
|
|
3285
|
+
}
|
|
3286
|
+
if (!stepId) {
|
|
3287
|
+
throw new errors_1.ConfigurationError('Step ID is required');
|
|
3288
|
+
}
|
|
3289
|
+
await this.orchestratorRequest('POST', `/api/v1/workflows/${workflowId}/steps/${stepId}/complete`, request || {});
|
|
3290
|
+
}
|
|
3291
|
+
/**
|
|
3292
|
+
* Resume a workflow after approval.
|
|
3293
|
+
*
|
|
3294
|
+
* Call this after a step has been approved to continue the workflow.
|
|
3295
|
+
*
|
|
3296
|
+
* @example
|
|
3297
|
+
* ```typescript
|
|
3298
|
+
* // After approval received via webhook or polling
|
|
3299
|
+
* await client.resumeWorkflow('wf_123');
|
|
3300
|
+
* ```
|
|
3301
|
+
*/
|
|
3302
|
+
async resumeWorkflow(workflowId) {
|
|
3303
|
+
if (!workflowId) {
|
|
3304
|
+
throw new errors_1.ConfigurationError('Workflow ID is required');
|
|
3305
|
+
}
|
|
3306
|
+
await this.orchestratorRequest('POST', `/api/v1/workflows/${workflowId}/resume`, {});
|
|
3307
|
+
}
|
|
3308
|
+
/**
|
|
3309
|
+
* List workflows with optional filters.
|
|
3310
|
+
*
|
|
3311
|
+
* @example
|
|
3312
|
+
* ```typescript
|
|
3313
|
+
* const result = await client.listWorkflows({
|
|
3314
|
+
* status: 'in_progress',
|
|
3315
|
+
* source: 'langgraph',
|
|
3316
|
+
* limit: 10
|
|
3317
|
+
* });
|
|
3318
|
+
* console.log(`Found ${result.total} workflows`);
|
|
3319
|
+
* ```
|
|
3320
|
+
*/
|
|
3321
|
+
async listWorkflows(options) {
|
|
3322
|
+
const params = new URLSearchParams();
|
|
3323
|
+
if (options?.status) {
|
|
3324
|
+
params.set('status', options.status);
|
|
3325
|
+
}
|
|
3326
|
+
if (options?.source) {
|
|
3327
|
+
params.set('source', options.source);
|
|
3328
|
+
}
|
|
3329
|
+
if (options?.limit !== undefined) {
|
|
3330
|
+
params.set('limit', options.limit.toString());
|
|
3331
|
+
}
|
|
3332
|
+
if (options?.offset !== undefined) {
|
|
3333
|
+
params.set('offset', options.offset.toString());
|
|
3334
|
+
}
|
|
3335
|
+
const queryString = params.toString();
|
|
3336
|
+
const path = queryString ? `/api/v1/workflows?${queryString}` : '/api/v1/workflows';
|
|
3337
|
+
const response = await this.orchestratorRequest('GET', path);
|
|
3338
|
+
return response;
|
|
3339
|
+
}
|
|
3340
|
+
// =============================================================================
|
|
3341
|
+
// WCP Approval Methods (Feature 5)
|
|
3342
|
+
// =============================================================================
|
|
3343
|
+
/**
|
|
3344
|
+
* Approve a workflow step that requires human approval.
|
|
3345
|
+
*
|
|
3346
|
+
* Call this to approve a step that was gated with a 'require_approval' decision.
|
|
3347
|
+
*
|
|
3348
|
+
* @param workflowId - ID of the workflow
|
|
3349
|
+
* @param stepId - ID of the step to approve
|
|
3350
|
+
* @returns Approval response with status
|
|
3351
|
+
*
|
|
3352
|
+
* @example
|
|
3353
|
+
* ```typescript
|
|
3354
|
+
* const result = await client.approveStep('wf_123', 'step_456');
|
|
3355
|
+
* console.log(`Step ${result.step_id} status: ${result.status}`);
|
|
3356
|
+
* ```
|
|
3357
|
+
*/
|
|
3358
|
+
async approveStep(workflowId, stepId) {
|
|
3359
|
+
if (!workflowId) {
|
|
3360
|
+
throw new errors_1.ConfigurationError('Workflow ID is required');
|
|
3361
|
+
}
|
|
3362
|
+
if (!stepId) {
|
|
3363
|
+
throw new errors_1.ConfigurationError('Step ID is required');
|
|
3364
|
+
}
|
|
3365
|
+
return this.orchestratorRequest('POST', `/api/v1/workflow-control/${workflowId}/steps/${stepId}/approve`, {});
|
|
3366
|
+
}
|
|
3367
|
+
/**
|
|
3368
|
+
* Reject a workflow step that requires human approval.
|
|
3369
|
+
*
|
|
3370
|
+
* Call this to reject a step that was gated with a 'require_approval' decision.
|
|
3371
|
+
*
|
|
3372
|
+
* @param workflowId - ID of the workflow
|
|
3373
|
+
* @param stepId - ID of the step to reject
|
|
3374
|
+
* @param reason - Optional reason for rejection
|
|
3375
|
+
* @returns Rejection response with status
|
|
3376
|
+
*
|
|
3377
|
+
* @example
|
|
3378
|
+
* ```typescript
|
|
3379
|
+
* const result = await client.rejectStep('wf_123', 'step_456', 'Policy violation detected');
|
|
3380
|
+
* console.log(`Step ${result.step_id} status: ${result.status}`);
|
|
3381
|
+
* ```
|
|
3382
|
+
*/
|
|
3383
|
+
async rejectStep(workflowId, stepId, reason) {
|
|
3384
|
+
if (!workflowId) {
|
|
3385
|
+
throw new errors_1.ConfigurationError('Workflow ID is required');
|
|
3386
|
+
}
|
|
3387
|
+
if (!stepId) {
|
|
3388
|
+
throw new errors_1.ConfigurationError('Step ID is required');
|
|
3389
|
+
}
|
|
3390
|
+
const body = {};
|
|
3391
|
+
if (reason) {
|
|
3392
|
+
body.reason = reason;
|
|
3393
|
+
}
|
|
3394
|
+
return this.orchestratorRequest('POST', `/api/v1/workflow-control/${workflowId}/steps/${stepId}/reject`, body);
|
|
3395
|
+
}
|
|
3396
|
+
/**
|
|
3397
|
+
* Get pending approvals for workflow steps.
|
|
3398
|
+
*
|
|
3399
|
+
* Lists all steps that are waiting for human approval across all workflows.
|
|
3400
|
+
*
|
|
3401
|
+
* @param options - Optional filtering options
|
|
3402
|
+
* @returns List of pending approvals with total count
|
|
3403
|
+
*
|
|
3404
|
+
* @example
|
|
3405
|
+
* ```typescript
|
|
3406
|
+
* const pending = await client.getPendingApprovals({ limit: 10 });
|
|
3407
|
+
* console.log(`${pending.total} approvals pending`);
|
|
3408
|
+
* for (const approval of pending.approvals) {
|
|
3409
|
+
* console.log(`${approval.workflow_name} / ${approval.step_name}`);
|
|
3410
|
+
* }
|
|
3411
|
+
* ```
|
|
3412
|
+
*/
|
|
3413
|
+
async getPendingApprovals(options) {
|
|
3414
|
+
const params = new URLSearchParams();
|
|
3415
|
+
if (options?.limit !== undefined) {
|
|
3416
|
+
params.set('limit', options.limit.toString());
|
|
3417
|
+
}
|
|
3418
|
+
const queryString = params.toString();
|
|
3419
|
+
const path = queryString
|
|
3420
|
+
? `/api/v1/workflow-control/pending-approvals?${queryString}`
|
|
3421
|
+
: '/api/v1/workflow-control/pending-approvals';
|
|
3422
|
+
return this.orchestratorRequest('GET', path);
|
|
3423
|
+
}
|
|
3424
|
+
// =============================================================================
|
|
3425
|
+
// Plan Rollback (Feature 7)
|
|
3426
|
+
// =============================================================================
|
|
3427
|
+
/**
|
|
3428
|
+
* Rollback a plan to a previous version.
|
|
3429
|
+
*
|
|
3430
|
+
* @param planId - ID of the plan to rollback
|
|
3431
|
+
* @param targetVersion - Version number to rollback to
|
|
3432
|
+
* @returns Rollback response with version information
|
|
3433
|
+
*
|
|
3434
|
+
* @example
|
|
3435
|
+
* ```typescript
|
|
3436
|
+
* const result = await client.rollbackPlan('plan_123', 2);
|
|
3437
|
+
* console.log(`Rolled back to v${result.version} from v${result.previousVersion}`);
|
|
3438
|
+
* ```
|
|
3439
|
+
*/
|
|
3440
|
+
async rollbackPlan(planId, targetVersion) {
|
|
3441
|
+
const url = `${this.config.endpoint}/api/v1/plan/${planId}/rollback/${targetVersion}`;
|
|
3442
|
+
const headers = {
|
|
3443
|
+
'Content-Type': 'application/json',
|
|
3444
|
+
...this.getAuthHeaders(),
|
|
3445
|
+
};
|
|
3446
|
+
const response = await fetch(url, {
|
|
3447
|
+
method: 'POST',
|
|
3448
|
+
headers,
|
|
3449
|
+
body: JSON.stringify({}),
|
|
3450
|
+
signal: AbortSignal.timeout(this.config.mapTimeout),
|
|
3451
|
+
});
|
|
3452
|
+
if (!response.ok) {
|
|
3453
|
+
const errorText = await response.text();
|
|
3454
|
+
throw new errors_1.PlanExecutionError(`Plan rollback failed: ${response.status} ${response.statusText} - ${errorText}`, planId, 'rollback');
|
|
3455
|
+
}
|
|
3456
|
+
const data = await response.json();
|
|
3457
|
+
if (this.config.debug) {
|
|
3458
|
+
(0, helpers_1.debugLog)('Plan rolled back', { planId, version: data.version });
|
|
3459
|
+
}
|
|
3460
|
+
return {
|
|
3461
|
+
planId: data.plan_id || planId,
|
|
3462
|
+
version: data.version,
|
|
3463
|
+
previousVersion: data.previous_version,
|
|
3464
|
+
status: data.status,
|
|
3465
|
+
};
|
|
3466
|
+
}
|
|
3467
|
+
// =============================================================================
|
|
3468
|
+
// Webhook CRUD Methods (Feature 7)
|
|
3469
|
+
// =============================================================================
|
|
3470
|
+
/**
|
|
3471
|
+
* Create a webhook subscription.
|
|
3472
|
+
*
|
|
3473
|
+
* @param request - Webhook configuration
|
|
3474
|
+
* @returns Created webhook subscription
|
|
3475
|
+
*
|
|
3476
|
+
* @example
|
|
3477
|
+
* ```typescript
|
|
3478
|
+
* const webhook = await client.createWebhook({
|
|
3479
|
+
* url: 'https://example.com/webhook',
|
|
3480
|
+
* events: ['workflow.completed', 'step.approval_required'],
|
|
3481
|
+
* active: true
|
|
3482
|
+
* });
|
|
3483
|
+
* console.log(`Webhook created: ${webhook.id}`);
|
|
3484
|
+
* ```
|
|
3485
|
+
*/
|
|
3486
|
+
async createWebhook(request) {
|
|
3487
|
+
return this.orchestratorRequest('POST', '/api/v1/webhooks', request);
|
|
3488
|
+
}
|
|
3489
|
+
/**
|
|
3490
|
+
* Get a webhook subscription by ID.
|
|
3491
|
+
*
|
|
3492
|
+
* @param webhookId - ID of the webhook to retrieve
|
|
3493
|
+
* @returns Webhook subscription details
|
|
3494
|
+
*
|
|
3495
|
+
* @example
|
|
3496
|
+
* ```typescript
|
|
3497
|
+
* const webhook = await client.getWebhook('wh_123');
|
|
3498
|
+
* console.log(`Webhook URL: ${webhook.url}, Active: ${webhook.active}`);
|
|
3499
|
+
* ```
|
|
3500
|
+
*/
|
|
3501
|
+
async getWebhook(webhookId) {
|
|
3502
|
+
if (!webhookId) {
|
|
3503
|
+
throw new errors_1.ConfigurationError('Webhook ID is required');
|
|
3504
|
+
}
|
|
3505
|
+
return this.orchestratorRequest('GET', `/api/v1/webhooks/${webhookId}`);
|
|
3506
|
+
}
|
|
3507
|
+
/**
|
|
3508
|
+
* Update a webhook subscription.
|
|
3509
|
+
*
|
|
3510
|
+
* @param webhookId - ID of the webhook to update
|
|
3511
|
+
* @param request - Fields to update
|
|
3512
|
+
* @returns Updated webhook subscription
|
|
3513
|
+
*
|
|
3514
|
+
* @example
|
|
3515
|
+
* ```typescript
|
|
3516
|
+
* const webhook = await client.updateWebhook('wh_123', {
|
|
3517
|
+
* events: ['workflow.completed'],
|
|
3518
|
+
* active: false
|
|
3519
|
+
* });
|
|
3520
|
+
* ```
|
|
3521
|
+
*/
|
|
3522
|
+
async updateWebhook(webhookId, request) {
|
|
3523
|
+
if (!webhookId) {
|
|
3524
|
+
throw new errors_1.ConfigurationError('Webhook ID is required');
|
|
3525
|
+
}
|
|
3526
|
+
return this.orchestratorRequest('PUT', `/api/v1/webhooks/${webhookId}`, request);
|
|
3527
|
+
}
|
|
3528
|
+
/**
|
|
3529
|
+
* Delete a webhook subscription.
|
|
3530
|
+
*
|
|
3531
|
+
* @param webhookId - ID of the webhook to delete
|
|
3532
|
+
*
|
|
3533
|
+
* @example
|
|
3534
|
+
* ```typescript
|
|
3535
|
+
* await client.deleteWebhook('wh_123');
|
|
3536
|
+
* ```
|
|
3537
|
+
*/
|
|
3538
|
+
async deleteWebhook(webhookId) {
|
|
3539
|
+
if (!webhookId) {
|
|
3540
|
+
throw new errors_1.ConfigurationError('Webhook ID is required');
|
|
3541
|
+
}
|
|
3542
|
+
await this.orchestratorRequest('DELETE', `/api/v1/webhooks/${webhookId}`);
|
|
3543
|
+
}
|
|
3544
|
+
/**
|
|
3545
|
+
* List all webhook subscriptions.
|
|
3546
|
+
*
|
|
3547
|
+
* @returns List of webhook subscriptions with total count
|
|
3548
|
+
*
|
|
3549
|
+
* @example
|
|
3550
|
+
* ```typescript
|
|
3551
|
+
* const result = await client.listWebhooks();
|
|
3552
|
+
* console.log(`${result.total} webhooks configured`);
|
|
3553
|
+
* for (const wh of result.webhooks) {
|
|
3554
|
+
* console.log(`${wh.id}: ${wh.url} (${wh.active ? 'active' : 'inactive'})`);
|
|
3555
|
+
* }
|
|
3556
|
+
* ```
|
|
3557
|
+
*/
|
|
3558
|
+
async listWebhooks() {
|
|
3559
|
+
return this.orchestratorRequest('GET', '/api/v1/webhooks');
|
|
3560
|
+
}
|
|
3561
|
+
// ===========================================================================
|
|
3562
|
+
// MAS FEAT Compliance Methods (Enterprise)
|
|
3563
|
+
// ===========================================================================
|
|
3564
|
+
/**
|
|
3565
|
+
* MAS FEAT compliance module for Singapore regulatory compliance.
|
|
3566
|
+
*
|
|
3567
|
+
* Enterprise Feature: Requires AxonFlow Enterprise license.
|
|
3568
|
+
*
|
|
3569
|
+
* @example
|
|
3570
|
+
* ```typescript
|
|
3571
|
+
* // Register an AI system
|
|
3572
|
+
* const system = await axonflow.masfeat.registerSystem({
|
|
3573
|
+
* systemId: 'credit-scoring-v1',
|
|
3574
|
+
* systemName: 'Credit Scoring AI',
|
|
3575
|
+
* useCase: 'credit_scoring',
|
|
3576
|
+
* ownerTeam: 'Risk Management',
|
|
3577
|
+
* customerImpact: 4,
|
|
3578
|
+
* modelComplexity: 3,
|
|
3579
|
+
* humanReliance: 5
|
|
3580
|
+
* });
|
|
3581
|
+
*
|
|
3582
|
+
* // Configure kill switch
|
|
3583
|
+
* const ks = await axonflow.masfeat.configureKillSwitch('credit-scoring-v1', {
|
|
3584
|
+
* accuracyThreshold: 0.85,
|
|
3585
|
+
* biasThreshold: 0.15,
|
|
3586
|
+
* autoTriggerEnabled: true
|
|
3587
|
+
* });
|
|
3588
|
+
* ```
|
|
3589
|
+
*/
|
|
3590
|
+
get masfeat() {
|
|
3591
|
+
return {
|
|
3592
|
+
// Registry methods
|
|
3593
|
+
registerSystem: this.masfeatRegisterSystem.bind(this),
|
|
3594
|
+
getSystem: this.masfeatGetSystem.bind(this),
|
|
3595
|
+
updateSystem: this.masfeatUpdateSystem.bind(this),
|
|
3596
|
+
listSystems: this.masfeatListSystems.bind(this),
|
|
3597
|
+
activateSystem: this.masfeatActivateSystem.bind(this),
|
|
3598
|
+
retireSystem: this.masfeatRetireSystem.bind(this),
|
|
3599
|
+
getRegistrySummary: this.masfeatGetRegistrySummary.bind(this),
|
|
3600
|
+
// Assessment methods
|
|
3601
|
+
createAssessment: this.masfeatCreateAssessment.bind(this),
|
|
3602
|
+
getAssessment: this.masfeatGetAssessment.bind(this),
|
|
3603
|
+
updateAssessment: this.masfeatUpdateAssessment.bind(this),
|
|
3604
|
+
listAssessments: this.masfeatListAssessments.bind(this),
|
|
3605
|
+
submitAssessment: this.masfeatSubmitAssessment.bind(this),
|
|
3606
|
+
approveAssessment: this.masfeatApproveAssessment.bind(this),
|
|
3607
|
+
rejectAssessment: this.masfeatRejectAssessment.bind(this),
|
|
3608
|
+
// Kill switch methods
|
|
3609
|
+
getKillSwitch: this.masfeatGetKillSwitch.bind(this),
|
|
3610
|
+
configureKillSwitch: this.masfeatConfigureKillSwitch.bind(this),
|
|
3611
|
+
checkKillSwitch: this.masfeatCheckKillSwitch.bind(this),
|
|
3612
|
+
triggerKillSwitch: this.masfeatTriggerKillSwitch.bind(this),
|
|
3613
|
+
restoreKillSwitch: this.masfeatRestoreKillSwitch.bind(this),
|
|
3614
|
+
enableKillSwitch: this.masfeatEnableKillSwitch.bind(this),
|
|
3615
|
+
disableKillSwitch: this.masfeatDisableKillSwitch.bind(this),
|
|
3616
|
+
getKillSwitchHistory: this.masfeatGetKillSwitchHistory.bind(this),
|
|
3617
|
+
};
|
|
3618
|
+
}
|
|
3619
|
+
// Registry Methods
|
|
3620
|
+
async masfeatRegisterSystem(request) {
|
|
3621
|
+
const url = `${this.config.endpoint}/api/v1/masfeat/registry`;
|
|
3622
|
+
const body = {
|
|
3623
|
+
system_id: request.systemId,
|
|
3624
|
+
system_name: request.systemName,
|
|
3625
|
+
description: request.description,
|
|
3626
|
+
use_case: request.useCase,
|
|
3627
|
+
owner_team: request.ownerTeam,
|
|
3628
|
+
technical_owner: request.technicalOwner,
|
|
3629
|
+
owner_email: request.businessOwner,
|
|
3630
|
+
risk_rating_impact: request.customerImpact,
|
|
3631
|
+
risk_rating_complexity: request.modelComplexity,
|
|
3632
|
+
risk_rating_reliance: request.humanReliance,
|
|
3633
|
+
metadata: request.metadata,
|
|
3634
|
+
};
|
|
3635
|
+
const response = await fetch(url, {
|
|
3636
|
+
method: 'POST',
|
|
3637
|
+
headers: {
|
|
3638
|
+
'Content-Type': 'application/json',
|
|
3639
|
+
...this.getAuthHeaders(),
|
|
3640
|
+
},
|
|
3641
|
+
body: JSON.stringify(body),
|
|
3642
|
+
signal: AbortSignal.timeout(this.config.timeout),
|
|
3643
|
+
});
|
|
3644
|
+
if (!response.ok) {
|
|
3645
|
+
const errorText = await response.text();
|
|
3646
|
+
throw new errors_1.APIError(response.status, response.statusText, errorText);
|
|
3647
|
+
}
|
|
3648
|
+
return this.mapSystemResponse(await response.json());
|
|
3649
|
+
}
|
|
3650
|
+
async masfeatGetSystem(systemId) {
|
|
3651
|
+
const url = `${this.config.endpoint}/api/v1/masfeat/registry/${systemId}`;
|
|
3652
|
+
const response = await fetch(url, {
|
|
3653
|
+
method: 'GET',
|
|
3654
|
+
headers: {
|
|
3655
|
+
...this.getAuthHeaders(),
|
|
3656
|
+
},
|
|
3657
|
+
signal: AbortSignal.timeout(this.config.timeout),
|
|
3658
|
+
});
|
|
3659
|
+
if (!response.ok) {
|
|
3660
|
+
const errorText = await response.text();
|
|
3661
|
+
throw new errors_1.APIError(response.status, response.statusText, errorText);
|
|
3662
|
+
}
|
|
3663
|
+
return this.mapSystemResponse(await response.json());
|
|
3664
|
+
}
|
|
3665
|
+
async masfeatUpdateSystem(systemId, request) {
|
|
3666
|
+
const url = `${this.config.endpoint}/api/v1/masfeat/registry/${systemId}`;
|
|
3667
|
+
const body = {};
|
|
3668
|
+
if (request.systemName !== undefined)
|
|
3669
|
+
body.system_name = request.systemName;
|
|
3670
|
+
if (request.description !== undefined)
|
|
3671
|
+
body.description = request.description;
|
|
3672
|
+
if (request.ownerTeam !== undefined)
|
|
3673
|
+
body.owner_team = request.ownerTeam;
|
|
3674
|
+
if (request.technicalOwner !== undefined)
|
|
3675
|
+
body.technical_owner = request.technicalOwner;
|
|
3676
|
+
if (request.businessOwner !== undefined)
|
|
3677
|
+
body.business_owner = request.businessOwner;
|
|
3678
|
+
if (request.customerImpact !== undefined)
|
|
3679
|
+
body.customer_impact = request.customerImpact;
|
|
3680
|
+
if (request.modelComplexity !== undefined)
|
|
3681
|
+
body.model_complexity = request.modelComplexity;
|
|
3682
|
+
if (request.humanReliance !== undefined)
|
|
3683
|
+
body.human_reliance = request.humanReliance;
|
|
3684
|
+
if (request.metadata !== undefined)
|
|
3685
|
+
body.metadata = request.metadata;
|
|
3686
|
+
const response = await fetch(url, {
|
|
3687
|
+
method: 'PUT',
|
|
3688
|
+
headers: {
|
|
3689
|
+
'Content-Type': 'application/json',
|
|
3690
|
+
...this.getAuthHeaders(),
|
|
3691
|
+
},
|
|
3692
|
+
body: JSON.stringify(body),
|
|
3693
|
+
signal: AbortSignal.timeout(this.config.timeout),
|
|
3694
|
+
});
|
|
3695
|
+
if (!response.ok) {
|
|
3696
|
+
const errorText = await response.text();
|
|
3697
|
+
throw new errors_1.APIError(response.status, response.statusText, errorText);
|
|
3698
|
+
}
|
|
3699
|
+
return this.mapSystemResponse(await response.json());
|
|
3700
|
+
}
|
|
3701
|
+
async masfeatListSystems(options) {
|
|
3702
|
+
const params = new URLSearchParams();
|
|
3703
|
+
if (options?.status)
|
|
3704
|
+
params.append('status', options.status);
|
|
3705
|
+
if (options?.useCase)
|
|
3706
|
+
params.append('use_case', options.useCase);
|
|
3707
|
+
if (options?.materiality)
|
|
3708
|
+
params.append('materiality', options.materiality);
|
|
3709
|
+
if (options?.limit)
|
|
3710
|
+
params.append('limit', options.limit.toString());
|
|
3711
|
+
if (options?.offset)
|
|
3712
|
+
params.append('offset', options.offset.toString());
|
|
3713
|
+
const queryString = params.toString();
|
|
3714
|
+
const url = `${this.config.endpoint}/api/v1/masfeat/registry${queryString ? `?${queryString}` : ''}`;
|
|
3715
|
+
const response = await fetch(url, {
|
|
3716
|
+
method: 'GET',
|
|
3717
|
+
headers: {
|
|
3718
|
+
...this.getAuthHeaders(),
|
|
3719
|
+
},
|
|
3720
|
+
signal: AbortSignal.timeout(this.config.timeout),
|
|
3721
|
+
});
|
|
3722
|
+
if (!response.ok) {
|
|
3723
|
+
const errorText = await response.text();
|
|
3724
|
+
throw new errors_1.APIError(response.status, response.statusText, errorText);
|
|
3725
|
+
}
|
|
3726
|
+
const data = await response.json();
|
|
3727
|
+
return (data || []).map((s) => this.mapSystemResponse(s));
|
|
3728
|
+
}
|
|
3729
|
+
async masfeatActivateSystem(systemId) {
|
|
3730
|
+
// Use PUT to update status - the /activate endpoint doesn't exist
|
|
3731
|
+
const url = `${this.config.endpoint}/api/v1/masfeat/registry/${systemId}`;
|
|
3732
|
+
const response = await fetch(url, {
|
|
3733
|
+
method: 'PUT',
|
|
3734
|
+
headers: {
|
|
3735
|
+
'Content-Type': 'application/json',
|
|
3736
|
+
...this.getAuthHeaders(),
|
|
3737
|
+
},
|
|
3738
|
+
body: JSON.stringify({ status: 'active' }),
|
|
3739
|
+
signal: AbortSignal.timeout(this.config.timeout),
|
|
3740
|
+
});
|
|
3741
|
+
if (!response.ok) {
|
|
3742
|
+
const errorText = await response.text();
|
|
3743
|
+
throw new errors_1.APIError(response.status, response.statusText, errorText);
|
|
3744
|
+
}
|
|
3745
|
+
return this.mapSystemResponse(await response.json());
|
|
3746
|
+
}
|
|
3747
|
+
async masfeatRetireSystem(systemId) {
|
|
3748
|
+
const url = `${this.config.endpoint}/api/v1/masfeat/registry/${systemId}`;
|
|
3749
|
+
const response = await fetch(url, {
|
|
3750
|
+
method: 'DELETE',
|
|
3751
|
+
headers: {
|
|
3752
|
+
...this.getAuthHeaders(),
|
|
3753
|
+
},
|
|
3754
|
+
signal: AbortSignal.timeout(this.config.timeout),
|
|
3755
|
+
});
|
|
3756
|
+
if (!response.ok) {
|
|
3757
|
+
const errorText = await response.text();
|
|
3758
|
+
throw new errors_1.APIError(response.status, response.statusText, errorText);
|
|
3759
|
+
}
|
|
3760
|
+
return this.mapSystemResponse(await response.json());
|
|
3761
|
+
}
|
|
3762
|
+
async masfeatGetRegistrySummary() {
|
|
3763
|
+
const url = `${this.config.endpoint}/api/v1/masfeat/registry/summary`;
|
|
3764
|
+
const response = await fetch(url, {
|
|
3765
|
+
method: 'GET',
|
|
3766
|
+
headers: {
|
|
3767
|
+
...this.getAuthHeaders(),
|
|
3768
|
+
},
|
|
3769
|
+
signal: AbortSignal.timeout(this.config.timeout),
|
|
3770
|
+
});
|
|
3771
|
+
if (!response.ok) {
|
|
3772
|
+
const errorText = await response.text();
|
|
3773
|
+
throw new errors_1.APIError(response.status, response.statusText, errorText);
|
|
3774
|
+
}
|
|
3775
|
+
const data = await response.json();
|
|
3776
|
+
return {
|
|
3777
|
+
totalSystems: data.total_systems,
|
|
3778
|
+
activeSystems: data.active_systems,
|
|
3779
|
+
highMaterialityCount: data.high_materiality_count ?? data.high_materiality ?? 0,
|
|
3780
|
+
mediumMaterialityCount: data.medium_materiality_count ?? data.medium_materiality ?? 0,
|
|
3781
|
+
lowMaterialityCount: data.low_materiality_count ?? data.low_materiality ?? 0,
|
|
3782
|
+
byUseCase: data.by_use_case || {},
|
|
3783
|
+
byStatus: data.by_status || {},
|
|
3784
|
+
};
|
|
3785
|
+
}
|
|
3786
|
+
// Assessment Methods
|
|
3787
|
+
async masfeatCreateAssessment(request) {
|
|
3788
|
+
const url = `${this.config.endpoint}/api/v1/masfeat/assessments`;
|
|
3789
|
+
const body = {
|
|
3790
|
+
system_id: request.systemId,
|
|
3791
|
+
assessment_type: request.assessmentType || 'periodic',
|
|
3792
|
+
assessors: request.assessors,
|
|
3793
|
+
};
|
|
3794
|
+
if (request.assessmentDate)
|
|
3795
|
+
body.assessment_date = request.assessmentDate.toISOString();
|
|
3796
|
+
if (request.fairnessScore !== undefined)
|
|
3797
|
+
body.fairness_score = request.fairnessScore;
|
|
3798
|
+
if (request.ethicsScore !== undefined)
|
|
3799
|
+
body.ethics_score = request.ethicsScore;
|
|
3800
|
+
if (request.accountabilityScore !== undefined)
|
|
3801
|
+
body.accountability_score = request.accountabilityScore;
|
|
3802
|
+
if (request.transparencyScore !== undefined)
|
|
3803
|
+
body.transparency_score = request.transparencyScore;
|
|
3804
|
+
if (request.fairnessDetails)
|
|
3805
|
+
body.fairness_details = request.fairnessDetails;
|
|
3806
|
+
if (request.ethicsDetails)
|
|
3807
|
+
body.ethics_details = request.ethicsDetails;
|
|
3808
|
+
if (request.accountabilityDetails)
|
|
3809
|
+
body.accountability_details = request.accountabilityDetails;
|
|
3810
|
+
if (request.transparencyDetails)
|
|
3811
|
+
body.transparency_details = request.transparencyDetails;
|
|
3812
|
+
if (request.recommendations)
|
|
3813
|
+
body.recommendations = request.recommendations;
|
|
3814
|
+
if (request.findings) {
|
|
3815
|
+
body.findings = request.findings.map(f => ({
|
|
3816
|
+
id: f.id,
|
|
3817
|
+
pillar: f.pillar,
|
|
3818
|
+
severity: f.severity,
|
|
3819
|
+
category: f.category,
|
|
3820
|
+
description: f.description,
|
|
3821
|
+
status: f.status,
|
|
3822
|
+
remediation: f.remediation,
|
|
3823
|
+
due_date: f.dueDate?.toISOString(),
|
|
3824
|
+
}));
|
|
3825
|
+
}
|
|
3826
|
+
const response = await fetch(url, {
|
|
3827
|
+
method: 'POST',
|
|
3828
|
+
headers: {
|
|
3829
|
+
'Content-Type': 'application/json',
|
|
3830
|
+
...this.getAuthHeaders(),
|
|
3831
|
+
},
|
|
3832
|
+
body: JSON.stringify(body),
|
|
3833
|
+
signal: AbortSignal.timeout(this.config.timeout),
|
|
3834
|
+
});
|
|
3835
|
+
if (!response.ok) {
|
|
3836
|
+
const errorText = await response.text();
|
|
3837
|
+
throw new errors_1.APIError(response.status, response.statusText, errorText);
|
|
3838
|
+
}
|
|
3839
|
+
return this.mapAssessmentResponse(await response.json());
|
|
3840
|
+
}
|
|
3841
|
+
async masfeatGetAssessment(assessmentId) {
|
|
3842
|
+
const url = `${this.config.endpoint}/api/v1/masfeat/assessments/${assessmentId}`;
|
|
3843
|
+
const response = await fetch(url, {
|
|
3844
|
+
method: 'GET',
|
|
3845
|
+
headers: {
|
|
3846
|
+
...this.getAuthHeaders(),
|
|
3847
|
+
},
|
|
3848
|
+
signal: AbortSignal.timeout(this.config.timeout),
|
|
3849
|
+
});
|
|
3850
|
+
if (!response.ok) {
|
|
3851
|
+
const errorText = await response.text();
|
|
3852
|
+
throw new errors_1.APIError(response.status, response.statusText, errorText);
|
|
3853
|
+
}
|
|
3854
|
+
return this.mapAssessmentResponse(await response.json());
|
|
3855
|
+
}
|
|
3856
|
+
async masfeatUpdateAssessment(assessmentId, request) {
|
|
3857
|
+
const url = `${this.config.endpoint}/api/v1/masfeat/assessments/${assessmentId}`;
|
|
3858
|
+
const body = {};
|
|
3859
|
+
if (request.fairnessScore !== undefined)
|
|
3860
|
+
body.fairness_score = request.fairnessScore;
|
|
3861
|
+
if (request.ethicsScore !== undefined)
|
|
3862
|
+
body.ethics_score = request.ethicsScore;
|
|
3863
|
+
if (request.accountabilityScore !== undefined)
|
|
3864
|
+
body.accountability_score = request.accountabilityScore;
|
|
3865
|
+
if (request.transparencyScore !== undefined)
|
|
3866
|
+
body.transparency_score = request.transparencyScore;
|
|
3867
|
+
if (request.fairnessDetails !== undefined)
|
|
3868
|
+
body.fairness_details = request.fairnessDetails;
|
|
3869
|
+
if (request.ethicsDetails !== undefined)
|
|
3870
|
+
body.ethics_details = request.ethicsDetails;
|
|
3871
|
+
if (request.accountabilityDetails !== undefined)
|
|
3872
|
+
body.accountability_details = request.accountabilityDetails;
|
|
3873
|
+
if (request.transparencyDetails !== undefined)
|
|
3874
|
+
body.transparency_details = request.transparencyDetails;
|
|
3875
|
+
if (request.findings !== undefined) {
|
|
3876
|
+
body.findings = request.findings.map(f => ({
|
|
3877
|
+
id: f.id,
|
|
3878
|
+
pillar: f.pillar,
|
|
3879
|
+
severity: f.severity,
|
|
3880
|
+
category: f.category,
|
|
3881
|
+
description: f.description,
|
|
3882
|
+
status: f.status,
|
|
3883
|
+
remediation: f.remediation,
|
|
3884
|
+
due_date: f.dueDate?.toISOString(),
|
|
3885
|
+
}));
|
|
3886
|
+
}
|
|
3887
|
+
if (request.recommendations !== undefined)
|
|
3888
|
+
body.recommendations = request.recommendations;
|
|
3889
|
+
if (request.assessors !== undefined)
|
|
3890
|
+
body.assessors = request.assessors;
|
|
3891
|
+
const response = await fetch(url, {
|
|
3892
|
+
method: 'PUT',
|
|
3893
|
+
headers: {
|
|
3894
|
+
'Content-Type': 'application/json',
|
|
3895
|
+
...this.getAuthHeaders(),
|
|
3896
|
+
},
|
|
3897
|
+
body: JSON.stringify(body),
|
|
3898
|
+
signal: AbortSignal.timeout(this.config.timeout),
|
|
3899
|
+
});
|
|
3900
|
+
if (!response.ok) {
|
|
3901
|
+
const errorText = await response.text();
|
|
3902
|
+
throw new errors_1.APIError(response.status, response.statusText, errorText);
|
|
3903
|
+
}
|
|
3904
|
+
return this.mapAssessmentResponse(await response.json());
|
|
3905
|
+
}
|
|
3906
|
+
async masfeatListAssessments(options) {
|
|
3907
|
+
const params = new URLSearchParams();
|
|
3908
|
+
if (options?.systemId)
|
|
3909
|
+
params.append('system_id', options.systemId);
|
|
3910
|
+
if (options?.status)
|
|
3911
|
+
params.append('status', options.status);
|
|
3912
|
+
if (options?.limit)
|
|
3913
|
+
params.append('limit', options.limit.toString());
|
|
3914
|
+
if (options?.offset)
|
|
3915
|
+
params.append('offset', options.offset.toString());
|
|
3916
|
+
const queryString = params.toString();
|
|
3917
|
+
const url = `${this.config.endpoint}/api/v1/masfeat/assessments${queryString ? `?${queryString}` : ''}`;
|
|
3918
|
+
const response = await fetch(url, {
|
|
3919
|
+
method: 'GET',
|
|
3920
|
+
headers: {
|
|
3921
|
+
...this.getAuthHeaders(),
|
|
3922
|
+
},
|
|
3923
|
+
signal: AbortSignal.timeout(this.config.timeout),
|
|
3924
|
+
});
|
|
3925
|
+
if (!response.ok) {
|
|
3926
|
+
const errorText = await response.text();
|
|
3927
|
+
throw new errors_1.APIError(response.status, response.statusText, errorText);
|
|
3928
|
+
}
|
|
3929
|
+
const data = await response.json();
|
|
3930
|
+
return (data || []).map((a) => this.mapAssessmentResponse(a));
|
|
3931
|
+
}
|
|
3932
|
+
async masfeatSubmitAssessment(assessmentId) {
|
|
3933
|
+
const url = `${this.config.endpoint}/api/v1/masfeat/assessments/${assessmentId}/submit`;
|
|
3934
|
+
const response = await fetch(url, {
|
|
3935
|
+
method: 'POST',
|
|
3936
|
+
headers: {
|
|
3937
|
+
'Content-Type': 'application/json',
|
|
3938
|
+
...this.getAuthHeaders(),
|
|
3939
|
+
},
|
|
3940
|
+
signal: AbortSignal.timeout(this.config.timeout),
|
|
3941
|
+
});
|
|
3942
|
+
if (!response.ok) {
|
|
3943
|
+
const errorText = await response.text();
|
|
3944
|
+
throw new errors_1.APIError(response.status, response.statusText, errorText);
|
|
3945
|
+
}
|
|
3946
|
+
return this.mapAssessmentResponse(await response.json());
|
|
3947
|
+
}
|
|
3948
|
+
async masfeatApproveAssessment(assessmentId, request) {
|
|
3949
|
+
const url = `${this.config.endpoint}/api/v1/masfeat/assessments/${assessmentId}/approve`;
|
|
3950
|
+
const response = await fetch(url, {
|
|
3951
|
+
method: 'POST',
|
|
3952
|
+
headers: {
|
|
3953
|
+
'Content-Type': 'application/json',
|
|
3954
|
+
...this.getAuthHeaders(),
|
|
3955
|
+
},
|
|
3956
|
+
body: JSON.stringify({
|
|
3957
|
+
approved_by: request.approvedBy,
|
|
3958
|
+
comments: request.comments,
|
|
3959
|
+
}),
|
|
3960
|
+
signal: AbortSignal.timeout(this.config.timeout),
|
|
3961
|
+
});
|
|
3962
|
+
if (!response.ok) {
|
|
3963
|
+
const errorText = await response.text();
|
|
3964
|
+
throw new errors_1.APIError(response.status, response.statusText, errorText);
|
|
3965
|
+
}
|
|
3966
|
+
return this.mapAssessmentResponse(await response.json());
|
|
3967
|
+
}
|
|
3968
|
+
async masfeatRejectAssessment(assessmentId, request) {
|
|
3969
|
+
const url = `${this.config.endpoint}/api/v1/masfeat/assessments/${assessmentId}/reject`;
|
|
3970
|
+
const response = await fetch(url, {
|
|
3971
|
+
method: 'POST',
|
|
3972
|
+
headers: {
|
|
3973
|
+
'Content-Type': 'application/json',
|
|
3974
|
+
...this.getAuthHeaders(),
|
|
3975
|
+
},
|
|
3976
|
+
body: JSON.stringify({
|
|
3977
|
+
rejected_by: request.rejectedBy,
|
|
3978
|
+
reason: request.reason,
|
|
3979
|
+
}),
|
|
3980
|
+
signal: AbortSignal.timeout(this.config.timeout),
|
|
3981
|
+
});
|
|
3982
|
+
if (!response.ok) {
|
|
3983
|
+
const errorText = await response.text();
|
|
3984
|
+
throw new errors_1.APIError(response.status, response.statusText, errorText);
|
|
3985
|
+
}
|
|
3986
|
+
return this.mapAssessmentResponse(await response.json());
|
|
3987
|
+
}
|
|
3988
|
+
// Kill Switch Methods
|
|
3989
|
+
async masfeatGetKillSwitch(systemId) {
|
|
3990
|
+
const url = `${this.config.endpoint}/api/v1/masfeat/killswitch/${systemId}`;
|
|
3991
|
+
const response = await fetch(url, {
|
|
3992
|
+
method: 'GET',
|
|
3993
|
+
headers: {
|
|
3994
|
+
...this.getAuthHeaders(),
|
|
3995
|
+
},
|
|
3996
|
+
signal: AbortSignal.timeout(this.config.timeout),
|
|
3997
|
+
});
|
|
3998
|
+
if (!response.ok) {
|
|
3999
|
+
const errorText = await response.text();
|
|
4000
|
+
throw new errors_1.APIError(response.status, response.statusText, errorText);
|
|
4001
|
+
}
|
|
4002
|
+
return this.mapKillSwitchResponse(await response.json());
|
|
4003
|
+
}
|
|
4004
|
+
async masfeatConfigureKillSwitch(systemId, request) {
|
|
4005
|
+
const url = `${this.config.endpoint}/api/v1/masfeat/killswitch/${systemId}/configure`;
|
|
4006
|
+
const body = {};
|
|
4007
|
+
if (request.accuracyThreshold !== undefined)
|
|
4008
|
+
body.accuracy_threshold = request.accuracyThreshold;
|
|
4009
|
+
if (request.biasThreshold !== undefined)
|
|
4010
|
+
body.bias_threshold = request.biasThreshold;
|
|
4011
|
+
if (request.errorRateThreshold !== undefined)
|
|
4012
|
+
body.error_rate_threshold = request.errorRateThreshold;
|
|
4013
|
+
if (request.autoTriggerEnabled !== undefined)
|
|
4014
|
+
body.auto_trigger_enabled = request.autoTriggerEnabled;
|
|
4015
|
+
const response = await fetch(url, {
|
|
4016
|
+
method: 'POST',
|
|
4017
|
+
headers: {
|
|
4018
|
+
'Content-Type': 'application/json',
|
|
4019
|
+
...this.getAuthHeaders(),
|
|
4020
|
+
},
|
|
4021
|
+
body: JSON.stringify(body),
|
|
4022
|
+
signal: AbortSignal.timeout(this.config.timeout),
|
|
4023
|
+
});
|
|
4024
|
+
if (!response.ok) {
|
|
4025
|
+
const errorText = await response.text();
|
|
4026
|
+
throw new errors_1.APIError(response.status, response.statusText, errorText);
|
|
4027
|
+
}
|
|
4028
|
+
return this.mapKillSwitchResponse(await response.json());
|
|
4029
|
+
}
|
|
4030
|
+
async masfeatCheckKillSwitch(systemId, request) {
|
|
4031
|
+
const url = `${this.config.endpoint}/api/v1/masfeat/killswitch/${systemId}/check`;
|
|
4032
|
+
const response = await fetch(url, {
|
|
4033
|
+
method: 'POST',
|
|
4034
|
+
headers: {
|
|
4035
|
+
'Content-Type': 'application/json',
|
|
4036
|
+
...this.getAuthHeaders(),
|
|
4037
|
+
},
|
|
4038
|
+
body: JSON.stringify({
|
|
4039
|
+
accuracy: request.accuracy,
|
|
4040
|
+
bias_score: request.biasScore,
|
|
4041
|
+
error_rate: request.errorRate,
|
|
4042
|
+
}),
|
|
4043
|
+
signal: AbortSignal.timeout(this.config.timeout),
|
|
4044
|
+
});
|
|
4045
|
+
if (!response.ok) {
|
|
4046
|
+
const errorText = await response.text();
|
|
4047
|
+
throw new errors_1.APIError(response.status, response.statusText, errorText);
|
|
4048
|
+
}
|
|
4049
|
+
return this.mapKillSwitchResponse(await response.json());
|
|
4050
|
+
}
|
|
4051
|
+
async masfeatTriggerKillSwitch(systemId, request) {
|
|
4052
|
+
const url = `${this.config.endpoint}/api/v1/masfeat/killswitch/${systemId}/trigger`;
|
|
4053
|
+
const response = await fetch(url, {
|
|
4054
|
+
method: 'POST',
|
|
4055
|
+
headers: {
|
|
4056
|
+
'Content-Type': 'application/json',
|
|
4057
|
+
...this.getAuthHeaders(),
|
|
4058
|
+
},
|
|
4059
|
+
body: JSON.stringify({
|
|
4060
|
+
reason: request.reason,
|
|
4061
|
+
triggered_by: request.triggeredBy,
|
|
4062
|
+
}),
|
|
4063
|
+
signal: AbortSignal.timeout(this.config.timeout),
|
|
4064
|
+
});
|
|
4065
|
+
if (!response.ok) {
|
|
4066
|
+
const errorText = await response.text();
|
|
4067
|
+
throw new errors_1.APIError(response.status, response.statusText, errorText);
|
|
4068
|
+
}
|
|
4069
|
+
return this.mapKillSwitchResponse(await response.json());
|
|
4070
|
+
}
|
|
4071
|
+
async masfeatRestoreKillSwitch(systemId, request) {
|
|
4072
|
+
const url = `${this.config.endpoint}/api/v1/masfeat/killswitch/${systemId}/restore`;
|
|
4073
|
+
const response = await fetch(url, {
|
|
4074
|
+
method: 'POST',
|
|
4075
|
+
headers: {
|
|
4076
|
+
'Content-Type': 'application/json',
|
|
4077
|
+
...this.getAuthHeaders(),
|
|
4078
|
+
},
|
|
4079
|
+
body: JSON.stringify({
|
|
4080
|
+
reason: request.reason,
|
|
4081
|
+
restored_by: request.restoredBy,
|
|
4082
|
+
}),
|
|
4083
|
+
signal: AbortSignal.timeout(this.config.timeout),
|
|
4084
|
+
});
|
|
4085
|
+
if (!response.ok) {
|
|
4086
|
+
const errorText = await response.text();
|
|
4087
|
+
throw new errors_1.APIError(response.status, response.statusText, errorText);
|
|
4088
|
+
}
|
|
4089
|
+
return this.mapKillSwitchResponse(await response.json());
|
|
4090
|
+
}
|
|
4091
|
+
async masfeatEnableKillSwitch(systemId) {
|
|
4092
|
+
const url = `${this.config.endpoint}/api/v1/masfeat/killswitch/${systemId}/enable`;
|
|
4093
|
+
const response = await fetch(url, {
|
|
4094
|
+
method: 'POST',
|
|
4095
|
+
headers: {
|
|
4096
|
+
'Content-Type': 'application/json',
|
|
4097
|
+
...this.getAuthHeaders(),
|
|
4098
|
+
},
|
|
4099
|
+
signal: AbortSignal.timeout(this.config.timeout),
|
|
4100
|
+
});
|
|
4101
|
+
if (!response.ok) {
|
|
4102
|
+
const errorText = await response.text();
|
|
4103
|
+
throw new errors_1.APIError(response.status, response.statusText, errorText);
|
|
4104
|
+
}
|
|
4105
|
+
return this.mapKillSwitchResponse(await response.json());
|
|
4106
|
+
}
|
|
4107
|
+
async masfeatDisableKillSwitch(systemId, request) {
|
|
4108
|
+
const url = `${this.config.endpoint}/api/v1/masfeat/killswitch/${systemId}/disable`;
|
|
4109
|
+
const response = await fetch(url, {
|
|
4110
|
+
method: 'POST',
|
|
4111
|
+
headers: {
|
|
4112
|
+
'Content-Type': 'application/json',
|
|
4113
|
+
...this.getAuthHeaders(),
|
|
4114
|
+
},
|
|
4115
|
+
body: JSON.stringify({ reason: request?.reason }),
|
|
4116
|
+
signal: AbortSignal.timeout(this.config.timeout),
|
|
4117
|
+
});
|
|
4118
|
+
if (!response.ok) {
|
|
4119
|
+
const errorText = await response.text();
|
|
4120
|
+
throw new errors_1.APIError(response.status, response.statusText, errorText);
|
|
4121
|
+
}
|
|
4122
|
+
return this.mapKillSwitchResponse(await response.json());
|
|
4123
|
+
}
|
|
4124
|
+
async masfeatGetKillSwitchHistory(systemId, limit) {
|
|
4125
|
+
const params = new URLSearchParams();
|
|
4126
|
+
if (limit)
|
|
4127
|
+
params.append('limit', limit.toString());
|
|
4128
|
+
const queryString = params.toString();
|
|
4129
|
+
const url = `${this.config.endpoint}/api/v1/masfeat/killswitch/${systemId}/history${queryString ? `?${queryString}` : ''}`;
|
|
4130
|
+
const response = await fetch(url, {
|
|
4131
|
+
method: 'GET',
|
|
4132
|
+
headers: {
|
|
4133
|
+
...this.getAuthHeaders(),
|
|
4134
|
+
},
|
|
4135
|
+
signal: AbortSignal.timeout(this.config.timeout),
|
|
4136
|
+
});
|
|
4137
|
+
if (!response.ok) {
|
|
4138
|
+
const errorText = await response.text();
|
|
4139
|
+
throw new errors_1.APIError(response.status, response.statusText, errorText);
|
|
4140
|
+
}
|
|
4141
|
+
let data = await response.json();
|
|
4142
|
+
// Handle nested response format {history: [...], count: N}
|
|
4143
|
+
if (data && typeof data === 'object' && 'history' in data) {
|
|
4144
|
+
data = data.history;
|
|
4145
|
+
}
|
|
4146
|
+
return (data || []).map((e) => ({
|
|
4147
|
+
id: e.id,
|
|
4148
|
+
killSwitchId: e.kill_switch_id,
|
|
4149
|
+
// Handle both API formats: event_type (SDK expected) vs action (API actual)
|
|
4150
|
+
eventType: e.event_type || e.action,
|
|
4151
|
+
// Build eventData from additional fields if not present
|
|
4152
|
+
eventData: e.event_data ||
|
|
4153
|
+
(e.previous_status || e.new_status || e.reason
|
|
4154
|
+
? { previousStatus: e.previous_status, newStatus: e.new_status, reason: e.reason }
|
|
4155
|
+
: undefined),
|
|
4156
|
+
// Handle both API formats: created_by vs performed_by
|
|
4157
|
+
createdBy: e.created_by || e.performed_by,
|
|
4158
|
+
// Handle both API formats: created_at vs performed_at
|
|
4159
|
+
createdAt: new Date(e.created_at || e.performed_at),
|
|
4160
|
+
}));
|
|
4161
|
+
}
|
|
4162
|
+
// Helper methods for MAS FEAT
|
|
4163
|
+
mapSystemResponse(data) {
|
|
4164
|
+
return {
|
|
4165
|
+
id: data.id,
|
|
4166
|
+
orgId: data.org_id,
|
|
4167
|
+
systemId: data.system_id,
|
|
4168
|
+
systemName: data.system_name,
|
|
4169
|
+
description: data.description,
|
|
4170
|
+
useCase: data.use_case,
|
|
4171
|
+
ownerTeam: data.owner_team,
|
|
4172
|
+
technicalOwner: data.technical_owner,
|
|
4173
|
+
businessOwner: data.business_owner || data.owner_email,
|
|
4174
|
+
customerImpact: data.customer_impact ?? data.risk_rating_impact,
|
|
4175
|
+
modelComplexity: data.model_complexity ?? data.risk_rating_complexity,
|
|
4176
|
+
humanReliance: data.human_reliance ?? data.risk_rating_reliance,
|
|
4177
|
+
materiality: data.materiality || data.materiality_classification,
|
|
4178
|
+
status: data.status,
|
|
4179
|
+
metadata: data.metadata,
|
|
4180
|
+
createdAt: new Date(data.created_at),
|
|
4181
|
+
updatedAt: new Date(data.updated_at),
|
|
4182
|
+
createdBy: data.created_by,
|
|
4183
|
+
};
|
|
4184
|
+
}
|
|
4185
|
+
mapFindingResponse(data) {
|
|
4186
|
+
return {
|
|
4187
|
+
id: data.id,
|
|
4188
|
+
pillar: data.pillar,
|
|
4189
|
+
severity: data.severity,
|
|
4190
|
+
category: data.category,
|
|
4191
|
+
description: data.description,
|
|
4192
|
+
status: data.status,
|
|
4193
|
+
remediation: data.remediation,
|
|
4194
|
+
dueDate: data.due_date ? new Date(data.due_date) : undefined,
|
|
4195
|
+
};
|
|
4196
|
+
}
|
|
4197
|
+
mapAssessmentResponse(data) {
|
|
4198
|
+
return {
|
|
4199
|
+
id: data.id,
|
|
4200
|
+
orgId: data.org_id,
|
|
4201
|
+
systemId: data.system_id,
|
|
4202
|
+
assessmentType: data.assessment_type,
|
|
4203
|
+
status: data.status,
|
|
4204
|
+
assessmentDate: new Date(data.assessment_date),
|
|
4205
|
+
validUntil: data.valid_until ? new Date(data.valid_until) : undefined,
|
|
4206
|
+
fairnessScore: data.fairness_score,
|
|
4207
|
+
ethicsScore: data.ethics_score,
|
|
4208
|
+
accountabilityScore: data.accountability_score,
|
|
4209
|
+
transparencyScore: data.transparency_score,
|
|
4210
|
+
overallScore: data.overall_score,
|
|
4211
|
+
fairnessDetails: data.fairness_details,
|
|
4212
|
+
ethicsDetails: data.ethics_details,
|
|
4213
|
+
accountabilityDetails: data.accountability_details,
|
|
4214
|
+
transparencyDetails: data.transparency_details,
|
|
4215
|
+
findings: data.findings?.map((f) => this.mapFindingResponse(f)),
|
|
4216
|
+
recommendations: data.recommendations,
|
|
4217
|
+
assessors: data.assessors,
|
|
4218
|
+
approvedBy: data.approved_by,
|
|
4219
|
+
approvedAt: data.approved_at ? new Date(data.approved_at) : undefined,
|
|
4220
|
+
createdAt: new Date(data.created_at),
|
|
4221
|
+
updatedAt: new Date(data.updated_at),
|
|
4222
|
+
createdBy: data.created_by,
|
|
4223
|
+
};
|
|
4224
|
+
}
|
|
4225
|
+
mapKillSwitchResponse(data) {
|
|
4226
|
+
// Handle nested response format (trigger/restore return {kill_switch: {...}, message: ...})
|
|
4227
|
+
if (data.kill_switch) {
|
|
4228
|
+
data = data.kill_switch;
|
|
4229
|
+
}
|
|
4230
|
+
return {
|
|
4231
|
+
id: data.id,
|
|
4232
|
+
orgId: data.org_id,
|
|
4233
|
+
systemId: data.system_id,
|
|
4234
|
+
status: data.status,
|
|
4235
|
+
accuracyThreshold: data.accuracy_threshold,
|
|
4236
|
+
biasThreshold: data.bias_threshold,
|
|
4237
|
+
errorRateThreshold: data.error_rate_threshold,
|
|
4238
|
+
autoTriggerEnabled: data.auto_trigger_enabled,
|
|
4239
|
+
triggeredAt: data.triggered_at ? new Date(data.triggered_at) : undefined,
|
|
4240
|
+
triggeredBy: data.triggered_by,
|
|
4241
|
+
triggeredReason: data.triggered_reason || data.trigger_reason,
|
|
4242
|
+
restoredAt: data.restored_at ? new Date(data.restored_at) : undefined,
|
|
4243
|
+
restoredBy: data.restored_by,
|
|
4244
|
+
createdAt: new Date(data.created_at),
|
|
4245
|
+
updatedAt: new Date(data.updated_at),
|
|
4246
|
+
};
|
|
4247
|
+
}
|
|
4248
|
+
// ============================================================================
|
|
4249
|
+
// Unified Execution Tracking Methods (Issue #1075)
|
|
4250
|
+
// ============================================================================
|
|
4251
|
+
/**
|
|
4252
|
+
* Get unified execution status for a MAP plan or WCP workflow.
|
|
4253
|
+
*
|
|
4254
|
+
* This method provides a consistent interface for tracking execution progress
|
|
4255
|
+
* regardless of whether the underlying execution is a MAP plan or WCP workflow.
|
|
4256
|
+
*
|
|
4257
|
+
* @param executionId - The execution ID (plan ID or workflow ID)
|
|
4258
|
+
* @returns Unified execution status
|
|
4259
|
+
*
|
|
4260
|
+
* @example
|
|
4261
|
+
* ```typescript
|
|
4262
|
+
* // Get status for any execution (MAP or WCP)
|
|
4263
|
+
* const status = await client.getExecutionStatus('exec_123');
|
|
4264
|
+
* console.log(`Type: ${status.execution_type}`);
|
|
4265
|
+
* console.log(`Status: ${status.status}`);
|
|
4266
|
+
* console.log(`Progress: ${status.progress_percent}%`);
|
|
4267
|
+
*
|
|
4268
|
+
* // Check steps
|
|
4269
|
+
* for (const step of status.steps) {
|
|
4270
|
+
* console.log(` Step ${step.step_index}: ${step.step_name} - ${step.status}`);
|
|
4271
|
+
* }
|
|
4272
|
+
* ```
|
|
4273
|
+
*/
|
|
4274
|
+
async getExecutionStatus(executionId) {
|
|
4275
|
+
if (!executionId) {
|
|
4276
|
+
throw new errors_1.ConfigurationError('Execution ID is required');
|
|
4277
|
+
}
|
|
4278
|
+
if (this.config.debug) {
|
|
4279
|
+
(0, helpers_1.debugLog)('Getting execution status', { executionId });
|
|
4280
|
+
}
|
|
4281
|
+
return this.orchestratorRequest('GET', `/api/v1/unified/executions/${executionId}`);
|
|
4282
|
+
}
|
|
4283
|
+
/**
|
|
4284
|
+
* List unified executions with optional filters.
|
|
4285
|
+
*
|
|
4286
|
+
* Returns a paginated list of executions (both MAP plans and WCP workflows)
|
|
4287
|
+
* with optional filtering by type, status, tenant, or organization.
|
|
4288
|
+
* This method provides a unified view across all execution types.
|
|
4289
|
+
*
|
|
4290
|
+
* @param options - Filter and pagination options
|
|
4291
|
+
* @returns Paginated list of unified executions
|
|
4292
|
+
*
|
|
4293
|
+
* @example
|
|
4294
|
+
* ```typescript
|
|
4295
|
+
* // List all running executions
|
|
4296
|
+
* const result = await client.listUnifiedExecutions({
|
|
4297
|
+
* status: 'running',
|
|
4298
|
+
* limit: 20
|
|
4299
|
+
* });
|
|
4300
|
+
* console.log(`Found ${result.total} running executions`);
|
|
4301
|
+
*
|
|
4302
|
+
* // List only MAP plans
|
|
4303
|
+
* const mapPlans = await client.listUnifiedExecutions({
|
|
4304
|
+
* execution_type: 'map_plan',
|
|
4305
|
+
* limit: 50
|
|
4306
|
+
* });
|
|
4307
|
+
*
|
|
4308
|
+
* // List WCP workflows for a specific tenant
|
|
4309
|
+
* const workflows = await client.listUnifiedExecutions({
|
|
4310
|
+
* execution_type: 'wcp_workflow',
|
|
4311
|
+
* tenant_id: 'tenant_123'
|
|
4312
|
+
* });
|
|
4313
|
+
* ```
|
|
4314
|
+
*/
|
|
4315
|
+
async listUnifiedExecutions(options) {
|
|
4316
|
+
const params = new URLSearchParams();
|
|
4317
|
+
if (options?.execution_type) {
|
|
4318
|
+
params.set('execution_type', options.execution_type);
|
|
4319
|
+
}
|
|
4320
|
+
if (options?.status) {
|
|
4321
|
+
params.set('status', options.status);
|
|
4322
|
+
}
|
|
4323
|
+
if (options?.tenant_id) {
|
|
4324
|
+
params.set('tenant_id', options.tenant_id);
|
|
4325
|
+
}
|
|
4326
|
+
if (options?.org_id) {
|
|
4327
|
+
params.set('org_id', options.org_id);
|
|
4328
|
+
}
|
|
4329
|
+
if (options?.limit !== undefined) {
|
|
4330
|
+
params.set('limit', options.limit.toString());
|
|
4331
|
+
}
|
|
4332
|
+
if (options?.offset !== undefined) {
|
|
4333
|
+
params.set('offset', options.offset.toString());
|
|
4334
|
+
}
|
|
4335
|
+
const queryString = params.toString();
|
|
4336
|
+
const path = queryString
|
|
4337
|
+
? `/api/v1/unified/executions?${queryString}`
|
|
4338
|
+
: '/api/v1/unified/executions';
|
|
4339
|
+
if (this.config.debug) {
|
|
4340
|
+
(0, helpers_1.debugLog)('Listing unified executions', { options });
|
|
4341
|
+
}
|
|
4342
|
+
return this.orchestratorRequest('GET', path);
|
|
4343
|
+
}
|
|
4344
|
+
/**
|
|
4345
|
+
* Cancel a unified execution (MAP plan or WCP workflow).
|
|
4346
|
+
*
|
|
4347
|
+
* This method cancels an execution via the unified execution API,
|
|
4348
|
+
* automatically propagating to the correct subsystem (MAP or WCP).
|
|
4349
|
+
*
|
|
4350
|
+
* @param executionId - The execution ID (plan ID or workflow ID)
|
|
4351
|
+
* @param reason - Optional reason for cancellation
|
|
4352
|
+
*
|
|
4353
|
+
* @example
|
|
4354
|
+
* ```typescript
|
|
4355
|
+
* await client.cancelExecution('wf_abc123', 'User requested cancellation');
|
|
4356
|
+
* ```
|
|
4357
|
+
*/
|
|
4358
|
+
async cancelExecution(executionId, reason) {
|
|
4359
|
+
if (!executionId) {
|
|
4360
|
+
throw new errors_1.ConfigurationError('Execution ID is required');
|
|
4361
|
+
}
|
|
4362
|
+
const body = reason ? { reason } : {};
|
|
4363
|
+
await this.orchestratorRequest('POST', `/api/v1/unified/executions/${executionId}/cancel`, body);
|
|
4364
|
+
}
|
|
4365
|
+
/**
|
|
4366
|
+
* Stream real-time execution status updates via Server-Sent Events (SSE).
|
|
4367
|
+
*
|
|
4368
|
+
* Connects to the SSE streaming endpoint and invokes the callback with each
|
|
4369
|
+
* ExecutionStatus update as it arrives. The stream automatically closes when
|
|
4370
|
+
* the execution reaches a terminal state (completed, failed, cancelled, aborted,
|
|
4371
|
+
* or expired).
|
|
4372
|
+
*
|
|
4373
|
+
* @param executionId - The execution ID (plan ID or workflow ID)
|
|
4374
|
+
* @param callback - Function called with each ExecutionStatus update
|
|
4375
|
+
* @param options - Optional configuration including an AbortSignal for cancellation
|
|
4376
|
+
*
|
|
4377
|
+
* @example
|
|
4378
|
+
* ```typescript
|
|
4379
|
+
* // Stream with a callback
|
|
4380
|
+
* await client.streamExecutionStatus('exec_123', (status) => {
|
|
4381
|
+
* console.log(`Progress: ${status.progress_percent}%`);
|
|
4382
|
+
* console.log(`Status: ${status.status}`);
|
|
4383
|
+
* for (const step of status.steps) {
|
|
4384
|
+
* console.log(` Step ${step.step_index}: ${step.step_name} - ${step.status}`);
|
|
4385
|
+
* }
|
|
4386
|
+
* });
|
|
4387
|
+
*
|
|
4388
|
+
* // Stream with abort support
|
|
4389
|
+
* const controller = new AbortController();
|
|
4390
|
+
* setTimeout(() => controller.abort(), 60000); // timeout after 1 minute
|
|
4391
|
+
* await client.streamExecutionStatus('exec_123', (status) => {
|
|
4392
|
+
* console.log(`${status.status}: ${status.progress_percent}%`);
|
|
4393
|
+
* }, { signal: controller.signal });
|
|
4394
|
+
* ```
|
|
4395
|
+
*/
|
|
4396
|
+
async streamExecutionStatus(executionId, callback, options) {
|
|
4397
|
+
if (!executionId) {
|
|
4398
|
+
throw new errors_1.ConfigurationError('Execution ID is required');
|
|
4399
|
+
}
|
|
4400
|
+
const url = `${this.config.endpoint}/api/v1/executions/${executionId}/stream`;
|
|
4401
|
+
const headers = this.buildAuthHeaders();
|
|
4402
|
+
// Override Content-Type for SSE — Accept is what matters
|
|
4403
|
+
headers['Accept'] = 'text/event-stream';
|
|
4404
|
+
delete headers['Content-Type'];
|
|
4405
|
+
if (this.config.debug) {
|
|
4406
|
+
(0, helpers_1.debugLog)('Streaming execution status', { executionId, url });
|
|
4407
|
+
}
|
|
4408
|
+
const fetchOptions = {
|
|
4409
|
+
method: 'GET',
|
|
4410
|
+
headers,
|
|
4411
|
+
};
|
|
4412
|
+
if (options?.signal) {
|
|
4413
|
+
fetchOptions.signal = options.signal;
|
|
4414
|
+
}
|
|
4415
|
+
let response;
|
|
4416
|
+
try {
|
|
4417
|
+
response = await fetch(url, fetchOptions);
|
|
4418
|
+
}
|
|
4419
|
+
catch (error) {
|
|
4420
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
4421
|
+
return; // Clean exit on abort
|
|
4422
|
+
}
|
|
4423
|
+
throw error;
|
|
4424
|
+
}
|
|
4425
|
+
if (!response.ok) {
|
|
4426
|
+
const errorText = await response.text();
|
|
4427
|
+
if (response.status === 401 || response.status === 403) {
|
|
4428
|
+
throw new errors_1.AuthenticationError(`Stream request failed: ${errorText}`);
|
|
4429
|
+
}
|
|
4430
|
+
if (response.status === 404) {
|
|
4431
|
+
throw new errors_1.APIError(404, 'Not Found', errorText);
|
|
4432
|
+
}
|
|
4433
|
+
throw new errors_1.APIError(response.status, response.statusText, errorText);
|
|
4434
|
+
}
|
|
4435
|
+
if (!response.body) {
|
|
4436
|
+
throw new errors_1.APIError(0, 'No Body', 'SSE response has no body');
|
|
4437
|
+
}
|
|
4438
|
+
const reader = response.body.getReader();
|
|
4439
|
+
const decoder = new TextDecoder();
|
|
4440
|
+
let buffer = '';
|
|
4441
|
+
try {
|
|
4442
|
+
while (true) {
|
|
4443
|
+
const { done, value } = await reader.read();
|
|
4444
|
+
if (done) {
|
|
4445
|
+
break;
|
|
4446
|
+
}
|
|
4447
|
+
buffer += decoder.decode(value, { stream: true });
|
|
4448
|
+
// Process complete SSE events (separated by double newline)
|
|
4449
|
+
const events = buffer.split('\n\n');
|
|
4450
|
+
// Keep the last (potentially incomplete) chunk in the buffer
|
|
4451
|
+
buffer = events.pop() || '';
|
|
4452
|
+
for (const event of events) {
|
|
4453
|
+
const trimmed = event.trim();
|
|
4454
|
+
if (!trimmed) {
|
|
4455
|
+
continue;
|
|
4456
|
+
}
|
|
4457
|
+
// Parse SSE data lines (handle both "data: " and "data:" formats per SSE spec)
|
|
4458
|
+
for (const line of trimmed.split('\n')) {
|
|
4459
|
+
let jsonStr;
|
|
4460
|
+
if (line.startsWith('data: ')) {
|
|
4461
|
+
jsonStr = line.slice(6);
|
|
4462
|
+
}
|
|
4463
|
+
else if (line.startsWith('data:')) {
|
|
4464
|
+
jsonStr = line.slice(5);
|
|
4465
|
+
}
|
|
4466
|
+
if (jsonStr !== undefined) {
|
|
4467
|
+
if (!jsonStr || jsonStr === '[DONE]') {
|
|
4468
|
+
continue;
|
|
4469
|
+
}
|
|
4470
|
+
try {
|
|
4471
|
+
const status = JSON.parse(jsonStr);
|
|
4472
|
+
callback(status);
|
|
4473
|
+
// Check for terminal status — stream is done
|
|
4474
|
+
if (status.status === 'completed' ||
|
|
4475
|
+
status.status === 'failed' ||
|
|
4476
|
+
status.status === 'cancelled' ||
|
|
4477
|
+
status.status === 'aborted' ||
|
|
4478
|
+
status.status === 'expired') {
|
|
4479
|
+
return;
|
|
4480
|
+
}
|
|
4481
|
+
}
|
|
4482
|
+
catch (parseError) {
|
|
4483
|
+
if (this.config.debug) {
|
|
4484
|
+
(0, helpers_1.debugLog)('Failed to parse SSE data', { jsonStr, error: parseError });
|
|
4485
|
+
}
|
|
4486
|
+
}
|
|
4487
|
+
}
|
|
4488
|
+
}
|
|
4489
|
+
}
|
|
4490
|
+
}
|
|
4491
|
+
}
|
|
4492
|
+
catch (error) {
|
|
4493
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
4494
|
+
return; // Clean exit on abort
|
|
4495
|
+
}
|
|
4496
|
+
throw error;
|
|
4497
|
+
}
|
|
4498
|
+
finally {
|
|
4499
|
+
try {
|
|
4500
|
+
reader.releaseLock();
|
|
4501
|
+
}
|
|
4502
|
+
catch {
|
|
4503
|
+
// Reader may already be released
|
|
4504
|
+
}
|
|
4505
|
+
}
|
|
4506
|
+
}
|
|
2792
4507
|
}
|
|
2793
4508
|
exports.AxonFlow = AxonFlow;
|
|
2794
4509
|
//# sourceMappingURL=client.js.map
|