codexapi 0.12.7__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.7/src/codexapi.egg-info → codexapi-0.12.10}/PKG-INFO +23 -8
  2. {codexapi-0.12.7 → codexapi-0.12.10}/README.md +22 -7
  3. {codexapi-0.12.7 → codexapi-0.12.10}/pyproject.toml +1 -1
  4. {codexapi-0.12.7 → codexapi-0.12.10}/src/codexapi/__init__.py +3 -2
  5. {codexapi-0.12.7 → codexapi-0.12.10}/src/codexapi/agent.py +173 -11
  6. {codexapi-0.12.7 → codexapi-0.12.10}/src/codexapi/agents.py +23 -9
  7. {codexapi-0.12.7 → codexapi-0.12.10}/src/codexapi/async_agent.py +35 -12
  8. {codexapi-0.12.7 → codexapi-0.12.10}/src/codexapi/cli.py +136 -55
  9. {codexapi-0.12.7 → codexapi-0.12.10}/src/codexapi/foreach.py +4 -0
  10. {codexapi-0.12.7 → codexapi-0.12.10}/src/codexapi/gh_integration.py +4 -1
  11. {codexapi-0.12.7 → codexapi-0.12.10}/src/codexapi/lead.py +6 -1
  12. {codexapi-0.12.7 → codexapi-0.12.10}/src/codexapi/ralph.py +26 -18
  13. {codexapi-0.12.7 → codexapi-0.12.10}/src/codexapi/science.py +23 -2
  14. {codexapi-0.12.7 → codexapi-0.12.10}/src/codexapi/task.py +48 -13
  15. {codexapi-0.12.7 → codexapi-0.12.10}/src/codexapi/taskfile.py +3 -0
  16. {codexapi-0.12.7 → codexapi-0.12.10/src/codexapi.egg-info}/PKG-INFO +23 -8
  17. {codexapi-0.12.7 → codexapi-0.12.10}/tests/test_agent_backend.py +56 -1
  18. {codexapi-0.12.7 → codexapi-0.12.10}/tests/test_agents.py +51 -0
  19. {codexapi-0.12.7 → codexapi-0.12.10}/tests/test_async_agent.py +61 -0
  20. {codexapi-0.12.7 → codexapi-0.12.10}/LICENSE +0 -0
  21. {codexapi-0.12.7 → codexapi-0.12.10}/setup.cfg +0 -0
  22. {codexapi-0.12.7 → codexapi-0.12.10}/src/codexapi/__main__.py +0 -0
  23. {codexapi-0.12.7 → codexapi-0.12.10}/src/codexapi/pushover.py +0 -0
  24. {codexapi-0.12.7 → codexapi-0.12.10}/src/codexapi/rate_limits.py +0 -0
  25. {codexapi-0.12.7 → codexapi-0.12.10}/src/codexapi/welfare.py +0 -0
  26. {codexapi-0.12.7 → codexapi-0.12.10}/src/codexapi.egg-info/SOURCES.txt +0 -0
  27. {codexapi-0.12.7 → codexapi-0.12.10}/src/codexapi.egg-info/dependency_links.txt +0 -0
  28. {codexapi-0.12.7 → codexapi-0.12.10}/src/codexapi.egg-info/entry_points.txt +0 -0
  29. {codexapi-0.12.7 → codexapi-0.12.10}/src/codexapi.egg-info/requires.txt +0 -0
  30. {codexapi-0.12.7 → codexapi-0.12.10}/src/codexapi.egg-info/top_level.txt +0 -0
  31. {codexapi-0.12.7 → codexapi-0.12.10}/tests/test_rate_limits.py +0 -0
  32. {codexapi-0.12.7 → codexapi-0.12.10}/tests/test_science.py +0 -0
  33. {codexapi-0.12.7 → 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.7
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
@@ -78,6 +78,10 @@ for update in agent.watch(poll_interval=2.0):
78
78
 
79
79
  Use `backend="cursor"` (or set `CODEXAPI_BACKEND=cursor`) to switch to the
80
80
  Cursor agent backend.
81
+ Use `fast=True` in Codex API calls, or `--fast` in the CLI, to opt into Codex
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.
81
85
 
82
86
  ## CLI
83
87
 
@@ -88,6 +92,7 @@ codexapi --version
88
92
  codexapi run "Summarize this repo."
89
93
  codexapi run --cwd /path/to/project "Fix the failing tests."
90
94
  echo "Say hello." | codexapi run
95
+ codexapi run --fast "Summarize this repo quickly."
91
96
  codexapi run --backend cursor "Summarize this repo."
92
97
  ```
93
98
 
@@ -333,7 +338,7 @@ codexapi foreach list.txt task.yaml --retry-all
333
338
 
334
339
  ## API
335
340
 
336
- ### `agent(prompt, cwd=None, yolo=True, flags=None, include_thinking=False, backend=None) -> str`
341
+ ### `agent(prompt, cwd=None, yolo=True, flags=None, include_thinking=False, backend=None, fast=False, model=None, thinking=None) -> str`
337
342
 
338
343
  Runs a single agent turn and returns only the agent's message. Any reasoning
339
344
  items are filtered out.
@@ -344,8 +349,11 @@ items are filtered out.
344
349
  - `flags` (str | None): extra CLI flags to pass to the agent backend.
345
350
  - `include_thinking` (bool): when true, return all agent messages joined.
346
351
  - `backend` (str | None): `codex` or `cursor` (defaults to `CODEXAPI_BACKEND` or `codex`).
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=...`.
347
355
 
348
- ### `Agent(cwd=None, yolo=True, thread_id=None, flags=None, welfare=False, include_thinking=False, backend=None)`
356
+ ### `Agent(cwd=None, yolo=True, thread_id=None, flags=None, welfare=False, include_thinking=False, backend=None, fast=False, model=None, thinking=None)`
349
357
 
350
358
  Creates a stateful session wrapper. Calling the instance sends the prompt into
351
359
  the same conversation and returns only the agent's message.
@@ -358,9 +366,12 @@ the same conversation and returns only the agent's message.
358
366
  and raise `WelfareStop` if the agent outputs `MAKE IT STOP`.
359
367
  - `include_thinking` (bool): when true, return all agent messages joined.
360
368
  - `backend` (str | None): `codex` or `cursor` (defaults to `CODEXAPI_BACKEND` or `codex`).
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.
361
372
  For Cursor, `thread_id` corresponds to the `session_id` returned by the agent.
362
373
 
363
- ### `lead(minutes, prompt, cwd=None, yolo=True, flags=None, leadbook=None, backend=None) -> dict`
374
+ ### `lead(minutes, prompt, cwd=None, yolo=True, flags=None, leadbook=None, backend=None, fast=False) -> dict`
364
375
 
365
376
  Runs a long-lived agent session and periodically checks in with the current
366
377
  local time and a reminder of `prompt`. Each check-in expects JSON with keys:
@@ -373,7 +384,7 @@ Lead also injects the leadbook content into each prompt. By default it uses
373
384
  path string to override the location.
374
385
  Set `backend="cursor"` (or `CODEXAPI_BACKEND=cursor`) to use Cursor.
375
386
 
376
- ### `task(prompt, check=None, max_iterations=10, cwd=None, yolo=True, flags=None, progress=False, set_up=None, tear_down=None, on_success=None, on_failure=None, backend=None) -> str`
387
+ ### `task(prompt, check=None, max_iterations=10, cwd=None, yolo=True, flags=None, progress=False, set_up=None, tear_down=None, on_success=None, on_failure=None, backend=None, fast=False) -> str`
377
388
 
378
389
  Runs a task with checker-driven retries and returns the success summary.
379
390
  Raises `TaskFailed` when the maximum iterations are reached.
@@ -383,14 +394,15 @@ Raises `TaskFailed` when the maximum iterations are reached.
383
394
  - `progress` (bool): show a tqdm progress bar with a one-line status after each round.
384
395
  - `set_up`/`tear_down`/`on_success`/`on_failure` (str | None): optional hook prompts.
385
396
  - `backend` (str | None): `codex` or `cursor` (defaults to `CODEXAPI_BACKEND` or `codex`).
397
+ - `fast` (bool): enable Codex fast mode (defaults to normal mode).
386
398
 
387
- ### `task_result(prompt, check=None, max_iterations=10, cwd=None, yolo=True, flags=None, progress=False, set_up=None, tear_down=None, on_success=None, on_failure=None, backend=None) -> TaskResult`
399
+ ### `task_result(prompt, check=None, max_iterations=10, cwd=None, yolo=True, flags=None, progress=False, set_up=None, tear_down=None, on_success=None, on_failure=None, backend=None, fast=False) -> TaskResult`
388
400
 
389
401
  Runs a task with checker-driven retries and returns a `TaskResult` without
390
402
  raising `TaskFailed`.
391
403
  Arguments mirror `task()` (including hooks).
392
404
 
393
- ### `Task(prompt, max_iterations=10, cwd=None, yolo=True, thread_id=None, flags=None, backend=None)`
405
+ ### `Task(prompt, max_iterations=10, cwd=None, yolo=True, thread_id=None, flags=None, backend=None, fast=False)`
394
406
 
395
407
  Runs an agent task with checker-driven retries. Subclass it and implement
396
408
  `check()` to return an error string when the task is incomplete, or return
@@ -423,7 +435,7 @@ Exception raised by `task()` when iterations are exhausted.
423
435
  - `iterations` (int | None): iterations made when the task failed.
424
436
  - `errors` (str | None): last checker error, if any.
425
437
 
426
- ### `foreach(list_file, task_file, n=None, cwd=None, yolo=True, flags=None, backend=None) -> ForeachResult`
438
+ ### `foreach(list_file, task_file, n=None, cwd=None, yolo=True, flags=None, backend=None, fast=False) -> ForeachResult`
427
439
 
428
440
  Runs a task file over a list of items, updating the list file in place.
429
441
 
@@ -434,6 +446,7 @@ Runs a task file over a list of items, updating the list file in place.
434
446
  - `yolo` (bool): pass `--yolo` when true (defaults to true).
435
447
  - `flags` (str | None): extra CLI flags to pass to the agent backend.
436
448
  - `backend` (str | None): `codex` or `cursor` (defaults to `CODEXAPI_BACKEND` or `codex`).
449
+ - `fast` (bool): enable Codex fast mode (defaults to normal mode).
437
450
 
438
451
  ### `ForeachResult(succeeded, failed, skipped, results)`
439
452
 
@@ -448,6 +461,8 @@ Simple result object returned by `foreach()`.
448
461
 
449
462
  - Codex backend uses `codex exec --json` and parses JSONL `agent_message` items.
450
463
  - Codex backend passes `--skip-git-repo-check` so it can run outside a git repo.
464
+ - Codex backend defaults to normal mode and passes `features.fast_mode=false`;
465
+ `fast=True` / `--fast` also passes `service_tier=fast` and `features.fast_mode=true`.
451
466
  - Cursor backend uses `cursor agent --print --output-format json --trust` and parses the JSON result.
452
467
  - `include_thinking=True` only affects Codex; Cursor returns a single result string.
453
468
  - Passes `--yolo` by default (Codex uses `--full-auto` when disabled).
@@ -63,6 +63,10 @@ for update in agent.watch(poll_interval=2.0):
63
63
 
64
64
  Use `backend="cursor"` (or set `CODEXAPI_BACKEND=cursor`) to switch to the
65
65
  Cursor agent backend.
66
+ Use `fast=True` in Codex API calls, or `--fast` in the CLI, to opt into Codex
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.
66
70
 
67
71
  ## CLI
68
72
 
@@ -73,6 +77,7 @@ codexapi --version
73
77
  codexapi run "Summarize this repo."
74
78
  codexapi run --cwd /path/to/project "Fix the failing tests."
75
79
  echo "Say hello." | codexapi run
80
+ codexapi run --fast "Summarize this repo quickly."
76
81
  codexapi run --backend cursor "Summarize this repo."
77
82
  ```
78
83
 
@@ -318,7 +323,7 @@ codexapi foreach list.txt task.yaml --retry-all
318
323
 
319
324
  ## API
320
325
 
321
- ### `agent(prompt, cwd=None, yolo=True, flags=None, include_thinking=False, backend=None) -> str`
326
+ ### `agent(prompt, cwd=None, yolo=True, flags=None, include_thinking=False, backend=None, fast=False, model=None, thinking=None) -> str`
322
327
 
323
328
  Runs a single agent turn and returns only the agent's message. Any reasoning
324
329
  items are filtered out.
@@ -329,8 +334,11 @@ items are filtered out.
329
334
  - `flags` (str | None): extra CLI flags to pass to the agent backend.
330
335
  - `include_thinking` (bool): when true, return all agent messages joined.
331
336
  - `backend` (str | None): `codex` or `cursor` (defaults to `CODEXAPI_BACKEND` or `codex`).
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=...`.
332
340
 
333
- ### `Agent(cwd=None, yolo=True, thread_id=None, flags=None, welfare=False, include_thinking=False, backend=None)`
341
+ ### `Agent(cwd=None, yolo=True, thread_id=None, flags=None, welfare=False, include_thinking=False, backend=None, fast=False, model=None, thinking=None)`
334
342
 
335
343
  Creates a stateful session wrapper. Calling the instance sends the prompt into
336
344
  the same conversation and returns only the agent's message.
@@ -343,9 +351,12 @@ the same conversation and returns only the agent's message.
343
351
  and raise `WelfareStop` if the agent outputs `MAKE IT STOP`.
344
352
  - `include_thinking` (bool): when true, return all agent messages joined.
345
353
  - `backend` (str | None): `codex` or `cursor` (defaults to `CODEXAPI_BACKEND` or `codex`).
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.
346
357
  For Cursor, `thread_id` corresponds to the `session_id` returned by the agent.
347
358
 
348
- ### `lead(minutes, prompt, cwd=None, yolo=True, flags=None, leadbook=None, backend=None) -> dict`
359
+ ### `lead(minutes, prompt, cwd=None, yolo=True, flags=None, leadbook=None, backend=None, fast=False) -> dict`
349
360
 
350
361
  Runs a long-lived agent session and periodically checks in with the current
351
362
  local time and a reminder of `prompt`. Each check-in expects JSON with keys:
@@ -358,7 +369,7 @@ Lead also injects the leadbook content into each prompt. By default it uses
358
369
  path string to override the location.
359
370
  Set `backend="cursor"` (or `CODEXAPI_BACKEND=cursor`) to use Cursor.
360
371
 
361
- ### `task(prompt, check=None, max_iterations=10, cwd=None, yolo=True, flags=None, progress=False, set_up=None, tear_down=None, on_success=None, on_failure=None, backend=None) -> str`
372
+ ### `task(prompt, check=None, max_iterations=10, cwd=None, yolo=True, flags=None, progress=False, set_up=None, tear_down=None, on_success=None, on_failure=None, backend=None, fast=False) -> str`
362
373
 
363
374
  Runs a task with checker-driven retries and returns the success summary.
364
375
  Raises `TaskFailed` when the maximum iterations are reached.
@@ -368,14 +379,15 @@ Raises `TaskFailed` when the maximum iterations are reached.
368
379
  - `progress` (bool): show a tqdm progress bar with a one-line status after each round.
369
380
  - `set_up`/`tear_down`/`on_success`/`on_failure` (str | None): optional hook prompts.
370
381
  - `backend` (str | None): `codex` or `cursor` (defaults to `CODEXAPI_BACKEND` or `codex`).
382
+ - `fast` (bool): enable Codex fast mode (defaults to normal mode).
371
383
 
372
- ### `task_result(prompt, check=None, max_iterations=10, cwd=None, yolo=True, flags=None, progress=False, set_up=None, tear_down=None, on_success=None, on_failure=None, backend=None) -> TaskResult`
384
+ ### `task_result(prompt, check=None, max_iterations=10, cwd=None, yolo=True, flags=None, progress=False, set_up=None, tear_down=None, on_success=None, on_failure=None, backend=None, fast=False) -> TaskResult`
373
385
 
374
386
  Runs a task with checker-driven retries and returns a `TaskResult` without
375
387
  raising `TaskFailed`.
376
388
  Arguments mirror `task()` (including hooks).
377
389
 
378
- ### `Task(prompt, max_iterations=10, cwd=None, yolo=True, thread_id=None, flags=None, backend=None)`
390
+ ### `Task(prompt, max_iterations=10, cwd=None, yolo=True, thread_id=None, flags=None, backend=None, fast=False)`
379
391
 
380
392
  Runs an agent task with checker-driven retries. Subclass it and implement
381
393
  `check()` to return an error string when the task is incomplete, or return
@@ -408,7 +420,7 @@ Exception raised by `task()` when iterations are exhausted.
408
420
  - `iterations` (int | None): iterations made when the task failed.
409
421
  - `errors` (str | None): last checker error, if any.
410
422
 
411
- ### `foreach(list_file, task_file, n=None, cwd=None, yolo=True, flags=None, backend=None) -> ForeachResult`
423
+ ### `foreach(list_file, task_file, n=None, cwd=None, yolo=True, flags=None, backend=None, fast=False) -> ForeachResult`
412
424
 
413
425
  Runs a task file over a list of items, updating the list file in place.
414
426
 
@@ -419,6 +431,7 @@ Runs a task file over a list of items, updating the list file in place.
419
431
  - `yolo` (bool): pass `--yolo` when true (defaults to true).
420
432
  - `flags` (str | None): extra CLI flags to pass to the agent backend.
421
433
  - `backend` (str | None): `codex` or `cursor` (defaults to `CODEXAPI_BACKEND` or `codex`).
434
+ - `fast` (bool): enable Codex fast mode (defaults to normal mode).
422
435
 
423
436
  ### `ForeachResult(succeeded, failed, skipped, results)`
424
437
 
@@ -433,6 +446,8 @@ Simple result object returned by `foreach()`.
433
446
 
434
447
  - Codex backend uses `codex exec --json` and parses JSONL `agent_message` items.
435
448
  - Codex backend passes `--skip-git-repo-check` so it can run outside a git repo.
449
+ - Codex backend defaults to normal mode and passes `features.fast_mode=false`;
450
+ `fast=True` / `--fast` also passes `service_tier=fast` and `features.fast_mode=true`.
436
451
  - Cursor backend uses `cursor agent --print --output-format json --trust` and parses the JSON result.
437
452
  - `include_thinking=True` only affects Codex; Cursor returns a single result string.
438
453
  - Passes `--yolo` by default (Codex uses `--full-auto` when disabled).
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "codexapi"
7
- version = "0.12.7"
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.7"
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(
@@ -54,6 +58,9 @@ def agent(
54
58
  include_thinking=False,
55
59
  backend=None,
56
60
  env=None,
61
+ fast=False,
62
+ model=None,
63
+ thinking=None,
57
64
  ):
58
65
  """Run a single agent turn and return only the agent's message.
59
66
 
@@ -65,12 +72,25 @@ def agent(
65
72
  include_thinking: When true, return all agent messages joined together.
66
73
  backend: Agent backend to use ("codex" or "cursor").
67
74
  env: Optional environment variables for the backend subprocess.
75
+ fast: Enable Codex fast mode. Defaults to normal mode.
76
+ model: Optional backend model override.
77
+ thinking: Optional backend reasoning/thinking effort override.
68
78
 
69
79
  Returns:
70
80
  The agent's visible response text with reasoning traces removed.
71
81
  """
72
82
  message, _thread_id, _usage = _run_agent(
73
- prompt, cwd, None, yolo, flags, include_thinking, backend, env
83
+ prompt,
84
+ cwd,
85
+ None,
86
+ yolo,
87
+ flags,
88
+ include_thinking,
89
+ backend,
90
+ env,
91
+ fast,
92
+ model,
93
+ thinking,
74
94
  )
75
95
  return message
76
96
 
@@ -103,6 +123,9 @@ class Agent:
103
123
  include_thinking=False,
104
124
  backend=None,
105
125
  env=None,
126
+ fast=False,
127
+ model=None,
128
+ thinking=None,
106
129
  ):
107
130
  """Create a new session wrapper.
108
131
 
@@ -116,6 +139,9 @@ class Agent:
116
139
  include_thinking: When true, return all agent messages joined together.
117
140
  backend: Agent backend to use ("codex" or "cursor").
118
141
  env: Optional environment variables for the backend subprocess.
142
+ fast: Enable Codex fast mode. Defaults to normal mode.
143
+ model: Optional backend model override.
144
+ thinking: Optional backend reasoning/thinking effort override.
119
145
  """
120
146
  self.cwd = cwd
121
147
  self._yolo = yolo
@@ -125,6 +151,9 @@ class Agent:
125
151
  self.thread_id = thread_id
126
152
  self._backend = backend
127
153
  self._env = env
154
+ self._fast = fast
155
+ self._model = model
156
+ self._thinking = thinking
128
157
  self.last_usage = {}
129
158
 
130
159
  def __call__(self, prompt):
@@ -140,6 +169,9 @@ class Agent:
140
169
  self._include_thinking,
141
170
  self._backend,
142
171
  self._env,
172
+ self._fast,
173
+ self._model,
174
+ self._thinking,
143
175
  )
