codex-sdk-python 0.98.0__tar.gz → 0.104.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.
Files changed (20) hide show
  1. {codex_sdk_python-0.98.0 → codex_sdk_python-0.104.0}/PKG-INFO +21 -7
  2. {codex_sdk_python-0.98.0 → codex_sdk_python-0.104.0}/README.md +19 -5
  3. {codex_sdk_python-0.98.0 → codex_sdk_python-0.104.0}/pyproject.toml +3 -6
  4. {codex_sdk_python-0.98.0 → codex_sdk_python-0.104.0}/src/codex_sdk/__init__.py +5 -11
  5. {codex_sdk_python-0.98.0 → codex_sdk_python-0.104.0}/src/codex_sdk/app_server.py +309 -16
  6. {codex_sdk_python-0.98.0 → codex_sdk_python-0.104.0}/src/codex_sdk/integrations/pydantic_ai_model.py +122 -81
  7. codex_sdk_python-0.104.0/src/codex_sdk/tool_envelope.py +524 -0
  8. {codex_sdk_python-0.98.0 → codex_sdk_python-0.104.0}/src/codex_sdk/abort.py +0 -0
  9. {codex_sdk_python-0.98.0 → codex_sdk_python-0.104.0}/src/codex_sdk/codex.py +0 -0
  10. {codex_sdk_python-0.98.0 → codex_sdk_python-0.104.0}/src/codex_sdk/config_overrides.py +0 -0
  11. {codex_sdk_python-0.98.0 → codex_sdk_python-0.104.0}/src/codex_sdk/events.py +0 -0
  12. {codex_sdk_python-0.98.0 → codex_sdk_python-0.104.0}/src/codex_sdk/exceptions.py +0 -0
  13. {codex_sdk_python-0.98.0 → codex_sdk_python-0.104.0}/src/codex_sdk/exec.py +0 -0
  14. {codex_sdk_python-0.98.0 → codex_sdk_python-0.104.0}/src/codex_sdk/hooks.py +0 -0
  15. {codex_sdk_python-0.98.0 → codex_sdk_python-0.104.0}/src/codex_sdk/integrations/__init__.py +0 -0
  16. {codex_sdk_python-0.98.0 → codex_sdk_python-0.104.0}/src/codex_sdk/integrations/pydantic_ai.py +0 -0
  17. {codex_sdk_python-0.98.0 → codex_sdk_python-0.104.0}/src/codex_sdk/items.py +0 -0
  18. {codex_sdk_python-0.98.0 → codex_sdk_python-0.104.0}/src/codex_sdk/options.py +0 -0
  19. {codex_sdk_python-0.98.0 → codex_sdk_python-0.104.0}/src/codex_sdk/telemetry.py +0 -0
  20. {codex_sdk_python-0.98.0 → codex_sdk_python-0.104.0}/src/codex_sdk/thread.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: codex-sdk-python
3
- Version: 0.98.0
3
+ Version: 0.104.0
4
4
  Summary: Python SDK for the Codex CLI agent with async threads, streaming events, and structured outputs
5
5
  Keywords: codex,sdk,python,api,cli,agent,async,streaming
6
6
  Author: Vectorfy Co
@@ -18,7 +18,7 @@ Classifier: Programming Language :: Python :: 3.11
18
18
  Classifier: Programming Language :: Python :: 3.12
19
19
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
20
  Classifier: Topic :: Software Development :: Build Tools
21
- Requires-Dist: logfire-api>=4 ; extra == 'logfire'
21
+ Requires-Dist: logfire ; extra == 'logfire'
22
22
  Requires-Dist: pydantic>=2 ; extra == 'pydantic'
23
23
  Requires-Dist: pydantic-ai ; python_full_version >= '3.10' and extra == 'pydantic-ai'
24
24
  Maintainer: Vectorfy Co
@@ -44,7 +44,7 @@ Embed the Codex agent in Python workflows. This SDK wraps the bundled `codex` CL
44
44
  <td><strong>Lifecycle</strong></td>
