swiftapi-python 1.2.2__py3-none-any.whl → 1.3.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.
swiftapi/__init__.py CHANGED
@@ -1,96 +1,75 @@
1
- """
2
- SwiftAPI Python SDK
3
-
4
- No AI action executes without verification.
5
-
6
- Usage:
7
- from swiftapi import SwiftAPI, Enforcement
8
-
9
- # Initialize client
10
- api = SwiftAPI(key="swiftapi_live_...")
11
-
12
- # Create enforcement point
13
- guard = Enforcement(api)
14
-
15
- # Protect dangerous operations
16
- guard.run(
17
- lambda: os.system("rm -rf /tmp/data"),
18
- action="file_delete",
19
- intent="Cleanup temporary data"
20
- )
21
-
22
- # Or use decorator
23
- @guard.protect(action="api_call", intent="Send email")
24
- def send_email(to, subject, body):
25
- ...
26
-
27
- # Or use context manager
28
- with guard.guard(action="database_write", intent="Update user"):
29
- db.update(user_id, data)
30
- """
31
-
32
- __version__ = "1.2.2"
33
- __author__ = "Rayan Pal"
34
-
35
- # Core exports
36
- from .client import SwiftAPI
37
- from .enforcement import Enforcement, enforce
38
- from .verifier import verify_signature, is_valid, get_public_key
39
- from .openai import OpenAI
40
- from .anthropic import Anthropic
41
-
42
- # Exceptions
43
- from .exceptions import (
44
- SwiftAPIError,
45
- AuthenticationError,
46
- PolicyViolation,
47
- SignatureVerificationError,
48
- AttestationExpiredError,
49
- AttestationRevokedError,
50
- RateLimitError,
51
- NetworkError,
52
- )
53
-
54
- # UX utilities
55
- from .utils import (
56
- Colors,
57
- Symbols,
58
- print_approved,
59
- print_denied,
60
- print_verified,
61
- print_error,
62
- print_info,
63
- )
64
-
65
- __all__ = [
66
- # Version
67
- "__version__",
68
- # Core
69
- "SwiftAPI",
70
- "Enforcement",
71
- "enforce",
72
- # Drop-in replacements
73
- "OpenAI",
74
- "Anthropic",
75
- # Verification
76
- "verify_signature",
77
- "is_valid",
78
- "get_public_key",
79
- # Exceptions
80
- "SwiftAPIError",
81
- "AuthenticationError",
82
- "PolicyViolation",
83
- "SignatureVerificationError",
84
- "AttestationExpiredError",
85
- "AttestationRevokedError",
86
- "RateLimitError",
87
- "NetworkError",
88
- # UX
89
- "Colors",
90
- "Symbols",
91
- "print_approved",
92
- "print_denied",
93
- "print_verified",
94
- "print_error",
95
- "print_info",
96
- ]
1
+ """SwiftAPI: verified execution for explicitly mediated capabilities."""
2
+ __version__ = "1.3.0"
3
+ __author__ = "Rayan Pal"
4
+
5
+ # Core exports
6
+ from .client import SwiftAPI
7
+ from .enforcement import Enforcement, enforce
8
+ from .verifier import verify_signature, is_valid, get_public_key
9
+ from .openai import OpenAI
10
+ from .anthropic import Anthropic
11
+
12
+ # Exceptions
13
+ from .exceptions import (
14
+ SwiftAPIError,
15
+ AuthenticationError,
16
+ PolicyViolation,
17
+ SignatureVerificationError,
18
+ AttestationExpiredError,
19
+ AttestationRevokedError,
20
+ RateLimitError,
21
+ NetworkError,
22
+ )
23
+
24
+ # UX utilities
25
+ from .utils import (
26
+ Colors,
27
+ Symbols,
28
+ print_approved,
29
+ print_denied,
30
+ print_verified,
31
+ print_error,
32
+ print_info,
33
+ )
34
+
35
+ __all__ = [
36
+ # Version
37
+ "__version__",
38
+ # Core
39
+ "SwiftAPI",
40
+ "Enforcement",
41
+ "enforce",
42
+ # Drop-in replacements
43
+ "OpenAI",
44
+ "Anthropic",
45
+ # Verification
46
+ "verify_signature",
47
+ "is_valid",
48
+ "get_public_key",
49
+ # Exceptions
50
+ "SwiftAPIError",
51
+ "AuthenticationError",
52
+ "PolicyViolation",
53
+ "SignatureVerificationError",
54
+ "AttestationExpiredError",
55
+ "AttestationRevokedError",
56
+ "RateLimitError",
57
+ "NetworkError",
58
+ # UX
59
+ "Colors",
60
+ "Symbols",
61
+ "print_approved",
62
+ "print_denied",
63
+ "print_verified",
64
+ "print_error",
65
+ "print_info",
66
+ ]
67
+
68
+ from .brake import (RemoteAuthority, ActionEnvelope, BindingCondition, ContinuationAuthority, Decision,
69
+ DecisionReceipt, LocalAuthority, LocalGuard, CapabilityBroker,
70
+ AuthorizationDenied, ReplayError, verify_receipt)
71
+ __all__ += ["ActionEnvelope", "BindingCondition", "ContinuationAuthority", "Decision",
72
+ "DecisionReceipt", "LocalAuthority", "LocalGuard", "CapabilityBroker",
73
+ "AuthorizationDenied", "ReplayError", "verify_receipt"]
74
+
75
+ __all__ += ["RemoteAuthority"]
swiftapi/__main__.py ADDED
@@ -0,0 +1,2 @@
1
+ from .cli import main
2
+ raise SystemExit(main())
swiftapi/anthropic.py CHANGED
@@ -1,222 +1,236 @@
1
- """
2
- SwiftAPI Attested Anthropic - Drop-in replacement.
3
-
4
- # Before (vibe coding):
5
- from anthropic import Anthropic
6
- client = Anthropic(api_key="sk-ant-...")
7
-
8
- # After (attested):
9
- from swiftapi import Anthropic
10
- client = Anthropic(swiftapi_key="swiftapi_live_...", anthropic_key="sk-ant-...")
11
-
12
- Same interface. Same return type. Every completion is cryptographically attested.
13
- No attestation, no output. SwiftAPI governs. Anthropic executes. You bring both keys.
14
- """
15
-
16
- import json
17
- import requests
18
- from typing import List, Dict, Optional, Any
19
-
20
- from . import __version__
21
- from .exceptions import SwiftAPIError, AuthenticationError, NetworkError
22
-
23
-
24
- class ContentBlock:
25
- """Anthropic-compatible content block."""
26
-
27
- def __init__(self, text: str, type: str = "text"):
28
- self.text = text
29
- self.type = type
30
-
31
- def __repr__(self):
32
- truncated = self.text[:80] if self.text else ""
33
- return f"ContentBlock(type='{self.type}', text='{truncated}')"
34
-
35
-
36
- class MessageResponse:
37
- """Anthropic-compatible message response."""
38
-
39
- def __init__(
40
- self,
41
- content: str,
42
- model: str,
43
- role: str = "assistant",
44
- stop_reason: str = None,
45
- attested: bool = False,
46
- jti: str = None,
47
- ):
48
- self.content = [ContentBlock(text=content)] if content else [ContentBlock(text="")]
49
- self.model = model
50
- self.role = role
51
- self.stop_reason = stop_reason or ("end_turn" if content else "void")
52
- self.attested = attested
53
- self.jti = jti
54
-
55
- @property
56
- def text(self):
57
- """Convenience: response.text instead of response.content[0].text"""
58
- return self.content[0].text if self.content else ""
59
-
60
-
61
- class Messages:
62
- """Handles messages.create() calls."""
63
-
64
- def __init__(self, swiftapi_key: str, anthropic_key: str, authority_url: str, timeout: int):
65
- self._swiftapi_key = swiftapi_key
66
- self._anthropic_key = anthropic_key
67
- self._authority_url = authority_url.rstrip("/")
68
- self._timeout = timeout
69
- self._session = requests.Session()
70
-
71
- def create(
72
- self,
73
- model: str,
74
- messages: List[Dict[str, Any]],
75
- max_tokens: int = 1024,
76
- temperature: Optional[float] = None,
77
- system: Optional[str] = None,
78
- **kwargs,
79
- ) -> MessageResponse:
80
- """
81
- Create an attested message completion.
82
-
83
- Same interface as anthropic.messages.create().
84
- Step 1: Attest with SwiftAPI (governance).
85
- Step 2: Call Anthropic directly with your key (execution).
86
-
87
- If governance denies, content is empty string. Void.
88
-
89
- Args:
90
- model: Model name (e.g. "claude-sonnet-4-5-20250929")
91
- messages: List of message dicts with role and content
92
- max_tokens: Max tokens in response
93
- temperature: Sampling temperature
94
- system: System prompt
95
-
96
- Returns:
97
- MessageResponse with .content[0].text
98
- """
99
- payload = {
100
- "model": model,
101
- "messages": messages,
102
- "max_tokens": max_tokens,
103
- }
104
- if temperature is not None:
105
- payload["temperature"] = temperature
106
- if system is not None:
107
- payload["system"] = system
108
-
109
- # Step 1: Attest with SwiftAPI — governance check
110
- try:
111
- attest_resp = self._session.post(
112
- f"{self._authority_url}/attest",
113
- json={"action_type": "chat_completion", "action_data": payload},
114
- headers={
115
- "X-SwiftAPI-Authority": self._swiftapi_key,
116
- "Content-Type": "application/json",
117
- "User-Agent": f"swiftapi-python/{__version__}",
118
- },
119
- timeout=self._timeout,
120
- )
121
- except requests.exceptions.RequestException as e:
122
- raise NetworkError(f"Attestation request failed: {e}")
123
-
124
- if attest_resp.status_code == 403:
125
- raise AuthenticationError("Invalid SwiftAPI authority key")
126
- if attest_resp.status_code == 429:
127
- raise SwiftAPIError("Rate limit exceeded")
128
- if attest_resp.status_code != 200:
129
- raise SwiftAPIError(f"Attestation failed: {attest_resp.text[:200]}")
130
-
131
- attestation = attest_resp.json()
132
-
133
- if not attestation.get("approved"):
134
- return MessageResponse(content="", model=model)
135
-
136
- # Step 2: Call Anthropic directly — user's own key, user's own credits
137
- anthropic_payload = {
138
- "model": model,
139
- "messages": messages,
140
- "max_tokens": max_tokens,
141
- }
142
- if temperature is not None:
143
- anthropic_payload["temperature"] = temperature
144
- if system is not None:
145
- anthropic_payload["system"] = system
146
- anthropic_payload.update(kwargs)
147
-
148
- try:
149
- anthropic_resp = self._session.post(
150
- "https://api.anthropic.com/v1/messages",
151
- json=anthropic_payload,
152
- headers={
153
- "x-api-key": self._anthropic_key,
154
- "anthropic-version": "2023-06-01",
155
- "Content-Type": "application/json",
156
- "User-Agent": f"swiftapi-python/{__version__}",
157
- },
158
- timeout=self._timeout,
159
- )
160
- except requests.exceptions.RequestException as e:
161
- raise NetworkError(f"Anthropic request failed: {e}")
162
-
163
- if anthropic_resp.status_code == 401:
164
- raise AuthenticationError("Invalid Anthropic API key")
165
- if anthropic_resp.status_code != 200:
166
- raise SwiftAPIError(f"Anthropic error: {anthropic_resp.text[:200]}")
167
-
168
- data = anthropic_resp.json()
169
- content_blocks = data.get("content", [])
170
- text = content_blocks[0].get("text", "") if content_blocks else ""
171
-
172
- return MessageResponse(
173
- content=text,
174
- model=model,
175
- role=data.get("role", "assistant"),
176
- stop_reason=data.get("stop_reason"),
177
- attested=True,
178
- jti=attestation.get("jti"),
179
- )
180
-
181
-
182
- class Anthropic:
183
- """
184
- Drop-in replacement for anthropic.Anthropic.
185
-
186
- Every completion is cryptographically attested via SwiftAPI.
187
- SwiftAPI governs. Anthropic executes. You bring both keys.
188
-
189
- Usage:
190
- from swiftapi import Anthropic
191
-
192
- client = Anthropic(
193
- swiftapi_key="swiftapi_live_...",
194
- anthropic_key="sk-ant-..."
195
- )
196
- r = client.messages.create(
197
- model="claude-sonnet-4-5-20250929",
198
- max_tokens=1024,
199
- messages=[{"role": "user", "content": "Hello"}]
200
- )
201
- print(r.content[0].text)
202
- """
203
-
204
- def __init__(
205
- self,
206
- swiftapi_key: str = None,
207
- anthropic_key: str = None,
208
- api_key: str = None,
209
- authority_url: str = "https://swiftapi.ai",
210
- timeout: int = 60,
211
- **kwargs,
212
- ):
213
- if not swiftapi_key:
214
- raise AuthenticationError("swiftapi_key is required")
215
- if not swiftapi_key.startswith("swiftapi_live_"):
216
- raise AuthenticationError("Invalid key format: must start with 'swiftapi_live_'")
217
-
218
- ant_key = anthropic_key or api_key
219
- if not ant_key:
220
- raise AuthenticationError("anthropic_key is required (your Anthropic API key)")
221
-
222
- self.messages = Messages(swiftapi_key, ant_key, authority_url, timeout)
1
+ """
2
+ SwiftAPI Attested Anthropic - Compatibility wrapper.
3
+
4
+ # Before (vibe coding):
5
+ from anthropic import Anthropic
6
+ client = Anthropic(api_key="sk-ant-...")
7
+
8
+ # After (attested):
9
+ from swiftapi import Anthropic
10
+ client = Anthropic(swiftapi_key="swiftapi_live_...", anthropic_key="sk-ant-...")
11
+
12
+ Compatibility wrapper for the documented nonstreaming text subset. Authorization is attested.
13
+ Provider keys are sent only to the provider. This wrapper does not establish native EOS.
14
+ """
15
+
16
+ import requests
17
+ from typing import List, Dict, Optional, Any
18
+
19
+ from . import __version__
20
+ from .exceptions import SwiftAPIError, AuthenticationError, NetworkError
21
+ from .verifier import (
22
+ verify_signature,
23
+ claim_attestation,
24
+ _action_fingerprint,
25
+ _snapshot_action_data,
26
+ _validate_action_binding,
27
+ )
28
+
29
+
30
+ class ContentBlock:
31
+ """Anthropic-compatible content block."""
32
+
33
+ def __init__(self, text: str, type: str = "text"):
34
+ self.text = text
35
+ self.type = type
36
+
37
+ def __repr__(self):
38
+ truncated = self.text[:80] if self.text else ""
39
+ return f"ContentBlock(type='{self.type}', text='{truncated}')"
40
+
41
+
42
+ class MessageResponse:
43
+ """Anthropic-compatible message response."""
44
+
45
+ def __init__(
46
+ self,
47
+ content: str,
48
+ model: str,
49
+ role: str = "assistant",
50
+ stop_reason: str = None,
51
+ attested: bool = False,
52
+ jti: str = None,
53
+ ):
54
+ self.content = [ContentBlock(text=content)] if content else [ContentBlock(text="")]
55
+ self.model = model
56
+ self.role = role
57
+ self.stop_reason = stop_reason
58
+ self.classification = "GATE_BLOCK" if not attested else "UNCLASSIFIED_PROVIDER_OUTPUT"
59
+ self.native_eos_verified = False
60
+ self.attested = attested
61
+ self.jti = jti
62
+
63
+ @property
64
+ def text(self):
65
+ """Convenience: response.text instead of response.content[0].text"""
66
+ return self.content[0].text if self.content else ""
67
+
68
+
69
+ class Messages:
70
+ """Handles messages.create() calls."""
71
+
72
+ def __init__(self, swiftapi_key: str, anthropic_key: str, authority_url: str, timeout: int):
73
+ self._swiftapi_key = swiftapi_key
74
+ self._anthropic_key = anthropic_key
75
+ from urllib.parse import urlparse
76
+ parsed = urlparse(authority_url)
77
+ if parsed.scheme != "https" or not parsed.hostname or parsed.username or parsed.password or parsed.query or parsed.fragment:
78
+ raise SwiftAPIError("Authority URL must use HTTPS without embedded credentials")
79
+ self._authority_url = authority_url.rstrip("/")
80
+ self._timeout = timeout
81
+ self._session = requests.Session()
82
+
83
+ def create(
84
+ self,
85
+ model: str,
86
+ messages: List[Dict[str, Any]],
87
+ max_tokens: int = 1024,
88
+ temperature: Optional[float] = None,
89
+ system: Optional[str] = None,
90
+ **kwargs,
91
+ ) -> MessageResponse:
92
+ """
93
+ Create an attested message completion.
94
+
95
+ Same interface as anthropic.messages.create().
96
+ Step 1: Attest with SwiftAPI (governance).
97
+ Step 2: Call Anthropic directly with your key (execution).
98
+
99
+ If governance denies, content is empty and classification is GATE_BLOCK.
100
+
101
+ Args:
102
+ model: Model name (e.g. "claude-sonnet-4-5-20250929")
103
+ messages: List of message dicts with role and content
104
+ max_tokens: Max tokens in response
105
+ temperature: Sampling temperature
106
+ system: System prompt
107
+
108
+ Returns:
109
+ MessageResponse with .content[0].text
110
+ """
111
+ payload = {
112
+ "model": model,
113
+ "messages": messages,
114
+ "max_tokens": max_tokens,
115
+ }
116
+ if temperature is not None:
117
+ payload["temperature"] = temperature
118
+ if system is not None:
119
+ payload["system"] = system
120
+ if kwargs.get("stream"):
121
+ raise SwiftAPIError("Streaming is unsupported by this compatibility wrapper")
122
+ payload.update(kwargs)
123
+ payload = _snapshot_action_data(payload)
124
+ expected_fingerprint = _action_fingerprint(payload, truncated=True)
125
+
126
+ # Step 1: Attest with SwiftAPI — governance check
127
+ try:
128
+ attest_resp = self._session.post(
129
+ f"{self._authority_url}/attest",
130
+ json={"action_type": "chat_completion", "action_data": payload},
131
+ headers={
132
+ "X-SwiftAPI-Authority": self._swiftapi_key,
133
+ "Content-Type": "application/json",
134
+ "User-Agent": f"swiftapi-python/{__version__}",
135
+ },
136
+ timeout=self._timeout,
137
+ allow_redirects=False,
138
+ )
139
+ except requests.exceptions.RequestException:
140
+ raise NetworkError("Attestation request failed") from None
141
+
142
+ if attest_resp.status_code == 403:
143
+ raise AuthenticationError("Invalid SwiftAPI authority key")
144
+ if attest_resp.status_code == 429:
145
+ raise SwiftAPIError("Rate limit exceeded")
146
+ if attest_resp.status_code != 200:
147
+ raise SwiftAPIError("Attestation failed")
148
+
149
+ attestation = attest_resp.json()
150
+ verify_signature(attestation)
151
+ if not _validate_action_binding(attestation, "chat_completion", expected_fingerprint):
152
+ return MessageResponse(content="", model=model)
153
+
154
+ claim_attestation(attestation)
155
+
156
+ # Step 2: Call Anthropic directly — user's own key, user's own credits
157
+ anthropic_payload = payload
158
+
159
+ try:
160
+ anthropic_resp = self._session.post(
161
+ "https://api.anthropic.com/v1/messages",
162
+ json=anthropic_payload,
163
+ headers={
164
+ "x-api-key": self._anthropic_key,
165
+ "anthropic-version": "2023-06-01",
166
+ "Content-Type": "application/json",
167
+ "User-Agent": f"swiftapi-python/{__version__}",
168
+ },
169
+ timeout=self._timeout,
170
+ allow_redirects=False,
171
+ )
172
+ except requests.exceptions.RequestException:
173
+ raise NetworkError("Anthropic request failed") from None
174
+
175
+ if anthropic_resp.status_code == 401:
176
+ raise AuthenticationError("Invalid Anthropic API key")
177
+ if anthropic_resp.status_code != 200:
178
+ raise SwiftAPIError("Anthropic error")
179
+
180
+ data = anthropic_resp.json()
181
+ content_blocks = data.get("content", [])
182
+ text = content_blocks[0].get("text", "") if content_blocks else ""
183
+
184
+ result = MessageResponse(
185
+ content=text,
186
+ model=model,
187
+ role=data.get("role", "assistant"),
188
+ stop_reason=data.get("stop_reason"),
189
+ attested=True,
190
+ jti=attestation.get("jti"),
191
+ )
192
+ result.raw_response = data
193
+ return result
194
+
195
+
196
+ class Anthropic:
197
+ """
198
+ Compatibility wrapper for anthropic.Anthropic.
199
+
200
+ Every completion is cryptographically attested via SwiftAPI.
201
+ SwiftAPI governs. Anthropic executes. You bring both keys.
202
+
203
+ Usage:
204
+ from swiftapi import Anthropic
205
+
206
+ client = Anthropic(
207
+ swiftapi_key="swiftapi_live_...",
208
+ anthropic_key="sk-ant-..."
209
+ )
210
+ r = client.messages.create(
211
+ model="claude-sonnet-4-5-20250929",
212
+ max_tokens=1024,
213
+ messages=[{"role": "user", "content": "Hello"}]
214
+ )
215
+ print(r.content[0].text)
216
+ """
217
+
218
+ def __init__(
219
+ self,
220
+ swiftapi_key: str = None,
221
+ anthropic_key: str = None,
222
+ api_key: str = None,
223
+ authority_url: str = "https://swiftapi.ai",
224
+ timeout: int = 60,
225
+ **kwargs,
226
+ ):
227
+ if not swiftapi_key:
228
+ raise AuthenticationError("swiftapi_key is required")
229
+ if not swiftapi_key.startswith("swiftapi_live_"):
230
+ raise AuthenticationError("Invalid key format: must start with 'swiftapi_live_'")
231
+
232
+ ant_key = anthropic_key or api_key
233
+ if not ant_key:
234
+ raise AuthenticationError("anthropic_key is required (your Anthropic API key)")
235
+
236
+ self.messages = Messages(swiftapi_key, ant_key, authority_url, timeout)