codex-python 1.114.0__tar.gz → 1.114.2__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 (44) hide show
  1. {codex_python-1.114.0 → codex_python-1.114.2}/PKG-INFO +49 -2
  2. {codex_python-1.114.0 → codex_python-1.114.2}/README.md +48 -1
  3. {codex_python-1.114.0 → codex_python-1.114.2}/codex/__init__.py +9 -1
  4. codex_python-1.114.2/codex/_config_types.py +108 -0
  5. {codex_python-1.114.0 → codex_python-1.114.2}/codex/_runtime.py +8 -3
  6. {codex_python-1.114.0 → codex_python-1.114.2}/codex/app_server/__init__.py +4 -0
  7. {codex_python-1.114.0 → codex_python-1.114.2}/codex/app_server/_async_client.py +15 -4
  8. {codex_python-1.114.0 → codex_python-1.114.2}/codex/app_server/_session.py +6 -3
  9. {codex_python-1.114.0 → codex_python-1.114.2}/codex/app_server/_sync_client.py +6 -2
  10. {codex_python-1.114.0 → codex_python-1.114.2}/codex/app_server/models.py +3 -26
  11. codex_python-1.114.2/codex/app_server/options.py +503 -0
  12. {codex_python-1.114.0 → codex_python-1.114.2}/codex/app_server/transports.py +19 -2
  13. {codex_python-1.114.0 → codex_python-1.114.2}/codex/codex.py +39 -7
  14. codex_python-1.114.2/codex/dynamic_tools.py +325 -0
  15. codex_python-1.114.2/codex/options.py +266 -0
  16. {codex_python-1.114.0 → codex_python-1.114.2}/codex/thread.py +11 -4
  17. {codex_python-1.114.0 → codex_python-1.114.2}/crates/codex_native/Cargo.lock +1 -1
  18. {codex_python-1.114.0 → codex_python-1.114.2}/crates/codex_native/Cargo.toml +1 -1
  19. codex_python-1.114.0/codex/_config_types.py +0 -6
  20. codex_python-1.114.0/codex/app_server/options.py +0 -246
  21. codex_python-1.114.0/codex/options.py +0 -127
  22. {codex_python-1.114.0 → codex_python-1.114.2}/LICENSE +0 -0
  23. {codex_python-1.114.0 → codex_python-1.114.2}/codex/_binary.py +0 -0
  24. {codex_python-1.114.0 → codex_python-1.114.2}/codex/_file_utils.py +0 -0
  25. {codex_python-1.114.0 → codex_python-1.114.2}/codex/_turn_options.py +0 -0
  26. {codex_python-1.114.0 → codex_python-1.114.2}/codex/app_server/_async_services.py +0 -0
  27. {codex_python-1.114.0 → codex_python-1.114.2}/codex/app_server/_async_threads.py +0 -0
  28. {codex_python-1.114.0 → codex_python-1.114.2}/codex/app_server/_payloads.py +0 -0
  29. {codex_python-1.114.0 → codex_python-1.114.2}/codex/app_server/_protocol_helpers.py +0 -0
  30. {codex_python-1.114.0 → codex_python-1.114.2}/codex/app_server/_sync_services.py +0 -0
  31. {codex_python-1.114.0 → codex_python-1.114.2}/codex/app_server/_sync_support.py +0 -0
  32. {codex_python-1.114.0 → codex_python-1.114.2}/codex/app_server/_sync_threads.py +0 -0
  33. {codex_python-1.114.0 → codex_python-1.114.2}/codex/app_server/_types.py +0 -0
  34. {codex_python-1.114.0 → codex_python-1.114.2}/codex/app_server/errors.py +0 -0
  35. {codex_python-1.114.0 → codex_python-1.114.2}/codex/errors.py +0 -0
  36. {codex_python-1.114.0 → codex_python-1.114.2}/codex/output_schema.py +0 -0
  37. {codex_python-1.114.0 → codex_python-1.114.2}/codex/output_schema_file.py +0 -0
  38. {codex_python-1.114.0 → codex_python-1.114.2}/codex/protocol/__init__.py +0 -0
  39. {codex_python-1.114.0 → codex_python-1.114.2}/codex/protocol/types.py +0 -0
  40. {codex_python-1.114.0 → codex_python-1.114.2}/codex/py.typed +0 -0
  41. {codex_python-1.114.0 → codex_python-1.114.2}/codex/vendor/.gitkeep +0 -0
  42. {codex_python-1.114.0 → codex_python-1.114.2}/crates/codex_native/codex/__init__.py +0 -0
  43. {codex_python-1.114.0 → codex_python-1.114.2}/crates/codex_native/src/lib.rs +0 -0
  44. {codex_python-1.114.0 → codex_python-1.114.2}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codex-python