45
45
  <td>
46
46
  <a href="#ci-cd"><img src="https://img.shields.io/badge/CI%2FCD-Active-16a34a?style=flat&logo=githubactions&logoColor=white" alt="CI/CD badge" /></a>
47
- <img src="https://img.shields.io/badge/Release-0.98.0-6b7280?style=flat&logo=pypi&logoColor=white" alt="Release badge" />
47
+ <img src="https://img.shields.io/badge/Release-0.104.0-6b7280?style=flat&logo=pypi&logoColor=white" alt="Release 0.104.0 badge" />
48
48
  <a href="#license"><img src="https://img.shields.io/badge/License-Apache--2.0-0f766e?style=flat&logo=apache&logoColor=white" alt="License badge" /></a>
49
49
  </td>
50
50
  </tr>
@@ -366,8 +366,21 @@ for payload shapes and event semantics.
366
366
  Note: some endpoints and fields are gated behind an experimental capability; set
367
367
  `AppServerOptions(experimental_api_enabled=True)` to opt in.
368
368
 
369
- `thread_list` supports `archived`, `sort_key`, and `source_kinds` filters, and `config_read` accepts an optional `cwd`
370
- to compute the effective layered config for a specific working directory.
369
+ `thread_list` supports `archived`, `sort_key`, and `source_kinds` filters (unchanged), and now also accepts `cwd`
370
+ for scoped thread queries. `config_read` accepts an optional `cwd` to compute effective layered config for a specific
371
+ working directory.
372
+
373
+ `skills_remote_read` supports `cwds`, `enabled`, `hazelnut_scope`, and `product_surface` filters. `model_list` accepts
374
+ an optional `include_hidden` flag.
375
+
376
+ ```python
377
+ threads = await app.thread_list(archived=False, sort_key="updatedAt", source_kinds=["local"], cwd=".")
378
+ config = await app.config_read(include_layers=True, cwd=".")
379
+ skills = await app.skills_remote_read(
380
+ cwds=["."], enabled=True, hazelnut_scope="user", product_surface="codex_desktop"
381
+ )
382
+ models = await app.model_list(limit=20, include_hidden=False)
383
+ ```
371
384
 
372
385
  ### Observability (OTEL) and notify
373
386
 
@@ -410,8 +423,9 @@ Supported target triples:
410
423
  - macOS: `x86_64-apple-darwin`, `aarch64-apple-darwin`
411
424
  - Windows: `x86_64-pc-windows-msvc`, `aarch64-pc-windows-msvc`
412
425
 
413
- If you are working from source and the vendor directory is missing, run `python scripts/setup_binary.py`
414
- or follow `SETUP.md` to download the official npm package and copy the `vendor/` directory.
426
+ If you are working from source and the vendor directory is missing, run
427
+ `python scripts/setup_binary.py` to fetch and assemble the platform `@openai/codex`
428
+ artifacts into `src/codex_sdk/vendor/`.
415
429
 
416
430
  <a id="auth"></a>
417
431
  ## ![Auth](https://img.shields.io/badge/Auth%20%26%20Credentials-Access-2563eb?style=for-the-badge&logo=gnubash&logoColor=white)
@@ -8,7 +8,7 @@ Embed the Codex agent in Python workflows. This SDK wraps the bundled `codex` CL
8
8
  <td><strong>Lifecycle</strong></td>
9
9
  <td>
10
10
  <a href="#ci-cd"><img src="https://img.shields.io/badge/CI%2FCD-Active-16a34a?style=flat&logo=githubactions&logoColor=white" alt="CI/CD badge" /></a>
11
- <img src="https://img.shields.io/badge/Release-0.98.0-6b7280?style=flat&logo=pypi&logoColor=white" alt="Release badge" />
11
+ <img src="https://img.shields.io/badge/Release-0.104.0-6b7280?style=flat&logo=pypi&logoColor=white" alt="Release 0.104.0 badge" />
12
12
  <a href="#license"><img src="https://img.shields.io/badge/License-Apache--2.0-0f766e?style=flat&logo=apache&logoColor=white" alt="License badge" /></a>
