codexed 0.0.2__tar.gz → 0.0.4__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 (30) hide show
  1. {codexed-0.0.2 → codexed-0.0.4}/PKG-INFO +1 -1
  2. {codexed-0.0.2 → codexed-0.0.4}/pyproject.toml +1 -1
  3. {codexed-0.0.2 → codexed-0.0.4}/src/codexed/client.py +19 -11
  4. {codexed-0.0.2 → codexed-0.0.4}/src/codexed/models/request_params.py +73 -19
  5. {codexed-0.0.2 → codexed-0.0.4}/src/codexed/models/responses.py +10 -0
  6. {codexed-0.0.2 → codexed-0.0.4}/src/codexed/request_handlers.py +45 -5
  7. {codexed-0.0.2 → codexed-0.0.4}/LICENSE +0 -0
  8. {codexed-0.0.2 → codexed-0.0.4}/README.md +0 -0
  9. {codexed-0.0.2 → codexed-0.0.4}/src/codexed/README.md +0 -0
  10. {codexed-0.0.2 → codexed-0.0.4}/src/codexed/__init__.py +0 -0
  11. {codexed-0.0.2 → codexed-0.0.4}/src/codexed/example.py +0 -0
  12. {codexed-0.0.2 → codexed-0.0.4}/src/codexed/exceptions.py +0 -0
  13. {codexed-0.0.2 → codexed-0.0.4}/src/codexed/helpers.py +0 -0
  14. {codexed-0.0.2 → codexed-0.0.4}/src/codexed/log.py +0 -0
  15. {codexed-0.0.2 → codexed-0.0.4}/src/codexed/models/__init__.py +0 -0
  16. {codexed-0.0.2 → codexed-0.0.4}/src/codexed/models/base.py +0 -0
  17. {codexed-0.0.2 → codexed-0.0.4}/src/codexed/models/codex_types.py +0 -0
  18. {codexed-0.0.2 → codexed-0.0.4}/src/codexed/models/command_action.py +0 -0
  19. {codexed-0.0.2 → codexed-0.0.4}/src/codexed/models/event_data.py +0 -0
  20. {codexed-0.0.2 → codexed-0.0.4}/src/codexed/models/events.py +0 -0
  21. {codexed-0.0.2 → codexed-0.0.4}/src/codexed/models/mcp_server.py +0 -0
  22. {codexed-0.0.2 → codexed-0.0.4}/src/codexed/models/misc.py +0 -0
  23. {codexed-0.0.2 → codexed-0.0.4}/src/codexed/models/response_item.py +0 -0
  24. {codexed-0.0.2 → codexed-0.0.4}/src/codexed/models/thread_item.py +0 -0
  25. {codexed-0.0.2 → codexed-0.0.4}/src/codexed/models/thread_status.py +0 -0
  26. {codexed-0.0.2 → codexed-0.0.4}/src/codexed/models/token_usage.py +0 -0
  27. {codexed-0.0.2 → codexed-0.0.4}/src/codexed/models/tool_config.py +0 -0
  28. {codexed-0.0.2 → codexed-0.0.4}/src/codexed/models/user_input.py +0 -0
  29. {codexed-0.0.2 → codexed-0.0.4}/src/codexed/models/web_search.py +0 -0
  30. {codexed-0.0.2 → codexed-0.0.4}/src/codexed/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codexed
3
- Version: 0.0.2
3
+ Version: 0.0.4
4
4
  Summary: Alternative codex app-server client
5
5
  Keywords:
6
6
  Author: Philipp Temminghoff
@@ -2,7 +2,7 @@
2
2
 
3
3
  [project]
4
4
  name = "codexed"
5
- version = "0.0.2"
5
+ version = "0.0.4"
6
6
  description = "Alternative codex app-server client"
7
7
  readme = "README.md"
8
8
  requires-python = ">=3.13"
@@ -104,6 +104,8 @@ from codexed.request_handlers import (
104
104
  SERVER_REQUEST_COMMAND_APPROVAL,
105
105
  SERVER_REQUEST_DYNAMIC_TOOL_CALL,
106
106
  SERVER_REQUEST_FILE_CHANGE_APPROVAL,
107
+ SERVER_REQUEST_MCP_ELICITATION,
108
+ SERVER_REQUEST_SKILL_APPROVAL,
107
109
  SERVER_REQUEST_TYPES,
108
110
  SERVER_REQUEST_USER_INPUT,
109
111
  create_auto_approve_dict,
@@ -141,10 +143,10 @@ if TYPE_CHECKING:
141
143
  )
