codexapi 0.12.9__tar.gz → 0.12.10__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 (33) hide show
  1. {codexapi-0.12.9/src/codexapi.egg-info → codexapi-0.12.10}/PKG-INFO +9 -3
  2. {codexapi-0.12.9 → codexapi-0.12.10}/README.md +8 -2
  3. {codexapi-0.12.9 → codexapi-0.12.10}/pyproject.toml +1 -1
  4. {codexapi-0.12.9 → codexapi-0.12.10}/src/codexapi/__init__.py +3 -2
  5. {codexapi-0.12.9 → codexapi-0.12.10}/src/codexapi/agent.py +143 -10
  6. {codexapi-0.12.9 → codexapi-0.12.10}/src/codexapi/async_agent.py +13 -10
  7. {codexapi-0.12.9 → codexapi-0.12.10/src/codexapi.egg-info}/PKG-INFO +9 -3
  8. {codexapi-0.12.9 → codexapi-0.12.10}/tests/test_agent_backend.py +32 -2
  9. {codexapi-0.12.9 → codexapi-0.12.10}/LICENSE +0 -0
  10. {codexapi-0.12.9 → codexapi-0.12.10}/setup.cfg +0 -0
  11. {codexapi-0.12.9 → codexapi-0.12.10}/src/codexapi/__main__.py +0 -0
  12. {codexapi-0.12.9 → codexapi-0.12.10}/src/codexapi/agents.py +0 -0
  13. {codexapi-0.12.9 → codexapi-0.12.10}/src/codexapi/cli.py +0 -0
  14. {codexapi-0.12.9 → codexapi-0.12.10}/src/codexapi/foreach.py +0 -0
  15. {codexapi-0.12.9 → codexapi-0.12.10}/src/codexapi/gh_integration.py +0 -0
  16. {codexapi-0.12.9 → codexapi-0.12.10}/src/codexapi/lead.py +0 -0
  17. {codexapi-0.12.9 → codexapi-0.12.10}/src/codexapi/pushover.py +0 -0
  18. {codexapi-0.12.9 → codexapi-0.12.10}/src/codexapi/ralph.py +0 -0
  19. {codexapi-0.12.9 → codexapi-0.12.10}/src/codexapi/rate_limits.py +0 -0
  20. {codexapi-0.12.9 → codexapi-0.12.10}/src/codexapi/science.py +0 -0
  21. {codexapi-0.12.9 → codexapi-0.12.10}/src/codexapi/task.py +0 -0
  22. {codexapi-0.12.9 → codexapi-0.12.10}/src/codexapi/taskfile.py +0 -0
  23. {codexapi-0.12.9 → codexapi-0.12.10}/src/codexapi/welfare.py +0 -0
  24. {codexapi-0.12.9 → codexapi-0.12.10}/src/codexapi.egg-info/SOURCES.txt +0 -0
  25. {codexapi-0.12.9 → codexapi-0.12.10}/src/codexapi.egg-info/dependency_links.txt +0 -0
  26. {codexapi-0.12.9 → codexapi-0.12.10}/src/codexapi.egg-info/entry_points.txt +0 -0
  27. {codexapi-0.12.9 → codexapi-0.12.10}/src/codexapi.egg-info/requires.txt +0 -0
  28. {codexapi-0.12.9 → codexapi-0.12.10}/src/codexapi.egg-info/top_level.txt +0 -0
  29. {codexapi-0.12.9 → codexapi-0.12.10}/tests/test_agents.py +0 -0
  30. {codexapi-0.12.9 → codexapi-0.12.10}/tests/test_async_agent.py +0 -0
  31. {codexapi-0.12.9 → codexapi-0.12.10}/tests/test_rate_limits.py +0 -0
  32. {codexapi-0.12.9 → codexapi-0.12.10}/tests/test_science.py +0 -0
  33. {codexapi-0.12.9 → codexapi-0.12.10}/tests/test_task_progress.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: codexapi
3
- Version: 0.12.9
3
+ Version: 0.12.10
4
4
  Summary: Minimal Python API for running the Codex CLI.
5
5
  License: MIT
6
6
  Keywords: codex,agent,cli,openai
@@ -80,6 +80,8 @@ Use `backend="cursor"` (or set `CODEXAPI_BACKEND=cursor`) to switch to the
80
80
  Cursor agent backend.
81
81
  Use `fast=True` in Codex API calls, or `--fast` in the CLI, to opt into Codex
82
82
  fast mode. Normal mode is the default.
83
+ Use `model="..."` and `thinking="..."` in Codex API calls to override the
84
+ backend model and reasoning effort for a run.
83
85
 
84
86
  ## CLI
85
87
 
@@ -336,7 +338,7 @@ codexapi foreach list.txt task.yaml --retry-all
336
338
 
337
339
  ## API
338
340
 
339
- ### `agent(prompt, cwd=None, yolo=True, flags=None, include_thinking=False, backend=None, fast=False) -> str`
341
+ ### `agent(prompt, cwd=None, yolo=True, flags=None, include_thinking=False, backend=None, fast=False, model=None, thinking=None) -> str`
340
342
 
341
343
  Runs a single agent turn and returns only the agent's message. Any reasoning
342
344
  items are filtered out.
@@ -348,8 +350,10 @@ items are filtered out.
348
350
  - `include_thinking` (bool): when true, return all agent messages joined.
349
351
  - `backend` (str | None): `codex` or `cursor` (defaults to `CODEXAPI_BACKEND` or `codex`).
350
352
  - `fast` (bool): enable Codex fast mode (defaults to normal mode).
353
+ - `model` (str | None): backend model override. Codex maps this to `-c model=...`; Cursor maps it to `--model ...`.
354
+ - `thinking` (str | None): Codex reasoning effort override, mapped to `-c model_reasoning_effort=...`.
351
355
 
352
- ### `Agent(cwd=None, yolo=True, thread_id=None, flags=None, welfare=False, include_thinking=False, backend=None, fast=False)`
356
+ ### `Agent(cwd=None, yolo=True, thread_id=None, flags=None, welfare=False, include_thinking=False, backend=None, fast=False, model=None, thinking=None)`
353
357
 
354
358
  Creates a stateful session wrapper. Calling the instance sends the prompt into
355
359
  the same conversation and returns only the agent's message.
@@ -363,6 +367,8 @@ the same conversation and returns only the agent's message.
363
367
  - `include_thinking` (bool): when true, return all agent messages joined.
364
368
  - `backend` (str | None): `codex` or `cursor` (defaults to `CODEXAPI_BACKEND` or `codex`).
365
369
  - `fast` (bool): enable Codex fast mode (defaults to normal mode).
370
+ - `model` (str | None): backend model override.
371
+ - `thinking` (str | None): Codex reasoning effort override.
366
372
  For Cursor, `thread_id` corresponds to the `session_id` returned by the agent.
367
373
 
368
374
  ### `lead(minutes, prompt, cwd=None, yolo=True, flags=None, leadbook=None, backend=None, fast=False) -> dict`
@@ -65,6 +65,8 @@ Use `backend="cursor"` (or set `CODEXAPI_BACKEND=cursor`) to switch to the
65
65
  Cursor agent backend.
66
66
  Use `fast=True` in Codex API calls, or `--fast` in the CLI, to opt into Codex
67
67
  fast mode. Normal mode is the default.
68
+ Use `model="..."` and `thinking="..."` in Codex API calls to override the
69
+ backend model and reasoning effort for a run.
68
70
 
69
71
  ## CLI
70
72
 
@@ -321,7 +323,7 @@ codexapi foreach list.txt task.yaml --retry-all
321
323
 
322
324
  ## API
323
325
 
324
- ### `agent(prompt, cwd=None, yolo=True, flags=None, include_thinking=False, backend=None, fast=False) -> str`
326
+ ### `agent(prompt, cwd=None, yolo=True, flags=None, include_thinking=False, backend=None, fast=False, model=None, thinking=None) -> str`
325
327
 
326
328
  Runs a single agent turn and returns only the agent's message. Any reasoning
327
329
  items are filtered out.
@@ -333,8 +335,10 @@ items are filtered out.
333
335
  - `include_thinking` (bool): when true, return all agent messages joined.
334
336
  - `backend` (str | None): `codex` or `cursor` (defaults to `CODEXAPI_BACKEND` or `codex`).
335
337
  - `fast` (bool): enable Codex fast mode (defaults to normal mode).
