aivil-python 1.0.0__py3-none-any.whl
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.
aivil/__init__.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from typing import Optional, Dict
|
|
3
|
+
|
|
4
|
+
AIVIL_API = "https://api.aivildev.com"
|
|
5
|
+
|
|
6
|
+
class AIVILError(Exception):
|
|
7
|
+
def __init__(self, message, code=None, status=None):
|
|
8
|
+
super().__init__(message)
|
|
9
|
+
self.code = code
|
|
10
|
+
self.status = status
|
|
11
|
+
|
|
12
|
+
class AIVILAuthError(AIVILError):
|
|
13
|
+
def __init__(self, message):
|
|
14
|
+
super().__init__(message, "AUTH_ERROR", 401)
|
|
15
|
+
|
|
16
|
+
class AIVILPlanError(AIVILError):
|
|
17
|
+
def __init__(self, message, upgrade_url=None):
|
|
18
|
+
super().__init__(message, "PLAN_LIMIT", 403)
|
|
19
|
+
self.upgrade_url = upgrade_url or "https://aivildev.com/pricing"
|
|
20
|
+
|
|
21
|
+
class AIVILTimeoutError(AIVILError):
|
|
22
|
+
def __init__(self, message):
|
|
23
|
+
super().__init__(message, "TIMEOUT", 408)
|
|
24
|
+
|
|
25
|
+
class AIVIL:
|
|
26
|
+
def __init__(self, api_key, api_url=AIVIL_API, timeout=5, fallback_behavior="escalate", retries=2, debug=False):
|
|
27
|
+
if not api_key:
|
|
28
|
+
raise AIVILError("api_key is required. Sign up at aivildev.com/signup")
|
|
29
|
+
self.api_key = api_key
|
|
30
|
+
self.api_url = api_url
|
|
31
|
+
self.timeout = timeout
|
|
32
|
+
self.fallback_behavior = fallback_behavior
|
|
33
|
+
self.retries = retries
|
|
34
|
+
self.debug = debug
|
|
35
|
+
|
|
36
|
+
def _log(self, msg):
|
|
37
|
+
if self.debug:
|
|
38
|
+
print(f"[AIVIL] {msg}")
|
|
39
|
+
|
|
40
|
+
def _request(self, method, path, body=None, attempt=0):
|
|
41
|
+
try:
|
|
42
|
+
r = requests.request(method, f"{self.api_url}{path}",
|
|
43
|
+
headers={"Content-Type": "application/json", "Authorization": f"Bearer {self.api_key}"},
|
|
44
|
+
json=body, timeout=self.timeout)
|
|
45
|
+
data = r.json()
|
|
46
|
+
if r.status_code == 401: raise AIVILAuthError(data.get("error", "Invalid API key"))
|
|
47
|
+
if r.status_code == 403: raise AIVILPlanError(data.get("error", "Plan limit"), data.get("upgrade_url"))
|
|
48
|
+
if not r.ok: raise AIVILError(data.get("error", f"Error {r.status_code}"))
|
|
49
|
+
return data
|
|
50
|
+
except requests.Timeout:
|
|
51
|
+
if attempt < self.retries:
|
|
52
|
+
import time; time.sleep(1)
|
|
53
|
+
return self._request(method, path, body, attempt + 1)
|
|
54
|
+
raise AIVILTimeoutError(f"Timed out after {self.timeout}s")
|
|
55
|
+
except requests.ConnectionError as e:
|
|
56
|
+
if attempt < self.retries:
|
|
57
|
+
import time; time.sleep(1)
|
|
58
|
+
return self._request(method, path, body, attempt + 1)
|
|
59
|
+
raise AIVILError(str(e), "CONNECTION_ERROR")
|
|
60
|
+
|
|
61
|
+
def create_agent(self, config):
|
|
62
|
+
for f in ["name","role","owner","jurisdiction"]:
|
|
63
|
+
if f not in config: raise AIVILError(f"create_agent requires '{f}'")
|
|
64
|
+
return self._request("POST", "/agents", config)
|
|
65
|
+
|
|
66
|
+
def audit(self, agent_id, action, fallback=None):
|
|
67
|
+
fallback = fallback or self.fallback_behavior
|
|
68
|
+
try:
|
|
69
|
+
return self._request("POST", f"/agents/{agent_id}/audit", {"action": action})["verdict"]
|
|
70
|
+
except (AIVILTimeoutError, AIVILError) as e:
|
|
71
|
+
if e.code in ("TIMEOUT", "CONNECTION_ERROR"):
|
|
72
|
+
import warnings
|
|
73
|
+
warnings.warn(f"[AIVIL] Unreachable. Fallback: {fallback}")
|
|
74
|
+
return {"status": fallback.upper(), "reason": "AIVIL unreachable", "flags": ["AIVIL_FALLBACK"], "agent_id": agent_id, "fallback": True}
|
|
75
|
+
raise
|
|
76
|
+
|
|
77
|
+
def list_agents(self):
|
|
78
|
+
return self._request("GET", "/agents")["agents"]
|
|
79
|
+
|
|
80
|
+
def get_agent(self, agent_id):
|
|
81
|
+
return self._request("GET", f"/agents/{agent_id}")["agent"]
|
|
82
|
+
|
|
83
|
+
def find_agent(self, name):
|
|
84
|
+
return next((a for a in self.list_agents() if a["name"] == name), None)
|
|
85
|
+
|
|
86
|
+
def get_or_create(self, config):
|
|
87
|
+
existing = self.find_agent(config["name"])
|
|
88
|
+
if existing:
|
|
89
|
+
return {"agent": existing, "created": False}
|
|
90
|
+
data = self.create_agent(config)
|
|
91
|
+
return {**data, "created": True}
|
|
92
|
+
|
|
93
|
+
def update_policy(self, agent_id, policy, reason=""):
|
|
94
|
+
return self._request("PATCH", f"/agents/{agent_id}/policy", {"policy": policy, "change_reason": reason})
|
|
95
|
+
|
|
96
|
+
def suspend(self, agent_id, reason=""):
|
|
97
|
+
return self._request("POST", f"/agents/{agent_id}/suspend", {"reason": reason})
|
|
98
|
+
|
|
99
|
+
def reactivate(self, agent_id, reason=""):
|
|
100
|
+
return self._request("POST", f"/agents/{agent_id}/reactivate", {"reason": reason})
|
|
101
|
+
|
|
102
|
+
def retire(self, agent_id, reason=""):
|
|
103
|
+
return self._request("POST", f"/agents/{agent_id}/retire", {"reason": reason})
|
|
104
|
+
|
|
105
|
+
def get_audit_log(self, agent_id, limit=50):
|
|
106
|
+
return self._request("GET", f"/agents/{agent_id}/audit?limit={limit}")["logs"]
|
|
107
|
+
|
|
108
|
+
def verify(self, agent_id):
|
|
109
|
+
return requests.get(f"{self.api_url}/verify/{agent_id}", timeout=self.timeout).json()
|
|
110
|
+
|
|
111
|
+
def get_stats(self):
|
|
112
|
+
return self._request("GET", "/stats")
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: aivil-python
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: The AI Vital Identity Layer — civil registry for AI agents
|
|
5
|
+
Home-page: https://aivildev.com
|
|
6
|
+
Author: AIVIL
|
|
7
|
+
Author-email: ihimanshu882@gmail.com
|
|
8
|
+
Keywords: ai,agents,identity,policy,governance
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Requires-Dist: requests >=2.28.0
|
|
11
|
+
|
|
12
|
+
Full docs at https://aivildev.com/docs
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
aivil/__init__.py,sha256=TPHNwqxk0XNHt5_MsL1SKmgHMxMfupFQNnmxCXM5UhE,4707
|
|
2
|
+
aivil_python-1.0.0.dist-info/METADATA,sha256=lTYbA51-t-4JXiDUm9gHgPVgC2-TE_c6WVfUZezsr70,352
|
|
3
|
+
aivil_python-1.0.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
4
|
+
aivil_python-1.0.0.dist-info/top_level.txt,sha256=dMhostO7-swcoTACfzni6e_z-WnK4pvOTo3ExRxhX48,6
|
|
5
|
+
aivil_python-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
aivil
|