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/openai.py CHANGED
@@ -1,225 +1,243 @@
1
- """
2
- SwiftAPI Attested OpenAI - Drop-in replacement.
3
-
4
- # Before (vibe coding):
5
- from openai import OpenAI
6
- client = OpenAI(api_key="sk-...")
7
-
8
- # After (attested):
9
- from swiftapi import OpenAI
10
- client = OpenAI(swiftapi_key="swiftapi_live_...", openai_key="sk-...")
11
-
12
- Same interface. Same return type. Every completion is cryptographically attested.
13
- No attestation, no output. SwiftAPI governs. OpenAI 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 Message:
25
- """OpenAI-compatible message object."""
26
-
27
- def __init__(self, role: str, content: str):
28
- self.role = role
29
- self.content = content
30
-
31
- def __repr__(self):
32
- truncated = self.content[:80] if self.content else ""
33
- return f"Message(role='{self.role}', content='{truncated}')"
34
-
35
-
36
- class Choice:
37
- """OpenAI-compatible choice object."""
38
-
39
- def __init__(self, index: int, message: Message, finish_reason: str):
40
- self.index = index
41
- self.message = message
42
- self.finish_reason = finish_reason
43
-
44
-
45
- class ChatCompletion:
46
- """OpenAI-compatible chat completion response."""
47
-
48
- def __init__(self, content: str, model: str, attested: bool = False, jti: str = None):
49
- self.choices = [
50
- Choice(
51
- index=0,
52
- message=Message(role="assistant", content=content),
53
- finish_reason="stop" if content else "void",
54
- )
55
- ]
56
- self.model = model
57
- self.attested = attested
58
- self.jti = jti
59
-
60
- @property
61
- def content(self):
62
- """Convenience: response.content instead of response.choices[0].message.content"""
63
- return self.choices[0].message.content
64
-
65
-
66
- class Completions:
67
- """Handles chat.completions.create() calls."""
68
-
69
- def __init__(self, swiftapi_key: str, openai_key: str, authority_url: str, timeout: int):
70
- self._swiftapi_key = swiftapi_key
71
- self._openai_key = openai_key
72
- self._authority_url = authority_url.rstrip("/")
73
- self._timeout = timeout
74
- self._session = requests.Session()
75
-
76
- def create(
77
- self,
78
- model: str,
79
- messages: List[Dict[str, Any]],
80
- temperature: float = 0.7,
81
- max_completion_tokens: int = 300,
82
- max_tokens: Optional[int] = None,
83
- **kwargs,
84
- ) -> ChatCompletion:
85
- """
86
- Create an attested chat completion.
87
-
88
- Same interface as openai.chat.completions.create().
89
- Step 1: Attest with SwiftAPI (governance).
90
- Step 2: Call OpenAI directly with your key (execution).
91
-
92
- If governance denies, content is empty string. Void.
93
-
94
- Args:
95
- model: Model name (e.g. "gpt-4o")
96
- messages: List of message dicts with role and content
97
- temperature: Sampling temperature
98
- max_completion_tokens: Max tokens in response
99
- max_tokens: Alias for max_completion_tokens (OpenAI compat)
100
-
101
- Returns:
102
- ChatCompletion with .choices[0].message.content
103
- """
104
- effective_max = max_tokens or max_completion_tokens
105
-
106
- payload = {
107
- "model": model,
108
- "messages": messages,
109
- "temperature": temperature,
110
- "max_completion_tokens": effective_max,
111
- }
112
-
113
- # Step 1: Attest with SwiftAPI — governance check
114
- try:
115
- attest_resp = self._session.post(
116
- f"{self._authority_url}/attest",
117
- json={"action_type": "chat_completion", "action_data": payload},
118
- headers={
119
- "X-SwiftAPI-Authority": self._swiftapi_key,
120
- "Content-Type": "application/json",
121
- "User-Agent": f"swiftapi-python/{__version__}",
122
- },
123
- timeout=self._timeout,
124
- )
125
- except requests.exceptions.RequestException as e:
126
- raise NetworkError(f"Attestation request failed: {e}")
127
-
128
- if attest_resp.status_code == 403:
129
- raise AuthenticationError("Invalid SwiftAPI authority key")
130
- if attest_resp.status_code == 429:
131
- raise SwiftAPIError("Rate limit exceeded")
132
- if attest_resp.status_code != 200:
133
- raise SwiftAPIError(f"Attestation failed: {attest_resp.text[:200]}")
134
-
135
- attestation = attest_resp.json()
136
-
137
- if not attestation.get("approved"):
138
- return ChatCompletion(content="", model=model)
139
-
140
- # Step 2: Call OpenAI directly — user's own key, user's own credits
141
- openai_payload = {
142
- "model": model,
143
- "messages": messages,
144
- "temperature": temperature,
145
- "max_completion_tokens": effective_max,
146
- }
147
- openai_payload.update(kwargs)
148
-
149
- try:
150
- openai_resp = self._session.post(
151
- "https://api.openai.com/v1/chat/completions",
152
- json=openai_payload,
153
- headers={
154
- "Authorization": f"Bearer {self._openai_key}",
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"OpenAI request failed: {e}")
162
-
163
- if openai_resp.status_code == 401:
164
- raise AuthenticationError("Invalid OpenAI API key")
165
- if openai_resp.status_code != 200:
166
- raise SwiftAPIError(f"OpenAI error: {openai_resp.text[:200]}")
167
-
168
- data = openai_resp.json()
169
- content = data.get("choices", [{}])[0].get("message", {}).get("content", "")
170
-
171
- return ChatCompletion(
172
- content=content,
173
- model=model,
174
- attested=True,
175
- jti=attestation.get("jti"),
176
- )
177
-
178
-
179
- class _ChatNamespace:
180
- """Namespace for client.chat.completions"""
181
-
182
- def __init__(self, swiftapi_key: str, openai_key: str, authority_url: str, timeout: int):
183
- self.completions = Completions(swiftapi_key, openai_key, authority_url, timeout)
184
-
185
-
186
- class OpenAI:
187
- """
188
- Drop-in replacement for openai.OpenAI.
189
-
190
- Every completion is cryptographically attested via SwiftAPI.
191
- SwiftAPI governs. OpenAI executes. You bring both keys.
192
-
193
- Usage:
194
- from swiftapi import OpenAI
195
-
196
- client = OpenAI(
197
- swiftapi_key="swiftapi_live_...",
198
- openai_key="sk-..."
199
- )
200
- r = client.chat.completions.create(
201
- model="gpt-4o",
202
- messages=[{"role": "user", "content": "Hello"}]
203
- )
204
- print(r.choices[0].message.content)
205
- """
206
-
207
- def __init__(
208
- self,
209
- swiftapi_key: str = None,
210
- openai_key: str = None,
211
- api_key: str = None,
212
- authority_url: str = "https://swiftapi.ai",
213
- timeout: int = 60,
214
- **kwargs,
215
- ):
216
- if not swiftapi_key:
217
- raise AuthenticationError("swiftapi_key is required")
218
- if not swiftapi_key.startswith("swiftapi_live_"):
219
- raise AuthenticationError("Invalid key format: must start with 'swiftapi_live_'")
220
-
221
- oai_key = openai_key or api_key
222
- if not oai_key:
223
- raise AuthenticationError("openai_key is required (your OpenAI API key)")
224
-
225
- self.chat = _ChatNamespace(swiftapi_key, oai_key, authority_url, timeout)
1
+ """
2
+ SwiftAPI Attested OpenAI - Compatibility wrapper.
3
+
4
+ # Before (vibe coding):
5
+ from openai import OpenAI
6
+ client = OpenAI(api_key="sk-...")
7
+
8
+ # After (attested):
9
+ from swiftapi import OpenAI
10
+ client = OpenAI(swiftapi_key="swiftapi_live_...", openai_key="sk-...")
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 Message:
31
+ """OpenAI-compatible message object."""
32
+
33
+ def __init__(self, role: str, content: str):
34
+ self.role = role
35
+ self.content = content
36
+
37
+ def __repr__(self):
38
+ truncated = self.content[:80] if self.content else ""
39
+ return f"Message(role='{self.role}', content='{truncated}')"
40
+
41
+
42
+ class Choice:
43
+ """OpenAI-compatible choice object."""
44
+
45
+ def __init__(self, index: int, message: Message, finish_reason: str):
46
+ self.index = index
47
+ self.message = message
48
+ self.finish_reason = finish_reason
49
+
50
+
51
+ class ChatCompletion:
52
+ """OpenAI-compatible chat completion response."""
53
+
54
+ def __init__(self, content: str, model: str, attested: bool = False, jti: str = None):
55
+ self.choices = [
56
+ Choice(
57
+ index=0,
58
+ message=Message(role="assistant", content=content),
59
+ finish_reason=None,
60
+ )
61
+ ]
62
+ self.model = model
63
+ self.classification = "GATE_BLOCK" if not attested else "UNCLASSIFIED_PROVIDER_OUTPUT"
64
+ self.native_eos_verified = False
65
+ self.attested = attested
66
+ self.jti = jti
67
+
68
+ @property
69
+ def content(self):
70
+ """Convenience: response.content instead of response.choices[0].message.content"""
71
+ return self.choices[0].message.content
72
+
73
+
74
+ class Completions:
75
+ """Handles chat.completions.create() calls."""
76
+
77
+ def __init__(self, swiftapi_key: str, openai_key: str, authority_url: str, timeout: int):
78
+ self._swiftapi_key = swiftapi_key
79
+ self._openai_key = openai_key
80
+ from urllib.parse import urlparse
81
+ parsed = urlparse(authority_url)
82
+ if parsed.scheme != "https" or not parsed.hostname or parsed.username or parsed.password or parsed.query or parsed.fragment:
83
+ raise SwiftAPIError("Authority URL must use HTTPS without embedded credentials")
84
+ self._authority_url = authority_url.rstrip("/")
85
+ self._timeout = timeout
86
+ self._session = requests.Session()
87
+
88
+ def create(
89
+ self,
90
+ model: str,
91
+ messages: List[Dict[str, Any]],
92
+ temperature: float = 0.7,
93
+ max_completion_tokens: int = 300,
94
+ max_tokens: Optional[int] = None,
95
+ **kwargs,
96
+ ) -> ChatCompletion:
97
+ """
98
+ Create an attested chat completion.
99
+
100
+ Same interface as openai.chat.completions.create().
101
+ Step 1: Attest with SwiftAPI (governance).
102
+ Step 2: Call OpenAI directly with your key (execution).
103
+
104
+ If governance denies, content is empty and classification is GATE_BLOCK.
105
+
106
+ Args:
107
+ model: Model name (e.g. "gpt-4o")
108
+ messages: List of message dicts with role and content
109
+ temperature: Sampling temperature
110
+ max_completion_tokens: Max tokens in response
111
+ max_tokens: Alias for max_completion_tokens (OpenAI compat)
112
+
113
+ Returns:
114
+ ChatCompletion with .choices[0].message.content
115
+ """
116
+ effective_max = max_tokens or max_completion_tokens
117
+
118
+ payload = {
119
+ "model": model,
120
+ "messages": messages,
121
+ "temperature": temperature,
122
+ "max_completion_tokens": effective_max,
123
+ }
124
+ if kwargs.get("stream"):
125
+ raise SwiftAPIError("Streaming is unsupported by this compatibility wrapper")
126
+ payload.update(kwargs)
127
+ payload = _snapshot_action_data(payload)
128
+ expected_fingerprint = _action_fingerprint(payload, truncated=True)
129
+
130
+ # Step 1: Attest with SwiftAPI — governance check
131
+ try:
132
+ attest_resp = self._session.post(
133
+ f"{self._authority_url}/attest",
134
+ json={"action_type": "chat_completion", "action_data": payload},
135
+ headers={
136
+ "X-SwiftAPI-Authority": self._swiftapi_key,
137
+ "Content-Type": "application/json",
138
+ "User-Agent": f"swiftapi-python/{__version__}",
139
+ },
140
+ timeout=self._timeout,
141
+ allow_redirects=False,
142
+ )
143
+ except requests.exceptions.RequestException:
144
+ raise NetworkError("Attestation request failed") from None
145
+
146
+ if attest_resp.status_code == 403:
147
+ raise AuthenticationError("Invalid SwiftAPI authority key")
148
+ if attest_resp.status_code == 429:
149
+ raise SwiftAPIError("Rate limit exceeded")
150
+ if attest_resp.status_code != 200:
151
+ raise SwiftAPIError("Attestation failed")
152
+
153
+ attestation = attest_resp.json()
154
+ verify_signature(attestation)
155
+ if not _validate_action_binding(attestation, "chat_completion", expected_fingerprint):
156
+ return ChatCompletion(content="", model=model)
157
+
158
+ claim_attestation(attestation)
159
+
160
+ # Step 2: Call OpenAI directly — user's own key, user's own credits
161
+ openai_payload = payload
162
+
163
+ try:
164
+ openai_resp = self._session.post(
165
+ "https://api.openai.com/v1/chat/completions",
166
+ json=openai_payload,
167
+ headers={
168
+ "Authorization": f"Bearer {self._openai_key}",
169
+ "Content-Type": "application/json",
170
+ "User-Agent": f"swiftapi-python/{__version__}",
171
+ },
172
+ timeout=self._timeout,
173
+ allow_redirects=False,
174
+ )
175
+ except requests.exceptions.RequestException:
176
+ raise NetworkError("OpenAI request failed") from None
177
+
178
+ if openai_resp.status_code == 401:
179
+ raise AuthenticationError("Invalid OpenAI API key")
180
+ if openai_resp.status_code != 200:
181
+ raise SwiftAPIError("OpenAI error")
182
+
183
+ data = openai_resp.json()
184
+ content = data.get("choices", [{}])[0].get("message", {}).get("content", "")
185
+
186
+ result = ChatCompletion(
187
+ content=content,
188
+ model=model,
189
+ attested=True,
190
+ jti=attestation.get("jti"),
191
+ )
192
+ result.choices[0].finish_reason = data.get("choices", [{}])[0].get("finish_reason")
193
+ result.raw_response = data
194
+ return result
195
+
196
+
197
+ class _ChatNamespace:
198
+ """Namespace for client.chat.completions"""
199
+
200
+ def __init__(self, swiftapi_key: str, openai_key: str, authority_url: str, timeout: int):
201
+ self.completions = Completions(swiftapi_key, openai_key, authority_url, timeout)
202
+
203
+
204
+ class OpenAI:
205
+ """
206
+ Compatibility wrapper for openai.OpenAI.
207
+
208
+ Every completion is cryptographically attested via SwiftAPI.
209
+ SwiftAPI governs. OpenAI executes. You bring both keys.
210
+
211
+ Usage:
212
+ from swiftapi import OpenAI
213
+
214
+ client = OpenAI(
215
+ swiftapi_key="swiftapi_live_...",
216
+ openai_key="sk-..."
217
+ )
218
+ r = client.chat.completions.create(
219
+ model="gpt-4o",
220
+ messages=[{"role": "user", "content": "Hello"}]
221
+ )
222
+ print(r.choices[0].message.content)
223
+ """
224
+
225
+ def __init__(
226
+ self,
227
+ swiftapi_key: str = None,
228
+ openai_key: str = None,
229
+ api_key: str = None,
230
+ authority_url: str = "https://swiftapi.ai",
231
+ timeout: int = 60,
232
+ **kwargs,
233
+ ):
234
+ if not swiftapi_key:
235
+ raise AuthenticationError("swiftapi_key is required")
236
+ if not swiftapi_key.startswith("swiftapi_live_"):
237
+ raise AuthenticationError("Invalid key format: must start with 'swiftapi_live_'")
238
+
239
+ oai_key = openai_key or api_key
240
+ if not oai_key:
241
+ raise AuthenticationError("openai_key is required (your OpenAI API key)")
242
+
243
+ self.chat = _ChatNamespace(swiftapi_key, oai_key, authority_url, timeout)
swiftapi/py.typed ADDED
File without changes