@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.
Files changed (126) hide show
  1. package/README.md +185 -8
  2. package/dist/cjs/client.d.ts +487 -8
  3. package/dist/cjs/client.d.ts.map +1 -1
  4. package/dist/cjs/client.js +1672 -45
  5. package/dist/cjs/client.js.map +1 -1
  6. package/dist/cjs/errors.d.ts +22 -0
  7. package/dist/cjs/errors.d.ts.map +1 -1
  8. package/dist/cjs/errors.js +32 -1
  9. package/dist/cjs/errors.js.map +1 -1
  10. package/dist/cjs/index.d.ts +10 -14
  11. package/dist/cjs/index.d.ts.map +1 -1
  12. package/dist/cjs/index.js +11 -20
  13. package/dist/cjs/index.js.map +1 -1
  14. package/dist/cjs/types/connector.d.ts +54 -0
  15. package/dist/cjs/types/connector.d.ts.map +1 -1
  16. package/dist/cjs/types/connector.js +7 -0
  17. package/dist/cjs/types/connector.js.map +1 -1
  18. package/dist/cjs/types/execution.d.ts +227 -0
  19. package/dist/cjs/types/execution.d.ts.map +1 -0
  20. package/dist/cjs/types/execution.js +73 -0
  21. package/dist/cjs/types/execution.js.map +1 -0
  22. package/dist/cjs/types/index.d.ts +3 -0
  23. package/dist/cjs/types/index.d.ts.map +1 -1
  24. package/dist/cjs/types/index.js +3 -0
  25. package/dist/cjs/types/index.js.map +1 -1
  26. package/dist/cjs/types/masfeat.d.ts +238 -0
  27. package/dist/cjs/types/masfeat.d.ts.map +1 -0
  28. package/dist/cjs/types/masfeat.js +11 -0
  29. package/dist/cjs/types/masfeat.js.map +1 -0
  30. package/dist/cjs/types/planning.d.ts +126 -1
  31. package/dist/cjs/types/planning.d.ts.map +1 -1
  32. package/dist/cjs/types/policies.d.ts +19 -1
  33. package/dist/cjs/types/policies.d.ts.map +1 -1
  34. package/dist/cjs/types/proxy.d.ts +29 -3
  35. package/dist/cjs/types/proxy.d.ts.map +1 -1
  36. package/dist/cjs/types/workflows.d.ts +318 -0
  37. package/dist/cjs/types/workflows.d.ts.map +1 -0
  38. package/dist/cjs/types/workflows.js +61 -0
  39. package/dist/cjs/types/workflows.js.map +1 -0
  40. package/dist/esm/client.d.ts +487 -8
  41. package/dist/esm/client.d.ts.map +1 -1
  42. package/dist/esm/client.js +1673 -46
  43. package/dist/esm/client.js.map +1 -1
  44. package/dist/esm/errors.d.ts +22 -0
  45. package/dist/esm/errors.d.ts.map +1 -1
  46. package/dist/esm/errors.js +30 -0
  47. package/dist/esm/errors.js.map +1 -1
  48. package/dist/esm/index.d.ts +10 -14
  49. package/dist/esm/index.d.ts.map +1 -1
  50. package/dist/esm/index.js +7 -15
  51. package/dist/esm/index.js.map +1 -1
  52. package/dist/esm/types/connector.d.ts +54 -0
  53. package/dist/esm/types/connector.d.ts.map +1 -1
  54. package/dist/esm/types/connector.js +6 -1
  55. package/dist/esm/types/connector.js.map +1 -1
  56. package/dist/esm/types/execution.d.ts +227 -0
  57. package/dist/esm/types/execution.d.ts.map +1 -0
  58. package/dist/esm/types/execution.js +70 -0
  59. package/dist/esm/types/execution.js.map +1 -0
  60. package/dist/esm/types/index.d.ts +3 -0
  61. package/dist/esm/types/index.d.ts.map +1 -1
  62. package/dist/esm/types/index.js +3 -0
  63. package/dist/esm/types/index.js.map +1 -1
  64. package/dist/esm/types/masfeat.d.ts +238 -0
  65. package/dist/esm/types/masfeat.d.ts.map +1 -0
  66. package/dist/esm/types/masfeat.js +10 -0
  67. package/dist/esm/types/masfeat.js.map +1 -0
  68. package/dist/esm/types/planning.d.ts +126 -1
  69. package/dist/esm/types/planning.d.ts.map +1 -1
  70. package/dist/esm/types/policies.d.ts +19 -1
  71. package/dist/esm/types/policies.d.ts.map +1 -1
  72. package/dist/esm/types/proxy.d.ts +29 -3
  73. package/dist/esm/types/proxy.d.ts.map +1 -1
  74. package/dist/esm/types/workflows.d.ts +318 -0
  75. package/dist/esm/types/workflows.d.ts.map +1 -0
  76. package/dist/esm/types/workflows.js +58 -0
  77. package/dist/esm/types/workflows.js.map +1 -0
  78. package/package.json +7 -2
  79. package/dist/cjs/interceptors/anthropic.d.ts +0 -40
  80. package/dist/cjs/interceptors/anthropic.d.ts.map +0 -1
  81. package/dist/cjs/interceptors/anthropic.js +0 -101
  82. package/dist/cjs/interceptors/anthropic.js.map +0 -1
  83. package/dist/cjs/interceptors/base.d.ts +0 -23
  84. package/dist/cjs/interceptors/base.d.ts.map +0 -1
  85. package/dist/cjs/interceptors/base.js +0 -10
  86. package/dist/cjs/interceptors/base.js.map +0 -1
  87. package/dist/cjs/interceptors/bedrock.d.ts +0 -142
  88. package/dist/cjs/interceptors/bedrock.d.ts.map +0 -1
  89. package/dist/cjs/interceptors/bedrock.js +0 -263
  90. package/dist/cjs/interceptors/bedrock.js.map +0 -1
  91. package/dist/cjs/interceptors/gemini.d.ts +0 -89
  92. package/dist/cjs/interceptors/gemini.d.ts.map +0 -1
  93. package/dist/cjs/interceptors/gemini.js +0 -121
  94. package/dist/cjs/interceptors/gemini.js.map +0 -1
  95. package/dist/cjs/interceptors/ollama.d.ts +0 -143
  96. package/dist/cjs/interceptors/ollama.d.ts.map +0 -1
  97. package/dist/cjs/interceptors/ollama.js +0 -153
  98. package/dist/cjs/interceptors/ollama.js.map +0 -1
  99. package/dist/cjs/interceptors/openai.d.ts +0 -40
  100. package/dist/cjs/interceptors/openai.d.ts.map +0 -1
  101. package/dist/cjs/interceptors/openai.js +0 -100
  102. package/dist/cjs/interceptors/openai.js.map +0 -1
  103. package/dist/esm/interceptors/anthropic.d.ts +0 -40
  104. package/dist/esm/interceptors/anthropic.d.ts.map +0 -1
  105. package/dist/esm/interceptors/anthropic.js +0 -96
  106. package/dist/esm/interceptors/anthropic.js.map +0 -1
  107. package/dist/esm/interceptors/base.d.ts +0 -23
  108. package/dist/esm/interceptors/base.d.ts.map +0 -1
  109. package/dist/esm/interceptors/base.js +0 -6
  110. package/dist/esm/interceptors/base.js.map +0 -1
  111. package/dist/esm/interceptors/bedrock.d.ts +0 -142
  112. package/dist/esm/interceptors/bedrock.d.ts.map +0 -1
  113. package/dist/esm/interceptors/bedrock.js +0 -224
  114. package/dist/esm/interceptors/bedrock.js.map +0 -1
  115. package/dist/esm/interceptors/gemini.d.ts +0 -89
  116. package/dist/esm/interceptors/gemini.d.ts.map +0 -1
  117. package/dist/esm/interceptors/gemini.js +0 -116
  118. package/dist/esm/interceptors/gemini.js.map +0 -1
  119. package/dist/esm/interceptors/ollama.d.ts +0 -143
  120. package/dist/esm/interceptors/ollama.d.ts.map +0 -1
  121. package/dist/esm/interceptors/ollama.js +0 -147
  122. package/dist/esm/interceptors/ollama.js.map +0 -1
  123. package/dist/esm/interceptors/openai.d.ts +0 -40
  124. package/dist/esm/interceptors/openai.d.ts.map +0 -1
  125. package/dist/esm/interceptors/openai.js +0 -95
  126. package/dist/esm/interceptors/openai.js.map +0 -1
