driftstack-sdk 0.4.0__tar.gz → 0.5.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 (31) hide show
  1. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/CHANGELOG.md +189 -0
  2. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/PKG-INFO +2 -2
  3. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/pyproject.toml +5 -2
  4. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/__init__.py +2 -0
  5. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/_generated/models.py +410 -1158
  6. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/_version.py +1 -1
  7. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/resources/agent_sessions.py +232 -0
  8. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/resources/sessions.py +7 -0
  9. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/.gitignore +0 -0
  10. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/LICENSE +0 -0
  11. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/README.md +0 -0
  12. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/_generated/__init__.py +0 -0
  13. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/client.py +0 -0
  14. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/errors.py +0 -0
  15. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/http.py +0 -0
  16. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/pagination.py +0 -0
  17. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/py.typed +0 -0
  18. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/resources/__init__.py +0 -0
  19. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/resources/_common.py +0 -0
  20. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/resources/_session_search_login.py +0 -0
  21. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/resources/account.py +0 -0
  22. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/resources/archetypes.py +0 -0
  23. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/resources/egress.py +0 -0
  24. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/resources/profile_snapshots.py +0 -0
  25. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/resources/profiles.py +0 -0
  26. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/resources/recipes.py +0 -0
  27. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/resources/support.py +0 -0
  28. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/resources/usage.py +0 -0
  29. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/resources/webhooks.py +0 -0
  30. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/retry.py +0 -0
  31. {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/webhook_signature.py +0 -0
@@ -6,6 +6,195 @@ follows [SemVer](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.5.0] - 2026-10-04
10
+
11
+ **The client methods remove nothing; the generated models change shape.**
12
+ Every method, error class and exported name 0.4.0 published is still there,
13
+ and the methods still return plain `dict`s. If you import classes from
14
+ `driftstack._generated.models` to validate or build a turn's `intents` or
15
+ `results`, read **Changed — BREAKING** below first. The headline is
16
+ `agent_sessions.run_steps()`: run a list of steps you write, as written, with
17
+ no AI planning. Some answers change for the same request — an empty `type`
18
+ value now clears the field, and `ok` is `False` in more cases; read
19
+ **Changed** below before upgrading. Every change below applies to both
20
+ `Driftstack` and `AsyncDriftstack`.
21
+
22
+ ### Changed — BREAKING
23
+
24
+ - **The generated models for a step and its result are one class each.** The
25
+ published spec now references `AgentIntent` and `IntentResult` from every
26
+ turn result, list-of-steps result and conflict problem, where each of those
27
+ used to carry its own copy. In `driftstack._generated.models`, `intents` is
28
+ `list[AgentIntent]`, `results` and `partial_results` are
29
+ `list[IntentResult]`, and an `IntentResult`'s `intent` is an `AgentIntent`.
30
+ The 135 numbered copies are gone: `Intent` and `Intent1` to `Intent107`,
31
+ `Intents` and `Intents1` to `Intents17`, `Results` and `Results1` to
32
+ `Results5`, and `PartialResults`, `PartialResults1` and `PartialResults2`.
33
+ `AgentIntent` and `IntentResult` are root models: read the step or result
34
+ through `.root` (`response.intents[0].root.kind`,
35
+ `response.results[0].root.intent.root.kind`), and replace an import or an
36
+ `isinstance` check of a numbered copy with `AgentIntent` or `IntentResult`.
37
+ Nothing changes for the client methods, which return the response as a
38
+ `dict`, and nothing changes on the wire.
39
+
40
+ ### Added
41
+
42
+ - **`agent_sessions.run_steps(agent_session_id, steps, ...)`: run steps you
43
+ already know, without the AI** (`POST /v1/agent-sessions/{id}/steps`), sync
44
+ and async. `steps` is a list of up to 8 step dicts, the vocabulary a
45
+ message's `intents` use; they run in order, as written, through the same
46
+ checks a message's steps go through, with no planning, no read-back and none
47
+ of the AI budget (no `usage` and no `answer` on the result). The result is a
48
+ `dict` keyed by `kind`: `plan-executed` (`intents`, `results`, `ok`, and
49
+ `notice` / `notice_reason` when the run ended early for a reason no step
50
+ says) or `stopped` when you called `stop()`. A step the session cannot run —
51
+ one outside the vocabulary, an address on a private network or carrying a
52
+ user name or password, a key no iPhone keyboard has, a native list, a frame
53
+ the page did not list or this device cannot act in — is a failed row in
54
+ `results` whose `reason` says why; nothing after it is sent, and the steps
55
+ after it are listed in `intents` with no result. A `type` step with text
56
+ adds to what the field holds; one with an empty `value` clears it. A wait
57
+ that times out does not stop the run. More than 8 steps raises a 400
58
+ `ValidationError`; nothing is cut. `approve_consequential_actions` approves
59
+ a step a previous call stopped at (`confirmation_required`); it counts only
60
+ when that call is the session's latest and `steps` is the rest of its list
61
+ from the stopped step on. `idempotency_key`, `timeout_s`, `on_step` and
62
+ `on_event` work as on `message()`; a key used for a message cannot be reused
63
+ here. It needs an account key with `write`, and shares the message rate
64
+ limit and the running-turns limit with `message()`. A session a person has
65
+ control of, or one in manual mode, raises a 409 `ConflictError`
66
+ (`ai_control_unavailable`) and nothing runs. New generated models describe
67
+ the result: `AgentStepsResponse` (a root model over `AgentStepsResponse1`,
68
+ `plan-executed`, and `AgentStepsResponse2`, `stopped`, with the numbered
69
+ `Session6`, `Session7`, `CapabilityReport6` and `CapabilityReport7` they
70
+ carry) and `AgentStepsResponseStream`.
71
+
72
+ - **A transcript entry's `origin`.** `"steps"` on the one entry a `run_steps`
73
+ call writes (steps you sent and ran as written, not planned by the agent);
74
+ absent on every other entry.
75
+
76
+ - **A turn's `answer` can name the selector of any control on the page, and
77
+ is no longer cut at 512 characters.** Asked "what is the selector of the Pay
78
+ button?" or to list a page's buttons with their selectors, the answer quotes
79
+ the selectors the agent was shown for the page it ended on (`#pay`).
80
+ `answer` is now at most 4,000 characters (it was cut at 512, which could stop
81
+ inside a selector); a longer one is cut after its last whole line and ends
82
+ with `… (the rest was cut for length)` on a line of its own. Its line breaks
83
+ are kept, so a list asked for one control per line comes back one per line;
84
+ the transcript entry holds the same text on one line. A CSS selector written
85
+ in the message is used exactly as written.
86
+
87
+ - **An `extract` step can read one attribute of an element.** A step with
88
+ `"kind": "extract"` and a `"selector"` can carry `"attribute"` — never with
89
+ `"body"` or a frame — naming the attribute that was read instead of the
90
+ element's text (`"href"`, `"src"`, `"data-order-id"`; the generated model's
91
+ field allows 1-128 characters of an attribute name). The step's summary reads
92
+ `extracted attribute href from #terms: …`, and an element without the
93
+ attribute is said to have none
94
+ (`extracted nothing: the element #terms has no href attribute`), which is not
95
+ the same answer as a selector that matched nothing.
96
+
97
+ - **`Session.proxy_id`: which of your saved proxies a session runs through.**
98
+ Additive. `sessions.create()`, `sessions.get()`, `sessions.list()` and
99
+ `profiles.launch()` now report the saved proxy the session's traffic goes
100
+ out through: the `proxy_id` the create named, or the proxy the launched
101
+ profile is bound to (on a team, a session an admin started with a proxy
102
+ saved on the admin's own account reports that admin's proxy). It is kept
103
+ after the session ends. `None` for a session started without a saved proxy,
104
+ and when it is not reported: on a read by a team member without admin role,
105
+ on a read that could not look it up at that moment, and on an older
106
+ server. `None` alone does not tell those apart; this does:
107
+ `"proxy_id" in session.model_fields_set` is true only when the server sent
108
+ the key. A `None` with the key present means no saved proxy, and a `None`
109
+ without it means not reported (never read that as "no proxy"). Before
110
+ this, a session started through a proxy did not say which one.
111
+
112
+ - **On a session whose device supports it, a tap or a wait can name a frame.**
113
+ Additive, and nothing changes on any other session. On a session whose
114
+ device can act inside an embedded frame (an iframe), a step in a turn's
115
+ `intents` can carry `frame` on an `interact` with `"action": "tap"`, and on a
116
+ `wait` with `"condition": "selector_visible"` — the generated `wait` models
117
+ gain an optional `frame`, the same path of positions as a read's. The tap's
118
+ selector is matched, and the element is waited for, inside that frame. A
119
+ `"scroll"` or `"press"` never carries `frame`: both act on the page itself.
120
+ Such a step succeeded only when the session confirmed it was done inside
121
+ exactly that frame; one it did not confirm fails and is not tried again
122
+ (`diagnosis.category` `"unknown"` for a tap, `"condition_not_met"` for a
123
+ wait). A tap inside a frame that pays, buys or deletes an account asks for
124
+ approval as one on the page does.
125
+
126
+ - **Session secrets: `agent_sessions.register_secret()`, `list_secrets()` and
127
+ `delete_secret()`**, sync and async. `register_secret` takes the session id
128
+ and the keyword arguments `label`, `secret` and `sites` (and optionally
129
+ `include_subdomains` and `ttl_seconds`), holds a password, a one-time code or
130
+ a payment card number for that one agent session, and returns its handle
131
+ (`sec_…`); write `{{credential:<handle>}}` in a `message()` as the whole value
132
+ of the step that types it. The plan, the transcript and every response carry
133
+ the handle, and the value is typed only on an `https://` page on one of
134
+ `sites`. Once typed it is on the page; where the page shows it again it is
135
+ replaced by its placeholder in what the model is shown, the step results and
136
+ the transcript, as for a saved credential (a card number however its digits
137
+ are grouped, but not part of it). `label` appears in refusals the model reads,
138
+ so put nothing secret in it. It is held in server memory only and dropped
139
+ when the session ends, when deleted, when the service restarts, or when
140
+ `ttl_seconds` runs out (default 3600); one a step has typed, or tried to, is
141
+ kept past `ttl_seconds`, never typed again, only to keep hiding it from the
142
+ model. The answer is an `AgentSessionSecret` (exported from `driftstack`);
143
+ the generated models gain `CreateAgentSessionSecret`, `AgentSessionSecret`
144
+ and `AgentSessionSecretList`.
145
+ `register_secret` is not retried on a network error, since a retry could
146
+ register the value twice.
147
+
148
+ - **`frame_match` on `sessions.capture`** (the generated `FrameMatch` model, or a
149
+ dict). With `kind="dom_snapshot"`, reads one embedded frame (an iframe) named
150
+ by its address instead of the page: `host` (required; exact, letter case
151
+ aside, port not compared), `path_prefix` (whole path segments) and `query`
152
+ (each parameter with exactly that value; at most 10). Exactly one frame must
153
+ match. With none or several, nothing is read, the session stays ready, and
154
+ the call raises a 409 `ConflictError` whose `problem["code"]` is
155
+ `"frame_not_found"`, `"frame_match_ambiguous"` (`problem["matched_frames"]`
156
+ is the count) or `"frame_match_unconfirmed"`. A `query` parameter whose value
157
+ the browser removes before it reaches Driftstack (`client_secret`, `token`,
158
+ `code_verifier`, ...; the full list is in the Sessions reference), or
159
+ `frame_match` on a screenshot or a PDF, raises a 400 `BadRequestError`; a key
160
+ `frame_match` does not know, a 400 `ValidationError` (built with the
161
+ `FrameMatch` model, pydantic refuses it before anything is sent). A session started with a `proxy_id` cannot run
162
+ it yet, like every step-by-step operation.
163
+
164
+ ### Changed
165
+
166
+ - **A `type` step with an empty `value` clears the field.** The session
167
+ deletes what the field holds, one key at a time as a person would, types
168
+ nothing, and the step's summary says `cleared #email`, never `typed`. To
169
+ replace what a field holds, send an empty `type` and then one with the new
170
+ text; the agent now plans a replacement that way. A `type` step with text
171
+ still adds to what the field holds. A field that cannot be emptied key by
172
+ key (over 200 characters, or one whose page puts characters back, such as a
173
+ fixed prefix or an input mask) is not cleared and the step fails with
174
+ nothing typed; a session whose browser cannot empty a field refuses the
175
+ step without sending it, and the turn stops there.
176
+ - **`ok` is `False` in more cases.** A planned step that would have acted on
177
+ the page but could not be sent is now a failed row
178
+ (`diagnosis.category` `"invalid_request"`) that halts its plan like any
179
+ failed step; the steps planned after it are listed in `intents` with no
180
+ result instead of disappearing, and `ok` stays `False` unless a later step
181
+ acts on the page. `ok` is also `False` when the turn delivered nothing — no
182
+ step that changed the page or scrolled it, no screenshot and no `answer` —
183
+ unless the message only asked to wait, and when an English message asks for
184
+ more things to be done than distinct steps that changed the page ran. Every
185
+ entry of `results` can then be a success: read `notice`, or
186
+ `answer_unavailable` when the answer asked for could not be read.
187
+ - **`recipes.create()` leaves out steps that never ran.** A recipe's
188
+ `intent_log` no longer includes a step the agent planned but could not send,
189
+ nor the steps planned after it in that plan.
190
+ - **The `refuse` result is documented in full.** No step was planned or run
191
+ and the session stays active, but the turn is still recorded: the refusal is
192
+ added to the transcript, and the tokens used to read the message are charged
193
+ to the session's token budget like any other turn (not when the AI was
194
+ briefly unavailable or the session stopped answering). Resending cannot
195
+ repeat anything on the page, but each resend is a new turn; use a new
196
+ idempotency key. Nothing about the response changed.
197
+
9
198
  ## [0.4.0] - 2026-10-03
