rayrun-sdk 0.8.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.
- rayrun/__init__.py +3 -0
- rayrun/client.py +257 -0
- rayrun/py.typed +1 -0
- rayrun_sdk-0.8.0.dist-info/METADATA +57 -0
- rayrun_sdk-0.8.0.dist-info/RECORD +6 -0
- rayrun_sdk-0.8.0.dist-info/WHEEL +4 -0
rayrun/__init__.py
ADDED
rayrun/client.py
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Mapping, Optional
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
JsonObject = Mapping[str, Any]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _page_params(limit: int, cursor: Optional[str]) -> Dict[str, Any]:
|
|
12
|
+
return {"limit": limit, **({"cursor": cursor} if cursor is not None else {})}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RayrunApiError(RuntimeError):
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
message: str,
|
|
19
|
+
*,
|
|
20
|
+
code: str,
|
|
21
|
+
request_id: Optional[str],
|
|
22
|
+
status: int,
|
|
23
|
+
) -> None:
|
|
24
|
+
super().__init__(message)
|
|
25
|
+
self.code = code
|
|
26
|
+
self.request_id = request_id
|
|
27
|
+
self.status = status
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _raise_for_status(response: httpx.Response) -> None:
|
|
31
|
+
if response.is_success:
|
|
32
|
+
return
|
|
33
|
+
try:
|
|
34
|
+
body = response.json()
|
|
35
|
+
except ValueError:
|
|
36
|
+
body = {}
|
|
37
|
+
error = body.get("error", {}) if isinstance(body, dict) else {}
|
|
38
|
+
raise RayrunApiError(
|
|
39
|
+
error.get("message", f"Rayrun API request failed with HTTP {response.status_code}."),
|
|
40
|
+
code=error.get("code", "request_failed"),
|
|
41
|
+
request_id=error.get("requestId") or response.headers.get("rayrun-request-id"),
|
|
42
|
+
status=response.status_code,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ExternalPrincipals:
|
|
47
|
+
def __init__(self, client: httpx.Client) -> None:
|
|
48
|
+
self._client = client
|
|
49
|
+
|
|
50
|
+
def _request(self, method: str, path: str, **kwargs: Any) -> Any:
|
|
51
|
+
response = self._client.request(method, path, **kwargs)
|
|
52
|
+
_raise_for_status(response)
|
|
53
|
+
return None if response.status_code == 204 else response.json()
|
|
54
|
+
|
|
55
|
+
def upsert(
|
|
56
|
+
self,
|
|
57
|
+
external_id: str,
|
|
58
|
+
*,
|
|
59
|
+
display_name: Optional[str] = None,
|
|
60
|
+
metadata: Optional[JsonObject] = None,
|
|
61
|
+
) -> Dict[str, str]:
|
|
62
|
+
return self._request(
|
|
63
|
+
"POST",
|
|
64
|
+
"/external-principals",
|
|
65
|
+
json={
|
|
66
|
+
"displayName": display_name,
|
|
67
|
+
"externalId": external_id,
|
|
68
|
+
"metadata": dict(metadata or {}),
|
|
69
|
+
},
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def list(self, *, limit: int = 50, cursor: Optional[str] = None) -> Dict[str, Any]:
|
|
73
|
+
return self._request(
|
|
74
|
+
"GET", "/external-principals", params=_page_params(limit, cursor)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def delete(self, principal_uid: str) -> None:
|
|
78
|
+
self._request("DELETE", f"/external-principals/{principal_uid}")
|
|
79
|
+
|
|
80
|
+
def create_session(
|
|
81
|
+
self,
|
|
82
|
+
principal_uid: str,
|
|
83
|
+
*,
|
|
84
|
+
expires_in_seconds: int = 3600,
|
|
85
|
+
runtime_context: Optional[JsonObject] = None,
|
|
86
|
+
tool_access_profile_uid: Optional[str] = None,
|
|
87
|
+
) -> Dict[str, Any]:
|
|
88
|
+
return self._request(
|
|
89
|
+
"POST",
|
|
90
|
+
f"/external-principals/{principal_uid}/sessions",
|
|
91
|
+
json={
|
|
92
|
+
"expiresInSeconds": expires_in_seconds,
|
|
93
|
+
"runtimeContext": dict(runtime_context or {}),
|
|
94
|
+
"toolAccessProfileUid": tool_access_profile_uid,
|
|
95
|
+
},
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def list_sessions(
|
|
99
|
+
self,
|
|
100
|
+
principal_uid: str,
|
|
101
|
+
*,
|
|
102
|
+
limit: int = 50,
|
|
103
|
+
cursor: Optional[str] = None,
|
|
104
|
+
) -> Dict[str, Any]:
|
|
105
|
+
return self._request(
|
|
106
|
+
"GET",
|
|
107
|
+
f"/external-principals/{principal_uid}/sessions",
|
|
108
|
+
params=_page_params(limit, cursor),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def revoke_session(self, principal_uid: str, session_uid: str) -> None:
|
|
112
|
+
self._request("DELETE", f"/external-principals/{principal_uid}/sessions/{session_uid}")
|
|
113
|
+
|
|
114
|
+
def create_connect_link(self, principal_uid: str, connection_uid: str) -> Dict[str, str]:
|
|
115
|
+
return self._request(
|
|
116
|
+
"POST",
|
|
117
|
+
f"/external-principals/{principal_uid}/connect-links",
|
|
118
|
+
json={"connectionUid": connection_uid},
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class AsyncExternalPrincipals:
|
|
123
|
+
def __init__(self, client: httpx.AsyncClient) -> None:
|
|
124
|
+
self._client = client
|
|
125
|
+
|
|
126
|
+
async def _request(self, method: str, path: str, **kwargs: Any) -> Any:
|
|
127
|
+
response = await self._client.request(method, path, **kwargs)
|
|
128
|
+
_raise_for_status(response)
|
|
129
|
+
return None if response.status_code == 204 else response.json()
|
|
130
|
+
|
|
131
|
+
async def upsert(
|
|
132
|
+
self,
|
|
133
|
+
external_id: str,
|
|
134
|
+
*,
|
|
135
|
+
display_name: Optional[str] = None,
|
|
136
|
+
metadata: Optional[JsonObject] = None,
|
|
137
|
+
) -> Dict[str, str]:
|
|
138
|
+
return await self._request(
|
|
139
|
+
"POST",
|
|
140
|
+
"/external-principals",
|
|
141
|
+
json={
|
|
142
|
+
"displayName": display_name,
|
|
143
|
+
"externalId": external_id,
|
|
144
|
+
"metadata": dict(metadata or {}),
|
|
145
|
+
},
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
async def list(
|
|
149
|
+
self, *, limit: int = 50, cursor: Optional[str] = None
|
|
150
|
+
) -> Dict[str, Any]:
|
|
151
|
+
return await self._request(
|
|
152
|
+
"GET", "/external-principals", params=_page_params(limit, cursor)
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
async def delete(self, principal_uid: str) -> None:
|
|
156
|
+
await self._request("DELETE", f"/external-principals/{principal_uid}")
|
|
157
|
+
|
|
158
|
+
async def create_session(
|
|
159
|
+
self,
|
|
160
|
+
principal_uid: str,
|
|
161
|
+
*,
|
|
162
|
+
expires_in_seconds: int = 3600,
|
|
163
|
+
runtime_context: Optional[JsonObject] = None,
|
|
164
|
+
tool_access_profile_uid: Optional[str] = None,
|
|
165
|
+
) -> Dict[str, Any]:
|
|
166
|
+
return await self._request(
|
|
167
|
+
"POST",
|
|
168
|
+
f"/external-principals/{principal_uid}/sessions",
|
|
169
|
+
json={
|
|
170
|
+
"expiresInSeconds": expires_in_seconds,
|
|
171
|
+
"runtimeContext": dict(runtime_context or {}),
|
|
172
|
+
"toolAccessProfileUid": tool_access_profile_uid,
|
|
173
|
+
},
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
async def list_sessions(
|
|
177
|
+
self,
|
|
178
|
+
principal_uid: str,
|
|
179
|
+
*,
|
|
180
|
+
limit: int = 50,
|
|
181
|
+
cursor: Optional[str] = None,
|
|
182
|
+
) -> Dict[str, Any]:
|
|
183
|
+
return await self._request(
|
|
184
|
+
"GET",
|
|
185
|
+
f"/external-principals/{principal_uid}/sessions",
|
|
186
|
+
params=_page_params(limit, cursor),
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
async def revoke_session(self, principal_uid: str, session_uid: str) -> None:
|
|
190
|
+
await self._request("DELETE", f"/external-principals/{principal_uid}/sessions/{session_uid}")
|
|
191
|
+
|
|
192
|
+
async def create_connect_link(
|
|
193
|
+
self, principal_uid: str, connection_uid: str
|
|
194
|
+
) -> Dict[str, str]:
|
|
195
|
+
return await self._request(
|
|
196
|
+
"POST",
|
|
197
|
+
f"/external-principals/{principal_uid}/connect-links",
|
|
198
|
+
json={"connectionUid": connection_uid},
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class Rayrun:
|
|
203
|
+
def __init__(
|
|
204
|
+
self,
|
|
205
|
+
api_key: str,
|
|
206
|
+
*,
|
|
207
|
+
base_url: str = "https://ray.run",
|
|
208
|
+
timeout: float = 30.0,
|
|
209
|
+
transport: Optional[httpx.BaseTransport] = None,
|
|
210
|
+
) -> None:
|
|
211
|
+
if not api_key:
|
|
212
|
+
raise ValueError("Rayrun requires an api_key.")
|
|
213
|
+
self._client = httpx.Client(
|
|
214
|
+
base_url=f"{base_url.rstrip('/')}/v1",
|
|
215
|
+
headers={"Authorization": f"Bearer {api_key}", "User-Agent": "rayrun-python/0.8.0"},
|
|
216
|
+
timeout=timeout,
|
|
217
|
+
transport=transport,
|
|
218
|
+
)
|
|
219
|
+
self.external_principals = ExternalPrincipals(self._client)
|
|
220
|
+
|
|
221
|
+
def close(self) -> None:
|
|
222
|
+
self._client.close()
|
|
223
|
+
|
|
224
|
+
def __enter__(self) -> "Rayrun":
|
|
225
|
+
return self
|
|
226
|
+
|
|
227
|
+
def __exit__(self, *_args: Any) -> None:
|
|
228
|
+
self.close()
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class AsyncRayrun:
|
|
232
|
+
def __init__(
|
|
233
|
+
self,
|
|
234
|
+
api_key: str,
|
|
235
|
+
*,
|
|
236
|
+
base_url: str = "https://ray.run",
|
|
237
|
+
timeout: float = 30.0,
|
|
238
|
+
transport: Optional[httpx.AsyncBaseTransport] = None,
|
|
239
|
+
) -> None:
|
|
240
|
+
if not api_key:
|
|
241
|
+
raise ValueError("Rayrun requires an api_key.")
|
|
242
|
+
self._client = httpx.AsyncClient(
|
|
243
|
+
base_url=f"{base_url.rstrip('/')}/v1",
|
|
244
|
+
headers={"Authorization": f"Bearer {api_key}", "User-Agent": "rayrun-python/0.8.0"},
|
|
245
|
+
timeout=timeout,
|
|
246
|
+
transport=transport,
|
|
247
|
+
)
|
|
248
|
+
self.external_principals = AsyncExternalPrincipals(self._client)
|
|
249
|
+
|
|
250
|
+
async def close(self) -> None:
|
|
251
|
+
await self._client.aclose()
|
|
252
|
+
|
|
253
|
+
async def __aenter__(self) -> "AsyncRayrun":
|
|
254
|
+
return self
|
|
255
|
+
|
|
256
|
+
async def __aexit__(self, *_args: Any) -> None:
|
|
257
|
+
await self.close()
|
rayrun/py.typed
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: rayrun-sdk
|
|
3
|
+
Version: 0.8.0
|
|
4
|
+
Summary: Python SDK for Rayrun embedded agents and MCP infrastructure
|
|
5
|
+
Project-URL: Documentation, https://ray.run/docs/public-api
|
|
6
|
+
Project-URL: Homepage, https://ray.run
|
|
7
|
+
Project-URL: Repository, https://github.com/rayrundev/rayrun
|
|
8
|
+
Author-email: Rayrun <support@ray.run>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Keywords: agents,mcp,model-context-protocol,rayrun
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Requires-Dist: httpx<1,>=0.28
|
|
13
|
+
Provides-Extra: test
|
|
14
|
+
Requires-Dist: pytest<9,>=8; extra == 'test'
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# Rayrun Python SDK
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
pip install rayrun-sdk
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Create one short-lived MCP session for an application user:
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import os
|
|
27
|
+
|
|
28
|
+
from rayrun import Rayrun
|
|
29
|
+
|
|
30
|
+
with Rayrun(os.environ["RAYRUN_API_KEY"]) as rayrun:
|
|
31
|
+
principal = rayrun.external_principals.upsert(
|
|
32
|
+
"customer_123",
|
|
33
|
+
display_name="Ada",
|
|
34
|
+
metadata={"plan": "pro"},
|
|
35
|
+
)
|
|
36
|
+
session = rayrun.external_principals.create_session(
|
|
37
|
+
principal["uid"],
|
|
38
|
+
runtime_context={"accountId": "customer_123", "plan": "pro"},
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
page = rayrun.external_principals.list_sessions(principal["uid"], limit=50)
|
|
42
|
+
if page["page"]["nextCursor"]:
|
|
43
|
+
next_page = rayrun.external_principals.list_sessions(
|
|
44
|
+
principal["uid"], cursor=page["page"]["nextCursor"], limit=50
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
print(session["endpoint"], session["sessionToken"])
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`AsyncRayrun` exposes the same methods for async applications. Use a key with
|
|
51
|
+
`principals:read`, `principals:write`, `sessions:read`, and `sessions:write`. Create a Connect Link
|
|
52
|
+
with `create_connect_link(principal_uid, connection_uid)` when the user must authorize an upstream
|
|
53
|
+
OAuth account. Correlate the returned `uid` with `connectLinkUid` in either terminal webhook:
|
|
54
|
+
`external.authorization.completed` or `external.authorization.failed` (`provider_denied`,
|
|
55
|
+
`incomplete_response`, `authorization_failed`, or `expired`). A session created without
|
|
56
|
+
`tool_access_profile_uid` is read-only; pass an access profile to define an explicit tool ceiling that
|
|
57
|
+
includes approved mutations.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
rayrun/__init__.py,sha256=_KixIpQE2Wxx33NPQ6ojNmmKMoDp5Q1Km7Sq1-Xbj8I,111
|
|
2
|
+
rayrun/client.py,sha256=eEO6dI5YqFsgEWBtkn5c-LUwkKr6xfOsqFjZcIIEJRg,8010
|
|
3
|
+
rayrun/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
4
|
+
rayrun_sdk-0.8.0.dist-info/METADATA,sha256=GcxPdHMEfAJsL67Wi1RniMaqF8fOJs0r2V3pcC5vRBo,2078
|
|
5
|
+
rayrun_sdk-0.8.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
6
|
+
rayrun_sdk-0.8.0.dist-info/RECORD,,
|