3
- Version: 1.114.0
3
+ Version: 1.114.2
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3 :: Only
6
6
  Classifier: Programming Language :: Python :: 3.12
@@ -66,13 +66,29 @@ Use `AppServerClient` when you want a deeper integration:
66
66
  ## Quickstart: `Codex`
67
67
 
68
68
  ```python
69
- from codex import Codex
69
+ from codex import Codex, ThreadStartOptions
70
70
 
71
71
  client = Codex()
72
+
73
+ # Simplest one-shot call.
72
74
  summary = client.run_text("Diagnose the failing tests and propose a fix")
73
75
  print(summary)
76
+
77
+ # One-shot call with thread-scoped defaults for that run's fresh internal thread.
78
+ summary = client.run_text(
79
+ "Diagnose the failing tests in this repo",
80
+ thread_options=ThreadStartOptions(
81
+ cwd="/repo",
82
+ model="gpt-5",
83
+ ),
84
+ )
85
+ print(summary)
74
86
  ```
75
87
 
88
+ Use `thread_options=` on `run()`, `run_text()`, `run_json()`, and `run_model()` when you want to
89
+ set defaults on the fresh internal thread created for that one-shot call. Use
90
+ `start_thread()` / `resume_thread()` when later runs should share context.
91
+
76
92
  More `Codex` examples: [docs/exec_api.md](docs/exec_api.md)
77
93
 
78
94
  ## Quickstart: `AppServerClient`
@@ -97,6 +113,35 @@ with AppServerClient.connect_stdio(initialize_options=initialize_options) as cli
97
113
  More app-server examples: [docs/app_server.md](docs/app_server.md)
98
114
  For websocket transport, install the optional extra: `pip install "codex-python[websocket]"`.
99
115
 
116
+ ## Dynamic tools
117
+
118
+ Decorator-driven dynamic tools are available on both SDK surfaces.
119
+
120
+ ### `Codex`
121
+
122
+ ```python
123
+ from codex import Codex, dynamic_tool
124
+
125
+
126
+ @dynamic_tool
127
+ def lookup_ticket(id: str) -> str:
128
+ """Look up a support ticket by id."""
129
+ return f"Ticket {id}: Login requests time out in eu-west-1."
130
+
131
+
132
+ client = Codex()
133
+ summary = client.run_text(
134
+ "Use the lookup_ticket dynamic tool for ticket 123 and summarize the result.",
135
+ tools=[lookup_ticket],
136
+ )
137
+ print(summary)
138
+ ```
139
+
140
+ ### `AppServerClient`
141
+
142
+ For a complete app-server example using the same decorator-driven flow, see
143
+ [examples/app_server_dynamic_tool.py](examples/app_server_dynamic_tool.py).
144
+
100
145
  ## Structured output
101
146
 
102
147
  ### `Codex`
@@ -186,10 +231,12 @@ Advanced app-server usage, including typed stable RPC domains such as `client.mo
186
231
  ## Examples
187
232
 
188
233
  - [examples/basic_conversation.py](examples/basic_conversation.py): minimal `Codex` flow
234
+ - [examples/basic_dynamic_tool.py](examples/basic_dynamic_tool.py): high-level `Codex` dynamic tool flow
189
235
  - [examples/app_server_conversation.py](examples/app_server_conversation.py): minimal app-server flow
190
236
  - [examples/app_server_websocket_conversation.py](examples/app_server_websocket_conversation.py): minimal websocket app-server flow
191
237
  - [examples/app_server_stream_events.py](examples/app_server_stream_events.py): protocol-native app-server streaming
192
238
  - [examples/app_server_tool_handler.py](examples/app_server_tool_handler.py): typed app-server request handling
239
+ - [examples/app_server_dynamic_tool.py](examples/app_server_dynamic_tool.py): decorator-driven dynamic tool registration
193
240
 
194
241
  ## Bundled binary behavior
195
242
 
@@ -44,13 +44,29 @@ Use `AppServerClient` when you want a deeper integration:
44
44
  ## Quickstart: `Codex`
45
45
 
46
46
  ```python
