evalrx 0.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- evalrx/__init__.py +139 -0
- evalrx/agent_assets/__init__.py +2 -0
- evalrx/agent_assets/skills/README.md +28 -0
- evalrx/agent_assets/skills/eval-chart-style/SKILL.md +172 -0
- evalrx/agent_assets/skills/evalrx-report-ui/SKILL.md +116 -0
- evalrx/agent_assets/skills/nature-figure/LICENSE +201 -0
- evalrx/agent_assets/skills/nature-figure/README.md +412 -0
- evalrx/agent_assets/skills/nature-figure/SKILL.md +60 -0
- evalrx/agent_assets/skills/nature-figure/manifest.yaml +59 -0
- evalrx/agent_assets/skills/nature-figure/references/api.md +436 -0
- evalrx/agent_assets/skills/nature-figure/references/backend-selection.md +100 -0
- evalrx/agent_assets/skills/nature-figure/references/chart-types.md +281 -0
- evalrx/agent_assets/skills/nature-figure/references/common-patterns.md +350 -0
- evalrx/agent_assets/skills/nature-figure/references/demos.md +65 -0
- evalrx/agent_assets/skills/nature-figure/references/design-theory.md +439 -0
- evalrx/agent_assets/skills/nature-figure/references/figure-contract.md +93 -0
- evalrx/agent_assets/skills/nature-figure/references/figure-legend-conventions.md +71 -0
- evalrx/agent_assets/skills/nature-figure/references/nature-2026-observations.md +112 -0
- evalrx/agent_assets/skills/nature-figure/references/qa-contract.md +119 -0
- evalrx/agent_assets/skills/nature-figure/references/r-template-index.md +66 -0
- evalrx/agent_assets/skills/nature-figure/references/r-workflow.md +161 -0
- evalrx/agent_assets/skills/nature-figure/references/tutorials.md +251 -0
- evalrx/agent_assets/skills/nature-figure/static/core/contract.md +29 -0
- evalrx/agent_assets/skills/nature-figure/static/core/stance.md +37 -0
- evalrx/agent_assets/skills/nature-figure/static/fragments/backend/python.md +37 -0
- evalrx/agent_assets/skills/nature-figure/static/fragments/backend/r.md +44 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/SKILL.md +213 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/assets/analysis_report_template.md +53 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/references/model_selection.md +72 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/scripts/explanatory_var_eda.R +130 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/scripts/explanatory_var_eda.py +150 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/scripts/fit_outcome_model.R +181 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/scripts/fit_outcome_model.py +186 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/scripts/univariate_eda.R +149 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/scripts/univariate_eda.py +177 -0
- evalrx/agent_assets/skills.py +27 -0
- evalrx/agent_runtime/__init__.py +78 -0
- evalrx/agent_runtime/_docker_runner.py +89 -0
- evalrx/agent_runtime/cli_runtime.py +103 -0
- evalrx/agent_runtime/cli_transcript.py +138 -0
- evalrx/agent_runtime/cli_types.py +68 -0
- evalrx/agent_runtime/codegen/__init__.py +5 -0
- evalrx/agent_runtime/codegen/runner.py +94 -0
- evalrx/agent_runtime/experiment_harness.py +117 -0
- evalrx/agent_runtime/factory.py +102 -0
- evalrx/agent_runtime/json_shape.py +44 -0
- evalrx/agent_runtime/judges/__init__.py +28 -0
- evalrx/agent_runtime/judges/agy.py +179 -0
- evalrx/agent_runtime/judges/autodetect.py +135 -0
- evalrx/agent_runtime/judges/claude.py +159 -0
- evalrx/agent_runtime/judges/codex.py +120 -0
- evalrx/agent_runtime/providers/__init__.py +21 -0
- evalrx/agent_runtime/providers/antigravity.py +31 -0
- evalrx/agent_runtime/providers/base.py +145 -0
- evalrx/agent_runtime/providers/claude_code.py +49 -0
- evalrx/agent_runtime/providers/codex.py +37 -0
- evalrx/agent_runtime/providers/gemini_cli.py +26 -0
- evalrx/agent_runtime/providers/kimi_cli.py +27 -0
- evalrx/agent_runtime/providers/opencode.py +27 -0
- evalrx/agent_runtime/providers/registry.py +58 -0
- evalrx/agent_runtime/sandbox.py +517 -0
- evalrx/agent_runtime/skill_audit.py +143 -0
- evalrx/agent_runtime/skills/__init__.py +19 -0
- evalrx/agent_runtime/skills/installer.py +68 -0
- evalrx/agent_runtime/skills/prompt_policy.py +86 -0
- evalrx/agent_runtime/skills/resolver.py +19 -0
- evalrx/analysis/__init__.py +132 -0
- evalrx/analysis/adjudicate.py +154 -0
- evalrx/analysis/analysis_module.py +361 -0
- evalrx/analysis/api.py +171 -0
- evalrx/analysis/case_studio.py +651 -0
- evalrx/analysis/cli.py +114 -0
- evalrx/analysis/dashboard.py +350 -0
- evalrx/analysis/eval_case_matrix.py +118 -0
- evalrx/analysis/eval_viz_theme.py +833 -0
- evalrx/analysis/explore_run.py +333 -0
- evalrx/analysis/explorer.py +1276 -0
- evalrx/analysis/failure_modes.py +607 -0
- evalrx/analysis/fused_pipeline.py +489 -0
- evalrx/analysis/holdout.py +300 -0
- evalrx/analysis/hypothesis_agent.py +230 -0
- evalrx/analysis/narration.py +177 -0
- evalrx/analysis/operationalize.py +442 -0
- evalrx/analysis/plain_language.py +42 -0
- evalrx/analysis/planner.py +283 -0
- evalrx/analysis/probe_search.py +203 -0
- evalrx/analysis/profile.py +268 -0
- evalrx/analysis/prompts/__init__.py +0 -0
- evalrx/analysis/prompts/explorer.py +417 -0
- evalrx/analysis/prompts/failure_modes.py +33 -0
- evalrx/analysis/prompts/holdout.py +27 -0
- evalrx/analysis/prompts/hypothesis_agent.py +78 -0
- evalrx/analysis/prompts/run_codebase.py +47 -0
- evalrx/analysis/prompts/stats_agent.py +72 -0
- evalrx/analysis/prompts/stats_tool_generator.py +43 -0
- evalrx/analysis/result_marker.py +47 -0
- evalrx/analysis/run_codebase.py +242 -0
- evalrx/analysis/run_view.py +205 -0
- evalrx/analysis/stage_views.py +93 -0
- evalrx/analysis/stats_agent.py +944 -0
- evalrx/analysis/stats_tool_agent.py +261 -0
- evalrx/analysis/stats_tool_generator.py +415 -0
- evalrx/analysis/stats_tools.py +1153 -0
- evalrx/analysis/trajectory_records.py +193 -0
- evalrx/analysis/workbench.py +431 -0
- evalrx/analyzers/__init__.py +42 -0
- evalrx/analyzers/agent/__init__.py +25 -0
- evalrx/analyzers/agent/counterfactual.py +84 -0
- evalrx/analyzers/agent/first_error_judge.py +96 -0
- evalrx/analyzers/agent/ignored_obs.py +81 -0
- evalrx/analyzers/agent/loop_detect.py +79 -0
- evalrx/analyzers/agent/reliability.py +165 -0
- evalrx/analyzers/agent/tool_shap.py +225 -0
- evalrx/analyzers/agent/trajectory_rubric.py +168 -0
- evalrx/analyzers/attention/__init__.py +19 -0
- evalrx/analyzers/attention/relative_attn.py +610 -0
- evalrx/analyzers/attention/rollout.py +73 -0
- evalrx/analyzers/attention/sink.py +56 -0
- evalrx/analyzers/attention/summary.py +190 -0
- evalrx/analyzers/attribution/__init__.py +6 -0
- evalrx/analyzers/attribution/generic_attn.py +31 -0
- evalrx/analyzers/attribution/gradcam.py +30 -0
- evalrx/analyzers/base.py +12 -0
- evalrx/analyzers/geometry/__init__.py +6 -0
- evalrx/analyzers/geometry/cka.py +70 -0
- evalrx/analyzers/geometry/linear_probe.py +157 -0
- evalrx/analyzers/hallucination/__init__.py +9 -0
- evalrx/analyzers/hallucination/chair.py +78 -0
- evalrx/analyzers/hallucination/opera.py +29 -0
- evalrx/analyzers/hallucination/pope.py +119 -0
- evalrx/analyzers/hallucination/selfcheck.py +155 -0
- evalrx/analyzers/hallucination/vcd.py +29 -0
- evalrx/analyzers/lens/__init__.py +7 -0
- evalrx/analyzers/lens/layer_contrast.py +133 -0
- evalrx/analyzers/lens/logit_lens.py +138 -0
- evalrx/analyzers/lens/tuned_lens.py +30 -0
- evalrx/analyzers/patching/__init__.py +5 -0
- evalrx/analyzers/patching/causal_trace.py +30 -0
- evalrx/analyzers/perturbation/__init__.py +23 -0
- evalrx/analyzers/perturbation/_shapley.py +54 -0
- evalrx/analyzers/perturbation/context_shap.py +174 -0
- evalrx/analyzers/perturbation/cot_faithfulness.py +239 -0
- evalrx/analyzers/perturbation/format_sensitivity.py +237 -0
- evalrx/analyzers/perturbation/mm_shap.py +146 -0
- evalrx/analyzers/perturbation/modality_ablation.py +196 -0
- evalrx/analyzers/perturbation/perturbation_battery.py +274 -0
- evalrx/analyzers/perturbation/prompt_contrast.py +265 -0
- evalrx/analyzers/perturbation/rise.py +94 -0
- evalrx/analyzers/perturbation/vl_shap.py +102 -0
- evalrx/analyzers/reasoning/__init__.py +33 -0
- evalrx/analyzers/reasoning/_text.py +328 -0
- evalrx/analyzers/reasoning/answer_extraction_audit.py +327 -0
- evalrx/analyzers/reasoning/arith_audit.py +226 -0
- evalrx/analyzers/reasoning/contamination.py +214 -0
- evalrx/analyzers/reasoning/knowledge_split.py +253 -0
- evalrx/analyzers/reasoning/self_repair.py +246 -0
- evalrx/analyzers/reasoning/step_rollout_value.py +216 -0
- evalrx/analyzers/reasoning/termination_audit.py +258 -0
- evalrx/analyzers/uncertainty/__init__.py +18 -0
- evalrx/analyzers/uncertainty/calibration.py +174 -0
- evalrx/analyzers/uncertainty/coverage_gap.py +199 -0
- evalrx/analyzers/uncertainty/entropy.py +90 -0
- evalrx/analyzers/uncertainty/logprob_entropy.py +69 -0
- evalrx/analyzers/uncertainty/self_consistency.py +204 -0
- evalrx/analyzers/uncertainty/verbalized_conf.py +64 -0
- evalrx/cli.py +411 -0
- evalrx/config.py +77 -0
- evalrx/contract/__init__.py +179 -0
- evalrx/contract/common.py +452 -0
- evalrx/contract/emit.py +948 -0
- evalrx/contract/export.py +237 -0
- evalrx/contract/m1.py +325 -0
- evalrx/contract/m2.py +317 -0
- evalrx/contract/m3.py +165 -0
- evalrx/contract/m4.py +130 -0
- evalrx/contract/m5.py +292 -0
- evalrx/contract/methodology.py +76 -0
- evalrx/contract/pre_m1.py +58 -0
- evalrx/contract/typescript.py +140 -0
- evalrx/core/__init__.py +85 -0
- evalrx/core/analyzer.py +174 -0
- evalrx/core/capability.py +54 -0
- evalrx/core/case.py +443 -0
- evalrx/core/experiment.py +106 -0
- evalrx/core/model.py +198 -0
- evalrx/core/pipeline.py +42 -0
- evalrx/core/registry.py +142 -0
- evalrx/core/result.py +64 -0
- evalrx/core/spec.py +173 -0
- evalrx/core/tokentype.py +165 -0
- evalrx/core/tool.py +92 -0
- evalrx/datasets/__init__.py +41 -0
- evalrx/datasets/base.py +68 -0
- evalrx/datasets/gui_os.py +52 -0
- evalrx/datasets/llm_qa.py +57 -0
- evalrx/datasets/pure_qa.py +12 -0
- evalrx/datasets/vlm_qa.py +695 -0
- evalrx/datasets/web_search_qa.py +52 -0
- evalrx/eval_agent/__init__.py +341 -0
- evalrx/eval_agent/_tools.py +81 -0
- evalrx/eval_agent/ab_runner.py +50 -0
- evalrx/eval_agent/agentic/__init__.py +43 -0
- evalrx/eval_agent/agentic/actions.py +216 -0
- evalrx/eval_agent/agentic/board.py +107 -0
- evalrx/eval_agent/agentic/loop.py +190 -0
- evalrx/eval_agent/agentic/tools.py +538 -0
- evalrx/eval_agent/checkpoint.py +57 -0
- evalrx/eval_agent/cli_agent.py +59 -0
- evalrx/eval_agent/cli_skills.py +5 -0
- evalrx/eval_agent/evolution.py +396 -0
- evalrx/eval_agent/git_manager.py +215 -0
- evalrx/eval_agent/hypothesis.py +172 -0
- evalrx/eval_agent/label_quarantine.py +209 -0
- evalrx/eval_agent/legacy.py +530 -0
- evalrx/eval_agent/log_schema.py +497 -0
- evalrx/eval_agent/loop.py +2159 -0
- evalrx/eval_agent/loop_reports.py +116 -0
- evalrx/eval_agent/model_instrumentation.py +282 -0
- evalrx/eval_agent/narration.py +193 -0
- evalrx/eval_agent/nl_runner.py +460 -0
- evalrx/eval_agent/orchestrator.py +61 -0
- evalrx/eval_agent/preregister.py +93 -0
- evalrx/eval_agent/prompts/__init__.py +1 -0
- evalrx/eval_agent/prompts/agentic.py +46 -0
- evalrx/eval_agent/prompts/case_discovery.py +25 -0
- evalrx/eval_agent/prompts/diagnosis.py +125 -0
- evalrx/eval_agent/prompts/experiment_writer.py +265 -0
- evalrx/eval_agent/prompts/explore_step.py +37 -0
- evalrx/eval_agent/prompts/fix_agent.py +257 -0
- evalrx/eval_agent/prompts/hypothesis_tester.py +15 -0
- evalrx/eval_agent/prompts/nl_runner.py +38 -0
- evalrx/eval_agent/prompts/probe_agent.py +25 -0
- evalrx/eval_agent/prompts/probe_candidate_generator.py +14 -0
- evalrx/eval_agent/prompts/probe_generator.py +35 -0
- evalrx/eval_agent/prompts/whitebox_probe_generator.py +38 -0
- evalrx/eval_agent/report.py +58 -0
- evalrx/eval_agent/run_context.py +354 -0
- evalrx/eval_agent/run_log.schema.json +1215 -0
- evalrx/eval_agent/run_logger_v2.py +1764 -0
- evalrx/eval_agent/run_metadata.py +208 -0
- evalrx/eval_agent/stages/__init__.py +56 -0
- evalrx/eval_agent/stages/case_discovery.py +293 -0
- evalrx/eval_agent/stages/diagnosis.py +1017 -0
- evalrx/eval_agent/stages/experiment_writer.py +1634 -0
- evalrx/eval_agent/stages/fix_agent.py +3916 -0
- evalrx/eval_agent/stages/fix_internals.py +499 -0
- evalrx/eval_agent/stages/fix_pipeline.py +725 -0
- evalrx/eval_agent/stages/fix_tiers.py +187 -0
- evalrx/eval_agent/stages/fix_tools.py +1034 -0
- evalrx/eval_agent/stages/hypothesis_tester.py +1014 -0
- evalrx/eval_agent/stages/probe.py +439 -0
- evalrx/eval_agent/stages/probe_agent.py +1079 -0
- evalrx/eval_agent/stages/probe_candidate_generator.py +128 -0
- evalrx/eval_agent/stages/probe_generator.py +326 -0
- evalrx/eval_agent/stages/probe_search_agent.py +106 -0
- evalrx/eval_agent/stages/protocol.py +112 -0
- evalrx/eval_agent/stages/repair_catalog.py +273 -0
- evalrx/eval_agent/stages/surgery.py +524 -0
- evalrx/eval_agent/stages/whitebox_probe_generator.py +351 -0
- evalrx/eval_agent/store.py +231 -0
- evalrx/logging_utils.py +112 -0
- evalrx/models/__init__.py +161 -0
- evalrx/models/_discover.py +101 -0
- evalrx/models/agent.py +380 -0
- evalrx/models/backends/__init__.py +58 -0
- evalrx/models/backends/api.py +169 -0
- evalrx/models/backends/base.py +57 -0
- evalrx/models/backends/gemini_compat.py +579 -0
- evalrx/models/backends/hf_local.py +2074 -0
- evalrx/models/backends/openai_compat.py +301 -0
- evalrx/models/backends/vllm_offline.py +116 -0
- evalrx/models/base.py +24 -0
- evalrx/models/blackbox/__init__.py +4 -0
- evalrx/models/blackbox/agent.py +31 -0
- evalrx/models/blackbox/base.py +29 -0
- evalrx/models/blackbox/gemini.py +279 -0
- evalrx/models/blackbox/llm_api.py +17 -0
- evalrx/models/blackbox/vlm_api.py +17 -0
- evalrx/models/compose.py +66 -0
- evalrx/models/inference.py +88 -0
- evalrx/models/paper_methods/__init__.py +8 -0
- evalrx/models/paper_methods/aad.py +53 -0
- evalrx/models/paper_methods/ifcd.py +204 -0
- evalrx/models/paper_methods/pai.py +164 -0
- evalrx/models/paper_methods/tcd.py +202 -0
- evalrx/models/paper_methods/vcd.py +45 -0
- evalrx/models/paper_methods/vicrop.py +137 -0
- evalrx/models/toolcodec.py +143 -0
- evalrx/models/tools/__init__.py +20 -0
- evalrx/models/tools/perception.py +300 -0
- evalrx/models/tools/visual.py +174 -0
- evalrx/models/whitebox/__init__.py +26 -0
- evalrx/models/whitebox/agent.py +31 -0
- evalrx/models/whitebox/base.py +24 -0
- evalrx/models/whitebox/qwen.py +61 -0
- evalrx/models/whitebox/qwen2_5_omni.py +29 -0
- evalrx/models/whitebox/qwen2_audio.py +25 -0
- evalrx/models/whitebox/qwen_omni.py +53 -0
- evalrx/models/whitebox/qwen_vl.py +62 -0
- evalrx/observability/__init__.py +21 -0
- evalrx/observability/envelope.py +122 -0
- evalrx/observability/outbox.py +111 -0
- evalrx/observability/tracer.py +882 -0
- evalrx/reporting/__init__.py +28 -0
- evalrx/reporting/case_study.py +947 -0
- evalrx/reporting/compiler.py +587 -0
- evalrx/reporting/dynamic.py +1882 -0
- evalrx/reporting/html_report.py +2225 -0
- evalrx/reporting/langfuse_exporter.py +38 -0
- evalrx/reporting/langfuse_source.py +155 -0
- evalrx/reporting/model.py +151 -0
- evalrx/reporting/run_events.py +184 -0
- evalrx/reporting/server.py +557 -0
- evalrx/reporting/stages.py +58 -0
- evalrx/reporting/static_export.py +142 -0
- evalrx/reporting/web_dist/index.html +146 -0
- evalrx/specs.py +727 -0
- evalrx/stats/__init__.py +47 -0
- evalrx/stats/api.py +192 -0
- evalrx/stats/bootstrap.py +86 -0
- evalrx/stats/ebh.py +27 -0
- evalrx/stats/evalue.py +98 -0
- evalrx/stats/friedman.py +138 -0
- evalrx/stats/mcnemar.py +40 -0
- evalrx/stats/multiplicity.py +159 -0
- evalrx/stats/subset_sampling.py +55 -0
- evalrx/term_links.py +43 -0
- evalrx/viz/__init__.py +7 -0
- evalrx/viz/labels.py +77 -0
- evalrx/viz/prompts.py +39 -0
- evalrx/viz/renderer.py +590 -0
- evalrx/viz/schema.py +36 -0
- evalrx/viz/style.py +134 -0
- evalrx-0.1.2.dist-info/METADATA +532 -0
- evalrx-0.1.2.dist-info/RECORD +339 -0
- evalrx-0.1.2.dist-info/WHEEL +5 -0
- evalrx-0.1.2.dist-info/entry_points.txt +3 -0
- evalrx-0.1.2.dist-info/licenses/LICENSE +121 -0
- evalrx-0.1.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""OpenAI Codex CLI wrapped as a text-generation judge model.
|
|
2
|
+
|
|
3
|
+
The judge receives only the prompt placed in an isolated temporary directory.
|
|
4
|
+
It uses ``codex exec`` in read-only mode; coding-agent uses elsewhere in the
|
|
5
|
+
pipeline are configured independently through :class:`CliAgentConfig`.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import pathlib
|
|
12
|
+
import shutil
|
|
13
|
+
import subprocess
|
|
14
|
+
import tempfile
|
|
15
|
+
import warnings
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CodexModel:
|
|
20
|
+
"""Codex CLI wrapped behind EvalRX' ``generate`` model protocol."""
|
|
21
|
+
|
|
22
|
+
key = "codex"
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
binary_path: str = "",
|
|
27
|
+
timeout_sec: int = 300,
|
|
28
|
+
model: str = "gpt-5.6-terra",
|
|
29
|
+
effort: str = "",
|
|
30
|
+
) -> None:
|
|
31
|
+
from evalrx.core.capability import Capability
|
|
32
|
+
|
|
33
|
+
binary = binary_path or shutil.which("codex") or ""
|
|
34
|
+
if not binary or not os.path.isfile(binary) or not os.access(binary, os.X_OK):
|
|
35
|
+
raise RuntimeError(
|
|
36
|
+
"CodexModel: 'codex' binary not found or not executable. "
|
|
37
|
+
"Set CODEX_PATH=$(which codex) and re-run, or pass binary_path= explicitly."
|
|
38
|
+
)
|
|
39
|
+
self._binary = binary
|
|
40
|
+
self._timeout_sec = timeout_sec
|
|
41
|
+
self._model = model
|
|
42
|
+
self._effort = effort
|
|
43
|
+
self.capabilities = frozenset({Capability.GENERATE})
|
|
44
|
+
self.modalities = frozenset({"text"})
|
|
45
|
+
|
|
46
|
+
def generate(
|
|
47
|
+
self,
|
|
48
|
+
inputs: object,
|
|
49
|
+
*,
|
|
50
|
+
images: "list[Path] | None" = None,
|
|
51
|
+
**kwargs: object,
|
|
52
|
+
) -> str:
|
|
53
|
+
"""Run an isolated, non-interactive Codex turn and return its answer.
|
|
54
|
+
|
|
55
|
+
The prompt is passed over stdin, never as an argv entry, so an M2
|
|
56
|
+
evidence table cannot hit Linux's per-argument size limit. Images are
|
|
57
|
+
copied into the isolated workdir and named in the prompt; this mirrors
|
|
58
|
+
the existing CLI judge contract without exposing the repository.
|
|
59
|
+
"""
|
|
60
|
+
workspace = tempfile.mkdtemp(prefix="codex_judge_")
|
|
61
|
+
output_path = Path(workspace) / "answer.txt"
|
|
62
|
+
try:
|
|
63
|
+
prompt_text = str(inputs)
|
|
64
|
+
valid = [path for path in images or [] if isinstance(path, pathlib.Path) and path.exists()]
|
|
65
|
+
if valid:
|
|
66
|
+
names: list[str] = []
|
|
67
|
+
for path in valid:
|
|
68
|
+
destination = Path(workspace) / path.name
|
|
69
|
+
shutil.copy2(path, destination)
|
|
70
|
+
names.append(destination.name)
|
|
71
|
+
prompt_text = f"Images available in workspace: {', '.join(names)}\n\n{prompt_text}"
|
|
72
|
+
|
|
73
|
+
cmd = [
|
|
74
|
+
self._binary,
|
|
75
|
+
"exec",
|
|
76
|
+
"-",
|
|
77
|
+
"--sandbox", "read-only",
|
|
78
|
+
"--skip-git-repo-check",
|
|
79
|
+
"--ephemeral",
|
|
80
|
+
"--output-last-message", str(output_path),
|
|
81
|
+
"-C", workspace,
|
|
82
|
+
]
|
|
83
|
+
if self._model:
|
|
84
|
+
cmd += ["--model", self._model]
|
|
85
|
+
if self._effort:
|
|
86
|
+
cmd += ["-c", f'model_reasoning_effort="{self._effort}"']
|
|
87
|
+
try:
|
|
88
|
+
proc = subprocess.run(
|
|
89
|
+
cmd,
|
|
90
|
+
input=prompt_text,
|
|
91
|
+
capture_output=True,
|
|
92
|
+
timeout=self._timeout_sec,
|
|
93
|
+
text=True,
|
|
94
|
+
encoding="utf-8",
|
|
95
|
+
errors="replace",
|
|
96
|
+
env={**os.environ},
|
|
97
|
+
)
|
|
98
|
+
except subprocess.TimeoutExpired as exc:
|
|
99
|
+
raise RuntimeError(
|
|
100
|
+
f"CodexModel: codex timed out after {self._timeout_sec}s"
|
|
101
|
+
) from exc
|
|
102
|
+
|
|
103
|
+
output = output_path.read_text(encoding="utf-8", errors="replace").strip() if output_path.is_file() else ""
|
|
104
|
+
if proc.returncode != 0 and not output:
|
|
105
|
+
reason = (proc.stderr or proc.stdout or "").strip()[-500:]
|
|
106
|
+
raise RuntimeError(f"CodexModel: codex exited {proc.returncode}: {reason}")
|
|
107
|
+
if not output:
|
|
108
|
+
warnings.warn(
|
|
109
|
+
"CodexModel: codex returned an empty response; the caller will fall back to a non-LLM path.",
|
|
110
|
+
stacklevel=2,
|
|
111
|
+
)
|
|
112
|
+
return output
|
|
113
|
+
finally:
|
|
114
|
+
shutil.rmtree(workspace, ignore_errors=True)
|
|
115
|
+
|
|
116
|
+
def __repr__(self) -> str:
|
|
117
|
+
return (
|
|
118
|
+
f"CodexModel(binary={self._binary!r}, model={self._model!r}, "
|
|
119
|
+
f"effort={self._effort!r})"
|
|
120
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""CLI coding-provider adapters."""
|
|
2
|
+
|
|
3
|
+
from evalrx.agent_runtime.providers.antigravity import AntigravityAgent
|
|
4
|
+
from evalrx.agent_runtime.providers.base import CliAgentBase
|
|
5
|
+
from evalrx.agent_runtime.providers.claude_code import ClaudeCodeAgent
|
|
6
|
+
from evalrx.agent_runtime.providers.codex import CodexAgent
|
|
7
|
+
from evalrx.agent_runtime.providers.gemini_cli import GeminiCliAgent
|
|
8
|
+
from evalrx.agent_runtime.providers.kimi_cli import KimiCliAgent
|
|
9
|
+
from evalrx.agent_runtime.providers.opencode import OpenCodeAgent
|
|
10
|
+
from evalrx.agent_runtime.providers.registry import create_cli_agent
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"AntigravityAgent",
|
|
14
|
+
"CliAgentBase",
|
|
15
|
+
"ClaudeCodeAgent",
|
|
16
|
+
"CodexAgent",
|
|
17
|
+
"GeminiCliAgent",
|
|
18
|
+
"KimiCliAgent",
|
|
19
|
+
"OpenCodeAgent",
|
|
20
|
+
"create_cli_agent",
|
|
21
|
+
]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Antigravity CLI coding-provider adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from evalrx.agent_runtime.providers.base import CliAgentBase
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AntigravityAgent(CliAgentBase):
|
|
11
|
+
"""Antigravity CLI backend (``agy -p``)."""
|
|
12
|
+
|
|
13
|
+
_provider_name = "antigravity"
|
|
14
|
+
|
|
15
|
+
def _build_cmd(self, prompt: str, workdir: Path) -> list[str]:
|
|
16
|
+
cmd = [
|
|
17
|
+
self._binary,
|
|
18
|
+
"-p",
|
|
19
|
+
prompt,
|
|
20
|
+
# Limit terminal/file access to the explicitly added workdir. A
|
|
21
|
+
# repair author must never inspect benchmark manifests, gold labels,
|
|
22
|
+
# or confirmation outputs while selecting a candidate.
|
|
23
|
+
"--sandbox",
|
|
24
|
+
"--dangerously-skip-permissions",
|
|
25
|
+
"--add-dir",
|
|
26
|
+
str(workdir),
|
|
27
|
+
]
|
|
28
|
+
if self._model:
|
|
29
|
+
cmd += ["--model", self._model]
|
|
30
|
+
cmd.extend(self._extra_args)
|
|
31
|
+
return cmd
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""Base class for CLI coding-provider adapters."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from evalrx.agent_runtime.cli_runtime import ProcessRun, SubprocessRunner, collect_py_files
|
|
9
|
+
from evalrx.agent_runtime.cli_transcript import RAW_OUTPUT_CAP
|
|
10
|
+
from evalrx.agent_runtime.cli_types import CliAgentResult
|
|
11
|
+
from evalrx.agent_runtime.skill_audit import build_agent_audit
|
|
12
|
+
from evalrx.agent_runtime.skills.installer import SkillInstaller
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CliAgentBase:
|
|
18
|
+
"""Subprocess runner shared by all CLI coding providers."""
|
|
19
|
+
|
|
20
|
+
_provider_name: str = "unknown"
|
|
21
|
+
|
|
22
|
+
# Every provider's ``_build_cmd`` places *prompt* directly in argv (``-p
|
|
23
|
+
# <prompt>`` / ``exec <prompt>`` / ``--message <prompt>``). This
|
|
24
|
+
# framework's prompts (a full M1 evidence dump, an L1/L2 fix-candidate
|
|
25
|
+
# brief with per-case tables, ...) can exceed the kernel's argv+envp size
|
|
26
|
+
# limit -- observed in practice as ``OSError: [Errno 7] Argument list
|
|
27
|
+
# too long`` on exec. Above this conservative threshold, ``run()`` spills
|
|
28
|
+
# the prompt to a file in *workdir* instead of passing it inline.
|
|
29
|
+
_LARGE_PROMPT_BYTES = 60_000
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
binary_path: str,
|
|
34
|
+
model: str = "",
|
|
35
|
+
max_budget_usd: float = 5.0,
|
|
36
|
+
timeout_sec: int = 600,
|
|
37
|
+
extra_args: list[str] | None = None,
|
|
38
|
+
skills: list[str] | None = None,
|
|
39
|
+
allow_skills: bool = False,
|
|
40
|
+
) -> None:
|
|
41
|
+
self._binary = binary_path
|
|
42
|
+
self._model = model
|
|
43
|
+
self._max_budget_usd = max_budget_usd
|
|
44
|
+
self._timeout_sec = timeout_sec
|
|
45
|
+
self._extra_args: list[str] = extra_args or []
|
|
46
|
+
self._skills: list[str] = list(skills or [])
|
|
47
|
+
self._allow_skills: bool = bool(allow_skills or self._skills)
|
|
48
|
+
self._runner = SubprocessRunner()
|
|
49
|
+
self._skill_installer = self._make_skill_installer(self._skills)
|
|
50
|
+
|
|
51
|
+
def _make_skill_installer(self, skills: list[str]) -> SkillInstaller:
|
|
52
|
+
return SkillInstaller(skills)
|
|
53
|
+
|
|
54
|
+
def _install_skills(self, workdir: Path) -> None:
|
|
55
|
+
"""Install configured skills into the provider-visible workspace."""
|
|
56
|
+
self._skill_installer.install(workdir)
|
|
57
|
+
|
|
58
|
+
def run(
|
|
59
|
+
self,
|
|
60
|
+
prompt: str,
|
|
61
|
+
workdir: Path,
|
|
62
|
+
timeout_sec: int | None = None,
|
|
63
|
+
) -> CliAgentResult:
|
|
64
|
+
"""Invoke the CLI provider with *prompt* and return collected files."""
|
|
65
|
+
timeout = timeout_sec if timeout_sec is not None else self._timeout_sec
|
|
66
|
+
workdir.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
self._install_skills(workdir)
|
|
68
|
+
cli_prompt = prompt
|
|
69
|
+
if len(prompt.encode("utf-8", errors="replace")) > self._LARGE_PROMPT_BYTES:
|
|
70
|
+
(workdir / "prompt.txt").write_text(prompt, encoding="utf-8")
|
|
71
|
+
cli_prompt = (
|
|
72
|
+
"Your full task instructions are in the file `prompt.txt` in "
|
|
73
|
+
"this workspace (too large to pass inline). Read it "
|
|
74
|
+
"completely and follow it exactly -- do not summarize, "
|
|
75
|
+
"truncate, or skip any part of it."
|
|
76
|
+
)
|
|
77
|
+
cmd = self._build_cmd(cli_prompt, workdir)
|
|
78
|
+
logger.debug("%s: running %s", self._provider_name, cmd[0])
|
|
79
|
+
|
|
80
|
+
run = self._run_subprocess(cmd, workdir, timeout)
|
|
81
|
+
return self._build_result(workdir, run)
|
|
82
|
+
|
|
83
|
+
def _build_cmd(self, prompt: str, workdir: Path) -> list[str]: # pragma: no cover
|
|
84
|
+
raise NotImplementedError
|
|
85
|
+
|
|
86
|
+
def _postprocess_output(self, stdout: str) -> tuple[str, dict | None]:
|
|
87
|
+
return stdout[:RAW_OUTPUT_CAP], None
|
|
88
|
+
|
|
89
|
+
def _run_subprocess(self, cmd: list[str], workdir: Path, timeout_sec: int) -> ProcessRun:
|
|
90
|
+
return self._runner.run(cmd, workdir, timeout_sec)
|
|
91
|
+
|
|
92
|
+
def _build_result(self, workdir: Path, run: ProcessRun) -> CliAgentResult:
|
|
93
|
+
files = collect_py_files(workdir)
|
|
94
|
+
error: str | None = None
|
|
95
|
+
if run.timed_out:
|
|
96
|
+
error = f"[TIMEOUT] agent killed after {run.elapsed_sec:.0f}s"
|
|
97
|
+
elif run.returncode != 0 and not files:
|
|
98
|
+
error = f"Exited {run.returncode}: {run.stderr[:500]}"
|
|
99
|
+
|
|
100
|
+
# Persist the UNTRUNCATED raw agent stream before any rendering caps
|
|
101
|
+
# apply — this is the most detailed audit layer (every tool call and
|
|
102
|
+
# tool result). The truncated rendering stays in CliAgentResult.raw_output
|
|
103
|
+
# for UIs; the full stream is durable evidence in the workdir.
|
|
104
|
+
raw_stream_path = ""
|
|
105
|
+
if run.stdout:
|
|
106
|
+
try:
|
|
107
|
+
stream_file = workdir / "agent_raw_stream.txt"
|
|
108
|
+
stream_file.write_text(run.stdout, encoding="utf-8")
|
|
109
|
+
raw_stream_path = "agent_raw_stream.txt"
|
|
110
|
+
except OSError:
|
|
111
|
+
logger.debug("could not persist raw agent stream for %s", self._provider_name)
|
|
112
|
+
|
|
113
|
+
raw_output, usage = self._postprocess_output(run.stdout)
|
|
114
|
+
audit = build_agent_audit(
|
|
115
|
+
provider=self._provider_name,
|
|
116
|
+
skills=self._skills,
|
|
117
|
+
workdir=workdir,
|
|
118
|
+
stdout=run.stdout,
|
|
119
|
+
stderr=run.stderr,
|
|
120
|
+
returncode=run.returncode,
|
|
121
|
+
timed_out=run.timed_out,
|
|
122
|
+
elapsed_sec=run.elapsed_sec,
|
|
123
|
+
files=files,
|
|
124
|
+
error=error,
|
|
125
|
+
)
|
|
126
|
+
if audit is not None and raw_stream_path:
|
|
127
|
+
audit.setdefault("execution", {})["raw_stream"] = raw_stream_path
|
|
128
|
+
logger.debug(
|
|
129
|
+
"%s: rc=%d files=%s elapsed=%.1fs timed_out=%s",
|
|
130
|
+
self._provider_name,
|
|
131
|
+
run.returncode,
|
|
132
|
+
list(files),
|
|
133
|
+
run.elapsed_sec,
|
|
134
|
+
run.timed_out,
|
|
135
|
+
)
|
|
136
|
+
return CliAgentResult(
|
|
137
|
+
files=files,
|
|
138
|
+
provider_name=self._provider_name,
|
|
139
|
+
elapsed_sec=run.elapsed_sec,
|
|
140
|
+
raw_output=raw_output,
|
|
141
|
+
usage=usage,
|
|
142
|
+
error=error,
|
|
143
|
+
audit=audit,
|
|
144
|
+
raw_stream_path=raw_stream_path,
|
|
145
|
+
)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Claude Code CLI coding-provider adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from evalrx.agent_runtime.cli_transcript import render_claude_stream
|
|
8
|
+
from evalrx.agent_runtime.providers.base import CliAgentBase
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ClaudeCodeAgent(CliAgentBase):
|
|
12
|
+
"""Claude Code CLI backend (``claude -p``)."""
|
|
13
|
+
|
|
14
|
+
_provider_name = "claude_code"
|
|
15
|
+
|
|
16
|
+
def _build_cmd(self, prompt: str, workdir: Path) -> list[str]:
|
|
17
|
+
# No --dangerously-skip-permissions. The narrow grant below already says
|
|
18
|
+
# exactly what this agent may do, so the blanket flag added nothing --
|
|
19
|
+
# and it is refused outright under root unless something sets
|
|
20
|
+
# IS_SANDBOX=1, which our compose files do and a bare root box (Colab, a
|
|
21
|
+
# plain container, CI) does not. There the coder exited in 0.7s having
|
|
22
|
+
# written no files, ExperimentWriter aborted for want of a .py, and every
|
|
23
|
+
# L2-and-above repair candidate was dead on arrival while the log said
|
|
24
|
+
# only "CLI agent produced no .py files".
|
|
25
|
+
#
|
|
26
|
+
# Same reasoning as the judge in agent_runtime/judges/claude.py: ask for
|
|
27
|
+
# the tools the work needs, not for every permission there is.
|
|
28
|
+
allowed = "Bash Edit Write Read" + (" Skill" if self._allow_skills else "")
|
|
29
|
+
cmd = [
|
|
30
|
+
self._binary,
|
|
31
|
+
"-p",
|
|
32
|
+
prompt,
|
|
33
|
+
"--output-format",
|
|
34
|
+
"stream-json",
|
|
35
|
+
"--verbose",
|
|
36
|
+
"--allowed-tools",
|
|
37
|
+
allowed,
|
|
38
|
+
"--add-dir",
|
|
39
|
+
str(workdir),
|
|
40
|
+
]
|
|
41
|
+
if self._model:
|
|
42
|
+
cmd += ["--model", self._model]
|
|
43
|
+
if self._max_budget_usd:
|
|
44
|
+
cmd += ["--max-budget-usd", str(self._max_budget_usd)]
|
|
45
|
+
cmd.extend(self._extra_args)
|
|
46
|
+
return cmd
|
|
47
|
+
|
|
48
|
+
def _postprocess_output(self, stdout: str) -> tuple[str, dict | None]:
|
|
49
|
+
return render_claude_stream(stdout)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""OpenAI Codex CLI coding-provider adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from evalrx.agent_runtime.providers.base import CliAgentBase
|
|
8
|
+
from evalrx.agent_runtime.skills.installer import CodexSkillInstaller, SkillInstaller
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CodexAgent(CliAgentBase):
|
|
12
|
+
"""OpenAI Codex CLI backend (``codex exec``)."""
|
|
13
|
+
|
|
14
|
+
_provider_name = "codex"
|
|
15
|
+
|
|
16
|
+
def _make_skill_installer(self, skills: list[str]) -> SkillInstaller:
|
|
17
|
+
return CodexSkillInstaller(skills)
|
|
18
|
+
|
|
19
|
+
def _build_cmd(self, prompt: str, workdir: Path) -> list[str]:
|
|
20
|
+
cmd = [
|
|
21
|
+
self._binary,
|
|
22
|
+
"exec",
|
|
23
|
+
prompt,
|
|
24
|
+
# Generated-code workspaces are intentionally isolated artifact
|
|
25
|
+
# directories, not Git checkouts. Codex otherwise exits before
|
|
26
|
+
# doing any work with "Not inside a trusted directory".
|
|
27
|
+
"--skip-git-repo-check",
|
|
28
|
+
"--sandbox",
|
|
29
|
+
"workspace-write",
|
|
30
|
+
"--json",
|
|
31
|
+
"-C",
|
|
32
|
+
str(workdir),
|
|
33
|
+
]
|
|
34
|
+
if self._model:
|
|
35
|
+
cmd += ["-m", self._model]
|
|
36
|
+
cmd.extend(self._extra_args)
|
|
37
|
+
return cmd
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Gemini CLI coding-provider adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from evalrx.agent_runtime.providers.base import CliAgentBase
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GeminiCliAgent(CliAgentBase):
|
|
11
|
+
"""Gemini CLI backend (``gemini -p``)."""
|
|
12
|
+
|
|
13
|
+
_provider_name = "gemini_cli"
|
|
14
|
+
|
|
15
|
+
def _build_cmd(self, prompt: str, workdir: Path) -> list[str]:
|
|
16
|
+
cmd = [
|
|
17
|
+
self._binary,
|
|
18
|
+
"-p",
|
|
19
|
+
prompt,
|
|
20
|
+
"--cwd",
|
|
21
|
+
str(workdir),
|
|
22
|
+
]
|
|
23
|
+
if self._model:
|
|
24
|
+
cmd += ["--model", self._model]
|
|
25
|
+
cmd.extend(self._extra_args)
|
|
26
|
+
return cmd
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Kimi CLI coding-provider adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from evalrx.agent_runtime.providers.base import CliAgentBase
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class KimiCliAgent(CliAgentBase):
|
|
11
|
+
"""Kimi CLI backend (``kimi chat``)."""
|
|
12
|
+
|
|
13
|
+
_provider_name = "kimi_cli"
|
|
14
|
+
|
|
15
|
+
def _build_cmd(self, prompt: str, workdir: Path) -> list[str]:
|
|
16
|
+
cmd = [
|
|
17
|
+
self._binary,
|
|
18
|
+
"chat",
|
|
19
|
+
"--message",
|
|
20
|
+
prompt,
|
|
21
|
+
"--workdir",
|
|
22
|
+
str(workdir),
|
|
23
|
+
]
|
|
24
|
+
if self._model:
|
|
25
|
+
cmd += ["--model", self._model]
|
|
26
|
+
cmd.extend(self._extra_args)
|
|
27
|
+
return cmd
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""OpenCode CLI coding-provider adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from evalrx.agent_runtime.providers.base import CliAgentBase
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OpenCodeAgent(CliAgentBase):
|
|
11
|
+
"""OpenCode CLI backend (``opencode run``)."""
|
|
12
|
+
|
|
13
|
+
_provider_name = "opencode"
|
|
14
|
+
|
|
15
|
+
def _build_cmd(self, prompt: str, workdir: Path) -> list[str]:
|
|
16
|
+
cmd = [
|
|
17
|
+
self._binary,
|
|
18
|
+
"run",
|
|
19
|
+
"--message",
|
|
20
|
+
prompt,
|
|
21
|
+
"--cwd",
|
|
22
|
+
str(workdir),
|
|
23
|
+
]
|
|
24
|
+
if self._model:
|
|
25
|
+
cmd += ["--model", self._model]
|
|
26
|
+
cmd.extend(self._extra_args)
|
|
27
|
+
return cmd
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Provider registry and factory for CLI coding agents."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import shutil
|
|
6
|
+
|
|
7
|
+
from evalrx.agent_runtime.cli_types import BINARY_DEFAULTS, CliAgentConfig
|
|
8
|
+
from evalrx.agent_runtime.providers.antigravity import AntigravityAgent
|
|
9
|
+
from evalrx.agent_runtime.providers.base import CliAgentBase
|
|
10
|
+
from evalrx.agent_runtime.providers.claude_code import ClaudeCodeAgent
|
|
11
|
+
from evalrx.agent_runtime.providers.codex import CodexAgent
|
|
12
|
+
from evalrx.agent_runtime.providers.gemini_cli import GeminiCliAgent
|
|
13
|
+
from evalrx.agent_runtime.providers.kimi_cli import KimiCliAgent
|
|
14
|
+
from evalrx.agent_runtime.providers.opencode import OpenCodeAgent
|
|
15
|
+
|
|
16
|
+
PROVIDER_CLASSES: dict[str, type[CliAgentBase]] = {
|
|
17
|
+
"claude_code": ClaudeCodeAgent,
|
|
18
|
+
"codex": CodexAgent,
|
|
19
|
+
"opencode": OpenCodeAgent,
|
|
20
|
+
"gemini_cli": GeminiCliAgent,
|
|
21
|
+
"kimi_cli": KimiCliAgent,
|
|
22
|
+
"antigravity": AntigravityAgent,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def create_cli_agent(config: CliAgentConfig) -> CliAgentBase:
|
|
27
|
+
"""Instantiate the appropriate CLI coding provider for *config*."""
|
|
28
|
+
provider = config.provider
|
|
29
|
+
|
|
30
|
+
if provider == "llm":
|
|
31
|
+
raise ValueError(
|
|
32
|
+
"'llm' is not a CLI provider. Use ExperimentWriter directly "
|
|
33
|
+
"(leave cli_agent=None or CliAgentConfig(provider='llm'))."
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
cls = PROVIDER_CLASSES.get(provider)
|
|
37
|
+
if cls is None:
|
|
38
|
+
raise ValueError(
|
|
39
|
+
f"Unknown CLI provider: {provider!r}. Valid: {sorted(PROVIDER_CLASSES)}"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
binary = config.binary_path or shutil.which(BINARY_DEFAULTS[provider]) or ""
|
|
43
|
+
if not binary:
|
|
44
|
+
raise RuntimeError(
|
|
45
|
+
f"CLI agent binary for {provider!r} not found in PATH. "
|
|
46
|
+
f"Install '{BINARY_DEFAULTS[provider]}' or pass "
|
|
47
|
+
f"CliAgentConfig(binary_path='/path/to/{BINARY_DEFAULTS[provider]}')."
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
return cls(
|
|
51
|
+
binary_path=binary,
|
|
52
|
+
model=config.model,
|
|
53
|
+
max_budget_usd=config.max_budget_usd,
|
|
54
|
+
timeout_sec=config.timeout_sec,
|
|
55
|
+
extra_args=list(config.extra_args),
|
|
56
|
+
skills=list(config.skills),
|
|
57
|
+
allow_skills=config.allow_skills,
|
|
58
|
+
)
|