diff --git a/README.md b/README.md index 3ce04aa..bffbecf 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ CODEX_API_KEY="" ### 2. Install dependencies ```bash -pip install modaic dspy python-dotenv +uv add modaic dspy ``` ### 3. Use the agent @@ -44,9 +44,6 @@ pip install modaic dspy python-dotenv from modaic import AutoAgent import dspy from pydantic import BaseModel -from dotenv import load_dotenv - -load_dotenv() class Dependencies(BaseModel): dependencies: list[str] diff --git a/main.py b/main.py index b66b97b..707ab2b 100644 --- a/main.py +++ b/main.py @@ -1,17 +1,19 @@ -from dotenv import load_dotenv from src.index import CodexAgent, CodexAgentConfig -load_dotenv() - codex_agent = CodexAgent(CodexAgentConfig()) def main(): - codex_agent.push_to_hub("darinkishore/codex-agent", with_code=True, commit_message="add thread id property") - #result = codex_agent(message="What files are in this directory?") - #print(result.answer) # String response - #print(result.trace) # Execution items (commands, files, etc.) - #print(result.usage) # Token counts + codex_agent.push_to_hub( + "darinkishore/codex-agent", + with_code=True, + commit_message="update readme", + ) + # result = codex_agent(message="What files are in this directory?") + # print(result.answer) # String response + # print(result.trace) # Execution items (commands, files, etc.) + # print(result.usage) # Token counts + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/src/codex/client.py b/src/codex/client.py index a8bd18e..7c21972 100644 --- a/src/codex/client.py +++ b/src/codex/client.py @@ -17,6 +17,8 @@ class Codex: thread_options = options or ThreadOptions() return Thread(self._exec, self._options, thread_options) - def resume_thread(self, thread_id: str, options: Optional[ThreadOptions] = None) -> Thread: + def resume_thread( + self, thread_id: str, options: Optional[ThreadOptions] = None + ) -> Thread: thread_options = options or ThreadOptions() return Thread(self._exec, self._options, thread_options, thread_id) diff --git a/src/codex/config.py b/src/codex/config.py index 0ab71e3..0dfdecd 100644 --- a/src/codex/config.py +++ b/src/codex/config.py @@ -6,6 +6,7 @@ from typing import Mapping, Optional, TYPE_CHECKING if TYPE_CHECKING: # pragma: no cover - typing only from pydantic import BaseModel as PydanticBaseModel + SchemaInput = Mapping[str, object] | type[PydanticBaseModel] | PydanticBaseModel else: SchemaInput = Mapping[str, object] diff --git a/src/codex/events.py b/src/codex/events.py index 1832ddb..745394e 100644 --- a/src/codex/events.py +++ b/src/codex/events.py @@ -100,7 +100,9 @@ def _parse_usage(payload: object) -> Usage: data = _ensure_dict(payload) return Usage( input_tokens=_ensure_int(data.get("input_tokens"), "input_tokens"), - cached_input_tokens=_ensure_int(data.get("cached_input_tokens"), "cached_input_tokens"), + cached_input_tokens=_ensure_int( + data.get("cached_input_tokens"), "cached_input_tokens" + ), output_tokens=_ensure_int(data.get("output_tokens"), "output_tokens"), ) diff --git a/src/codex/exceptions.py b/src/codex/exceptions.py index 843d428..264f970 100644 --- a/src/codex/exceptions.py +++ b/src/codex/exceptions.py @@ -26,7 +26,9 @@ class SpawnError(CodexError): def __init__(self, command: Sequence[str] | None, error: OSError) -> None: self.command = list(command) if command else None self.original_error = error - super().__init__(f"Failed to spawn codex exec: {_format_command(self.command)}: {error}") + super().__init__( + f"Failed to spawn codex exec: {_format_command(self.command)}: {error}" + ) @dataclass(slots=True) diff --git a/src/codex/exec.py b/src/codex/exec.py index 0a3ca65..12e68d0 100644 --- a/src/codex/exec.py +++ b/src/codex/exec.py @@ -82,6 +82,7 @@ class CodexExec: stderr_thread: Thread | None = None if process.stderr: + def _drain_stderr(pipe: io.TextIOBase, buffer: list[str]) -> None: while True: try: diff --git a/src/codex/items.py b/src/codex/items.py index 7a7e16e..8dd0a81 100644 --- a/src/codex/items.py +++ b/src/codex/items.py @@ -171,12 +171,16 @@ def parse_thread_item(payload: object) -> ThreadItem: if type_name == "command_execution": command = _ensure_str(payload.get("command"), "command") - aggregated_output = _ensure_str(payload.get("aggregated_output"), "aggregated_output") + aggregated_output = _ensure_str( + payload.get("aggregated_output"), "aggregated_output" + ) status_str = _ensure_str(payload.get("status"), "status") try: status = CommandExecutionStatus(status_str) except ValueError as exc: - raise CodexError(f"Unsupported command execution status: {status_str}") from exc + raise CodexError( + f"Unsupported command execution status: {status_str}" + ) from exc exit_code = payload.get("exit_code") exit_value = int(exit_code) if isinstance(exit_code, int) else None return CommandExecutionItem( diff --git a/src/codex/schema.py b/src/codex/schema.py index 92f42a4..f8b86ae 100644 --- a/src/codex/schema.py +++ b/src/codex/schema.py @@ -23,7 +23,11 @@ def _get_pydantic_base_model() -> Type[Any] | None: # pragma: no cover - import def _is_pydantic_model(value: object) -> bool: base_model = _get_pydantic_base_model() - return isinstance(value, type) and base_model is not None and issubclass(value, base_model) + return ( + isinstance(value, type) + and base_model is not None + and issubclass(value, base_model) + ) def _is_pydantic_instance(value: object) -> bool: diff --git a/src/codex/thread.py b/src/codex/thread.py index 67c6564..88159d0 100644 --- a/src/codex/thread.py +++ b/src/codex/thread.py @@ -53,11 +53,15 @@ class Thread: def id(self) -> Optional[str]: return self._id - def run_streamed(self, prompt: str, turn_options: Optional[TurnOptions] = None) -> ThreadStream: + def run_streamed( + self, prompt: str, turn_options: Optional[TurnOptions] = None + ) -> ThreadStream: events = self._stream_events(prompt, turn_options) return ThreadStream(events=events) - def run(self, prompt: str, turn_options: Optional[TurnOptions] = None) -> ThreadRunResult: + def run( + self, prompt: str, turn_options: Optional[TurnOptions] = None + ) -> ThreadRunResult: final_response = "" items: list[ThreadItem] = [] usage: Optional[Usage] = None diff --git a/src/codex_dspy/agent.py b/src/codex_dspy/agent.py index 7686990..6ac445f 100644 --- a/src/codex_dspy/agent.py +++ b/src/codex_dspy/agent.py @@ -170,7 +170,9 @@ class CodexModule(dspy.Module): if not _is_str_type(self.output_type): # Parse as Pydantic model try: - parsed_output = self.output_type.model_validate_json(result.final_response) + parsed_output = self.output_type.model_validate_json( + result.final_response + ) except Exception as e: # Provide helpful error with response preview response_preview = result.final_response[:500] diff --git a/src/index.py b/src/index.py index 62294fe..5226f3b 100644 --- a/src/index.py +++ b/src/index.py @@ -6,6 +6,7 @@ from dspy.signatures.signature import Signature from dspy.primitives.prediction import Prediction from typing import Optional + class CodexAgentConfig(PrecompiledConfig): signature: str | type[Signature] = "message:str -> answer:str" working_directory: str = "." @@ -16,8 +17,9 @@ class CodexAgentConfig(PrecompiledConfig): base_url: Optional[str] = None codex_path_override: Optional[str] = "/opt/homebrew/bin/codex" + class CodexAgent(PrecompiledAgent): - config : CodexAgentConfig + config: CodexAgentConfig def __init__(self, config: CodexAgentConfig, **kwargs): super().__init__(config, **kwargs) @@ -30,9 +32,9 @@ class CodexAgent(PrecompiledAgent): skip_git_repo_check=config.skip_git_repo_check, api_key=config.api_key, base_url=config.base_url, - codex_path_override=config.codex_path_override + codex_path_override=config.codex_path_override, ) - + def forward(self, **kwargs) -> Prediction: return self.codex_module(**kwargs) @@ -46,4 +48,4 @@ class CodexAgent(PrecompiledAgent): Returns: Thread ID string, or None if no forward() calls have been made yet """ - return self.codex_module.thread.id \ No newline at end of file + return self.codex_module.thread.id