47
- from codex import Codex
47
+ from codex import Codex, ThreadStartOptions
48
48
 
49
49
  client = Codex()
50
+
51
+ # Simplest one-shot call.
50
52
  summary = client.run_text("Diagnose the failing tests and propose a fix")
51
53
  print(summary)
54
+
55
+ # One-shot call with thread-scoped defaults for that run's fresh internal thread.
56
+ summary = client.run_text(
57
+ "Diagnose the failing tests in this repo",
58
+ thread_options=ThreadStartOptions(
59
+ cwd="/repo",
60
+ model="gpt-5",
61
+ ),
62
+ )
63
+ print(summary)
52
64
  ```
53
65
 
66
+ Use `thread_options=` on `run()`, `run_text()`, `run_json()`, and `run_model()` when you want to
67
+ set defaults on the fresh internal thread created for that one-shot call. Use
68
+ `start_thread()` / `resume_thread()` when later runs should share context.
69
+
54
70
  More `Codex` examples: [docs/exec_api.md](docs/exec_api.md)
55
71
 
56
72
  ## Quickstart: `AppServerClient`
@@ -75,6 +91,35 @@ with AppServerClient.connect_stdio(initialize_options=initialize_options) as cli
75
91
  More app-server examples: [docs/app_server.md](docs/app_server.md)
76
92
  For websocket transport, install the optional extra: `pip install "codex-python[websocket]"`.
77
93
 
94
+ ## Dynamic tools
95
+
96
+ Decorator-driven dynamic tools are available on both SDK surfaces.
97
+
98
+ ### `Codex`
99
+
100
+ ```python
101
+ from codex import Codex, dynamic_tool
102
+
103
+
104
+ @dynamic_tool
105
+ def lookup_ticket(id: str) -> str:
106
+ """Look up a support ticket by id."""
107
+ return f"Ticket {id}: Login requests time out in eu-west-1."
108
+
109
+
110
+ client = Codex()
111
+ summary = client.run_text(
112
+ "Use the lookup_ticket dynamic tool for ticket 123 and summarize the result.",
113
+ tools=[lookup_ticket],
114
+ )
115
+ print(summary)
116
+ ```
117
+
118
+ ### `AppServerClient`
119
+
120
+ For a complete app-server example using the same decorator-driven flow, see
121
+ [examples/app_server_dynamic_tool.py](examples/app_server_dynamic_tool.py).
122
+
78
123
  ## Structured output
79
124
 
80
125
  ### `Codex`
@@ -164,10 +209,12 @@ Advanced app-server usage, including typed stable RPC domains such as `client.mo
164
209
  ## Examples
165
210
 
166
211
  - [examples/basic_conversation.py](examples/basic_conversation.py): minimal `Codex` flow
212
+ - [examples/basic_dynamic_tool.py](examples/basic_dynamic_tool.py): high-level `Codex` dynamic tool flow
167
213
  - [examples/app_server_conversation.py](examples/app_server_conversation.py): minimal app-server flow
168
214
  - [examples/app_server_websocket_conversation.py](examples/app_server_websocket_conversation.py): minimal websocket app-server flow
169
215
  - [examples/app_server_stream_events.py](examples/app_server_stream_events.py): protocol-native app-server streaming
170
216
  - [examples/app_server_tool_handler.py](examples/app_server_tool_handler.py): typed app-server request handling
217
+ - [examples/app_server_dynamic_tool.py](examples/app_server_dynamic_tool.py): decorator-driven dynamic tool registration
171
218
 
172
219
  ## Bundled binary behavior
173
220
 
