shadowob-sdk 1.1.3.dev271__tar.gz → 1.1.4__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.
- {shadowob_sdk-1.1.3.dev271 → shadowob_sdk-1.1.4}/PKG-INFO +1 -1
- {shadowob_sdk-1.1.3.dev271 → shadowob_sdk-1.1.4}/pyproject.toml +1 -1
- {shadowob_sdk-1.1.3.dev271 → shadowob_sdk-1.1.4}/shadowob_sdk/__init__.py +2 -0
- {shadowob_sdk-1.1.3.dev271 → shadowob_sdk-1.1.4}/shadowob_sdk/client.py +147 -1
- {shadowob_sdk-1.1.3.dev271 → shadowob_sdk-1.1.4}/shadowob_sdk/types.py +22 -1
- {shadowob_sdk-1.1.3.dev271 → shadowob_sdk-1.1.4}/tests/test_client.py +55 -0
- {shadowob_sdk-1.1.3.dev271 → shadowob_sdk-1.1.4}/.gitignore +0 -0
- {shadowob_sdk-1.1.3.dev271 → shadowob_sdk-1.1.4}/README.md +0 -0
- {shadowob_sdk-1.1.3.dev271 → shadowob_sdk-1.1.4}/shadowob_sdk/socket.py +0 -0
- {shadowob_sdk-1.1.3.dev271 → shadowob_sdk-1.1.4}/tests/__init__.py +0 -0
|
@@ -20,6 +20,7 @@ from shadowob_sdk.types import (
|
|
|
20
20
|
ShadowModelProxyBilling,
|
|
21
21
|
ShadowModelProxyModel,
|
|
22
22
|
ShadowModelProxyModelsResponse,
|
|
23
|
+
ShadowServerAppTokenIntrospection,
|
|
23
24
|
ShadowServerAccess,
|
|
24
25
|
ShadowShop,
|
|
25
26
|
ShadowUser,
|
|
@@ -47,6 +48,7 @@ __all__ = [
|
|
|
47
48
|
"ShadowModelProxyBilling",
|
|
48
49
|
"ShadowModelProxyModel",
|
|
49
50
|
"ShadowModelProxyModelsResponse",
|
|
51
|
+
"ShadowServerAppTokenIntrospection",
|
|
50
52
|
"ShadowServerAccess",
|
|
51
53
|
"ShadowShop",
|
|
52
54
|
"ShadowSocket",
|
|
@@ -248,9 +248,23 @@ class ShadowClient:
|
|
|
248
248
|
def list_oauth_accounts(self) -> list[dict[str, Any]]:
|
|
249
249
|
return self._get("/api/auth/oauth/accounts")
|
|
250
250
|
|
|
251
|
+
def create_oauth_connect_url(
|
|
252
|
+
self, provider: str, *, redirect: str | None = None
|
|
253
|
+
) -> dict[str, Any]:
|
|
254
|
+
payload: dict[str, Any] = {}
|
|
255
|
+
if redirect is not None:
|
|
256
|
+
payload["redirect"] = redirect
|
|
257
|
+
return self._post(f"/api/auth/oauth/{provider}/link", json=payload)
|
|
258
|
+
|
|
251
259
|
def unlink_oauth_account(self, account_id: str) -> dict[str, Any]:
|
|
252
260
|
return self._delete(f"/api/auth/oauth/accounts/{account_id}")
|
|
253
261
|
|
|
262
|
+
def list_auth_sessions(self) -> list[dict[str, Any]]:
|
|
263
|
+
return self._get("/api/auth/sessions")
|
|
264
|
+
|
|
265
|
+
def revoke_auth_session(self, session_id: str) -> dict[str, Any]:
|
|
266
|
+
return self._delete(f"/api/auth/sessions/{session_id}")
|
|
267
|
+
|
|
254
268
|
# ── Agents ───────────────────────────────────────────────────────────
|
|
255
269
|
|
|
256
270
|
def list_agents(self, *, include_rentals: bool = False) -> list[dict[str, Any]]:
|
|
@@ -404,6 +418,123 @@ class ShadowClient:
|
|
|
404
418
|
def get_server_access(self, server_id_or_slug: str) -> dict[str, Any]:
|
|
405
419
|
return self._get(f"/api/servers/{server_id_or_slug}/access")
|
|
406
420
|
|
|
421
|
+
def list_server_apps(self, server_id_or_slug: str) -> list[dict[str, Any]]:
|
|
422
|
+
return self._get(f"/api/servers/{server_id_or_slug}/apps")
|
|
423
|
+
|
|
424
|
+
def list_server_app_catalog(self, server_id_or_slug: str) -> list[dict[str, Any]]:
|
|
425
|
+
return self._get(f"/api/servers/{server_id_or_slug}/apps/catalog")
|
|
426
|
+
|
|
427
|
+
def discover_server_app(
|
|
428
|
+
self,
|
|
429
|
+
server_id_or_slug: str,
|
|
430
|
+
*,
|
|
431
|
+
manifest_url: str | None = None,
|
|
432
|
+
manifest: dict[str, Any] | None = None,
|
|
433
|
+
) -> dict[str, Any]:
|
|
434
|
+
payload: dict[str, Any] = {}
|
|
435
|
+
if manifest_url:
|
|
436
|
+
payload["manifestUrl"] = manifest_url
|
|
437
|
+
if manifest is not None:
|
|
438
|
+
payload["manifest"] = manifest
|
|
439
|
+
return self._post(f"/api/servers/{server_id_or_slug}/apps/discover", json=payload)
|
|
440
|
+
|
|
441
|
+
def install_server_app(
|
|
442
|
+
self,
|
|
443
|
+
server_id_or_slug: str,
|
|
444
|
+
*,
|
|
445
|
+
manifest_url: str | None = None,
|
|
446
|
+
manifest: dict[str, Any] | None = None,
|
|
447
|
+
) -> dict[str, Any]:
|
|
448
|
+
payload: dict[str, Any] = {}
|
|
449
|
+
if manifest_url:
|
|
450
|
+
payload["manifestUrl"] = manifest_url
|
|
451
|
+
if manifest is not None:
|
|
452
|
+
payload["manifest"] = manifest
|
|
453
|
+
return self._post(f"/api/servers/{server_id_or_slug}/apps", json=payload)
|
|
454
|
+
|
|
455
|
+
def install_server_app_from_catalog(
|
|
456
|
+
self,
|
|
457
|
+
server_id_or_slug: str,
|
|
458
|
+
catalog_entry_id: str,
|
|
459
|
+
) -> dict[str, Any]:
|
|
460
|
+
payload: dict[str, Any] = {}
|
|
461
|
+
return self._post(
|
|
462
|
+
f"/api/servers/{server_id_or_slug}/apps/catalog/{catalog_entry_id}/install",
|
|
463
|
+
json=payload,
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
def get_server_app(self, server_id_or_slug: str, app_key: str) -> dict[str, Any]:
|
|
467
|
+
return self._get(f"/api/servers/{server_id_or_slug}/apps/{app_key}")
|
|
468
|
+
|
|
469
|
+
def delete_server_app(self, server_id_or_slug: str, app_key: str) -> dict[str, Any]:
|
|
470
|
+
return self._delete(f"/api/servers/{server_id_or_slug}/apps/{app_key}")
|
|
471
|
+
|
|
472
|
+
def grant_server_app_to_buddy(
|
|
473
|
+
self,
|
|
474
|
+
server_id_or_slug: str,
|
|
475
|
+
app_key: str,
|
|
476
|
+
*,
|
|
477
|
+
buddy_agent_id: str,
|
|
478
|
+
permissions: list[str],
|
|
479
|
+
resource_rules: dict[str, Any] | None = None,
|
|
480
|
+
approval_mode: str = "none",
|
|
481
|
+
expires_at: str | None = None,
|
|
482
|
+
) -> dict[str, Any]:
|
|
483
|
+
payload: dict[str, Any] = {
|
|
484
|
+
"buddyAgentId": buddy_agent_id,
|
|
485
|
+
"permissions": permissions,
|
|
486
|
+
"approvalMode": approval_mode,
|
|
487
|
+
}
|
|
488
|
+
if resource_rules is not None:
|
|
489
|
+
payload["resourceRules"] = resource_rules
|
|
490
|
+
if expires_at:
|
|
491
|
+
payload["expiresAt"] = expires_at
|
|
492
|
+
return self._post(
|
|
493
|
+
f"/api/servers/{server_id_or_slug}/apps/{app_key}/grants",
|
|
494
|
+
json=payload,
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
def get_server_app_skills(
|
|
498
|
+
self, server_id_or_slug: str, app_key: str
|
|
499
|
+
) -> dict[str, Any]:
|
|
500
|
+
return self._get(f"/api/servers/{server_id_or_slug}/apps/{app_key}/skills")
|
|
501
|
+
|
|
502
|
+
def create_server_app_launch(
|
|
503
|
+
self, server_id_or_slug: str, app_key: str
|
|
504
|
+
) -> dict[str, Any]:
|
|
505
|
+
return self._post(f"/api/servers/{server_id_or_slug}/apps/{app_key}/launch")
|
|
506
|
+
|
|
507
|
+
def introspect_server_app_token(
|
|
508
|
+
self, server_id_or_slug: str, app_key: str, token: str
|
|
509
|
+
) -> dict[str, Any]:
|
|
510
|
+
response = self._http.post(
|
|
511
|
+
f"/api/servers/{server_id_or_slug}/apps/{app_key}/oauth/introspect",
|
|
512
|
+
headers={
|
|
513
|
+
"Authorization": f"Bearer {token}",
|
|
514
|
+
"Content-Type": "application/json",
|
|
515
|
+
},
|
|
516
|
+
json={"token": token},
|
|
517
|
+
)
|
|
518
|
+
response.raise_for_status()
|
|
519
|
+
return response.json()
|
|
520
|
+
|
|
521
|
+
def call_server_app_command(
|
|
522
|
+
self,
|
|
523
|
+
server_id_or_slug: str,
|
|
524
|
+
app_key: str,
|
|
525
|
+
command_name: str,
|
|
526
|
+
*,
|
|
527
|
+
input: Any | None = None,
|
|
528
|
+
channel_id: str | None = None,
|
|
529
|
+
) -> dict[str, Any]:
|
|
530
|
+
payload: dict[str, Any] = {"input": input if input is not None else {}}
|
|
531
|
+
if channel_id:
|
|
532
|
+
payload["channelId"] = channel_id
|
|
533
|
+
return self._post(
|
|
534
|
+
f"/api/servers/{server_id_or_slug}/apps/{app_key}/commands/{command_name}",
|
|
535
|
+
json=payload,
|
|
536
|
+
)
|
|
537
|
+
|
|
407
538
|
def update_server(self, server_id: str, **kwargs: Any) -> dict[str, Any]:
|
|
408
539
|
return self._patch(f"/api/servers/{server_id}", json=kwargs)
|
|
409
540
|
|
|
@@ -474,6 +605,17 @@ class ShadowClient:
|
|
|
474
605
|
def get_channel(self, channel_id: str) -> dict[str, Any]:
|
|
475
606
|
return self._get(f"/api/channels/{channel_id}")
|
|
476
607
|
|
|
608
|
+
def get_channel_bootstrap(
|
|
609
|
+
self,
|
|
610
|
+
channel_id: str,
|
|
611
|
+
*,
|
|
612
|
+
messages_limit: int | None = None,
|
|
613
|
+
) -> dict[str, Any]:
|
|
614
|
+
params: dict[str, Any] = {}
|
|
615
|
+
if messages_limit is not None:
|
|
616
|
+
params["messagesLimit"] = messages_limit
|
|
617
|
+
return self._get(f"/api/channels/{channel_id}/bootstrap", params=params or None)
|
|
618
|
+
|
|
477
619
|
def get_channel_access(self, channel_id: str) -> dict[str, Any]:
|
|
478
620
|
return self._get(f"/api/channels/{channel_id}/access")
|
|
479
621
|
|
|
@@ -858,9 +1000,13 @@ class ShadowClient:
|
|
|
858
1000
|
attachment_id: str,
|
|
859
1001
|
*,
|
|
860
1002
|
disposition: str = "inline",
|
|
1003
|
+
variant: str | None = None,
|
|
861
1004
|
) -> dict[str, Any]:
|
|
862
1005
|
path = f"/api/attachments/{attachment_id}/media-url"
|
|
863
|
-
|
|
1006
|
+
params: dict[str, Any] = {"disposition": disposition}
|
|
1007
|
+
if variant:
|
|
1008
|
+
params["variant"] = variant
|
|
1009
|
+
return self._get(path, params=params)
|
|
864
1010
|
|
|
865
1011
|
def resolve_workspace_media_url(
|
|
866
1012
|
self,
|
|
@@ -114,7 +114,6 @@ class ShadowServer:
|
|
|
114
114
|
description: str | None = None
|
|
115
115
|
icon_url: str | None = None
|
|
116
116
|
banner_url: str | None = None
|
|
117
|
-
homepage_html: str | None = None
|
|
118
117
|
is_public: bool = False
|
|
119
118
|
|
|
120
119
|
|
|
@@ -171,6 +170,20 @@ class ShadowSignedMediaUrl:
|
|
|
171
170
|
expires_at: str
|
|
172
171
|
|
|
173
172
|
|
|
173
|
+
@dataclass
|
|
174
|
+
class ShadowServerAppTokenIntrospection:
|
|
175
|
+
active: bool
|
|
176
|
+
token_type: str | None = None
|
|
177
|
+
iss: str | None = None
|
|
178
|
+
aud: str | None = None
|
|
179
|
+
sub: str | None = None
|
|
180
|
+
scope: str | None = None
|
|
181
|
+
client_id: str | None = None
|
|
182
|
+
exp: int | None = None
|
|
183
|
+
iat: int | None = None
|
|
184
|
+
shadow: dict[str, Any] | None = None
|
|
185
|
+
|
|
186
|
+
|
|
174
187
|
@dataclass
|
|
175
188
|
class ShadowMessageMention:
|
|
176
189
|
kind: str
|
|
@@ -184,6 +197,10 @@ class ShadowMessageMention:
|
|
|
184
197
|
server_name: str | None = None
|
|
185
198
|
channel_id: str | None = None
|
|
186
199
|
channel_name: str | None = None
|
|
200
|
+
app_id: str | None = None
|
|
201
|
+
app_key: str | None = None
|
|
202
|
+
app_name: str | None = None
|
|
203
|
+
icon_url: str | None = None
|
|
187
204
|
user_id: str | None = None
|
|
188
205
|
username: str | None = None
|
|
189
206
|
display_name: str | None = None
|
|
@@ -205,6 +222,10 @@ class ShadowMentionSuggestion:
|
|
|
205
222
|
server_name: str | None = None
|
|
206
223
|
channel_id: str | None = None
|
|
207
224
|
channel_name: str | None = None
|
|
225
|
+
app_id: str | None = None
|
|
226
|
+
app_key: str | None = None
|
|
227
|
+
app_name: str | None = None
|
|
228
|
+
icon_url: str | None = None
|
|
208
229
|
user_id: str | None = None
|
|
209
230
|
username: str | None = None
|
|
210
231
|
display_name: str | None = None
|
|
@@ -129,6 +129,61 @@ def test_get_wallet_transactions_with_display_filters(monkeypatch):
|
|
|
129
129
|
client.close()
|
|
130
130
|
|
|
131
131
|
|
|
132
|
+
def test_resolve_attachment_media_url_accepts_variant(monkeypatch):
|
|
133
|
+
client = ShadowClient("https://example.com", "test-token")
|
|
134
|
+
captured = {}
|
|
135
|
+
|
|
136
|
+
def fake_get(path, *, params=None):
|
|
137
|
+
captured["path"] = path
|
|
138
|
+
captured["params"] = params
|
|
139
|
+
return {
|
|
140
|
+
"url": "/api/media/signed/token",
|
|
141
|
+
"expiresAt": "2026-05-13T04:00:00.000Z",
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
monkeypatch.setattr(client, "_get", fake_get)
|
|
145
|
+
|
|
146
|
+
result = client.resolve_attachment_media_url(
|
|
147
|
+
"attachment-1", disposition="inline", variant="preview"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
assert captured == {
|
|
151
|
+
"path": "/api/attachments/attachment-1/media-url",
|
|
152
|
+
"params": {"disposition": "inline", "variant": "preview"},
|
|
153
|
+
}
|
|
154
|
+
assert result["url"] == "/api/media/signed/token"
|
|
155
|
+
client.close()
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def test_get_channel_bootstrap_uses_message_limit(monkeypatch):
|
|
159
|
+
client = ShadowClient("https://example.com", "test-token")
|
|
160
|
+
captured = {}
|
|
161
|
+
|
|
162
|
+
def fake_get(path, *, params=None):
|
|
163
|
+
captured["path"] = path
|
|
164
|
+
captured["params"] = params
|
|
165
|
+
return {
|
|
166
|
+
"access": {"canAccess": True},
|
|
167
|
+
"channel": {"id": "channel-1"},
|
|
168
|
+
"server": None,
|
|
169
|
+
"channels": [],
|
|
170
|
+
"members": [],
|
|
171
|
+
"messages": {"messages": [], "hasMore": False},
|
|
172
|
+
"slashCommands": {"commands": []},
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
monkeypatch.setattr(client, "_get", fake_get)
|
|
176
|
+
|
|
177
|
+
result = client.get_channel_bootstrap("channel-1", messages_limit=50)
|
|
178
|
+
|
|
179
|
+
assert captured == {
|
|
180
|
+
"path": "/api/channels/channel-1/bootstrap",
|
|
181
|
+
"params": {"messagesLimit": 50},
|
|
182
|
+
}
|
|
183
|
+
assert result["messages"]["hasMore"] is False
|
|
184
|
+
client.close()
|
|
185
|
+
|
|
186
|
+
|
|
132
187
|
def test_socket_creation():
|
|
133
188
|
sock = ShadowSocket("https://example.com", "test-token")
|
|
134
189
|
assert sock.connected is False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|