144
176
  if thread_id:
145
177
  self.thread_id = thread_id
@@ -149,15 +181,59 @@ class Agent:
149
181
  return message
150
182
 
151
183
 
152
- def _run_agent(prompt, cwd, thread_id, yolo, flags, include_thinking, backend, env):
184
+ def _run_agent(
185
+ prompt,
186
+ cwd,
187
+ thread_id,
188
+ yolo,
189
+ flags,
190
+ include_thinking,
191
+ backend,
192
+ env,
193
+ fast=False,
194
+ model=None,
195
+ thinking=None,
196
+ ):
153
197
  backend = _resolve_backend(backend)
154
198
  _ensure_backend_available(backend, env)
155
199
  if backend == "codex":
156
- return _run_codex(prompt, cwd, thread_id, yolo, flags, include_thinking, env)
157
- 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
+ )
158
223
 
159
224
 
160
- def _run_codex(prompt, cwd, thread_id, yolo, flags, include_thinking, env):
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
+ ):
161
237
  """Invoke the Codex CLI and return the message plus thread id (if any)."""
162
238
  command = [
163
239
  _CODEX_BIN,
@@ -171,6 +247,8 @@ def _run_codex(prompt, cwd, thread_id, yolo, flags, include_thinking, env):
171
247
  command.append("--yolo")
172
248
  else:
173
249
  command.append("--full-auto")
250
+ command.extend(_codex_fast_config(fast))
251
+ command.extend(_agent_config_flag_parts("codex", model, thinking))
174
252
  if flags:
175
253
  command.extend(shlex.split(flags))
176
254
  if cwd:
@@ -198,11 +276,94 @@ def _run_codex(prompt, cwd, thread_id, yolo, flags, include_thinking, env):
198
276
  return _parse_jsonl(result.stdout, include_thinking)
199
277
 
200
278
 
201
- def _run_cursor(prompt, cwd, thread_id, yolo, flags, include_thinking, env):
279
+ def _codex_fast_config(fast):
280
+ """Return Codex config flags for normal or fast mode."""
281
+ if fast:
282
+ return [
283
+ "-c",
284
+ "service_tier=fast",
285
+ "-c",
286
+ "features.fast_mode=true",
287
+ ]
288
+ return ["-c", "features.fast_mode=false"]
289
+
290
+
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
+ ):
202
365
  """Invoke the Cursor agent CLI and return the message plus session id (if any)."""
