swiftapi-python 1.1.2__tar.gz → 1.2.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.
- {swiftapi_python-1.1.2 → swiftapi_python-1.2.0}/PKG-INFO +1 -1
- {swiftapi_python-1.1.2 → swiftapi_python-1.2.0}/pyproject.toml +1 -1
- {swiftapi_python-1.1.2 → swiftapi_python-1.2.0}/swiftapi/__init__.py +4 -2
- swiftapi_python-1.2.0/swiftapi/anthropic.py +221 -0
- {swiftapi_python-1.1.2 → swiftapi_python-1.2.0}/swiftapi_python.egg-info/PKG-INFO +1 -1
- {swiftapi_python-1.1.2 → swiftapi_python-1.2.0}/swiftapi_python.egg-info/SOURCES.txt +1 -0
- {swiftapi_python-1.1.2 → swiftapi_python-1.2.0}/README.md +0 -0
- {swiftapi_python-1.1.2 → swiftapi_python-1.2.0}/setup.cfg +0 -0
- {swiftapi_python-1.1.2 → swiftapi_python-1.2.0}/swiftapi/client.py +0 -0
- {swiftapi_python-1.1.2 → swiftapi_python-1.2.0}/swiftapi/enforcement.py +0 -0
- {swiftapi_python-1.1.2 → swiftapi_python-1.2.0}/swiftapi/exceptions.py +0 -0
- {swiftapi_python-1.1.2 → swiftapi_python-1.2.0}/swiftapi/openai.py +0 -0
- {swiftapi_python-1.1.2 → swiftapi_python-1.2.0}/swiftapi/utils.py +0 -0
- {swiftapi_python-1.1.2 → swiftapi_python-1.2.0}/swiftapi/verifier.py +0 -0
- {swiftapi_python-1.1.2 → swiftapi_python-1.2.0}/swiftapi_python.egg-info/dependency_links.txt +0 -0
- {swiftapi_python-1.1.2 → swiftapi_python-1.2.0}/swiftapi_python.egg-info/requires.txt +0 -0
- {swiftapi_python-1.1.2 → swiftapi_python-1.2.0}/swiftapi_python.egg-info/top_level.txt +0 -0
|
@@ -29,7 +29,7 @@ Usage:
|
|
|
29
29
|
db.update(user_id, data)
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
|
-
__version__ = "1.
|
|
32
|
+
__version__ = "1.2.0"
|
|
33
33
|
__author__ = "Rayan Pal"
|
|
34
34
|
|
|
35
35
|
# Core exports
|
|
@@ -37,6 +37,7 @@ from .client import SwiftAPI
|
|
|
37
37
|
from .enforcement import Enforcement, enforce
|
|
38
38
|
from .verifier import verify_signature, is_valid, get_public_key
|
|
39
39
|
from .openai import OpenAI
|
|
40
|
+
from .anthropic import Anthropic
|
|
40
41
|
|
|
41
42
|
# Exceptions
|
|
42
43
|
from .exceptions import (
|
|
@@ -68,8 +69,9 @@ __all__ = [
|
|
|
68
69
|
"SwiftAPI",
|
|
69
70
|
"Enforcement",
|
|
70
71
|
"enforce",
|
|
71
|
-
# Drop-in
|
|
72
|
+
# Drop-in replacements
|
|
72
73
|
"OpenAI",
|
|
74
|
+
"Anthropic",
|
|
73
75
|
# Verification
|
|
74
76
|
"verify_signature",
|
|
75
77
|
"is_valid",
|
|
@@ -0,0 +1,221 @@
|
|
|
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 .exceptions import SwiftAPIError, AuthenticationError, NetworkError
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ContentBlock:
|
|
24
|
+
"""Anthropic-compatible content block."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, text: str, type: str = "text"):
|
|
27
|
+
self.text = text
|
|
28
|
+
self.type = type
|
|
29
|
+
|
|
30
|
+
def __repr__(self):
|
|
31
|
+
truncated = self.text[:80] if self.text else ""
|
|
32
|
+
return f"ContentBlock(type='{self.type}', text='{truncated}')"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class MessageResponse:
|
|
36
|
+
"""Anthropic-compatible message response."""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
content: str,
|
|
41
|
+
model: str,
|
|
42
|
+
role: str = "assistant",
|
|
43
|
+
stop_reason: str = None,
|
|
44
|
+
attested: bool = False,
|
|
45
|
+
jti: str = None,
|
|
46
|
+
):
|
|
47
|
+
self.content = [ContentBlock(text=content)] if content else [ContentBlock(text="")]
|
|
48
|
+
self.model = model
|
|
49
|
+
self.role = role
|
|
50
|
+
self.stop_reason = stop_reason or ("end_turn" if content else "void")
|
|
51
|
+
self.attested = attested
|
|
52
|
+
self.jti = jti
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def text(self):
|
|
56
|
+
"""Convenience: response.text instead of response.content[0].text"""
|
|
57
|
+
return self.content[0].text if self.content else ""
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class Messages:
|
|
61
|
+
"""Handles messages.create() calls."""
|
|
62
|
+
|
|
63
|
+
def __init__(self, swiftapi_key: str, anthropic_key: str, authority_url: str, timeout: int):
|
|
64
|
+
self._swiftapi_key = swiftapi_key
|
|
65
|
+
self._anthropic_key = anthropic_key
|
|
66
|
+
self._authority_url = authority_url.rstrip("/")
|
|
67
|
+
self._timeout = timeout
|
|
68
|
+
self._session = requests.Session()
|
|
69
|
+
|
|
70
|
+
def create(
|
|
71
|
+
self,
|
|
72
|
+
model: str,
|
|
73
|
+
messages: List[Dict[str, Any]],
|
|
74
|
+
max_tokens: int = 1024,
|
|
75
|
+
temperature: Optional[float] = None,
|
|
76
|
+
system: Optional[str] = None,
|
|
77
|
+
**kwargs,
|
|
78
|
+
) -> MessageResponse:
|
|
79
|
+
"""
|
|
80
|
+
Create an attested message completion.
|
|
81
|
+
|
|
82
|
+
Same interface as anthropic.messages.create().
|
|
83
|
+
Step 1: Attest with SwiftAPI (governance).
|
|
84
|
+
Step 2: Call Anthropic directly with your key (execution).
|
|
85
|
+
|
|
86
|
+
If governance denies, content is empty string. Void.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
model: Model name (e.g. "claude-sonnet-4-5-20250929")
|
|
90
|
+
messages: List of message dicts with role and content
|
|
91
|
+
max_tokens: Max tokens in response
|
|
92
|
+
temperature: Sampling temperature
|
|
93
|
+
system: System prompt
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
MessageResponse with .content[0].text
|
|
97
|
+
"""
|
|
98
|
+
payload = {
|
|
99
|
+
"model": model,
|
|
100
|
+
"messages": messages,
|
|
101
|
+
"max_tokens": max_tokens,
|
|
102
|
+
}
|
|
103
|
+
if temperature is not None:
|
|
104
|
+
payload["temperature"] = temperature
|
|
105
|
+
if system is not None:
|
|
106
|
+
payload["system"] = system
|
|
107
|
+
|
|
108
|
+
# Step 1: Attest with SwiftAPI — governance check
|
|
109
|
+
try:
|
|
110
|
+
attest_resp = self._session.post(
|
|
111
|
+
f"{self._authority_url}/attest",
|
|
112
|
+
json={"action_type": "chat_completion", "action_data": payload},
|
|
113
|
+
headers={
|
|
114
|
+
"X-SwiftAPI-Authority": self._swiftapi_key,
|
|
115
|
+
"Content-Type": "application/json",
|
|
116
|
+
"User-Agent": "swiftapi-python/1.2.0",
|
|
117
|
+
},
|
|
118
|
+
timeout=self._timeout,
|
|
119
|
+
)
|
|
120
|
+
except requests.exceptions.RequestException as e:
|
|
121
|
+
raise NetworkError(f"Attestation request failed: {e}")
|
|
122
|
+
|
|
123
|
+
if attest_resp.status_code == 403:
|
|
124
|
+
raise AuthenticationError("Invalid SwiftAPI authority key")
|
|
125
|
+
if attest_resp.status_code == 429:
|
|
126
|
+
raise SwiftAPIError("Rate limit exceeded")
|
|
127
|
+
if attest_resp.status_code != 200:
|
|
128
|
+
raise SwiftAPIError(f"Attestation failed: {attest_resp.text[:200]}")
|
|
129
|
+
|
|
130
|
+
attestation = attest_resp.json()
|
|
131
|
+
|
|
132
|
+
if not attestation.get("approved"):
|
|
133
|
+
return MessageResponse(content="", model=model)
|
|
134
|
+
|
|
135
|
+
# Step 2: Call Anthropic directly — user's own key, user's own credits
|
|
136
|
+
anthropic_payload = {
|
|
137
|
+
"model": model,
|
|
138
|
+
"messages": messages,
|
|
139
|
+
"max_tokens": max_tokens,
|
|
140
|
+
}
|
|
141
|
+
if temperature is not None:
|
|
142
|
+
anthropic_payload["temperature"] = temperature
|
|
143
|
+
if system is not None:
|
|
144
|
+
anthropic_payload["system"] = system
|
|
145
|
+
anthropic_payload.update(kwargs)
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
anthropic_resp = self._session.post(
|
|
149
|
+
"https://api.anthropic.com/v1/messages",
|
|
150
|
+
json=anthropic_payload,
|
|
151
|
+
headers={
|
|
152
|
+
"x-api-key": self._anthropic_key,
|
|
153
|
+
"anthropic-version": "2023-06-01",
|
|
154
|
+
"Content-Type": "application/json",
|
|
155
|
+
"User-Agent": "swiftapi-python/1.2.0",
|
|
156
|
+
},
|
|
157
|
+
timeout=self._timeout,
|
|
158
|
+
)
|
|
159
|
+
except requests.exceptions.RequestException as e:
|
|
160
|
+
raise NetworkError(f"Anthropic request failed: {e}")
|
|
161
|
+
|
|
162
|
+
if anthropic_resp.status_code == 401:
|
|
163
|
+
raise AuthenticationError("Invalid Anthropic API key")
|
|
164
|
+
if anthropic_resp.status_code != 200:
|
|
165
|
+
raise SwiftAPIError(f"Anthropic error: {anthropic_resp.text[:200]}")
|
|
166
|
+
|
|
167
|
+
data = anthropic_resp.json()
|
|
168
|
+
content_blocks = data.get("content", [])
|
|
169
|
+
text = content_blocks[0].get("text", "") if content_blocks else ""
|
|
170
|
+
|
|
171
|
+
return MessageResponse(
|
|
172
|
+
content=text,
|
|
173
|
+
model=model,
|
|
174
|
+
role=data.get("role", "assistant"),
|
|
175
|
+
stop_reason=data.get("stop_reason"),
|
|
176
|
+
attested=True,
|
|
177
|
+
jti=attestation.get("jti"),
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class Anthropic:
|
|
182
|
+
"""
|
|
183
|
+
Drop-in replacement for anthropic.Anthropic.
|
|
184
|
+
|
|
185
|
+
Every completion is cryptographically attested via SwiftAPI.
|
|
186
|
+
SwiftAPI governs. Anthropic executes. You bring both keys.
|
|
187
|
+
|
|
188
|
+
Usage:
|
|
189
|
+
from swiftapi import Anthropic
|
|
190
|
+
|
|
191
|
+
client = Anthropic(
|
|
192
|
+
swiftapi_key="swiftapi_live_...",
|
|
193
|
+
anthropic_key="sk-ant-..."
|
|
194
|
+
)
|
|
195
|
+
r = client.messages.create(
|
|
196
|
+
model="claude-sonnet-4-5-20250929",
|
|
197
|
+
max_tokens=1024,
|
|
198
|
+
messages=[{"role": "user", "content": "Hello"}]
|
|
199
|
+
)
|
|
200
|
+
print(r.content[0].text)
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
def __init__(
|
|
204
|
+
self,
|
|
205
|
+
swiftapi_key: str = None,
|
|
206
|
+
anthropic_key: str = None,
|
|
207
|
+
api_key: str = None,
|
|
208
|
+
authority_url: str = "https://swiftapi.ai",
|
|
209
|
+
timeout: int = 60,
|
|
210
|
+
**kwargs,
|
|
211
|
+
):
|
|
212
|
+
if not swiftapi_key:
|
|
213
|
+
raise AuthenticationError("swiftapi_key is required")
|
|
214
|
+
if not swiftapi_key.startswith("swiftapi_live_"):
|
|
215
|
+
raise AuthenticationError("Invalid key format: must start with 'swiftapi_live_'")
|
|
216
|
+
|
|
217
|
+
ant_key = anthropic_key or api_key
|
|
218
|
+
if not ant_key:
|
|
219
|
+
raise AuthenticationError("anthropic_key is required (your Anthropic API key)")
|
|
220
|
+
|
|
221
|
+
self.messages = Messages(swiftapi_key, ant_key, authority_url, timeout)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{swiftapi_python-1.1.2 → swiftapi_python-1.2.0}/swiftapi_python.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|