13
13
  </td>
14
14
  </tr>
@@ -330,8 +330,21 @@ for payload shapes and event semantics.
330
330
  Note: some endpoints and fields are gated behind an experimental capability; set
331
331
  `AppServerOptions(experimental_api_enabled=True)` to opt in.
332
332
 
333
- `thread_list` supports `archived`, `sort_key`, and `source_kinds` filters, and `config_read` accepts an optional `cwd`
334
- to compute the effective layered config for a specific working directory.
333
+ `thread_list` supports `archived`, `sort_key`, and `source_kinds` filters (unchanged), and now also accepts `cwd`
334
+ for scoped thread queries. `config_read` accepts an optional `cwd` to compute effective layered config for a specific
335
+ working directory.
336
+
337
+ `skills_remote_read` supports `cwds`, `enabled`, `hazelnut_scope`, and `product_surface` filters. `model_list` accepts
338
+ an optional `include_hidden` flag.
339
+
340
+ ```python
341
+ threads = await app.thread_list(archived=False, sort_key="updatedAt", source_kinds=["local"], cwd=".")
342
+ config = await app.config_read(include_layers=True, cwd=".")
343
+ skills = await app.skills_remote_read(
344
+ cwds=["."], enabled=True, hazelnut_scope="user", product_surface="codex_desktop"
345
+ )
346
+ models = await app.model_list(limit=20, include_hidden=False)
347
+ ```
335
348
 
336
349
  ### Observability (OTEL) and notify
337
350
 
@@ -374,8 +387,9 @@ Supported target triples:
374
387
  - macOS: `x86_64-apple-darwin`, `aarch64-apple-darwin`
375
388
  - Windows: `x86_64-pc-windows-msvc`, `aarch64-pc-windows-msvc`
376
389
 
377
- If you are working from source and the vendor directory is missing, run `python scripts/setup_binary.py`
378
- or follow `SETUP.md` to download the official npm package and copy the `vendor/` directory.
390
+ If you are working from source and the vendor directory is missing, run
391
+ `python scripts/setup_binary.py` to fetch and assemble the platform `@openai/codex`
392
+ artifacts into `src/codex_sdk/vendor/`.
379
393
 
380
394
  <a id="auth"></a>
381
395
  ## ![Auth](https://img.shields.io/badge/Auth%20%26%20Credentials-Access-2563eb?style=for-the-badge&logo=gnubash&logoColor=white)
@@ -1,10 +1,10 @@
1
1
  [build-system]
2
- requires = ["uv_build>=0.9.21,<0.10.0"]
2
+ requires = ["uv_build>=0.9.21,<0.11.0"]
3
3
  build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "codex-sdk-python"
7
- version = "0.98.0"
7
+ version = "0.104.0"
8
8
  description = "Python SDK for the Codex CLI agent with async threads, streaming events, and structured outputs"
9
9
  readme = "README.md"
10
10
  license = {text = "Apache-2.0"}
@@ -40,10 +40,7 @@ pydantic-ai = [
40
40
  "pydantic-ai; python_version >= '3.10'",
41
41
  ]
42
42
  logfire = [
43
- # `pydantic-ai` imports the `logfire` module, which is provided by the
44
- # `logfire-api` distribution. Avoid the unrelated legacy `logfire` package
45
- # on PyPI which can shadow it and break imports.
46
- "logfire-api>=4",
43
+ "logfire",
47
44
  ]
48
45
 
49
46
  [dependency-groups]
@@ -15,6 +15,8 @@ from .app_server import (
15
15
  AppServerRequest,
16
16
  AppServerTurnSession,
17
17
  AppServerUserInput,
18
+ SkillsRemoteReadRequest,
19
+ SkillsRemoteWriteRequest,
18
20
  )
19
21
  from .codex import Codex