142
144
  from codexed.models.request_params import HazelnutScope, LoginType, ProductSurface
143
145
  from codexed.request_handlers import (
144
- CommandApprovalHandler,
146
+ ApprovalHandler,
145
147
  DynamicToolCallHandler,
146
- FileChangeApprovalHandler,
147
148
  HandlerMethod,
149
+ McpElicitationHandler,
148
150
  ServerRequestHandler,
149
151
  UserInputHandler,
150
152
  )
@@ -354,10 +356,10 @@ class CodexClient:
354
356
  profile: str | None = None,
355
357
  env_vars: dict[str, str] | None = None,
356
358
  mcp_servers: Mapping[str, McpServerConfig] | None = None,
357
- on_command_approval: CommandApprovalHandler | None = None,
358
- on_file_change_approval: FileChangeApprovalHandler | None = None,
359
+ on_approval: ApprovalHandler | None = None,
359
360
  on_user_input: UserInputHandler | None = None,
360
361
  on_dynamic_tool_call: DynamicToolCallHandler | None = None,
362
+ on_mcp_elicitation: McpElicitationHandler | None = None,
361
363
  ) -> None:
362
364
  """Initialize the Codex app-server client.
363
365
 
@@ -367,10 +369,11 @@ class CodexClient:
367
369
  env_vars: Optional environment variables to set for the Codex process.
368
370
  mcp_servers: Optional MCP servers to inject programmatically.
369
371
  Keys are server names, values are server configurations.
370
- on_command_approval: Handler for command execution approval requests.
371
- on_file_change_approval: Handler for file change approval requests.
372
+ on_approval: Handler for all approval requests
373
+ (command execution, file change, and skill approval).
372
374
  on_user_input: Handler for tool user input requests.
373
375
  on_dynamic_tool_call: Handler for dynamic tool call requests.
376
+ on_mcp_elicitation: Handler for MCP elicitation requests.
374
377
  """
375
378
  self._codex_command = codex_command
376
379
  self._profile = profile
@@ -385,14 +388,16 @@ class CodexClient:
385
388
  self._writer_lock = asyncio.Lock()
386
389
  self._active_threads: set[str] = set()
387
390
  self._server_request_handlers: dict[str, ServerRequestHandler] = {}
388
- if on_command_approval:
389
- self.register_handler(SERVER_REQUEST_COMMAND_APPROVAL, on_command_approval) # type: ignore[arg-type]
390
- if on_file_change_approval:
391
- self.register_handler(SERVER_REQUEST_FILE_CHANGE_APPROVAL, on_file_change_approval) # type: ignore[arg-type]
391
+ if on_approval:
392
+ self.register_handler(SERVER_REQUEST_COMMAND_APPROVAL, on_approval) # type: ignore[arg-type]
393
+ self.register_handler(SERVER_REQUEST_FILE_CHANGE_APPROVAL, on_approval) # type: ignore[arg-type]
394
+ self.register_handler(SERVER_REQUEST_SKILL_APPROVAL, on_approval) # type: ignore[arg-type]
392
395
  if on_user_input:
393
396
  self.register_handler(SERVER_REQUEST_USER_INPUT, on_user_input) # type: ignore[arg-type]
394
397
  if on_dynamic_tool_call:
395
398
  self.register_handler(SERVER_REQUEST_DYNAMIC_TOOL_CALL, on_dynamic_tool_call) # type: ignore[arg-type]
399
+ if on_mcp_elicitation:
400
+ self.register_handler(SERVER_REQUEST_MCP_ELICITATION, on_mcp_elicitation) # type: ignore[arg-type]
396
401
 
397
402
  async def __aenter__(self) -> Self:
398
403
  """Async context manager entry - starts the app-server."""
@@ -1768,7 +1773,10 @@ class CodexClient:
1768
1773
  return
1769
1774
 
1770
1775
  try:
1771
- parsed_params = params_type.model_validate(params)
1776
+ if isinstance(params_type, TypeAdapter):
1777
+ parsed_params = params_type.validate_python(params)
1778
+ else:
1779
+ parsed_params = params_type.model_validate(params)
1772
1780
  response_model = await handler(parsed_params)
1773
1781
  await self._send_server_request_response(request_id, response_model)
1774
1782
  except Exception:
@@ -1,7 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from collections.abc import Sequence # noqa: TC003
4
- from typing import Any, Literal, Self
4
+ from typing import Annotated, Any, Literal, Self
5
+
6
+ import mcp.types
7
+ from pydantic import Discriminator, TypeAdapter
5
8
 
6
9
  from codexed.models.base import CodexBaseModel
7
10
  from codexed.models.codex_types import ( # noqa: TC001
@@ -507,34 +510,85 @@ McpElicitationAction = Literal["accept", "decline", "cancel"]
507
510
  """Action taken by the user on an elicitation request."""
508
511
 
509
512
 
510
- class McpElicitationFormParams(CodexBaseModel):
511
- """Form-mode MCP elicitation request."""
513
+ class _McpElicitationBase(CodexBaseModel):
514
+ """Common fields for MCP elicitation requests."""
512
515
 
513
- mode: Literal["form"] = "form"
516
+ thread_id: str
517
+ turn_id: str | None = None
518
+ server_name: str
514
519
  meta: Any | None = None
515
520
  message: str
521
+
522
+
523
+ class McpElicitationFormParams(_McpElicitationBase):
524
+ """Form-mode MCP elicitation request."""
525
+
526
+ mode: Literal["form"] = "form"
516
527
  requested_schema: dict[str, Any]
517
528
 
529
+ def to_mcp(self) -> mcp.types.ElicitRequestFormParams:
530
+ return mcp.types.ElicitRequestFormParams(
531
+ message=self.message,
532
+ requestedSchema=self.requested_schema,
533
+ )
518
534
 
519
- class McpElicitationUrlParams(CodexBaseModel):
535
+ @classmethod
536
+ def from_mcp(
537
+ cls,
538
+ params: mcp.types.ElicitRequestFormParams,
539
+ thread_id: str,
540
+ server_name: str,
541
+ turn_id: str | None = None,
542
+ ) -> McpElicitationFormParams:
543
+ """Create from MCP ElicitRequestFormParams."""
544
+ return cls(
545
+ message=params.message,
546
+ requested_schema=dict(params.requestedSchema),
547
+ thread_id=thread_id,
548
+ server_name=server_name,
549
+ turn_id=turn_id,
550
+ )
551
+
552
+
553
+ class McpElicitationUrlParams(_McpElicitationBase):
520
554
  """URL-mode MCP elicitation request (browser-based auth)."""
521
555
 
522
556
  mode: Literal["url"] = "url"
523
- meta: Any | None = None
524
- message: str
525
557
  url: str
526
558
  elicitation_id: str
527
559
 
560
+ def to_mcp(self) -> mcp.types.ElicitRequestURLParams:
561
+ return mcp.types.ElicitRequestURLParams(
562
+ message=self.message,
563
+ url=self.url,
564
+ elicitationId=self.elicitation_id,
565
+ )
528
566
 
529
- class McpServerElicitationRequestParams(CodexBaseModel):
530
- """Parameters for mcpServer/elicitation/request server request."""
531
-
532
- thread_id: str
533
- turn_id: str | None = None
534
- server_name: str
535
- mode: Literal["form", "url"]
536
- meta: Any | None = None
537
- message: str
538
- requested_schema: dict[str, Any] | None = None
539
- url: str | None = None
540
- elicitation_id: str | None = None
567
+ @classmethod
568
+ def from_mcp(
569
+ cls,
570
+ params: mcp.types.ElicitRequestURLParams,
571
+ thread_id: str,
572
+ server_name: str,
573
+ turn_id: str | None = None,
574
+ ) -> McpElicitationUrlParams:
575
+ """Create from MCP ElicitRequestURLParams."""
576
+ return cls(
577
+ message=params.message,
578
+ url=params.url,
579
+ elicitation_id=params.elicitationId,
580
+ thread_id=thread_id,
581
+ server_name=server_name,
582
+ turn_id=turn_id,
583
+ )
584
+
585
+
586
+ McpServerElicitationRequestParams = Annotated[
587
+ McpElicitationFormParams | McpElicitationUrlParams,
588
+ Discriminator("mode"),
589
+ ]
590
+ """Parameters for mcpServer/elicitation/request, discriminated by mode."""
591
+
592
+ mcp_elicitation_adapter: TypeAdapter[McpServerElicitationRequestParams] = TypeAdapter(
593
+ McpServerElicitationRequestParams
594
+ )
@@ -2,6 +2,8 @@ from __future__ import annotations
2
2
 
3
3
  from typing import Any, Literal
4
4
 
5
+ import mcp.types
6
+
5
7
  from codexed.models.base import CodexBaseModel
6
8
  from codexed.models.codex_types import ( # noqa: TC001
7
9
  AskForApproval,
@@ -62,6 +64,14 @@ class McpServerElicitationResponse(CodexBaseModel):
62
64
  content: Any | None = None
63
65
  meta: Any | None = None
64
66
 
67
+ def to_mcp(self) -> mcp.types.ElicitResult:
68
+ return mcp.types.ElicitResult(action=self.action, content=self.content)
69
+
70
+ @classmethod
71
+ def from_mcp(cls, result: mcp.types.ElicitResult) -> McpServerElicitationResponse:
72
+ """Create from MCP ElicitResult."""
73
+ return cls(action=result.action, content=result.content)
74
+
65
75
 
66
76
  class ThreadReadResponse(CodexBaseModel):
67
77
  """Response for thread/read request."""
@@ -5,6 +5,8 @@ from __future__ import annotations
5
5
  from collections.abc import Awaitable, Callable
6
6
  from typing import Literal
7
7
 
8
+ from pydantic import TypeAdapter
9
+
8
10
  from codexed.models import (
9
11
  CommandExecutionRequestApprovalParams,
10
12
  CommandExecutionRequestApprovalResponse,
@@ -12,11 +14,18 @@ from codexed.models import (
12
14
  DynamicToolCallResponse,
13
15
  FileChangeRequestApprovalParams,
14
16
  FileChangeRequestApprovalResponse,
15
- McpServerElicitationRequestParams,
16
17
  McpServerElicitationResponse,
18
+ SkillRequestApprovalParams,
19
+ SkillRequestApprovalResponse,
17
20
  ToolRequestUserInputParams,
18
21
  ToolRequestUserInputResponse,
19
22
  )
23
+ from codexed.models.request_params import (
24
+ McpElicitationFormParams,
25
+ McpElicitationUrlParams,
26
+ McpServerElicitationRequestParams,
27
+ mcp_elicitation_adapter,
28
+ )
20
29
 
21
30
 
22
31
  # Server request method constants
@@ -24,6 +33,7 @@ SERVER_REQUEST_COMMAND_APPROVAL = "item/commandExecution/requestApproval"
24
33
  SERVER_REQUEST_FILE_CHANGE_APPROVAL = "item/fileChange/requestApproval"
25
34
  SERVER_REQUEST_USER_INPUT = "item/tool/requestUserInput"
26
35
  SERVER_REQUEST_DYNAMIC_TOOL_CALL = "item/tool/call"
36
+ SERVER_REQUEST_SKILL_APPROVAL = "skill/requestApproval"
27
37
  SERVER_REQUEST_MCP_ELICITATION = "mcpServer/elicitation/request"
28
38
 
29
39
  HandlerMethod = Literal[
@@ -31,21 +41,25 @@ HandlerMethod = Literal[
31
41
  "item/fileChange/requestApproval",
32
42
  "item/tool/requestUserInput",
33
43
  "item/tool/call",
44
+ "skill/requestApproval",
34
45
  "mcpServer/elicitation/request",
35
46
  ]
36
47
  # Type for server request parameter models
37
48
  ServerRequestParams = (
38
49
  CommandExecutionRequestApprovalParams
39
50
  | FileChangeRequestApprovalParams
51
+ | SkillRequestApprovalParams
40
52
  | ToolRequestUserInputParams
41
53
  | DynamicToolCallParams
42
- | McpServerElicitationRequestParams
54
+ | McpElicitationFormParams
55
+ | McpElicitationUrlParams
43
56
  )
44
57
 
45
58
  # Type for server request response models
46
59
  ServerRequestResponse = (
47
60
  CommandExecutionRequestApprovalResponse
48
61
  | FileChangeRequestApprovalResponse
62
+ | SkillRequestApprovalResponse
49
63
  | ToolRequestUserInputResponse
50
64
  | DynamicToolCallResponse
51
65
  | McpServerElicitationResponse
@@ -69,8 +83,24 @@ McpElicitationHandler = Callable[
69
83
  [McpServerElicitationRequestParams], Awaitable[McpServerElicitationResponse]
70
84
  ]
71
85
 
72
- # Map from wire method names to param/response model types
73
- SERVER_REQUEST_TYPES: dict[str, tuple[type[ServerRequestParams], type[ServerRequestResponse]]] = {
86
+ # Unified approval handler: one callback for all approval types
87
+ ApprovalParams = (
88
+ CommandExecutionRequestApprovalParams
89
+ | FileChangeRequestApprovalParams
90
+ | SkillRequestApprovalParams
91
+ )
92
+ ApprovalResponse = (
93
+ CommandExecutionRequestApprovalResponse
94
+ | FileChangeRequestApprovalResponse
95
+ | SkillRequestApprovalResponse
96
+ )
97
+ ApprovalHandler = Callable[[ApprovalParams], Awaitable[ApprovalResponse]]
98
+
99
+ # Validator: either a model class or a TypeAdapter for discriminated unions
100
+ ParamsValidator = type[ServerRequestParams] | TypeAdapter[ServerRequestParams]
101
+
102
+ # Map from wire method names to param validator + response model type
103
+ SERVER_REQUEST_TYPES: dict[str, tuple[ParamsValidator, type[ServerRequestResponse]]] = {
74
104
  SERVER_REQUEST_COMMAND_APPROVAL: (
75
105
  CommandExecutionRequestApprovalParams,
76
106
  CommandExecutionRequestApprovalResponse,
@@ -84,8 +114,12 @@ SERVER_REQUEST_TYPES: dict[str, tuple[type[ServerRequestParams], type[ServerRequ
84
114
  ToolRequestUserInputResponse,
85
115
  ),
86
116
  SERVER_REQUEST_DYNAMIC_TOOL_CALL: (DynamicToolCallParams, DynamicToolCallResponse),
117
+ SERVER_REQUEST_SKILL_APPROVAL: (
118
+ SkillRequestApprovalParams,
119
+ SkillRequestApprovalResponse,
120
+ ),
87
121
  SERVER_REQUEST_MCP_ELICITATION: (
88
- McpServerElicitationRequestParams,
122
+ mcp_elicitation_adapter, # type: ignore[dict-item]
89
123
  McpServerElicitationResponse,
90
124
  ),
91
125
  }
@@ -118,6 +152,11 @@ def create_auto_approve_dict() -> dict[str, ServerRequestHandler]:
118
152
  ) -> ServerRequestResponse:
119
153
  return DynamicToolCallResponse(content_items=[], success=False)
120
154
 
155
+ async def auto_approve_skill(
156
+ _params: ServerRequestParams,
157
+ ) -> ServerRequestResponse:
158
+ return SkillRequestApprovalResponse(decision="allow")
159
+
121
160
  async def auto_decline_elicitation(
122
161
  _params: ServerRequestParams,
123
162
  ) -> ServerRequestResponse:
@@ -128,5 +167,6 @@ def create_auto_approve_dict() -> dict[str, ServerRequestHandler]:
128
167
  dct[SERVER_REQUEST_FILE_CHANGE_APPROVAL] = auto_approve_file_change
129
168
  dct[SERVER_REQUEST_USER_INPUT] = auto_approve_user_input
130
169
  dct[SERVER_REQUEST_DYNAMIC_TOOL_CALL] = auto_approve_dynamic_tool
170
+ dct[SERVER_REQUEST_SKILL_APPROVAL] = auto_approve_skill
131
171
  dct[SERVER_REQUEST_MCP_ELICITATION] = auto_decline_elicitation
132
172
  return dct
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes