qoder-agent-sdk 1.0.14__tar.gz → 1.0.16__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 (41) hide show
  1. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/.gitignore +1 -0
  2. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/PKG-INFO +33 -1
  3. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/README.md +32 -0
  4. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/pyproject.toml +1 -1
  5. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/__init__.py +64 -0
  6. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/_cli_version.py +1 -1
  7. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/_internal/client.py +3 -0
  8. qoder_agent_sdk-1.0.16/src/qoder_agent_sdk/_internal/memory.py +186 -0
  9. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/_internal/message_parser.py +36 -8
  10. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/_internal/query.py +131 -4
  11. qoder_agent_sdk-1.0.16/src/qoder_agent_sdk/_tool_results.py +38 -0
  12. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/_version.py +1 -1
  13. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/client.py +67 -0
  14. qoder_agent_sdk-1.0.16/src/qoder_agent_sdk/memory.py +142 -0
  15. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/query.py +4 -0
  16. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/types.py +192 -2
  17. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/LICENSE +0 -0
  18. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/_brand.py +0 -0
  19. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/_bundled/.gitignore +0 -0
  20. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/_errors.py +0 -0
  21. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/_internal/__init__.py +0 -0
  22. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/_internal/auth_helpers.py +0 -0
  23. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/_internal/cloud_agent.py +0 -0
  24. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/_internal/mcp_handler.py +0 -0
  25. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/_internal/mcp_serialize.py +0 -0
  26. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/_internal/sdk_mcp_transport.py +0 -0
  27. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/_internal/session_mutations.py +0 -0
  28. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/_internal/session_storage.py +0 -0
  29. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/_internal/sessions.py +0 -0
  30. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/_internal/skill_evolution.py +0 -0
  31. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/_internal/transport/__init__.py +0 -0
  32. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/_internal/transport/subprocess_cli.py +0 -0
  33. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/auth.py +0 -0
  34. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/plugins.py +0 -0
  35. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/py.typed +0 -0
  36. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/session_import.py +0 -0
  37. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/session_store.py +0 -0
  38. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/settings.py +0 -0
  39. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/store_sessions.py +0 -0
  40. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/testing/__init__.py +0 -0
  41. {qoder_agent_sdk-1.0.14 → qoder_agent_sdk-1.0.16}/src/qoder_agent_sdk/testing/session_store_conformance.py +0 -0
@@ -27,6 +27,7 @@ ENV/
27
27
  env/
28
28
  .venv
29
29
  .venv-release/
30
+ .venv-e2e-build/
30
31
  .tmp-brand-packages/
31
32
  .tmp-qoder-python-release-plan.json
32
33
  uv.lock
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: qoder-agent-sdk
3
- Version: 1.0.14
3
+ Version: 1.0.16
4
4
  Summary: Python SDK for Qoder Agent
5
5
  Author: Qoder
6
6
  License: Copyright (c) 2026 Qoder
@@ -315,6 +315,34 @@ cancellation, and do not reuse UUIDs within a session.
315
315
  `await client.cancel_async_message(message_uuid)` returns `True` when the
316
316
  queued message is cancelled and `False` when it can no longer be cancelled.
317
317
 