203
- command = [
204
- _CURSOR_BIN,
205
- "agent",
366
+ command = _cursor_command_prefix(env) + [
206
367
  "--trust",
207
368
  ]
208
369
  if cwd:
@@ -211,6 +372,7 @@ def _run_cursor(prompt, cwd, thread_id, yolo, flags, include_thinking, env):
211
372
  command.extend(["--resume", thread_id])
212
373
  if yolo:
213
374
  command.append("--yolo")
375
+ command.extend(_agent_config_flag_parts("cursor", model, thinking))
214
376
  if flags:
215
377
  command.extend(shlex.split(flags))
216
378
  command.extend(["--print", "--output-format", "json"])
@@ -210,6 +210,7 @@ def start_agent(
210
210
  home=None,
211
211
  hostname=None,
212
212
  now=None,
213
+ fast=False,
213
214
  ):
214
215
  """Create a durable agent and return its current snapshot."""
215
216
  if not isinstance(prompt, str) or not prompt.strip():
@@ -250,6 +251,7 @@ def start_agent(
250
251
  "backend": backend_name,
251
252
  "yolo": bool(yolo),
252
253
  "flags": flags or "",
254
+ "fast": bool(fast),
253
255
  "cwd": cwd,
254
256
  "env": session_env,
255
257
  "pending_messages": [],
@@ -1053,15 +1055,27 @@ def _run_agent_turn(meta, session, prompt, runner=None):
1053
1055
  raise TypeError("runner must return a dict")
1054
1056
  return outcome
1055
1057
  started = utc_now()
1056
- worker = Agent(
1057
- session.get("cwd") or meta.get("cwd"),
1058
- session.get("yolo", True),
1059
- session.get("thread_id") or None,
1060
- session.get("flags") or None,
1061
- include_thinking=False,
1062
- backend=session.get("backend") or None,
1063
- env=_agent_env(meta, session),
1064
- )
1058
+ if session.get("fast", False):
1059
+ worker = Agent(
1060
+ session.get("cwd") or meta.get("cwd"),
1061
+ session.get("yolo", True),
1062
+ session.get("thread_id") or None,
1063
+ session.get("flags") or None,
1064
+ include_thinking=False,
1065
+ backend=session.get("backend") or None,
1066
+ env=_agent_env(meta, session),
1067
+ fast=True,
1068
+ )
1069
+ else:
1070
+ worker = Agent(
1071
+ session.get("cwd") or meta.get("cwd"),
1072
+ session.get("yolo", True),
1073
+ session.get("thread_id") or None,
1074
+ session.get("flags") or None,
1075
+ include_thinking=False,
1076
+ backend=session.get("backend") or None,
1077
+ env=_agent_env(meta, session),
1078
+ )
1065
1079
  message = worker(prompt)
1066
1080
  usage = worker.last_usage or {}
1067
1081
  rollout_path = ""