@@ -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 || 'default',
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
- // Initialize interceptors
45
- this.interceptors = [new openai_1.OpenAIInterceptor(), new anthropic_1.AnthropicInterceptor()];
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.executeQuery({
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 v2.0.0. ' +
122
- 'Use Gateway Mode (getPolicyApprovedContext + auditLLMCall) or Proxy Mode (executeQuery) instead. ' +
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
- * Execute a query through AxonFlow with policy enforcement (Proxy Mode).
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
- * This is the primary method for Proxy Mode, where AxonFlow handles policy
386
- * checking and optionally routes requests to LLM providers.
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.executeQuery({
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 executeQuery(options) {
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: executeQuery', {
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
- if (response.status === 401 || response.status === 403) {
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
- throw new errors_1.APIError(response.status, response.statusText, errorText);
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
- if (data.blocked) {
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: executeQuery result', {
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: domain ? { domain } : {},
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: agentResponse.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: agentResponse.success ? 'completed' : 'failed',
753
- result: agentResponse.result,
754
- stepResults: agentResponse.metadata?.step_results,
755
- error: agentResponse.error,
756
- duration: agentResponse.metadata?.duration,
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
- // Gateway Mode - credentials optional for community/self-hosted mode
835
- // Server decides whether to require authentication based on DEPLOYMENT_MODE
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: this.config.tenant,
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
- // Gateway Mode - credentials optional for community/self-hosted mode
920
- // Server decides whether to require authentication based on DEPLOYMENT_MODE
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: this.config.tenant,
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
- // Always include tenant ID for policy APIs (X-Org-ID header for server compatibility)
1131
- // Note: getAuthHeaders() already adds X-Tenant-ID when tenant is non-default
1132
- if (this.config.tenant) {
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', policy);
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}`, policy);
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