@axonflow/sdk 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -36
- package/dist/cjs/client.d.ts +75 -2
- package/dist/cjs/client.d.ts.map +1 -1
- package/dist/cjs/client.js +196 -77
- package/dist/cjs/client.js.map +1 -1
- package/dist/cjs/errors.d.ts +93 -6
- package/dist/cjs/errors.d.ts.map +1 -1
- package/dist/cjs/errors.js +126 -12
- package/dist/cjs/errors.js.map +1 -1
- package/dist/cjs/index.d.ts +3 -3
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +7 -3
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/code-governance.d.ts +2 -0
- package/dist/cjs/types/code-governance.d.ts.map +1 -1
- package/dist/cjs/types/config.d.ts +15 -7
- package/dist/cjs/types/config.d.ts.map +1 -1
- package/dist/cjs/types/connector.d.ts +28 -0
- package/dist/cjs/types/connector.d.ts.map +1 -1
- package/dist/cjs/types/proxy.d.ts +2 -2
- package/dist/cjs/types/proxy.d.ts.map +1 -1
- package/dist/esm/client.d.ts +75 -2
- package/dist/esm/client.d.ts.map +1 -1
- package/dist/esm/client.js +197 -78
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/errors.d.ts +93 -6
- package/dist/esm/errors.d.ts.map +1 -1
- package/dist/esm/errors.js +121 -11
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.d.ts +3 -3
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +3 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/code-governance.d.ts +2 -0
- package/dist/esm/types/code-governance.d.ts.map +1 -1
- package/dist/esm/types/config.d.ts +15 -7
- package/dist/esm/types/config.d.ts.map +1 -1
- package/dist/esm/types/connector.d.ts +28 -0
- package/dist/esm/types/connector.d.ts.map +1 -1
- package/dist/esm/types/proxy.d.ts +2 -2
- package/dist/esm/types/proxy.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/esm/client.js
CHANGED
|
@@ -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
|
|
15
|
-
|
|
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
|
-
|
|
20
|
-
|
|
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
|
|
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:
|
|
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(
|
|
266
|
+
static sandbox(clientId = 'demo-client', clientSecret = 'demo-secret') {
|
|
251
267
|
return new AxonFlow({
|
|
252
|
-
|
|
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:
|
|
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:
|
|
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
|
|
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) {
|
|
@@ -565,6 +575,94 @@ export class AxonFlow {
|
|
|
565
575
|
meta: agentResponse.metadata,
|
|
566
576
|
};
|
|
567
577
|
}
|
|
578
|
+
/**
|
|
579
|
+
* Execute a query directly against the MCP connector endpoint.
|
|
580
|
+
*
|
|
581
|
+
* This method calls the agent's /mcp/resources/query endpoint which provides:
|
|
582
|
+
* - Request-phase policy evaluation (SQLi blocking, PII blocking)
|
|
583
|
+
* - Response-phase policy evaluation (PII redaction)
|
|
584
|
+
* - PolicyInfo metadata in responses
|
|
585
|
+
*
|
|
586
|
+
* @example
|
|
587
|
+
* ```typescript
|
|
588
|
+
* const response = await axonflow.mcpQuery({
|
|
589
|
+
* connector: 'postgres',
|
|
590
|
+
* statement: 'SELECT * FROM customers LIMIT 10',
|
|
591
|
+
* });
|
|
592
|
+
*
|
|
593
|
+
* if (response.redacted) {
|
|
594
|
+
* console.log('Fields redacted:', response.redacted_fields);
|
|
595
|
+
* }
|
|
596
|
+
* console.log('Policies evaluated:', response.policy_info?.policies_evaluated);
|
|
597
|
+
* ```
|
|
598
|
+
*
|
|
599
|
+
* @param options - Query options including connector name and SQL statement
|
|
600
|
+
* @returns ConnectorResponse with data, redaction info, and policy_info
|
|
601
|
+
* @throws ConnectorError if the request is blocked by policy or fails
|
|
602
|
+
*/
|
|
603
|
+
async mcpQuery(options) {
|
|
604
|
+
if (!options.connector) {
|
|
605
|
+
throw new ConnectorError('connector name is required', undefined, 'mcpQuery');
|
|
606
|
+
}
|
|
607
|
+
if (!options.statement) {
|
|
608
|
+
throw new ConnectorError('statement is required', undefined, 'mcpQuery');
|
|
609
|
+
}
|
|
610
|
+
const url = `${this.config.endpoint}/mcp/resources/query`;
|
|
611
|
+
const headers = {
|
|
612
|
+
'Content-Type': 'application/json',
|
|
613
|
+
...this.getAuthHeaders(),
|
|
614
|
+
};
|
|
615
|
+
const body = {
|
|
616
|
+
connector: options.connector,
|
|
617
|
+
statement: options.statement,
|
|
618
|
+
options: options.options || {},
|
|
619
|
+
};
|
|
620
|
+
if (this.config.debug) {
|
|
621
|
+
debugLog('MCP Query', {
|
|
622
|
+
connector: options.connector,
|
|
623
|
+
statement: options.statement.substring(0, 50),
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
const response = await fetch(url, {
|
|
627
|
+
method: 'POST',
|
|
628
|
+
headers,
|
|
629
|
+
body: JSON.stringify(body),
|
|
630
|
+
signal: AbortSignal.timeout(this.config.timeout),
|
|
631
|
+
});
|
|
632
|
+
const responseData = await response.json();
|
|
633
|
+
// Handle policy blocks (403 responses)
|
|
634
|
+
if (!response.ok) {
|
|
635
|
+
throw new ConnectorError(responseData.error || `MCP query failed: ${response.status} ${response.statusText}`, options.connector, 'mcpQuery');
|
|
636
|
+
}
|
|
637
|
+
if (this.config.debug) {
|
|
638
|
+
debugLog('MCP Query result', {
|
|
639
|
+
connector: options.connector,
|
|
640
|
+
success: responseData.success,
|
|
641
|
+
redacted: responseData.redacted,
|
|
642
|
+
policiesEvaluated: responseData.policy_info?.policies_evaluated,
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
return {
|
|
646
|
+
success: responseData.success,
|
|
647
|
+
data: responseData.data,
|
|
648
|
+
error: responseData.error,
|
|
649
|
+
meta: responseData.meta,
|
|
650
|
+
redacted: responseData.redacted,
|
|
651
|
+
redacted_fields: responseData.redacted_fields,
|
|
652
|
+
policy_info: responseData.policy_info,
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Execute a statement against an MCP connector (alias for mcpQuery).
|
|
657
|
+
*
|
|
658
|
+
* Same as mcpQuery but follows the naming convention of other execute* methods.
|
|
659
|
+
*
|
|
660
|
+
* @param options - Query options including connector name and SQL statement
|
|
661
|
+
* @returns ConnectorResponse with data, redaction info, and policy_info
|
|
662
|
+
*/
|
|
663
|
+
async mcpExecute(options) {
|
|
664
|
+
return this.mcpQuery(options);
|
|
665
|
+
}
|
|
568
666
|
/**
|
|
569
667
|
* Generate a multi-agent execution plan from a natural language query
|
|
570
668
|
* @param query - Natural language query describing the task
|
|
@@ -582,10 +680,8 @@ export class AxonFlow {
|
|
|
582
680
|
const url = `${this.config.endpoint}/api/request`;
|
|
583
681
|
const headers = {
|
|
584
682
|
'Content-Type': 'application/json',
|
|
683
|
+
...this.getAuthHeaders(),
|
|
585
684
|
};
|
|
586
|
-
if (this.config.licenseKey) {
|
|
587
|
-
headers['X-License-Key'] = this.config.licenseKey;
|
|
588
|
-
}
|
|
589
685
|
// Use mapTimeout for MAP operations (default 2 minutes)
|
|
590
686
|
const response = await fetch(url, {
|
|
591
687
|
method: 'POST',
|
|
@@ -595,11 +691,11 @@ export class AxonFlow {
|
|
|
595
691
|
});
|
|
596
692
|
if (!response.ok) {
|
|
597
693
|
const errorText = await response.text();
|
|
598
|
-
throw new
|
|
694
|
+
throw new PlanExecutionError(`Plan generation failed: ${response.status} ${response.statusText} - ${errorText}`, undefined, 'generation');
|
|
599
695
|
}
|
|
600
696
|
const agentResponse = await response.json();
|
|
601
697
|
if (!agentResponse.success) {
|
|
602
|
-
throw new
|
|
698
|
+
throw new PlanExecutionError(`Plan generation failed: ${agentResponse.error}`, undefined, 'generation');
|
|
603
699
|
}
|
|
604
700
|
// plan_id can be at top level or inside data
|
|
605
701
|
const planId = agentResponse.plan_id || agentResponse.data?.plan_id;
|
|
@@ -631,10 +727,8 @@ export class AxonFlow {
|
|
|
631
727
|
const url = `${this.config.endpoint}/api/request`;
|
|
632
728
|
const headers = {
|
|
633
729
|
'Content-Type': 'application/json',
|
|
730
|
+
...this.getAuthHeaders(),
|
|
634
731
|
};
|
|
635
|
-
if (this.config.licenseKey) {
|
|
636
|
-
headers['X-License-Key'] = this.config.licenseKey;
|
|
637
|
-
}
|
|
638
732
|
// Use mapTimeout for MAP operations (default 2 minutes)
|
|
639
733
|
const response = await fetch(url, {
|
|
640
734
|
method: 'POST',
|
|
@@ -644,7 +738,7 @@ export class AxonFlow {
|
|
|
644
738
|
});
|
|
645
739
|
if (!response.ok) {
|
|
646
740
|
const errorText = await response.text();
|
|
647
|
-
throw new
|
|
741
|
+
throw new PlanExecutionError(`Plan execution failed: ${response.status} ${response.statusText} - ${errorText}`, planId, 'execution');
|
|
648
742
|
}
|
|
649
743
|
const agentResponse = await response.json();
|
|
650
744
|
if (this.config.debug) {
|
|
@@ -663,7 +757,7 @@ export class AxonFlow {
|
|
|
663
757
|
* Get the status of a running or completed plan
|
|
664
758
|
*/
|
|
665
759
|
async getPlanStatus(planId) {
|
|
666
|
-
const url = `${this.config.endpoint}/api/
|
|
760
|
+
const url = `${this.config.endpoint}/api/v1/plan/${planId}`;
|
|
667
761
|
const response = await fetch(url, {
|
|
668
762
|
method: 'GET',
|
|
669
763
|
signal: AbortSignal.timeout(this.config.timeout),
|
|
@@ -746,15 +840,8 @@ export class AxonFlow {
|
|
|
746
840
|
};
|
|
747
841
|
const headers = {
|
|
748
842
|
'Content-Type': 'application/json',
|
|
843
|
+
...this.getAuthHeaders(),
|
|
749
844
|
};
|
|
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
845
|
if (this.config.debug) {
|
|
759
846
|
debugLog('Gateway Mode: Pre-check', { query: options.query.substring(0, 50) });
|
|
760
847
|
}
|
|
@@ -845,15 +932,8 @@ export class AxonFlow {
|
|
|
845
932
|
};
|
|
846
933
|
const headers = {
|
|
847
934
|
'Content-Type': 'application/json',
|
|
935
|
+
...this.getAuthHeaders(),
|
|
848
936
|
};
|
|
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
937
|
if (this.config.debug) {
|
|
858
938
|
debugLog('Gateway Mode: Audit', {
|
|
859
939
|
contextId: options.contextId,
|
|
@@ -1035,24 +1115,20 @@ export class AxonFlow {
|
|
|
1035
1115
|
// Policy CRUD Methods - Static Policies
|
|
1036
1116
|
// ============================================================================
|
|
1037
1117
|
/**
|
|
1038
|
-
* Build authentication headers for API requests
|
|
1118
|
+
* Build authentication headers for API requests.
|
|
1119
|
+
* Includes Content-Type and X-Org-ID for policy APIs.
|
|
1120
|
+
* Uses getAuthHeaders() for authentication credentials.
|
|
1039
1121
|
*/
|
|
1040
1122
|
buildAuthHeaders() {
|
|
1041
1123
|
const headers = {
|
|
1042
1124
|
'Content-Type': 'application/json',
|
|
1125
|
+
...this.getAuthHeaders(),
|
|
1043
1126
|
};
|
|
1044
1127
|
// Always include tenant ID for policy APIs (X-Org-ID header for server compatibility)
|
|
1128
|
+
// Note: getAuthHeaders() already adds X-Tenant-ID when tenant is non-default
|
|
1045
1129
|
if (this.config.tenant) {
|
|
1046
1130
|
headers['X-Org-ID'] = this.config.tenant;
|
|
1047
1131
|
}
|
|
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
1132
|
return headers;
|
|
1057
1133
|
}
|
|
1058
1134
|
/**
|
|
@@ -1077,8 +1153,8 @@ export class AxonFlow {
|
|
|
1077
1153
|
}
|
|
1078
1154
|
throw new APIError(response.status, response.statusText, errorText);
|
|
1079
1155
|
}
|
|
1080
|
-
// Handle
|
|
1081
|
-
if (response.status === 204
|
|
1156
|
+
// Handle 204 No Content responses
|
|
1157
|
+
if (response.status === 204) {
|
|
1082
1158
|
return undefined;
|
|
1083
1159
|
}
|
|
1084
1160
|
return response.json();
|
|
@@ -1432,7 +1508,7 @@ export class AxonFlow {
|
|
|
1432
1508
|
// API returns {"policies": [...]} wrapper via Agent proxy
|
|
1433
1509
|
const response = await this.orchestratorRequest('GET', path);
|
|
1434
1510
|
// Handle both wrapped and unwrapped responses for compatibility
|
|
1435
|
-
return Array.isArray(response) ? response : response.policies;
|
|
1511
|
+
return Array.isArray(response) ? response : response.policies || [];
|
|
1436
1512
|
}
|
|
1437
1513
|
/**
|
|
1438
1514
|
* Get a specific dynamic policy by ID.
|
|
@@ -1540,7 +1616,7 @@ export class AxonFlow {
|
|
|
1540
1616
|
// API returns {"policies": [...]} wrapper via Agent proxy
|
|
1541
1617
|
const response = await this.orchestratorRequest('GET', path);
|
|
1542
1618
|
// Handle both wrapped and unwrapped responses for compatibility
|
|
1543
|
-
return Array.isArray(response) ? response : response.policies;
|
|
1619
|
+
return Array.isArray(response) ? response : response.policies || [];
|
|
1544
1620
|
}
|
|
1545
1621
|
// ============================================================================
|
|
1546
1622
|
// Portal Authentication Methods (Enterprise)
|
|
@@ -1925,6 +2001,49 @@ export class AxonFlow {
|
|
|
1925
2001
|
providerType: response.provider_type,
|
|
1926
2002
|
};
|
|
1927
2003
|
}
|
|
2004
|
+
/**
|
|
2005
|
+
* Close a PR without merging and optionally delete the branch.
|
|
2006
|
+
* Useful for cleaning up test PRs created by examples.
|
|
2007
|
+
*
|
|
2008
|
+
* @param prId - PR record ID
|
|
2009
|
+
* @param deleteBranch - Whether to delete the associated branch (default: true)
|
|
2010
|
+
* @returns Closed PR record
|
|
2011
|
+
*
|
|
2012
|
+
* @example
|
|
2013
|
+
* ```typescript
|
|
2014
|
+
* // Close PR and delete branch
|
|
2015
|
+
* const pr = await axonflow.closePR('pr_123');
|
|
2016
|
+
* console.log(`PR #${pr.prNumber} closed`);
|
|
2017
|
+
*
|
|
2018
|
+
* // Close PR but keep branch
|
|
2019
|
+
* const pr = await axonflow.closePR('pr_123', false);
|
|
2020
|
+
* ```
|
|
2021
|
+
*/
|
|
2022
|
+
async closePR(prId, deleteBranch = true) {
|
|
2023
|
+
if (this.config.debug) {
|
|
2024
|
+
debugLog('Closing PR', { prId, deleteBranch });
|
|
2025
|
+
}
|
|
2026
|
+
const query = deleteBranch ? '?delete_branch=true' : '';
|
|
2027
|
+
const response = await this.portalRequest('DELETE', `/api/v1/code-governance/prs/${prId}${query}`);
|
|
2028
|
+
return {
|
|
2029
|
+
id: response.id,
|
|
2030
|
+
prNumber: response.pr_number,
|
|
2031
|
+
prUrl: response.pr_url,
|
|
2032
|
+
title: response.title,
|
|
2033
|
+
state: response.state,
|
|
2034
|
+
owner: response.owner,
|
|
2035
|
+
repo: response.repo,
|
|
2036
|
+
headBranch: response.head_branch,
|
|
2037
|
+
baseBranch: response.base_branch,
|
|
2038
|
+
filesCount: response.files_count,
|
|
2039
|
+
secretsDetected: response.secrets_detected,
|
|
2040
|
+
unsafePatterns: response.unsafe_patterns,
|
|
2041
|
+
createdAt: response.created_at,
|
|
2042
|
+
closedAt: response.closed_at,
|
|
2043
|
+
createdBy: response.created_by,
|
|
2044
|
+
providerType: response.provider_type,
|
|
2045
|
+
};
|
|
2046
|
+
}
|
|
1928
2047
|
/**
|
|
1929
2048
|
* Sync PR status with the Git provider.
|
|
1930
2049
|
* This updates the local record with the current state from GitHub/GitLab/Bitbucket.
|
|
@@ -2118,8 +2237,8 @@ export class AxonFlow {
|
|
|
2118
2237
|
}
|
|
2119
2238
|
throw new APIError(response.status, response.statusText, errorText);
|
|
2120
2239
|
}
|
|
2121
|
-
// Handle
|
|
2122
|
-
if (response.status === 204
|
|
2240
|
+
// Handle 204 No Content responses
|
|
2241
|
+
if (response.status === 204) {
|
|
2123
2242
|
return undefined;
|
|
2124
2243
|
}
|
|
2125
2244
|
return response.json();
|
|
@@ -2162,8 +2281,8 @@ export class AxonFlow {
|
|
|
2162
2281
|
}
|
|
2163
2282
|
throw new APIError(response.status, response.statusText, errorText);
|
|
2164
2283
|
}
|
|
2165
|
-
// Handle
|
|
2166
|
-
if (response.status === 204
|
|
2284
|
+
// Handle 204 No Content responses
|
|
2285
|
+
if (response.status === 204) {
|
|
2167
2286
|
return undefined;
|
|
2168
2287
|
}
|
|
2169
2288
|
return response.json();
|