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.
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/CHANGELOG.md +189 -0
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/PKG-INFO +2 -2
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/pyproject.toml +5 -2
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/__init__.py +2 -0
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/_generated/models.py +410 -1158
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/_version.py +1 -1
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/resources/agent_sessions.py +232 -0
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/resources/sessions.py +7 -0
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/.gitignore +0 -0
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/LICENSE +0 -0
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/README.md +0 -0
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/_generated/__init__.py +0 -0
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/client.py +0 -0
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/errors.py +0 -0
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/http.py +0 -0
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/pagination.py +0 -0
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/py.typed +0 -0
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/resources/__init__.py +0 -0
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/resources/_common.py +0 -0
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/resources/_session_search_login.py +0 -0
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/resources/account.py +0 -0
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/resources/archetypes.py +0 -0
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/resources/egress.py +0 -0
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/resources/profile_snapshots.py +0 -0
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/resources/profiles.py +0 -0
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/resources/recipes.py +0 -0
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/resources/support.py +0 -0
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/resources/usage.py +0 -0
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/resources/webhooks.py +0 -0
- {driftstack_sdk-0.4.0 → driftstack_sdk-0.5.0}/src/driftstack/retry.py +0 -0
- {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.
|
|
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]
|
|
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.
|
|
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
|
-
|
|
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",
|