338
+ - `model` (str | None): backend model override. Codex maps this to `-c model=...`; Cursor maps it to `--model ...`.
339
+ - `thinking` (str | None): Codex reasoning effort override, mapped to `-c model_reasoning_effort=...`.
336
340
 
337
- ### `Agent(cwd=None, yolo=True, thread_id=None, flags=None, welfare=False, include_thinking=False, backend=None, fast=False)`
341
+ ### `Agent(cwd=None, yolo=True, thread_id=None, flags=None, welfare=False, include_thinking=False, backend=None, fast=False, model=None, thinking=None)`
338
342
 
339
343
  Creates a stateful session wrapper. Calling the instance sends the prompt into
340
344
  the same conversation and returns only the agent's message.
@@ -348,6 +352,8 @@ the same conversation and returns only the agent's message.
348
352
  - `include_thinking` (bool): when true, return all agent messages joined.
349
353
  - `backend` (str | None): `codex` or `cursor` (defaults to `CODEXAPI_BACKEND` or `codex`).
350
354
  - `fast` (bool): enable Codex fast mode (defaults to normal mode).
355
+ - `model` (str | None): backend model override.
356
+ - `thinking` (str | None): Codex reasoning effort override.
351
357
  For Cursor, `thread_id` corresponds to the `session_id` returned by the agent.
352
358
 
353
359
  ### `lead(minutes, prompt, cwd=None, yolo=True, flags=None, leadbook=None, backend=None, fast=False) -> dict`
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "codexapi"
7
- version = "0.12.9"
7
+ version = "0.12.10"
8
8
  description = "Minimal Python API for running the Codex CLI."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -1,6 +1,6 @@
1
1
  """Minimal Python API for running agent CLIs."""
2
2
 
3
- from .agent import Agent, WelfareStop, agent
3
+ from .agent import Agent, WelfareStop, agent, build_agent_flags
4
4
  from .async_agent import AsyncAgent
5
5
  from .foreach import ForeachResult, foreach
6
6
  from .pushover import Pushover
@@ -15,6 +15,7 @@ __all__ = [
15
15
  "AsyncAgent",
16
16
  "ForeachResult",
17
17
  "Pushover",
18
+ "build_agent_flags",
18
19
  "quota_line",
19
20
  "rate_limits",
20
21
  "Ralph",
@@ -29,4 +30,4 @@ __all__ = [
29
30
  "task_result",
30
31
  "lead",
31
32
  ]
32
- __version__ = "0.12.9"
33
+ __version__ = "0.12.10"
@@ -11,6 +11,7 @@ from . import welfare
11
11
  _CODEX_BIN = os.environ.get("CODEX_BIN", "codex")
12
12
  _CURSOR_BIN = os.environ.get("CURSOR_BIN", "cursor")
13
13
  _SUPPORTED_BACKENDS = {"codex", "cursor"}
14
+ _CURSOR_AGENT_BIN = os.path.expanduser("~/.local/bin/cursor-agent")
14
15
 
15
16
 
16
17
  def _resolve_backend(backend):
@@ -33,12 +34,15 @@ def _ensure_backend_available(backend, env=None):
33
34
  env_var = "CODEX_BIN"
34
35
  label = "Codex CLI"
35
36
  else:
36
- command = _CURSOR_BIN
37
+ command = _cursor_bin(env)
37
38
  env_var = "CURSOR_BIN"
38
39
  label = "Cursor agent CLI"
39
40
  merged = _merged_env(env)
40
41
  path_value = None if merged is None else merged.get("PATH")
41
- resolved = shutil.which(command, path=path_value)
42
+ if os.path.isabs(command):
43
+ resolved = command if os.path.exists(command) else None
44
+ else:
45
+ resolved = shutil.which(command, path=path_value)
42
46
  if resolved:
43
47
  return resolved
44
48
  raise RuntimeError(
@@ -55,6 +59,8 @@ def agent(
55
59
  backend=None,
56
60
  env=None,
57
61
  fast=False,
62
+ model=None,
63
+ thinking=None,
58
64
  ):
59
65
  """Run a single agent turn and return only the agent's message.
60
66
 
@@ -67,12 +73,24 @@ def agent(
67
73
  backend: Agent backend to use ("codex" or "cursor").
68
74
  env: Optional environment variables for the backend subprocess.
69
75
  fast: Enable Codex fast mode. Defaults to normal mode.
76
+ model: Optional backend model override.
77
+ thinking: Optional backend reasoning/thinking effort override.
70
78
 
71
79
  Returns:
72
80
  The agent's visible response text with reasoning traces removed.
73
81
  """
74
82
  message, _thread_id, _usage = _run_agent(
75
- prompt, cwd, None, yolo, flags, include_thinking, backend, env, fast
83
+ prompt,
84
+ cwd,
85
+ None,
86
+ yolo,
87
+ flags,
88
+ include_thinking,
89
+ backend,
90
+ env,
91
+ fast,
92
+ model,
93
+ thinking,
76
94
  )
77
95
  return message
78
96
 
@@ -106,6 +124,8 @@ class Agent:
106
124
  backend=None,
107
125
  env=None,
108
126
  fast=False,
127
+ model=None,
128
+ thinking=None,
109
129
  ):
110
130
  """Create a new session wrapper.
111
131
 
@@ -120,6 +140,8 @@ class Agent:
120
140
  backend: Agent backend to use ("codex" or "cursor").
121
141
  env: Optional environment variables for the backend subprocess.
122
142
  fast: Enable Codex fast mode. Defaults to normal mode.
143
+ model: Optional backend model override.
144
+ thinking: Optional backend reasoning/thinking effort override.
123
145
  """
124
146
  self.cwd = cwd
125
147
  self._yolo = yolo
@@ -130,6 +152,8 @@ class Agent:
130
152
  self._backend = backend
131
153
  self._env = env
132
154
  self._fast = fast
155
+ self._model = model
156
+ self._thinking = thinking
133
157
  self.last_usage = {}
134
158
 
135
159
  def __call__(self, prompt):
@@ -146,6 +170,8 @@ class Agent:
146
170
  self._backend,
147
171
  self._env,
148
172
  self._fast,
173
+ self._model,
174
+ self._thinking,
149
175
  )
150
176
  if thread_id:
151
177
  self.thread_id = thread_id
@@ -165,15 +191,49 @@ def _run_agent(
165
191
  backend,
166
192
  env,
167
193
  fast=False,
194
+ model=None,
195
+ thinking=None,
168
196
  ):
169
197
  backend = _resolve_backend(backend)
170
198
  _ensure_backend_available(backend, env)
171
199
  if backend == "codex":
172
- return _run_codex(prompt, cwd, thread_id, yolo, flags, include_thinking, env, fast)
173
- return _run_cursor(prompt, cwd, thread_id, yolo, flags, include_thinking, env)
200
+ return _run_codex(
201
+ prompt,
202
+ cwd,
203
+ thread_id,
204
+ yolo,
205
+ flags,
206
+ include_thinking,
207
+ env,
208
+ fast,
209
+ model,
210
+ thinking,
211
+ )
212
+ return _run_cursor(
213
+ prompt,
214
+ cwd,
215
+ thread_id,
216
+ yolo,
217
+ flags,
218
+ include_thinking,
219
+ env,
220
+ model,
221
+ thinking,
222
+ )
174
223
 
175
224
 
176
- def _run_codex(prompt, cwd, thread_id, yolo, flags, include_thinking, env, fast=False):
225
+ def _run_codex(
226
+ prompt,
227
+ cwd,
228
+ thread_id,
229
+ yolo,
230
+ flags,
231
+ include_thinking,
232
+ env,
233
+ fast=False,
234
+ model=None,
235
+ thinking=None,
236
+ ):
177
237
  """Invoke the Codex CLI and return the message plus thread id (if any)."""
