driftstack-sdk 0.1.5__tar.gz → 0.3.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.
Files changed (51) hide show
  1. driftstack_sdk-0.3.0/.gitignore +17 -0
  2. driftstack_sdk-0.3.0/CHANGELOG.md +438 -0
  3. driftstack_sdk-0.3.0/LICENSE +21 -0
  4. driftstack_sdk-0.3.0/PKG-INFO +350 -0
  5. driftstack_sdk-0.3.0/README.md +314 -0
  6. {driftstack_sdk-0.1.5 → driftstack_sdk-0.3.0}/pyproject.toml +47 -3
  7. driftstack_sdk-0.3.0/src/driftstack/__init__.py +137 -0
  8. driftstack_sdk-0.3.0/src/driftstack/_generated/models.py +2730 -0
  9. {driftstack_sdk-0.1.5 → driftstack_sdk-0.3.0}/src/driftstack/_version.py +1 -1
  10. driftstack_sdk-0.3.0/src/driftstack/client.py +206 -0
  11. driftstack_sdk-0.3.0/src/driftstack/errors.py +684 -0
  12. driftstack_sdk-0.3.0/src/driftstack/http.py +1248 -0
  13. driftstack_sdk-0.3.0/src/driftstack/pagination.py +103 -0
  14. driftstack_sdk-0.3.0/src/driftstack/resources/account.py +171 -0
  15. driftstack_sdk-0.3.0/src/driftstack/resources/agent_sessions.py +996 -0
  16. driftstack_sdk-0.3.0/src/driftstack/resources/api_keys.py +119 -0
  17. driftstack_sdk-0.3.0/src/driftstack/resources/archetypes.py +47 -0
  18. driftstack_sdk-0.3.0/src/driftstack/resources/audit_log.py +98 -0
  19. driftstack_sdk-0.3.0/src/driftstack/resources/auth.py +187 -0
  20. driftstack_sdk-0.3.0/src/driftstack/resources/billing.py +54 -0
  21. driftstack_sdk-0.3.0/src/driftstack/resources/crypto_orders.py +271 -0
  22. driftstack_sdk-0.3.0/src/driftstack/resources/egress.py +119 -0
  23. driftstack_sdk-0.3.0/src/driftstack/resources/email_preferences.py +68 -0
  24. driftstack_sdk-0.3.0/src/driftstack/resources/legal.py +52 -0
  25. driftstack_sdk-0.3.0/src/driftstack/resources/mfa.py +74 -0
  26. driftstack_sdk-0.3.0/src/driftstack/resources/profile_snapshots.py +141 -0
  27. driftstack_sdk-0.3.0/src/driftstack/resources/profiles.py +290 -0
  28. driftstack_sdk-0.3.0/src/driftstack/resources/recipes.py +149 -0
  29. driftstack_sdk-0.3.0/src/driftstack/resources/sessions.py +353 -0
  30. driftstack_sdk-0.3.0/src/driftstack/resources/team.py +197 -0
  31. driftstack_sdk-0.3.0/src/driftstack/resources/usage.py +47 -0
  32. driftstack_sdk-0.3.0/src/driftstack/resources/webhooks.py +231 -0
  33. {driftstack_sdk-0.1.5 → driftstack_sdk-0.3.0}/src/driftstack/retry.py +36 -12
  34. driftstack_sdk-0.3.0/src/driftstack/webhook_signature.py +165 -0
  35. driftstack_sdk-0.1.5/.gitignore +0 -45
  36. driftstack_sdk-0.1.5/PKG-INFO +0 -226
  37. driftstack_sdk-0.1.5/README.md +0 -191
  38. driftstack_sdk-0.1.5/src/driftstack/__init__.py +0 -64
  39. driftstack_sdk-0.1.5/src/driftstack/_generated/models.py +0 -490
  40. driftstack_sdk-0.1.5/src/driftstack/client.py +0 -120
  41. driftstack_sdk-0.1.5/src/driftstack/errors.py +0 -240
  42. driftstack_sdk-0.1.5/src/driftstack/http.py +0 -324
  43. driftstack_sdk-0.1.5/src/driftstack/resources/api_keys.py +0 -61
  44. driftstack_sdk-0.1.5/src/driftstack/resources/sessions.py +0 -156
  45. driftstack_sdk-0.1.5/src/driftstack/resources/usage.py +0 -29
  46. driftstack_sdk-0.1.5/src/driftstack/resources/webhooks.py +0 -110
  47. driftstack_sdk-0.1.5/src/driftstack/webhook_signature.py +0 -106
  48. {driftstack_sdk-0.1.5 → driftstack_sdk-0.3.0}/src/driftstack/_generated/__init__.py +0 -0
  49. {driftstack_sdk-0.1.5 → driftstack_sdk-0.3.0}/src/driftstack/py.typed +0 -0
  50. {driftstack_sdk-0.1.5 → driftstack_sdk-0.3.0}/src/driftstack/resources/__init__.py +0 -0
  51. {driftstack_sdk-0.1.5 → driftstack_sdk-0.3.0}/src/driftstack/resources/_common.py +0 -0
