self-evolve-framework 1.4.0 → 1.6.0
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.
- package/package.json +1 -1
- package/template/rules/ponytail.mdc +98 -23
- package/template/skills/skillopt-sleep/SKILL.md +42 -0
- package/template/skills/skillopt-sleep/configs/_base_/default.yaml +103 -0
- package/template/skills/skillopt-sleep/configs/alfworld/default.yaml +29 -0
- package/template/skills/skillopt-sleep/configs/docvqa/default.yaml +28 -0
- package/template/skills/skillopt-sleep/configs/features/soft_gate.yaml +47 -0
- package/template/skills/skillopt-sleep/configs/livemathematicianbench/default.yaml +22 -0
- package/template/skills/skillopt-sleep/configs/officeqa/default.yaml +34 -0
- package/template/skills/skillopt-sleep/configs/searchqa/default.yaml +32 -0
- package/template/skills/skillopt-sleep/configs/spreadsheetbench/default.yaml +34 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/__init__.py +28 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/config.py +282 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/datasets/__init__.py +7 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/datasets/base.py +512 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/engine/__init__.py +9 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/engine/trainer.py +2379 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/__init__.py +1 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/_template/README.md +43 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/_template/config_template.yaml +55 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/_template/env_template.py +151 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/_template/loader_template.py +87 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/__init__.py +5 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/adapter.py +428 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/dataloader.py +123 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/analyst_error.md +55 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/analyst_success.md +33 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/rollout_no_history.md +8 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/rollout_with_history.md +9 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/rollout_with_memory.md +16 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/reflect.py +4 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/rollout.py +366 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/skills/initial.md +45 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/__init__.py +9 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/alfworld_envs.py +221 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/alfworld_projection.py +60 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/alfworld_prompts.py +8 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/config_tw.yaml +145 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/env_base.py +84 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/env_manager.py +139 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/memory.py +87 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/base.py +329 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/__init__.py +1 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/adapter.py +90 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/dataloader.py +61 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/evaluator.py +113 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/prompts/analyst_error.md +35 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/prompts/analyst_success.md +24 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/prompts/rollout_system.md +12 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/rollout.py +391 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/skills/initial.md +11 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/__init__.py +1 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/adapter.py +129 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/dataloader.py +308 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/evaluator.py +62 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/prompts/analyst_error.md +37 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/prompts/analyst_success.md +25 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/prompts/rollout_system.md +12 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/reflect.py +4 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/rollout.py +434 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/skills/initial.md +16 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/__init__.py +1 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/adapter.py +112 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/dataloader.py +71 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/evaluator.py +46 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/prompts/analyst_error.md +37 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/prompts/analyst_success.md +25 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/prompts/rollout_system.md +15 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/rollout.py +799 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/skills/initial.md +15 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/tool_runtime.py +552 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/__init__.py +1 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/adapter.py +96 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/dataloader.py +42 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/evaluator.py +100 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/prompts/analyst_error.md +46 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/prompts/analyst_success.md +32 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/prompts/rollout_system.md +13 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/reflect.py +4 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/rollout.py +494 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/skills/initial.md +3 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/__init__.py +5 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/adapter.py +159 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/codegen_agent.py +731 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/dataloader.py +37 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/evaluator.py +158 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/executor.py +67 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/analyst_error.md +46 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/analyst_success.md +32 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/codegen_system.md +1 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/critical_rules.md +9 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/react_system.md +21 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/react_agent.py +395 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/reflect.py +4 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/rollout.py +979 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/skills/initial.md +56 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/evaluation/__init__.py +13 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/evaluation/gate.py +148 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/gradient/__init__.py +15 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/gradient/aggregate.py +253 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/gradient/reflect.py +635 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/__init__.py +514 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/azure_openai.py +915 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/backend_config.py +185 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/claude_backend.py +371 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/codex_backend.py +666 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/codex_harness.py +1057 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/common.py +229 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/minimax_backend.py +277 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/qwen_backend.py +456 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/router.py +236 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/__init__.py +15 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/appendix.py +156 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/clip.py +109 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/lr_autonomous.py +108 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/meta_skill.py +79 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/rewrite.py +59 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/scheduler.py +127 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/select.py +4 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/skill.py +201 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/skill_aware.py +206 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/slow_update.py +396 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/update_modes.py +135 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/__init__.py +63 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_error.md +41 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_error_full_rewrite.md +32 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_error_rewrite.md +44 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_success.md +36 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_success_full_rewrite.md +30 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_success_rewrite.md +33 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/lr_autonomous.md +20 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_failure.md +30 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_failure_full_rewrite.md +28 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_failure_rewrite.md +26 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_final.md +33 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_final_full_rewrite.md +28 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_final_rewrite.md +25 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_success.md +28 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_success_full_rewrite.md +28 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_success_rewrite.md +25 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/meta_skill.md +40 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/ranking.md +20 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/ranking_rewrite.md +15 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/rewrite_skill.md +25 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/slow_update.md +60 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/scheduler/__init__.py +8 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/types.py +306 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/utils/__init__.py +4 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/utils/json_utils.py +172 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/utils/scoring.py +28 -0
- package/template/skills/ponytail/SKILL.md +0 -133
- package/template/skills/ponytail/scripts/hooks/claude-codex-hooks.json +0 -44
- package/template/skills/ponytail/scripts/hooks/copilot-hooks.json +0 -21
- package/template/skills/ponytail/scripts/hooks/ponytail-activate.js +0 -91
- package/template/skills/ponytail/scripts/hooks/ponytail-config.js +0 -122
- package/template/skills/ponytail/scripts/hooks/ponytail-instructions.js +0 -94
- package/template/skills/ponytail/scripts/hooks/ponytail-mode-tracker.js +0 -55
- package/template/skills/ponytail/scripts/hooks/ponytail-runtime.js +0 -68
- package/template/skills/ponytail/scripts/hooks/ponytail-statusline.ps1 +0 -21
- package/template/skills/ponytail/scripts/hooks/ponytail-statusline.sh +0 -12
- package/template/skills/ponytail/scripts/hooks/ponytail-subagent.js +0 -22
- package/template/skills/ponytail/scripts/mcp/README.md +0 -46
- package/template/skills/ponytail/scripts/mcp/index.js +0 -48
- package/template/skills/ponytail/scripts/mcp/instructions.js +0 -26
- package/template/skills/ponytail/scripts/mcp/package.json +0 -13
- package/template/skills/ponytail/scripts/mcp/test/instructions.test.js +0 -22
- package/template/skills/ponytail-audit/SKILL.md +0 -41
- package/template/skills/ponytail-debt/SKILL.md +0 -44
- package/template/skills/ponytail-gain/SKILL.md +0 -50
- package/template/skills/ponytail-help/SKILL.md +0 -69
- package/template/skills/ponytail-review/SKILL.md +0 -57
|
@@ -0,0 +1,1057 @@
|
|
|
1
|
+
"""Helpers for running exec backends as the target harness."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import shutil
|
|
9
|
+
import subprocess
|
|
10
|
+
import threading
|
|
11
|
+
import traceback
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from skillopt.model.backend_config import (
|
|
15
|
+
get_claude_code_exec_config,
|
|
16
|
+
get_codex_exec_config,
|
|
17
|
+
get_target_backend,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
ANSWER_SCHEMA: dict[str, Any] = {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"properties": {
|
|
24
|
+
"final_response": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"description": "The exact final answer text to return, preserving required <answer>...</answer> tags.",
|
|
27
|
+
},
|
|
28
|
+
"final_answer": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"description": "The concise answer value without explanation, if separable.",
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
"required": ["final_response", "final_answer"],
|
|
34
|
+
"additionalProperties": False,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def render_skill_md(
|
|
39
|
+
skill_content: str,
|
|
40
|
+
*,
|
|
41
|
+
name: str = "skillopt-target",
|
|
42
|
+
description: str = "Dynamic ReflACT skill for the current benchmark task.",
|
|
43
|
+
preamble: str = "",
|
|
44
|
+
) -> str:
|
|
45
|
+
body = skill_content.strip() or "No additional dynamic guidance was provided for this task."
|
|
46
|
+
chunks = [
|
|
47
|
+
"---",
|
|
48
|
+
f'name: "{name}"',
|
|
49
|
+
f'description: "{description}"',
|
|
50
|
+
"---",
|
|
51
|
+
"",
|
|
52
|
+
"# ReflACT Target Skill",
|
|
53
|
+
"",
|
|
54
|
+
]
|
|
55
|
+
if preamble.strip():
|
|
56
|
+
chunks.append(preamble.strip())
|
|
57
|
+
chunks.append("")
|
|
58
|
+
chunks.extend([
|
|
59
|
+
"## Dynamic Guidance",
|
|
60
|
+
"",
|
|
61
|
+
body,
|
|
62
|
+
"",
|
|
63
|
+
])
|
|
64
|
+
return "\n".join(chunks)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def prepare_workspace(
|
|
68
|
+
*,
|
|
69
|
+
work_dir: str,
|
|
70
|
+
skill_md: str,
|
|
71
|
+
task_text: str = "",
|
|
72
|
+
task_filename: str = "task.md",
|
|
73
|
+
images: list[str] | None = None,
|
|
74
|
+
extra_files: dict[str, str] | None = None,
|
|
75
|
+
copy_files: list[tuple[str, str]] | None = None,
|
|
76
|
+
link_dirs: list[tuple[str, str]] | None = None,
|
|
77
|
+
) -> tuple[str, str]:
|
|
78
|
+
if os.path.exists(work_dir):
|
|
79
|
+
shutil.rmtree(work_dir)
|
|
80
|
+
os.makedirs(os.path.join(work_dir, ".agents", "skills", "skillopt-target"), exist_ok=True)
|
|
81
|
+
|
|
82
|
+
skill_path = os.path.join(work_dir, ".agents", "skills", "skillopt-target", "SKILL.md")
|
|
83
|
+
with open(skill_path, "w", encoding="utf-8") as f:
|
|
84
|
+
f.write(skill_md)
|
|
85
|
+
|
|
86
|
+
task_path = os.path.join(work_dir, task_filename)
|
|
87
|
+
if task_text:
|
|
88
|
+
with open(task_path, "w", encoding="utf-8") as f:
|
|
89
|
+
f.write(task_text)
|
|
90
|
+
|
|
91
|
+
if extra_files:
|
|
92
|
+
for rel_path, content in extra_files.items():
|
|
93
|
+
full_path = os.path.join(work_dir, rel_path)
|
|
94
|
+
parent = os.path.dirname(full_path)
|
|
95
|
+
if parent:
|
|
96
|
+
os.makedirs(parent, exist_ok=True)
|
|
97
|
+
with open(full_path, "w", encoding="utf-8") as f:
|
|
98
|
+
f.write(content)
|
|
99
|
+
|
|
100
|
+
if copy_files:
|
|
101
|
+
for src, rel_dst in copy_files:
|
|
102
|
+
dst = os.path.join(work_dir, rel_dst)
|
|
103
|
+
parent = os.path.dirname(dst)
|
|
104
|
+
if parent:
|
|
105
|
+
os.makedirs(parent, exist_ok=True)
|
|
106
|
+
shutil.copy2(src, dst)
|
|
107
|
+
|
|
108
|
+
if link_dirs:
|
|
109
|
+
for src, rel_dst in link_dirs:
|
|
110
|
+
dst = os.path.join(work_dir, rel_dst)
|
|
111
|
+
parent = os.path.dirname(dst)
|
|
112
|
+
if parent:
|
|
113
|
+
os.makedirs(parent, exist_ok=True)
|
|
114
|
+
os.symlink(os.path.abspath(src), dst)
|
|
115
|
+
|
|
116
|
+
attachment_lines: list[str] = []
|
|
117
|
+
if images:
|
|
118
|
+
attachments_dir = os.path.join(work_dir, "attachments")
|
|
119
|
+
os.makedirs(attachments_dir, exist_ok=True)
|
|
120
|
+
for index, image in enumerate(images, 1):
|
|
121
|
+
if not os.path.exists(image):
|
|
122
|
+
raise FileNotFoundError(image)
|
|
123
|
+
src = os.path.abspath(image)
|
|
124
|
+
base = os.path.basename(src) or f"image_{index}"
|
|
125
|
+
dst_name = f"{index:02d}_{base}"
|
|
126
|
+
dst = os.path.join(attachments_dir, dst_name)
|
|
127
|
+
if os.path.abspath(src) != os.path.abspath(dst):
|
|
128
|
+
shutil.copy2(src, dst)
|
|
129
|
+
rel_dst = os.path.relpath(dst, work_dir)
|
|
130
|
+
attachment_lines.append(f"- `{rel_dst}` (source: `{src}`)")
|
|
131
|
+
|
|
132
|
+
if attachment_lines:
|
|
133
|
+
with open(os.path.join(work_dir, "ATTACHMENTS.md"), "w", encoding="utf-8") as f:
|
|
134
|
+
f.write(
|
|
135
|
+
"# Attachments\n\n"
|
|
136
|
+
"Use these local files when the task refers to attached images or documents.\n\n"
|
|
137
|
+
+ "\n".join(attachment_lines)
|
|
138
|
+
+ "\n"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
return skill_path, task_path
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _build_codex_trace_summary(raw: str, response: str) -> str:
|
|
145
|
+
lines = [ln.rstrip() for ln in (raw or "").splitlines()]
|
|
146
|
+
|
|
147
|
+
def _find(prefix: str) -> str:
|
|
148
|
+
for ln in lines:
|
|
149
|
+
if ln.startswith(prefix):
|
|
150
|
+
return ln[len(prefix):].strip()
|
|
151
|
+
return ""
|
|
152
|
+
|
|
153
|
+
sandbox = _find("sandbox: ")
|
|
154
|
+
reasoning = _find("reasoning effort: ")
|
|
155
|
+
task_read = "unknown"
|
|
156
|
+
skill_read = "unknown"
|
|
157
|
+
exec_errors: list[str] = []
|
|
158
|
+
tokens_used = ""
|
|
159
|
+
|
|
160
|
+
for idx, ln in enumerate(lines):
|
|
161
|
+
if ln.startswith("exec"):
|
|
162
|
+
cmd = lines[idx + 1] if idx + 1 < len(lines) else ""
|
|
163
|
+
outcome = lines[idx + 2] if idx + 2 < len(lines) else ""
|
|
164
|
+
joined = f"{cmd}\n{outcome}"
|
|
165
|
+
if "task.md" in joined:
|
|
166
|
+
if "succeeded" in outcome:
|
|
167
|
+
task_read = "success"
|
|
168
|
+
elif "failed" in outcome or "ERROR" in outcome:
|
|
169
|
+
task_read = "failed"
|
|
170
|
+
if "SKILL.md" in joined:
|
|
171
|
+
if "succeeded" in outcome:
|
|
172
|
+
skill_read = "success"
|
|
173
|
+
elif "failed" in outcome or "ERROR" in outcome:
|
|
174
|
+
skill_read = "failed"
|
|
175
|
+
if ln.startswith("ERROR:"):
|
|
176
|
+
exec_errors.append(ln[len("ERROR:"):].strip())
|
|
177
|
+
if ln == "tokens used" and idx + 1 < len(lines):
|
|
178
|
+
tokens_used = lines[idx + 1].strip()
|
|
179
|
+
|
|
180
|
+
match = re.search(r"<answer>\s*([A-E])\s*</answer>", response or "", re.IGNORECASE)
|
|
181
|
+
if match:
|
|
182
|
+
answer_format = "well_formed"
|
|
183
|
+
answer_label = match.group(1).upper()
|
|
184
|
+
elif "<answer>" in (response or "").lower():
|
|
185
|
+
answer_format = "tagged_nonlabel"
|
|
186
|
+
answer_label = ""
|
|
187
|
+
elif (response or "").strip():
|
|
188
|
+
answer_format = "plain_text"
|
|
189
|
+
answer_label = ""
|
|
190
|
+
else:
|
|
191
|
+
answer_format = "missing"
|
|
192
|
+
answer_label = ""
|
|
193
|
+
|
|
194
|
+
parts = ["Codex Trace Summary"]
|
|
195
|
+
if sandbox:
|
|
196
|
+
parts.append(f"- sandbox: {sandbox}")
|
|
197
|
+
if reasoning:
|
|
198
|
+
parts.append(f"- reasoning: {reasoning}")
|
|
199
|
+
parts.append(f"- read task.md: {task_read}")
|
|
200
|
+
parts.append(f"- read SKILL.md: {skill_read}")
|
|
201
|
+
if exec_errors:
|
|
202
|
+
parts.append(f"- shell/tool errors: {' | '.join(exec_errors[:3])}")
|
|
203
|
+
else:
|
|
204
|
+
parts.append("- shell/tool errors: none")
|
|
205
|
+
parts.append(f"- final answer format: {answer_format}")
|
|
206
|
+
parts.append(f"- final answer label: {answer_label or '(none)'}")
|
|
207
|
+
if tokens_used:
|
|
208
|
+
parts.append(f"- tokens used: {tokens_used}")
|
|
209
|
+
return "\n".join(parts)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _build_claude_trace_summary(raw: str, response: str) -> str:
|
|
213
|
+
answer_format = "missing"
|
|
214
|
+
if "<answer>" in (response or "").lower():
|
|
215
|
+
answer_format = "tagged"
|
|
216
|
+
elif (response or "").strip():
|
|
217
|
+
answer_format = "plain_text"
|
|
218
|
+
errors: list[str] = []
|
|
219
|
+
for ln in (raw or "").splitlines():
|
|
220
|
+
if "error" in ln.lower() or "traceback" in ln.lower():
|
|
221
|
+
errors.append(ln.strip())
|
|
222
|
+
if len(errors) >= 3:
|
|
223
|
+
break
|
|
224
|
+
parts = ["Claude Code Trace Summary", f"- final answer format: {answer_format}"]
|
|
225
|
+
parts.append(f"- final response chars: {len(response or '')}")
|
|
226
|
+
parts.append(f"- errors: {' | '.join(errors) if errors else 'none'}")
|
|
227
|
+
return "\n".join(parts)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _persist_artifacts(
|
|
231
|
+
*,
|
|
232
|
+
work_dir: str,
|
|
233
|
+
raw: str,
|
|
234
|
+
response: str,
|
|
235
|
+
prefix: str,
|
|
236
|
+
summary_builder,
|
|
237
|
+
) -> None:
|
|
238
|
+
pred_dir = os.path.dirname(work_dir.rstrip(os.sep))
|
|
239
|
+
raw_path = os.path.join(pred_dir, f"{prefix}_raw.txt")
|
|
240
|
+
summary_path = os.path.join(pred_dir, f"{prefix}_trace_summary.txt")
|
|
241
|
+
|
|
242
|
+
combined_raw = raw
|
|
243
|
+
if os.path.exists(raw_path):
|
|
244
|
+
with open(raw_path, encoding="utf-8") as f:
|
|
245
|
+
prev = f.read()
|
|
246
|
+
combined_raw = f"{prev}\n\n===== TURN BREAK =====\n\n{raw}" if prev.strip() else raw
|
|
247
|
+
|
|
248
|
+
with open(raw_path, "w", encoding="utf-8") as f:
|
|
249
|
+
f.write(combined_raw)
|
|
250
|
+
with open(summary_path, "w", encoding="utf-8") as f:
|
|
251
|
+
f.write(summary_builder(combined_raw, response))
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _persist_codex_artifacts(work_dir: str, raw: str, response: str) -> None:
|
|
255
|
+
_persist_artifacts(
|
|
256
|
+
work_dir=work_dir,
|
|
257
|
+
raw=raw,
|
|
258
|
+
response=response,
|
|
259
|
+
prefix="codex",
|
|
260
|
+
summary_builder=_build_codex_trace_summary,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _persist_claude_artifacts(work_dir: str, raw: str, response: str) -> None:
|
|
265
|
+
_persist_artifacts(
|
|
266
|
+
work_dir=work_dir,
|
|
267
|
+
raw=raw,
|
|
268
|
+
response=response,
|
|
269
|
+
prefix="claude",
|
|
270
|
+
summary_builder=_build_claude_trace_summary,
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def parse_codex_raw(raw: str) -> dict:
|
|
275
|
+
"""Parse raw Codex CLI output into step sections.
|
|
276
|
+
|
|
277
|
+
Returns a dict with:
|
|
278
|
+
- ``steps``: ordered sections beginning at the first ``user/codex/exec`` marker
|
|
279
|
+
- ``trace_body``: raw trace starting at the first marker
|
|
280
|
+
"""
|
|
281
|
+
lines = (raw or "").splitlines()
|
|
282
|
+
markers = {"user", "codex", "exec"}
|
|
283
|
+
first_step_line: int | None = None
|
|
284
|
+
for idx, line in enumerate(lines):
|
|
285
|
+
if line in markers:
|
|
286
|
+
first_step_line = idx
|
|
287
|
+
break
|
|
288
|
+
if first_step_line is None:
|
|
289
|
+
return {"steps": [], "trace_body": ""}
|
|
290
|
+
|
|
291
|
+
steps: list[dict] = []
|
|
292
|
+
current: dict | None = None
|
|
293
|
+
for idx in range(first_step_line, len(lines)):
|
|
294
|
+
line = lines[idx]
|
|
295
|
+
if line in markers:
|
|
296
|
+
if current is not None:
|
|
297
|
+
current["end_line"] = idx
|
|
298
|
+
current["content"] = "\n".join(current["content_lines"]).strip()
|
|
299
|
+
current.pop("content_lines", None)
|
|
300
|
+
steps.append(current)
|
|
301
|
+
current = {
|
|
302
|
+
"index": len(steps) + 1,
|
|
303
|
+
"type": line,
|
|
304
|
+
"start_line": idx,
|
|
305
|
+
"content_lines": [],
|
|
306
|
+
}
|
|
307
|
+
continue
|
|
308
|
+
if current is not None:
|
|
309
|
+
current["content_lines"].append(line)
|
|
310
|
+
if current is not None:
|
|
311
|
+
current["end_line"] = len(lines)
|
|
312
|
+
current["content"] = "\n".join(current["content_lines"]).strip()
|
|
313
|
+
current.pop("content_lines", None)
|
|
314
|
+
steps.append(current)
|
|
315
|
+
|
|
316
|
+
trace_body = "\n".join(lines[first_step_line:]).strip()
|
|
317
|
+
return {"steps": steps, "trace_body": trace_body}
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def format_codex_trace_steps(raw: str, *, max_chars: int = 4000) -> str:
|
|
321
|
+
"""Render parsed Codex trace into numbered compact steps for optimizer prompts."""
|
|
322
|
+
parsed = parse_codex_raw(raw)
|
|
323
|
+
steps = parsed["steps"]
|
|
324
|
+
if not steps:
|
|
325
|
+
return ""
|
|
326
|
+
|
|
327
|
+
rendered: list[str] = []
|
|
328
|
+
for step in steps:
|
|
329
|
+
summary = ""
|
|
330
|
+
content = str(step.get("content") or "").strip()
|
|
331
|
+
if step["type"] == "exec":
|
|
332
|
+
body_lines = [ln.strip() for ln in content.splitlines() if ln.strip()]
|
|
333
|
+
cmd = body_lines[0] if body_lines else ""
|
|
334
|
+
status = ""
|
|
335
|
+
for ln in body_lines[1:]:
|
|
336
|
+
low = ln.lower()
|
|
337
|
+
if "succeeded in" in low or "failed in" in low or "timed out" in low or low.startswith("error"):
|
|
338
|
+
status = ln
|
|
339
|
+
break
|
|
340
|
+
summary = cmd
|
|
341
|
+
if status:
|
|
342
|
+
summary = f"{summary} | {status}" if summary else status
|
|
343
|
+
else:
|
|
344
|
+
summary = " ".join(content.splitlines())
|
|
345
|
+
summary = summary[:500] if summary else "(empty)"
|
|
346
|
+
rendered.append(f"[{step['index']}] {step['type']}: {summary}")
|
|
347
|
+
|
|
348
|
+
text = "\n".join(rendered)
|
|
349
|
+
if len(text) > max_chars:
|
|
350
|
+
text = text[:max_chars] + "\n...[trace steps truncated]..."
|
|
351
|
+
return text
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def extract_codex_trace_prefix(raw: str, *, after_step: int) -> str:
|
|
355
|
+
"""Return raw trace body up to and including ``after_step``.
|
|
356
|
+
|
|
357
|
+
``after_step <= 0`` yields an empty string.
|
|
358
|
+
"""
|
|
359
|
+
if after_step <= 0:
|
|
360
|
+
return ""
|
|
361
|
+
parsed = parse_codex_raw(raw)
|
|
362
|
+
steps = parsed["steps"]
|
|
363
|
+
if not steps:
|
|
364
|
+
return ""
|
|
365
|
+
clamped = min(after_step, len(steps))
|
|
366
|
+
lines = parsed["trace_body"].splitlines()
|
|
367
|
+
end_line = int(steps[clamped - 1]["end_line"]) - int(steps[0]["start_line"])
|
|
368
|
+
return "\n".join(lines[:end_line]).strip()
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
_DENIED_DATA_DIR_NAMES = {"officeqa_split", "sealqa_split"}
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def _normalize_tools(allowed_tools: list[str] | str | None) -> str:
|
|
375
|
+
if allowed_tools is None:
|
|
376
|
+
return ""
|
|
377
|
+
if isinstance(allowed_tools, str):
|
|
378
|
+
return ",".join(part.strip() for part in allowed_tools.split(",") if part.strip())
|
|
379
|
+
return ",".join(str(tool).strip() for tool in allowed_tools if str(tool).strip())
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _tools_list(allowed_tools: list[str] | str | None) -> list[str]:
|
|
383
|
+
tools = _normalize_tools(allowed_tools)
|
|
384
|
+
return [part.strip() for part in tools.split(",") if part.strip()]
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def _validate_exec_path(path: str) -> str:
|
|
388
|
+
resolved = os.path.realpath(os.path.abspath(path))
|
|
389
|
+
parts = set(resolved.split(os.sep))
|
|
390
|
+
denied = parts & _DENIED_DATA_DIR_NAMES
|
|
391
|
+
if denied:
|
|
392
|
+
raise ValueError(f"Refusing to expose denied data directory to exec backend: {', '.join(sorted(denied))}")
|
|
393
|
+
return resolved
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def _validated_add_dirs(work_dir: str, data_dirs: list[str] | None, images: list[str] | None) -> list[str]:
|
|
397
|
+
add_dirs = [_validate_exec_path(work_dir)]
|
|
398
|
+
for data_dir in data_dirs or []:
|
|
399
|
+
add_dirs.append(_validate_exec_path(data_dir))
|
|
400
|
+
for image in images or []:
|
|
401
|
+
add_dirs.append(_validate_exec_path(os.path.dirname(image) or work_dir))
|
|
402
|
+
deduped: list[str] = []
|
|
403
|
+
for path in add_dirs:
|
|
404
|
+
if path not in deduped:
|
|
405
|
+
deduped.append(path)
|
|
406
|
+
return deduped
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def _sdk_mode(value: Any) -> str:
|
|
410
|
+
mode = str(value or "auto").strip().lower()
|
|
411
|
+
if mode in {"1", "true", "yes", "on", "sdk"}:
|
|
412
|
+
return "sdk"
|
|
413
|
+
if mode in {"0", "false", "no", "off", "cli"}:
|
|
414
|
+
return "cli"
|
|
415
|
+
return "auto"
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def _claude_effort(value: Any) -> str:
|
|
419
|
+
effort = str(value or "medium").strip().lower()
|
|
420
|
+
if effort in {"", "none", "off"}:
|
|
421
|
+
return ""
|
|
422
|
+
if effort == "xhigh":
|
|
423
|
+
return "max"
|
|
424
|
+
if effort not in {"low", "medium", "high", "max"}:
|
|
425
|
+
return "medium"
|
|
426
|
+
return effort
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def _json_default(obj: Any) -> Any:
|
|
430
|
+
if isinstance(obj, (str, int, float, bool)) or obj is None:
|
|
431
|
+
return obj
|
|
432
|
+
if isinstance(obj, (list, tuple)):
|
|
433
|
+
return list(obj)
|
|
434
|
+
if isinstance(obj, dict):
|
|
435
|
+
return obj
|
|
436
|
+
if hasattr(obj, "model_dump"):
|
|
437
|
+
return obj.model_dump(mode="json")
|
|
438
|
+
if hasattr(obj, "__dict__"):
|
|
439
|
+
return {k: v for k, v in vars(obj).items() if not k.startswith("_")}
|
|
440
|
+
return str(obj)
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def _json_dumps(data: Any) -> str:
|
|
444
|
+
return json.dumps(data, ensure_ascii=False, indent=2, default=_json_default)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def _run_async(coro):
|
|
448
|
+
try:
|
|
449
|
+
asyncio.get_running_loop()
|
|
450
|
+
except RuntimeError:
|
|
451
|
+
return asyncio.run(coro)
|
|
452
|
+
|
|
453
|
+
box: dict[str, Any] = {}
|
|
454
|
+
|
|
455
|
+
def _target() -> None:
|
|
456
|
+
try:
|
|
457
|
+
box["result"] = asyncio.run(coro)
|
|
458
|
+
except BaseException as exc: # noqa: BLE001
|
|
459
|
+
box["exception"] = exc
|
|
460
|
+
|
|
461
|
+
thread = threading.Thread(target=_target, daemon=True)
|
|
462
|
+
thread.start()
|
|
463
|
+
thread.join()
|
|
464
|
+
if "exception" in box:
|
|
465
|
+
raise box["exception"]
|
|
466
|
+
return box.get("result")
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def _exec_prompt(prompt: str, *, allow_file_edits: bool = False) -> str:
|
|
470
|
+
edit_instruction = (
|
|
471
|
+
"You may modify files in the workspace when the task asks you to create an artifact. "
|
|
472
|
+
if allow_file_edits
|
|
473
|
+
else "Do not modify files. "
|
|
474
|
+
)
|
|
475
|
+
return (
|
|
476
|
+
"Use the workspace files to solve the task. Read task.md and the skill at "
|
|
477
|
+
".agents/skills/skillopt-target/SKILL.md before answering. "
|
|
478
|
+
"If ATTACHMENTS.md exists, read it and inspect the listed local files. "
|
|
479
|
+
"Do not call a Skill tool; the ReflACT guidance is a local markdown file. "
|
|
480
|
+
f"Do not ask for permission. {edit_instruction}"
|
|
481
|
+
"Return only the final answer text, keeping any required <answer>...</answer> tags exactly.\n\n"
|
|
482
|
+
f"{_normalize_target_exec_prompt(prompt)}"
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def _retry_prompt(prompt: str, attempt: int) -> str:
|
|
487
|
+
if attempt <= 0:
|
|
488
|
+
return prompt
|
|
489
|
+
return (
|
|
490
|
+
f"{prompt}\n\n"
|
|
491
|
+
"Previous execution returned an empty final response. Re-read task.md and "
|
|
492
|
+
".agents/skills/skillopt-target/SKILL.md. If ATTACHMENTS.md exists, use the listed files. "
|
|
493
|
+
"Then produce the final answer inside <answer>...</answer>."
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def _normalize_target_exec_prompt(prompt: str) -> str:
|
|
498
|
+
"""Avoid wording that makes Claude Code call an unregistered Skill tool."""
|
|
499
|
+
text = prompt or ""
|
|
500
|
+
replacements = {
|
|
501
|
+
"Use the `skillopt-target` skill available in this workspace.": (
|
|
502
|
+
"Read `.agents/skills/skillopt-target/SKILL.md` directly; do not call a Skill tool."
|
|
503
|
+
),
|
|
504
|
+
"- Use the local `skillopt-target` skill before writing code.": (
|
|
505
|
+
"- Read `.agents/skills/skillopt-target/SKILL.md` before writing code; do not call a Skill tool."
|
|
506
|
+
),
|
|
507
|
+
}
|
|
508
|
+
for old, new in replacements.items():
|
|
509
|
+
text = text.replace(old, new)
|
|
510
|
+
return text
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def _strict_schema(schema: dict[str, Any]) -> dict[str, Any]:
|
|
514
|
+
strict = json.loads(json.dumps(schema))
|
|
515
|
+
strict["additionalProperties"] = False
|
|
516
|
+
properties = strict.get("properties") or {}
|
|
517
|
+
strict["required"] = list(properties.keys())
|
|
518
|
+
return strict
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def _structured_response(data: Any) -> tuple[str, str]:
|
|
522
|
+
if not isinstance(data, dict):
|
|
523
|
+
return "", f"Structured output was not an object: {type(data).__name__}"
|
|
524
|
+
final_response = str(data.get("final_response") or "").strip()
|
|
525
|
+
final_answer = str(data.get("final_answer") or "").strip()
|
|
526
|
+
if final_response:
|
|
527
|
+
return final_response, ""
|
|
528
|
+
if final_answer:
|
|
529
|
+
if "<answer>" in final_answer.lower():
|
|
530
|
+
return final_answer, ""
|
|
531
|
+
return f"<answer>{final_answer}</answer>", ""
|
|
532
|
+
return "", "Structured output did not contain a final response."
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
def _extract_claude_structured_output(messages: list[Any]) -> Any:
|
|
536
|
+
"""Claude Code SDK can finish with error_during_execution after StructuredOutput."""
|
|
537
|
+
for msg in reversed(messages):
|
|
538
|
+
structured = getattr(msg, "structured_output", None)
|
|
539
|
+
if isinstance(structured, dict):
|
|
540
|
+
return structured
|
|
541
|
+
|
|
542
|
+
content = getattr(msg, "content", None)
|
|
543
|
+
if content is None and isinstance(msg, dict):
|
|
544
|
+
content = msg.get("content")
|
|
545
|
+
if not isinstance(content, list):
|
|
546
|
+
continue
|
|
547
|
+
|
|
548
|
+
for item in reversed(content):
|
|
549
|
+
name = getattr(item, "name", None)
|
|
550
|
+
payload = getattr(item, "input", None)
|
|
551
|
+
if isinstance(item, dict):
|
|
552
|
+
name = item.get("name", name)
|
|
553
|
+
payload = item.get("input", payload)
|
|
554
|
+
if name == "StructuredOutput" and isinstance(payload, dict):
|
|
555
|
+
return payload
|
|
556
|
+
return None
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
def _raw_exception(label: str, exc: BaseException) -> str:
|
|
560
|
+
return _json_dumps({
|
|
561
|
+
"backend": label,
|
|
562
|
+
"is_error": True,
|
|
563
|
+
"error_type": type(exc).__name__,
|
|
564
|
+
"error": str(exc),
|
|
565
|
+
"traceback": traceback.format_exc(),
|
|
566
|
+
})
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def _run_claude_code_sdk_exec(
|
|
570
|
+
*,
|
|
571
|
+
work_dir: str,
|
|
572
|
+
prompt: str,
|
|
573
|
+
model: str,
|
|
574
|
+
timeout: int,
|
|
575
|
+
images: list[str] | None = None,
|
|
576
|
+
data_dirs: list[str] | None = None,
|
|
577
|
+
allowed_tools: list[str] | str | None = None,
|
|
578
|
+
permission_mode: str | None = None,
|
|
579
|
+
allow_file_edits: bool = False,
|
|
580
|
+
) -> tuple[str, str]:
|
|
581
|
+
from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient
|
|
582
|
+
|
|
583
|
+
async def _query() -> tuple[str, str]:
|
|
584
|
+
system_prompt: dict[str, Any] = {
|
|
585
|
+
"type": "preset",
|
|
586
|
+
"preset": "claude_code",
|
|
587
|
+
"append": (
|
|
588
|
+
"Use the workspace files to solve the task. Read task.md and the skill at "
|
|
589
|
+
".agents/skills/skillopt-target/SKILL.md before answering. "
|
|
590
|
+
"If ATTACHMENTS.md exists, read it and inspect the listed local files. "
|
|
591
|
+
"Do not call a Skill tool; the ReflACT guidance is a local markdown file. "
|
|
592
|
+
+ (
|
|
593
|
+
"You may modify files in the workspace when the task asks you to create an artifact. "
|
|
594
|
+
if allow_file_edits
|
|
595
|
+
else "Do not modify files. "
|
|
596
|
+
)
|
|
597
|
+
+ "Return structured output whose final_response preserves required <answer>...</answer> tags."
|
|
598
|
+
),
|
|
599
|
+
}
|
|
600
|
+
kwargs: dict[str, Any] = {
|
|
601
|
+
"system_prompt": system_prompt,
|
|
602
|
+
"output_format": {"type": "json_schema", "schema": ANSWER_SCHEMA},
|
|
603
|
+
"allowed_tools": _tools_list(allowed_tools) or ["Read", "Bash"],
|
|
604
|
+
"cwd": str(work_dir),
|
|
605
|
+
"permission_mode": permission_mode or "bypassPermissions",
|
|
606
|
+
"add_dirs": _validated_add_dirs(work_dir, data_dirs, images),
|
|
607
|
+
"max_buffer_size": 8 * 1024 * 1024,
|
|
608
|
+
}
|
|
609
|
+
config = get_claude_code_exec_config()
|
|
610
|
+
effort = _claude_effort(config.get("effort"))
|
|
611
|
+
if effort:
|
|
612
|
+
kwargs["effort"] = effort
|
|
613
|
+
max_thinking_tokens = int(config.get("max_thinking_tokens", 0) or 0)
|
|
614
|
+
if max_thinking_tokens > 0:
|
|
615
|
+
kwargs["max_thinking_tokens"] = max_thinking_tokens
|
|
616
|
+
options = ClaudeAgentOptions(**kwargs)
|
|
617
|
+
if model:
|
|
618
|
+
options.model = model.split("/", 1)[1] if model.startswith("anthropic/") else model
|
|
619
|
+
|
|
620
|
+
messages = []
|
|
621
|
+
async with ClaudeSDKClient(options) as client:
|
|
622
|
+
await client.query(_normalize_target_exec_prompt(prompt))
|
|
623
|
+
messages = [msg async for msg in client.receive_response()]
|
|
624
|
+
last = messages[-1] if messages else None
|
|
625
|
+
raw_structured_output = _extract_claude_structured_output(messages)
|
|
626
|
+
response, parse_error = _structured_response(raw_structured_output)
|
|
627
|
+
first = messages[0] if messages else None
|
|
628
|
+
first_data = getattr(first, "data", {}) if first is not None else {}
|
|
629
|
+
terminal_is_error = bool(getattr(last, "is_error", False)) if last is not None else False
|
|
630
|
+
raw = _json_dumps({
|
|
631
|
+
"backend": "claude_code_sdk",
|
|
632
|
+
"uuid": first_data.get("uuid", "") if isinstance(first_data, dict) else "",
|
|
633
|
+
"session_id": getattr(last, "session_id", "") if last is not None else "",
|
|
634
|
+
"model": first_data.get("model", model) if isinstance(first_data, dict) else model,
|
|
635
|
+
"tools": first_data.get("tools", _tools_list(allowed_tools)) if isinstance(first_data, dict) else _tools_list(allowed_tools),
|
|
636
|
+
"duration_ms": getattr(last, "duration_ms", 0) if last is not None else 0,
|
|
637
|
+
"total_cost_usd": getattr(last, "total_cost_usd", 0.0) if last is not None else 0.0,
|
|
638
|
+
"num_turns": getattr(last, "num_turns", 0) if last is not None else 0,
|
|
639
|
+
"usage": getattr(last, "usage", {}) if last is not None else {},
|
|
640
|
+
"result": getattr(last, "result", "") if last is not None else "",
|
|
641
|
+
"is_error": bool(parse_error) or (terminal_is_error and not response.strip()),
|
|
642
|
+
"terminal_is_error": terminal_is_error,
|
|
643
|
+
"parse_error": parse_error,
|
|
644
|
+
"raw_structured_output": raw_structured_output,
|
|
645
|
+
"messages": messages,
|
|
646
|
+
})
|
|
647
|
+
return response, raw
|
|
648
|
+
|
|
649
|
+
return _run_async(asyncio.wait_for(_query(), timeout=timeout))
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
def _run_claude_code_cli_exec(
|
|
653
|
+
*,
|
|
654
|
+
work_dir: str,
|
|
655
|
+
prompt: str,
|
|
656
|
+
model: str,
|
|
657
|
+
timeout: int,
|
|
658
|
+
images: list[str] | None = None,
|
|
659
|
+
data_dirs: list[str] | None = None,
|
|
660
|
+
allowed_tools: list[str] | str | None = None,
|
|
661
|
+
permission_mode: str | None = None,
|
|
662
|
+
allow_file_edits: bool = False,
|
|
663
|
+
) -> tuple[str, str]:
|
|
664
|
+
config = get_claude_code_exec_config()
|
|
665
|
+
tools = "Read,Bash" if allowed_tools is None else _normalize_tools(allowed_tools)
|
|
666
|
+
cmd = [
|
|
667
|
+
str(config["path"]),
|
|
668
|
+
"-p",
|
|
669
|
+
"--output-format",
|
|
670
|
+
"text",
|
|
671
|
+
"--permission-mode",
|
|
672
|
+
permission_mode or "bypassPermissions",
|
|
673
|
+
"--add-dir",
|
|
674
|
+
work_dir,
|
|
675
|
+
"--tools",
|
|
676
|
+
tools,
|
|
677
|
+
"--allowedTools",
|
|
678
|
+
tools,
|
|
679
|
+
]
|
|
680
|
+
if config.get("profile"):
|
|
681
|
+
cmd.extend(["--settings", '{"env":{"CLAUDE_CODE_USE_BEDROCK":"0"}}'])
|
|
682
|
+
cmd.extend(["--append-system-prompt", f"Profile: {config['profile']}"])
|
|
683
|
+
if model:
|
|
684
|
+
cmd.extend(["--model", model])
|
|
685
|
+
effort = _claude_effort(config.get("effort"))
|
|
686
|
+
if effort:
|
|
687
|
+
cmd.extend(["--effort", effort])
|
|
688
|
+
max_thinking_tokens = int(config.get("max_thinking_tokens", 0) or 0)
|
|
689
|
+
if max_thinking_tokens > 0:
|
|
690
|
+
cmd.extend(["--max-thinking-tokens", str(max_thinking_tokens)])
|
|
691
|
+
for data_dir in data_dirs or []:
|
|
692
|
+
cmd.extend(["--add-dir", _validate_exec_path(data_dir)])
|
|
693
|
+
if images:
|
|
694
|
+
for image in images:
|
|
695
|
+
cmd.extend(["--add-dir", _validate_exec_path(os.path.dirname(image) or work_dir)])
|
|
696
|
+
cmd.extend(["--", _exec_prompt(prompt, allow_file_edits=allow_file_edits)])
|
|
697
|
+
|
|
698
|
+
try:
|
|
699
|
+
proc = subprocess.run(
|
|
700
|
+
cmd,
|
|
701
|
+
cwd=work_dir,
|
|
702
|
+
capture_output=True,
|
|
703
|
+
text=True,
|
|
704
|
+
timeout=timeout,
|
|
705
|
+
)
|
|
706
|
+
except subprocess.TimeoutExpired as exc:
|
|
707
|
+
stdout = exc.stdout or ""
|
|
708
|
+
stderr = exc.stderr or ""
|
|
709
|
+
raw = stdout
|
|
710
|
+
if stderr:
|
|
711
|
+
raw = f"{raw}\n[stderr]\n{stderr}" if raw else stderr
|
|
712
|
+
return "", raw
|
|
713
|
+
|
|
714
|
+
stdout = proc.stdout or ""
|
|
715
|
+
stderr = proc.stderr or ""
|
|
716
|
+
raw = stdout
|
|
717
|
+
if stderr:
|
|
718
|
+
raw = f"{raw}\n[stderr]\n{stderr}" if raw else stderr
|
|
719
|
+
response = stdout.strip()
|
|
720
|
+
if proc.returncode != 0 and not response:
|
|
721
|
+
return "", raw
|
|
722
|
+
return response, raw
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
def run_claude_code_exec(
|
|
726
|
+
*,
|
|
727
|
+
work_dir: str,
|
|
728
|
+
prompt: str,
|
|
729
|
+
model: str,
|
|
730
|
+
timeout: int,
|
|
731
|
+
images: list[str] | None = None,
|
|
732
|
+
data_dirs: list[str] | None = None,
|
|
733
|
+
allowed_tools: list[str] | str | None = None,
|
|
734
|
+
permission_mode: str | None = None,
|
|
735
|
+
allow_file_edits: bool = False,
|
|
736
|
+
) -> tuple[str, str]:
|
|
737
|
+
config = get_claude_code_exec_config()
|
|
738
|
+
mode = _sdk_mode(config.get("use_sdk"))
|
|
739
|
+
retries = int(config.get("empty_response_retries", 0) or 0)
|
|
740
|
+
last_response = ""
|
|
741
|
+
all_raw: list[str] = []
|
|
742
|
+
|
|
743
|
+
for attempt in range(retries + 1):
|
|
744
|
+
attempt_prompt = _retry_prompt(prompt, attempt)
|
|
745
|
+
if mode != "cli":
|
|
746
|
+
try:
|
|
747
|
+
response, raw = _run_claude_code_sdk_exec(
|
|
748
|
+
work_dir=work_dir,
|
|
749
|
+
prompt=attempt_prompt,
|
|
750
|
+
model=model,
|
|
751
|
+
timeout=timeout,
|
|
752
|
+
images=images,
|
|
753
|
+
data_dirs=data_dirs,
|
|
754
|
+
allowed_tools=allowed_tools,
|
|
755
|
+
permission_mode=permission_mode,
|
|
756
|
+
allow_file_edits=allow_file_edits,
|
|
757
|
+
)
|
|
758
|
+
all_raw.append(f"===== CLAUDE SDK ATTEMPT {attempt + 1} =====\n{raw}")
|
|
759
|
+
if response.strip():
|
|
760
|
+
combined = "\n\n".join(all_raw)
|
|
761
|
+
_persist_claude_artifacts(work_dir, combined, response)
|
|
762
|
+
return response, combined
|
|
763
|
+
except (ImportError, ModuleNotFoundError) as exc:
|
|
764
|
+
raw = _raw_exception("claude_code_sdk", exc)
|
|
765
|
+
all_raw.append(f"===== CLAUDE SDK ATTEMPT {attempt + 1} =====\n{raw}")
|
|
766
|
+
if mode == "sdk":
|
|
767
|
+
_persist_claude_artifacts(work_dir, "\n\n".join(all_raw), "")
|
|
768
|
+
raise
|
|
769
|
+
except Exception as exc: # noqa: BLE001
|
|
770
|
+
raw = _raw_exception("claude_code_sdk", exc)
|
|
771
|
+
all_raw.append(f"===== CLAUDE SDK ATTEMPT {attempt + 1} =====\n{raw}")
|
|
772
|
+
if mode == "sdk" and attempt >= retries:
|
|
773
|
+
_persist_claude_artifacts(work_dir, "\n\n".join(all_raw), "")
|
|
774
|
+
raise
|
|
775
|
+
if mode != "sdk":
|
|
776
|
+
response, raw = _run_claude_code_cli_exec(
|
|
777
|
+
work_dir=work_dir,
|
|
778
|
+
prompt=attempt_prompt,
|
|
779
|
+
model=model,
|
|
780
|
+
timeout=timeout,
|
|
781
|
+
images=images,
|
|
782
|
+
data_dirs=data_dirs,
|
|
783
|
+
allowed_tools=allowed_tools,
|
|
784
|
+
permission_mode=permission_mode,
|
|
785
|
+
allow_file_edits=allow_file_edits,
|
|
786
|
+
)
|
|
787
|
+
all_raw.append(f"===== CLAUDE CLI ATTEMPT {attempt + 1} =====\n{raw}")
|
|
788
|
+
last_response = response
|
|
789
|
+
if response.strip():
|
|
790
|
+
combined = "\n\n".join(all_raw)
|
|
791
|
+
_persist_claude_artifacts(work_dir, combined, response)
|
|
792
|
+
return response, combined
|
|
793
|
+
|
|
794
|
+
combined = "\n\n".join(all_raw)
|
|
795
|
+
_persist_claude_artifacts(work_dir, combined, last_response)
|
|
796
|
+
return last_response, combined
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
def _run_codex_sdk_exec(
|
|
800
|
+
*,
|
|
801
|
+
work_dir: str,
|
|
802
|
+
prompt: str,
|
|
803
|
+
model: str,
|
|
804
|
+
timeout: int,
|
|
805
|
+
images: list[str] | None = None,
|
|
806
|
+
data_dirs: list[str] | None = None,
|
|
807
|
+
) -> tuple[str, str]:
|
|
808
|
+
from openai_codex_sdk import Codex
|
|
809
|
+
|
|
810
|
+
for data_dir in data_dirs or []:
|
|
811
|
+
_validate_exec_path(data_dir)
|
|
812
|
+
for image in images or []:
|
|
813
|
+
_validate_exec_path(os.path.dirname(image) or work_dir)
|
|
814
|
+
|
|
815
|
+
async def _query() -> tuple[str, str]:
|
|
816
|
+
config = get_codex_exec_config()
|
|
817
|
+
reasoning_effort = str(config.get("reasoning_effort", "") or "").strip()
|
|
818
|
+
thread_options: dict[str, Any] = {
|
|
819
|
+
"working_directory": work_dir,
|
|
820
|
+
"skip_git_repo_check": True,
|
|
821
|
+
"sandbox_mode": str(config.get("sandbox") or "workspace-write"),
|
|
822
|
+
"network_access_enabled": bool(config.get("network_access", False)),
|
|
823
|
+
"web_search_enabled": bool(config.get("web_search", False)),
|
|
824
|
+
"approval_policy": str(config.get("approval_policy") or "never"),
|
|
825
|
+
}
|
|
826
|
+
if model:
|
|
827
|
+
thread_options["model"] = model
|
|
828
|
+
if data_dirs:
|
|
829
|
+
thread_options["additional_directories"] = data_dirs
|
|
830
|
+
if reasoning_effort and reasoning_effort != "none":
|
|
831
|
+
thread_options["model_reasoning_effort"] = reasoning_effort
|
|
832
|
+
|
|
833
|
+
codex_options: dict[str, Any] = {"env": os.environ.copy()}
|
|
834
|
+
codex_path = str(config.get("path") or "").strip()
|
|
835
|
+
if codex_path:
|
|
836
|
+
codex_options["codexPathOverride"] = codex_path
|
|
837
|
+
codex = Codex(codex_options)
|
|
838
|
+
thread = codex.start_thread(thread_options)
|
|
839
|
+
turn = await thread.run(prompt, {"output_schema": _strict_schema(ANSWER_SCHEMA)})
|
|
840
|
+
result_text = str(getattr(turn, "final_response", "") or "")
|
|
841
|
+
parsed: Any = None
|
|
842
|
+
parse_error = ""
|
|
843
|
+
response = ""
|
|
844
|
+
if result_text.strip():
|
|
845
|
+
try:
|
|
846
|
+
parsed = json.loads(result_text)
|
|
847
|
+
response, parse_error = _structured_response(parsed)
|
|
848
|
+
except Exception as exc: # noqa: BLE001
|
|
849
|
+
parse_error = f"{type(exc).__name__}: {exc}"
|
|
850
|
+
else:
|
|
851
|
+
parse_error = "No response from Codex SDK (final_response is empty)."
|
|
852
|
+
raw = _json_dumps({
|
|
853
|
+
"backend": "codex_sdk",
|
|
854
|
+
"id": getattr(turn, "id", ""),
|
|
855
|
+
"thread_id": getattr(turn, "thread_id", ""),
|
|
856
|
+
"model": model,
|
|
857
|
+
"thread_options": thread_options,
|
|
858
|
+
"final_response": result_text,
|
|
859
|
+
"raw_structured_output": parsed,
|
|
860
|
+
"parse_error": parse_error,
|
|
861
|
+
"is_error": bool(parse_error),
|
|
862
|
+
"items": getattr(turn, "items", []),
|
|
863
|
+
})
|
|
864
|
+
return response, raw
|
|
865
|
+
|
|
866
|
+
return _run_async(asyncio.wait_for(_query(), timeout=timeout))
|
|
867
|
+
|
|
868
|
+
|
|
869
|
+
def _run_codex_cli_exec(
|
|
870
|
+
*,
|
|
871
|
+
work_dir: str,
|
|
872
|
+
prompt: str,
|
|
873
|
+
model: str,
|
|
874
|
+
timeout: int,
|
|
875
|
+
images: list[str] | None = None,
|
|
876
|
+
data_dirs: list[str] | None = None,
|
|
877
|
+
sandbox: str | None = None,
|
|
878
|
+
full_auto: bool | None = None,
|
|
879
|
+
) -> tuple[str, str]:
|
|
880
|
+
config = get_codex_exec_config()
|
|
881
|
+
last_message_path = os.path.join(work_dir, "codex_last_message.txt")
|
|
882
|
+
cmd = [
|
|
883
|
+
str(config["path"]),
|
|
884
|
+
"exec",
|
|
885
|
+
"--skip-git-repo-check",
|
|
886
|
+
"--color",
|
|
887
|
+
"never",
|
|
888
|
+
"-C",
|
|
889
|
+
work_dir,
|
|
890
|
+
]
|
|
891
|
+
if config.get("profile"):
|
|
892
|
+
cmd.extend(["-p", str(config["profile"])])
|
|
893
|
+
reasoning_effort = str(config.get("reasoning_effort", "")).strip()
|
|
894
|
+
if reasoning_effort:
|
|
895
|
+
cmd.extend(["-c", f'model_reasoning_effort="{reasoning_effort}"'])
|
|
896
|
+
actual_full_auto = bool(config.get("full_auto", True)) if full_auto is None else bool(full_auto)
|
|
897
|
+
actual_sandbox = str(sandbox or config["sandbox"])
|
|
898
|
+
if actual_full_auto:
|
|
899
|
+
cmd.append("--full-auto")
|
|
900
|
+
else:
|
|
901
|
+
cmd.extend(["--sandbox", actual_sandbox])
|
|
902
|
+
if model:
|
|
903
|
+
cmd.extend(["-m", model])
|
|
904
|
+
for data_dir in data_dirs or []:
|
|
905
|
+
_validate_exec_path(data_dir)
|
|
906
|
+
for image in images or []:
|
|
907
|
+
_validate_exec_path(os.path.dirname(image) or work_dir)
|
|
908
|
+
cmd.extend(["-i", image])
|
|
909
|
+
cmd.extend(["--output-last-message", last_message_path, prompt])
|
|
910
|
+
|
|
911
|
+
try:
|
|
912
|
+
proc = subprocess.run(
|
|
913
|
+
cmd,
|
|
914
|
+
cwd=work_dir,
|
|
915
|
+
capture_output=True,
|
|
916
|
+
text=True,
|
|
917
|
+
timeout=timeout,
|
|
918
|
+
)
|
|
919
|
+
except subprocess.TimeoutExpired as exc:
|
|
920
|
+
stdout = exc.stdout or ""
|
|
921
|
+
stderr = exc.stderr or ""
|
|
922
|
+
raw = stdout
|
|
923
|
+
if stderr:
|
|
924
|
+
raw = f"{raw}\n[stderr]\n{stderr}" if raw else stderr
|
|
925
|
+
_persist_codex_artifacts(work_dir, raw, "")
|
|
926
|
+
raise
|
|
927
|
+
try:
|
|
928
|
+
from skillopt.model import azure_openai as _openai
|
|
929
|
+
_openai.tracker.record("rollout", 0, 0)
|
|
930
|
+
except Exception:
|
|
931
|
+
pass
|
|
932
|
+
stdout = proc.stdout or ""
|
|
933
|
+
stderr = proc.stderr or ""
|
|
934
|
+
last_message = ""
|
|
935
|
+
if os.path.exists(last_message_path):
|
|
936
|
+
with open(last_message_path, encoding="utf-8") as f:
|
|
937
|
+
last_message = f.read()
|
|
938
|
+
raw = stdout
|
|
939
|
+
if stderr:
|
|
940
|
+
raw = f"{raw}\n[stderr]\n{stderr}" if raw else stderr
|
|
941
|
+
if proc.returncode != 0:
|
|
942
|
+
_persist_codex_artifacts(work_dir, raw, last_message)
|
|
943
|
+
detail = (stderr or stdout).strip()
|
|
944
|
+
raise RuntimeError(
|
|
945
|
+
f"codex exec failed with exit code {proc.returncode}: {detail[:4000]}"
|
|
946
|
+
)
|
|
947
|
+
return last_message, raw
|
|
948
|
+
|
|
949
|
+
|
|
950
|
+
def run_codex_exec(
|
|
951
|
+
*,
|
|
952
|
+
work_dir: str,
|
|
953
|
+
prompt: str,
|
|
954
|
+
model: str,
|
|
955
|
+
timeout: int,
|
|
956
|
+
images: list[str] | None = None,
|
|
957
|
+
data_dirs: list[str] | None = None,
|
|
958
|
+
sandbox: str | None = None,
|
|
959
|
+
full_auto: bool | None = None,
|
|
960
|
+
) -> tuple[str, str]:
|
|
961
|
+
config = get_codex_exec_config()
|
|
962
|
+
mode = _sdk_mode(config.get("use_sdk"))
|
|
963
|
+
retries = int(config.get("empty_response_retries", 0) or 0)
|
|
964
|
+
last_response = ""
|
|
965
|
+
all_raw: list[str] = []
|
|
966
|
+
|
|
967
|
+
for attempt in range(retries + 1):
|
|
968
|
+
attempt_prompt = _retry_prompt(prompt, attempt)
|
|
969
|
+
if mode != "cli":
|
|
970
|
+
try:
|
|
971
|
+
response, raw = _run_codex_sdk_exec(
|
|
972
|
+
work_dir=work_dir,
|
|
973
|
+
prompt=attempt_prompt,
|
|
974
|
+
model=model,
|
|
975
|
+
timeout=timeout,
|
|
976
|
+
images=images,
|
|
977
|
+
data_dirs=data_dirs,
|
|
978
|
+
)
|
|
979
|
+
all_raw.append(f"===== CODEX SDK ATTEMPT {attempt + 1} =====\n{raw}")
|
|
980
|
+
if response.strip():
|
|
981
|
+
combined = "\n\n".join(all_raw)
|
|
982
|
+
_persist_codex_artifacts(work_dir, combined, response)
|
|
983
|
+
return response, combined
|
|
984
|
+
except (ImportError, ModuleNotFoundError) as exc:
|
|
985
|
+
raw = _raw_exception("codex_sdk", exc)
|
|
986
|
+
all_raw.append(f"===== CODEX SDK ATTEMPT {attempt + 1} =====\n{raw}")
|
|
987
|
+
if mode == "sdk":
|
|
988
|
+
_persist_codex_artifacts(work_dir, "\n\n".join(all_raw), "")
|
|
989
|
+
raise
|
|
990
|
+
except Exception as exc: # noqa: BLE001
|
|
991
|
+
raw = _raw_exception("codex_sdk", exc)
|
|
992
|
+
all_raw.append(f"===== CODEX SDK ATTEMPT {attempt + 1} =====\n{raw}")
|
|
993
|
+
if mode == "sdk" and attempt >= retries:
|
|
994
|
+
_persist_codex_artifacts(work_dir, "\n\n".join(all_raw), "")
|
|
995
|
+
raise
|
|
996
|
+
if mode != "sdk":
|
|
997
|
+
response, raw = _run_codex_cli_exec(
|
|
998
|
+
work_dir=work_dir,
|
|
999
|
+
prompt=attempt_prompt,
|
|
1000
|
+
model=model,
|
|
1001
|
+
timeout=timeout,
|
|
1002
|
+
images=images,
|
|
1003
|
+
data_dirs=data_dirs,
|
|
1004
|
+
sandbox=sandbox,
|
|
1005
|
+
full_auto=full_auto,
|
|
1006
|
+
)
|
|
1007
|
+
all_raw.append(f"===== CODEX CLI ATTEMPT {attempt + 1} =====\n{raw}")
|
|
1008
|
+
last_response = response
|
|
1009
|
+
if response.strip():
|
|
1010
|
+
combined = "\n\n".join(all_raw)
|
|
1011
|
+
_persist_codex_artifacts(work_dir, combined, response)
|
|
1012
|
+
return response, combined
|
|
1013
|
+
|
|
1014
|
+
combined = "\n\n".join(all_raw)
|
|
1015
|
+
_persist_codex_artifacts(work_dir, combined, last_response)
|
|
1016
|
+
return last_response, combined
|
|
1017
|
+
|
|
1018
|
+
|
|
1019
|
+
def run_target_exec(
|
|
1020
|
+
*,
|
|
1021
|
+
work_dir: str,
|
|
1022
|
+
prompt: str,
|
|
1023
|
+
model: str,
|
|
1024
|
+
timeout: int,
|
|
1025
|
+
images: list[str] | None = None,
|
|
1026
|
+
data_dirs: list[str] | None = None,
|
|
1027
|
+
allowed_tools: list[str] | str | None = None,
|
|
1028
|
+
permission_mode: str | None = None,
|
|
1029
|
+
sandbox: str | None = None,
|
|
1030
|
+
full_auto: bool | None = None,
|
|
1031
|
+
allow_file_edits: bool = False,
|
|
1032
|
+
) -> tuple[str, str]:
|
|
1033
|
+
backend = get_target_backend()
|
|
1034
|
+
if backend == "codex_exec":
|
|
1035
|
+
return run_codex_exec(
|
|
1036
|
+
work_dir=work_dir,
|
|
1037
|
+
prompt=prompt,
|
|
1038
|
+
model=model,
|
|
1039
|
+
timeout=timeout,
|
|
1040
|
+
images=images,
|
|
1041
|
+
data_dirs=data_dirs,
|
|
1042
|
+
sandbox=sandbox,
|
|
1043
|
+
full_auto=full_auto,
|
|
1044
|
+
)
|
|
1045
|
+
if backend == "claude_code_exec":
|
|
1046
|
+
return run_claude_code_exec(
|
|
1047
|
+
work_dir=work_dir,
|
|
1048
|
+
prompt=prompt,
|
|
1049
|
+
model=model,
|
|
1050
|
+
timeout=timeout,
|
|
1051
|
+
images=images,
|
|
1052
|
+
data_dirs=data_dirs,
|
|
1053
|
+
allowed_tools=allowed_tools,
|
|
1054
|
+
permission_mode=permission_mode,
|
|
1055
|
+
allow_file_edits=allow_file_edits,
|
|
1056
|
+
)
|
|
1057
|
+
raise ValueError(f"Unsupported exec backend: {backend}")
|