318
+ Manage background tasks from a connected `QoderSDKClient`:
319
+
320
+ ```python
321
+ await client.background_tasks(tool_use_id) # ToolUseBlock.id; omit for bulk conversion
322
+ await client.stop_task(task_id) # TaskStartedMessage.task_id
323
+ ```
324
+
325
+ `background_tasks()` moves running foreground Bash / Agent executions into the
326
+ background; it does not list tasks. A targeted call returns whether the execution
327
+ moved; bulk `True` acknowledges the request without guaranteeing a transition.
328
+ `stop_task()` returns `None` and leaves the main turn and other independent tasks
329
+ running. Unknown IDs and repeated stops are safe no-ops. Both methods require the
330
+ CLI capability `background_tasks_v1` (otherwise `UnsupportedCliCapabilityError`)
331
+ and are unavailable in Cloud Agent sessions.
332
+
333
+ `interrupt()` does not stop existing background tasks. To clean them up, continue
334
+ consuming `receive_messages()` after the main `ResultMessage`, replace the roster
335
+ on each `BackgroundTasksChangedMessage`, and stop each task separately. Bulk
336
+ conversion can detach an Agent's Bash execution as an independent task.
337
+
338
+ Use `AgentDefinition(background=True, ...)` to launch delegated agents in the
339
+ background. Merge `TaskUpdatedMessage.patch` into stored task state. Task events
340
+ retain raw `data`; optional metadata depends on the CLI. Missing progress `usage`
341
+ is `None`, and `total_tokens` can be absent within usage.
342
+
343
+ For event fields, launch result types, the Bash result guard, and examples, see
344
+ [Python SDK Reference](https://docs.qoder.com/cli/sdk/references-python#clientstop-task).
345
+
318
346
  ## External Session Storage
319
347
 
320
348
  Use `session_store` when a host needs durable transcripts outside the local
@@ -405,6 +433,10 @@ against shared storage operated by your application, then validate its
405
433
  append/load ordering, project isolation, subkey handling, and deletion behavior
406
434
  with `run_session_store_conformance`.
407
435
 
436
+ ## Memory
437
+
438
+ Configure native or custom memory with `QoderAgentOptions(memory=...)`. Interactive clients expose `flush_memory()` and `refresh_memory()`. See [Memory](doc/memory.md) for configuration, generation callbacks, and lifecycle behavior.
439
+
408
440
  ## Custom Tools
409
441
 
410
442
  You can expose Python functions to Qoder Agent as in-process SDK MCP servers.
@@ -273,6 +273,34 @@ cancellation, and do not reuse UUIDs within a session.
273
273
  `await client.cancel_async_message(message_uuid)` returns `True` when the
274
274
  queued message is cancelled and `False` when it can no longer be cancelled.
275
275
 
276
+ Manage background tasks from a connected `QoderSDKClient`:
277
+
278
+ ```python
279
+ await client.background_tasks(tool_use_id) # ToolUseBlock.id; omit for bulk conversion
280
+ await client.stop_task(task_id) # TaskStartedMessage.task_id
281
+ ```
282
+
283
+ `background_tasks()` moves running foreground Bash / Agent executions into the
284
+ background; it does not list tasks. A targeted call returns whether the execution
285
+ moved; bulk `True` acknowledges the request without guaranteeing a transition.
286
+ `stop_task()` returns `None` and leaves the main turn and other independent tasks
287
+ running. Unknown IDs and repeated stops are safe no-ops. Both methods require the
288
+ CLI capability `background_tasks_v1` (otherwise `UnsupportedCliCapabilityError`)
289
+ and are unavailable in Cloud Agent sessions.
290
+
291
+ `interrupt()` does not stop existing background tasks. To clean them up, continue
292
+ consuming `receive_messages()` after the main `ResultMessage`, replace the roster
293
+ on each `BackgroundTasksChangedMessage`, and stop each task separately. Bulk
294
+ conversion can detach an Agent's Bash execution as an independent task.
295
+
296
+ Use `AgentDefinition(background=True, ...)` to launch delegated agents in the
297
+ background. Merge `TaskUpdatedMessage.patch` into stored task state. Task events
298
+ retain raw `data`; optional metadata depends on the CLI. Missing progress `usage`
299
+ is `None`, and `total_tokens` can be absent within usage.
300
+
301
+ For event fields, launch result types, the Bash result guard, and examples, see
302
+ [Python SDK Reference](https://docs.qoder.com/cli/sdk/references-python#clientstop-task).
303
+
276
304
  ## External Session Storage
277
305
 
278
306
  Use `session_store` when a host needs durable transcripts outside the local
@@ -363,6 +391,10 @@ against shared storage operated by your application, then validate its
363
391
  append/load ordering, project isolation, subkey handling, and deletion behavior
364
392
  with `run_session_store_conformance`.
365
393
 
394
+ ## Memory
395
+
396
+ Configure native or custom memory with `QoderAgentOptions(memory=...)`. Interactive clients expose `flush_memory()` and `refresh_memory()`. See [Memory](doc/memory.md) for configuration, generation callbacks, and lifecycle behavior.
397
+
366
398
  ## Custom Tools
367
399
 
368
400
  You can expose Python functions to Qoder Agent as in-process SDK MCP servers.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "qoder-agent-sdk"
7
- version = "1.0.14"
7
+ version = "1.0.16"
8
8
  description = "Python SDK for Qoder Agent"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -50,6 +50,7 @@ from ._internal.sessions import (
50
50
  project_key_for_directory,
51
51
  )
52
52
  from ._internal.transport import Transport
53
+ from ._tool_results import is_bash_tool_background_launch_result
53
54
  from ._version import __version__
54
55
  from .auth import (
55
56
  DEFAULT_ACCESS_TOKEN_ENV_VAR,
@@ -78,6 +79,29 @@ from .auth import (
78
79
  service_account_from_env,
79
80
  )
80
81
  from .client import QoderSDKClient
82
+ from .memory import (
83
+ EffectiveMemoryConfig,
84
+ EffectiveMemoryRoot,
85
+ MemoryConsumptionCallback,
86
+ MemoryConsumptionFile,
87
+ MemoryConsumptionFileResult,
88
+ MemoryConsumptionOptions,
89
+ MemoryConsumptionResult,
90
+ MemoryGateContext,
91
+ MemoryGenerationCallback,
92
+ MemoryGenerationFailedFile,
93
+ MemoryGenerationGateInput,
94
+ MemoryGenerationGateResult,
95
+ MemoryGenerationOptions,
96
+ MemoryGenerationResult,
97
+ MemoryMode,
98
+ MemoryOptions,
99
+ MemoryRoot,
100
+ MemoryRootAccess,
101
+ MemoryShouldGenerate,
102
+ MemoryTurnCompleteOptions,
103
+ MemoryWrittenFile,
104
+ )
81
105
  from .plugins import (
82
106
  PluginError,
83
107
  disable_plugin,
@@ -106,10 +130,14 @@ from .types import (
106
130
  MAX_SKILL_EVOLUTION_REVIEW_THRESHOLD,
107
131
  AgentCreateParams,
108
132
  AgentDefinition,
133
+ AgentToolBackgroundLaunchResult,
109
134
  AssistantMessage,
110
135
  AssistantMessageWithRequestId,
111
136
  AsyncHookJSONOutput,
137
+ BackgroundTask,
138
+ BackgroundTasksChangedMessage,
112
139
  BaseHookInput,
140
+ BashToolBackgroundLaunchResult,
113
141
  BetaManagedAgentsAgentToolName,
114
142
  BetaManagedAgentsAgentToolset20260401Params,
115
143
  BetaManagedAgentsCustomSkillParams,
@@ -186,6 +214,8 @@ from .types import (
186
214
  McpStatusResponse,
187
215
  McpToolAnnotations,
188
216
  McpToolInfo,
217
+ MemoryConsumptionMessage,
218
+ MemoryGenerationMessage,
189
219
  Message,
190
220
  ModelContextConfig,
191
221
  ModelContextWindowEntry,
@@ -306,6 +336,9 @@ from .types import (
306
336
  TaskNotificationStatus,
307
337
  TaskProgressMessage,
308
338
  TaskStartedMessage,
339
+ TaskStatus,
340
+ TaskUpdatedMessage,
341
+ TaskUpdatePatch,
309
342
  TaskUsage,
310
343
  TextBlock,
311
344
  ThinkingBlock,
@@ -844,6 +877,29 @@ def create_sdk_mcp_server(
844
877
 
845
878
 
846
879
  __all__ = [
880
+ "EffectiveMemoryConfig",
881
+ "EffectiveMemoryRoot",
882
+ "MemoryMode",
883
+ "MemoryRootAccess",
884
+ "MemoryRoot",
885
+ "MemoryConsumptionFile",
886
+ "MemoryGenerationGateInput",
887
+ "MemoryGenerationGateResult",
888
+ "MemoryGateContext",
889
+ "MemoryWrittenFile",
890
+ "MemoryGenerationFailedFile",
891
+ "MemoryGenerationResult",
892
+ "MemoryConsumptionFileResult",
893
+ "MemoryConsumptionResult",
894
+ "MemoryShouldGenerate",
895
+ "MemoryGenerationCallback",
896
+ "MemoryConsumptionCallback",
897
+ "MemoryTurnCompleteOptions",
898
+ "MemoryGenerationOptions",
899
+ "MemoryConsumptionOptions",
900
+ "MemoryOptions",
901
+ "MemoryGenerationMessage",
902
+ "MemoryConsumptionMessage",
847
903
  "GoalClearedMessage",
848
904
  "GoalSnapshot",
849
905
  "GoalStatus",
@@ -935,6 +991,14 @@ __all__ = [
935
991
  "TaskNotificationMessage",
936
992
  "TaskNotificationStatus",
937
993
  "TaskUsage",
994
+ "TaskStatus",
995
+ "TaskUpdatePatch",
996
+ "TaskUpdatedMessage",
997
+ "BackgroundTask",
998
+ "BackgroundTasksChangedMessage",
999
+ "BashToolBackgroundLaunchResult",
1000
+ "AgentToolBackgroundLaunchResult",
1001
+ "is_bash_tool_background_launch_result",
938
1002
  "ResultMessage",
939
1003
  "StreamEvent",
940
1004
  "CloudAgentEventMessage",
@@ -1,3 +1,3 @@
1
1
  """Bundled runtime CLI version."""
2
2
 
3
- __cli_version__ = "1.1.38"
3
+ __cli_version__ = "1.1.76"
@@ -16,6 +16,7 @@ from .auth_helpers import (
16
16
  get_fetch_service_account_token,
17
17
  once_callback,
18
18
  )
19
+ from .memory import prepare_memory_options
19
20
  from .message_parser import parse_message
20
21
  from .query import Query, resolve_initialize_timeout
21
22
  from .session_storage import (
@@ -101,6 +102,7 @@ class InternalClient:
101
102
  configured_options, on_auth_expired=on_auth_expired
102
103
  )
103
104
 
105
+ prepared_memory = prepare_memory_options(configured_options.memory)
104
106
  requires_store_resume = configured_options.session_store is not None and (
105
107
  configured_options.resume is not None
106
108
  or configured_options.continue_conversation
@@ -170,6 +172,7 @@ class InternalClient:
170
172
  try:
171
173
  query = Query(
172
174
  transport=chosen_transport,
175
+ memory=prepared_memory,
173
176
  is_streaming_mode=True, # Always streaming internally
174
177
  can_use_tool=configured_options.can_use_tool,
175
178
  hooks=convert_hooks_to_internal_format(configured_options.hooks)
@@ -0,0 +1,186 @@
1
+ """Validate memory options before starting the CLI; never serialize callbacks."""
2
+
3
+ import math
4
+ from dataclasses import dataclass, field
5
+ from pathlib import PurePosixPath, PureWindowsPath
6
+ from typing import Any, cast
7
+
8
+ from ..memory import (
9
+ MemoryConsumptionCallback,
10
+ MemoryGenerationCallback,
11
+ MemoryOptions,
12
+ MemoryShouldGenerate,
13
+ )
14
+
15
+
16
+ @dataclass
17
+ class PreparedMemoryOptions:
18
+ config: dict[str, Any] | None = None
19
+ gates: dict[str, MemoryShouldGenerate] = field(default_factory=dict)
20
+ on_generation: MemoryGenerationCallback | None = None
21
+ on_consumption: MemoryConsumptionCallback | None = None
22
+
23
+
24
+ def _object(value: Any, name: str) -> dict[str, Any]:
25
+ if not isinstance(value, dict):
26
+ raise TypeError(f"{name} must be an object")
27
+ return cast(dict[str, Any], value)
28
+
29
+
30
+ def _string(value: Any, name: str) -> None:
31
+ if not isinstance(value, str) or not value.strip():
32
+ raise TypeError(f"{name} must be a non-empty string")
33
+
34
+
35
+ def _boolean(data: dict[str, Any], key: str, name: str) -> None:
36
+ if key in data and not isinstance(data[key], bool):
37
+ raise TypeError(f"{name}.{key} must be a boolean")
38
+
39
+
40
+ def _callback(data: dict[str, Any], key: str, name: str) -> None:
41
+ if key in data and not callable(data[key]):
42
+ raise TypeError(f"{name}.{key} must be callable")
43
+
44
+
45
+ def _choice(
46
+ data: dict[str, Any], key: str, choices: tuple[str, ...], name: str
47
+ ) -> None:
48
+ if key in data and data[key] not in choices:
49
+ raise TypeError(f"{name}.{key} must be one of {choices}")
50
+
51
+
52
+ def _files(value: Any, name: str, *, roots: bool) -> list[dict[str, Any]]:
53
+ if not isinstance(value, list) or not value:
54
+ raise TypeError(f"{name} must be a non-empty list")
55
+ ids: set[str] = set()
56
+ result = []
57
+ for index, raw in enumerate(value):
58
+ label = f"{name}[{index}]"
59
+ item = _object(raw, label)
60
+ for key in ("id", "path"):
61
+ _string(item.get(key), f"{label}.{key}")
62
+ if item["id"] in ids:
63
+ raise TypeError(f'{name} contains duplicate id "{item["id"]}"')
64
+ ids.add(item["id"])
65
+ output = {"id": item["id"], "path": item["path"]}
66
+ if roots:
67
+ _choice(item, "access", ("read", "read-write"), label)
68
+ if "access" in item:
69
+ output["access"] = item["access"]
70
+ if "index_file" in item:
71
+ path = item["index_file"]
72
+ _string(path, f"{label}.index_file")
73
+ normalized = path.replace("\\", "/")
74
+ if (
75
+ PurePosixPath(normalized).is_absolute()
76
+ or PureWindowsPath(path).root
77
+ or ".." in normalized.split("/")
78
+ ):
79
+ raise TypeError(f"{label}.index_file must stay within its root")
80
+ output["indexFile"] = path
81
+ else:
82
+ _boolean(item, "required", label)
83
+ output["required"] = item.get("required", False)
84
+ result.append(output)
85
+ return result
86
+
87
+
88
+ def prepare_memory_options(memory: MemoryOptions | None) -> PreparedMemoryOptions:
89
+ prepared = PreparedMemoryOptions()
90
+ if memory is None:
91
+ return prepared
92
+ data = _object(memory, "options.memory")
93
+ if not data:
94
+ return prepared
95
+ if data.get("mode") not in ("native", "custom"):
96
+ raise TypeError('options.memory.mode must be "native" or "custom"')
97
+ config: dict[str, Any] = {"requester": "sdk", "mode": data["mode"]}
98
+ for key, wire in (("project_scope", "projectScope"), ("user_scope", "userScope")):
99
+ _boolean(data, key, "options.memory")
100
+ if key in data:
101
+ config[wire] = data[key]
102
+ if (
103
+ data["mode"] == "native"
104
+ and data.get("project_scope") is False
105
+ and data.get("user_scope") is False
106
+ ):
107
+ raise TypeError(
108
+ "native memory requires at least one enabled scope; use memory={} to disable"
109
+ )
110
+ for section in ("generation", "consumption"):
111
+ if section not in data:
112
+ continue
113
+ name = f"options.memory.{section}"
114
+ options = _object(data[section], name)
115
+ _callback(options, "on_result", name)
116
+ if section == "generation":
117
+ prepared.on_generation = options.get("on_result")
118
+ else:
119
+ prepared.on_consumption = options.get("on_result")
120
+ if data["mode"] == "native":
121
+ if options.keys() - {"on_result"}:
122
+ raise TypeError(
123
+ "native mode only accepts generation.on_result and consumption.on_result"
124
+ )
125
+ continue
126
+ _boolean(options, "enabled", name)
127
+ output: dict[str, Any] = {"enabled": options.get("enabled", True)}
128
+ if section == "generation":
129
+ if "roots" in options:
130
+ output["roots"] = _files(options["roots"], f"{name}.roots", roots=True)
131
+ if "prompt" in options:
132
+ _string(options["prompt"], f"{name}.prompt")
133
+ output["prompt"] = options["prompt"]
134
+ if "turn_complete" in options:
135
+ turn = _object(options["turn_complete"], f"{name}.turn_complete")
136
+ label = f"{name}.turn_complete"
137
+ _boolean(turn, "enabled", label)
138
+ _callback(turn, "should_generate", label)
139
+ _choice(turn, "on_gate_error", ("skip", "report_failed"), label)
140
+ tc: dict[str, Any] = {"enabled": turn.get("enabled", True)}
141
+ if "timeout_ms" in turn:
142
+ timeout = turn["timeout_ms"]
143
+ if (
144
+ isinstance(timeout, bool)
145
+ or not isinstance(timeout, (int, float))
146
+ or not math.isfinite(timeout)
147
+ or timeout <= 0
148
+ ):
149
+ raise TypeError(
150
+ f"{label}.timeout_ms must be a positive finite number"
151
+ )
152
+ tc["timeoutMs"] = timeout
153
+ if "on_gate_error" in turn:
154
+ tc["onGateError"] = turn["on_gate_error"]
155
+ if "should_generate" in turn:
156
+ callback_id = "memory_should_generate_0"
157
+ prepared.gates[callback_id] = turn["should_generate"]
158
+ tc["shouldGenerateCallbackId"] = callback_id
159
+ if turn.get("enabled") is True and options.get("enabled") is False:
160
+ raise TypeError(
161
+ "turn_complete cannot be enabled when generation.enabled is false"
162
+ )
163
+ output["turnComplete"] = tc
164
+ else:
165
+ if "files" in options:
166
+ output["files"] = _files(options["files"], f"{name}.files", roots=False)
167
+ if "max_tokens" in options:
168
+ budget = options["max_tokens"]
169
+ if (
170
+ isinstance(budget, bool)
171
+ or not isinstance(budget, int)
172
+ or budget <= 0
173
+ ):
174
+ raise TypeError(f"{name}.max_tokens must be a positive integer")
175
+ output["maxTokens"] = budget
176
+ _choice(options, "overflow", ("truncate", "fail_query"), name)
177
+ _choice(options, "failure_mode", ("best_effort", "fail_query"), name)
178
+ for key, wire in (
179
+ ("overflow", "overflow"),
180
+ ("failure_mode", "failureMode"),
181
+ ):
182
+ if key in options:
183
+ output[wire] = options[key]
184
+ config[section] = output
185
+ prepared.config = config
186
+ return prepared
@@ -7,10 +7,13 @@ from .._errors import MessageParseError
7
7
  from ..types import (
8
8
  AssistantMessage,
9
9
  AssistantMessageWithRequestId,
10
+ BackgroundTasksChangedMessage,
10
11
  CloudAgentEventMessage,
11
12
  ContentBlock,
12
13
  GoalClearedMessage,
13
14
  GoalUpdatedMessage,
15
+ MemoryConsumptionMessage,
16
+ MemoryGenerationMessage,
14
17
  Message,
15
18
  PlanModeChangedMessage,
16
19
  ResultMessage,
@@ -22,6 +25,7 @@ from ..types import (
22
25
  TaskNotificationMessage,
23
26
  TaskProgressMessage,
24
27
  TaskStartedMessage,
28
+ TaskUpdatedMessage,
25
29
  TextBlock,
26
30
  ThinkingBlock,
27
31
  ToolResultBlock,
@@ -166,6 +170,14 @@ def parse_message(data: dict[str, Any]) -> Message | None:
166
170
  try:
167
171
  subtype = data["subtype"]
168
172
  match subtype:
173
+ case "memory_generation":
174
+ return MemoryGenerationMessage(
175
+ subtype=subtype, data=data, result=data["result"]
176
+ )
177
+ case "memory_consumption":
178
+ return MemoryConsumptionMessage(
179
+ subtype=subtype, data=data, result=data["result"]
180
+ )
169
181
  case "permission_denied":
170
182
  return SDKPermissionDeniedMessage(
171
183
  subtype=subtype,
@@ -189,6 +201,10 @@ def parse_message(data: dict[str, Any]) -> Message | None:
189
201
  session_id=data["session_id"],
190
202
  tool_use_id=data.get("tool_use_id"),
191
203
  task_type=data.get("task_type"),
204
+ subagent_type=data.get("subagent_type"),
205
+ workflow_name=data.get("workflow_name"),
206
+ prompt=data.get("prompt"),
207
+ tools=data.get("tools"),
192
208
  )
193
209
  case "task_progress":
194
210
  return TaskProgressMessage(
@@ -196,18 +212,13 @@ def parse_message(data: dict[str, Any]) -> Message | None:
196
212
  data=data,
197
213
  task_id=data["task_id"],
198
214
  description=data["description"],
199
- usage=data.get(
200
- "usage",
201
- {
202
- "total_tokens": 0,
203
- "tool_uses": 0,
204
- "duration_ms": 0,
205
- },
206
- ),
215
+ usage=data.get("usage"),
207
216
  uuid=data["uuid"],
208
217
  session_id=data["session_id"],
209
218
  tool_use_id=data.get("tool_use_id"),
210
219
  last_tool_name=data.get("last_tool_name"),
220
+ subagent_type=data.get("subagent_type"),
221
+ summary=data.get("summary"),
211
222
  )
212
223
  case "task_notification":
213
224
  return TaskNotificationMessage(
@@ -222,6 +233,23 @@ def parse_message(data: dict[str, Any]) -> Message | None:
222
233
  tool_use_id=data.get("tool_use_id"),
223
234
  usage=data.get("usage"),
224
235
  )
236
+ case "task_updated":
237
+ return TaskUpdatedMessage(
238
+ subtype=subtype,
239
+ data=data,
240
+ task_id=data["task_id"],
241
+ patch=data["patch"],
242
+ uuid=data["uuid"],
243
+ session_id=data["session_id"],
244
+ )
245
+ case "background_tasks_changed":
246
+ return BackgroundTasksChangedMessage(
247
+ subtype=subtype,
248
+ data=data,
249
+ tasks=data["tasks"],
250
+ uuid=data["uuid"],
251
+ session_id=data["session_id"],
252
+ )
225
253
  case "goal_updated":
226
254
  return GoalUpdatedMessage(
227
255
  subtype=subtype,