@axonflow/sdk 1.2.0 → 1.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 (101) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +313 -9
  3. package/dist/cjs/client.d.ts +106 -41
  4. package/dist/cjs/client.d.ts.map +1 -1
  5. package/dist/cjs/client.js +446 -198
  6. package/dist/cjs/client.js.map +1 -1
  7. package/dist/cjs/errors.d.ts +51 -0
  8. package/dist/cjs/errors.d.ts.map +1 -0
  9. package/dist/cjs/errors.js +84 -0
  10. package/dist/cjs/errors.js.map +1 -0
  11. package/dist/cjs/index.d.ts +6 -2
  12. package/dist/cjs/index.d.ts.map +1 -1
  13. package/dist/cjs/index.js +16 -2
  14. package/dist/cjs/index.js.map +1 -1
  15. package/dist/cjs/interceptors/anthropic.d.ts +1 -1
  16. package/dist/cjs/interceptors/anthropic.d.ts.map +1 -1
  17. package/dist/cjs/interceptors/anthropic.js +6 -6
  18. package/dist/cjs/interceptors/anthropic.js.map +1 -1
  19. package/dist/cjs/interceptors/bedrock.d.ts +141 -0
  20. package/dist/cjs/interceptors/bedrock.d.ts.map +1 -0
  21. package/dist/cjs/interceptors/bedrock.js +259 -0
  22. package/dist/cjs/interceptors/bedrock.js.map +1 -0
  23. package/dist/cjs/interceptors/gemini.d.ts +81 -0
  24. package/dist/cjs/interceptors/gemini.d.ts.map +1 -0
  25. package/dist/cjs/interceptors/gemini.js +110 -0
  26. package/dist/cjs/interceptors/gemini.js.map +1 -0
  27. package/dist/cjs/interceptors/ollama.d.ts +143 -0
  28. package/dist/cjs/interceptors/ollama.d.ts.map +1 -0
  29. package/dist/cjs/interceptors/ollama.js +150 -0
  30. package/dist/cjs/interceptors/ollama.js.map +1 -0
  31. package/dist/cjs/interceptors/openai.d.ts +1 -1
  32. package/dist/cjs/interceptors/openai.d.ts.map +1 -1
  33. package/dist/cjs/interceptors/openai.js +5 -5
  34. package/dist/cjs/interceptors/openai.js.map +1 -1
  35. package/dist/cjs/types/config.d.ts +7 -1
  36. package/dist/cjs/types/config.d.ts.map +1 -1
  37. package/dist/cjs/types/gateway.d.ts +51 -114
  38. package/dist/cjs/types/gateway.d.ts.map +1 -1
  39. package/dist/cjs/types/gateway.js +2 -7
  40. package/dist/cjs/types/gateway.js.map +1 -1
  41. package/dist/cjs/types/index.d.ts +1 -0
  42. package/dist/cjs/types/index.d.ts.map +1 -1
  43. package/dist/cjs/types/index.js +1 -0
  44. package/dist/cjs/types/index.js.map +1 -1
  45. package/dist/cjs/types/proxy.d.ts +78 -0
  46. package/dist/cjs/types/proxy.d.ts.map +1 -0
  47. package/dist/cjs/types/proxy.js +9 -0
  48. package/dist/cjs/types/proxy.js.map +1 -0
  49. package/dist/cjs/utils/helpers.d.ts.map +1 -1
  50. package/dist/cjs/utils/helpers.js +3 -1
  51. package/dist/cjs/utils/helpers.js.map +1 -1
  52. package/dist/esm/client.d.ts +106 -41
  53. package/dist/esm/client.d.ts.map +1 -1
  54. package/dist/esm/client.js +446 -198
  55. package/dist/esm/client.js.map +1 -1
  56. package/dist/esm/errors.d.ts +51 -0
  57. package/dist/esm/errors.d.ts.map +1 -0
  58. package/dist/esm/errors.js +75 -0
  59. package/dist/esm/errors.js.map +1 -0
  60. package/dist/esm/index.d.ts +6 -2
  61. package/dist/esm/index.d.ts.map +1 -1
  62. package/dist/esm/index.js +6 -1
  63. package/dist/esm/index.js.map +1 -1
  64. package/dist/esm/interceptors/anthropic.d.ts +1 -1
  65. package/dist/esm/interceptors/anthropic.d.ts.map +1 -1
  66. package/dist/esm/interceptors/anthropic.js +6 -6
  67. package/dist/esm/interceptors/anthropic.js.map +1 -1
  68. package/dist/esm/interceptors/bedrock.d.ts +141 -0
  69. package/dist/esm/interceptors/bedrock.d.ts.map +1 -0
  70. package/dist/esm/interceptors/bedrock.js +220 -0
  71. package/dist/esm/interceptors/bedrock.js.map +1 -0
  72. package/dist/esm/interceptors/gemini.d.ts +81 -0
  73. package/dist/esm/interceptors/gemini.d.ts.map +1 -0
  74. package/dist/esm/interceptors/gemini.js +105 -0
  75. package/dist/esm/interceptors/gemini.js.map +1 -0
  76. package/dist/esm/interceptors/ollama.d.ts +143 -0
  77. package/dist/esm/interceptors/ollama.d.ts.map +1 -0
  78. package/dist/esm/interceptors/ollama.js +144 -0
  79. package/dist/esm/interceptors/ollama.js.map +1 -0
  80. package/dist/esm/interceptors/openai.d.ts +1 -1
  81. package/dist/esm/interceptors/openai.d.ts.map +1 -1
  82. package/dist/esm/interceptors/openai.js +5 -5
  83. package/dist/esm/interceptors/openai.js.map +1 -1
  84. package/dist/esm/types/config.d.ts +7 -1
  85. package/dist/esm/types/config.d.ts.map +1 -1
  86. package/dist/esm/types/gateway.d.ts +51 -114
  87. package/dist/esm/types/gateway.d.ts.map +1 -1
  88. package/dist/esm/types/gateway.js +2 -7
  89. package/dist/esm/types/gateway.js.map +1 -1
  90. package/dist/esm/types/index.d.ts +1 -0
  91. package/dist/esm/types/index.d.ts.map +1 -1
  92. package/dist/esm/types/index.js +1 -0
  93. package/dist/esm/types/index.js.map +1 -1
  94. package/dist/esm/types/proxy.d.ts +78 -0
  95. package/dist/esm/types/proxy.d.ts.map +1 -0
  96. package/dist/esm/types/proxy.js +8 -0
  97. package/dist/esm/types/proxy.js.map +1 -0
  98. package/dist/esm/utils/helpers.d.ts.map +1 -1
  99. package/dist/esm/utils/helpers.js +3 -1
  100. package/dist/esm/utils/helpers.js.map +1 -1
  101. package/package.json +22 -7
