Skip to content

Commit 77a1316

Browse files
chore: release main (#463)
Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com> Co-authored-by: Declan Brady <declan.brady@scale.com>
1 parent 90ca889 commit 77a1316

43 files changed

Lines changed: 986 additions & 43 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.release-please-manifest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
".": "0.19.0",
3-
"adk": "0.19.0"
2+
".": "0.20.0",
3+
"adk": "0.20.0"
44
}

.stats.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
configured_endpoints: 74
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/sgp/agentex-sdk-f3f1b47fa785a0d7b4e1cce1932a2ab0c0b6531d67d499e6188292ea2a00c9d8.yml
3-
openapi_spec_hash: 77044dc5774eb88f1541ebbe53b413a7
4-
config_hash: 5c4e326f797c879e72d6bf32fc1ea49d
1+
configured_endpoints: 75
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/sgp/agentex-sdk-b2df5f506330ad5fba5a0d518ab8a4bcf876e8c3684a4fe0d0cc5102fd9c569e.yml
3+
openapi_spec_hash: 132e9efdb0535d9594abadf799431cf5
4+
config_hash: 593e89b291976a5e84e4c3c3f8324354

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@
1212

1313
* **tracing:** emit OTel metrics for async span queue depth, batch drain, and SGP export success/failure (HTTP status labels). Disable SDK-side recording with ``AGENTEX_TRACING_METRICS=0``.
1414