20
22
  from .events import (
@@ -41,11 +43,6 @@ from .exceptions import (
41
43
  from .hooks import ThreadHooks
42
44
  from .items import (
43
45
  AgentMessageItem,
44
- CollabAgentState,
45
- CollabAgentStatus,
46
- CollabTool,
47
- CollabToolCallItem,
48
- CollabToolCallStatus,
49
46
  CommandExecutionItem,
50
47
  CommandExecutionStatus,
51
48
  ErrorItem,
@@ -82,7 +79,7 @@ from .thread import (
82
79
  Turn,
83
80
  )
84
81
 
85
- __version__ = "0.98.0"
82
+ __version__ = "0.104.0"
86
83
 
87
84
  __all__ = [
88
85
  "AbortController",
@@ -97,6 +94,8 @@ __all__ = [
97
94
  "ApprovalDecisions",
98
95
  "AppServerInput",
99
96
  "AppServerUserInput",
97
+ "SkillsRemoteReadRequest",
98
+ "SkillsRemoteWriteRequest",
100
99
  "Thread",
101
100
  "ThreadHooks",
102
101
  "Input",
@@ -122,7 +121,6 @@ __all__ = [
122
121
  "CommandExecutionItem",
123
122
  "FileChangeItem",
124
123
  "McpToolCallItem",
125
- "CollabToolCallItem",
126
124
  "McpToolCallItemResult",
127
125
  "McpToolCallItemError",
128
126
  "WebSearchItem",
@@ -132,10 +130,6 @@ __all__ = [
132
130
  "PatchChangeKind",
133
131
  "PatchApplyStatus",
134
132
  "McpToolCallStatus",
135
- "CollabToolCallStatus",
136
- "CollabTool",
137
- "CollabAgentStatus",
138
- "CollabAgentState",
139
133
  "TodoItem",
140
134
  "CodexOptions",
141
135
  "ThreadOptions",
@@ -558,6 +558,7 @@ class AppServerClient:
558
558
  model_providers: Optional[Sequence[str]] = None,
559
559
  source_kinds: Optional[Sequence[str]] = None,
560
560
  archived: Optional[bool] = None,
561
+ cwd: Optional[Union[str, Path]] = None,
561
562
  ) -> Dict[str, Any]:
562
563
  """
563
564
  Retrieve a page of threads from the app-server with optional filtering and sorting.
@@ -570,6 +571,7 @@ class AppServerClient:
570
571
  source_kinds: Filter threads by one or more source kinds.
571
572
  archived: If set, restrict results to archived (`True`) or unarchived (`False`)
572
573
  threads.
574
+ cwd: Optional working directory scope for server-side filtering.
573
575
 
574
576
  Returns:
575
577
  The raw response dictionary returned by the app-server for the `thread/list`
@@ -588,6 +590,8 @@ class AppServerClient:
588
590
  params["source_kinds"] = list(source_kinds)
589
591
  if archived is not None:
590
592
  params["archived"] = archived
593
+ if cwd is not None:
594
+ params["cwd"] = str(cwd)
591
595
  return await self._request_dict("thread/list", _coerce_keys(params) or None)
592
596
 
593
597
  async def thread_read(
@@ -636,14 +640,41 @@ class AppServerClient:
636
640
  """
637
641
  return await self._request_dict("thread/unarchive", {"threadId": thread_id})
638
642
 
639
- async def thread_compact_start(self, thread_id: str) -> Dict[str, Any]:
643
+ async def thread_compact_start(
644
+ self, thread_id: str, *, instructions: Optional[str] = None
645
+ ) -> Dict[str, Any]:
640
646
  """
641
647
  Starts a compaction operation for the specified thread on the app-server.
642
648
 
649
+ Args:
650
+ thread_id: Identifier of the thread to compact.
651
+ instructions: Optional server hint for compaction behavior.
652
+
643
653
  Returns:
644
654
  dict: The app-server's result payload for the compaction start request.
645
655
  """
646
- return await self._request_dict("thread/compact/start", {"threadId": thread_id})
656
+ payload: Dict[str, Any] = {"thread_id": thread_id}
657
+ if instructions is not None:
658
+ payload["instructions"] = instructions
659
+ return await self._request_dict("thread/compact/start", _coerce_keys(payload))
660
+
661
+ async def thread_background_terminals_clean(
662
+ self, thread_id: str, *, terminal_ids: Sequence[str]
663
+ ) -> Dict[str, Any]:
664
+ """
665
+ Clean up background terminal sessions for a thread.
666
+
667
+ Args:
668
+ thread_id: Identifier of the thread.
669
+ terminal_ids: Terminal ids to clean.
670
+
671
+ Returns:
672
+ App-server response payload.
673
+ """
674
+ payload = {"thread_id": thread_id, "terminal_ids": list(terminal_ids)}
675
+ return await self._request_dict(
676
+ "thread/backgroundTerminals/clean", _coerce_keys(payload)
677
+ )
647
678
 
648
679
  async def thread_rollback(
649
680
  self, thread_id: str, *, num_turns: int
@@ -741,43 +772,110 @@ class AppServerClient:
741
772
  payload["cwds"] = [str(path) for path in cwds]
742
773
  return await self._request_dict("skills/list", _coerce_keys(payload))
743
774
 
744
- async def skills_remote_read(self) -> Dict[str, Any]:
775
+ async def skills_remote_read(
776
+ self,
777
+ *,
778
+ cwds: Optional[Sequence[Union[str, Path]]] = None,
779
+ enabled: Optional[bool] = None,
780
+ hazelnut_scope: Optional[str] = None,
781
+ product_surface: Optional[str] = None,
782
+ params: Optional["SkillsRemoteReadRequest"] = None,
783
+ ) -> Dict[str, Any]:
745
784
  """
746
785
  Read remote skills metadata from the app server.
747
786
 
787
+ Args:
788
+ cwds: Optional workspace roots to scope the remote skill listing.
789
+ enabled: Optional filter for enabled/disabled remote skills.
790
+ hazelnut_scope: Optional Hazelnut scope identifier.
791
+ product_surface: Optional product surface identifier.
792
+ params: Optional raw request payload for protocol-forward fields.
793
+
748
794
  Returns:
749
795
  result (Dict[str, Any]): The app-server response payload for the `skills/remote/read` request.
750
796
  """
751
- return await self._request_dict("skills/remote/read", {})
797
+ payload: Dict[str, Any] = {}
798
+ if params is not None:
799
+ _validate_alias_conflicts(
800
+ params,
801
+ (
802
+ ("hazelnut_scope", "hazelnutScope"),
803
+ ("product_surface", "productSurface"),
804
+ ),
805
+ context="SkillsRemoteReadRequest",
806
+ )
807
+ payload.update(dict(params))
808
+ if cwds is not None:
809
+ payload["cwds"] = [str(path) for path in cwds]
810
+ if enabled is not None:
811
+ payload["enabled"] = enabled
812
+ if hazelnut_scope is not None:
813
+ payload["hazelnut_scope"] = hazelnut_scope
814
+ if product_surface is not None:
815
+ payload["product_surface"] = product_surface
816
+ return await self._request_dict("skills/remote/read", _coerce_keys(payload))
752
817
 
753
818
  async def skills_remote_write(
754
- self, *, hazelnut_id: str, is_preload: bool
819
+ self,
820
+ *,
821
+ hazelnut_id: Optional[str] = None,
822
+ is_preload: Optional[bool] = None,
823
+ params: Optional["SkillsRemoteWriteRequest"] = None,
755
824
  ) -> Dict[str, Any]:
756
825
  """
757
- Start a remote skill write operation for a Hazelnut package.
826
+ Start a remote skill write operation.
758
827
 
759
828
  Args:
760
- hazelnut_id: Identifier of the remote Hazelnut skill to write.
761
- is_preload: Whether the skill should be marked as a preload.
829
+ hazelnut_id: Optional Hazelnut identifier.
830
+ is_preload: Optional preload flag.
831
+ params: Optional raw request payload.
762
832
 
763
833
  Returns:
764
834
  Result returned by the app-server for the "skills/remote/write" request.
765
835
  """
766
- payload = {"hazelnut_id": hazelnut_id, "is_preload": is_preload}
836
+ payload: Dict[str, Any] = {}
837
+ if params is not None:
838
+ _validate_alias_conflicts(
839
+ params,
840
+ (
841
+ ("hazelnut_id", "hazelnutId"),
842
+ ("is_preload", "isPreload"),
843
+ ),
844
+ context="SkillsRemoteWriteRequest",
845
+ )
846
+ payload.update(dict(params))
847
+ if hazelnut_id is not None:
848
+ payload["hazelnut_id"] = hazelnut_id
849
+ if is_preload is not None:
850
+ payload["is_preload"] = is_preload
767
851
  return await self._request_dict("skills/remote/write", _coerce_keys(payload))
768
852
 
769
- async def skills_config_write(self, *, path: str, enabled: bool) -> Dict[str, Any]:
853
+ async def skills_config_write(
854
+ self,
855
+ *,
856
+ path: Optional[str] = None,
857
+ enabled: Optional[bool] = None,
858
+ params: Optional["SkillsConfigWriteRequest"] = None,
859
+ ) -> Dict[str, Any]:
770
860
  """
771
- Set the enabled state of a skill configuration at the given path.
861
+ Set skill configuration state.
772
862
 
773
863
  Args:
774
- path: The configuration path identifying the skill.
775
- enabled: True to enable the skill at the path, False to disable it.
864
+ path: Optional configuration path identifying the skill.
865
+ enabled: Optional enabled state for the skill.
866
+ params: Optional typed request payload for evolving protocol fields.
776
867
 
777
868
  Returns:
778
869
  The app-server response as a dictionary.
779
870
  """
780
- payload = {"path": path, "enabled": enabled}
871
+ # TODO(app-server-schema): tighten request shape after protocol stabilizes.
872
+ payload: Dict[str, Any] = {}
873
+ if params is not None:
874
+ payload.update(_coerce_keys(dict(params)))
875
+ if path is not None:
876
+ payload["path"] = path
877
+ if enabled is not None:
878
+ payload["enabled"] = enabled
781
879
  return await self._request_dict("skills/config/write", payload)
782
880
 
783
881
  async def turn_start(
@@ -848,8 +946,29 @@ class AppServerClient:
848
946
  "turn/interrupt", {"threadId": thread_id, "turnId": turn_id}
849
947
  )
850
948
 
949
+ async def turn_steer(
950
+ self, thread_id: str, turn_id: str, *, prompt: str
951
+ ) -> Dict[str, Any]:
952
+ """
953
+ Send steering guidance to an in-progress turn.
954
+
955
+ Args:
956
+ thread_id: Identifier of the thread.
957
+ turn_id: Identifier of the turn.
958
+ prompt: Steering prompt text.
959
+
960
+ Returns:
961
+ App-server response payload.
962
+ """
963
+ payload = {"thread_id": thread_id, "turn_id": turn_id, "prompt": prompt}
964
+ return await self._request_dict("turn/steer", _coerce_keys(payload))
965
+
851
966
  async def model_list(
852
- self, *, cursor: Optional[str] = None, limit: Optional[int] = None
967
+ self,
968
+ *,
969
+ cursor: Optional[str] = None,
970
+ limit: Optional[int] = None,
971
+ include_hidden: Optional[bool] = None,
853
972
  ) -> Dict[str, Any]:
854
973
  """List models available to the app-server."""
855
974
  params: Dict[str, Any] = {}
@@ -857,7 +976,9 @@ class AppServerClient:
857
976
  params["cursor"] = cursor
858
977
  if limit is not None:
859
978
  params["limit"] = limit
860
- return await self._request_dict("model/list", params or None)
979
+ if include_hidden is not None:
980
+ params["include_hidden"] = include_hidden
981
+ return await self._request_dict("model/list", _coerce_keys(params) or None)
861
982
 
862
983
  async def app_list(
863
984
  self, *, cursor: Optional[str] = None, limit: Optional[int] = None
@@ -874,6 +995,26 @@ class AppServerClient:
874
995
  """List supported collaboration modes from the app-server."""
875
996
  return await self._request_dict("collaborationMode/list", {})
876
997
 
998
+ async def experimental_feature_list(
999
+ self, *, cursor: Optional[str] = None, limit: Optional[int] = None
1000
+ ) -> Dict[str, Any]:
1001
+ """
1002
+ List experimental features available from the app-server.
1003
+
1004
+ Args:
1005
+ cursor: Optional pagination cursor.
1006
+ limit: Optional page size.
1007
+
1008
+ Returns:
1009
+ App-server response payload.
1010
+ """
1011
+ params: Dict[str, Any] = {}
1012
+ if cursor is not None:
1013
+ params["cursor"] = cursor
1014
+ if limit is not None:
1015
+ params["limit"] = limit
1016
+ return await self._request_dict("experimentalFeature/list", params or None)
1017
+
877
1018
  async def command_exec(
878
1019
  self,
879
1020
  *,
@@ -938,6 +1079,103 @@ class AppServerClient:
938
1079
  "account/read", {"refreshToken": refresh_token} if refresh_token else None
939
1080
  )
940
1081
 
1082
+ async def account_chatgpt_auth_tokens_refresh(
1083
+ self, *, params: Mapping[str, Any]
1084
+ ) -> Dict[str, Any]:
1085
+ """
1086
+ Refresh ChatGPT auth tokens via the app-server.
1087
+
1088
+ Args:
1089
+ params: Refresh payload (snake_case or camelCase keys are accepted).
1090
+
1091
+ Returns:
1092
+ App-server response payload.
1093
+ """
1094
+ return await self._request_dict(
1095
+ "account/chatgptAuthTokens/refresh", _coerce_keys(dict(params))
1096
+ )
1097
+
1098
+ async def item_tool_call(self, *, params: "ItemToolCallRequest") -> Dict[str, Any]:
1099
+ """
1100
+ Send an item tool-call payload.
1101
+
1102
+ Args:
1103
+ params: Typed request payload for `item/tool/call`.
1104
+
1105
+ Returns:
1106
+ App-server response payload.
1107
+ """
1108
+ # TODO(app-server-schema): tighten request shape after protocol stabilizes.
1109
+ return await self._request_dict("item/tool/call", _coerce_keys(dict(params)))
1110
+
1111
+ async def item_tool_request_user_input(
1112
+ self, *, params: Mapping[str, Any]
1113
+ ) -> Dict[str, Any]:
1114
+ """
1115
+ Send an item request-user-input payload.
1116
+
1117
+ Args:
1118
+ params: Request payload for `item/tool/requestUserInput`.
1119
+
1120
+ Returns:
1121
+ App-server response payload.
1122
+ """
1123
+ return await self._request_dict(
1124
+ "item/tool/requestUserInput", _coerce_keys(dict(params))
1125
+ )
1126
+
1127
+ async def item_command_execution_request_approval(
1128
+ self, *, params: Mapping[str, Any]
1129
+ ) -> Dict[str, Any]:
1130
+ """
1131
+ Send an item command-execution approval payload.
1132
+
1133
+ Args:
1134
+ params: Request payload for `item/commandExecution/requestApproval`.
1135
+
1136
+ Returns:
1137
+ App-server response payload.
1138
+ """
1139
+ return await self._request_dict(
1140
+ "item/commandExecution/requestApproval", _coerce_keys(dict(params))
1141
+ )
1142
+
1143
+ async def item_file_change_request_approval(
1144
+ self, *, params: Mapping[str, Any]
1145
+ ) -> Dict[str, Any]:
1146
+ """
1147
+ Send an item file-change approval payload.
1148
+
1149
+ Args:
1150
+ params: Request payload for `item/fileChange/requestApproval`.
1151
+
1152
+ Returns:
1153
+ App-server response payload.
1154
+ """
1155
+ return await self._request_dict(
1156
+ "item/fileChange/requestApproval", _coerce_keys(dict(params))
1157
+ )
1158
+
1159
+ async def mock_experimental_method(
1160
+ self, *, params: Optional[Mapping[str, Any]] = None
1161
+ ) -> Dict[str, Any]:
1162
+ """
1163
+ Call a mock experimental app-server endpoint.
1164
+
1165
+ Args:
1166
+ params: Optional request payload.
1167
+
1168
+ Returns:
1169
+ App-server response payload.
1170
+ """
1171
+ if not self._options.experimental_api_enabled:
1172
+ raise CodexError(
1173
+ "`mock/experimentalMethod` requires "
1174
+ "AppServerOptions(experimental_api_enabled=True)."
1175
+ )
1176
+ payload = _coerce_keys(dict(params)) if params is not None else {}
1177
+ return await self._request_dict("mock/experimentalMethod", payload)
1178
+
941
1179
  async def feedback_upload(
942
1180
  self,
943
1181
  *,
@@ -1109,6 +1347,46 @@ class AppServerSkillInput(TypedDict):
1109
1347
  path: str
1110
1348
 
1111
1349
 
1350
+ class SkillsConfigWriteRequest(TypedDict, total=False):
1351
+ """Typed payload for `skills/config/write` requests."""
1352
+
1353
+ path: str
1354
+ enabled: bool
1355
+ mode: str
1356
+
1357
+
1358
+ class SkillsRemoteReadRequest(TypedDict, total=False):
1359
+ """Typed payload for `skills/remote/read` requests."""
1360
+
1361
+ cwds: List[str]
1362
+ enabled: bool
1363
+ hazelnut_scope: str
1364
+ hazelnutScope: str
1365
+ product_surface: str
1366
+ productSurface: str
1367
+
1368
+
1369
+ class SkillsRemoteWriteRequest(TypedDict, total=False):
1370
+ """Typed payload for `skills/remote/write` requests."""
1371
+
1372
+ hazelnut_id: str
1373
+ hazelnutId: str
1374
+ is_preload: bool
1375
+ isPreload: bool
1376
+
1377
+
1378
+ class ItemToolCallRequest(TypedDict, total=False):
1379
+ """Typed payload for `item/tool/call` requests."""
1380
+
1381
+ name: str
1382
+ tool_name: str
1383
+ toolName: str
1384
+ tool_call_id: str
1385
+ toolCallId: str
1386
+ arguments: Mapping[str, Any]
1387
+ args: Mapping[str, Any]
1388
+
1389
+
1112
1390
  AppServerUserInput = Union[
1113
1391
  AppServerTextInput,
1114
1392
  AppServerImageInput,
@@ -1180,6 +1458,21 @@ def _coerce_keys(params: Mapping[str, Any]) -> Dict[str, Any]:
1180
1458
  return coerced
1181
1459
 
1182
1460
 
1461
+ def _validate_alias_conflicts(
1462
+ params: Mapping[str, Any],
1463
+ alias_pairs: Sequence[tuple[str, str]],
1464
+ *,
1465
+ context: str,
1466
+ ) -> None:
1467
+ """Reject payloads that provide both snake_case and camelCase aliases."""
1468
+ for snake_case_key, camel_case_key in alias_pairs:
1469
+ if snake_case_key in params and camel_case_key in params:
1470
+ raise CodexError(
1471
+ f"{context} received both '{snake_case_key}' and "
1472
+ f"'{camel_case_key}'. Provide only one key variant."
1473
+ )
1474
+
1475
+
1183
1476
  def _snake_to_camel(value: str) -> str:
1184
1477
  """Convert snake_case strings to lowerCamelCase."""
1185
1478
  parts = value.split("_")