@@ -1,3 +1,4 @@
1
+ import { AuthenticationError, APIError, PolicyViolationError } from './errors.js';
1
2
  import { OpenAIInterceptor } from './interceptors/openai.js';
2
3
  import { AnthropicInterceptor } from './interceptors/anthropic.js';
3
4
  import { generateRequestId, debugLog } from './utils/helpers.js';
@@ -7,24 +8,49 @@ import { generateRequestId, debugLog } from './utils/helpers.js';
7
8
  export class AxonFlow {
8
9
  constructor(config) {
9
10
  this.interceptors = [];
10
- // Set defaults
11
+ // Set defaults first to determine endpoint
12
+ const endpoint = config.endpoint || 'https://staging-eu.getaxonflow.com';
13
+ // Check if running in self-hosted mode (localhost)
14
+ const isLocalhost = endpoint.includes('localhost') || endpoint.includes('127.0.0.1');
15
+ // License key is optional for self-hosted deployments
16
+ // When not provided, agent must have SELF_HOSTED_MODE=true
17
+ if (!isLocalhost && !config.licenseKey && !config.apiKey) {
18
+ throw new Error('Either licenseKey or apiKey must be provided for non-localhost endpoints');
19
+ }
20
+ if (isLocalhost && !config.licenseKey && !config.apiKey && config.debug) {
21
+ console.warn('[AxonFlow] No license key provided - ensure agent has SELF_HOSTED_MODE=true');
22
+ }
23
+ // Set configuration
11
24
  this.config = {
12
25
  apiKey: config.apiKey,
13
- endpoint: config.endpoint || 'https://staging-eu.getaxonflow.com',
14
- mode: config.mode || 'production',
26
+ licenseKey: config.licenseKey,
27
+ endpoint,
28
+ mode: config.mode || (isLocalhost ? 'sandbox' : 'production'),
15
29
  tenant: config.tenant || 'default',
16
30
  debug: config.debug || false,
17
31
  timeout: config.timeout || 30000,
18
- retry: config.retry || { enabled: true, maxAttempts: 3, delay: 1000 },
19
- cache: config.cache || { enabled: true, ttl: 60000 }
32
+ retry: {
33
+ enabled: config.retry?.enabled !== false,
34
+ maxAttempts: config.retry?.maxAttempts || 3,
35
+ delay: config.retry?.delay || 1000,
36
+ },
37
+ cache: {
38
+ enabled: config.cache?.enabled !== false,
39
+ ttl: config.cache?.ttl || 60000,
40
+ },
20
41
  };
21
42
  // Initialize interceptors
22
- this.interceptors = [
23
- new OpenAIInterceptor(),
24
- new AnthropicInterceptor()
25
- ];
43
+ this.interceptors = [new OpenAIInterceptor(), new AnthropicInterceptor()];
26
44
  if (this.config.debug) {
27
- debugLog('AxonFlow initialized', { mode: this.config.mode, endpoint: this.config.endpoint });
45
+ debugLog('AxonFlow initialized', {
46
+ mode: this.config.mode,
47
+ endpoint: this.config.endpoint,
48
+ authMethod: isLocalhost
49
+ ? 'self-hosted (no auth)'
50
+ : this.config.licenseKey
51
+ ? 'license-key'
52
+ : 'api-key',
53
+ });
28
54
  }
29
55
  }
30
56
  /**
@@ -45,7 +71,7 @@ export class AxonFlow {
45
71
  timestamp: Date.now(),
46
72
  aiRequest,
47
73
  mode: this.config.mode,
48
- tenant: this.config.tenant
74
+ tenant: this.config.tenant,
49
75
  };
50
76
  // Check policies with AxonFlow Agent
51
77
  const governanceResponse = await this.checkPolicies(governanceRequest);
@@ -90,7 +116,7 @@ export class AxonFlow {
90
116
  provider: 'unknown',
91
117
  model: 'unknown',
92
118
  prompt: aiCall.toString(),
93
- parameters: {}
119
+ parameters: {},
94
120
  };
95
121
  }
96
122
  /**
@@ -101,7 +127,7 @@ export class AxonFlow {
101
127
  // Transform SDK request to Agent API format
102
128
  const agentRequest = {
103
129
  query: request.aiRequest.prompt,
104
- user_token: this.config.apiKey,
130
+ user_token: this.config.apiKey || '',
105
131
  client_id: this.config.tenant,
106
132
  request_type: 'llm_chat',
107
133
  context: {
@@ -109,16 +135,23 @@ export class AxonFlow {
109
135
  model: request.aiRequest.model,
110
136
  parameters: request.aiRequest.parameters,
111
137
  requestId: request.requestId,
112
- mode: this.config.mode
113
- }
138
+ mode: this.config.mode,
139
+ },
140
+ };
141
+ const headers = {
142
+ 'Content-Type': 'application/json',
114
143
  };
144
+ // Add license key header if available (preferred auth method)
145
+ // Skip auth headers for localhost (self-hosted mode)
146
+ const isLocalhost = this.config.endpoint.includes('localhost') || this.config.endpoint.includes('127.0.0.1');
147
+ if (!isLocalhost && this.config.licenseKey) {
148
+ headers['X-License-Key'] = this.config.licenseKey;
149
+ }
115
150
  const response = await fetch(url, {
116
151
  method: 'POST',
117
- headers: {
118
- 'Content-Type': 'application/json'
119
- },
152
+ headers,
120
153
  body: JSON.stringify(agentRequest),
121
- signal: AbortSignal.timeout(this.config.timeout)
154
+ signal: AbortSignal.timeout(this.config.timeout),
122
155
  });
123
156
  if (!response.ok) {
124
157
  const errorText = await response.text();
@@ -126,23 +159,29 @@ export class AxonFlow {
126
159
  }
127
160
  const agentResponse = await response.json();
128
161
  // Transform Agent API response to SDK format
162
+ // Extract policy name from policy_info if available
163
+ const policyName = agentResponse.policy_info?.policies_evaluated?.[0] || 'agent-policy';
129
164
  return {
130
165
  requestId: request.requestId,
131
166
  allowed: !agentResponse.blocked,
132
- violations: agentResponse.blocked ? [{
133
- type: 'security',
134
- severity: 'high',
135
- description: agentResponse.block_reason || 'Request blocked by policy',
136
- policy: 'agent-policy',
137
- action: 'blocked'
138
- }] : [],
167
+ violations: agentResponse.blocked
168
+ ? [
169
+ {
170
+ type: 'security',
171
+ severity: 'high',
172
+ description: agentResponse.block_reason || 'Request blocked by policy',
173
+ policy: policyName,
174
+ action: 'blocked',
175
+ },
176
+ ]
177
+ : [],
139
178
  modifiedRequest: agentResponse.data,
140
- policies: [],
179
+ policies: agentResponse.policy_info?.policies_evaluated || [],
141
180
  audit: {
142
181
  timestamp: Date.now(),
143
182
  duration: parseInt(agentResponse.policy_info?.processing_time?.replace('ms', '') || '0'),
144
- tenant: this.config.tenant
145
- }
183
+ tenant: this.config.tenant,
184
+ },
146
185
  };
147
186
  }
148
187
  /**
@@ -155,7 +194,7 @@ export class AxonFlow {
155
194
  debugLog('Request processed', {
156
195
  allowed: response.allowed,
157
196
  violations: response.violations?.length || 0,
158
- duration: response.audit.duration
197
+ duration: response.audit.duration,
159
198
  });
160
199
  }
161
200
  }
@@ -163,9 +202,9 @@ export class AxonFlow {
163
202
  * Check if an error is from AxonFlow (vs the AI provider)
164
203
  */
165
204
  isAxonFlowError(error) {
166
- return error?.message?.includes('AxonFlow') ||
205
+ return (error?.message?.includes('AxonFlow') ||
167
206
  error?.message?.includes('governance') ||
168
- error?.message?.includes('fetch');
207
+ error?.message?.includes('fetch'));
169
208
  }
170
209
  /**
171
210
  * Create a sandbox client for testing
@@ -175,9 +214,176 @@ export class AxonFlow {
175
214
  apiKey,
176
215
  mode: 'sandbox',
177
216
  endpoint: 'https://staging-eu.getaxonflow.com',
178
- debug: true
217
+ debug: true,
179
218
  });
180
219
  }
220
+ // ============================================================================
221
+ // Proxy Mode Methods
222
+ // ============================================================================
223
+ /**
224
+ * Check if AxonFlow Agent is healthy and available.
225
+ *
226
+ * @returns HealthStatus object with agent health information
227
+ *
228
+ * @example
229
+ * ```typescript
230
+ * const health = await axonflow.healthCheck();
231
+ * if (health.status === 'healthy') {
232
+ * console.log('Agent is healthy');
233
+ * }
234
+ * ```
235
+ */
236
+ async healthCheck() {
237
+ const url = `${this.config.endpoint}/health`;
238
+ try {
239
+ const response = await fetch(url, {
240
+ method: 'GET',
241
+ signal: AbortSignal.timeout(this.config.timeout),
242
+ });
243
+ if (!response.ok) {
244
+ return {
245
+ status: 'unhealthy',
246
+ components: {
247
+ agent: { status: 'error', message: `HTTP ${response.status}` },
248
+ },
249
+ };
250
+ }
251
+ const data = await response.json();
252
+ return {
253
+ status: data.status === 'healthy' ? 'healthy' : 'degraded',
254
+ version: data.version,
255
+ uptime: data.uptime,
256
+ components: data.components,
257
+ };
258
+ }
259
+ catch (error) {
260
+ if (this.config.debug) {
261
+ debugLog('Health check failed', error);
262
+ }
263
+ return {
264
+ status: 'unhealthy',
265
+ components: {
266
+ agent: {
267
+ status: 'error',
268
+ message: error instanceof Error ? error.message : 'Unknown error',
269
+ },
270
+ },
271
+ };
272
+ }
273
+ }
274
+ /**
275
+ * Execute a query through AxonFlow with policy enforcement (Proxy Mode).
276
+ *
277
+ * This is the primary method for Proxy Mode, where AxonFlow handles policy
278
+ * checking and optionally routes requests to LLM providers.
279
+ *
280
+ * @param options - Query execution options
281
+ * @returns ExecuteQueryResponse with results or error information
282
+ * @throws PolicyViolationError if request is blocked by policy
283
+ * @throws AuthenticationError if credentials are invalid
284
+ * @throws APIError for other API errors
285
+ *
286
+ * @example
287
+ * ```typescript
288
+ * const response = await axonflow.executeQuery({
289
+ * userToken: 'user-123',
290
+ * query: 'Explain quantum computing',
291
+ * requestType: 'chat',
292
+ * context: { provider: 'openai', model: 'gpt-4' }
293
+ * });
294
+ *
295
+ * if (response.success) {
296
+ * console.log('Response:', response.data);
297
+ * }
298
+ * ```
299
+ */
300
+ async executeQuery(options) {
301
+ const agentRequest = {
302
+ query: options.query,
303
+ user_token: options.userToken,
304
+ client_id: this.config.tenant,
305
+ request_type: options.requestType,
306
+ context: options.context || {},
307
+ };
308
+ const url = `${this.config.endpoint}/api/request`;
309
+ const headers = {
310
+ 'Content-Type': 'application/json',
311
+ };
312
+ // Add authentication headers
313
+ const isLocalhost = this.config.endpoint.includes('localhost') || this.config.endpoint.includes('127.0.0.1');
314
+ if (!isLocalhost) {
315
+ if (this.config.licenseKey) {
316
+ headers['X-License-Key'] = this.config.licenseKey;
317
+ }
318
+ else if (this.config.apiKey) {
319
+ headers['X-Client-Secret'] = this.config.apiKey;
320
+ }
321
+ }
322
+ if (this.config.debug) {
323
+ debugLog('Proxy Mode: executeQuery', {
324
+ requestType: options.requestType,
325
+ query: options.query.substring(0, 50),
326
+ });
327
+ }
328
+ const response = await fetch(url, {
329
+ method: 'POST',
330
+ headers,
331
+ body: JSON.stringify(agentRequest),
332
+ signal: AbortSignal.timeout(this.config.timeout),
333
+ });
334
+ if (!response.ok) {
335
+ const errorText = await response.text();
336
+ if (response.status === 401 || response.status === 403) {
337
+ // Try to parse as JSON for policy violation info
338
+ try {
339
+ const errorJson = JSON.parse(errorText);
340
+ if (errorJson.blocked || errorJson.block_reason) {
341
+ throw new PolicyViolationError(errorJson.block_reason || 'Request blocked by policy', errorJson.policy_info?.policies_evaluated);
342
+ }
343
+ }
344
+ catch (e) {
345
+ if (e instanceof PolicyViolationError)
346
+ throw e;
347
+ }
348
+ throw new AuthenticationError(`Request failed: ${errorText}`);
349
+ }
350
+ throw new APIError(response.status, response.statusText, errorText);
351
+ }
352
+ const data = await response.json();
353
+ // Check for policy violation in successful response (some blocked responses return 200)
354
+ if (data.blocked) {
355
+ throw new PolicyViolationError(data.block_reason || 'Request blocked by policy', data.policy_info?.policies_evaluated);
356
+ }
357
+ // Transform snake_case response to camelCase
358
+ const result = {
359
+ success: data.success,
360
+ data: data.data,
361
+ result: data.result,
362
+ planId: data.plan_id,
363
+ requestId: data.request_id,
364
+ metadata: data.metadata || {},
365
+ error: data.error,
366
+ blocked: data.blocked || false,
367
+ blockReason: data.block_reason,
368
+ };
369
+ // Parse policy info if present
370
+ if (data.policy_info) {
371
+ result.policyInfo = {
372
+ policiesEvaluated: data.policy_info.policies_evaluated || [],
373
+ staticChecks: data.policy_info.static_checks || [],
374
+ processingTime: data.policy_info.processing_time || '',
375
+ tenantId: data.policy_info.tenant_id || '',
376
+ };
377
+ }
378
+ if (this.config.debug) {
379
+ debugLog('Proxy Mode: executeQuery result', {
380
+ success: result.success,
381
+ blocked: result.blocked,
382
+ hasData: !!result.data,
383
+ });
384
+ }
385
+ return result;
386
+ }
181
387
  /**
182
388
  * List all available MCP connectors from the marketplace
183
389
  */
@@ -185,7 +391,7 @@ export class AxonFlow {
185
391
  const url = `${this.config.endpoint}/api/connectors`;
186
392
  const response = await fetch(url, {
187
393
  method: 'GET',
188
- signal: AbortSignal.timeout(this.config.timeout)
394
+ signal: AbortSignal.timeout(this.config.timeout),
189
395
  });
190
396
  if (!response.ok) {
191
397
  throw new Error(`Failed to list connectors: ${response.status} ${response.statusText}`);
@@ -201,14 +407,21 @@ export class AxonFlow {
201
407
  */
202
408
  async installConnector(request) {
203
409
  const url = `${this.config.endpoint}/api/connectors/install`;
410
+ const headers = {
411
+ 'Content-Type': 'application/json',
412
+ };
413
+ // Add authentication headers
414
+ if (this.config.licenseKey) {
415
+ headers['X-License-Key'] = this.config.licenseKey;
416
+ }
417
+ else if (this.config.apiKey) {
418
+ headers['X-Client-Secret'] = this.config.apiKey;
419
+ }
204
420
  const response = await fetch(url, {
205
421
  method: 'POST',
206
- headers: {
207
- 'Content-Type': 'application/json',
208
- 'X-Client-Secret': this.config.apiKey
209
- },
422
+ headers,
210
423
  body: JSON.stringify(request),
211
- signal: AbortSignal.timeout(this.config.timeout)
424
+ signal: AbortSignal.timeout(this.config.timeout),
212
425
  });
213
426
  if (!response.ok) {
214
427
  const errorText = await response.text();
@@ -224,22 +437,26 @@ export class AxonFlow {
224
437
  async queryConnector(connectorName, query, params) {
225
438
  const agentRequest = {
226
439
  query,
227
- user_token: this.config.apiKey,
440
+ user_token: this.config.apiKey || '',
228
441
  client_id: this.config.tenant,
229
442
  request_type: 'mcp-query',
230
443
  context: {
231
444
  connector: connectorName,
232
- params: params || {}
233
- }
445
+ params: params || {},
446
+ },
234
447
  };
235
448
  const url = `${this.config.endpoint}/api/request`;
449
+ const headers = {
450
+ 'Content-Type': 'application/json',
451
+ };
452
+ if (this.config.licenseKey) {
453
+ headers['X-License-Key'] = this.config.licenseKey;
454
+ }
236
455
  const response = await fetch(url, {
237
456
  method: 'POST',
238
- headers: {
239
- 'Content-Type': 'application/json'
240
- },
457
+ headers,
241
458
  body: JSON.stringify(agentRequest),
242
- signal: AbortSignal.timeout(this.config.timeout)
459
+ signal: AbortSignal.timeout(this.config.timeout),
243
460
  });
244
461
  if (!response.ok) {
245
462
  const errorText = await response.text();
@@ -253,28 +470,35 @@ export class AxonFlow {
253
470
  success: agentResponse.success,
254
471
  data: agentResponse.data,
255
472
  error: agentResponse.error,
256
- meta: agentResponse.metadata
473
+ meta: agentResponse.metadata,
257
474
  };
258
475
  }
259
476
  /**
260
477
  * Generate a multi-agent execution plan from a natural language query
478
+ * @param query - Natural language query describing the task
479
+ * @param domain - Optional domain hint (travel, healthcare, etc.)
480
+ * @param userToken - Optional user token for authentication (defaults to tenant/client_id)
261
481
  */
262
- async generatePlan(query, domain) {
482
+ async generatePlan(query, domain, userToken) {
263
483
  const agentRequest = {
264
484
  query,
265
- user_token: this.config.apiKey,
485
+ user_token: userToken || this.config.tenant,
266
486
  client_id: this.config.tenant,
267
487
  request_type: 'multi-agent-plan',
268
- context: domain ? { domain } : {}
488
+ context: domain ? { domain } : {},
269
489
  };
270
490
  const url = `${this.config.endpoint}/api/request`;
491
+ const headers = {
492
+ 'Content-Type': 'application/json',
493
+ };
494
+ if (this.config.licenseKey) {
495
+ headers['X-License-Key'] = this.config.licenseKey;
496
+ }
271
497
  const response = await fetch(url, {
272
498
  method: 'POST',
273
- headers: {
274
- 'Content-Type': 'application/json'
275
- },
499
+ headers,
276
500
  body: JSON.stringify(agentRequest),
277
- signal: AbortSignal.timeout(this.config.timeout)
501
+ signal: AbortSignal.timeout(this.config.timeout),
278
502
  });
279
503
  if (!response.ok) {
280
504
  const errorText = await response.text();
@@ -293,28 +517,34 @@ export class AxonFlow {
293
517
  domain: agentResponse.data?.domain || domain || 'generic',
294
518
  complexity: agentResponse.data?.complexity || 0,
295
519
  parallel: agentResponse.data?.parallel || false,
296
- metadata: agentResponse.metadata || {}
520
+ metadata: agentResponse.metadata || {},
297
521
  };
298
522
  }
299
523
  /**
300
524
  * Execute a previously generated multi-agent plan
525
+ * @param planId - ID of the plan to execute
526
+ * @param userToken - Optional user token for authentication (defaults to tenant/client_id)
301
527
  */
302
- async executePlan(planId) {
528
+ async executePlan(planId, userToken) {
303
529
  const agentRequest = {
304
530
  query: '',
305
- user_token: this.config.apiKey,
531
+ user_token: userToken || this.config.tenant,
306
532
  client_id: this.config.tenant,
307
533
  request_type: 'execute-plan',
308
- context: { plan_id: planId }
534
+ context: { plan_id: planId },
309
535
  };
310
536
  const url = `${this.config.endpoint}/api/request`;
537
+ const headers = {
538
+ 'Content-Type': 'application/json',
539
+ };
540
+ if (this.config.licenseKey) {
541
+ headers['X-License-Key'] = this.config.licenseKey;
542
+ }
311
543
  const response = await fetch(url, {
312
544
  method: 'POST',
313
- headers: {
314
- 'Content-Type': 'application/json'
315
- },
545
+ headers,
316
546
  body: JSON.stringify(agentRequest),
317
- signal: AbortSignal.timeout(this.config.timeout)
547
+ signal: AbortSignal.timeout(this.config.timeout),
318
548
  });
319
549
  if (!response.ok) {
320
550
  const errorText = await response.text();
@@ -330,7 +560,7 @@ export class AxonFlow {
330
560
  result: agentResponse.result,
331
561
  stepResults: agentResponse.metadata?.step_results,
332
562
  error: agentResponse.error,
333
- duration: agentResponse.metadata?.duration
563
+ duration: agentResponse.metadata?.duration,
334
564
  };
335
565
  }
336
566
  /**
@@ -340,7 +570,7 @@ export class AxonFlow {
340
570
  const url = `${this.config.endpoint}/api/plans/${planId}`;
341
571
  const response = await fetch(url, {
342
572
  method: 'GET',
343
- signal: AbortSignal.timeout(this.config.timeout)
573
+ signal: AbortSignal.timeout(this.config.timeout),
344
574
  });
345
575
  if (!response.ok) {
346
576
  const errorText = await response.text();
@@ -353,191 +583,209 @@ export class AxonFlow {
353
583
  result: status.result,
354
584
  stepResults: status.step_results,
355
585
  error: status.error,
356
- duration: status.duration
586
+ duration: status.duration,
357
587
  };
358
588
  }
359
- // ===========================================================================
360
- // Gateway Mode SDK Methods
361
- // ===========================================================================
362
- // Gateway Mode allows clients to make LLM calls directly while still using
363
- // AxonFlow for policy enforcement and audit logging.
364
- //
365
- // Usage:
366
- // 1. Call getPolicyApprovedContext() before making LLM call
367
- // 2. Make LLM call directly to your provider (using returned approved data)
368
- // 3. Call auditLLMCall() after to record the call for compliance
369
- //
370
- // Example:
371
- // const ctx = await axonflow.getPolicyApprovedContext({
372
- // userToken: 'user-jwt',
373
- // dataSources: ['postgres'],
374
- // query: 'Find patients with diabetes'
375
- // });
376
- // if (!ctx.approved) throw new Error(ctx.blockReason);
377
- //
378
- // const llmResp = await openai.chat.completions.create({...}); // Your LLM call
379
- //
380
- // await axonflow.auditLLMCall({
381
- // contextId: ctx.contextId,
382
- // responseSummary: 'Found 5 patients',
383
- // provider: 'openai',
384
- // model: 'gpt-4',
385
- // tokenUsage: { promptTokens: 100, completionTokens: 50, totalTokens: 150 },
386
- // latencyMs: 250
387
- // });
589
+ // ============================================================================
590
+ // Gateway Mode Methods
591
+ // ============================================================================
388
592
  /**
389
- * Perform policy pre-check before making LLM call
390
- *
391
- * This is the first step in Gateway Mode. Call this before making your
392
- * LLM call to ensure policy compliance.
593
+ * Gateway Mode: Pre-check policy approval before making a direct LLM call.
594
+ * Alias for getPolicyApprovedContext() for simpler API.
595
+ */
596
+ async preCheck(options) {
597
+ return this.getPolicyApprovedContext(options);
598
+ }
599
+ /**
600
+ * Gateway Mode: Get policy-approved context before making a direct LLM call.
393
601
  *
394
- * @param request Pre-check request containing user token, query, and optional data sources
395
- * @returns PolicyApprovalResult with context ID and approved data (if any)
602
+ * Use this when you want to:
603
+ * - Make direct LLM calls (not through AxonFlow proxy)
604
+ * - Have full control over your LLM provider/model selection
605
+ * - Minimize latency by calling LLM directly
396
606
  *
397
607
  * @example
398
- * const result = await axonflow.getPolicyApprovedContext({
399
- * userToken: 'user-jwt-token',
400
- * dataSources: ['postgres', 'salesforce'],
401
- * query: 'Find all patients with recent lab results'
608
+ * ```typescript
609
+ * const ctx = await axonflow.getPolicyApprovedContext({
610
+ * userToken: 'user-jwt',
611
+ * query: 'Analyze this customer data',
612
+ * dataSources: ['postgres']
402
613
  * });
403
614
  *
404
- * if (!result.approved) {
405
- * throw new Error(`Request blocked: ${result.blockReason}`);
615
+ * if (!ctx.approved) {
616
+ * throw new Error(`Blocked: ${ctx.blockReason}`);
406
617
  * }
407
618
  *
408
- * // Use result.approvedData to build your LLM prompt
409
- * const prompt = buildPrompt(result.approvedData);
619
+ * // Make direct LLM call with approved data
620
+ * const response = await openai.chat.completions.create({
621
+ * model: 'gpt-4',
622
+ * messages: [{ role: 'user', content: JSON.stringify(ctx.approvedData) }]
623
+ * });
624
+ *
625
+ * // Audit the call
626
+ * await axonflow.auditLLMCall({
627
+ * contextId: ctx.contextId,
628
+ * responseSummary: response.choices[0].message.content.substring(0, 100),
629
+ * provider: 'openai',
630
+ * model: 'gpt-4',
631
+ * tokenUsage: {
632
+ * promptTokens: response.usage.prompt_tokens,
633
+ * completionTokens: response.usage.completion_tokens,
634
+ * totalTokens: response.usage.total_tokens
635
+ * },
636
+ * latencyMs: 250
637
+ * });
638
+ * ```
410
639
  */
411
- async getPolicyApprovedContext(request) {
640
+ async getPolicyApprovedContext(options) {
412
641
  const url = `${this.config.endpoint}/api/policy/pre-check`;
413
- const body = {
414
- user_token: request.userToken,
642
+ const requestBody = {
643
+ user_token: options.userToken,
415
644
  client_id: this.config.tenant,
416
- query: request.query,
417
- data_sources: request.dataSources || [],
418
- context: request.context || {}
645
+ query: options.query,
646
+ data_sources: options.dataSources || [],
647
+ context: options.context || {},
648
+ };
649
+ const headers = {
650
+ 'Content-Type': 'application/json',
419
651
  };
652
+ // Add authentication headers
653
+ const isLocalhost = this.config.endpoint.includes('localhost') || this.config.endpoint.includes('127.0.0.1');
654
+ if (!isLocalhost) {
655
+ if (this.config.licenseKey) {
656
+ headers['X-License-Key'] = this.config.licenseKey;
657
+ }
658
+ else if (this.config.apiKey) {
659
+ headers['X-Client-Secret'] = this.config.apiKey;
660
+ }
661
+ }
420
662
  if (this.config.debug) {
421
- debugLog('Gateway pre-check request', {
422
- query: request.query.substring(0, 50),
423
- dataSources: request.dataSources
424
- });
663
+ debugLog('Gateway Mode: Pre-check', { query: options.query.substring(0, 50) });
425
664
  }
426
- const startTime = Date.now();
427
665
  const response = await fetch(url, {
428
666
  method: 'POST',
429
- headers: {
430
- 'Content-Type': 'application/json',
431
- 'X-Client-Secret': this.config.apiKey,
432
- 'X-License-Key': this.config.apiKey
433
- },
434
- body: JSON.stringify(body),
435
- signal: AbortSignal.timeout(this.config.timeout)
667
+ headers,
668
+ body: JSON.stringify(requestBody),
669
+ signal: AbortSignal.timeout(this.config.timeout),
436
670
  });
437
- const duration = Date.now() - startTime;
438
671
  if (!response.ok) {
439
672
  const errorText = await response.text();
440
- throw new Error(`Pre-check failed: ${response.status} ${response.statusText} - ${errorText}`);
673
+ if (response.status === 401 || response.status === 403) {
674
+ throw new AuthenticationError(`Policy pre-check authentication failed: ${errorText}`);
675
+ }
676
+ throw new APIError(response.status, response.statusText, errorText);
441
677
  }
442
678
  const data = await response.json();
443
- if (this.config.debug) {
444
- debugLog('Gateway pre-check complete', {
445
- contextId: data.context_id,
446
- approved: data.approved,
447
- duration
448
- });
449
- }
450
- return {
679
+ // Transform snake_case response to camelCase
680
+ // Default expiration to 5 minutes from now if not provided
681
+ const expiresAt = data.expires_at
682
+ ? new Date(data.expires_at)
683
+ : new Date(Date.now() + 5 * 60 * 1000);
684
+ const result = {
451
685
  contextId: data.context_id,
452
686
  approved: data.approved,
453
- approvedData: data.approved_data,
687
+ approvedData: data.approved_data || {},
454
688
  policies: data.policies || [],
455
- rateLimitInfo: data.rate_limit ? {
689
+ expiresAt,
690
+ blockReason: data.block_reason,
691
+ };
692
+ // Parse rate limit info if present
693
+ if (data.rate_limit) {
694
+ result.rateLimitInfo = {
456
695
  limit: data.rate_limit.limit,
457
696
  remaining: data.rate_limit.remaining,
458
- resetAt: new Date(data.rate_limit.reset_at)
459
- } : undefined,
460
- expiresAt: new Date(data.expires_at),
461
- blockReason: data.block_reason
462
- };
697
+ resetAt: new Date(data.rate_limit.reset_at),
698
+ };
699
+ }
700
+ if (this.config.debug) {
701
+ debugLog('Gateway Mode: Pre-check result', {
702
+ approved: result.approved,
703
+ contextId: result.contextId,
704
+ policies: result.policies.length,
705
+ });
706
+ }
707
+ return result;
463
708
  }
464
709
  /**
465
- * Report LLM call details for audit logging
466
- *
467
- * This is the second step in Gateway Mode. Call this after making your
468
- * LLM call to record it in the audit trail.
710
+ * Gateway Mode: Audit an LLM call after completion.
469
711
  *
470
- * @param contextId Context ID from getPolicyApprovedContext()
471
- * @param responseSummary Brief summary of the LLM response (not full response)
472
- * @param provider LLM provider name
473
- * @param model Model name
474
- * @param tokenUsage Token counts from LLM response
475
- * @param latencyMs Time taken for LLM call in milliseconds
476
- * @param metadata Optional additional metadata
477
- * @returns AuditResult confirming the audit was recorded
712
+ * Call this after making a direct LLM call to log the audit trail.
713
+ * This is required for compliance and monitoring.
478
714
  *
479
715
  * @example
480
- * const result = await axonflow.auditLLMCall(
481
- * ctx.contextId,
482
- * 'Found 5 patients with recent lab results',
483
- * 'openai',
484
- * 'gpt-4',
485
- * { promptTokens: 100, completionTokens: 50, totalTokens: 150 },
486
- * 250,
487
- * { sessionId: 'session-123' }
488
- * );
716
+ * ```typescript
717
+ * await axonflow.auditLLMCall({
718
+ * contextId: ctx.contextId,
719
+ * responseSummary: 'Generated report with 5 items',
720
+ * provider: 'openai',
721
+ * model: 'gpt-4',
722
+ * tokenUsage: {
723
+ * promptTokens: 100,
724
+ * completionTokens: 50,
725
+ * totalTokens: 150
726
+ * },
727
+ * latencyMs: 250
728
+ * });
729
+ * ```
489
730
  */
490
- async auditLLMCall(contextId, responseSummary, provider, model, tokenUsage, latencyMs, metadata) {
731
+ async auditLLMCall(options) {
491
732
  const url = `${this.config.endpoint}/api/audit/llm-call`;
492
- const body = {
493
- context_id: contextId,
733
+ const requestBody = {
734
+ context_id: options.contextId,
494
735
  client_id: this.config.tenant,
495
- response_summary: responseSummary,
496
- provider,
497
- model,
736
+ response_summary: options.responseSummary,
737
+ provider: options.provider,
738
+ model: options.model,
498
739
  token_usage: {
499
- prompt_tokens: tokenUsage.promptTokens,
500
- completion_tokens: tokenUsage.completionTokens,
501
- total_tokens: tokenUsage.totalTokens
740
+ prompt_tokens: options.tokenUsage.promptTokens,
741
+ completion_tokens: options.tokenUsage.completionTokens,
742
+ total_tokens: options.tokenUsage.totalTokens,
502
743
  },
503
- latency_ms: latencyMs,
504
- metadata
744
+ latency_ms: options.latencyMs,
745
+ metadata: options.metadata || {},
746
+ };
747
+ const headers = {
748
+ 'Content-Type': 'application/json',
505
749
  };
750
+ // Add authentication headers
751
+ const isLocalhost = this.config.endpoint.includes('localhost') || this.config.endpoint.includes('127.0.0.1');
752
+ if (!isLocalhost) {
753
+ if (this.config.licenseKey) {
754
+ headers['X-License-Key'] = this.config.licenseKey;
755
+ }
756
+ else if (this.config.apiKey) {
757
+ headers['X-Client-Secret'] = this.config.apiKey;
758
+ }
759
+ }
506
760
  if (this.config.debug) {
507
- debugLog('Gateway audit request', {
508
- contextId,
509
- provider,
510
- model,
511
- tokens: tokenUsage.totalTokens
761
+ debugLog('Gateway Mode: Audit', {
762
+ contextId: options.contextId,
763
+ provider: options.provider,
764
+ model: options.model,
512
765
  });
513
766
  }
514
- const startTime = Date.now();
515
767
  const response = await fetch(url, {
516
768
  method: 'POST',
517
- headers: {
518
- 'Content-Type': 'application/json',
519
- 'X-Client-Secret': this.config.apiKey,
520
- 'X-License-Key': this.config.apiKey
521
- },
522
- body: JSON.stringify(body),
523
- signal: AbortSignal.timeout(this.config.timeout)
769
+ headers,
770
+ body: JSON.stringify(requestBody),
771
+ signal: AbortSignal.timeout(this.config.timeout),
524
772
  });
525
- const duration = Date.now() - startTime;
526
773
  if (!response.ok) {
527
774
  const errorText = await response.text();
528
- throw new Error(`Audit failed: ${response.status} ${response.statusText} - ${errorText}`);
775
+ if (response.status === 401 || response.status === 403) {
776
+ throw new AuthenticationError(`Audit logging authentication failed: ${errorText}`);
777
+ }
778
+ throw new APIError(response.status, response.statusText, errorText);
529
779
  }
530
780
  const data = await response.json();
531
- if (this.config.debug) {
532
- debugLog('Gateway audit complete', {
533
- auditId: data.audit_id,
534
- duration
535
- });
536
- }
537
- return {
781
+ const result = {
538
782
  success: data.success,
539
- auditId: data.audit_id
783
+ auditId: data.audit_id,
540
784
  };
785
+ if (this.config.debug) {
786
+ debugLog('Gateway Mode: Audit logged', { auditId: result.auditId });
787
+ }
788
+ return result;
541
789
  }
542
790
  }
543
791
  //# sourceMappingURL=client.js.map