codexapi 0.5.4__py3-none-any.whl

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.
codexapi/__init__.py ADDED
@@ -0,0 +1,18 @@
1
+ """Minimal Python API for running the Codex CLI."""
2
+
3
+ from .agent import Agent, agent
4
+ from .foreach import ForeachResult, foreach
5
+ from .task import Task, TaskFailed, TaskResult, task, task_result
6
+
7
+ __all__ = [
8
+ "Agent",
9
+ "ForeachResult",
10
+ "Task",
11
+ "TaskFailed",
12
+ "TaskResult",
13
+ "agent",
14
+ "foreach",
15
+ "task",
16
+ "task_result",
17
+ ]
18
+ __version__ = "0.5.4"
codexapi/__main__.py ADDED
@@ -0,0 +1,5 @@
1
+ from .cli import main
2
+
3
+
4
+ if __name__ == "__main__":
5
+ main()
codexapi/agent.py ADDED
@@ -0,0 +1,145 @@
1
+ """Codex CLI wrapper used by the codexapi public interface."""
2
+
3
+ import json
4
+ import os
5
+ import shlex
6
+ import subprocess
7
+
8
+ _CODEX_BIN = os.environ.get("CODEX_BIN", "codex")
9
+
10
+
11
+ def agent(prompt, cwd=None, yolo=True, flags=None):
12
+ """Run a single Codex turn and return only the agent's message.
13
+
14
+ Args:
15
+ prompt: The user prompt to send to Codex.
16
+ cwd: Optional working directory for the Codex session.
17
+ yolo: Whether to pass --yolo to Codex.
18
+ flags: Additional raw CLI flags to pass to Codex.
19
+
20
+ Returns:
21
+ The agent's visible response text with reasoning traces removed.
22
+ """
23
+ message, _thread_id = _run_codex(prompt, cwd, None, yolo, flags)
24
+ return message
25
+
26
+
27
+ class Agent:
28
+ """Stateful Codex session wrapper that resumes the same conversation.
29
+
30
+ Example:
31
+ session = Agent()
32
+ first = session("Say hi")
33
+ follow_up = session("What did you just say?")
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ cwd=None,
39
+ yolo=True,
40
+ thread_id=None,
41
+ flags=None,
42
+ ):
43
+ """Create a new session wrapper.
44
+
45
+ Args:
46
+ cwd: Optional working directory for the Codex session.
47
+ yolo: Whether to pass --yolo to Codex.
48
+ agent: Agent backend to use (only "codex" is supported).
49
+ trace_id: Optional Codex thread id to resume from the first call.
50
+ flags: Additional raw CLI flags to pass to Codex.
51
+ """
52
+ self.cwd = cwd
53
+ self._yolo = yolo
54
+ self._flags = flags
55
+ self.thread_id = thread_id
56
+
57
+ def __call__(self, prompt):
58
+ """Send a prompt to Codex and return only the agent's message."""
59
+ message, thread_id = _run_codex(
60
+ prompt,
61
+ self.cwd,
62
+ self.thread_id,
63
+ self._yolo,
64
+ self._flags,
65
+ )
66
+ if thread_id:
67
+ self.thread_id = thread_id
68
+ return message
69
+
70
+
71
+ def _run_codex(prompt, cwd, thread_id, yolo, flags):
72
+ """Invoke the Codex CLI and return the message plus thread id (if any)."""
73
+ command = [
74
+ _CODEX_BIN,
75
+ "exec",
76
+ "--json",
77
+ "--color",
78
+ "never",
79
+ "--skip-git-repo-check",
80
+ ]
81
+ if yolo:
82
+ command.append("--yolo")
83
+ else:
84
+ command.append("--full-auto")
85
+ if flags:
86
+ command.extend(shlex.split(flags))
87
+ if cwd:
88
+ command.extend(["--cd", os.fspath(cwd)])
89
+ if thread_id:
90
+ command.extend(["resume", thread_id, "-"])
91
+ else:
92
+ command.append("-")
93
+
94
+ result = subprocess.run(
95
+ command,
96
+ input=prompt,
97
+ text=True,
98
+ capture_output=True,
99
+ cwd=os.fspath(cwd) if cwd else None,
100
+ )
101
+ if result.returncode != 0:
102
+ stderr = result.stderr.strip()
103
+ msg = f"Codex failed with exit code {result.returncode}."
104
+ if stderr:
105
+ msg = f"{msg}\n{stderr}"
106
+ raise RuntimeError(msg)
107
+
108
+ return _parse_jsonl(result.stdout)
109
+
110
+
111
+ def _parse_jsonl(output):
112
+ """Extract agent messages and the latest thread id from Codex JSONL output."""
113
+ thread_id = None
114
+ messages = []
115
+ raw_lines = []
116
+
117
+ for line in output.splitlines():
118
+ line = line.strip()
119
+ if not line:
120
+ continue
121
+ try:
122
+ event = json.loads(line)
123
+ except json.JSONDecodeError:
124
+ raw_lines.append(line)
125
+ continue
126
+
127
+ if event.get("type") == "thread.started":
128
+ maybe_thread = event.get("thread_id")
129
+ if isinstance(maybe_thread, str):
130
+ thread_id = maybe_thread
131
+
132
+ if event.get("type") == "item.completed":
133
+ item = event.get("item") or {}
134
+ if item.get("type") == "agent_message":
135
+ text = item.get("text")
136
+ if isinstance(text, str):
137
+ messages.append(text)
138
+
139
+ if not messages:
140
+ fallback = "\n".join(raw_lines) if raw_lines else output.strip()
141
+ raise RuntimeError(
142
+ "Codex returned no agent message. Raw output:\n" + fallback
143
+ )
144
+
145
+ return "\n\n".join(messages), thread_id