15+
## 0.20.0 (2026-07-16)
16+
17+
Full Changelog: [agentex-client-v0.19.0...agentex-client-v0.20.0](https://github.com/scaleapi/scale-agentex-python/compare/agentex-client-v0.19.0...agentex-client-v0.20.0)
18+
19+
### Features
20+
21+
* **api:** add interrupt task ([936a2b1](https://github.com/scaleapi/scale-agentex-python/commit/936a2b1d28a5409bd7548d59b0f18491495c805b))
22+
* **api:** add task/interrupt method and INTERRUPTED status to agents/tasks ([4f1c093](https://github.com/scaleapi/scale-agentex-python/commit/4f1c09348c2ddffeb8c78738fb9b7aa14ed4b752))
23+
* **interrupt:** task/interrupt hook + protocol + resume-safe session capture (AGX1-391) ([#462](https://github.com/scaleapi/scale-agentex-python/issues/462)) ([eaa3dd5](https://github.com/scaleapi/scale-agentex-python/commit/eaa3dd526b88c454fc604d0e617f7191b66952bb))
24+
1525
## 0.19.0 (2026-07-14)
1626

1727
Full Changelog: [agentex-client-v0.18.0...agentex-client-v0.19.0](https://github.com/scaleapi/scale-agentex-python/compare/agentex-client-v0.18.0...agentex-client-v0.19.0)

adk/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
## 0.20.0 (2026-07-16)
4+
5+
Full Changelog: [agentex-sdk-v0.19.0...agentex-sdk-v0.20.0](https://github.com/scaleapi/scale-agentex-python/compare/agentex-sdk-v0.19.0...agentex-sdk-v0.20.0)
6+
7+
### Chores
8+
9+
* **agentex-sdk:** Synchronize agentex versions
10+
311
## 0.19.0 (2026-07-14)
412

513
Full Changelog: [agentex-sdk-v0.18.0...agentex-sdk-v0.19.0](https://github.com/scaleapi/scale-agentex-python/compare/agentex-sdk-v0.18.0...agentex-sdk-v0.19.0)

adk/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# (agentex/{__init__.py, _*.py, types/, resources/}) ships from the slim
55
# sibling package `agentex-client` which is pinned as a runtime dep.
66
name = "agentex-sdk"
7-
version = "0.19.0"
7+
version = "0.20.0"
88
description = "Agent Development Kit (ADK) overlay for the Agentex API — FastACP server, Temporal workflows, LLM provider integrations, observability"
99
license = "Apache-2.0"
1010
authors = [

api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ Methods:
126126
- <code title="post /tasks/{task_id}/complete">client.tasks.<a href="./src/agentex/resources/tasks.py">complete</a>(task_id, \*\*<a href="src/agentex/types/task_complete_params.py">params</a>) -> <a href="./src/agentex/types/task.py">Task</a></code>
127127
- <code title="delete /tasks/name/{task_name}">client.tasks.<a href="./src/agentex/resources/tasks.py">delete_by_name</a>(task_name) -> <a href="./src/agentex/types/shared/delete_response.py">DeleteResponse</a></code>
128128
- <code title="post /tasks/{task_id}/fail">client.tasks.<a href="./src/agentex/resources/tasks.py">fail</a>(task_id, \*\*<a href="src/agentex/types/task_fail_params.py">params</a>) -> <a href="./src/agentex/types/task.py">Task</a></code>
129+
- <code title="post /tasks/{task_id}/interrupt">client.tasks.<a href="./src/agentex/resources/tasks.py">interrupt</a>(task_id, \*\*<a href="src/agentex/types/task_interrupt_params.py">params</a>) -> <a href="./src/agentex/types/task.py">Task</a></code>
129130
- <code title="get /tasks/{task_id}/query/{query_name}">client.tasks.<a href="./src/agentex/resources/tasks.py">query_workflow</a>(query_name, \*, task_id) -> <a href="./src/agentex/types/task_query_workflow_response.py">TaskQueryWorkflowResponse</a></code>
130131
- <code title="get /tasks/name/{task_name}">client.tasks.<a href="./src/agentex/resources/tasks.py">retrieve_by_name</a>(task_name, \*\*<a href="src/agentex/types/task_retrieve_by_name_params.py">params</a>) -> <a href="./src/agentex/types/task_retrieve_by_name_response.py">TaskRetrieveByNameResponse</a></code>
131132
- <code title="get /tasks/{task_id}/stream">client.tasks.<a href="./src/agentex/resources/tasks.py">stream_events</a>(task_id) -> object</code>

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# overlay (formerly `src/agentex/lib/*`) now lives in `adk/` and ships
44
# as the sibling `agentex-sdk` package — see `adk/pyproject.toml`.
55
name = "agentex-client"
6-
version = "0.19.0"
6+
version = "0.20.0"
77
description = "The official Python REST client for the Agentex API"
88
dynamic = ["readme"]
99
license = "Apache-2.0"

src/agentex/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
22

33
__title__ = "agentex"
4-
__version__ = "0.19.0" # x-release-please-version
4+
__version__ = "0.20.0" # x-release-please-version

src/agentex/lib/adk/_modules/_claude_code_sync.py

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,35 @@ def _extract_summary(text: str, max_len: int = 300) -> str:
7474
async def convert_claude_code_to_agentex_events(
7575
lines: AsyncIterator[str | dict[str, Any]],
7676
on_result: Callable[[dict[str, Any]], Awaitable[None]] | None = None,
77+
on_init: Callable[[dict[str, Any]], Awaitable[None]] | None = None,
78+
) -> AsyncIterator[StreamTaskMessageStart | StreamTaskMessageDelta | StreamTaskMessageFull | StreamTaskMessageDone]:
79+
"""Public tap: convert a claude-code ``stream-json`` line stream to events.
80+
81+
Thin wrapper over :func:`_convert_claude_code_impl` that owns the
82+
cancellation backstop: a ``finally`` closes the underlying ``lines`` iterator
83+
(when it exposes ``aclose``) whenever this generator is closed — including on
84+
the ``GeneratorExit``/``CancelledError`` raised when a consuming task is
85+
cancelled mid-turn by an interrupt. This terminates the CLI stdout handle /
86+
subprocess instead of leaking it. Kept as a wrapper (rather than a
87+
``try/finally`` inside the impl) so the large parser body stays untouched.
88+
"""
89+
inner = _convert_claude_code_impl(lines, on_result=on_result, on_init=on_init)
90+
try:
91+
async for event in inner:
92+
yield event
93+
finally:
94+
inner_aclose = getattr(inner, "aclose", None)
95+
if inner_aclose is not None:
96+
await inner_aclose()
97+
aclose = getattr(lines, "aclose", None)
98+
if aclose is not None:
99+
await aclose()
100+
101+
102+
async def _convert_claude_code_impl(
103+
lines: AsyncIterator[str | dict[str, Any]],
104+
on_result: Callable[[dict[str, Any]], Awaitable[None]] | None = None,
105+
on_init: Callable[[dict[str, Any]], Awaitable[None]] | None = None,
77106
) -> AsyncIterator[StreamTaskMessageStart | StreamTaskMessageDelta | StreamTaskMessageFull | StreamTaskMessageDone]:
78107
"""Convert a claude-code ``stream-json`` line stream into Agentex ``StreamTaskMessage*`` events.
79108
@@ -85,7 +114,20 @@ async def convert_claude_code_to_agentex_events(
85114
caller can capture usage and cost. It is awaited before the generator
86115
continues. When ``None``, the result envelope is silently dropped.
87116
117+
``on_init`` is called with the ``system``/``init`` envelope the moment it
118+
arrives (the FIRST envelope of a claude-code stream), so the caller can
119+
capture ``session_id`` EARLY — before the turn completes. This is what makes
120+
an interrupted-before-completion turn resumable: the terminal ``result``
121+
envelope (which ``on_result`` reads) never arrives when a turn is cut short,
122+
so relying on it alone loses the session id. Mirrors how the inline
123+
``claude_agents`` activity captures session_id from its SystemMessage init.
124+
When ``None``, the init envelope's session metadata is not surfaced early.
125+
88126
Envelope → canonical mapping is documented in this module's docstring.
127+
128+
A ``finally`` closes the underlying ``lines`` iterator (when it exposes
129+
``aclose``) as a backstop, so a cancellation mid-turn (e.g. an interrupt that
130+
cancels the consuming task) does not leak the CLI stdout handle / subprocess.
89131
"""
90132
next_index = 0
91133
tool_call_count = 0
@@ -355,9 +397,14 @@ async def convert_claude_code_to_agentex_events(
355397
# system / init — session metadata (ignored at this layer)
356398
# -----------------------------------------------------------------------
357399
elif evt_type == "system":
358-
# Session ID tracking and MCP status logging are provider concerns.
359-
# This pure parser layer intentionally emits nothing for system events.
360-
pass
400+
# Session ID tracking and MCP status logging are provider concerns:
401+
# this pure parser layer emits no StreamTaskMessage for system events.
402+
# It DOES surface the init envelope's session metadata early via
403+
# on_init so a caller can capture session_id before the turn's
404+
# terminal ``result`` arrives — required for resuming a turn that was
405+
# interrupted before completion (no ``result`` is ever emitted then).
406+
if on_init is not None and evt.get("subtype") == "init":
407+
await on_init(evt)
361408

362409
# -----------------------------------------------------------------------
363410
# result — carries usage + cost; fired to on_result, not emitted as msgs

src/agentex/lib/adk/_modules/_claude_code_turn.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,31 +118,45 @@ class ClaudeCodeTurn:
118118
def __init__(self, lines: AsyncIterator[str | dict[str, Any]]) -> None:
119119
self._lines = lines
120120
self._result_envelope: dict[str, Any] | None = None
121+
# session_id captured from the early system/init envelope. This is what
122+
# keeps an interrupted-before-completion turn resumable: the terminal
123+
# ``result`` envelope never arrives when a turn is cut short, so we must
124+
# capture the session id up front (from init) rather than only at the end.
125+
self._init_session_id: str | None = None
121126
self._events_stream: AsyncIterator[StreamTaskMessage] | None = None
122127

123128
async def _on_result(self, envelope: dict[str, Any]) -> None:
124129
self._result_envelope = envelope
125130

131+
async def _on_init(self, envelope: dict[str, Any]) -> None:
132+
sid = envelope.get("session_id")
133+
if sid:
134+
self._init_session_id = sid
135+
126136
@property
127137
def events(self) -> AsyncIterator[StreamTaskMessage]:
128138
if self._events_stream is None:
129139
self._events_stream = convert_claude_code_to_agentex_events(
130140
self._lines,
131141
on_result=self._on_result,
142+
on_init=self._on_init,
132143
)
133144
return self._events_stream
134145

135146
@property
136147
def session_id(self) -> str | None:
137148
"""The Claude Code session id, for resuming a multi-turn session.
138149
139-
Valid only after ``events`` has been fully consumed (populated by the
140-
``result`` envelope). Returns ``None`` if the stream was truncated or
141-
Claude Code reported no session id.
150+
Prefers the id from the terminal ``result`` envelope (fully-complete
151+
turn), then falls back to the id captured from the early ``system/init``
152+
envelope. The init fallback is what makes a turn that was interrupted
153+
before completion still resumable (no ``result`` is emitted then).
154+
Returns ``None`` only if neither envelope was seen (e.g. the stream was
155+
truncated before init) or Claude Code reported no session id.
142156
"""
143-
if not self._result_envelope:
144-
return None
145-
return self._result_envelope.get("session_id")
157+
if self._result_envelope and self._result_envelope.get("session_id"):
158+
return self._result_envelope.get("session_id")
159+
return self._init_session_id
146160

147161
def usage(self) -> TurnUsage:
148162
"""Return normalised usage for this turn.

0 commit comments

Comments
 (0)