codex-python 1.131.0__tar.gz → 1.140.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.
- {codex_python-1.131.0 → codex_python-1.140.0}/PKG-INFO +1 -1
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/__init__.py +1 -1
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/app_server/_async_client.py +33 -2
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/app_server/_async_services.py +74 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/app_server/_async_threads.py +41 -5
- codex_python-1.140.0/codex/app_server/_payloads.py +102 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/app_server/_sync_client.py +2 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/app_server/_sync_services.py +83 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/app_server/_sync_threads.py +42 -5
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/app_server/options.py +66 -15
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/options.py +52 -4
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/protocol/types.py +1505 -725
- {codex_python-1.131.0 → codex_python-1.140.0}/crates/codex_native/Cargo.lock +9 -9
- {codex_python-1.131.0 → codex_python-1.140.0}/crates/codex_native/Cargo.toml +1 -1
- codex_python-1.131.0/codex/app_server/_payloads.py +0 -59
- {codex_python-1.131.0 → codex_python-1.140.0}/LICENSE +0 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/README.md +0 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/_binary.py +0 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/_config_types.py +0 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/_file_utils.py +0 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/_runtime.py +0 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/_turn_options.py +0 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/app_server/__init__.py +0 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/app_server/_protocol_helpers.py +0 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/app_server/_session.py +0 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/app_server/_sync_support.py +0 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/app_server/_types.py +0 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/app_server/errors.py +0 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/app_server/models.py +0 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/app_server/transports.py +0 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/codex.py +0 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/dynamic_tools.py +0 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/errors.py +0 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/output_schema.py +0 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/output_schema_file.py +0 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/protocol/__init__.py +0 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/py.typed +0 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/thread.py +0 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/codex/vendor/.gitkeep +0 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/crates/codex_native/codex/__init__.py +0 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/crates/codex_native/src/lib.rs +0 -0
- {codex_python-1.131.0 → codex_python-1.140.0}/pyproject.toml +0 -0
|
@@ -14,6 +14,7 @@ from codex.app_server._async_services import (
|
|
|
14
14
|
AsyncConfigClient,
|
|
15
15
|
AsyncExternalAgentConfigClient,
|
|
16
16
|
AsyncFeedbackClient,
|
|
17
|
+
AsyncFsClient,
|
|
17
18
|
AsyncMcpServersClient,
|
|
18
19
|
AsyncModelsClient,
|
|
19
20
|
AsyncSkillsClient,
|
|
@@ -22,7 +23,7 @@ from codex.app_server._async_services import (
|
|
|
22
23
|
from codex.app_server._async_threads import AsyncAppServerThread as AsyncAppServerThread
|
|
23
24
|
from codex.app_server._async_threads import AsyncTurnStream as AsyncTurnStream
|
|
24
25
|
from codex.app_server._async_threads import _ThreadClient
|
|
25
|
-
from codex.app_server._protocol_helpers import RequestHandler
|
|
26
|
+
from codex.app_server._protocol_helpers import Notification, RequestHandler
|
|
26
27
|
from codex.app_server._session import _AsyncNotificationSubscription, _AsyncSession
|
|
27
28
|
from codex.app_server.models import (
|
|
28
29
|
InitializeResult,
|
|
@@ -107,6 +108,35 @@ class AsyncEventsClient:
|
|
|
107
108
|
def subscribe(self, methods: Collection[str] | None = None) -> _AsyncNotificationSubscription:
|
|
108
109
|
return self._session.subscribe_notifications(methods)
|
|
109
110
|
|
|
111
|
+
def subscribe_command_exec_output(self, process_id: str) -> _AsyncNotificationSubscription:
|
|
112
|
+
"""Subscribe to `command/exec/outputDelta` notifications for one process id."""
|
|
113
|
+
|
|
114
|
+
def predicate(notification: Notification) -> bool:
|
|
115
|
+
return (
|
|
116
|
+
isinstance(notification, protocol.CommandExecOutputDeltaNotificationModel)
|
|
117
|
+
and notification.params.processId == process_id
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
return self._session.subscribe_notifications(
|
|
121
|
+
{"command/exec/outputDelta"},
|
|
122
|
+
predicate=predicate,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
def subscribe_process_events(self, process_handle: str) -> _AsyncNotificationSubscription:
|
|
126
|
+
"""Subscribe to `process/outputDelta` and `process/exited` for one process handle."""
|
|
127
|
+
|
|
128
|
+
def predicate(notification: Notification) -> bool:
|
|
129
|
+
if isinstance(notification, protocol.ProcessOutputDeltaNotificationModel):
|
|
130
|
+
return notification.params.processHandle == process_handle
|
|
131
|
+
if isinstance(notification, protocol.ProcessExitedNotificationModel):
|
|
132
|
+
return notification.params.processHandle == process_handle
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
return self._session.subscribe_notifications(
|
|
136
|
+
{"process/outputDelta", "process/exited"},
|
|
137
|
+
predicate=predicate,
|
|
138
|
+
)
|
|
139
|
+
|
|
110
140
|
|
|
111
141
|
class AsyncAppServerClient:
|
|
112
142
|
"""Async client for `codex app-server`."""
|
|
@@ -122,7 +152,8 @@ class AsyncAppServerClient:
|
|
|
122
152
|
self.events = AsyncEventsClient(self._session)
|
|
123
153
|
self.models = AsyncModelsClient(self.rpc)
|
|
124
154
|
self.apps = AsyncAppsClient(self.rpc)
|
|
125
|
-
self.
|
|
155
|
+
self.fs = AsyncFsClient(self.rpc)
|
|
156
|
+
self.skills = AsyncSkillsClient(self.rpc, self.fs)
|
|
126
157
|
self.account = AsyncAccountClient(self.rpc)
|
|
127
158
|
self.config = AsyncConfigClient(self.rpc)
|
|
128
159
|
self.mcp_servers = AsyncMcpServersClient(self.rpc)
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import base64
|
|
3
4
|
from collections.abc import Mapping, Sequence
|
|
4
5
|
from typing import Any, Protocol, TypeVar
|
|
5
6
|
|
|
6
7
|
from pydantic import BaseModel
|
|
7
8
|
|
|
9
|
+
from codex.app_server._payloads import skill_input
|
|
8
10
|
from codex.app_server.models import (
|
|
9
11
|
AccountCancelLoginResult,
|
|
10
12
|
AccountRateLimitsResult,
|
|
@@ -49,6 +51,16 @@ class _AsyncServiceClient:
|
|
|
49
51
|
self._rpc = rpc
|
|
50
52
|
|
|
51
53
|
|
|
54
|
+
def _skill_markdown_path(directory: str) -> str:
|
|
55
|
+
if directory == "":
|
|
56
|
+
raise ValueError("Skill directory cannot be empty.")
|
|
57
|
+
if directory.endswith("/") or directory.endswith("\\"):
|
|
58
|
+
return f"{directory}SKILL.md"
|
|
59
|
+
if "\\" in directory and "/" not in directory:
|
|
60
|
+
return f"{directory}\\SKILL.md"
|
|
61
|
+
return f"{directory}/SKILL.md"
|
|
62
|
+
|
|
63
|
+
|
|
52
64
|
class AsyncModelsClient(_AsyncServiceClient):
|
|
53
65
|
async def list(
|
|
54
66
|
self,
|
|
@@ -107,7 +119,50 @@ class AsyncAppsClient(_AsyncServiceClient):
|
|
|
107
119
|
return await self._rpc.request_typed("app/list", params, AppListResult)
|
|
108
120
|
|
|
109
121
|
|
|
122
|
+
class AsyncFsClient(_AsyncServiceClient):
|
|
123
|
+
async def create_directory(
|
|
124
|
+
self,
|
|
125
|
+
*,
|
|
126
|
+
path: str,
|
|
127
|
+
recursive: bool | None = True,
|
|
128
|
+
) -> protocol.FsCreateDirectoryResponse:
|
|
129
|
+
params = protocol.FsCreateDirectoryParams(
|
|
130
|
+
path=protocol.AbsolutePathBuf(path),
|
|
131
|
+
recursive=recursive,
|
|
132
|
+
)
|
|
133
|
+
return await self._rpc.request_typed(
|
|
134
|
+
"fs/createDirectory",
|
|
135
|
+
params,
|
|
136
|
+
protocol.FsCreateDirectoryResponse,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
async def write_file(
|
|
140
|
+
self,
|
|
141
|
+
*,
|
|
142
|
+
path: str,
|
|
143
|
+
data: str | bytes,
|
|
144
|
+
encoding: str = "utf-8",
|
|
145
|
+
) -> protocol.FsWriteFileResponse:
|
|
146
|
+
raw_data = data.encode(encoding) if isinstance(data, str) else data
|
|
147
|
+
params = protocol.FsWriteFileParams(
|
|
148
|
+
path=protocol.AbsolutePathBuf(path),
|
|
149
|
+
dataBase64=base64.b64encode(raw_data).decode("ascii"),
|
|
150
|
+
)
|
|
151
|
+
return await self._rpc.request_typed(
|
|
152
|
+
"fs/writeFile",
|
|
153
|
+
params,
|
|
154
|
+
protocol.FsWriteFileResponse,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
110
158
|
class AsyncSkillsClient(_AsyncServiceClient):
|
|
159
|
+
def __init__(self, rpc: _TypedRpcClient, fs: AsyncFsClient) -> None:
|
|
160
|
+
super().__init__(rpc)
|
|
161
|
+
self._fs = fs
|
|
162
|
+
|
|
163
|
+
def input(self, *, name: str, path: str) -> protocol.SkillUserInput:
|
|
164
|
+
return skill_input(name=name, path=path)
|
|
165
|
+
|
|
111
166
|
async def list(
|
|
112
167
|
self,
|
|
113
168
|
*,
|
|
@@ -133,6 +188,9 @@ class AsyncSkillsClient(_AsyncServiceClient):
|
|
|
133
188
|
)
|
|
134
189
|
return await self._rpc.request_typed("skills/list", params, SkillsListResult)
|
|
135
190
|
|
|
191
|
+
async def reload(self, *, cwds: Sequence[str] | None = None) -> Sequence[SkillsListEntry]:
|
|
192
|
+
return await self.list(cwds=cwds, force_reload=True)
|
|
193
|
+
|
|
136
194
|
async def write_config(self, *, path: str, enabled: bool) -> SkillsConfigWriteResult:
|
|
137
195
|
params = protocol.SkillsConfigWriteParams(
|
|
138
196
|
path=protocol.AbsolutePathBuf(path),
|
|
@@ -140,6 +198,20 @@ class AsyncSkillsClient(_AsyncServiceClient):
|
|
|
140
198
|
)
|
|
141
199
|
return await self._rpc.request_typed("skills/config/write", params, SkillsConfigWriteResult)
|
|
142
200
|
|
|
201
|
+
async def write_skill(
|
|
202
|
+
self,
|
|
203
|
+
*,
|
|
204
|
+
name: str,
|
|
205
|
+
directory: str,
|
|
206
|
+
instructions: str | bytes,
|
|
207
|
+
reload_cwds: Sequence[str] | None = None,
|
|
208
|
+
) -> protocol.SkillUserInput:
|
|
209
|
+
skill_path = _skill_markdown_path(directory)
|
|
210
|
+
await self._fs.create_directory(path=directory, recursive=True)
|
|
211
|
+
await self._fs.write_file(path=skill_path, data=instructions)
|
|
212
|
+
await self.reload(cwds=reload_cwds)
|
|
213
|
+
return self.input(name=name, path=skill_path)
|
|
214
|
+
|
|
143
215
|
|
|
144
216
|
class AsyncAccountClient(_AsyncServiceClient):
|
|
145
217
|
async def read(self, *, refresh_token: bool | None = None) -> AccountReadResult:
|
|
@@ -324,6 +396,7 @@ class AsyncCommandClient(_AsyncServiceClient):
|
|
|
324
396
|
disable_timeout: bool | None = None,
|
|
325
397
|
env: Mapping[str, object | None] | None = None,
|
|
326
398
|
output_bytes_cap: int | None = None,
|
|
399
|
+
permission_profile: str | None = None,
|
|
327
400
|
process_id: str | None = None,
|
|
328
401
|
sandbox_policy: protocol.SandboxPolicy | None = None,
|
|
329
402
|
size: protocol.CommandExecTerminalSize | None = None,
|
|
@@ -339,6 +412,7 @@ class AsyncCommandClient(_AsyncServiceClient):
|
|
|
339
412
|
disableTimeout=disable_timeout,
|
|
340
413
|
env=dict(env) if env is not None else None,
|
|
341
414
|
outputBytesCap=output_bytes_cap,
|
|
415
|
+
permissionProfile=permission_profile,
|
|
342
416
|
processId=process_id,
|
|
343
417
|
sandboxPolicy=sandbox_policy,
|
|
344
418
|
size=size,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
-
from collections.abc import Callable, Collection, Mapping
|
|
6
|
+
from collections.abc import Callable, Collection, Mapping, Sequence
|
|
7
7
|
from dataclasses import dataclass
|
|
8
8
|
from typing import Protocol, TypeVar, cast
|
|
9
9
|
|
|
@@ -37,9 +37,11 @@ _ModelT = TypeVar("_ModelT", bound=BaseModel)
|
|
|
37
37
|
DEFAULT_REVIEW_DELIVERY = protocol.ReviewDelivery("inline")
|
|
38
38
|
|
|
39
39
|
_TURN_STREAM_NOTIFICATION_METHODS = {
|
|
40
|
+
"error",
|
|
40
41
|
"turn/started",
|
|
41
42
|
"turn/completed",
|
|
42
43
|
"turn/diff/updated",
|
|
44
|
+
"turn/moderationMetadata",
|
|
43
45
|
"turn/plan/updated",
|
|
44
46
|
"hook/started",
|
|
45
47
|
"hook/completed",
|
|
@@ -54,6 +56,7 @@ _TURN_STREAM_NOTIFICATION_METHODS = {
|
|
|
54
56
|
"item/reasoning/summaryPartAdded",
|
|
55
57
|
"item/reasoning/textDelta",
|
|
56
58
|
"item/commandExecution/outputDelta",
|
|
59
|
+
"item/commandExecution/terminalInteraction",
|
|
57
60
|
"item/fileChange/outputDelta",
|
|
58
61
|
"serverRequest/resolved",
|
|
59
62
|
}
|
|
@@ -132,6 +135,7 @@ class AsyncTurnStream:
|
|
|
132
135
|
self.usage: protocol.ThreadTokenUsage | None = None
|
|
133
136
|
self._item_index: dict[str, int] = {}
|
|
134
137
|
self._text_deltas: list[str] = []
|
|
138
|
+
self._retryable_error_notifications: list[protocol.ErrorNotificationModel] = []
|
|
135
139
|
self._done = False
|
|
136
140
|
self._closed = False
|
|
137
141
|
|
|
@@ -188,6 +192,16 @@ class AsyncTurnStream:
|
|
|
188
192
|
raise StopAsyncIteration
|
|
189
193
|
notification = await self._subscription.next()
|
|
190
194
|
self._apply(notification)
|
|
195
|
+
if isinstance(notification, protocol.ErrorNotificationModel):
|
|
196
|
+
if not notification.params.willRetry:
|
|
197
|
+
self._done = True
|
|
198
|
+
await self.close()
|
|
199
|
+
error = notification.params.error
|
|
200
|
+
message = error.message
|
|
201
|
+
if error.additionalDetails is not None and error.additionalDetails != "":
|
|
202
|
+
message = f"{message}: {error.additionalDetails}"
|
|
203
|
+
raise AppServerTurnError(message)
|
|
204
|
+
self._retryable_error_notifications.append(notification)
|
|
191
205
|
if isinstance(notification, protocol.TurnCompletedNotificationModel):
|
|
192
206
|
self._done = True
|
|
193
207
|
return notification
|
|
@@ -244,12 +258,13 @@ class AsyncTurnStream:
|
|
|
244
258
|
input: TurnInput,
|
|
245
259
|
*,
|
|
246
260
|
responsesapi_client_metadata: Mapping[str, object] | None = None,
|
|
261
|
+
skills: Sequence[protocol.SkillUserInput] | None = None,
|
|
247
262
|
) -> TurnIdResult:
|
|
248
263
|
"""Append additional user input to the in-flight turn."""
|
|
249
264
|
payload: dict[str, object] = {
|
|
250
265
|
"threadId": self.thread_id,
|
|
251
266
|
"expectedTurnId": self.turn_id,
|
|
252
|
-
"input": normalize_turn_input(input),
|
|
267
|
+
"input": normalize_turn_input(input, skills=skills),
|
|
253
268
|
}
|
|
254
269
|
if responsesapi_client_metadata is not None:
|
|
255
270
|
payload["responsesapiClientMetadata"] = dict(responsesapi_client_metadata)
|
|
@@ -274,6 +289,18 @@ class AsyncTurnStream:
|
|
|
274
289
|
"""Return the streamed agent text deltas received so far."""
|
|
275
290
|
return tuple(self._text_deltas)
|
|
276
291
|
|
|
292
|
+
@property
|
|
293
|
+
def retryable_error_notifications(self) -> tuple[protocol.ErrorNotificationModel, ...]:
|
|
294
|
+
"""Return retryable turn error notifications received so far."""
|
|
295
|
+
return tuple(self._retryable_error_notifications)
|
|
296
|
+
|
|
297
|
+
@property
|
|
298
|
+
def retryable_errors(self) -> tuple[protocol.TurnError, ...]:
|
|
299
|
+
"""Return retryable turn errors received so far."""
|
|
300
|
+
return tuple(
|
|
301
|
+
notification.params.error for notification in self._retryable_error_notifications
|
|
302
|
+
)
|
|
303
|
+
|
|
277
304
|
def _apply(self, notification: Notification) -> None:
|
|
278
305
|
self._apply_text_delta(notification)
|
|
279
306
|
self._apply_token_usage(notification)
|
|
@@ -391,11 +418,13 @@ class AsyncAppServerThread:
|
|
|
391
418
|
self,
|
|
392
419
|
input: TurnInput,
|
|
393
420
|
options: AppServerTurnOptions | None = None,
|
|
421
|
+
*,
|
|
422
|
+
skills: Sequence[protocol.SkillUserInput] | None = None,
|
|
394
423
|
) -> AsyncTurnStream:
|
|
395
424
|
"""Start a turn and return the protocol-native notification stream."""
|
|
396
425
|
payload = (options or AppServerTurnOptions()).to_params(
|
|
397
426
|
thread_id=self.id,
|
|
398
|
-
input=normalize_turn_input(input),
|
|
427
|
+
input=normalize_turn_input(input, skills=skills),
|
|
399
428
|
)
|
|
400
429
|
return await AsyncTurnStream.start(self, payload)
|
|
401
430
|
|
|
@@ -403,8 +432,10 @@ class AsyncAppServerThread:
|
|
|
403
432
|
self,
|
|
404
433
|
input: TurnInput,
|
|
405
434
|
options: AppServerTurnOptions | None = None,
|
|
435
|
+
*,
|
|
436
|
+
skills: Sequence[protocol.SkillUserInput] | None = None,
|
|
406
437
|
) -> str:
|
|
407
|
-
stream = await self.run(input, options)
|
|
438
|
+
stream = await self.run(input, options, skills=skills)
|
|
408
439
|
await stream.wait()
|
|
409
440
|
stream.raise_for_terminal_status()
|
|
410
441
|
return stream.final_text
|
|
@@ -413,8 +444,10 @@ class AsyncAppServerThread:
|
|
|
413
444
|
self,
|
|
414
445
|
input: TurnInput,
|
|
415
446
|
options: AppServerTurnOptions | None = None,
|
|
447
|
+
*,
|
|
448
|
+
skills: Sequence[protocol.SkillUserInput] | None = None,
|
|
416
449
|
) -> object:
|
|
417
|
-
stream = await self.run(input, options)
|
|
450
|
+
stream = await self.run(input, options, skills=skills)
|
|
418
451
|
await stream.wait()
|
|
419
452
|
stream.raise_for_terminal_status()
|
|
420
453
|
return stream.final_json()
|
|
@@ -424,6 +457,8 @@ class AsyncAppServerThread:
|
|
|
424
457
|
input: TurnInput,
|
|
425
458
|
model_type: type[_ModelT],
|
|
426
459
|
options: AppServerTurnOptions | None = None,
|
|
460
|
+
*,
|
|
461
|
+
skills: Sequence[protocol.SkillUserInput] | None = None,
|
|
427
462
|
) -> _ModelT:
|
|
428
463
|
"""Run a turn and validate the final assistant text with `model_type`."""
|
|
429
464
|
stream = await self.run(
|
|
@@ -433,6 +468,7 @@ class AsyncAppServerThread:
|
|
|
433
468
|
model_type,
|
|
434
469
|
owner="AppServerThread.run_model()",
|
|
435
470
|
),
|
|
471
|
+
skills=skills,
|
|
436
472
|
)
|
|
437
473
|
await stream.wait()
|
|
438
474
|
stream.raise_for_terminal_status()
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping, Sequence
|
|
4
|
+
from typing import Any, cast
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
from codex.app_server._types import JsonObject
|
|
9
|
+
from codex.protocol import types as protocol
|
|
10
|
+
|
|
11
|
+
InputItem = (
|
|
12
|
+
str
|
|
13
|
+
| Mapping[str, Any]
|
|
14
|
+
| protocol.UserInput
|
|
15
|
+
| protocol.TextUserInput
|
|
16
|
+
| protocol.ImageUserInput
|
|
17
|
+
| protocol.LocalImageUserInput
|
|
18
|
+
| protocol.SkillUserInput
|
|
19
|
+
| protocol.MentionUserInput
|
|
20
|
+
)
|
|
21
|
+
TurnInput = InputItem | Sequence[InputItem]
|
|
22
|
+
|
|
23
|
+
type ParamsModel = BaseModel
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def serialize_value(value: object) -> object:
|
|
27
|
+
if isinstance(value, BaseModel):
|
|
28
|
+
return value.model_dump(
|
|
29
|
+
mode="json",
|
|
30
|
+
by_alias=True,
|
|
31
|
+
exclude_none=True,
|
|
32
|
+
exclude_unset=True,
|
|
33
|
+
)
|
|
34
|
+
if isinstance(value, type) and issubclass(value, BaseModel):
|
|
35
|
+
return value.model_json_schema()
|
|
36
|
+
if isinstance(value, list):
|
|
37
|
+
return [serialize_value(item) for item in value]
|
|
38
|
+
if isinstance(value, tuple):
|
|
39
|
+
return [serialize_value(item) for item in value]
|
|
40
|
+
if isinstance(value, Mapping):
|
|
41
|
+
return {key: serialize_value(item) for key, item in value.items()}
|
|
42
|
+
return value
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def normalize_input_item(item: InputItem) -> JsonObject:
|
|
46
|
+
if isinstance(item, str):
|
|
47
|
+
return {"type": "text", "text": item}
|
|
48
|
+
serialized = serialize_value(item)
|
|
49
|
+
if not isinstance(serialized, dict):
|
|
50
|
+
raise TypeError(f"Input item must serialize to an object, got {type(serialized).__name__}")
|
|
51
|
+
return cast(JsonObject, serialized)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def skill_input(*, name: str, path: str) -> protocol.SkillUserInput:
|
|
55
|
+
if name == "":
|
|
56
|
+
raise ValueError("Skill name cannot be empty.")
|
|
57
|
+
if name.startswith("$"):
|
|
58
|
+
raise ValueError("Skill name should not include the '$' activation marker.")
|
|
59
|
+
if any(character.isspace() for character in name):
|
|
60
|
+
raise ValueError("Skill name cannot contain whitespace.")
|
|
61
|
+
if "/" in name or "\\" in name:
|
|
62
|
+
raise ValueError("Skill name cannot contain path separators.")
|
|
63
|
+
if path == "":
|
|
64
|
+
raise ValueError("Skill path cannot be empty.")
|
|
65
|
+
return protocol.SkillUserInput(
|
|
66
|
+
type=protocol.SkillUserInputType("skill"),
|
|
67
|
+
name=name,
|
|
68
|
+
path=path,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _add_skill_markers(
|
|
73
|
+
items: list[JsonObject],
|
|
74
|
+
skills: Sequence[protocol.SkillUserInput],
|
|
75
|
+
) -> list[JsonObject]:
|
|
76
|
+
if not skills:
|
|
77
|
+
return items
|
|
78
|
+
markers = " ".join(f"${skill.name}" for skill in skills)
|
|
79
|
+
for item in items:
|
|
80
|
+
if item.get("type") != "text":
|
|
81
|
+
continue
|
|
82
|
+
text = item.get("text")
|
|
83
|
+
if isinstance(text, str):
|
|
84
|
+
item["text"] = f"{markers}\n\n{text}"
|
|
85
|
+
return items
|
|
86
|
+
return [{"type": "text", "text": markers}, *items]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def normalize_turn_input(
|
|
90
|
+
value: TurnInput,
|
|
91
|
+
*,
|
|
92
|
+
skills: Sequence[protocol.SkillUserInput] | None = None,
|
|
93
|
+
) -> list[JsonObject]:
|
|
94
|
+
if isinstance(value, str):
|
|
95
|
+
items = [{"type": "text", "text": value}]
|
|
96
|
+
elif isinstance(value, Sequence):
|
|
97
|
+
items = [normalize_input_item(item) for item in value]
|
|
98
|
+
else:
|
|
99
|
+
items = [normalize_input_item(value)]
|
|
100
|
+
if skills is None:
|
|
101
|
+
return items
|
|
102
|
+
return _add_skill_markers(items, skills) + [normalize_input_item(skill) for skill in skills]
|
|
@@ -21,6 +21,7 @@ from codex.app_server._sync_services import (
|
|
|
21
21
|
_ConfigClient,
|
|
22
22
|
_ExternalAgentConfigClient,
|
|
23
23
|
_FeedbackClient,
|
|
24
|
+
_FsClient,
|
|
24
25
|
_McpServersClient,
|
|
25
26
|
_ModelsClient,
|
|
26
27
|
_SkillsClient,
|
|
@@ -240,6 +241,7 @@ class AppServerClient(_SyncRunner):
|
|
|
240
241
|
self.mcp_servers = _McpServersClient(async_client.mcp_servers, self._run)
|
|
241
242
|
self.feedback = _FeedbackClient(async_client.feedback, self._run)
|
|
242
243
|
self.command = _CommandClient(async_client.command, self._run)
|
|
244
|
+
self.fs = _FsClient(async_client.fs, self._run)
|
|
243
245
|
self.external_agent_config = _ExternalAgentConfigClient(
|
|
244
246
|
async_client.external_agent_config,
|
|
245
247
|
self._run,
|
|
@@ -71,6 +71,8 @@ class _AsyncAppsClientLike(Protocol):
|
|
|
71
71
|
|
|
72
72
|
|
|
73
73
|
class _AsyncSkillsClientLike(Protocol):
|
|
74
|
+
def input(self, *, name: str, path: str) -> protocol.SkillUserInput: ...
|
|
75
|
+
|
|
74
76
|
async def list(
|
|
75
77
|
self,
|
|
76
78
|
*,
|
|
@@ -85,8 +87,36 @@ class _AsyncSkillsClientLike(Protocol):
|
|
|
85
87
|
force_reload: bool | None = None,
|
|
86
88
|
) -> SkillsListResult: ...
|
|
87
89
|
|
|
90
|
+
async def reload(self, *, cwds: Sequence[str] | None = None) -> Sequence[SkillsListEntry]: ...
|
|
91
|
+
|
|
88
92
|
async def write_config(self, *, path: str, enabled: bool) -> SkillsConfigWriteResult: ...
|
|
89
93
|
|
|
94
|
+
async def write_skill(
|
|
95
|
+
self,
|
|
96
|
+
*,
|
|
97
|
+
name: str,
|
|
98
|
+
directory: str,
|
|
99
|
+
instructions: str | bytes,
|
|
100
|
+
reload_cwds: Sequence[str] | None = None,
|
|
101
|
+
) -> protocol.SkillUserInput: ...
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class _AsyncFsClientLike(Protocol):
|
|
105
|
+
async def create_directory(
|
|
106
|
+
self,
|
|
107
|
+
*,
|
|
108
|
+
path: str,
|
|
109
|
+
recursive: bool | None = True,
|
|
110
|
+
) -> protocol.FsCreateDirectoryResponse: ...
|
|
111
|
+
|
|
112
|
+
async def write_file(
|
|
113
|
+
self,
|
|
114
|
+
*,
|
|
115
|
+
path: str,
|
|
116
|
+
data: str | bytes,
|
|
117
|
+
encoding: str = "utf-8",
|
|
118
|
+
) -> protocol.FsWriteFileResponse: ...
|
|
119
|
+
|
|
90
120
|
|
|
91
121
|
class _AsyncAccountClientLike(Protocol):
|
|
92
122
|
async def read(self, *, refresh_token: bool | None = None) -> AccountReadResult: ...
|
|
@@ -187,6 +217,7 @@ class _AsyncCommandClientLike(Protocol):
|
|
|
187
217
|
disable_timeout: bool | None = None,
|
|
188
218
|
env: Mapping[str, object | None] | None = None,
|
|
189
219
|
output_bytes_cap: int | None = None,
|
|
220
|
+
permission_profile: str | None = None,
|
|
190
221
|
process_id: str | None = None,
|
|
191
222
|
sandbox_policy: protocol.SandboxPolicy | None = None,
|
|
192
223
|
size: protocol.CommandExecTerminalSize | None = None,
|
|
@@ -331,6 +362,9 @@ class _SkillsClient(_SyncRunner):
|
|
|
331
362
|
super().__init__(runner)
|
|
332
363
|
self._async_client = async_client
|
|
333
364
|
|
|
365
|
+
def input(self, *, name: str, path: str) -> protocol.SkillUserInput:
|
|
366
|
+
return self._async_client.input(name=name, path=path)
|
|
367
|
+
|
|
334
368
|
def list(
|
|
335
369
|
self,
|
|
336
370
|
*,
|
|
@@ -357,9 +391,56 @@ class _SkillsClient(_SyncRunner):
|
|
|
357
391
|
)
|
|
358
392
|
)
|
|
359
393
|
|
|
394
|
+
def reload(self, *, cwds: Sequence[str] | None = None) -> Sequence[SkillsListEntry]:
|
|
395
|
+
return self._run(self._async_client.reload(cwds=cwds))
|
|
396
|
+
|
|
360
397
|
def write_config(self, *, path: str, enabled: bool) -> SkillsConfigWriteResult:
|
|
361
398
|
return self._run(self._async_client.write_config(path=path, enabled=enabled))
|
|
362
399
|
|
|
400
|
+
def write_skill(
|
|
401
|
+
self,
|
|
402
|
+
*,
|
|
403
|
+
name: str,
|
|
404
|
+
directory: str,
|
|
405
|
+
instructions: str | bytes,
|
|
406
|
+
reload_cwds: Sequence[str] | None = None,
|
|
407
|
+
) -> protocol.SkillUserInput:
|
|
408
|
+
return self._run(
|
|
409
|
+
self._async_client.write_skill(
|
|
410
|
+
name=name,
|
|
411
|
+
directory=directory,
|
|
412
|
+
instructions=instructions,
|
|
413
|
+
reload_cwds=reload_cwds,
|
|
414
|
+
)
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
class _FsClient(_SyncRunner):
|
|
419
|
+
def __init__(
|
|
420
|
+
self,
|
|
421
|
+
async_client: _AsyncFsClientLike,
|
|
422
|
+
runner: Callable[[Coroutine[Any, Any, Any]], Any],
|
|
423
|
+
) -> None:
|
|
424
|
+
super().__init__(runner)
|
|
425
|
+
self._async_client = async_client
|
|
426
|
+
|
|
427
|
+
def create_directory(
|
|
428
|
+
self,
|
|
429
|
+
*,
|
|
430
|
+
path: str,
|
|
431
|
+
recursive: bool | None = True,
|
|
432
|
+
) -> protocol.FsCreateDirectoryResponse:
|
|
433
|
+
return self._run(self._async_client.create_directory(path=path, recursive=recursive))
|
|
434
|
+
|
|
435
|
+
def write_file(
|
|
436
|
+
self,
|
|
437
|
+
*,
|
|
438
|
+
path: str,
|
|
439
|
+
data: str | bytes,
|
|
440
|
+
encoding: str = "utf-8",
|
|
441
|
+
) -> protocol.FsWriteFileResponse:
|
|
442
|
+
return self._run(self._async_client.write_file(path=path, data=data, encoding=encoding))
|
|
443
|
+
|
|
363
444
|
|
|
364
445
|
class _AccountClient(_SyncRunner):
|
|
365
446
|
def __init__(
|
|
@@ -554,6 +635,7 @@ class _CommandClient(_SyncRunner):
|
|
|
554
635
|
disable_timeout: bool | None = None,
|
|
555
636
|
env: Mapping[str, object | None] | None = None,
|
|
556
637
|
output_bytes_cap: int | None = None,
|
|
638
|
+
permission_profile: str | None = None,
|
|
557
639
|
process_id: str | None = None,
|
|
558
640
|
sandbox_policy: protocol.SandboxPolicy | None = None,
|
|
559
641
|
size: protocol.CommandExecTerminalSize | None = None,
|
|
@@ -570,6 +652,7 @@ class _CommandClient(_SyncRunner):
|
|
|
570
652
|
disable_timeout=disable_timeout,
|
|
571
653
|
env=env,
|
|
572
654
|
output_bytes_cap=output_bytes_cap,
|
|
655
|
+
permission_profile=permission_profile,
|
|
573
656
|
process_id=process_id,
|
|
574
657
|
sandbox_policy=sandbox_policy,
|
|
575
658
|
size=size,
|