@@ -3,9 +3,11 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from codex.codex import Codex
6
+ from codex.dynamic_tools import dynamic_tool
6
7
  from codex.errors import CodexError, CodexExecError, CodexParseError, ThreadRunError
7
8
  from codex.options import (
8
9
  CancelSignal,
10
+ CodexConfig,
9
11
  CodexConfigObject,
10
12
  CodexConfigValue,
11
13
  CodexOptions,
@@ -13,11 +15,16 @@ from codex.options import (
13
15
  ThreadStartOptions,
14
16
  TurnOptions,
15
17
  )
18
+ from codex.thread import CodexTurnStream, Input, Thread
16
19
 
17
- __version__ = "1.114.0"
20
+ __version__ = "1.114.2"
18
21
 
19
22
  __all__ = [
20
23
  "Codex",
24
+ "CodexTurnStream",
25
+ "Thread",
26
+ "Input",
27
+ "dynamic_tool",
21
28
  "CodexError",
22
29
  "CodexExecError",
23
30
  "CodexParseError",
@@ -26,6 +33,7 @@ __all__ = [
26
33
  "ThreadStartOptions",
27
34
  "ThreadResumeOptions",
28
35
  "TurnOptions",
36
+ "CodexConfig",
29
37
  "CodexConfigValue",
30
38
  "CodexConfigObject",
31
39
  "CancelSignal",
@@ -0,0 +1,108 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Literal
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field
6
+
7
+ from codex.protocol import types as protocol
8
+
9
+ type CodexConfigValue = (
10
+ str | int | float | bool | list["CodexConfigValue"] | dict[str, "CodexConfigValue"]
11
+ )
12
+ type CodexConfigObject = dict[str, CodexConfigValue]
13
+
14
+
15
+ class CodexConfig(BaseModel):
16
+ """Typed top-level Codex config shape with forward-compatible extra keys."""
17
+
18
+ model_config = ConfigDict(extra="allow")
19
+ __pydantic_extra__: dict[str, CodexConfigValue]
20
+
21
+ analytics: CodexConfigObject | None = Field(
22
+ default=None,
23
+ description="Open-ended analytics configuration subtree.",
24
+ )
25
+ approval_policy: protocol.AskForApproval | None = Field(
26
+ default=None,
27
+ description="Default approval policy for new turns and commands.",
28
+ )
29
+ compact_prompt: str | None = Field(
30
+ default=None,
31
+ description="Prompt text used when the app-server compacts thread history.",
32
+ )
33
+ developer_instructions: str | None = Field(
34
+ default=None,
35
+ description="Default developer instructions applied to new turns.",
36
+ )
37
+ forced_chatgpt_workspace_id: str | None = Field(
38
+ default=None,
39
+ description="Workspace identifier to force for ChatGPT-managed auth flows.",
40
+ )
41
+ forced_login_method: Literal["chatgpt", "api"] | None = Field(
42
+ default=None,
43
+ description="Force Codex auth to ChatGPT or API-key mode.",
44
+ )
45
+ instructions: str | None = Field(
46
+ default=None,
47
+ description="Default user-visible instructions prepended to new conversations.",
48
+ )
49
+ model: str | None = Field(
50
+ default=None,
51
+ description="Default model id used when threads or turns do not override it.",
52
+ )
53
+ model_auto_compact_token_limit: int | None = Field(
54
+ default=None,
55
+ description="Token threshold that triggers automatic thread compaction.",
56
+ )
57
+ model_context_window: int | None = Field(
58
+ default=None,
59
+ description="Context window size override for the configured model.",
60
+ )
61
+ model_provider: str | None = Field(
62
+ default=None,
63
+ description="Default model provider name.",
64
+ )
65
+ model_reasoning_effort: protocol.ReasoningEffort | None = Field(
66
+ default=None,
67
+ description="Default reasoning effort for turns that do not override it.",
68
+ )
69
+ model_reasoning_summary: protocol.ReasoningSummary | None = Field(
70
+ default=None,
71
+ description="Default reasoning-summary behavior for supported models.",
72
+ )
73
+ model_verbosity: Literal["low", "medium", "high"] | None = Field(
74
+ default=None,
75
+ description="Default verbosity for supported models.",
76
+ )
77
+ profile: str | None = Field(
78
+ default=None,
79
+ description="Name of the active Codex profile.",
80
+ )
81
+ profiles: CodexConfigObject | None = Field(
82
+ default_factory=dict,
83
+ description="Open-ended profile definitions keyed by profile name.",
84
+ )
85
+ review_model: str | None = Field(
86
+ default=None,
87
+ description="Model override used for review flows.",
88
+ )
89
+ sandbox_mode: protocol.SandboxMode | None = Field(
90
+ default=None,
91
+ description="Default sandbox mode for new threads and turns.",
92
+ )
93
+ sandbox_workspace_write: CodexConfigObject | None = Field(
94
+ default=None,
95
+ description="Open-ended workspace-write sandbox configuration subtree.",
96
+ )
97
+ service_tier: protocol.ServiceTier | None = Field(
98
+ default=None,
99
+ description="Default service tier for requests that do not override it.",
100
+ )
101
+ tools: CodexConfigObject | None = Field(
102
+ default=None,
103
+ description="Open-ended tool configuration subtree.",
104
+ )
105
+ web_search: Literal["disabled", "cached", "live"] | None = Field(
106
+ default=None,
107
+ description="Default web-search mode.",
108
+ )
@@ -7,7 +7,7 @@ from collections.abc import Callable, Mapping
7
7
  from pathlib import Path
8
8
 
9
9
  from codex._binary import BundledCodexNotFoundError
10
- from codex._config_types import CodexConfigObject, CodexConfigValue
10
+ from codex._config_types import CodexConfig, CodexConfigObject, CodexConfigValue
11
11
 
12
12
  INTERNAL_ORIGINATOR_ENV = "CODEX_INTERNAL_ORIGINATOR_OVERRIDE"
13
13
  PYTHON_SDK_ORIGINATOR = "codex_sdk_py"
@@ -50,9 +50,14 @@ def resolve_codex_path(
50
50
  return system_codex
51
51
 
52
52
 
53
- def serialize_config_overrides(config_overrides: CodexConfigObject) -> list[str]:
53
+ def serialize_config_overrides(config_overrides: CodexConfig | CodexConfigObject) -> list[str]:
54
54
  overrides: list[str] = []
55
- _flatten_config_overrides(config_overrides, "", overrides)
55
+ root = (
56
+ config_overrides.model_dump(mode="python", exclude_none=True, exclude_unset=True)
57
+ if isinstance(config_overrides, CodexConfig)
58
+ else config_overrides
59
+ )
60
+ _flatten_config_overrides(root, "", overrides)
56
61
  return overrides
57
62
 
58
63
 
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from codex._config_types import CodexConfig
3
4
  from codex.app_server._async_client import AsyncAppServerClient, AsyncRpcClient
4
5
  from codex.app_server._async_threads import AsyncAppServerThread, AsyncTurnStream
5
6
  from codex.app_server._sync_client import AppServerClient, RpcClient
@@ -23,6 +24,7 @@ from codex.app_server.options import (
23
24
  AppServerTurnOptions,
24
25
  AppServerWebSocketOptions,
25
26
  )
27
+ from codex.dynamic_tools import dynamic_tool
26
28
 
27
29
  __all__ = [
28
30
  "AsyncAppServerClient",
@@ -30,6 +32,7 @@ __all__ = [
30
32
  "AsyncRpcClient",
31
33
  "AsyncTurnStream",
32
34
  "AppServerClient",
35
+ "dynamic_tool",
33
36
  "AppServerThread",
34
37
  "RpcClient",
35
38
  "TurnStream",
@@ -40,6 +43,7 @@ __all__ = [
40
43
  "AppServerRpcError",
41
44
  "AppServerTurnError",
42
45
  "AppServerClientInfo",
46
+ "CodexConfig",
43
47
  "AppServerInitializeOptions",
44
48
  "AppServerProcessOptions",
45
49
  "AppServerWebSocketOptions",
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from collections.abc import Collection, Mapping
5
+ from collections.abc import Callable, Collection, Mapping
6
6
  from typing import TypeVar, cast
7
7
 
8
8
  from pydantic import BaseModel
@@ -43,6 +43,7 @@ from codex.app_server.transports import (
43
43
  AsyncStdioTransport,
44
44
  AsyncWebSocketTransport,
45
45
  )
46
+ from codex.dynamic_tools import _DynamicToolRuntime, merge_dynamic_tool_specs, resolve_dynamic_tools
46
47
  from codex.protocol import types as protocol
47
48
 
48
49
  _ModelT = TypeVar("_ModelT", bound=BaseModel)
@@ -60,8 +61,9 @@ __all__ = [
60
61
  class AsyncRpcClient:
61
62
  """Low-level async JSON-RPC access to app-server methods."""
62
63
 
63
- def __init__(self, session: _AsyncSession) -> None:
64
+ def __init__(self, session: _AsyncSession, dynamic_tools: _DynamicToolRuntime) -> None:
64
65
  self._session = session
66
+ self._dynamic_tools = dynamic_tools
65
67
 
66
68
  async def request(
67
69
  self,
@@ -92,6 +94,7 @@ class AsyncRpcClient:
92
94
  *,
93
95
  request_model: type[_RequestT] | None = None,
94
96
  ) -> None:
97
+ self._dynamic_tools.check_manual_handler_registration(method)
95
98
  self._session.on_request(method, handler, request_model=request_model)
96
99
 
97
100
 
@@ -114,7 +117,8 @@ class AsyncAppServerClient:
114
117
  initialize_options: AppServerInitializeOptions | None = None,
115
118
  ) -> None:
116
119
  self._session = _AsyncSession(transport, initialize_options)
117
- self.rpc = AsyncRpcClient(self._session)
120
+ self._dynamic_tools = _DynamicToolRuntime(self._session.on_request)
121
+ self.rpc = AsyncRpcClient(self._session, self._dynamic_tools)
118
122
  self.events = AsyncEventsClient(self._session)
119
123
  self.models = AsyncModelsClient(self.rpc)
120
124
  self.apps = AsyncAppsClient(self.rpc)
@@ -167,12 +171,19 @@ class AsyncAppServerClient:
167
171
  async def start_thread(
168
172
  self,
169
173
  options: AppServerThreadStartOptions | None = None,
174
+ *,
175
+ tools: Collection[Callable[..., object]] | None = None,
170
176
  ) -> AsyncAppServerThread:
177
+ start_options = options or AppServerThreadStartOptions()
178
+ resolved_tools = resolve_dynamic_tools(list(tools or ()))
179
+ dynamic_tools = merge_dynamic_tool_specs(start_options.dynamic_tools, resolved_tools)
180
+ self._dynamic_tools.prepare_activation(resolved_tools)
171
181
  result = await self.rpc.request_typed(
172
182
  "thread/start",
173
- (options or AppServerThreadStartOptions()).to_params(),
183
+ start_options.model_copy(update={"dynamic_tools": dynamic_tools}).to_params(),
174
184
  ThreadResult,
175
185
  )
186
+ self._dynamic_tools.activate(result.thread.id, resolved_tools)
176
187
  return AsyncAppServerThread(cast(_ThreadClient, self), result.thread)
177
188
 
178
189
  async def resume_thread(
@@ -31,6 +31,7 @@ from codex.protocol import types as protocol
31
31
  _ModelT = TypeVar("_ModelT", bound=BaseModel)
32
32
  _RequestT = TypeVar("_RequestT", bound=BaseModel)
33
33
  _NotificationPredicate = Callable[[Notification], bool]
34
+ _SubscriptionMessage = Notification | Exception | None
34
35
 
35
36
 
36
37
  class _NotificationSink:
@@ -41,7 +42,7 @@ class _NotificationSink:
41
42
  ) -> None:
42
43
  self.methods = methods
43
44
  self.predicate = predicate
44
- self.queue: asyncio.Queue[Notification | None] = asyncio.Queue()
45
+ self.queue: asyncio.Queue[_SubscriptionMessage] = asyncio.Queue()
45
46
 
46
47
  def matches(self, method: str, notification: Notification) -> bool:
47
48
  if self.methods is not None and method not in self.methods:
@@ -52,13 +53,15 @@ class _NotificationSink:
52
53
  @dataclass(slots=True)
53
54
  class _AsyncNotificationSubscription:
54
55
  sink: _NotificationSink
55
- queue: asyncio.Queue[Notification | None]
56
+ queue: asyncio.Queue[_SubscriptionMessage]
56
57
  close_callback: Callable[[], None]
57
58
 
58
59
  async def next(self) -> Notification:
59
60
  message = await self.queue.get()
60
61
  if message is None:
61
62
  raise StopAsyncIteration
63
+ if isinstance(message, Exception):
64
+ raise message
62
65
  return message
63
66
 
64
67
  async def close(self) -> None:
@@ -253,7 +256,7 @@ class _AsyncSession:
253
256
  self._reader_error = exc
254
257
  self._fail_pending(exc)
255
258
  for sink in list(self._notification_sinks):
256
- await sink.queue.put(None)
259
+ await sink.queue.put(exc)
257
260
 
258
261
  async def _await_future(self, future: asyncio.Future[object]) -> object:
259
262
  while True:
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  import asyncio
6
6
  import concurrent.futures
7
7
  import time
8
- from collections.abc import Coroutine, Mapping
8
+ from collections.abc import Callable, Collection, Coroutine, Mapping
9
9
  from contextlib import suppress
10
10
  from threading import Thread
11
11
  from typing import Any, TypeVar, cast
@@ -311,9 +311,13 @@ class AppServerClient(_SyncRunner):
311
311
  def start_thread(
312
312
  self,
313
313
  options: AppServerThreadStartOptions | None = None,
314
+ *,
315
+ tools: Collection[Callable[..., object]] | None = None,
314
316
  ) -> AppServerThread:
315
317
  return AppServerThread(
316
- cast(_AsyncThreadLike, self._run(self._async_client.start_thread(options))),
318
+ cast(
319
+ _AsyncThreadLike, self._run(self._async_client.start_thread(options, tools=tools))
320
+ ),
317
321
  self._run,
318
322
  )
319
323
 
@@ -6,7 +6,7 @@ from pydantic import BaseModel, Field
6
6
  from pydantic import ConfigDict as PydanticConfigDict
7
7
  from pydantic.alias_generators import to_camel
8
8
 
9
- from codex._config_types import CodexConfigObject, CodexConfigValue
9
+ from codex._config_types import CodexConfig, CodexConfigObject, CodexConfigValue
10
10
  from codex.protocol import types as protocol
11
11
 
12
12
 
@@ -144,31 +144,8 @@ class AccountRateLimitsResult(AppServerResultModel):
144
144
  rate_limits_by_limit_id: dict[str, protocol.RateLimitSnapshot] | None = None
145
145
 
146
146
 
147
- class AppServerConfig(BaseModel):
148
- model_config = PydanticConfigDict(extra="allow")
149
-
150
- analytics: CodexConfigObject | None = None
151
- approval_policy: protocol.AskForApproval | None = None
152
- compact_prompt: str | None = None
153
- developer_instructions: str | None = None
154
- forced_chatgpt_workspace_id: str | None = None
155
- forced_login_method: Literal["chatgpt", "api"] | None = None
156
- instructions: str | None = None
157
- model: str | None = None
158
- model_auto_compact_token_limit: int | None = None
159
- model_context_window: int | None = None
160
- model_provider: str | None = None
161
- model_reasoning_effort: protocol.ReasoningEffort | None = None
162
- model_reasoning_summary: protocol.ReasoningSummary | None = None
163
- model_verbosity: Literal["low", "medium", "high"] | None = None
164
- profile: str | None = None
165
- profiles: CodexConfigObject | None = Field(default_factory=dict)
166
- review_model: str | None = None
167
- sandbox_mode: protocol.SandboxMode | None = None
168
- sandbox_workspace_write: CodexConfigObject | None = None
169
- service_tier: protocol.ServiceTier | None = None
170
- tools: CodexConfigObject | None = None
171
- web_search: Literal["disabled", "cached", "live"] | None = None
147
+ class AppServerConfig(CodexConfig):
148
+ """Typed config payload returned by app-server config APIs."""
172
149
 
173
150
 
174
151
  class ConfigLayerMetadata(AppServerResultModel):