@axonflow/sdk 2.0.0 → 2.2.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 (42) hide show
  1. package/README.md +39 -36
  2. package/dist/cjs/client.d.ts +32 -2
  3. package/dist/cjs/client.d.ts.map +1 -1
  4. package/dist/cjs/client.js +142 -86
  5. package/dist/cjs/client.js.map +1 -1
  6. package/dist/cjs/errors.d.ts +93 -6
  7. package/dist/cjs/errors.d.ts.map +1 -1
  8. package/dist/cjs/errors.js +126 -12
  9. package/dist/cjs/errors.js.map +1 -1
  10. package/dist/cjs/index.d.ts +3 -3
  11. package/dist/cjs/index.d.ts.map +1 -1
  12. package/dist/cjs/index.js +7 -3
  13. package/dist/cjs/index.js.map +1 -1
  14. package/dist/cjs/types/code-governance.d.ts +2 -0
  15. package/dist/cjs/types/code-governance.d.ts.map +1 -1
  16. package/dist/cjs/types/config.d.ts +15 -7
  17. package/dist/cjs/types/config.d.ts.map +1 -1
  18. package/dist/cjs/types/policies.d.ts +14 -1
  19. package/dist/cjs/types/policies.d.ts.map +1 -1
  20. package/dist/cjs/types/proxy.d.ts +2 -2
  21. package/dist/cjs/types/proxy.d.ts.map +1 -1
  22. package/dist/esm/client.d.ts +32 -2
  23. package/dist/esm/client.d.ts.map +1 -1
  24. package/dist/esm/client.js +143 -87
  25. package/dist/esm/client.js.map +1 -1
  26. package/dist/esm/errors.d.ts +93 -6
  27. package/dist/esm/errors.d.ts.map +1 -1
  28. package/dist/esm/errors.js +121 -11
  29. package/dist/esm/errors.js.map +1 -1
  30. package/dist/esm/index.d.ts +3 -3
  31. package/dist/esm/index.d.ts.map +1 -1
  32. package/dist/esm/index.js +3 -3
  33. package/dist/esm/index.js.map +1 -1
  34. package/dist/esm/types/code-governance.d.ts +2 -0
  35. package/dist/esm/types/code-governance.d.ts.map +1 -1
  36. package/dist/esm/types/config.d.ts +15 -7
  37. package/dist/esm/types/config.d.ts.map +1 -1
  38. package/dist/esm/types/policies.d.ts +14 -1
  39. package/dist/esm/types/policies.d.ts.map +1 -1
  40. package/dist/esm/types/proxy.d.ts +2 -2
  41. package/dist/esm/types/proxy.d.ts.map +1 -1
  42. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { AuthenticationError, APIError, PolicyViolationError } from './errors.js';
1
+ import { AuthenticationError, APIError, PolicyViolationError, ConfigurationError, ConnectorError, PlanExecutionError, } from './errors.js';
2
2
  import { OpenAIInterceptor } from './interceptors/openai.js';
3
3
  import { AnthropicInterceptor } from './interceptors/anthropic.js';
4
4
  import { generateRequestId, debugLog } from './utils/helpers.js';