10
199
 
11
200
  **Breaking: the SDK now covers what a program needs to run Driftstack, and
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: driftstack-sdk
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Driftstack Python SDK — iPhone Safari browser automation. Import as `driftstack`.
5
5
  Project-URL: Homepage, https://driftstack.io
6
6
  Project-URL: Repository, https://github.com/driftstackdev/driftstack-api
@@ -26,7 +26,7 @@ Requires-Python: >=3.10
26
26
  Requires-Dist: httpx<1.0,>=0.27
27
27
  Requires-Dist: pydantic[email]<3.0,>=2.5
28
28
  Provides-Extra: dev
29
- Requires-Dist: datamodel-code-generator[http]>=0.25; extra == 'dev'
29
+ Requires-Dist: datamodel-code-generator[http]==0.83.0; extra == 'dev'
30
30
  Requires-Dist: mypy>=1.10; extra == 'dev'
31
31
  Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
32
32
  Requires-Dist: pytest>=8.0; extra == 'dev'
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "driftstack-sdk"
7
- version = "0.4.0"
7
+ version = "0.5.0"
8
8
  description = "Driftstack Python SDK — iPhone Safari browser automation. Import as `driftstack`."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -46,7 +46,10 @@ dev = [
46
46
  # Bump this deliberately, running `ruff format` in the same commit.
47
47
  "ruff==0.15.13",
48
48
  "mypy>=1.10",
49
- "datamodel-code-generator[http]>=0.25",
49
+ # Pinned (2026-10-04): 0.56.1 silently dropped the `extra="allow"` config
50
+ # of the Problem models (a problem's extra fields were thrown away) while
51
+ # 0.83.0 keeps it. A different version must regenerate in the same commit.
52
+ "datamodel-code-generator[http]==0.83.0",
50
53
  ]
51
54
 
52
55
  [project.urls]
@@ -64,6 +64,7 @@ from driftstack.errors import (
64
64
  from driftstack.resources.agent_sessions import (
65
65
  AgentCapture,
66
66
  AgentNoticeReason,
67
+ AgentSessionSecret,
67
68
  AgentTranscriptEvent,
68
69
  LiveKitInfo,
69
70
  )
@@ -127,6 +128,7 @@ __all__ = [
127
128
  "DeviceUnavailableError",
128
129
  "LiveKitInfo",
129
130
  "AgentCapture",
131
+ "AgentSessionSecret",
130
132
  "AgentNoticeReason",
131
133
  "AgentTranscriptEvent",
132
134
  "ListArchetypesResponse",