codexapi 0.5.4__tar.gz → 0.5.5__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.
- {codexapi-0.5.4/src/codexapi.egg-info → codexapi-0.5.5}/PKG-INFO +5 -1
- {codexapi-0.5.4 → codexapi-0.5.5}/README.md +4 -0
- {codexapi-0.5.4 → codexapi-0.5.5}/pyproject.toml +1 -1
- {codexapi-0.5.4 → codexapi-0.5.5}/src/codexapi/__init__.py +1 -1
- {codexapi-0.5.4 → codexapi-0.5.5}/src/codexapi/cli.py +64 -2
- {codexapi-0.5.4 → codexapi-0.5.5}/src/codexapi/task.py +25 -12
- {codexapi-0.5.4 → codexapi-0.5.5}/src/codexapi/taskfile.py +11 -0
- {codexapi-0.5.4 → codexapi-0.5.5/src/codexapi.egg-info}/PKG-INFO +5 -1
- {codexapi-0.5.4 → codexapi-0.5.5}/LICENSE +0 -0
- {codexapi-0.5.4 → codexapi-0.5.5}/setup.cfg +0 -0
- {codexapi-0.5.4 → codexapi-0.5.5}/src/codexapi/__main__.py +0 -0
- {codexapi-0.5.4 → codexapi-0.5.5}/src/codexapi/agent.py +0 -0
- {codexapi-0.5.4 → codexapi-0.5.5}/src/codexapi/foreach.py +0 -0
- {codexapi-0.5.4 → codexapi-0.5.5}/src/codexapi/ralph.py +0 -0
- {codexapi-0.5.4 → codexapi-0.5.5}/src/codexapi.egg-info/SOURCES.txt +0 -0
- {codexapi-0.5.4 → codexapi-0.5.5}/src/codexapi.egg-info/dependency_links.txt +0 -0
- {codexapi-0.5.4 → codexapi-0.5.5}/src/codexapi.egg-info/entry_points.txt +0 -0
- {codexapi-0.5.4 → codexapi-0.5.5}/src/codexapi.egg-info/requires.txt +0 -0
- {codexapi-0.5.4 → codexapi-0.5.5}/src/codexapi.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: codexapi
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.5
|
|
4
4
|
Summary: Minimal Python API for running the Codex CLI.
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: codex,agent,cli,openai
|
|
@@ -73,8 +73,10 @@ echo "Say hello." | codexapi run
|
|
|
73
73
|
```bash
|
|
74
74
|
codexapi task "Fix the failing tests." --max-iterations 5
|
|
75
75
|
codexapi task -f task.yaml
|
|
76
|
+
codexapi task -f task.yaml -i README.md
|
|
76
77
|
```
|
|
77
78
|
Progress is shown by default for `codexapi task`; use `--quiet` to suppress it.
|
|
79
|
+
When using `--item`, the task file must include at least one `{{item}}` placeholder.
|
|
78
80
|
|
|
79
81
|
Task files default to using the standard check prompt for the task. Set `check: "None"` to skip verification.
|
|
80
82
|
Use `max_iterations` in the task file to override the default attempt cap (0 means unlimited).
|
|
@@ -120,6 +122,8 @@ Run a task file across a list file:
|
|
|
120
122
|
```bash
|
|
121
123
|
codexapi foreach list.txt task.yaml
|
|
122
124
|
codexapi foreach list.txt task.yaml -n 4
|
|
125
|
+
codexapi foreach list.txt task.yaml --retry-failed
|
|
126
|
+
codexapi foreach list.txt task.yaml --retry-all
|
|
123
127
|
```
|
|
124
128
|
|
|
125
129
|
## API
|
|
@@ -59,8 +59,10 @@ echo "Say hello." | codexapi run
|
|
|
59
59
|
```bash
|
|
60
60
|
codexapi task "Fix the failing tests." --max-iterations 5
|
|
61
61
|
codexapi task -f task.yaml
|
|
62
|
+
codexapi task -f task.yaml -i README.md
|
|
62
63
|
```
|
|
63
64
|
Progress is shown by default for `codexapi task`; use `--quiet` to suppress it.
|
|
65
|
+
When using `--item`, the task file must include at least one `{{item}}` placeholder.
|
|
64
66
|
|
|
65
67
|
Task files default to using the standard check prompt for the task. Set `check: "None"` to skip verification.
|
|
66
68
|
Use `max_iterations` in the task file to override the default attempt cap (0 means unlimited).
|
|
@@ -106,6 +108,8 @@ Run a task file across a list file:
|
|
|
106
108
|
```bash
|
|
107
109
|
codexapi foreach list.txt task.yaml
|
|
108
110
|
codexapi foreach list.txt task.yaml -n 4
|
|
111
|
+
codexapi foreach list.txt task.yaml --retry-failed
|
|
112
|
+
codexapi foreach list.txt task.yaml --retry-all
|
|
109
113
|
```
|
|
110
114
|
|
|
111
115
|
## API
|
|
@@ -15,7 +15,7 @@ from .agent import Agent, agent
|
|
|
15
15
|
from .foreach import foreach
|
|
16
16
|
from .ralph import cancel_ralph_loop, run_ralph_loop
|
|
17
17
|
from .task import DEFAULT_MAX_ITERATIONS, TaskFailed, task
|
|
18
|
-
from .taskfile import TaskFile
|
|
18
|
+
from .taskfile import TaskFile, load_task_file, task_def_uses_item
|
|
19
19
|
|
|
20
20
|
_SESSION_ID_RE = re.compile(
|
|
21
21
|
r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
|
|
@@ -62,6 +62,7 @@ _COLUMN_TITLES = {
|
|
|
62
62
|
"perm": "PERM",
|
|
63
63
|
"cwd": "CWD",
|
|
64
64
|
}
|
|
65
|
+
_FOREACH_STATUS_MARKERS = {"⏳", "✅", "❌"}
|
|
65
66
|
|
|
66
67
|
|
|
67
68
|
def _read_prompt(prompt):
|
|
@@ -871,6 +872,37 @@ def _print_top_once(show):
|
|
|
871
872
|
print(_format_session(session, layout))
|
|
872
873
|
|
|
873
874
|
|
|
875
|
+
def _clean_foreach_list(path, retry_failed, retry_all):
|
|
876
|
+
with open(path, "r", encoding="utf-8") as handle:
|
|
877
|
+
data = handle.read()
|
|
878
|
+
ends_with_newline = data.endswith("\n")
|
|
879
|
+
lines = data.splitlines()
|
|
880
|
+
|
|
881
|
+
cleaned = []
|
|
882
|
+
changed = False
|
|
883
|
+
for line in lines:
|
|
884
|
+
new_line = line
|
|
885
|
+
if retry_all or (retry_failed and new_line.startswith("❌")):
|
|
886
|
+
if new_line and new_line[0] in _FOREACH_STATUS_MARKERS:
|
|
887
|
+
new_line = new_line[1:]
|
|
888
|
+
if new_line.startswith(" "):
|
|
889
|
+
new_line = new_line[1:]
|
|
890
|
+
pipe = new_line.find("|")
|
|
891
|
+
if pipe != -1:
|
|
892
|
+
new_line = new_line[:pipe].rstrip()
|
|
893
|
+
if new_line != line:
|
|
894
|
+
changed = True
|
|
895
|
+
cleaned.append(new_line)
|
|
896
|
+
|
|
897
|
+
if not changed:
|
|
898
|
+
return
|
|
899
|
+
text = "\n".join(cleaned)
|
|
900
|
+
if ends_with_newline:
|
|
901
|
+
text += "\n"
|
|
902
|
+
with open(path, "w", encoding="utf-8") as handle:
|
|
903
|
+
handle.write(text)
|
|
904
|
+
|
|
905
|
+
|
|
874
906
|
def _run_top(argv):
|
|
875
907
|
if argv and argv[0] in ("-h", "--help"):
|
|
876
908
|
print("usage: codexapi top")
|
|
@@ -995,6 +1027,11 @@ def main(argv=None):
|
|
|
995
1027
|
"--task-file",
|
|
996
1028
|
help="YAML task file to run.",
|
|
997
1029
|
)
|
|
1030
|
+
task_parser.add_argument(
|
|
1031
|
+
"-i",
|
|
1032
|
+
"--item",
|
|
1033
|
+
help="Item value for task files that use {{item}} placeholders.",
|
|
1034
|
+
)
|
|
998
1035
|
task_parser.add_argument(
|
|
999
1036
|
"prompt",
|
|
1000
1037
|
nargs="?",
|
|
@@ -1148,6 +1185,17 @@ def main(argv=None):
|
|
|
1148
1185
|
"task_file",
|
|
1149
1186
|
help="Path to the YAML task file.",
|
|
1150
1187
|
)
|
|
1188
|
+
foreach_retry_group = foreach_parser.add_mutually_exclusive_group()
|
|
1189
|
+
foreach_retry_group.add_argument(
|
|
1190
|
+
"--retry-failed",
|
|
1191
|
+
action="store_true",
|
|
1192
|
+
help="Reset failed (❌) items for re-run.",
|
|
1193
|
+
)
|
|
1194
|
+
foreach_retry_group.add_argument(
|
|
1195
|
+
"--retry-all",
|
|
1196
|
+
action="store_true",
|
|
1197
|
+
help="Reset all items for re-run.",
|
|
1198
|
+
)
|
|
1151
1199
|
foreach_parser.add_argument(
|
|
1152
1200
|
"-n",
|
|
1153
1201
|
type=int,
|
|
@@ -1181,6 +1229,12 @@ def main(argv=None):
|
|
|
1181
1229
|
if args.command == "foreach":
|
|
1182
1230
|
if args.n is not None and args.n < 1:
|
|
1183
1231
|
raise SystemExit("-n must be >= 1.")
|
|
1232
|
+
if args.retry_failed or args.retry_all:
|
|
1233
|
+
_clean_foreach_list(
|
|
1234
|
+
args.list_file,
|
|
1235
|
+
args.retry_failed,
|
|
1236
|
+
args.retry_all,
|
|
1237
|
+
)
|
|
1184
1238
|
result = foreach(
|
|
1185
1239
|
args.list_file,
|
|
1186
1240
|
args.task_file,
|
|
@@ -1225,13 +1279,19 @@ def main(argv=None):
|
|
|
1225
1279
|
if args.command == "task" and args.task_file:
|
|
1226
1280
|
if args.prompt:
|
|
1227
1281
|
raise SystemExit("task -f does not take a prompt.")
|
|
1282
|
+
if args.item is not None:
|
|
1283
|
+
task_def = load_task_file(args.task_file)
|
|
1284
|
+
if not task_def_uses_item(task_def):
|
|
1285
|
+
raise SystemExit(
|
|
1286
|
+
"task -f --item requires {{item}} in the task file."
|
|
1287
|
+
)
|
|
1228
1288
|
if args.check is not None:
|
|
1229
1289
|
raise SystemExit("--check is not allowed with -f.")
|
|
1230
1290
|
if args.max_iterations is not None:
|
|
1231
1291
|
raise SystemExit("--max-iterations is not allowed with -f.")
|
|
1232
1292
|
task_runner = TaskFile(
|
|
1233
1293
|
args.task_file,
|
|
1234
|
-
|
|
1294
|
+
args.item,
|
|
1235
1295
|
cwd=args.cwd,
|
|
1236
1296
|
yolo=args.yolo,
|
|
1237
1297
|
thread_id=None,
|
|
@@ -1279,6 +1339,8 @@ def main(argv=None):
|
|
|
1279
1339
|
)
|
|
1280
1340
|
return
|
|
1281
1341
|
if args.command == "task":
|
|
1342
|
+
if args.item is not None:
|
|
1343
|
+
raise SystemExit("--item is only supported with -f.")
|
|
1282
1344
|
if args.max_iterations is None:
|
|
1283
1345
|
args.max_iterations = DEFAULT_MAX_ITERATIONS
|
|
1284
1346
|
if args.max_iterations < 0:
|
|
@@ -134,7 +134,17 @@ def _format_duration(seconds):
|
|
|
134
134
|
return " ".join(parts)
|
|
135
135
|
|
|
136
136
|
|
|
137
|
-
def
|
|
137
|
+
def _progress_round_label(attempt, total):
|
|
138
|
+
if not total:
|
|
139
|
+
return f"Round {attempt}/unlimited"
|
|
140
|
+
return f"Round {attempt}/{total}"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _print_progress_start(attempt, total):
|
|
144
|
+
print(_progress_round_label(attempt, total), flush=True)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _print_progress_result(
|
|
138
148
|
attempt,
|
|
139
149
|
total,
|
|
140
150
|
start_time,
|
|
@@ -143,13 +153,13 @@ def _print_progress(
|
|
|
143
153
|
cwd,
|
|
144
154
|
yolo,
|
|
145
155
|
flags,
|
|
156
|
+
success,
|
|
146
157
|
):
|
|
147
158
|
elapsed = time.monotonic() - start_time
|
|
148
159
|
remaining = 0
|
|
149
160
|
remaining_text = "unknown"
|
|
150
|
-
if total:
|
|
151
|
-
|
|
152
|
-
remaining = (elapsed / attempt) * (total - attempt)
|
|
161
|
+
if total and attempt:
|
|
162
|
+
remaining = (elapsed / attempt) * (total - attempt)
|
|
153
163
|
remaining_text = _format_duration(remaining)
|
|
154
164
|
|
|
155
165
|
summary_prompt = _build_progress_prompt(agent_output, check_output)
|
|
@@ -157,16 +167,13 @@ def _print_progress(
|
|
|
157
167
|
agent_summary, check_summary = _progress_result(summary)
|
|
158
168
|
|
|
159
169
|
elapsed_text = _format_duration(elapsed)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
else
|
|
163
|
-
round_text = f"Round {attempt}/{total}"
|
|
170
|
+
print(f"Agent: {agent_summary}", flush=True)
|
|
171
|
+
print(f"Check: {check_summary}", flush=True)
|
|
172
|
+
verdict = "success" if success else "failure"
|
|
164
173
|
print(
|
|
165
|
-
f"{
|
|
174
|
+
f"Verdict: {verdict} ({elapsed_text} elapsed, {remaining_text} remaining)",
|
|
166
175
|
flush=True,
|
|
167
176
|
)
|
|
168
|
-
print(f"Agent: {agent_summary}", flush=True)
|
|
169
|
-
print(f"Check: {check_summary}", flush=True)
|
|
170
177
|
print("", flush=True)
|
|
171
178
|
|
|
172
179
|
def _fix_prompt(error):
|
|
@@ -443,6 +450,11 @@ class Task:
|
|
|
443
450
|
attempt = 0
|
|
444
451
|
while True:
|
|
445
452
|
attempt += 1
|
|
453
|
+
if progress:
|
|
454
|
+
_print_progress_start(
|
|
455
|
+
attempt,
|
|
456
|
+
self.max_attempts,
|
|
457
|
+
)
|
|
446
458
|
error = self.check(self.last_output)
|
|
447
459
|
if debug:
|
|
448
460
|
_logger.debug("Check error: %s", error)
|
|
@@ -451,7 +463,7 @@ class Task:
|
|
|
451
463
|
check_output = self.last_check_output
|
|
452
464
|
if self.check_skipped:
|
|
453
465
|
check_output = "Verification skipped."
|
|
454
|
-
|
|
466
|
+
_print_progress_result(
|
|
455
467
|
attempt,
|
|
456
468
|
self.max_attempts,
|
|
457
469
|
start_time,
|
|
@@ -460,6 +472,7 @@ class Task:
|
|
|
460
472
|
self.cwd,
|
|
461
473
|
self._yolo,
|
|
462
474
|
self._flags,
|
|
475
|
+
not error,
|
|
463
476
|
)
|
|
464
477
|
if not error:
|
|
465
478
|
summary = self.agent(self.success_prompt())
|
|
@@ -54,6 +54,17 @@ def _render(text, item):
|
|
|
54
54
|
return text.replace(_ITEM_TOKEN, item)
|
|
55
55
|
|
|
56
56
|
|
|
57
|
+
def task_def_uses_item(task_def):
|
|
58
|
+
"""Return True if a task definition includes the {{item}} placeholder."""
|
|
59
|
+
if not isinstance(task_def, dict):
|
|
60
|
+
raise TypeError("task definition must be a dict")
|
|
61
|
+
for key in ("prompt", "set_up", "tear_down", "check", "on_success", "on_failure"):
|
|
62
|
+
value = task_def.get(key)
|
|
63
|
+
if isinstance(value, str) and _ITEM_TOKEN in value:
|
|
64
|
+
return True
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
|
|
57
68
|
class TaskFile(AutoTask):
|
|
58
69
|
"""Task subclass that maps a YAML task file onto Task hooks."""
|
|
59
70
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: codexapi
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.5
|
|
4
4
|
Summary: Minimal Python API for running the Codex CLI.
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: codex,agent,cli,openai
|
|
@@ -73,8 +73,10 @@ echo "Say hello." | codexapi run
|
|
|
73
73
|
```bash
|
|
74
74
|
codexapi task "Fix the failing tests." --max-iterations 5
|
|
75
75
|
codexapi task -f task.yaml
|
|
76
|
+
codexapi task -f task.yaml -i README.md
|
|
76
77
|
```
|
|
77
78
|
Progress is shown by default for `codexapi task`; use `--quiet` to suppress it.
|
|
79
|
+
When using `--item`, the task file must include at least one `{{item}}` placeholder.
|
|
78
80
|
|
|
79
81
|
Task files default to using the standard check prompt for the task. Set `check: "None"` to skip verification.
|
|
80
82
|
Use `max_iterations` in the task file to override the default attempt cap (0 means unlimited).
|
|
@@ -120,6 +122,8 @@ Run a task file across a list file:
|
|
|
120
122
|
```bash
|
|
121
123
|
codexapi foreach list.txt task.yaml
|
|
122
124
|
codexapi foreach list.txt task.yaml -n 4
|
|
125
|
+
codexapi foreach list.txt task.yaml --retry-failed
|
|
126
|
+
codexapi foreach list.txt task.yaml --retry-all
|
|
123
127
|
```
|
|
124
128
|
|
|
125
129
|
## API
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|