@@ -9,15 +9,19 @@ export class AxonFlow {
9
9
  constructor(config) {
10
10
  this.interceptors = [];
11
11
  this.sessionCookie = null;
12
+ // Configuration validation
13
+ if (config.clientSecret && !config.clientId) {
14
+ throw new ConfigurationError('clientSecret requires clientId to be set. ' +
15
+ 'Provide both clientId and clientSecret for OAuth2-style authentication.');
16
+ }
12
17
  // Set defaults first to determine endpoint
13
18
  const endpoint = config.endpoint || 'https://staging-eu.getaxonflow.com';
14
- // Credentials are optional for community/self-hosted deployments
15
- // Enterprise features (Gateway Mode, Policy CRUD) require credentials
16
- const hasCredentials = !!(config.licenseKey || config.apiKey);
19
+ // Credentials check: OAuth2-style (clientId/clientSecret)
20
+ const hasCredentials = !!(config.clientId && config.clientSecret);
17
21
  // Set configuration
18
22
  this.config = {
19
- apiKey: config.apiKey,
20
- licenseKey: config.licenseKey,
23
+ clientId: config.clientId,
24
+ clientSecret: config.clientSecret,
21
25
  endpoint,
22
26
  mode: config.mode || (hasCredentials ? 'production' : 'sandbox'),
23
27
  tenant: config.tenant || 'default',
@@ -37,17 +41,33 @@ export class AxonFlow {
37
41
  // Initialize interceptors
38
42
  this.interceptors = [new OpenAIInterceptor(), new AnthropicInterceptor()];
39
43
  if (this.config.debug) {
44
+ // Determine auth method for logging
45
+ const authMethod = hasCredentials ? 'client-credentials' : 'community (no auth)';
40
46
  debugLog('AxonFlow initialized', {
41
47
  mode: this.config.mode,
42
48
  endpoint: this.config.endpoint,
43
- authMethod: hasCredentials
44
- ? this.config.licenseKey
45
- ? 'license-key'
46
- : 'api-key'
47
- : 'community (no auth)',
49
+ authMethod,
48
50
  });
49
51
  }
50
52
  }
53
+ /**
54
+ * Get authentication headers based on configured credentials.
55
+ *
56
+ * Uses OAuth2-style Basic auth: Authorization: Basic base64(clientId:clientSecret)
57
+ * Also adds X-Tenant-ID header from clientId for tenant context.
58
+ *
59
+ * @returns Headers object with authentication headers
60
+ */
61
+ getAuthHeaders() {
62
+ const headers = {};
63
+ // OAuth2-style client credentials
64
+ if (this.config.clientId && this.config.clientSecret) {
65
+ const credentials = Buffer.from(`${this.config.clientId}:${this.config.clientSecret}`).toString('base64');
66
+ headers['Authorization'] = `Basic ${credentials}`;
67
+ headers['X-Tenant-ID'] = this.config.clientId;
68
+ }
69
+ return headers;
70
+ }
51
71
  /**
52
72
  * Main method to protect AI calls with governance
53
73
  * @param aiCall The AI call to protect
@@ -166,8 +186,8 @@ export class AxonFlow {
166
186
  // Transform SDK request to Agent API format
167
187
  const agentRequest = {
168
188
  query: request.aiRequest.prompt,
169
- user_token: this.config.apiKey || '',
170
- client_id: this.config.tenant,
189
+ user_token: '',
190
+ client_id: this.config.clientId || this.config.tenant,
171
191
  request_type: 'llm_chat',
172
192
  context: {
173
193
  provider: request.aiRequest.provider,
@@ -179,12 +199,8 @@ export class AxonFlow {
179
199
  };
180
200
  const headers = {
181
201
  'Content-Type': 'application/json',
202
+ ...this.getAuthHeaders(),
182
203
  };
183
- // Add auth headers only when credentials are provided
184
- // Community/self-hosted mode works without credentials
185
- if (this.config.licenseKey) {
186
- headers['X-License-Key'] = this.config.licenseKey;
187
- }
188
204
  const response = await fetch(url, {
189
205
  method: 'POST',
190
206
  headers,
@@ -247,9 +263,10 @@ export class AxonFlow {
247
263
  /**
248
264
  * Create a sandbox client for testing
249
265
  */
250
- static sandbox(apiKey = 'demo-key') {
266
+ static sandbox(clientId = 'demo-client', clientSecret = 'demo-secret') {
251
267
  return new AxonFlow({
252
- apiKey,
268
+ clientId,
269
+ clientSecret,
253
270
  mode: 'sandbox',
254
271
  endpoint: 'https://staging-eu.getaxonflow.com',
255
272
  debug: true,
@@ -386,9 +403,11 @@ export class AxonFlow {
386
403
  * ```
387
404
  */
388
405
  async executeQuery(options) {
406
+ // Default to "anonymous" if userToken is empty/undefined (community mode)
407
+ const effectiveUserToken = options.userToken || 'anonymous';
389
408
  const agentRequest = {
390
409
  query: options.query,
391
- user_token: options.userToken,
410
+ user_token: effectiveUserToken,
392
411
  client_id: this.config.tenant,
393
412
  request_type: options.requestType,
394
413
  context: options.context || {},
@@ -396,15 +415,8 @@ export class AxonFlow {
396
415
  const url = `${this.config.endpoint}/api/request`;
397
416
  const headers = {
398
417
  'Content-Type': 'application/json',
418
+ ...this.getAuthHeaders(),
399
419
  };
400
- // Add auth headers only when credentials are provided
401
- // Community/self-hosted mode works without credentials
402
- if (this.config.licenseKey) {
403
- headers['X-License-Key'] = this.config.licenseKey;
404
- }
405
- else if (this.config.apiKey) {
406
- headers['X-Client-Secret'] = this.config.apiKey;
407
- }
408
420
  if (this.config.debug) {
409
421
  debugLog('Proxy Mode: executeQuery', {
410
422
  requestType: options.requestType,
@@ -529,8 +541,8 @@ export class AxonFlow {
529
541
  async queryConnector(connectorName, query, params) {
530
542
  const agentRequest = {
531
543
  query,
532
- user_token: this.config.apiKey || '',
533
- client_id: this.config.tenant,
544
+ user_token: '',
545
+ client_id: this.config.clientId || this.config.tenant,
534
546
  request_type: 'mcp-query',
535
547
  context: {
536
548
  connector: connectorName,
@@ -540,10 +552,8 @@ export class AxonFlow {
540
552
  const url = `${this.config.endpoint}/api/request`;
541
553
  const headers = {
542
554
  'Content-Type': 'application/json',
555
+ ...this.getAuthHeaders(),
543
556
  };
544
- if (this.config.licenseKey) {
545
- headers['X-License-Key'] = this.config.licenseKey;
546
- }
547
557
  const response = await fetch(url, {
548
558
  method: 'POST',
549
559
  headers,
@@ -552,7 +562,7 @@ export class AxonFlow {
552
562
  });
553
563
  if (!response.ok) {
554
564
  const errorText = await response.text();
555
- throw new Error(`Connector query failed: ${response.status} ${response.statusText} - ${errorText}`);
565
+ throw new ConnectorError(`Connector query failed: ${response.status} ${response.statusText} - ${errorText}`, connectorName, 'query');
556
566
  }
557
567
  const agentResponse = await response.json();
558
568
  if (this.config.debug) {
@@ -582,10 +592,8 @@ export class AxonFlow {
582
592
  const url = `${this.config.endpoint}/api/request`;
583
593
  const headers = {
584
594
  'Content-Type': 'application/json',
595
+ ...this.getAuthHeaders(),
585
596
  };
586
- if (this.config.licenseKey) {
587
- headers['X-License-Key'] = this.config.licenseKey;
588
- }
589
597
  // Use mapTimeout for MAP operations (default 2 minutes)
590
598
  const response = await fetch(url, {
591
599
  method: 'POST',
@@ -595,11 +603,11 @@ export class AxonFlow {
595
603
  });
596
604
  if (!response.ok) {
597
605
  const errorText = await response.text();
598
- throw new Error(`Plan generation failed: ${response.status} ${response.statusText} - ${errorText}`);
606
+ throw new PlanExecutionError(`Plan generation failed: ${response.status} ${response.statusText} - ${errorText}`, undefined, 'generation');
599
607
  }
600
608
  const agentResponse = await response.json();
601
609
  if (!agentResponse.success) {
602
- throw new Error(`Plan generation failed: ${agentResponse.error}`);
610
+ throw new PlanExecutionError(`Plan generation failed: ${agentResponse.error}`, undefined, 'generation');
603
611
  }
604
612
  // plan_id can be at top level or inside data
605
613
  const planId = agentResponse.plan_id || agentResponse.data?.plan_id;
@@ -631,10 +639,8 @@ export class AxonFlow {
631
639
  const url = `${this.config.endpoint}/api/request`;
632
640
  const headers = {
633
641
  'Content-Type': 'application/json',
642
+ ...this.getAuthHeaders(),
634
643
  };
635
- if (this.config.licenseKey) {
636
- headers['X-License-Key'] = this.config.licenseKey;
637
- }
638
644
  // Use mapTimeout for MAP operations (default 2 minutes)
639
645
  const response = await fetch(url, {
640
646
  method: 'POST',
@@ -644,7 +650,7 @@ export class AxonFlow {
644
650
  });
645
651
  if (!response.ok) {
646
652
  const errorText = await response.text();
647
- throw new Error(`Plan execution failed: ${response.status} ${response.statusText} - ${errorText}`);
653
+ throw new PlanExecutionError(`Plan execution failed: ${response.status} ${response.statusText} - ${errorText}`, planId, 'execution');
648
654
  }
649
655
  const agentResponse = await response.json();
650
656
  if (this.config.debug) {
@@ -663,7 +669,7 @@ export class AxonFlow {
663
669
  * Get the status of a running or completed plan
664
670
  */
665
671
  async getPlanStatus(planId) {
666
- const url = `${this.config.endpoint}/api/plans/${planId}`;
672
+ const url = `${this.config.endpoint}/api/v1/plan/${planId}`;
667
673
  const response = await fetch(url, {
668
674
  method: 'GET',
669
675
  signal: AbortSignal.timeout(this.config.timeout),
@@ -746,15 +752,8 @@ export class AxonFlow {
746
752
  };
747
753
  const headers = {
748
754
  'Content-Type': 'application/json',
755
+ ...this.getAuthHeaders(),
749
756
  };
750
- // Add auth headers only when credentials are provided
751
- // Community/self-hosted mode works without credentials
752
- if (this.config.licenseKey) {
753
- headers['X-License-Key'] = this.config.licenseKey;
754
- }
755
- else if (this.config.apiKey) {
756
- headers['X-Client-Secret'] = this.config.apiKey;
757
- }
758
757
  if (this.config.debug) {
759
758
  debugLog('Gateway Mode: Pre-check', { query: options.query.substring(0, 50) });
760
759
  }
@@ -845,15 +844,8 @@ export class AxonFlow {
845
844
  };
846
845
  const headers = {
847
846
  'Content-Type': 'application/json',
847
+ ...this.getAuthHeaders(),
848
848
  };
849
- // Add auth headers only when credentials are provided
850
- // Community/self-hosted mode works without credentials
851
- if (this.config.licenseKey) {
852
- headers['X-License-Key'] = this.config.licenseKey;
853
- }
854
- else if (this.config.apiKey) {
855
- headers['X-Client-Secret'] = this.config.apiKey;
856
- }
857
849
  if (this.config.debug) {
858
850
  debugLog('Gateway Mode: Audit', {
859
851
  contextId: options.contextId,
@@ -1035,23 +1027,19 @@ export class AxonFlow {
1035
1027
  // Policy CRUD Methods - Static Policies
1036
1028
  // ============================================================================
1037
1029
  /**
1038
- * Build authentication headers for API requests
1030
+ * Build authentication headers for API requests.
1031
+ * Includes Content-Type and X-Org-ID for policy APIs.
1032
+ * Uses getAuthHeaders() for authentication credentials.
1039
1033
  */
1040
1034
  buildAuthHeaders() {
1041
1035
  const headers = {
1042
1036
  'Content-Type': 'application/json',
1037
+ ...this.getAuthHeaders(),
1043
1038
  };
1044
- // Always include tenant ID for policy APIs
1039
+ // Always include tenant ID for policy APIs (X-Org-ID header for server compatibility)
1040
+ // Note: getAuthHeaders() already adds X-Tenant-ID when tenant is non-default
1045
1041
  if (this.config.tenant) {
1046
- headers['X-Tenant-ID'] = this.config.tenant;
1047
- }
1048
- // Add auth headers only when credentials are provided
1049
- // Community/self-hosted mode works without credentials
1050
- if (this.config.licenseKey) {
1051
- headers['X-License-Key'] = this.config.licenseKey;
1052
- }
1053
- else if (this.config.apiKey) {
1054
- headers['X-Client-Secret'] = this.config.apiKey;
1042
+ headers['X-Org-ID'] = this.config.tenant;
1055
1043
  }
1056
1044
  return headers;
1057
1045
  }
@@ -1077,8 +1065,8 @@ export class AxonFlow {
1077
1065
  }
1078
1066
  throw new APIError(response.status, response.statusText, errorText);
1079
1067
  }
1080
- // Handle DELETE responses with no body
1081
- if (response.status === 204 || method === 'DELETE') {
1068
+ // Handle 204 No Content responses
1069
+ if (response.status === 204) {
1082
1070
  return undefined;
1083
1071
  }
1084
1072
  return response.json();
@@ -1315,7 +1303,16 @@ export class AxonFlow {
1315
1303
  debugLog('Getting static policy versions', { id });
1316
1304
  }
1317
1305
  const response = await this.policyRequest('GET', `/api/v1/static-policies/${id}/versions`);
1318
- return response.versions;
1306
+ // Transform snake_case API response to camelCase
1307
+ return response.versions.map(v => ({
1308
+ version: v.version,
1309
+ changedBy: v.changed_by,
1310
+ changedAt: v.changed_at,
1311
+ changeType: v.change_type,
1312
+ changeDescription: v.change_description,
1313
+ previousValues: v.previous_values,
1314
+ newValues: v.new_values,
1315
+ }));
1319
1316
  }
1320
1317
  // ============================================================================
1321
1318
  // Policy Override Methods (Enterprise)
@@ -1420,7 +1417,10 @@ export class AxonFlow {
1420
1417
  if (this.config.debug) {
1421
1418
  debugLog('Listing dynamic policies', { options });
1422
1419
  }
1423
- return this.orchestratorRequest('GET', path);
1420
+ // API returns {"policies": [...]} wrapper via Agent proxy
1421
+ const response = await this.orchestratorRequest('GET', path);
1422
+ // Handle both wrapped and unwrapped responses for compatibility
1423
+ return Array.isArray(response) ? response : response.policies || [];
1424
1424
  }
1425
1425
  /**
1426
1426
  * Get a specific dynamic policy by ID.
@@ -1432,7 +1432,10 @@ export class AxonFlow {
1432
1432
  if (this.config.debug) {
1433
1433
  debugLog('Getting dynamic policy', { id });
1434
1434
  }
1435
- return this.orchestratorRequest('GET', `/api/v1/dynamic-policies/${id}`);
1435
+ // API returns {"policy": {...}} wrapper via Agent proxy
1436
+ const response = await this.orchestratorRequest('GET', `/api/v1/dynamic-policies/${id}`);
1437
+ // Handle both wrapped and unwrapped responses for compatibility
1438
+ return 'policy' in response ? response.policy : response;
1436
1439
  }
1437
1440
  /**
1438
1441
  * Create a new dynamic policy.
@@ -1457,7 +1460,10 @@ export class AxonFlow {
1457
1460
  if (this.config.debug) {
1458
1461
  debugLog('Creating dynamic policy', { name: policy.name });
1459
1462
  }
1460
- return this.orchestratorRequest('POST', '/api/v1/dynamic-policies', policy);
1463
+ // API returns {"policy": {...}} wrapper via Agent proxy
1464
+ const response = await this.orchestratorRequest('POST', '/api/v1/dynamic-policies', policy);
1465
+ // Handle both wrapped and unwrapped responses for compatibility
1466
+ return 'policy' in response ? response.policy : response;
1461
1467
  }
1462
1468
  /**
1463
1469
  * Update an existing dynamic policy.
@@ -1470,7 +1476,10 @@ export class AxonFlow {
1470
1476
  if (this.config.debug) {
1471
1477
  debugLog('Updating dynamic policy', { id, updates: Object.keys(policy) });
1472
1478
  }
1473
- return this.orchestratorRequest('PUT', `/api/v1/dynamic-policies/${id}`, policy);
1479
+ // API returns {"policy": {...}} wrapper via Agent proxy
1480
+ const response = await this.orchestratorRequest('PUT', `/api/v1/dynamic-policies/${id}`, policy);
1481
+ // Handle both wrapped and unwrapped responses for compatibility
1482
+ return 'policy' in response ? response.policy : response;
1474
1483
  }
1475
1484
  /**
1476
1485
  * Delete a dynamic policy.
@@ -1494,9 +1503,10 @@ export class AxonFlow {
1494
1503
  if (this.config.debug) {
1495
1504
  debugLog('Toggling dynamic policy', { id, enabled });
1496
1505
  }
1497
- return this.orchestratorRequest('PATCH', `/api/v1/dynamic-policies/${id}`, {
1498
- enabled,
1499
- });
1506
+ // API returns {"policy": {...}} wrapper via Agent proxy
1507
+ const response = await this.orchestratorRequest('PUT', `/api/v1/dynamic-policies/${id}`, { enabled });
1508
+ // Handle both wrapped and unwrapped responses for compatibility
1509
+ return 'policy' in response ? response.policy : response;
1500
1510
  }
1501
1511
  /**
1502
1512
  * Get effective dynamic policies with tier inheritance applied.
@@ -1515,7 +1525,10 @@ export class AxonFlow {
1515
1525
  if (this.config.debug) {
1516
1526
  debugLog('Getting effective dynamic policies', { options });
1517
1527
  }
1518
- return this.orchestratorRequest('GET', path);
1528
+ // API returns {"policies": [...]} wrapper via Agent proxy
1529
+ const response = await this.orchestratorRequest('GET', path);
1530
+ // Handle both wrapped and unwrapped responses for compatibility
1531
+ return Array.isArray(response) ? response : response.policies || [];
1519
1532
  }
1520
1533
  // ============================================================================
1521
1534
  // Portal Authentication Methods (Enterprise)
@@ -1900,6 +1913,49 @@ export class AxonFlow {
1900
1913
  providerType: response.provider_type,
1901
1914
  };
1902
1915
  }
1916
+ /**
1917
+ * Close a PR without merging and optionally delete the branch.
1918
+ * Useful for cleaning up test PRs created by examples.
1919
+ *
1920
+ * @param prId - PR record ID
1921
+ * @param deleteBranch - Whether to delete the associated branch (default: true)
1922
+ * @returns Closed PR record
1923
+ *
1924
+ * @example
1925
+ * ```typescript
1926
+ * // Close PR and delete branch
1927
+ * const pr = await axonflow.closePR('pr_123');
1928
+ * console.log(`PR #${pr.prNumber} closed`);
1929
+ *
1930
+ * // Close PR but keep branch
1931
+ * const pr = await axonflow.closePR('pr_123', false);
1932
+ * ```
1933
+ */
1934
+ async closePR(prId, deleteBranch = true) {
1935
+ if (this.config.debug) {
1936
+ debugLog('Closing PR', { prId, deleteBranch });
1937
+ }
1938
+ const query = deleteBranch ? '?delete_branch=true' : '';
1939
+ const response = await this.portalRequest('DELETE', `/api/v1/code-governance/prs/${prId}${query}`);
1940
+ return {
1941
+ id: response.id,
1942
+ prNumber: response.pr_number,
1943
+ prUrl: response.pr_url,
1944
+ title: response.title,
1945
+ state: response.state,
1946
+ owner: response.owner,
1947
+ repo: response.repo,
1948
+ headBranch: response.head_branch,
1949
+ baseBranch: response.base_branch,
1950
+ filesCount: response.files_count,
1951
+ secretsDetected: response.secrets_detected,
1952
+ unsafePatterns: response.unsafe_patterns,
1953
+ createdAt: response.created_at,
1954
+ closedAt: response.closed_at,
1955
+ createdBy: response.created_by,
1956
+ providerType: response.provider_type,
1957
+ };
1958
+ }
1903
1959
  /**
1904
1960
  * Sync PR status with the Git provider.
1905
1961
  * This updates the local record with the current state from GitHub/GitLab/Bitbucket.
@@ -2093,8 +2149,8 @@ export class AxonFlow {
2093
2149
  }
2094
2150
  throw new APIError(response.status, response.statusText, errorText);
2095
2151
  }
2096
- // Handle DELETE responses with no body
2097
- if (response.status === 204 || method === 'DELETE') {
2152
+ // Handle 204 No Content responses
2153
+ if (response.status === 204) {
2098
2154
  return undefined;
2099
2155
  }
2100
2156
  return response.json();
@@ -2137,8 +2193,8 @@ export class AxonFlow {
2137
2193
  }
2138
2194
  throw new APIError(response.status, response.statusText, errorText);
2139
2195
  }
2140
- // Handle DELETE responses with no body
2141
- if (response.status === 204 || method === 'DELETE') {
2196
+ // Handle 204 No Content responses
2197
+ if (response.status === 204) {
2142
2198
  return undefined;
2143
2199
  }
2144
2200
  return response.json();