@axonflow/sdk 2.1.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 (38) 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 +108 -77
  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/proxy.d.ts +2 -2
  19. package/dist/cjs/types/proxy.d.ts.map +1 -1
  20. package/dist/esm/client.d.ts +32 -2
  21. package/dist/esm/client.d.ts.map +1 -1
  22. package/dist/esm/client.js +109 -78
  23. package/dist/esm/client.js.map +1 -1
  24. package/dist/esm/errors.d.ts +93 -6
  25. package/dist/esm/errors.d.ts.map +1 -1
  26. package/dist/esm/errors.js +121 -11
  27. package/dist/esm/errors.js.map +1 -1
  28. package/dist/esm/index.d.ts +3 -3
  29. package/dist/esm/index.d.ts.map +1 -1
  30. package/dist/esm/index.js +3 -3
  31. package/dist/esm/index.js.map +1 -1
  32. package/dist/esm/types/code-governance.d.ts +2 -0
  33. package/dist/esm/types/code-governance.d.ts.map +1 -1
  34. package/dist/esm/types/config.d.ts +15 -7
  35. package/dist/esm/types/config.d.ts.map +1 -1
  36. package/dist/esm/types/proxy.d.ts +2 -2
  37. package/dist/esm/types/proxy.d.ts.map +1 -1
  38. 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,24 +1027,20 @@ 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
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
1042
  headers['X-Org-ID'] = this.config.tenant;
1047
1043
  }
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;
1055
- }
1056
1044
  return headers;
1057
1045
  }
1058
1046
  /**
@@ -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();
@@ -1432,7 +1420,7 @@ export class AxonFlow {
1432
1420
  // API returns {"policies": [...]} wrapper via Agent proxy
1433
1421
  const response = await this.orchestratorRequest('GET', path);
1434
1422
  // Handle both wrapped and unwrapped responses for compatibility
1435
- return Array.isArray(response) ? response : response.policies;
1423
+ return Array.isArray(response) ? response : response.policies || [];
1436
1424
  }
1437
1425
  /**
1438
1426
  * Get a specific dynamic policy by ID.
@@ -1540,7 +1528,7 @@ export class AxonFlow {
1540
1528
  // API returns {"policies": [...]} wrapper via Agent proxy
1541
1529
  const response = await this.orchestratorRequest('GET', path);
1542
1530
  // Handle both wrapped and unwrapped responses for compatibility
1543
- return Array.isArray(response) ? response : response.policies;
1531
+ return Array.isArray(response) ? response : response.policies || [];
1544
1532
  }
1545
1533
  // ============================================================================
1546
1534
  // Portal Authentication Methods (Enterprise)
@@ -1925,6 +1913,49 @@ export class AxonFlow {
1925
1913
  providerType: response.provider_type,
1926
1914
  };
1927
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
+ }
1928
1959
  /**
1929
1960
  * Sync PR status with the Git provider.
1930
1961
  * This updates the local record with the current state from GitHub/GitLab/Bitbucket.
@@ -2118,8 +2149,8 @@ export class AxonFlow {
2118
2149
  }
2119
2150
  throw new APIError(response.status, response.statusText, errorText);
2120
2151
  }
2121
- // Handle DELETE responses with no body
2122
- if (response.status === 204 || method === 'DELETE') {
2152
+ // Handle 204 No Content responses
2153
+ if (response.status === 204) {
2123
2154
  return undefined;
2124
2155
  }
2125
2156
  return response.json();
@@ -2162,8 +2193,8 @@ export class AxonFlow {
2162
2193
  }
2163
2194
  throw new APIError(response.status, response.statusText, errorText);
2164
2195
  }
2165
- // Handle DELETE responses with no body
2166
- if (response.status === 204 || method === 'DELETE') {
2196
+ // Handle 204 No Content responses
2197
+ if (response.status === 204) {
2167
2198
  return undefined;
2168
2199
  }
2169
2200
  return response.json();