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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swiftapi-python
3
- Version: 1.1.2
3
+ Version: 1.2.0
4
4
  Summary: SwiftAPI Python SDK - AI Action Verification Gateway
5
5
  Author-email: Rayan Pal <rayan@swiftapi.ai>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "swiftapi-python"
7
- version = "1.1.2"
7
+ version = "1.2.0"
8
8
  description = "SwiftAPI Python SDK - AI Action Verification Gateway"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -29,7 +29,7 @@ Usage:
29
29
  db.update(user_id, data)
30
30
  """
31
31
 
32
- __version__ = "1.1.2"
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 OpenAI replacement
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swiftapi-python
3
- Version: 1.1.2
3
+ Version: 1.2.0
4
4
  Summary: SwiftAPI Python SDK - AI Action Verification Gateway
5
5
  Author-email: Rayan Pal <rayan@swiftapi.ai>
6
6
  License-Expression: MIT
@@ -1,6 +1,7 @@
1
1
  README.md
2
2
  pyproject.toml
3
3
  swiftapi/__init__.py
4
+ swiftapi/anthropic.py
4
5
  swiftapi/client.py
5
6
  swiftapi/enforcement.py
6
7
  swiftapi/exceptions.py