axonflow 1.0.0__tar.gz → 1.4.0__tar.gz
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.
- {axonflow-1.0.0 → axonflow-1.4.0}/PKG-INFO +126 -5
- {axonflow-1.0.0 → axonflow-1.4.0}/README.md +125 -4
- {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/__init__.py +1 -1
- {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/client.py +182 -27
- {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/code_governance.py +1 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/policies.py +15 -2
- {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/types.py +101 -4
- {axonflow-1.0.0 → axonflow-1.4.0}/axonflow.egg-info/PKG-INFO +126 -5
- {axonflow-1.0.0 → axonflow-1.4.0}/pyproject.toml +1 -1
- {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_auth_headers.py +54 -95
- {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_client.py +174 -7
- {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_selfhosted_zero_config.py +3 -13
- {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_types.py +4 -4
- {axonflow-1.0.0 → axonflow-1.4.0}/LICENSE +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/exceptions.py +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/interceptors/__init__.py +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/interceptors/anthropic.py +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/interceptors/base.py +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/interceptors/bedrock.py +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/interceptors/gemini.py +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/interceptors/ollama.py +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/interceptors/openai.py +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/py.typed +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/utils/__init__.py +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/utils/cache.py +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/utils/logging.py +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/axonflow/utils/retry.py +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/axonflow.egg-info/SOURCES.txt +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/axonflow.egg-info/dependency_links.txt +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/axonflow.egg-info/requires.txt +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/axonflow.egg-info/top_level.txt +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/setup.cfg +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/setup.py +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_audit.py +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_client_coverage.py +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_contract.py +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_cost_controls.py +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_exceptions.py +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_gateway.py +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_integration.py +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_interceptors.py +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_interceptors_coverage.py +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_interceptors_execution.py +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_policies.py +0 -0
- {axonflow-1.0.0 → axonflow-1.4.0}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: axonflow
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: AxonFlow Python SDK - Enterprise AI Governance in 3 Lines of Code
|
|
5
5
|
Author-email: AxonFlow <dev@getaxonflow.com>
|
|
6
6
|
Maintainer-email: AxonFlow <dev@getaxonflow.com>
|
|
@@ -202,6 +202,38 @@ result = await client.query_connector(
|
|
|
202
202
|
)
|
|
203
203
|
```
|
|
204
204
|
|
|
205
|
+
### MCP Policy Features (v3.2.0)
|
|
206
|
+
|
|
207
|
+
**Exfiltration Detection** - Prevent large-scale data extraction:
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
# Query with exfiltration limits (default: 10K rows, 10MB)
|
|
211
|
+
result = await client.query_connector(
|
|
212
|
+
user_token="user-jwt",
|
|
213
|
+
connector_name="postgres",
|
|
214
|
+
operation="query",
|
|
215
|
+
params={"sql": "SELECT * FROM customers"}
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
# Check exfiltration info
|
|
219
|
+
if result.policy_info.exfiltration_check.exceeded:
|
|
220
|
+
print(f"Limit exceeded: {result.policy_info.exfiltration_check.limit_type}")
|
|
221
|
+
|
|
222
|
+
# Configure: MCP_MAX_ROWS_PER_QUERY=1000, MCP_MAX_BYTES_PER_QUERY=5242880
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**Dynamic Policy Evaluation** - Orchestrator-based rate limiting, budget controls:
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
# Response includes dynamic policy info when enabled
|
|
229
|
+
if result.policy_info.dynamic_policy_info.orchestrator_reachable:
|
|
230
|
+
print(f"Policies evaluated: {result.policy_info.dynamic_policy_info.policies_evaluated}")
|
|
231
|
+
for policy in result.policy_info.dynamic_policy_info.matched_policies:
|
|
232
|
+
print(f" {policy.policy_name}: {policy.action}")
|
|
233
|
+
|
|
234
|
+
# Enable: MCP_DYNAMIC_POLICIES_ENABLED=true
|
|
235
|
+
```
|
|
236
|
+
|
|
205
237
|
### Multi-Agent Planning
|
|
206
238
|
|
|
207
239
|
Generate and execute multi-agent plans:
|
|
@@ -227,9 +259,8 @@ from axonflow import AxonFlow, Mode, RetryConfig
|
|
|
227
259
|
|
|
228
260
|
client = AxonFlow(
|
|
229
261
|
endpoint="https://your-agent.axonflow.com",
|
|
230
|
-
client_id="your-client-id",
|
|
231
|
-
client_secret="your-client-secret",
|
|
232
|
-
license_key="optional-license-key", # For enterprise features
|
|
262
|
+
client_id="your-client-id", # Required for enterprise features
|
|
263
|
+
client_secret="your-client-secret", # Required for enterprise features
|
|
233
264
|
mode=Mode.PRODUCTION, # or Mode.SANDBOX
|
|
234
265
|
debug=True, # Enable debug logging
|
|
235
266
|
timeout=60.0, # Request timeout in seconds
|
|
@@ -305,12 +336,102 @@ ruff format .
|
|
|
305
336
|
mypy axonflow
|
|
306
337
|
```
|
|
307
338
|
|
|
339
|
+
## Examples
|
|
340
|
+
|
|
341
|
+
Complete working examples for all features are available in the [examples folder](https://github.com/getaxonflow/axonflow/tree/main/examples).
|
|
342
|
+
|
|
343
|
+
### Community Features
|
|
344
|
+
|
|
345
|
+
```python
|
|
346
|
+
# PII Detection - Automatically detect sensitive data
|
|
347
|
+
result = await client.get_policy_approved_context(
|
|
348
|
+
user_token="user-123",
|
|
349
|
+
query="My SSN is 123-45-6789"
|
|
350
|
+
)
|
|
351
|
+
# result.approved = True, result.requires_redaction = True (SSN detected)
|
|
352
|
+
|
|
353
|
+
# SQL Injection Detection - Block malicious queries
|
|
354
|
+
result = await client.get_policy_approved_context(
|
|
355
|
+
user_token="user-123",
|
|
356
|
+
query="SELECT * FROM users; DROP TABLE users;"
|
|
357
|
+
)
|
|
358
|
+
# result.approved = False, result.block_reason = "SQL injection detected"
|
|
359
|
+
|
|
360
|
+
# Static Policies - List and manage built-in policies
|
|
361
|
+
policies = await client.list_policies()
|
|
362
|
+
# Returns: [Policy(name="pii-detection", enabled=True), ...]
|
|
363
|
+
|
|
364
|
+
# Dynamic Policies - Create runtime policies
|
|
365
|
+
await client.create_dynamic_policy(
|
|
366
|
+
name="block-competitor-queries",
|
|
367
|
+
conditions={"contains": ["competitor", "pricing"]},
|
|
368
|
+
action="block"
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
# MCP Connectors - Query external data sources
|
|
372
|
+
resp = await client.query_connector(
|
|
373
|
+
user_token="user-123",
|
|
374
|
+
connector_name="postgres-db",
|
|
375
|
+
operation="query",
|
|
376
|
+
params={"sql": "SELECT name FROM customers"}
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
# Multi-Agent Planning - Orchestrate complex workflows
|
|
380
|
+
plan = await client.generate_plan(
|
|
381
|
+
query="Research AI governance regulations",
|
|
382
|
+
domain="legal"
|
|
383
|
+
)
|
|
384
|
+
result = await client.execute_plan(plan.plan_id)
|
|
385
|
+
|
|
386
|
+
# Audit Logging - Track all LLM interactions
|
|
387
|
+
await client.audit_llm_call(
|
|
388
|
+
context_id=ctx.context_id,
|
|
389
|
+
response_summary="AI response summary",
|
|
390
|
+
provider="openai",
|
|
391
|
+
model="gpt-4",
|
|
392
|
+
token_usage=TokenUsage(prompt_tokens=100, completion_tokens=200, total_tokens=300),
|
|
393
|
+
latency_ms=450
|
|
394
|
+
)
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Enterprise Features
|
|
398
|
+
|
|
399
|
+
These features require an AxonFlow Enterprise license:
|
|
400
|
+
|
|
401
|
+
```python
|
|
402
|
+
# Code Governance - Automated PR reviews with AI
|
|
403
|
+
pr_result = await client.review_pull_request(
|
|
404
|
+
repo_owner="your-org",
|
|
405
|
+
repo_name="your-repo",
|
|
406
|
+
pr_number=123,
|
|
407
|
+
check_types=["security", "style", "performance"]
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
# Cost Controls - Budget management for LLM usage
|
|
411
|
+
budget = await client.get_budget("team-engineering")
|
|
412
|
+
# Returns: Budget(limit=1000.00, used=234.56, remaining=765.44)
|
|
413
|
+
|
|
414
|
+
# MCP Policy Enforcement - Automatic PII redaction in connector responses
|
|
415
|
+
resp = await client.query_connector("user", "postgres", "SELECT * FROM customers", {})
|
|
416
|
+
# resp.policy_info.redacted = True
|
|
417
|
+
# resp.policy_info.redacted_fields = ["ssn", "credit_card"]
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
For enterprise features, contact [sales@getaxonflow.com](mailto:sales@getaxonflow.com).
|
|
421
|
+
|
|
308
422
|
## Documentation
|
|
309
423
|
|
|
310
424
|
- [Getting Started](https://docs.getaxonflow.com/sdk/python-getting-started)
|
|
311
425
|
- [Gateway Mode Guide](https://docs.getaxonflow.com/sdk/gateway-mode)
|
|
312
|
-
|
|
426
|
+
|
|
427
|
+
## Support
|
|
428
|
+
|
|
429
|
+
- **Documentation**: https://docs.getaxonflow.com
|
|
430
|
+
- **Issues**: https://github.com/getaxonflow/axonflow-sdk-python/issues
|
|
431
|
+
- **Email**: dev@getaxonflow.com
|
|
313
432
|
|
|
314
433
|
## License
|
|
315
434
|
|
|
316
435
|
MIT - See [LICENSE](LICENSE) for details.
|
|
436
|
+
|
|
437
|
+
<img referrerpolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=fbda6e64-1812-428b-b135-ed2b548ce50d" />
|
|
@@ -143,6 +143,38 @@ result = await client.query_connector(
|
|
|
143
143
|
)
|
|
144
144
|
```
|
|
145
145
|
|
|
146
|
+
### MCP Policy Features (v3.2.0)
|
|
147
|
+
|
|
148
|
+
**Exfiltration Detection** - Prevent large-scale data extraction:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
# Query with exfiltration limits (default: 10K rows, 10MB)
|
|
152
|
+
result = await client.query_connector(
|
|
153
|
+
user_token="user-jwt",
|
|
154
|
+
connector_name="postgres",
|
|
155
|
+
operation="query",
|
|
156
|
+
params={"sql": "SELECT * FROM customers"}
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# Check exfiltration info
|
|
160
|
+
if result.policy_info.exfiltration_check.exceeded:
|
|
161
|
+
print(f"Limit exceeded: {result.policy_info.exfiltration_check.limit_type}")
|
|
162
|
+
|
|
163
|
+
# Configure: MCP_MAX_ROWS_PER_QUERY=1000, MCP_MAX_BYTES_PER_QUERY=5242880
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Dynamic Policy Evaluation** - Orchestrator-based rate limiting, budget controls:
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
# Response includes dynamic policy info when enabled
|
|
170
|
+
if result.policy_info.dynamic_policy_info.orchestrator_reachable:
|
|
171
|
+
print(f"Policies evaluated: {result.policy_info.dynamic_policy_info.policies_evaluated}")
|
|
172
|
+
for policy in result.policy_info.dynamic_policy_info.matched_policies:
|
|
173
|
+
print(f" {policy.policy_name}: {policy.action}")
|
|
174
|
+
|
|
175
|
+
# Enable: MCP_DYNAMIC_POLICIES_ENABLED=true
|
|
176
|
+
```
|
|
177
|
+
|
|
146
178
|
### Multi-Agent Planning
|
|
147
179
|
|
|
148
180
|
Generate and execute multi-agent plans:
|
|
@@ -168,9 +200,8 @@ from axonflow import AxonFlow, Mode, RetryConfig
|
|
|
168
200
|
|
|
169
201
|
client = AxonFlow(
|
|
170
202
|
endpoint="https://your-agent.axonflow.com",
|
|
171
|
-
client_id="your-client-id",
|
|
172
|
-
client_secret="your-client-secret",
|
|
173
|
-
license_key="optional-license-key", # For enterprise features
|
|
203
|
+
client_id="your-client-id", # Required for enterprise features
|
|
204
|
+
client_secret="your-client-secret", # Required for enterprise features
|
|
174
205
|
mode=Mode.PRODUCTION, # or Mode.SANDBOX
|
|
175
206
|
debug=True, # Enable debug logging
|
|
176
207
|
timeout=60.0, # Request timeout in seconds
|
|
@@ -246,12 +277,102 @@ ruff format .
|
|
|
246
277
|
mypy axonflow
|
|
247
278
|
```
|
|
248
279
|
|
|
280
|
+
## Examples
|
|
281
|
+
|
|
282
|
+
Complete working examples for all features are available in the [examples folder](https://github.com/getaxonflow/axonflow/tree/main/examples).
|
|
283
|
+
|
|
284
|
+
### Community Features
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
# PII Detection - Automatically detect sensitive data
|
|
288
|
+
result = await client.get_policy_approved_context(
|
|
289
|
+
user_token="user-123",
|
|
290
|
+
query="My SSN is 123-45-6789"
|
|
291
|
+
)
|
|
292
|
+
# result.approved = True, result.requires_redaction = True (SSN detected)
|
|
293
|
+
|
|
294
|
+
# SQL Injection Detection - Block malicious queries
|
|
295
|
+
result = await client.get_policy_approved_context(
|
|
296
|
+
user_token="user-123",
|
|
297
|
+
query="SELECT * FROM users; DROP TABLE users;"
|
|
298
|
+
)
|
|
299
|
+
# result.approved = False, result.block_reason = "SQL injection detected"
|
|
300
|
+
|
|
301
|
+
# Static Policies - List and manage built-in policies
|
|
302
|
+
policies = await client.list_policies()
|
|
303
|
+
# Returns: [Policy(name="pii-detection", enabled=True), ...]
|
|
304
|
+
|
|
305
|
+
# Dynamic Policies - Create runtime policies
|
|
306
|
+
await client.create_dynamic_policy(
|
|
307
|
+
name="block-competitor-queries",
|
|
308
|
+
conditions={"contains": ["competitor", "pricing"]},
|
|
309
|
+
action="block"
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
# MCP Connectors - Query external data sources
|
|
313
|
+
resp = await client.query_connector(
|
|
314
|
+
user_token="user-123",
|
|
315
|
+
connector_name="postgres-db",
|
|
316
|
+
operation="query",
|
|
317
|
+
params={"sql": "SELECT name FROM customers"}
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
# Multi-Agent Planning - Orchestrate complex workflows
|
|
321
|
+
plan = await client.generate_plan(
|
|
322
|
+
query="Research AI governance regulations",
|
|
323
|
+
domain="legal"
|
|
324
|
+
)
|
|
325
|
+
result = await client.execute_plan(plan.plan_id)
|
|
326
|
+
|
|
327
|
+
# Audit Logging - Track all LLM interactions
|
|
328
|
+
await client.audit_llm_call(
|
|
329
|
+
context_id=ctx.context_id,
|
|
330
|
+
response_summary="AI response summary",
|
|
331
|
+
provider="openai",
|
|
332
|
+
model="gpt-4",
|
|
333
|
+
token_usage=TokenUsage(prompt_tokens=100, completion_tokens=200, total_tokens=300),
|
|
334
|
+
latency_ms=450
|
|
335
|
+
)
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Enterprise Features
|
|
339
|
+
|
|
340
|
+
These features require an AxonFlow Enterprise license:
|
|
341
|
+
|
|
342
|
+
```python
|
|
343
|
+
# Code Governance - Automated PR reviews with AI
|
|
344
|
+
pr_result = await client.review_pull_request(
|
|
345
|
+
repo_owner="your-org",
|
|
346
|
+
repo_name="your-repo",
|
|
347
|
+
pr_number=123,
|
|
348
|
+
check_types=["security", "style", "performance"]
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
# Cost Controls - Budget management for LLM usage
|
|
352
|
+
budget = await client.get_budget("team-engineering")
|
|
353
|
+
# Returns: Budget(limit=1000.00, used=234.56, remaining=765.44)
|
|
354
|
+
|
|
355
|
+
# MCP Policy Enforcement - Automatic PII redaction in connector responses
|
|
356
|
+
resp = await client.query_connector("user", "postgres", "SELECT * FROM customers", {})
|
|
357
|
+
# resp.policy_info.redacted = True
|
|
358
|
+
# resp.policy_info.redacted_fields = ["ssn", "credit_card"]
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
For enterprise features, contact [sales@getaxonflow.com](mailto:sales@getaxonflow.com).
|
|
362
|
+
|
|
249
363
|
## Documentation
|
|
250
364
|
|
|
251
365
|
- [Getting Started](https://docs.getaxonflow.com/sdk/python-getting-started)
|
|
252
366
|
- [Gateway Mode Guide](https://docs.getaxonflow.com/sdk/gateway-mode)
|
|
253
|
-
|
|
367
|
+
|
|
368
|
+
## Support
|
|
369
|
+
|
|
370
|
+
- **Documentation**: https://docs.getaxonflow.com
|
|
371
|
+
- **Issues**: https://github.com/getaxonflow/axonflow-sdk-python/issues
|
|
372
|
+
- **Email**: dev@getaxonflow.com
|
|
254
373
|
|
|
255
374
|
## License
|
|
256
375
|
|
|
257
376
|
MIT - See [LICENSE](LICENSE) for details.
|
|
377
|
+
|
|
378
|
+
<img referrerpolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=fbda6e64-1812-428b-b135-ed2b548ce50d" />
|
|
@@ -24,6 +24,7 @@ Example:
|
|
|
24
24
|
from __future__ import annotations
|
|
25
25
|
|
|
26
26
|
import asyncio
|
|
27
|
+
import base64
|
|
27
28
|
import concurrent.futures
|
|
28
29
|
import contextlib
|
|
29
30
|
import hashlib
|
|
@@ -63,6 +64,7 @@ from axonflow.exceptions import (
|
|
|
63
64
|
AuthenticationError,
|
|
64
65
|
AxonFlowError,
|
|
65
66
|
ConnectionError,
|
|
67
|
+
ConnectorError,
|
|
66
68
|
PolicyViolationError,
|
|
67
69
|
TimeoutError,
|
|
68
70
|
)
|
|
@@ -102,6 +104,7 @@ from axonflow.types import (
|
|
|
102
104
|
ConnectorHealthStatus,
|
|
103
105
|
ConnectorInstallRequest,
|
|
104
106
|
ConnectorMetadata,
|
|
107
|
+
ConnectorPolicyInfo,
|
|
105
108
|
ConnectorResponse,
|
|
106
109
|
CreateBudgetRequest,
|
|
107
110
|
ExecutionDetail,
|
|
@@ -182,7 +185,6 @@ class AxonFlow:
|
|
|
182
185
|
client_id: str | None = None,
|
|
183
186
|
client_secret: str | None = None,
|
|
184
187
|
*,
|
|
185
|
-
license_key: str | None = None,
|
|
186
188
|
mode: Mode | str = Mode.PRODUCTION,
|
|
187
189
|
debug: bool = False,
|
|
188
190
|
timeout: float = 60.0,
|
|
@@ -199,7 +201,6 @@ class AxonFlow:
|
|
|
199
201
|
endpoint: AxonFlow endpoint URL. Can also be set via AXONFLOW_AGENT_URL env var.
|
|
200
202
|
client_id: Client ID (optional for community/self-hosted mode)
|
|
201
203
|
client_secret: Client secret (optional for community/self-hosted mode)
|
|
202
|
-
license_key: Optional license key for organization-level auth
|
|
203
204
|
mode: Operation mode (production or sandbox)
|
|
204
205
|
debug: Enable debug logging
|
|
205
206
|
timeout: Request timeout in seconds
|
|
@@ -230,7 +231,6 @@ class AxonFlow:
|
|
|
230
231
|
endpoint=resolved_endpoint.rstrip("/"),
|
|
231
232
|
client_id=client_id,
|
|
232
233
|
client_secret=client_secret,
|
|
233
|
-
license_key=license_key,
|
|
234
234
|
mode=mode,
|
|
235
235
|
debug=debug,
|
|
236
236
|
timeout=timeout,
|
|
@@ -247,14 +247,16 @@ class AxonFlow:
|
|
|
247
247
|
headers: dict[str, str] = {
|
|
248
248
|
"Content-Type": "application/json",
|
|
249
249
|
}
|
|
250
|
-
# Add authentication
|
|
251
|
-
#
|
|
252
|
-
|
|
253
|
-
headers["X-Client-Secret"] = client_secret
|
|
250
|
+
# Add authentication and tenant headers
|
|
251
|
+
# client_id is always required for policy APIs (sets X-Tenant-ID)
|
|
252
|
+
# client_secret is optional for community mode but required for enterprise
|
|
254
253
|
if client_id:
|
|
255
254
|
headers["X-Tenant-ID"] = client_id # client_id is used as tenant ID for policy APIs
|
|
256
|
-
|
|
257
|
-
|
|
255
|
+
# OAuth2-style: Authorization: Basic base64(clientId:clientSecret)
|
|
256
|
+
if client_secret:
|
|
257
|
+
credentials = f"{client_id}:{client_secret}"
|
|
258
|
+
encoded = base64.b64encode(credentials.encode()).decode()
|
|
259
|
+
headers["Authorization"] = f"Basic {encoded}"
|
|
258
260
|
|
|
259
261
|
# Initialize HTTP client
|
|
260
262
|
self._http_client = httpx.AsyncClient(
|
|
@@ -298,27 +300,28 @@ class AxonFlow:
|
|
|
298
300
|
def _has_credentials(self) -> bool:
|
|
299
301
|
"""Check if credentials are configured.
|
|
300
302
|
|
|
301
|
-
Returns True if
|
|
302
|
-
|
|
303
|
-
but required for enterprise features like Gateway Mode.
|
|
303
|
+
Returns True if client_id is set.
|
|
304
|
+
client_secret is optional for community mode but required for enterprise.
|
|
304
305
|
"""
|
|
305
|
-
return bool(self._config.
|
|
306
|
+
return bool(self._config.client_id)
|
|
306
307
|
|
|
307
308
|
def _require_credentials(self, feature: str) -> None:
|
|
308
309
|
"""Require credentials for enterprise features.
|
|
309
310
|
|
|
310
|
-
Raises AuthenticationError if
|
|
311
|
+
Raises AuthenticationError if client_id is not configured.
|
|
312
|
+
Note: client_secret is optional for community mode.
|
|
311
313
|
|
|
312
314
|
Args:
|
|
313
315
|
feature: Name of the feature requiring credentials (for error message)
|
|
314
316
|
|
|
315
317
|
Raises:
|
|
316
|
-
AuthenticationError: If
|
|
318
|
+
AuthenticationError: If client_id is not configured
|
|
317
319
|
"""
|
|
318
320
|
if not self._has_credentials():
|
|
319
321
|
msg = (
|
|
320
|
-
f"{feature} requires
|
|
321
|
-
"Set
|
|
322
|
+
f"{feature} requires client_id. "
|
|
323
|
+
"Set client_id when creating the client "
|
|
324
|
+
"(client_secret is optional for community mode)."
|
|
322
325
|
)
|
|
323
326
|
raise AuthenticationError(msg)
|
|
324
327
|
|
|
@@ -362,19 +365,20 @@ class AxonFlow:
|
|
|
362
365
|
return SyncAxonFlow(cls(endpoint, client_id, client_secret, **kwargs))
|
|
363
366
|
|
|
364
367
|
@classmethod
|
|
365
|
-
def sandbox(cls,
|
|
368
|
+
def sandbox(cls, client_id: str = "demo-key", client_secret: str = "demo-key") -> AxonFlow: # noqa: S107
|
|
366
369
|
"""Create a sandbox client for testing.
|
|
367
370
|
|
|
368
371
|
Args:
|
|
369
|
-
|
|
372
|
+
client_id: Optional client ID (defaults to demo-key)
|
|
373
|
+
client_secret: Optional client secret (defaults to demo-key)
|
|
370
374
|
|
|
371
375
|
Returns:
|
|
372
376
|
Configured AxonFlow client for sandbox environment
|
|
373
377
|
"""
|
|
374
378
|
return cls(
|
|
375
379
|
endpoint="https://staging-eu.getaxonflow.com",
|
|
376
|
-
client_id=
|
|
377
|
-
client_secret=
|
|
380
|
+
client_id=client_id,
|
|
381
|
+
client_secret=client_secret,
|
|
378
382
|
mode=Mode.SANDBOX,
|
|
379
383
|
debug=True,
|
|
380
384
|
)
|
|
@@ -543,7 +547,8 @@ class AxonFlow:
|
|
|
543
547
|
"""Execute a query through AxonFlow with policy enforcement.
|
|
544
548
|
|
|
545
549
|
Args:
|
|
546
|
-
user_token: User authentication token
|
|
550
|
+
user_token: User authentication token. If empty, defaults to "anonymous"
|
|
551
|
+
for audit purposes (community mode).
|
|
547
552
|
query: The query or prompt
|
|
548
553
|
request_type: Type of request (chat, sql, mcp-query, multi-agent-plan)
|
|
549
554
|
context: Optional additional context
|
|
@@ -556,6 +561,10 @@ class AxonFlow:
|
|
|
556
561
|
AuthenticationError: If credentials are invalid
|
|
557
562
|
TimeoutError: If request times out
|
|
558
563
|
"""
|
|
564
|
+
# Default to "anonymous" if user_token is empty (community mode)
|
|
565
|
+
if not user_token:
|
|
566
|
+
user_token = "anonymous" # noqa: S105 - not a password, just a placeholder
|
|
567
|
+
|
|
559
568
|
# Check cache
|
|
560
569
|
if self._cache is not None:
|
|
561
570
|
cache_key = self._get_cache_key(request_type, query, user_token)
|
|
@@ -743,6 +752,98 @@ class AxonFlow:
|
|
|
743
752
|
},
|
|
744
753
|
)
|
|
745
754
|
|
|
755
|
+
async def mcp_query(
|
|
756
|
+
self,
|
|
757
|
+
connector: str,
|
|
758
|
+
statement: str,
|
|
759
|
+
options: dict[str, Any] | None = None,
|
|
760
|
+
) -> ConnectorResponse:
|
|
761
|
+
"""Execute a query directly against the MCP connector endpoint.
|
|
762
|
+
|
|
763
|
+
This method calls the agent's /mcp/resources/query endpoint which provides:
|
|
764
|
+
- Request-phase policy evaluation (SQLi blocking, PII blocking)
|
|
765
|
+
- Response-phase policy evaluation (PII redaction)
|
|
766
|
+
- PolicyInfo metadata in responses
|
|
767
|
+
|
|
768
|
+
Args:
|
|
769
|
+
connector: Name of the MCP connector (e.g., "postgres")
|
|
770
|
+
statement: SQL statement or query to execute
|
|
771
|
+
options: Optional additional options for the query
|
|
772
|
+
|
|
773
|
+
Returns:
|
|
774
|
+
ConnectorResponse with data, redaction info, and policy_info
|
|
775
|
+
|
|
776
|
+
Raises:
|
|
777
|
+
ConnectorError: If the request is blocked by policy or fails
|
|
778
|
+
|
|
779
|
+
Example:
|
|
780
|
+
response = await client.mcp_query(
|
|
781
|
+
connector="postgres",
|
|
782
|
+
statement="SELECT * FROM customers LIMIT 10"
|
|
783
|
+
)
|
|
784
|
+
if response.redacted:
|
|
785
|
+
print(f"Redacted fields: {response.redacted_fields}")
|
|
786
|
+
"""
|
|
787
|
+
if not connector:
|
|
788
|
+
msg = "connector name is required"
|
|
789
|
+
raise ConnectorError(msg, connector=None, operation="mcp_query")
|
|
790
|
+
if not statement:
|
|
791
|
+
msg = "statement is required"
|
|
792
|
+
raise ConnectorError(msg, connector=connector, operation="mcp_query")
|
|
793
|
+
|
|
794
|
+
url = f"{self._config.endpoint}/mcp/resources/query"
|
|
795
|
+
body = {
|
|
796
|
+
"connector": connector,
|
|
797
|
+
"statement": statement,
|
|
798
|
+
"options": options or {},
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
if self._config.debug:
|
|
802
|
+
self._logger.debug("MCP Query", connector=connector, statement=statement[:50])
|
|
803
|
+
|
|
804
|
+
response = await self._http_client.post(url, json=body)
|
|
805
|
+
response_data = response.json()
|
|
806
|
+
|
|
807
|
+
# Handle policy blocks (403 responses)
|
|
808
|
+
if not response.is_success:
|
|
809
|
+
error_msg = response_data.get("error", f"MCP query failed: {response.status_code}")
|
|
810
|
+
raise ConnectorError(error_msg, connector=connector, operation="mcp_query")
|
|
811
|
+
|
|
812
|
+
if self._config.debug:
|
|
813
|
+
self._logger.debug(
|
|
814
|
+
"MCP Query result",
|
|
815
|
+
connector=connector,
|
|
816
|
+
success=response_data.get("success"),
|
|
817
|
+
redacted=response_data.get("redacted"),
|
|
818
|
+
)
|
|
819
|
+
|
|
820
|
+
# Build policy_info if present
|
|
821
|
+
policy_info = None
|
|
822
|
+
if response_data.get("policy_info"):
|
|
823
|
+
policy_info = ConnectorPolicyInfo.model_validate(response_data["policy_info"])
|
|
824
|
+
|
|
825
|
+
return ConnectorResponse(
|
|
826
|
+
success=response_data.get("success", True),
|
|
827
|
+
data=response_data.get("data"),
|
|
828
|
+
error=response_data.get("error"),
|
|
829
|
+
meta=response_data.get("meta", {}),
|
|
830
|
+
redacted=response_data.get("redacted", False),
|
|
831
|
+
redacted_fields=response_data.get("redacted_fields", []),
|
|
832
|
+
policy_info=policy_info,
|
|
833
|
+
)
|
|
834
|
+
|
|
835
|
+
async def mcp_execute(
|
|
836
|
+
self,
|
|
837
|
+
connector: str,
|
|
838
|
+
statement: str,
|
|
839
|
+
options: dict[str, Any] | None = None,
|
|
840
|
+
) -> ConnectorResponse:
|
|
841
|
+
"""Execute a statement against an MCP connector (alias for mcp_query).
|
|
842
|
+
|
|
843
|
+
Same as mcp_query but follows the naming convention of other execute* methods.
|
|
844
|
+
"""
|
|
845
|
+
return await self.mcp_query(connector, statement, options)
|
|
846
|
+
|
|
746
847
|
async def generate_plan(
|
|
747
848
|
self,
|
|
748
849
|
query: str,
|
|
@@ -886,7 +987,7 @@ class AxonFlow:
|
|
|
886
987
|
Returns:
|
|
887
988
|
PlanExecutionResponse with current status
|
|
888
989
|
"""
|
|
889
|
-
response = await self._request("GET", f"/api/
|
|
990
|
+
response = await self._request("GET", f"/api/v1/plan/{plan_id}")
|
|
890
991
|
return PlanExecutionResponse.model_validate(response)
|
|
891
992
|
|
|
892
993
|
# =========================================================================
|
|
@@ -907,7 +1008,7 @@ class AxonFlow:
|
|
|
907
1008
|
|
|
908
1009
|
Note:
|
|
909
1010
|
This is an enterprise feature that requires credentials.
|
|
910
|
-
Set
|
|
1011
|
+
Set client_id and client_secret when creating the client.
|
|
911
1012
|
|
|
912
1013
|
Args:
|
|
913
1014
|
user_token: JWT token for the user making the request
|
|
@@ -1036,7 +1137,7 @@ class AxonFlow:
|
|
|
1036
1137
|
|
|
1037
1138
|
Note:
|
|
1038
1139
|
This is an enterprise feature that requires credentials.
|
|
1039
|
-
Set
|
|
1140
|
+
Set client_id and client_secret when creating the client.
|
|
1040
1141
|
|
|
1041
1142
|
Args:
|
|
1042
1143
|
context_id: Context ID from get_policy_approved_context()
|
|
@@ -1731,7 +1832,7 @@ class AxonFlow:
|
|
|
1731
1832
|
self._logger.debug("Toggling dynamic policy", policy_id=policy_id, enabled=enabled)
|
|
1732
1833
|
|
|
1733
1834
|
response = await self._orchestrator_request(
|
|
1734
|
-
"
|
|
1835
|
+
"PUT",
|
|
1735
1836
|
f"/api/v1/dynamic-policies/{policy_id}",
|
|
1736
1837
|
json_data={"enabled": enabled},
|
|
1737
1838
|
)
|
|
@@ -1992,6 +2093,29 @@ class AxonFlow:
|
|
|
1992
2093
|
response = await self._portal_request("POST", f"/api/v1/code-governance/prs/{pr_id}/sync")
|
|
1993
2094
|
return PRRecord.model_validate(response)
|
|
1994
2095
|
|
|
2096
|
+
async def close_pr(self, pr_id: str, delete_branch: bool = True) -> PRRecord:
|
|
2097
|
+
"""Close a PR without merging and optionally delete the branch.
|
|
2098
|
+
|
|
2099
|
+
This is an enterprise feature for cleaning up test/demo PRs.
|
|
2100
|
+
Supports all Git providers: GitHub, GitLab, Bitbucket.
|
|
2101
|
+
|
|
2102
|
+
Args:
|
|
2103
|
+
pr_id: PR record ID
|
|
2104
|
+
delete_branch: Whether to delete the source branch (default: True)
|
|
2105
|
+
|
|
2106
|
+
Returns:
|
|
2107
|
+
Closed PR record
|
|
2108
|
+
"""
|
|
2109
|
+
if self._config.debug:
|
|
2110
|
+
self._logger.debug("Closing PR", pr_id=pr_id, delete_branch=delete_branch)
|
|
2111
|
+
|
|
2112
|
+
path = f"/api/v1/code-governance/prs/{pr_id}"
|
|
2113
|
+
if delete_branch:
|
|
2114
|
+
path += "?delete_branch=true"
|
|
2115
|
+
|
|
2116
|
+
response = await self._portal_request("DELETE", path)
|
|
2117
|
+
return PRRecord.model_validate(response)
|
|
2118
|
+
|
|
1995
2119
|
# =========================================================================
|
|
1996
2120
|
# Code Governance Metrics and Export
|
|
1997
2121
|
# =========================================================================
|
|
@@ -2796,7 +2920,10 @@ class SyncAxonFlow:
|
|
|
2796
2920
|
request_type: str,
|
|
2797
2921
|
context: dict[str, Any] | None = None,
|
|
2798
2922
|
) -> ClientResponse:
|
|
2799
|
-
"""Execute a query through AxonFlow.
|
|
2923
|
+
"""Execute a query through AxonFlow.
|
|
2924
|
+
|
|
2925
|
+
If user_token is empty, defaults to "anonymous" for audit purposes.
|
|
2926
|
+
"""
|
|
2800
2927
|
return self._run_sync(
|
|
2801
2928
|
self._async_client.execute_query(user_token, query, request_type, context)
|
|
2802
2929
|
)
|
|
@@ -2833,6 +2960,30 @@ class SyncAxonFlow:
|
|
|
2833
2960
|
self._async_client.query_connector(user_token, connector_name, operation, params)
|
|
2834
2961
|
)
|
|
2835
2962
|
|
|
2963
|
+
def mcp_query(
|
|
2964
|
+
self,
|
|
2965
|
+
connector: str,
|
|
2966
|
+
statement: str,
|
|
2967
|
+
options: dict[str, Any] | None = None,
|
|
2968
|
+
) -> ConnectorResponse:
|
|
2969
|
+
"""Execute a query directly against the MCP connector endpoint.
|
|
2970
|
+
|
|
2971
|
+
This method calls the agent's /mcp/resources/query endpoint which provides:
|
|
2972
|
+
- Request-phase policy evaluation (SQLi blocking, PII blocking)
|
|
2973
|
+
- Response-phase policy evaluation (PII redaction)
|
|
2974
|
+
- PolicyInfo metadata in responses
|
|
2975
|
+
"""
|
|
2976
|
+
return self._run_sync(self._async_client.mcp_query(connector, statement, options))
|
|
2977
|
+
|
|
2978
|
+
def mcp_execute(
|
|
2979
|
+
self,
|
|
2980
|
+
connector: str,
|
|
2981
|
+
statement: str,
|
|
2982
|
+
options: dict[str, Any] | None = None,
|
|
2983
|
+
) -> ConnectorResponse:
|
|
2984
|
+
"""Execute a statement against an MCP connector (alias for mcp_query)."""
|
|
2985
|
+
return self._run_sync(self._async_client.mcp_execute(connector, statement, options))
|
|
2986
|
+
|
|
2836
2987
|
def generate_plan(
|
|
2837
2988
|
self,
|
|
2838
2989
|
query: str,
|
|
@@ -3084,6 +3235,10 @@ class SyncAxonFlow:
|
|
|
3084
3235
|
"""Sync PR status with the Git provider."""
|
|
3085
3236
|
return self._run_sync(self._async_client.sync_pr_status(pr_id))
|
|
3086
3237
|
|
|
3238
|
+
def close_pr(self, pr_id: str, delete_branch: bool = True) -> PRRecord:
|
|
3239
|
+
"""Close a PR without merging and optionally delete the branch."""
|
|
3240
|
+
return self._run_sync(self._async_client.close_pr(pr_id, delete_branch))
|
|
3241
|
+
|
|
3087
3242
|
def get_code_governance_metrics(self) -> CodeGovernanceMetrics:
|
|
3088
3243
|
"""Get aggregated code governance metrics."""
|
|
3089
3244
|
return self._run_sync(self._async_client.get_code_governance_metrics())
|