@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
@@ -12,15 +12,19 @@ class AxonFlow {
12
12
  constructor(config) {
13
13
  this.interceptors = [];
14
14
  this.sessionCookie = null;
15
+ // Configuration validation
16
+ if (config.clientSecret && !config.clientId) {
17
+ throw new errors_1.ConfigurationError('clientSecret requires clientId to be set. ' +
18
+ 'Provide both clientId and clientSecret for OAuth2-style authentication.');
19
+ }
15
20
  // Set defaults first to determine endpoint
16
21
  const endpoint = config.endpoint || 'https://staging-eu.getaxonflow.com';
17
- // Credentials are optional for community/self-hosted deployments
18
- // Enterprise features (Gateway Mode, Policy CRUD) require credentials
19
- const hasCredentials = !!(config.licenseKey || config.apiKey);
22
+ // Credentials check: OAuth2-style (clientId/clientSecret)
23
+ const hasCredentials = !!(config.clientId && config.clientSecret);
20
24
  // Set configuration
21
25
  this.config = {
22
- apiKey: config.apiKey,
23
- licenseKey: config.licenseKey,
26
+ clientId: config.clientId,
27
+ clientSecret: config.clientSecret,
24
28
  endpoint,
25
29
  mode: config.mode || (hasCredentials ? 'production' : 'sandbox'),
26
30
  tenant: config.tenant || 'default',
@@ -40,17 +44,33 @@ class AxonFlow {
40
44
  // Initialize interceptors
41
45
  this.interceptors = [new openai_1.OpenAIInterceptor(), new anthropic_1.AnthropicInterceptor()];
42
46
  if (this.config.debug) {
47
+ // Determine auth method for logging
48
+ const authMethod = hasCredentials ? 'client-credentials' : 'community (no auth)';
43
49
  (0, helpers_1.debugLog)('AxonFlow initialized', {
44
50
  mode: this.config.mode,
45
51
  endpoint: this.config.endpoint,
46
- authMethod: hasCredentials
47
- ? this.config.licenseKey
48
- ? 'license-key'
49
- : 'api-key'
50
- : 'community (no auth)',
52
+ authMethod,
51
53
  });
52
54
  }
53
55
  }
56
+ /**
57
+ * Get authentication headers based on configured credentials.
58
+ *
59
+ * Uses OAuth2-style Basic auth: Authorization: Basic base64(clientId:clientSecret)
60
+ * Also adds X-Tenant-ID header from clientId for tenant context.
61
+ *
62
+ * @returns Headers object with authentication headers
63
+ */
64
+ getAuthHeaders() {
65
+ const headers = {};
66
+ // OAuth2-style client credentials
67
+ if (this.config.clientId && this.config.clientSecret) {
68
+ const credentials = Buffer.from(`${this.config.clientId}:${this.config.clientSecret}`).toString('base64');
69
+ headers['Authorization'] = `Basic ${credentials}`;
70
+ headers['X-Tenant-ID'] = this.config.clientId;
71
+ }
72
+ return headers;
73
+ }
54
74
  /**
55
75
  * Main method to protect AI calls with governance
56
76
  * @param aiCall The AI call to protect
@@ -169,8 +189,8 @@ class AxonFlow {
169
189
  // Transform SDK request to Agent API format
170
190
  const agentRequest = {
171
191
  query: request.aiRequest.prompt,
172
- user_token: this.config.apiKey || '',
173
- client_id: this.config.tenant,
192
+ user_token: '',
193
+ client_id: this.config.clientId || this.config.tenant,
174
194
  request_type: 'llm_chat',
175
195
  context: {
176
196
  provider: request.aiRequest.provider,
@@ -182,12 +202,8 @@ class AxonFlow {
182
202
  };
183
203
  const headers = {
184
204
  'Content-Type': 'application/json',
205
+ ...this.getAuthHeaders(),
185
206
  };
186
- // Add auth headers only when credentials are provided
187
- // Community/self-hosted mode works without credentials
188
- if (this.config.licenseKey) {
189
- headers['X-License-Key'] = this.config.licenseKey;
190
- }
191
207
  const response = await fetch(url, {
192
208
  method: 'POST',
193
209
  headers,
@@ -250,9 +266,10 @@ class AxonFlow {
250
266
  /**
251
267
  * Create a sandbox client for testing
252
268
  */
253
- static sandbox(apiKey = 'demo-key') {
269
+ static sandbox(clientId = 'demo-client', clientSecret = 'demo-secret') {
254
270
  return new AxonFlow({
255
- apiKey,
271
+ clientId,
272
+ clientSecret,
256
273
  mode: 'sandbox',
257
274
  endpoint: 'https://staging-eu.getaxonflow.com',
258
275
  debug: true,
@@ -389,9 +406,11 @@ class AxonFlow {
389
406
  * ```
390
407
  */
391
408
  async executeQuery(options) {
409
+ // Default to "anonymous" if userToken is empty/undefined (community mode)
410
+ const effectiveUserToken = options.userToken || 'anonymous';
392
411
  const agentRequest = {
393
412
  query: options.query,
394
- user_token: options.userToken,
413
+ user_token: effectiveUserToken,
395
414
  client_id: this.config.tenant,
396
415
  request_type: options.requestType,
397
416
  context: options.context || {},
@@ -399,15 +418,8 @@ class AxonFlow {
399
418
  const url = `${this.config.endpoint}/api/request`;
400
419
  const headers = {
401
420
  'Content-Type': 'application/json',
421
+ ...this.getAuthHeaders(),
402
422
  };
403
- // Add auth headers only when credentials are provided
404
- // Community/self-hosted mode works without credentials
405
- if (this.config.licenseKey) {
406
- headers['X-License-Key'] = this.config.licenseKey;
407
- }
408
- else if (this.config.apiKey) {
409
- headers['X-Client-Secret'] = this.config.apiKey;
410
- }
411
423
  if (this.config.debug) {
412
424
  (0, helpers_1.debugLog)('Proxy Mode: executeQuery', {
413
425
  requestType: options.requestType,
@@ -532,8 +544,8 @@ class AxonFlow {
532
544
  async queryConnector(connectorName, query, params) {
533
545
  const agentRequest = {
534
546
  query,
535
- user_token: this.config.apiKey || '',
536
- client_id: this.config.tenant,
547
+ user_token: '',
548
+ client_id: this.config.clientId || this.config.tenant,
537
549
  request_type: 'mcp-query',
538
550
  context: {
539
551
  connector: connectorName,
@@ -543,10 +555,8 @@ class AxonFlow {
543
555
  const url = `${this.config.endpoint}/api/request`;
544
556
  const headers = {
545
557
  'Content-Type': 'application/json',
558
+ ...this.getAuthHeaders(),
546
559
  };
547
- if (this.config.licenseKey) {
548
- headers['X-License-Key'] = this.config.licenseKey;
549
- }
550
560
  const response = await fetch(url, {
551
561
  method: 'POST',
552
562
  headers,
@@ -555,7 +565,7 @@ class AxonFlow {
555
565
  });
556
566
  if (!response.ok) {
557
567
  const errorText = await response.text();
558
- throw new Error(`Connector query failed: ${response.status} ${response.statusText} - ${errorText}`);
568
+ throw new errors_1.ConnectorError(`Connector query failed: ${response.status} ${response.statusText} - ${errorText}`, connectorName, 'query');
559
569
  }
560
570
  const agentResponse = await response.json();
561
571
  if (this.config.debug) {
@@ -585,10 +595,8 @@ class AxonFlow {
585
595
  const url = `${this.config.endpoint}/api/request`;
586
596
  const headers = {
587
597
  'Content-Type': 'application/json',
598
+ ...this.getAuthHeaders(),
588
599
  };
589
- if (this.config.licenseKey) {
590
- headers['X-License-Key'] = this.config.licenseKey;
591
- }
592
600
  // Use mapTimeout for MAP operations (default 2 minutes)
593
601
  const response = await fetch(url, {
594
602
  method: 'POST',
@@ -598,11 +606,11 @@ class AxonFlow {
598
606
  });
599
607
  if (!response.ok) {
600
608
  const errorText = await response.text();
601
- throw new Error(`Plan generation failed: ${response.status} ${response.statusText} - ${errorText}`);
609
+ throw new errors_1.PlanExecutionError(`Plan generation failed: ${response.status} ${response.statusText} - ${errorText}`, undefined, 'generation');
602
610
  }
603
611
  const agentResponse = await response.json();
604
612
  if (!agentResponse.success) {
605
- throw new Error(`Plan generation failed: ${agentResponse.error}`);
613
+ throw new errors_1.PlanExecutionError(`Plan generation failed: ${agentResponse.error}`, undefined, 'generation');
606
614
  }
607
615
  // plan_id can be at top level or inside data
608
616
  const planId = agentResponse.plan_id || agentResponse.data?.plan_id;
@@ -634,10 +642,8 @@ class AxonFlow {
634
642
  const url = `${this.config.endpoint}/api/request`;
635
643
  const headers = {
636
644
  'Content-Type': 'application/json',
645
+ ...this.getAuthHeaders(),
637
646
  };
638
- if (this.config.licenseKey) {
639
- headers['X-License-Key'] = this.config.licenseKey;
640
- }
641
647
  // Use mapTimeout for MAP operations (default 2 minutes)
642
648
  const response = await fetch(url, {
643
649
  method: 'POST',
@@ -647,7 +653,7 @@ class AxonFlow {
647
653
  });
648
654
  if (!response.ok) {
649
655
  const errorText = await response.text();
650
- throw new Error(`Plan execution failed: ${response.status} ${response.statusText} - ${errorText}`);
656
+ throw new errors_1.PlanExecutionError(`Plan execution failed: ${response.status} ${response.statusText} - ${errorText}`, planId, 'execution');
651
657
  }
652
658
  const agentResponse = await response.json();
653
659
  if (this.config.debug) {
@@ -666,7 +672,7 @@ class AxonFlow {
666
672
  * Get the status of a running or completed plan
667
673
  */
668
674
  async getPlanStatus(planId) {
669
- const url = `${this.config.endpoint}/api/plans/${planId}`;
675
+ const url = `${this.config.endpoint}/api/v1/plan/${planId}`;
670
676
  const response = await fetch(url, {
671
677
  method: 'GET',
672
678
  signal: AbortSignal.timeout(this.config.timeout),
@@ -749,15 +755,8 @@ class AxonFlow {
749
755
  };
750
756
  const headers = {
751
757
  'Content-Type': 'application/json',
758
+ ...this.getAuthHeaders(),
752
759
  };
753
- // Add auth headers only when credentials are provided
754
- // Community/self-hosted mode works without credentials
755
- if (this.config.licenseKey) {
756
- headers['X-License-Key'] = this.config.licenseKey;
757
- }
758
- else if (this.config.apiKey) {
759
- headers['X-Client-Secret'] = this.config.apiKey;
760
- }
761
760
  if (this.config.debug) {
762
761
  (0, helpers_1.debugLog)('Gateway Mode: Pre-check', { query: options.query.substring(0, 50) });
763
762
  }
@@ -848,15 +847,8 @@ class AxonFlow {
848
847
  };
849
848
  const headers = {
850
849
  'Content-Type': 'application/json',
850
+ ...this.getAuthHeaders(),
851
851
  };
852
- // Add auth headers only when credentials are provided
853
- // Community/self-hosted mode works without credentials
854
- if (this.config.licenseKey) {
855
- headers['X-License-Key'] = this.config.licenseKey;
856
- }
857
- else if (this.config.apiKey) {
858
- headers['X-Client-Secret'] = this.config.apiKey;
859
- }
860
852
  if (this.config.debug) {
861
853
  (0, helpers_1.debugLog)('Gateway Mode: Audit', {
862
854
  contextId: options.contextId,
@@ -1038,23 +1030,19 @@ class AxonFlow {
1038
1030
  // Policy CRUD Methods - Static Policies
1039
1031
  // ============================================================================
1040
1032
  /**
1041
- * Build authentication headers for API requests
1033
+ * Build authentication headers for API requests.
1034
+ * Includes Content-Type and X-Org-ID for policy APIs.
1035
+ * Uses getAuthHeaders() for authentication credentials.
1042
1036
  */
1043
1037
  buildAuthHeaders() {
1044
1038
  const headers = {
1045
1039
  'Content-Type': 'application/json',
1040
+ ...this.getAuthHeaders(),
1046
1041
  };
1047
- // Always include tenant ID for policy APIs
1042
+ // Always include tenant ID for policy APIs (X-Org-ID header for server compatibility)
1043
+ // Note: getAuthHeaders() already adds X-Tenant-ID when tenant is non-default
1048
1044
  if (this.config.tenant) {
1049
- headers['X-Tenant-ID'] = this.config.tenant;
1050
- }
1051
- // Add auth headers only when credentials are provided
1052
- // Community/self-hosted mode works without credentials
1053
- if (this.config.licenseKey) {
1054
- headers['X-License-Key'] = this.config.licenseKey;
1055
- }
1056
- else if (this.config.apiKey) {
1057
- headers['X-Client-Secret'] = this.config.apiKey;
1045
+ headers['X-Org-ID'] = this.config.tenant;
1058
1046
  }
1059
1047
  return headers;
1060
1048
  }
@@ -1080,8 +1068,8 @@ class AxonFlow {
1080
1068
  }
1081
1069
  throw new errors_1.APIError(response.status, response.statusText, errorText);
1082
1070
  }
1083
- // Handle DELETE responses with no body
1084
- if (response.status === 204 || method === 'DELETE') {
1071
+ // Handle 204 No Content responses
1072
+ if (response.status === 204) {
1085
1073
  return undefined;
1086
1074
  }
1087
1075
  return response.json();
@@ -1318,7 +1306,16 @@ class AxonFlow {
1318
1306
  (0, helpers_1.debugLog)('Getting static policy versions', { id });
1319
1307
  }
1320
1308
  const response = await this.policyRequest('GET', `/api/v1/static-policies/${id}/versions`);
1321
- return response.versions;
1309
+ // Transform snake_case API response to camelCase
1310
+ return response.versions.map(v => ({
1311
+ version: v.version,
1312
+ changedBy: v.changed_by,
1313
+ changedAt: v.changed_at,
1314
+ changeType: v.change_type,
1315
+ changeDescription: v.change_description,
1316
+ previousValues: v.previous_values,
1317
+ newValues: v.new_values,
1318
+ }));
1322
1319
  }
1323
1320
  // ============================================================================
1324
1321
  // Policy Override Methods (Enterprise)
@@ -1423,7 +1420,10 @@ class AxonFlow {
1423
1420
  if (this.config.debug) {
1424
1421
  (0, helpers_1.debugLog)('Listing dynamic policies', { options });
1425
1422
  }
1426
- return this.orchestratorRequest('GET', path);
1423
+ // API returns {"policies": [...]} wrapper via Agent proxy
1424
+ const response = await this.orchestratorRequest('GET', path);
1425
+ // Handle both wrapped and unwrapped responses for compatibility
1426
+ return Array.isArray(response) ? response : response.policies || [];
1427
1427
  }
1428
1428
  /**
1429
1429
  * Get a specific dynamic policy by ID.
@@ -1435,7 +1435,10 @@ class AxonFlow {
1435
1435
  if (this.config.debug) {
1436
1436
  (0, helpers_1.debugLog)('Getting dynamic policy', { id });
1437
1437
  }
1438
- return this.orchestratorRequest('GET', `/api/v1/dynamic-policies/${id}`);
1438
+ // API returns {"policy": {...}} wrapper via Agent proxy
1439
+ const response = await this.orchestratorRequest('GET', `/api/v1/dynamic-policies/${id}`);
1440
+ // Handle both wrapped and unwrapped responses for compatibility
1441
+ return 'policy' in response ? response.policy : response;
1439
1442
  }
1440
1443
  /**
1441
1444
  * Create a new dynamic policy.
@@ -1460,7 +1463,10 @@ class AxonFlow {
1460
1463
  if (this.config.debug) {
1461
1464
  (0, helpers_1.debugLog)('Creating dynamic policy', { name: policy.name });
1462
1465
  }
1463
- return this.orchestratorRequest('POST', '/api/v1/dynamic-policies', policy);
1466
+ // API returns {"policy": {...}} wrapper via Agent proxy
1467
+ const response = await this.orchestratorRequest('POST', '/api/v1/dynamic-policies', policy);
1468
+ // Handle both wrapped and unwrapped responses for compatibility
1469
+ return 'policy' in response ? response.policy : response;
1464
1470
  }
1465
1471
  /**
1466
1472
  * Update an existing dynamic policy.
@@ -1473,7 +1479,10 @@ class AxonFlow {
1473
1479
  if (this.config.debug) {
1474
1480
  (0, helpers_1.debugLog)('Updating dynamic policy', { id, updates: Object.keys(policy) });
1475
1481
  }
1476
- return this.orchestratorRequest('PUT', `/api/v1/dynamic-policies/${id}`, policy);
1482
+ // API returns {"policy": {...}} wrapper via Agent proxy
1483
+ const response = await this.orchestratorRequest('PUT', `/api/v1/dynamic-policies/${id}`, policy);
1484
+ // Handle both wrapped and unwrapped responses for compatibility
1485
+ return 'policy' in response ? response.policy : response;
1477
1486
  }
1478
1487
  /**
1479
1488
  * Delete a dynamic policy.
@@ -1497,9 +1506,10 @@ class AxonFlow {
1497
1506
  if (this.config.debug) {
1498
1507
  (0, helpers_1.debugLog)('Toggling dynamic policy', { id, enabled });
1499
1508
  }
1500
- return this.orchestratorRequest('PATCH', `/api/v1/dynamic-policies/${id}`, {
1501
- enabled,
1502
- });
1509
+ // API returns {"policy": {...}} wrapper via Agent proxy
1510
+ const response = await this.orchestratorRequest('PUT', `/api/v1/dynamic-policies/${id}`, { enabled });
1511
+ // Handle both wrapped and unwrapped responses for compatibility
1512
+ return 'policy' in response ? response.policy : response;
1503
1513
  }
1504
1514
  /**
1505
1515
  * Get effective dynamic policies with tier inheritance applied.
@@ -1518,7 +1528,10 @@ class AxonFlow {
1518
1528
  if (this.config.debug) {
1519
1529
  (0, helpers_1.debugLog)('Getting effective dynamic policies', { options });
1520
1530
  }
1521
- return this.orchestratorRequest('GET', path);
1531
+ // API returns {"policies": [...]} wrapper via Agent proxy
1532
+ const response = await this.orchestratorRequest('GET', path);
1533
+ // Handle both wrapped and unwrapped responses for compatibility
1534
+ return Array.isArray(response) ? response : response.policies || [];
1522
1535
  }
1523
1536
  // ============================================================================
1524
1537
  // Portal Authentication Methods (Enterprise)
@@ -1903,6 +1916,49 @@ class AxonFlow {
1903
1916
  providerType: response.provider_type,
1904
1917
  };
1905
1918
  }
1919
+ /**
1920
+ * Close a PR without merging and optionally delete the branch.
1921
+ * Useful for cleaning up test PRs created by examples.
1922
+ *
1923
+ * @param prId - PR record ID
1924
+ * @param deleteBranch - Whether to delete the associated branch (default: true)
1925
+ * @returns Closed PR record
1926
+ *
1927
+ * @example
1928
+ * ```typescript
1929
+ * // Close PR and delete branch
1930
+ * const pr = await axonflow.closePR('pr_123');
1931
+ * console.log(`PR #${pr.prNumber} closed`);
1932
+ *
1933
+ * // Close PR but keep branch
1934
+ * const pr = await axonflow.closePR('pr_123', false);
1935
+ * ```
1936
+ */
1937
+ async closePR(prId, deleteBranch = true) {
1938
+ if (this.config.debug) {
1939
+ (0, helpers_1.debugLog)('Closing PR', { prId, deleteBranch });
1940
+ }
1941
+ const query = deleteBranch ? '?delete_branch=true' : '';
1942
+ const response = await this.portalRequest('DELETE', `/api/v1/code-governance/prs/${prId}${query}`);
1943
+ return {
1944
+ id: response.id,
1945
+ prNumber: response.pr_number,
1946
+ prUrl: response.pr_url,
1947
+ title: response.title,
1948
+ state: response.state,
1949
+ owner: response.owner,
1950
+ repo: response.repo,
1951
+ headBranch: response.head_branch,
1952
+ baseBranch: response.base_branch,
1953
+ filesCount: response.files_count,
1954
+ secretsDetected: response.secrets_detected,
1955
+ unsafePatterns: response.unsafe_patterns,
1956
+ createdAt: response.created_at,
1957
+ closedAt: response.closed_at,
1958
+ createdBy: response.created_by,
1959
+ providerType: response.provider_type,
1960
+ };
1961
+ }
1906
1962
  /**
1907
1963
  * Sync PR status with the Git provider.
1908
1964
  * This updates the local record with the current state from GitHub/GitLab/Bitbucket.
@@ -2096,8 +2152,8 @@ class AxonFlow {
2096
2152
  }
2097
2153
  throw new errors_1.APIError(response.status, response.statusText, errorText);
2098
2154
  }
2099
- // Handle DELETE responses with no body
2100
- if (response.status === 204 || method === 'DELETE') {
2155
+ // Handle 204 No Content responses
2156
+ if (response.status === 204) {
2101
2157
  return undefined;
2102
2158
  }
2103
2159
  return response.json();
@@ -2140,8 +2196,8 @@ class AxonFlow {
2140
2196
  }
2141
2197
  throw new errors_1.APIError(response.status, response.statusText, errorText);
2142
2198
  }
2143
- // Handle DELETE responses with no body
2144
- if (response.status === 204 || method === 'DELETE') {
2199
+ // Handle 204 No Content responses
2200
+ if (response.status === 204) {
2145
2201
  return undefined;
2146
2202
  }
2147
2203
  return response.json();