178
238
  command = [
179
239
  _CODEX_BIN,
@@ -188,6 +248,7 @@ def _run_codex(prompt, cwd, thread_id, yolo, flags, include_thinking, env, fast=
188
248
  else:
189
249
  command.append("--full-auto")
190
250
  command.extend(_codex_fast_config(fast))
251
+ command.extend(_agent_config_flag_parts("codex", model, thinking))
191
252
  if flags:
192
253
  command.extend(shlex.split(flags))
193
254
  if cwd:
@@ -227,11 +288,82 @@ def _codex_fast_config(fast):
227
288
  return ["-c", "features.fast_mode=false"]
228
289
 
229
290
 
230
- def _run_cursor(prompt, cwd, thread_id, yolo, flags, include_thinking, env):
291
+ def _cursor_bin(env=None):
292
+ merged = _merged_env(env)
293
+ env_source = merged or os.environ
294
+ override = env_source.get("CURSOR_BIN", "").strip()
295
+ if override:
296
+ return os.path.expanduser(override)
297
+
298
+ path_value = None if merged is None else merged.get("PATH")
299
+ direct = shutil.which("cursor-agent", path=path_value)
300
+ if direct:
301
+ return direct
302
+ if os.path.exists(_CURSOR_AGENT_BIN):
303
+ return _CURSOR_AGENT_BIN
304
+ return _CURSOR_BIN
305
+
306
+
307
+ def _cursor_command_prefix(env=None):
308
+ command = _cursor_bin(env)
309
+ if os.path.basename(command) == "cursor-agent":
310
+ return [command]
311
+ return [command, "agent"]
312
+
313
+
314
+ def build_agent_flags(*, backend=None, model=None, thinking=None, flags=None):
315
+ """Return raw backend flags for a model/thinking configuration.
316
+
317
+ The returned string is suitable for APIs that accept the existing ``flags``
318
+ parameter.
319
+ """
320
+ backend = _resolve_backend(backend)
321
+ parts = _agent_config_flag_parts(backend, model, thinking)
322
+ if flags:
323
+ parts.extend(shlex.split(flags))
324
+ return shlex.join(parts)
325
+
326
+
327
+ def _agent_config_flag_parts(backend, model=None, thinking=None):
328
+ backend = _resolve_backend(backend)
329
+ parts = []
330
+ model = _clean_optional_text(model)
331
+ thinking = _clean_optional_text(thinking)
332
+
333
+ if backend == "codex":
334
+ if model:
335
+ parts.extend(["-c", f"model={model}"])
336
+ if thinking:
337
+ parts.extend(["-c", f"model_reasoning_effort={thinking}"])
338
+ return parts
339
+
340
+ if model:
341
+ parts.extend(["--model", model])
342
+ if thinking:
343
+ raise ValueError("thinking is only supported by the codex backend")
344
+ return parts
345
+
346
+
347
+ def _clean_optional_text(value):
348
+ if value is None:
349
+ return None
350
+ text = str(value).strip()
351
+ return text or None
352
+
353
+
354
+ def _run_cursor(
355
+ prompt,
356
+ cwd,
357
+ thread_id,
358
+ yolo,
359
+ flags,
360
+ include_thinking,
361
+ env,
362
+ model=None,
363
+ thinking=None,
364
+ ):
231
365
  """Invoke the Cursor agent CLI and return the message plus session id (if any)."""
232
- command = [
233
- _CURSOR_BIN,
234
- "agent",
366
+ command = _cursor_command_prefix(env) + [
235
367
  "--trust",
236
368
  ]
237
369
  if cwd:
@@ -240,6 +372,7 @@ def _run_cursor(prompt, cwd, thread_id, yolo, flags, include_thinking, env):
240
372
  command.extend(["--resume", thread_id])
241
373
  if yolo:
242
374
  command.append("--yolo")
375
+ command.extend(_agent_config_flag_parts("cursor", model, thinking))
243
376
  if flags:
244
377
  command.extend(shlex.split(flags))
245
378
  command.extend(["--print", "--output-format", "json"])
@@ -12,8 +12,9 @@ import uuid
12
12
 
13
13
  from .agent import (
14
14
  _CODEX_BIN,
15
- _CURSOR_BIN,
15
+ _agent_config_flag_parts,
16
16
  _codex_fast_config,
17
+ _cursor_command_prefix,
17
18
  _ensure_backend_available,
18
19
  _event_usage,
19
20
  _merged_env,
@@ -88,6 +89,8 @@ class AsyncAgent:
88
89
  env=None,
89
90
  name=None,
90
91
  fast=False,
92
+ model=None,
93
+ thinking=None,
91
94
  ):
92
95
  """Start a backend subprocess and return an async handle immediately."""
93
96
  if not isinstance(prompt, str) or not prompt.strip():
@@ -95,7 +98,7 @@ class AsyncAgent:
95
98
 
96
99
  backend = _resolve_backend(backend)
97
100
  _ensure_backend_available(backend, env)
98
- command = _build_command(backend, cwd, yolo, flags, fast)
101
+ command = _build_command(backend, cwd, yolo, flags, fast, model, thinking)
99
102
  process = subprocess.Popen(
100
103
  command,
101
104
  stdin=subprocess.PIPE,
@@ -376,13 +379,13 @@ class AsyncAgent:
376
379
  return self._rollout_final_output
377
380
 
378
381
 
379
- def _build_command(backend, cwd, yolo, flags, fast=False):
382
+ def _build_command(backend, cwd, yolo, flags, fast=False, model=None, thinking=None):
380
383
  if backend == "codex":
381
- return _build_codex_command(cwd, yolo, flags, fast)
382
- return _build_cursor_command(cwd, yolo, flags)
384
+ return _build_codex_command(cwd, yolo, flags, fast, model, thinking)
385
+ return _build_cursor_command(cwd, yolo, flags, model, thinking)
383
386
 
384
387
 
385
- def _build_codex_command(cwd, yolo, flags, fast=False):
388
+ def _build_codex_command(cwd, yolo, flags, fast=False, model=None, thinking=None):
386
389
  command = [
387
390
  _CODEX_BIN,
388
391
  "exec",
@@ -396,6 +399,7 @@ def _build_codex_command(cwd, yolo, flags, fast=False):
396
399
  else:
397
400
  command.append("--full-auto")
398
401
  command.extend(_codex_fast_config(fast))
402
+ command.extend(_agent_config_flag_parts("codex", model, thinking))
399
403
  if flags:
400
404
  command.extend(shlex.split(flags))
401
405
  if cwd:
@@ -404,16 +408,15 @@ def _build_codex_command(cwd, yolo, flags, fast=False):
404
408
  return command
405
409
 
406
410
 
407
- def _build_cursor_command(cwd, yolo, flags):
408
- command = [
409
- _CURSOR_BIN,
410
- "agent",
411
+ def _build_cursor_command(cwd, yolo, flags, model=None, thinking=None):
412
+ command = _cursor_command_prefix() + [
411
413
  "--trust",
412
414
  ]
413
415
  if cwd:
414
416
  command.extend(["--workspace", os.fspath(cwd)])
415
417
  if yolo:
416
418
  command.append("--yolo")
419
+ command.extend(_agent_config_flag_parts("cursor", model, thinking))
417
420
  if flags:
418
421
  command.extend(shlex.split(flags))
419
422
  command.extend(["--print", "--output-format", "json"])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: codexapi
3
- Version: 0.12.9
3
+ Version: 0.12.10
4
4
  Summary: Minimal Python API for running the Codex CLI.
5
5
  License: MIT
6
6
  Keywords: codex,agent,cli,openai
@@ -80,6 +80,8 @@ Use `backend="cursor"` (or set `CODEXAPI_BACKEND=cursor`) to switch to the
80
80
  Cursor agent backend.
81
81
  Use `fast=True` in Codex API calls, or `--fast` in the CLI, to opt into Codex
82
82
  fast mode. Normal mode is the default.
83
+ Use `model="..."` and `thinking="..."` in Codex API calls to override the
84
+ backend model and reasoning effort for a run.
83
85
 
84
86
  ## CLI
85
87
 
@@ -336,7 +338,7 @@ codexapi foreach list.txt task.yaml --retry-all
336
338
 
337
339
  ## API
338
340
 
339
- ### `agent(prompt, cwd=None, yolo=True, flags=None, include_thinking=False, backend=None, fast=False) -> str`
341
+ ### `agent(prompt, cwd=None, yolo=True, flags=None, include_thinking=False, backend=None, fast=False, model=None, thinking=None) -> str`
340
342
 
341
343
  Runs a single agent turn and returns only the agent's message. Any reasoning
342
344
  items are filtered out.
@@ -348,8 +350,10 @@ items are filtered out.
348
350
  - `include_thinking` (bool): when true, return all agent messages joined.
349
351
  - `backend` (str | None): `codex` or `cursor` (defaults to `CODEXAPI_BACKEND` or `codex`).
350
352
  - `fast` (bool): enable Codex fast mode (defaults to normal mode).
353
+ - `model` (str | None): backend model override. Codex maps this to `-c model=...`; Cursor maps it to `--model ...`.
354
+ - `thinking` (str | None): Codex reasoning effort override, mapped to `-c model_reasoning_effort=...`.
351
355
 
352
- ### `Agent(cwd=None, yolo=True, thread_id=None, flags=None, welfare=False, include_thinking=False, backend=None, fast=False)`
356
+ ### `Agent(cwd=None, yolo=True, thread_id=None, flags=None, welfare=False, include_thinking=False, backend=None, fast=False, model=None, thinking=None)`
353
357
 
354
358
  Creates a stateful session wrapper. Calling the instance sends the prompt into
355
359
  the same conversation and returns only the agent's message.
@@ -363,6 +367,8 @@ the same conversation and returns only the agent's message.
363
367
  - `include_thinking` (bool): when true, return all agent messages joined.
364
368
  - `backend` (str | None): `codex` or `cursor` (defaults to `CODEXAPI_BACKEND` or `codex`).
365
369
  - `fast` (bool): enable Codex fast mode (defaults to normal mode).
370
+ - `model` (str | None): backend model override.
371
+ - `thinking` (str | None): Codex reasoning effort override.
366
372
  For Cursor, `thread_id` corresponds to the `session_id` returned by the agent.
367
373
 
368
374
  ### `lead(minutes, prompt, cwd=None, yolo=True, flags=None, leadbook=None, backend=None, fast=False) -> dict`
@@ -2,11 +2,12 @@ import json
2
2
  import sys
3
3
  import unittest
4
4
  from pathlib import Path
5
+ from unittest.mock import patch
5
6
 
6
7
  sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
7
8
 
8
- from codexapi.agent import _codex_fast_config, _parse_jsonl
9
- from codexapi.async_agent import _build_codex_command
9
+ from codexapi.agent import _codex_fast_config, _parse_jsonl, build_agent_flags
10
+ from codexapi.async_agent import _build_codex_command, _build_cursor_command
10
11
 
11
12
 
12
13
  class AgentBackendTests(unittest.TestCase):
@@ -34,6 +35,35 @@ class AgentBackendTests(unittest.TestCase):
34
35
  self.assertIn("service_tier=fast", command)
35
36
  self.assertIn("features.fast_mode=true", command)
36
37
 
38
+ def test_build_agent_flags_maps_codex_model_and_thinking_to_config(self):
39
+ self.assertEqual(
40
+ build_agent_flags(backend="codex", model="gpt-5.5", thinking="xhigh"),
41
+ "-c model=gpt-5.5 -c model_reasoning_effort=xhigh",
42
+ )
43
+
44
+ def test_build_agent_flags_maps_cursor_model_to_model_flag(self):
45
+ self.assertEqual(
46
+ build_agent_flags(backend="cursor", model="claude-4"),
47
+ "--model claude-4",
48
+ )
49
+
50
+ def test_build_agent_flags_rejects_cursor_thinking(self):
51
+ with self.assertRaises(ValueError):
52
+ build_agent_flags(backend="cursor", thinking="high")
53
+
54
+ def test_async_codex_command_can_set_model_and_thinking(self):
55
+ command = _build_codex_command(None, True, None, model="gpt-5.5", thinking="high")
56
+ self.assertIn("model=gpt-5.5", command)
57
+ self.assertIn("model_reasoning_effort=high", command)
58
+
59
+ def test_async_cursor_command_can_use_direct_cursor_agent(self):
60
+ with patch("codexapi.async_agent._cursor_command_prefix", return_value=["/tmp/cursor-agent"]):
61
+ command = _build_cursor_command("/tmp/work", True, None, model="composer-2")
62
+ self.assertEqual(command[0], "/tmp/cursor-agent")
63
+ self.assertNotEqual(command[1], "agent")
64
+ self.assertIn("--model", command)
65
+ self.assertIn("composer-2", command)
66
+
37
67
  def test_parse_jsonl_extracts_last_token_usage(self):
38
68
  output = "\n".join(
39
69
  [
File without changes
File without changes