@@ -0,0 +1,17 @@
1
+ # Build and tool caches for the Python SDK. Every pattern here is already
2
+ # covered by the repository root .gitignore, so this file changes nothing about
3
+ # what git tracks.
4
+ #
5
+ # It exists for the SDIST. Hatchling force-includes the nearest .gitignore above
6
+ # the package root into `driftstack_sdk-<version>.tar.gz`, and `exclude` does
7
+ # not override a force-include. Without this file the nearest one is the
8
+ # repository root's, which PyPI would then serve to anyone who downloads the
9
+ # source distribution. Pinned by the packaging guard in apps/server/tests/unit.
10
+ .venv/
11
+ __pycache__/
12
+ *.egg-info/
13
+ .pytest_cache/
14
+ .mypy_cache/
15
+ .ruff_cache/
16
+ dist/
17
+ build/
@@ -0,0 +1,438 @@
1
+ # Changelog
2
+
3
+ All notable changes to the `driftstack` Python SDK. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning
5
+ follows [SemVer](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.3.0] - 2026-09-22
10
+
11
+ **Nothing was removed.** Every name 0.2.0 exported, every method, every
12
+ keyword argument and every error class is still here and still means the
13
+ same thing, so upgrading takes no code change on its own — except where
14
+ **Migration** below says a string comparison needs updating. Every addition
15
+ below exists on **both** `Driftstack` and `AsyncDriftstack`.
16
+
17
+ ### Added
18
+
19
+ - **`EgressCapabilities.safeguards`** — `"passed"`, `"failed"` or
20
+ `"unverified"`, summarising whether every egress safeguard held for a
21
+ session. `"failed"` wins whenever any check did not pass; `"passed"` only
22
+ when the device declared the full set of checks a healthy session reports
23
+ and every one of them reported back; `"unverified"` otherwise. The field is
24
+ **absent**, not `None`, on a session reported before it existed — do not
25
+ read an absent value as `"unverified"` or `"passed"`. Rides everywhere
26
+ `egress_capabilities` already does: `sessions.get()` / `.list()` /
27
+ `.create()`, `profiles.launch()`, and the
28
+ `session.egress_capability_changed` webhook payload.
29
+ - **`measured_by`** on a `?check=full` proxy test result
30
+ (`egress.test_proxy(proxy_id)`) — `"phone"` when a real phone session took
31
+ the measurement, `"driftstack"` when Driftstack itself did because no phone
32
+ could be reached in time. The field this replaces, `measured_from`, is
33
+ still sent beside it with its original values for existing integrations,
34
+ but is no longer documented; read `measured_by` from here on.
35
+ - **`direct_reading` and `website_like_reading`** on `os_fingerprint` — the
36
+ same two facts `single_host_vantage` and `web_port_vantage` already carry,
37
+ under plainer names, added beside the originals rather than replacing
38
+ them. Present on the proxy test result **and** on each saved
39
+ proxy returned by `egress.list_proxies()` / `.update_proxy(proxy_id, body)`.
40
+ - **`"page_unreadable"`** joins the `AgentNoticeReason` values a
41
+ `plan-executed` result can carry: the page could not be read to plan the
42
+ next step, so the task stopped rather than guess. Send `continue` to try
43
+ again.
44
+
45
+ ### Changed
46
+
47
+ - **Two dead `egress_capabilities.warnings` codes are retired from the
48
+ documentation**: `quic_disabled_fallback_http2` and
49
+ `dns_remote_resolve_unsupported_by_proxy`. Neither has ever been sent, so
50
+ this is a documentation correction, not a behavioural change.
51
+
52
+ ### Migration
53
+
54
+ Two closed-string fields were narrowed — values removed, not added — inside
55
+ the `?check=full` proxy test result. `egress.test_proxy()` and
56
+ `egress.list_proxies()` return a plain `dict`, not a validated model, so the
57
+ affected `AccountProxyTestResult*` / `OsFingerprint` models in
58
+ `driftstack._generated.models` are typing-only for these two fields: neither
59
+ change raises at call time, and code comparing a value against one of the old
60
+ strings simply stops matching, silently. The server has sent the new values
61
+ only since 2026-09-21.
62
+
63
+ - **`not_run`** — `"node_busy"`, `"node_error"` and `"no_node"` merged into
64
+ `"check_unavailable"` (you can do exactly one thing about any of the
65
+ three: try again shortly, or contact support if it persists);
66
+ `"unresolvable"` is now `"config_unresolvable"`, matching the word the
67
+ "why a launch is refused" vocabulary already used for the identical fact.
68
+ `"live_session"` is unchanged.
69
+ - **`os_fingerprint_unavailable`** — `"vpn_tunnel"` is now
70
+ `"not_available_for_vpn"`, `"not_observed"` is now `"not_captured"`, and
71
+ `"observer_off"` is now `"not_offered_here"`.
72
+
73
+ Update any code that compares `not_run` or `os_fingerprint_unavailable`
74
+ against one of the old strings to compare against its replacement instead.
75
+
76
+ ### Pre-1.0 stability
77
+
78
+ The SDK is pre-1.0. Pin `driftstack-sdk~=0.3.0` rather than an exact version
79
+ and read this file before bumping.
80
+
81
+ ## [0.2.0] - 2026-09-20
82
+
83
+ The release the guide [Run AI tasks from your
84
+ code](https://docs.driftstack.io/guides/run-ai-tasks-from-code/) is written
85
+ against — 0.1.5 cannot run any of its examples, because the AI agent was not
86
+ reachable from it at all.
87
+
88
+ **Nothing was removed.** Every name 0.1.5 exported, every method, every
89
+ keyword argument and every error class is still here and still means the same
90
+ thing, so upgrading takes no code change. What grew: both clients went from 4
91
+ resources to 19, and `driftstack.__all__` from 22 names to 56. Every addition
92
+ below exists on **both** `Driftstack` and `AsyncDriftstack`. Read **Changed**
93
+ before you upgrade anyway — a few behaviours differ, and one of them changes
94
+ which exception an `except` clause sees.
95
+
96
+ ### Added
97
+
98
+ #### Run an AI task end to end
99
+
100
+ `client.agent_sessions` is new, and is the whole AI surface.
101
+
102
+ - **Start it, send the task, close it** — `create(body, ...)` opens a
103
+ session, on a saved profile if you pass one; `message(id, text, ...)` sends
104
+ the task in plain words and returns what happened; `get(id)`,
105
+ `list(...)` and `iterate(...)` read sessions back; `close(id)` ends one and
106
+ saves the profile's sign-in. A new session is `provisioning` until its
107
+ browser is ready, then `active`.
108
+ - **Every way a turn can end is a named result kind** — `plan-executed` (the
109
+ steps ran, with `answer` when you asked a question), `clarify` (the agent
110
+ is asking you something), `refuse` (it will not do this), `stopped`, and a
111
+ step held for your approval. `answer_unavailable` says why there is no
112
+ `answer` when you asked for one.
113
+ - **Live progress while it runs** — `message(..., on_step=..., on_event=...)`
114
+ parses the turn's stream as it arrives: `on_step(step)` gets each step
115
+ (`{"index", "result"}`) and `on_event(name, data)` every other progress
116
+ event (`phase`, `plan`, `step_start`, `answer`, `notice`, and any added
117
+ later). On the async client either callback may be `async def`.
118
+ - **Approve a step, or don't** — a step with real-world consequences (a
119
+ payment, a message sent, something deleted) pauses the turn and comes back
120
+ as `confirmation_required`. Send the same task again with
121
+ `approve_consequential_actions=` to release it; it accepts the step result
122
+ exactly as it was returned, as well as `{"category", "matched_text"}`
123
+ dicts, and raises `ValueError` before sending anything if an entry is
124
+ neither. An unattended job simply never approves.
125
+ - **Stop a task that runs too long** — `stop(id)` asks the running turn to
126
+ stop; the waiting `message()` then returns kind `stopped` rather than
127
+ raising.
128
+ - **Screenshots** — `get_capture(id, capture_id)` returns the image behind a
129
+ `capture` step's `captureId` as `{"content_type", "bytes"}` (`image/png` or
130
+ `image/jpeg`). Screenshots are kept only briefly, so fetch one as soon as
131
+ its turn ends; one that is no longer kept raises `NotFoundError`.
132
+ - **Transcripts** — `transcript(id, last_event_id=..., timeout_s=...)`
133
+ iterates the session's conversation — an iterator on the sync client, an
134
+ async iterator on the async one: every entry so far, then each new one as
135
+ it is written. `last_event_id` resumes after the last `index` you saw, and
136
+ closing the iterator closes the connection.
137
+ - **Why a turn handed back, in one word** — a `plan-executed` result carries
138
+ `notice_reason` beside the `notice` sentence: `"step_limit"`,
139
+ `"time_limit"`, `"budget_low"`, `"no_progress"`, `"repeated_step"`,
140
+ `"ai_unavailable"`, `"question"` or `"declined"`, exported as the open
141
+ union `AgentNoticeReason` (`Literal[...] | str`), so an ending this SDK has
142
+ never heard of still type-checks and still parses — match the ones you know
143
+ and show `notice` for the rest.
144
+ - **When it is safe to retry a message** — a refusal that did no work leaves
145
+ its `Idempotency-Key` free: after a 409 whose `turn_in_progress` is set, a
146
+ 429, a 402, a 403 about the plan's AI or the model, or a 502 whose
147
+ `key_rejected` is false, send the same request again with the **same**
148
+ `idempotency_key=`. Any other failure gets a new one. One message is one
149
+ key, always.
150
+ - **Typed refusals you can act on** — `ForbiddenError.requires_own_key` /
151
+ `.model` (this model needs your own Anthropic key);
152
+ `ConflictError.turn_in_progress`, `.session_status`, `.idempotency_status`,
153
+ `.ai_control_unavailable`, `.phase`, `.tokens_consumed`, `.usage`,
154
+ `.partial_results` and `.closed_reason` (why a closed session ended,
155
+ without a second call); `FeatureUnavailableError.stop_unconfirmed` (the one
156
+ `stop()` 503 worth calling again); and
157
+ `ByokAnthropicRequiredError.key_rejected` / `.key_source` /
158
+ `.key_rejected_reason` (your own key was refused, which key, and why).
159
+ - **Send your own Anthropic key** — `byok_api_key=` on `create()`, so a
160
+ session can run on a model that requires one without storing anything.
161
+ - **Bound the whole call** — `timeout_s=` on `message()` (default 50
162
+ minutes).
163
+ - **Why a step failed** — a failed step's `Diagnosis.category` explains it,
164
+ including `"target_unverified"` (the tap was not made because its target
165
+ could not be checked first — not retryable as the same step; the agent
166
+ re-plans).
167
+ - **`examples/agent_chat.py`** is the complete flow end to end: create, wait
168
+ until ready, send a task with a fresh idempotency key and live progress,
169
+ handle every result kind, close in `finally`.
170
+
171
+ #### Watch one live, or take the wheel
172
+
173
+ - **`livekit_token(id)`** — a token for the live video view of a running
174
+ session, so a person can watch it work. Returns `LiveKitInfo`.
175
+ - **`set_mode(id, body)`**, **`takeover(id, client_id)`** and
176
+ **`handback(id)`** — hand control of a running session between the agent
177
+ and a person, and hand it back. **`send_input_event(id, body)`** drives it
178
+ while a person holds it, and **`resume(id)`** picks a session back up.
179
+ - **`set_egress(id, body)`** changes which of your proxies a running session
180
+ goes out through.
181
+
182
+ #### The rest of the API
183
+
184
+ `client.sessions`, `client.api_keys`, `client.usage` and `client.webhooks`
185
+ were the whole client in 0.1.5, and each of the four gained methods:
186
+ `sessions` gained `get` / `iterate` / `extract` (pull structured data off the
187
+ page) / `search` / `login`; `usage` gained `series()` for usage over time;
188
+ `api_keys` gained
189
+ `rotate()` (issue the replacement and keep the old key working for a grace
190
+ window); `webhooks` gained `update()` (partial update — it does not rotate
191
+ the signing secret), `rotate_secret()` (fresh secret shown once, previous one
192
+ valid for 24h, both signatures sent during the window), `send_test()` (a
193
+ synthetic `test.ping` delivery so you can check your handler before depending
194
+ on it), `replay_delivery()` and `iterate_deliveries()`.
195
+
196
+ And fourteen resources are new:
197
+
198
+ - **`client.profiles`** — create, list, iterate, get, update, delete, plus
199
+ `clone(profile_id, body=None)` (pass `None` to let the server name it
200
+ "(copy)", "(copy 2)", …) and `trim()`.
201
+ - **`client.profile_snapshots`** — immutable point-in-time copies of a
202
+ profile: `capture`, `list_for_profile`, `list`, `iterate`, `get`,
203
+ `restore`, `delete`. `restore` creates a NEW profile; the original is never
204
+ modified.
205
+ - **`client.account`** — `me()` (the full account profile: timezone, slug,
206
+ region, avatar, whether MFA is enrolled, team memberships),
207
+ `update_me()`, `upload_avatar()` / `clear_avatar()`,
208
+ `list_web_sessions()` / `revoke_web_session(id)` /
209
+ `revoke_all_other_web_sessions()`, and `rate_limits()` for the limits
210
+ actually in force on your account.
211
+ - **`client.auth`** — sign-up, e-mail verification, log in, magic links,
212
+ password reset, refresh, log out, and the three-call activation flow a CLI
213
+ or desktop app uses instead of asking for a pasted key
214
+ (`cli_authorize_initiate` → open the returned `browser_url` → poll
215
+ `cli_authorize_exchange`, which delivers the key once, then reports
216
+ `expired`).
217
+ - **`client.mfa`** — `status`, `enroll`, `verify`, `disable`,
218
+ `regenerate_recovery_codes`; plus `auth.mfa_challenge()` to exchange a
219
+ login challenge for a session (the response says whether it was satisfied
220
+ by `"totp"` or `"recovery"`) and `auth.mfa_step_up()` to refresh the
221
+ freshness window an operation asked for.
222
+ - **`client.team`** — members, invites, roles, and `list_owners()` for the
223
+ workspaces your account has joined.
224
+ - **`client.audit_log`** — `list` / `iterate`, and `export()`: a single-call
225
+ JSON export of your account's audit log, up to 10,000 rows, with
226
+ `truncated` set when there were more.
227
+ - **`client.billing`** — current state, checkout, and the billing portal.
228
+ - **`client.crypto_orders`** — `quote`, `create_checkout` (takes
229
+ `idempotency_key=` so a retry cannot mint a second order), `list`,
230
+ `iterate` (walks every page for you; narrow it with `status`,
231
+ `created_after`, `created_before`), `get`, `update_note`, `cancel`,
232
+ `receipt`. Crypto payments are not refundable, and cancelling only works
233
+ while an order is pending.
234
+ - **`client.egress`** and account proxies — manage saved proxies and route a
235
+ session's traffic through one with `proxy_id` on create.
236
+ - **`client.archetypes`** — the device archetypes your plan can use.
237
+ - **`client.recipes`** — `create(agent_session_id=, label=, description=)`
238
+ snapshots a finished agent session's steps and transcript into a recipe you
239
+ can replay.
240
+ - **`client.email_preferences`** — `list` / `set` / `opt_in` / `opt_out`.
241
+ - **`client.legal`** — record acceptance of a document version.
242
+
243
+ #### Errors and retries
244
+
245
+ - **`is_retryable(err)`** is exported, so the predicate the built-in retry
246
+ loop uses is one you can call yourself.
247
+ - **New error classes**, all importable from `driftstack`:
248
+ `BadRequestError`, `InternalError`, `FeatureUnavailableError`,
249
+ `MfaStepUpRequiredError`, `EmailAlreadyRegisteredError`,
250
+ `InvalidCredentialsError`, `InvalidAuthTokenError`,
251
+ `EmailNotVerifiedError`, `ByokAnthropicRequiredError`,
252
+ `ProxyValidationFailedError`, `StorageQuotaExceededError`,
253
+ `ProfileInUseError`, `BundledLlmConsentRequiredError`,
254
+ `BundledLlmBudgetExhaustedError`, `PairModeConflictError` and
255
+ `PairModeStateInvalidTransitionError`.
256
+ - **`verify_webhook_signature` accepts `header_prev=`** — an optional second
257
+ signature header. You rarely need it: during a rotation grace window both
258
+ signatures arrive inside the one `x-driftstack-signature` header, which the
259
+ verifier already checks.
260
+
261
+ ### Changed
262
+
263
+ - **Too many AI turns is a `RateLimitError` now.** This refusal answers as
264
+ `rate-limited` with `retry_after_seconds` 5, so `message()` raises
265
+ `RateLimitError` where it raised `ConcurrencyLimitError`.
266
+ `ConcurrencyLimitError` still means exactly what it always meant on
267
+ `create()`: your plan's limit on sessions running at once.
268
+ - **A generic 400 raises `BadRequestError` now**, not `ValidationError`. A
269
+ 400 that carries field-level issues is still a `ValidationError`. Callers
270
+ with `except ValidationError` around a generic 400 should switch to
271
+ `except BadRequestError`; `except DriftstackError` catch-alls and
272
+ `is_retryable` are unaffected.
273
+ - **The agent-session docstrings describe what the API does** — result kinds,
274
+ how an approval resumes paused steps, when a key may be reused, the
275
+ progress event names — and no longer describe how the service is built. The
276
+ README no longer claims every resource returns a Pydantic model
277
+ (`agent_sessions` returns dicts) and now says that `ForbiddenError` is a
278
+ subclass of `AuthError`.
279
+
280
+ ### Fixed
281
+
282
+ - **A failure category newer than the SDK no longer breaks parsing.**
283
+ `Diagnosis.category` is `Literal[...] | str`, so a category the server adds
284
+ after this release is kept as a plain string. ⚠️ **This is the one reason
285
+ to upgrade before you need to.** In 0.1.5 the field is a closed `Literal`,
286
+ so a response carrying a category that SDK has never seen raises a pydantic
287
+ `ValidationError` for the **whole** response — not just that field. Treat a
288
+ value you do not recognise as `"unknown"`; code comparing `category`
289
+ against known strings needs no change.
290
+ - **The account profile matches the full response** — `me()` returns every
291
+ field the API sends, including the ones added since 0.1.5.
292
+
293
+ ### Pre-1.0 stability
294
+
295
+ The SDK is pre-1.0. The surface is stable enough to build against, but a
296
+ MINOR bump may still carry additive changes — new methods, new fields, new
297
+ error subclasses. **Patch** releases (0.2.x) are fixes and additive types
298
+ only. Breaking changes that would stop shipping customer code are deferred to
299
+ 1.0; until then pin `driftstack-sdk~=0.2.0` rather than an exact version, and
300
+ read this file before bumping.
301
+
302
+ ## [0.1.5] - 2026-05-03
303
+
304
+ Written on 2026-09-20 from the published wheel: 0.1.5 went to PyPI without a
305
+ CHANGELOG heading of its own.
306
+
307
+ ### Added
308
+
309
+ - **`LegalAcceptanceRequiredError`** — a 409 that asks you to accept a
310
+ document version is its own exception now, carrying the pending
311
+ acceptances (`document_key` + `current_version` for each) as
312
+ `pending_acceptances`. Exported from the package root.
313
+
314
+ ## [0.1.4] - 2026-05-03
315
+
316
+ ### Added
317
+
318
+ - **`SessionTimeoutError`** — new typed error subclass mapping
319
+ the `https://errors.driftstack.dev/session-timeout` problem type
320
+ (status 504). Distinguished from `DriverError` so callers can
321
+ react specifically to "the operation didn't finish within the
322
+ per-call timeout I supplied" without conflating with downstream
323
+ driver failures. Carries `timeout_ms: int | None` from the
324
+ problem extension. Re-exported at `driftstack.SessionTimeoutError`
325
+ for convenient `isinstance` checks.
326
+
327
+ ```python
328
+ from driftstack import SessionTimeoutError
329
+
330
+ try:
331
+ client.sessions.interact(sid, body)
332
+ except SessionTimeoutError as e:
333
+ # Retry with a longer timeout, or surface to the user.
334
+ print(f"Op timed out after {e.timeout_ms} ms")
335
+ ```
336
+
337
+ - Test coverage at `tests/test_errors.py::test_session_timeout_extracts_timeout_ms`.
338
+
339
+ ## [0.1.3] - 2026-05-03
340
+
341
+ ### Removed
342
+
343
+ - `tap.offset` field stripped from the public `InteractAction.tap`
344
+ shape. Same reason as `tap_at`: a coordinate primitive on the
345
+ customer-facing schema lets the customer bypass the behavioral
346
+ simulation layer. Bounded coordinates are still coordinates.
347
+
348
+ ### Migration
349
+
350
+ If your code passes `offset={"x": ..., "y": ...}` to a `tap`
351
+ action, the value is now silently dropped (Pydantic strips unknown
352
+ keys by default). Re-express the intent through selector
353
+ specificity — better selectors, child-element targeting, ARIA-role
354
+ qualifiers, text-content matching:
355
+
356
+ ```python
357
+ # Before (0.1.x):
358
+ client.sessions.interact(
359
+ session_id,
360
+ InteractRequest(action={"kind": "tap", "selector": "button.cta", "offset": {"x": 0, "y": 50}}),
361
+ )
362
+
363
+ # After (0.1.3+):
364
+ client.sessions.interact(
365
+ session_id,
366
+ InteractRequest(action={"kind": "tap", "selector": "button.cta .icon-arrow"}),
367
+ )
368
+ ```
369
+
370
+ Coordinate-level addressing for screenshot-driven workflows lives
371
+ behind a separate endpoint gated by the `gui_control` API-key
372
+ scope, and is not exposed in this SDK.
373
+
374
+ ## [0.1.2] - 2026-05-03
375
+
376
+ ### Added
377
+
378
+ - Wire-shape regression tests at `tests/test_wire_shape.py` (10
379
+ tests). Locks the canonical JSON shape for `InteractRequest`,
380
+ `WaitRequest`, `NavigateRequest`. Asserts rejection of `tap_at` /
381
+ `type_focused` (these live behind the `gui_control`-scoped
382
+ endpoint).
383
+
384
+ ### Fixed
385
+
386
+ - `tests/test_client.py::test_version_string_matches_pyproject_default`
387
+ was pinning `__version__ == "0.0.1"` (stale from the pre-publish
388
+ era). Fixed to assert SemVer shape, not exact value.
389
+
390
+ ## [0.1.1] - 2026-05-02
391
+
392
+ ### Changed
393
+
394
+ - Re-cut: `tap_at` and `type_focused` removed from
395
+ `InteractAction`. They were briefly added in 0.1.0+ for the
396
+ self-hosted GUI's manual-control input forwarding, and reverted.
397
+ Customer-facing schemas stay intent-only — coordinate primitives
398
+ bypass the behavioral simulation layer. The GUI now uses a
399
+ separate, scope-gated endpoint
400
+ (`/v1/sessions/:id/gui-input`).
401
+
402
+ ## [0.1.0] - 2026-05-02
403
+
404
+ ### Added
405
+
406
+ - Inaugural PyPI publish (under a maintainer account pre-entity;
407
+ will transfer to a company-owned account once the legal entity
408
+ is registered).
409
+ - Pydantic models regenerated from updated OpenAPI spec.
410
+
411
+ ## [0.0.1] - 2026-05-02
412
+
413
+ ### Added
414
+
415
+ - `Driftstack` (sync) and `AsyncDriftstack` (async) clients.
416
+ - Resource accessors mounted on the client: `sessions` (9 methods),
417
+ `api_keys` (3), `usage` (1), `webhooks` (5).
418
+ - Typed Pydantic v2 models generated from the API's OpenAPI 3.1 spec.
419
+ - Error hierarchy: `DriftstackError` base + 14 subclasses covering
420
+ every documented RFC 7807 problem type. `RateLimitError`,
421
+ `ConcurrencyLimitError`, `QuotaExceededError` carry the relevant
422
+ payload fields.
423
+ - Retry policy (`RetryConfig` + `with_retry`) with exponential
424
+ backoff and full jitter; honours `Retry-After`.
425
+ - `verify_webhook_signature` helper (Stripe-style HMAC-SHA256,
426
+ constant-time).
427
+ - 85 tests covering errors, retry, webhook signatures, every
428
+ resource method, and end-to-end customer-journey workflows.
429
+ - Examples: `quickstart`, `error_handling`, `webhook_receiver`,
430
+ `langchain_tool`, `pytest_fixture`.
431
+
432
+ ### Build
433
+
434
+ - Hatchling backend, `py.typed` marker for PEP 561.
435
+ - Runtime deps: `httpx>=0.27,<1.0`, `pydantic[email]>=2.5,<3.0`.
436
+ - Dev deps: `pytest`, `pytest-asyncio`, `respx`, `ruff`, `mypy`,
437
+ `datamodel-code-generator`.
438
+ - CI: lint + format + mypy + pytest on Ubuntu / Python 3.10.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 driftstackdev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.