shadow-os 1.0.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.
- shadow_os-1.0.0/.gitignore +7 -0
- shadow_os-1.0.0/PKG-INFO +281 -0
- shadow_os-1.0.0/README.md +246 -0
- shadow_os-1.0.0/pyproject.toml +65 -0
- shadow_os-1.0.0/src/shadow_os/__init__.py +79 -0
- shadow_os-1.0.0/src/shadow_os/_specs.py +281 -0
- shadow_os-1.0.0/src/shadow_os/_transport.py +260 -0
- shadow_os-1.0.0/src/shadow_os/_version.py +1 -0
- shadow_os-1.0.0/src/shadow_os/client.py +665 -0
- shadow_os-1.0.0/src/shadow_os/errors.py +164 -0
- shadow_os-1.0.0/src/shadow_os/models.py +434 -0
- shadow_os-1.0.0/src/shadow_os/py.typed +0 -0
- shadow_os-1.0.0/tests/test_sdk.py +317 -0
shadow_os-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: shadow-os
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Official Python SDK for Shadow-OS — build, operate and talk to AI agents.
|
|
5
|
+
Project-URL: Homepage, https://shadow-os-ai.vercel.app
|
|
6
|
+
Project-URL: Documentation, https://shadow-os-ai.vercel.app/developers
|
|
7
|
+
Project-URL: Source, https://github.com/daniel-sha/ai_web
|
|
8
|
+
Project-URL: Issues, https://github.com/daniel-sha/ai_web/issues
|
|
9
|
+
Author: Shadow-OS
|
|
10
|
+
License: MIT
|
|
11
|
+
Keywords: agents,ai,chatbot,llm,rag,sdk,shadow-os,whatsapp
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.9
|
|
27
|
+
Requires-Dist: anyio>=3.6
|
|
28
|
+
Requires-Dist: httpx>=0.24
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: mypy>=1.8; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest>=7.4; extra == 'dev'
|
|
33
|
+
Requires-Dist: respx>=0.20; extra == 'dev'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# Shadow-OS Python SDK
|
|
37
|
+
|
|
38
|
+
The official Python client for [Shadow-OS](https://shadow-os-ai.vercel.app) — build, operate and talk to AI agents.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install shadow-os
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from shadow_os import ShadowOS
|
|
46
|
+
|
|
47
|
+
with ShadowOS() as client: # reads $SHADOW_OS_API_KEY
|
|
48
|
+
print(client.chat("Summarise the notes I uploaded yesterday"))
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- **Typed** — every response is a dataclass, the package ships `py.typed`, and unknown server fields stay reachable via `.raw`.
|
|
52
|
+
- **Sync and async** — `ShadowOS` and `AsyncShadowOS` share one definition per endpoint, so they cannot drift.
|
|
53
|
+
- **Resilient** — timeouts, exponential backoff with jitter, `Retry-After` support, and automatic retries for cold starts and 5xx.
|
|
54
|
+
- **Complete** — everything the platform can do, not just chat: agents, knowledge, access links, customers, escalations, appointments, analytics.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Authentication
|
|
59
|
+
|
|
60
|
+
Two credentials, for two different jobs.
|
|
61
|
+
|
|
62
|
+
| Credential | Looks like | Use it for |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
| **Account key** | `sk-shadow-…` | Your account: chat, documents, and managing agents you own. |
|
|
65
|
+
| **Agent token / share code** | `sk-agent-…` / `lnk_…` | Talking *to* one configured agent, as your end-users do. |
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
client = ShadowOS("sk-shadow-…") # or set SHADOW_OS_API_KEY
|
|
69
|
+
agent = AgentClient("lnk_…") # or set SHADOW_OS_AGENT_TOKEN
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Create an account key at [/developers](https://shadow-os-ai.vercel.app/developers).
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Your account assistant
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
reply = client.chat("What did we agree with Dana?", session_id="conv-42")
|
|
80
|
+
print(reply.answer, reply.usage)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`session_id` is an **isolation boundary**, not just a label: memory, uploaded files and search are scoped to
|
|
84
|
+
it, so one end-user can never see another's. Pass `scope="account"` to opt into the shared account space
|
|
85
|
+
(which also has access to connected integrations such as Gmail).
|
|
86
|
+
|
|
87
|
+
### Documents & semantic search
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
client.documents.upload("handbook.pdf", session_id="conv-42")
|
|
91
|
+
for doc in client.documents.list(session_id="conv-42"):
|
|
92
|
+
print(doc.name)
|
|
93
|
+
|
|
94
|
+
for hit in client.documents.search("refund policy", top_k=5, session_id="conv-42"):
|
|
95
|
+
print(hit.score, hit.source, hit.text[:120])
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Quota
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
u = client.usage()
|
|
102
|
+
print(f"{u.used}/{u.quota} used — {u.remaining} left")
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Building an agent
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from shadow_os import ShadowOS, AgentConfig
|
|
111
|
+
|
|
112
|
+
client = ShadowOS()
|
|
113
|
+
|
|
114
|
+
created = client.agents.create(
|
|
115
|
+
name="Nona Pizza",
|
|
116
|
+
template="pizzeria", # or persona="..." for full control
|
|
117
|
+
fields={"business_name": "Nona", "hours": "Sun–Thu 11:00–23:00"},
|
|
118
|
+
config=AgentConfig(memory_mode="single", tone="warm and brief"),
|
|
119
|
+
)
|
|
120
|
+
print(created.id, created.manager_token) # the token is shown ONCE — store it now
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Browse what's available before you build:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
for t in client.templates():
|
|
127
|
+
print(t.id, t.name, [f["id"] for f in t.fields])
|
|
128
|
+
|
|
129
|
+
print(client.tool_catalog().groups) # optional tool bundles you can enable per agent
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Teaching it
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
agent = client.agent(created.id)
|
|
136
|
+
|
|
137
|
+
agent.knowledge.add_file("menu.pdf", description="Full menu with prices")
|
|
138
|
+
agent.knowledge.add_file("storefront.jpg") # images are understood AND sendable to customers
|
|
139
|
+
agent.knowledge.add_url("https://nona.example/about")
|
|
140
|
+
agent.knowledge.add_text("We deliver within 4km. Closed on holidays.", name="policies")
|
|
141
|
+
|
|
142
|
+
agent.knowledge.add_sendable("invoice-template.pdf") # deliverable, but NOT searchable knowledge
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Sharing it
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
link = agent.share_link() # persistent, created once, stable forever
|
|
149
|
+
print(link.web, link.whatsapp)
|
|
150
|
+
|
|
151
|
+
staff = agent.access.create_share_link(role="manager", label="Front desk")
|
|
152
|
+
api = agent.access.create_token(role="member", label="Website widget")
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Operating it
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
for c in agent.customers():
|
|
159
|
+
print(c.display_name, "·", c.profile)
|
|
160
|
+
|
|
161
|
+
for e in agent.escalations():
|
|
162
|
+
agent.answer_escalation(e.id, "Yes — we're open until 23:00 on Sunday.")
|
|
163
|
+
|
|
164
|
+
print(agent.analytics().raw)
|
|
165
|
+
print(agent.appointments())
|
|
166
|
+
print(agent.delivery_status())
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Talk to it **as its manager** — it sees your customer roster and acts with your authority:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
print(agent.talk("Message Dana that her order is ready"))
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Talking to an agent (what you embed)
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
from shadow_os import AgentClient
|
|
181
|
+
|
|
182
|
+
with AgentClient("lnk_…") as agent:
|
|
183
|
+
info = agent.info()
|
|
184
|
+
print(info["agent_name"], info["welcome"])
|
|
185
|
+
|
|
186
|
+
reply = agent.send("Are you open on Sunday?", member_key="user-7f3c")
|
|
187
|
+
print(reply.answer, reply.files)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
`member_key` is the identity of the person you are speaking for. Give each end-user a **stable, unguessable**
|
|
191
|
+
id and each gets private, durable memory and history inside the agent — they can never see each other.
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
dana = agent.for_member("user-7f3c", member_name="Dana") # shares the connection pool
|
|
195
|
+
for conv in dana.conversations():
|
|
196
|
+
print(conv.session_id, conv.title)
|
|
197
|
+
for msg in dana.history(session_id="main"):
|
|
198
|
+
print(msg.role, msg.content)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Async
|
|
204
|
+
|
|
205
|
+
Identical surface, awaited:
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
from shadow_os import AsyncShadowOS
|
|
209
|
+
|
|
210
|
+
async with AsyncShadowOS() as client:
|
|
211
|
+
reply = await client.chat("hello")
|
|
212
|
+
agents = await client.agents.list()
|
|
213
|
+
stats = await client.agent(agents[0].id).analytics()
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Errors
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
from shadow_os import QuotaExceeded, RateLimited, ShadowOSError
|
|
222
|
+
|
|
223
|
+
try:
|
|
224
|
+
client.chat("hello")
|
|
225
|
+
except QuotaExceeded as e:
|
|
226
|
+
print("out of quota:", e.usage)
|
|
227
|
+
except RateLimited as e:
|
|
228
|
+
print("slow down, retry in", e.retry_after)
|
|
229
|
+
except ShadowOSError as e:
|
|
230
|
+
print(e.status, e.code, e.message, e.request_id)
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Every error carries `status`, `code`, `message` and — when the server sent one — `request_id`. Quote that id
|
|
234
|
+
in a support request and the exact call can be found in the logs.
|
|
235
|
+
|
|
236
|
+
| Exception | HTTP |
|
|
237
|
+
|---|---|
|
|
238
|
+
| `BadRequestError` | 400 / 422 |
|
|
239
|
+
| `AuthenticationError` | 401 |
|
|
240
|
+
| `QuotaExceeded` | 402 |
|
|
241
|
+
| `PermissionDeniedError` | 403 |
|
|
242
|
+
| `NotFoundError` | 404 |
|
|
243
|
+
| `ConflictError` | 409 |
|
|
244
|
+
| `PayloadTooLarge` | 413 |
|
|
245
|
+
| `RateLimited` | 429 |
|
|
246
|
+
| `ServerError` / `ServiceUnavailable` | 5xx |
|
|
247
|
+
| `APITimeoutError` / `APIConnectionError` | no response |
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## Configuration
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
client = ShadowOS(
|
|
255
|
+
api_key="sk-shadow-…", # or $SHADOW_OS_API_KEY
|
|
256
|
+
base_url="https://…", # or $SHADOW_OS_BASE_URL — for self-hosting or staging
|
|
257
|
+
timeout=120.0, # per request; generous on purpose (the service can cold-start)
|
|
258
|
+
max_retries=3, # timeouts, connection errors, 429 and 5xx
|
|
259
|
+
)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Bring your own `httpx` client (proxies, custom TLS, shared pools):
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
import httpx
|
|
266
|
+
client = ShadowOS(http_client=httpx.Client(proxies="http://…", timeout=60))
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Retries use exponential backoff with full jitter. `Retry-After` always wins. 4xx responses other than 429
|
|
270
|
+
are never retried — they will not become a 200.
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## Development
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
pip install -e ".[dev]"
|
|
278
|
+
pytest
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
MIT licensed.
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# Shadow-OS Python SDK
|
|
2
|
+
|
|
3
|
+
The official Python client for [Shadow-OS](https://shadow-os-ai.vercel.app) — build, operate and talk to AI agents.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install shadow-os
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from shadow_os import ShadowOS
|
|
11
|
+
|
|
12
|
+
with ShadowOS() as client: # reads $SHADOW_OS_API_KEY
|
|
13
|
+
print(client.chat("Summarise the notes I uploaded yesterday"))
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- **Typed** — every response is a dataclass, the package ships `py.typed`, and unknown server fields stay reachable via `.raw`.
|
|
17
|
+
- **Sync and async** — `ShadowOS` and `AsyncShadowOS` share one definition per endpoint, so they cannot drift.
|
|
18
|
+
- **Resilient** — timeouts, exponential backoff with jitter, `Retry-After` support, and automatic retries for cold starts and 5xx.
|
|
19
|
+
- **Complete** — everything the platform can do, not just chat: agents, knowledge, access links, customers, escalations, appointments, analytics.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Authentication
|
|
24
|
+
|
|
25
|
+
Two credentials, for two different jobs.
|
|
26
|
+
|
|
27
|
+
| Credential | Looks like | Use it for |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| **Account key** | `sk-shadow-…` | Your account: chat, documents, and managing agents you own. |
|
|
30
|
+
| **Agent token / share code** | `sk-agent-…` / `lnk_…` | Talking *to* one configured agent, as your end-users do. |
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
client = ShadowOS("sk-shadow-…") # or set SHADOW_OS_API_KEY
|
|
34
|
+
agent = AgentClient("lnk_…") # or set SHADOW_OS_AGENT_TOKEN
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Create an account key at [/developers](https://shadow-os-ai.vercel.app/developers).
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Your account assistant
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
reply = client.chat("What did we agree with Dana?", session_id="conv-42")
|
|
45
|
+
print(reply.answer, reply.usage)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`session_id` is an **isolation boundary**, not just a label: memory, uploaded files and search are scoped to
|
|
49
|
+
it, so one end-user can never see another's. Pass `scope="account"` to opt into the shared account space
|
|
50
|
+
(which also has access to connected integrations such as Gmail).
|
|
51
|
+
|
|
52
|
+
### Documents & semantic search
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
client.documents.upload("handbook.pdf", session_id="conv-42")
|
|
56
|
+
for doc in client.documents.list(session_id="conv-42"):
|
|
57
|
+
print(doc.name)
|
|
58
|
+
|
|
59
|
+
for hit in client.documents.search("refund policy", top_k=5, session_id="conv-42"):
|
|
60
|
+
print(hit.score, hit.source, hit.text[:120])
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Quota
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
u = client.usage()
|
|
67
|
+
print(f"{u.used}/{u.quota} used — {u.remaining} left")
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Building an agent
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from shadow_os import ShadowOS, AgentConfig
|
|
76
|
+
|
|
77
|
+
client = ShadowOS()
|
|
78
|
+
|
|
79
|
+
created = client.agents.create(
|
|
80
|
+
name="Nona Pizza",
|
|
81
|
+
template="pizzeria", # or persona="..." for full control
|
|
82
|
+
fields={"business_name": "Nona", "hours": "Sun–Thu 11:00–23:00"},
|
|
83
|
+
config=AgentConfig(memory_mode="single", tone="warm and brief"),
|
|
84
|
+
)
|
|
85
|
+
print(created.id, created.manager_token) # the token is shown ONCE — store it now
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Browse what's available before you build:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
for t in client.templates():
|
|
92
|
+
print(t.id, t.name, [f["id"] for f in t.fields])
|
|
93
|
+
|
|
94
|
+
print(client.tool_catalog().groups) # optional tool bundles you can enable per agent
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Teaching it
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
agent = client.agent(created.id)
|
|
101
|
+
|
|
102
|
+
agent.knowledge.add_file("menu.pdf", description="Full menu with prices")
|
|
103
|
+
agent.knowledge.add_file("storefront.jpg") # images are understood AND sendable to customers
|
|
104
|
+
agent.knowledge.add_url("https://nona.example/about")
|
|
105
|
+
agent.knowledge.add_text("We deliver within 4km. Closed on holidays.", name="policies")
|
|
106
|
+
|
|
107
|
+
agent.knowledge.add_sendable("invoice-template.pdf") # deliverable, but NOT searchable knowledge
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Sharing it
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
link = agent.share_link() # persistent, created once, stable forever
|
|
114
|
+
print(link.web, link.whatsapp)
|
|
115
|
+
|
|
116
|
+
staff = agent.access.create_share_link(role="manager", label="Front desk")
|
|
117
|
+
api = agent.access.create_token(role="member", label="Website widget")
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Operating it
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
for c in agent.customers():
|
|
124
|
+
print(c.display_name, "·", c.profile)
|
|
125
|
+
|
|
126
|
+
for e in agent.escalations():
|
|
127
|
+
agent.answer_escalation(e.id, "Yes — we're open until 23:00 on Sunday.")
|
|
128
|
+
|
|
129
|
+
print(agent.analytics().raw)
|
|
130
|
+
print(agent.appointments())
|
|
131
|
+
print(agent.delivery_status())
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Talk to it **as its manager** — it sees your customer roster and acts with your authority:
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
print(agent.talk("Message Dana that her order is ready"))
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Talking to an agent (what you embed)
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
from shadow_os import AgentClient
|
|
146
|
+
|
|
147
|
+
with AgentClient("lnk_…") as agent:
|
|
148
|
+
info = agent.info()
|
|
149
|
+
print(info["agent_name"], info["welcome"])
|
|
150
|
+
|
|
151
|
+
reply = agent.send("Are you open on Sunday?", member_key="user-7f3c")
|
|
152
|
+
print(reply.answer, reply.files)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`member_key` is the identity of the person you are speaking for. Give each end-user a **stable, unguessable**
|
|
156
|
+
id and each gets private, durable memory and history inside the agent — they can never see each other.
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
dana = agent.for_member("user-7f3c", member_name="Dana") # shares the connection pool
|
|
160
|
+
for conv in dana.conversations():
|
|
161
|
+
print(conv.session_id, conv.title)
|
|
162
|
+
for msg in dana.history(session_id="main"):
|
|
163
|
+
print(msg.role, msg.content)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Async
|
|
169
|
+
|
|
170
|
+
Identical surface, awaited:
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
from shadow_os import AsyncShadowOS
|
|
174
|
+
|
|
175
|
+
async with AsyncShadowOS() as client:
|
|
176
|
+
reply = await client.chat("hello")
|
|
177
|
+
agents = await client.agents.list()
|
|
178
|
+
stats = await client.agent(agents[0].id).analytics()
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Errors
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
from shadow_os import QuotaExceeded, RateLimited, ShadowOSError
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
client.chat("hello")
|
|
190
|
+
except QuotaExceeded as e:
|
|
191
|
+
print("out of quota:", e.usage)
|
|
192
|
+
except RateLimited as e:
|
|
193
|
+
print("slow down, retry in", e.retry_after)
|
|
194
|
+
except ShadowOSError as e:
|
|
195
|
+
print(e.status, e.code, e.message, e.request_id)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Every error carries `status`, `code`, `message` and — when the server sent one — `request_id`. Quote that id
|
|
199
|
+
in a support request and the exact call can be found in the logs.
|
|
200
|
+
|
|
201
|
+
| Exception | HTTP |
|
|
202
|
+
|---|---|
|
|
203
|
+
| `BadRequestError` | 400 / 422 |
|
|
204
|
+
| `AuthenticationError` | 401 |
|
|
205
|
+
| `QuotaExceeded` | 402 |
|
|
206
|
+
| `PermissionDeniedError` | 403 |
|
|
207
|
+
| `NotFoundError` | 404 |
|
|
208
|
+
| `ConflictError` | 409 |
|
|
209
|
+
| `PayloadTooLarge` | 413 |
|
|
210
|
+
| `RateLimited` | 429 |
|
|
211
|
+
| `ServerError` / `ServiceUnavailable` | 5xx |
|
|
212
|
+
| `APITimeoutError` / `APIConnectionError` | no response |
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Configuration
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
client = ShadowOS(
|
|
220
|
+
api_key="sk-shadow-…", # or $SHADOW_OS_API_KEY
|
|
221
|
+
base_url="https://…", # or $SHADOW_OS_BASE_URL — for self-hosting or staging
|
|
222
|
+
timeout=120.0, # per request; generous on purpose (the service can cold-start)
|
|
223
|
+
max_retries=3, # timeouts, connection errors, 429 and 5xx
|
|
224
|
+
)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Bring your own `httpx` client (proxies, custom TLS, shared pools):
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
import httpx
|
|
231
|
+
client = ShadowOS(http_client=httpx.Client(proxies="http://…", timeout=60))
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Retries use exponential backoff with full jitter. `Retry-After` always wins. 4xx responses other than 429
|
|
235
|
+
are never retried — they will not become a 200.
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Development
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
pip install -e ".[dev]"
|
|
243
|
+
pytest
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
MIT licensed.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.21"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "shadow-os"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Official Python SDK for Shadow-OS — build, operate and talk to AI agents."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Shadow-OS" }]
|
|
13
|
+
keywords = ["shadow-os", "ai", "agents", "llm", "chatbot", "whatsapp", "rag", "sdk"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 5 - Production/Stable",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.9",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Programming Language :: Python :: Implementation :: CPython",
|
|
26
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
27
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
28
|
+
"Typing :: Typed",
|
|
29
|
+
]
|
|
30
|
+
dependencies = [
|
|
31
|
+
"httpx>=0.24",
|
|
32
|
+
"anyio>=3.6",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.optional-dependencies]
|
|
36
|
+
dev = [
|
|
37
|
+
"pytest>=7.4",
|
|
38
|
+
"pytest-asyncio>=0.23",
|
|
39
|
+
"respx>=0.20",
|
|
40
|
+
"mypy>=1.8",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[project.urls]
|
|
44
|
+
Homepage = "https://shadow-os-ai.vercel.app"
|
|
45
|
+
Documentation = "https://shadow-os-ai.vercel.app/developers"
|
|
46
|
+
Source = "https://github.com/daniel-sha/ai_web"
|
|
47
|
+
Issues = "https://github.com/daniel-sha/ai_web/issues"
|
|
48
|
+
|
|
49
|
+
[tool.hatch.version]
|
|
50
|
+
path = "src/shadow_os/_version.py"
|
|
51
|
+
|
|
52
|
+
[tool.hatch.build.targets.wheel]
|
|
53
|
+
packages = ["src/shadow_os"]
|
|
54
|
+
|
|
55
|
+
[tool.hatch.build.targets.sdist]
|
|
56
|
+
include = ["src/shadow_os", "README.md", "tests"]
|
|
57
|
+
|
|
58
|
+
[tool.pytest.ini_options]
|
|
59
|
+
testpaths = ["tests"]
|
|
60
|
+
asyncio_mode = "auto"
|
|
61
|
+
|
|
62
|
+
[tool.mypy]
|
|
63
|
+
python_version = "3.9"
|
|
64
|
+
warn_unused_ignores = true
|
|
65
|
+
ignore_missing_imports = true
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Shadow-OS — the official Python SDK.
|
|
2
|
+
|
|
3
|
+
Build on Shadow-OS from Python: talk to your account assistant, manage documents and semantic search, and
|
|
4
|
+
create + operate configured business agents end to end (knowledge, access links, customers, escalations,
|
|
5
|
+
appointments, analytics).
|
|
6
|
+
|
|
7
|
+
Quickstart::
|
|
8
|
+
|
|
9
|
+
from shadow_os import ShadowOS
|
|
10
|
+
|
|
11
|
+
with ShadowOS() as client: # reads $SHADOW_OS_API_KEY
|
|
12
|
+
print(client.chat("hello"))
|
|
13
|
+
|
|
14
|
+
See https://shadow-os-ai.vercel.app/developers for the full API reference.
|
|
15
|
+
"""
|
|
16
|
+
from ._version import __version__
|
|
17
|
+
from .client import (
|
|
18
|
+
AgentClient,
|
|
19
|
+
AgentHandle,
|
|
20
|
+
AsyncAgentClient,
|
|
21
|
+
AsyncAgentHandle,
|
|
22
|
+
AsyncShadowOS,
|
|
23
|
+
ShadowOS,
|
|
24
|
+
)
|
|
25
|
+
from .errors import (
|
|
26
|
+
APIConnectionError,
|
|
27
|
+
APIStatusError,
|
|
28
|
+
APITimeoutError,
|
|
29
|
+
AuthenticationError,
|
|
30
|
+
BadRequestError,
|
|
31
|
+
ConflictError,
|
|
32
|
+
NotFoundError,
|
|
33
|
+
PayloadTooLarge,
|
|
34
|
+
PermissionDeniedError,
|
|
35
|
+
QuotaExceeded,
|
|
36
|
+
RateLimited,
|
|
37
|
+
ServerError,
|
|
38
|
+
ServiceUnavailable,
|
|
39
|
+
ShadowOSError,
|
|
40
|
+
)
|
|
41
|
+
from .models import (
|
|
42
|
+
Agent,
|
|
43
|
+
AgentConfig,
|
|
44
|
+
AgentReply,
|
|
45
|
+
AgentToken,
|
|
46
|
+
Analytics,
|
|
47
|
+
Appointment,
|
|
48
|
+
ChatResponse,
|
|
49
|
+
Conversation,
|
|
50
|
+
CreatedAgent,
|
|
51
|
+
DeliveryStatus,
|
|
52
|
+
Document,
|
|
53
|
+
Escalation,
|
|
54
|
+
KnowledgeItem,
|
|
55
|
+
Member,
|
|
56
|
+
Message,
|
|
57
|
+
PrimaryShareLink,
|
|
58
|
+
SearchHit,
|
|
59
|
+
SearchResults,
|
|
60
|
+
ShareLink,
|
|
61
|
+
Template,
|
|
62
|
+
ToolCatalog,
|
|
63
|
+
Usage,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
__all__ = [
|
|
67
|
+
"__version__",
|
|
68
|
+
# clients
|
|
69
|
+
"ShadowOS", "AsyncShadowOS", "AgentClient", "AsyncAgentClient", "AgentHandle", "AsyncAgentHandle",
|
|
70
|
+
# models
|
|
71
|
+
"Agent", "AgentConfig", "AgentReply", "AgentToken", "Analytics", "Appointment", "ChatResponse",
|
|
72
|
+
"Conversation", "CreatedAgent", "DeliveryStatus", "Document", "Escalation", "KnowledgeItem",
|
|
73
|
+
"Member", "Message", "PrimaryShareLink", "SearchHit", "SearchResults", "ShareLink", "Template",
|
|
74
|
+
"ToolCatalog", "Usage",
|
|
75
|
+
# errors
|
|
76
|
+
"ShadowOSError", "APIConnectionError", "APITimeoutError", "APIStatusError", "BadRequestError",
|
|
77
|
+
"AuthenticationError", "PermissionDeniedError", "NotFoundError", "ConflictError", "QuotaExceeded",
|
|
78
|
+
"RateLimited", "PayloadTooLarge", "ServerError", "ServiceUnavailable",
|
|
79
|
+
]
|