@axonflow/sdk 2.3.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 +487 -8
- package/dist/cjs/client.d.ts.map +1 -1
- package/dist/cjs/client.js +1672 -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 +54 -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 +487 -8
- package/dist/esm/client.d.ts.map +1 -1
- package/dist/esm/client.js +1673 -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 +54 -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,
|
|
@@ -671,14 +730,22 @@ class AxonFlow {
|
|
|
671
730
|
* @param query - Natural language query describing the task
|
|
672
731
|
* @param domain - Optional domain hint (travel, healthcare, etc.)
|
|
673
732
|
* @param userToken - Optional user token for authentication (defaults to tenant/client_id)
|
|
733
|
+
* @param options - Optional plan generation options (execution mode, etc.)
|
|
674
734
|
*/
|
|
675
|
-
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
|
+
}
|
|
676
743
|
const agentRequest = {
|
|
677
744
|
query,
|
|
678
|
-
user_token: userToken || this.config.tenant,
|
|
679
|
-
client_id: this.config.tenant,
|
|
745
|
+
user_token: userToken || this.config.clientId || this.config.tenant,
|
|
746
|
+
client_id: this.config.clientId || this.config.tenant,
|
|
680
747
|
request_type: 'multi-agent-plan',
|
|
681
|
-
context
|
|
748
|
+
context,
|
|
682
749
|
};
|
|
683
750
|
const url = `${this.config.endpoint}/api/request`;
|
|
684
751
|
const headers = {
|
|
@@ -722,8 +789,8 @@ class AxonFlow {
|
|
|
722
789
|
async executePlan(planId, userToken) {
|
|
723
790
|
const agentRequest = {
|
|
724
791
|
query: '',
|
|
725
|
-
user_token: userToken || this.config.tenant,
|
|
726
|
-
client_id: this.config.tenant,
|
|
792
|
+
user_token: userToken || this.config.clientId || this.config.tenant,
|
|
793
|
+
client_id: this.config.clientId || this.config.tenant,
|
|
727
794
|
request_type: 'execute-plan',
|
|
728
795
|
context: { plan_id: planId },
|
|
729
796
|
};
|
|
@@ -744,16 +811,43 @@ class AxonFlow {
|
|
|
744
811
|
throw new errors_1.PlanExecutionError(`Plan execution failed: ${response.status} ${response.statusText} - ${errorText}`, planId, 'execution');
|
|
745
812
|
}
|
|
746
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;
|
|
747
828
|
if (this.config.debug) {
|
|
748
|
-
(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;
|
|
749
842
|
}
|
|
750
843
|
return {
|
|
751
844
|
planId,
|
|
752
|
-
status
|
|
753
|
-
result
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
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,
|
|
757
851
|
};
|
|
758
852
|
}
|
|
759
853
|
/**
|
|
@@ -779,6 +873,150 @@ class AxonFlow {
|
|
|
779
873
|
duration: status.duration,
|
|
780
874
|
};
|
|
781
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
|
+
}
|
|
782
1020
|
// ============================================================================
|
|
783
1021
|
// Gateway Mode Methods
|
|
784
1022
|
// ============================================================================
|
|
@@ -831,12 +1069,12 @@ class AxonFlow {
|
|
|
831
1069
|
* ```
|
|
832
1070
|
*/
|
|
833
1071
|
async getPolicyApprovedContext(options) {
|
|
834
|
-
//
|
|
835
|
-
|
|
1072
|
+
// Use smart default for clientId - enables zero-config community mode
|
|
1073
|
+
const clientId = this.getEffectiveClientId();
|
|
836
1074
|
const url = `${this.config.endpoint}/api/policy/pre-check`;
|
|
837
1075
|
const requestBody = {
|
|
838
1076
|
user_token: options.userToken,
|
|
839
|
-
client_id:
|
|
1077
|
+
client_id: clientId,
|
|
840
1078
|
query: options.query,
|
|
841
1079
|
data_sources: options.dataSources || [],
|
|
842
1080
|
context: options.context || {},
|
|
@@ -916,12 +1154,12 @@ class AxonFlow {
|
|
|
916
1154
|
* ```
|
|
917
1155
|
*/
|
|
918
1156
|
async auditLLMCall(options) {
|
|
919
|
-
//
|
|
920
|
-
|
|
1157
|
+
// Use smart default for clientId - enables zero-config community mode
|
|
1158
|
+
const clientId = this.getEffectiveClientId();
|
|
921
1159
|
const url = `${this.config.endpoint}/api/audit/llm-call`;
|
|
922
1160
|
const requestBody = {
|
|
923
1161
|
context_id: options.contextId,
|
|
924
|
-
client_id:
|
|
1162
|
+
client_id: clientId,
|
|
925
1163
|
response_summary: options.responseSummary,
|
|
926
1164
|
provider: options.provider,
|
|
927
1165
|
model: options.model,
|
|
@@ -1127,11 +1365,9 @@ class AxonFlow {
|
|
|
1127
1365
|
'Content-Type': 'application/json',
|
|
1128
1366
|
...this.getAuthHeaders(),
|
|
1129
1367
|
};
|
|
1130
|
-
//
|
|
1131
|
-
//
|
|
1132
|
-
|
|
1133
|
-
headers['X-Org-ID'] = this.config.tenant;
|
|
1134
|
-
}
|
|
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
|
|
1135
1371
|
return headers;
|
|
1136
1372
|
}
|
|
1137
1373
|
/**
|
|
@@ -1491,6 +1727,10 @@ class AxonFlow {
|
|
|
1491
1727
|
const params = new URLSearchParams();
|
|
1492
1728
|
if (options?.type)
|
|
1493
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);
|
|
1494
1734
|
if (options?.enabled !== undefined)
|
|
1495
1735
|
params.set('enabled', String(options.enabled));
|
|
1496
1736
|
if (options?.limit)
|
|
@@ -1551,8 +1791,27 @@ class AxonFlow {
|
|
|
1551
1791
|
if (this.config.debug) {
|
|
1552
1792
|
(0, helpers_1.debugLog)('Creating dynamic policy', { name: policy.name });
|
|
1553
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
|
+
}
|
|
1554
1813
|
// API returns {"policy": {...}} wrapper via Agent proxy
|
|
1555
|
-
const response = await this.orchestratorRequest('POST', '/api/v1/dynamic-policies',
|
|
1814
|
+
const response = await this.orchestratorRequest('POST', '/api/v1/dynamic-policies', requestBody);
|
|
1556
1815
|
// Handle both wrapped and unwrapped responses for compatibility
|
|
1557
1816
|
return 'policy' in response ? response.policy : response;
|
|
1558
1817
|
}
|
|
@@ -1567,8 +1826,30 @@ class AxonFlow {
|
|
|
1567
1826
|
if (this.config.debug) {
|
|
1568
1827
|
(0, helpers_1.debugLog)('Updating dynamic policy', { id, updates: Object.keys(policy) });
|
|
1569
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;
|
|
1570
1851
|
// API returns {"policy": {...}} wrapper via Agent proxy
|
|
1571
|
-
const response = await this.orchestratorRequest('PUT', `/api/v1/dynamic-policies/${id}`,
|
|
1852
|
+
const response = await this.orchestratorRequest('PUT', `/api/v1/dynamic-policies/${id}`, requestBody);
|
|
1572
1853
|
// Handle both wrapped and unwrapped responses for compatibility
|
|
1573
1854
|
return 'policy' in response ? response.policy : response;
|
|
1574
1855
|
}
|
|
@@ -2877,6 +3158,1352 @@ class AxonFlow {
|
|
|
2877
3158
|
}
|
|
2878
3159
|
return response.text();
|
|
2879
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
|
+
}
|
|
2880
4507
|
}
|
|
2881
4508
|
exports.AxonFlow = AxonFlow;
|
|
2882
4509
|
//# sourceMappingURL=client.js.map
|