coderouter-cli 2.7.8__tar.gz → 2.7.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/CHANGELOG.md +95 -1
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/PKG-INFO +1 -1
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/adapters/agent_cli.py +218 -21
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/config/schemas.py +15 -6
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/external-agents.en.md +110 -10
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/external-agents.md +110 -10
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/designs/external-agents-adapter.md +20 -1
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/examples/providers-agent-cli.yaml +64 -32
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/pyproject.toml +1 -1
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_agent_cli.py +382 -12
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/.gitignore +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/LICENSE +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/README.en.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/README.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/__init__.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/__main__.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/adapters/__init__.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/adapters/anthropic_native.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/adapters/base.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/adapters/openai_compat.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/adapters/registry.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/cli.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/cli_stats.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/config/__init__.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/config/capability_registry.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/config/env_file.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/config/loader.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/cost.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/data/__init__.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/data/model-capabilities.yaml +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/doctor.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/doctor_apply.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/env_security.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/errors.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/gguf_introspect.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/guards/__init__.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/guards/_fingerprint.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/guards/backend_health.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/guards/context_budget.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/guards/continuous_probe.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/guards/drift_actions.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/guards/drift_detection.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/guards/memory_budget.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/guards/memory_pressure.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/guards/self_healing.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/guards/tool_loop.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/hardware.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/ingress/__init__.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/ingress/anthropic_routes.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/ingress/app.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/ingress/dashboard_routes.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/ingress/launcher_routes.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/ingress/metrics_routes.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/ingress/openai_routes.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/language_tax.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/launcher_speculative.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/logging.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/metrics/__init__.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/metrics/collector.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/metrics/prometheus.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/output_filters.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/plugins/__init__.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/plugins/base.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/plugins/loader.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/plugins/registry.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/routing/__init__.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/routing/adaptive.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/routing/auto_router.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/routing/budget.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/routing/capability.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/routing/fallback.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/state/__init__.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/state/audit_log.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/state/replay.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/state/request_log.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/state/store.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/state/suggest_rules.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/token_estimation.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/token_estimation_accurate.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/translation/__init__.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/translation/anthropic.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/translation/convert.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/translation/tool_repair.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/README.en.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/README.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/assets/dashboard-demo.png +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/claude-code-llamacpp-vllm.en.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/claude-code-llamacpp-vllm.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/gguf_dl.en.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/gguf_dl.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/hf-ollama-models.en.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/hf-ollama-models.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/install-backends.en.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/install-backends.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/launcher-quickstart.en.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/launcher-quickstart.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/launcher.en.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/launcher.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/llamacpp-direct.en.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/llamacpp-direct.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/lmstudio-direct.en.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/lmstudio-direct.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/verify-ollama-0.23.1.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/concepts/architecture.en.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/concepts/architecture.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/concepts/context-budget.en.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/concepts/context-budget.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/concepts/continuous-probing.en.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/concepts/continuous-probing.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/concepts/drift-detection.en.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/concepts/drift-detection.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/concepts/partial-stitch.en.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/concepts/partial-stitch.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/designs/v1.5-dashboard-mockup.html +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/designs/v1.6-auto-router-verification.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/designs/v1.6-auto-router.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/guides/free-tier-guide.en.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/guides/free-tier-guide.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/guides/language-tax.en.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/guides/language-tax.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/guides/remote-access.en.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/guides/remote-access.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/guides/security.en.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/guides/security.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/guides/troubleshooting.en.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/guides/troubleshooting.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/guides/usage-guide.en.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/guides/usage-guide.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/low-memory-integration.en.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/low-memory-integration.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/openrouter-roster/CHANGES.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/openrouter-roster/README.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/openrouter-roster/latest.json +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/retrospectives/v0.4.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/retrospectives/v0.5-verify.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/retrospectives/v0.5.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/retrospectives/v0.6.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/retrospectives/v0.7.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/retrospectives/v1.0-verify.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/retrospectives/v1.0.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/start/quickstart.en.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/start/quickstart.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/start/when-do-i-need-coderouter.en.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/start/when-do-i-need-coderouter.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/examples/.env.example +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/examples/README.md +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/examples/providers-multiagent.yaml +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/examples/providers.context-budget-test.yaml +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/examples/providers.llamacpp-vllm.yaml +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/examples/providers.nvidia-nim.yaml +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/examples/providers.ollama-auto-custom.yaml +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/examples/providers.ollama-auto.yaml +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/examples/providers.ollama-free-chain.yaml +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/examples/providers.raspberrypi.yaml +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/examples/providers.yaml +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/scripts/demo_traffic.sh +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/scripts/openrouter_roster_diff.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/scripts/smoke_v2_2.sh +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/scripts/verify-providers.yaml +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/scripts/verify_ollama_0_23.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/scripts/verify_v0_5.sh +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/scripts/verify_v1_0.sh +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/__init__.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/conftest.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_adapter_anthropic.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_audit_log.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_auto_router.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_auto_router_cjk.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_backend_health.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_budget.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_capability.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_capability_degraded_payload.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_capability_registry.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_capability_registry_cache_control.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_capability_tool_choice.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_claude_code_suitability.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_cli.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_cli_bind_warning.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_cli_stats.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_config.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_config_shim_actions.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_context_budget.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_continuous_probe.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_dashboard_endpoint.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_doctor.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_doctor_apply.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_doctor_cache_probe.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_drift_actions.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_drift_detection.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_drift_detection_integration.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_env_file.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_env_security.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_errors.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_examples_yaml.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fallback.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fallback_anthropic.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fallback_cache_control.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fallback_cache_observed.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fallback_cache_strip.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fallback_empty_response.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fallback_misconfig_warn.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fallback_paid_gate.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fallback_thinking.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fallback_tool_choice_emulate.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fix_h1_prometheus_drift.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fix_h2_h6_translation.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fix_h3_client_reuse.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fix_h4_h5_guards.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fix_h7_probe_restore.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fix_h8_launcher_auth.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fix_mA_routing.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fix_mB_adapters.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fix_mC_convert.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fix_mD_logging_metrics.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fix_mE_config_validation.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fix_mF_ingress.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_gguf_introspect.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_guards_tool_loop.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_hardware.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_ingress_anthropic.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_ingress_count_tokens.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_ingress_profile.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_language_tax.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_language_tax_integration.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_launcher_mtp.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_launcher_mtp_fallback.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_launcher_provider_sync.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_memory_budget.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_memory_pressure.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_metrics_cache.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_metrics_collector.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_metrics_cost.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_metrics_endpoint.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_metrics_jsonl.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_metrics_prometheus.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_metrics_prometheus_cache.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_models_passthrough.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_openai_compat.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_openrouter_roster_diff.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_output_filters.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_output_filters_adapters.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_partial_stitch.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_plugins_integration.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_plugins_loader.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_plugins_registry.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_reasoning_strip.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_repair_byte_fallback.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_request_log.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_role_normalization.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_routing_adaptive.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_self_healing.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_setup_sh.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_state_store.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_token_estimation.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_token_estimation_accurate.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_tokens_saved_metric.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_tool_repair.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_tool_repair_lenient.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_tool_repair_r4.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_toolrepair_bench.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_translation_anthropic.py +0 -0
- {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_translation_reverse.py +0 -0
|
@@ -9,9 +9,103 @@ are kept verbatim where the Japanese text itself is the subject).
|
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## [v2.7.9] — 2026-07-11 (agent_cli Phase 1b: codex)
|
|
13
|
+
|
|
14
|
+
Adds the third target to the `agent_cli` adapter: `agent: codex` (the OpenAI
|
|
15
|
+
Codex CLI) is now implemented alongside `claude` (Phase 1a) and `grok`
|
|
16
|
+
(Phase 1d). The implementation was verified against the real CLI (codex-cli
|
|
17
|
+
0.144.1, 2026-07-11), and one design-time assumption from the 2026-07
|
|
18
|
+
research did not survive contact with it: `codex exec` has no approval flag
|
|
19
|
+
at all (`-a`/`--ask-for-approval` doesn't exist in `exec --help` on
|
|
20
|
+
0.144.1, since non-interactive execution has no approval prompt to control
|
|
21
|
+
in the first place), so the design's separate `edit`/`full_auto` mappings
|
|
22
|
+
(`-a on-request` / `-a never`) collapse into a single `-s workspace-write`
|
|
23
|
+
mapping — unlike claude/grok, codex does not distinguish `edit` from
|
|
24
|
+
`full_auto`. The verified JSONL `turn.completed.usage` payload also carries
|
|
25
|
+
a field the design didn't record, `reasoning_output_tokens`, which is now
|
|
26
|
+
normalized defensively into `completion_tokens_details.reasoning_tokens`.
|
|
27
|
+
Everything else (stdin prompt delivery, `--skip-git-repo-check`,
|
|
28
|
+
`--ephemeral`, the absence of `--max-turns`/`--timeout`, the ~8-day OAuth
|
|
29
|
+
staleness) matched the design closely. `gemini` remains schema-declared but
|
|
30
|
+
rejected (Phase 1c pending).
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- **`agent: codex` builder/parser in `AgentCliAdapter`**
|
|
35
|
+
(`coderouter/adapters/agent_cli.py`) — invokes the Codex CLI one-shot as
|
|
36
|
+
`codex exec --json --skip-git-repo-check --ephemeral -m <model>
|
|
37
|
+
-C <workdir> -s read-only -` (read_only default). The prompt is delivered
|
|
38
|
+
on stdin with an explicit trailing `-` sentinel — the same channel as
|
|
39
|
+
claude, unlike grok's `--prompt-file` delivery. Output is JSONL on
|
|
40
|
+
stdout (progress goes to stderr); the final answer is the `item.text` of
|
|
41
|
+
the **last** `item.completed` event whose `item.type=="agent_message"`.
|
|
42
|
+
Usage comes from `turn.completed.usage` and is normalized as
|
|
43
|
+
`prompt_tokens=input_tokens` / `completion_tokens=output_tokens`, with
|
|
44
|
+
`cached_input_tokens` kept as `prompt_tokens_details.cached_tokens` (a
|
|
45
|
+
subset of `input_tokens`, not added on top — verified 13810 ⊃ 9984) and
|
|
46
|
+
`reasoning_output_tokens` kept as
|
|
47
|
+
`completion_tokens_details.reasoning_tokens` when nonzero; multiple
|
|
48
|
+
`turn.completed` events, if seen, are summed. `thread_id` (from
|
|
49
|
+
`thread.started`) surfaces as `coderouter_session_id`. Parsing is
|
|
50
|
+
defensive line-by-line — a non-JSON line doesn't abort the rest of the
|
|
51
|
+
stream, but no valid `agent_message` (or empty stdout) raises a
|
|
52
|
+
retryable `AdapterError`, as does any `error` event or `turn.failed`.
|
|
53
|
+
The CLI is pre-1.0 and `--json`'s alias is still `--experimental-json`
|
|
54
|
+
(schema not frozen), so version pinning is recommended alongside the
|
|
55
|
+
defensive parsing.
|
|
56
|
+
- **`sandbox_mode` → codex flag mapping** — `read_only` → `-s read-only`;
|
|
57
|
+
`edit` → `-s workspace-write`; `full_auto` → `-s workspace-write` (same
|
|
58
|
+
as `edit` — `exec` has no approval flag in 0.144.1, and
|
|
59
|
+
`--dangerously-bypass-approvals-and-sandbox` is never used). As with
|
|
60
|
+
claude/grok, the effective mode is clamped to read-only whenever
|
|
61
|
+
`allow_file_writes=false`, regardless of `sandbox_mode`.
|
|
62
|
+
- **`--skip-git-repo-check` always passed** — CodeRouter's isolated workdir
|
|
63
|
+
is never a git repository, and codex's default "trusted directory" check
|
|
64
|
+
fails immediately outside one (exit 1, stderr `Not inside a trusted
|
|
65
|
+
directory and --skip-git-repo-check was not specified.` — field-verified).
|
|
66
|
+
The adapter always passes this flag, so that error should never surface
|
|
67
|
+
in normal operation.
|
|
68
|
+
- **`--ephemeral` always passed** — prevents the session from persisting to
|
|
69
|
+
disk, for the same stateless-transformation rationale as grok's
|
|
70
|
+
`--no-memory`. Not configurable.
|
|
71
|
+
- **`max_turns` ignored for codex** — `codex exec` has neither
|
|
72
|
+
`--max-turns` nor `--timeout`; `AgentCliConfig.max_turns` has no effect
|
|
73
|
+
when `agent: codex`, and `exec_timeout_s` + process-group `SIGKILL` is
|
|
74
|
+
the only time bound.
|
|
75
|
+
- **`coderouter_session_id` via `thread_id`** — same response-metadata
|
|
76
|
+
treatment as claude's `session_id` and grok's `sessionId`.
|
|
77
|
+
|
|
78
|
+
### Changed
|
|
79
|
+
|
|
80
|
+
- **Unsupported-agent error message** — constructing the adapter with a
|
|
81
|
+
not-yet-implemented agent now lists all three implemented agents (e.g.
|
|
82
|
+
`agent 'gemini' is not implemented yet (implemented: claude, codex,
|
|
83
|
+
grok)`) instead of the two-agent wording from v2.7.8.
|
|
84
|
+
- **Schema docstrings** (`coderouter/config/schemas.py`) — `AgentCliConfig`
|
|
85
|
+
field descriptions updated for the three-agent reality, including the
|
|
86
|
+
`max_turns` docstring noting codex ignores it.
|
|
87
|
+
- **Docs & examples** — `docs/backends/external-agents.md`/`.en.md` gain a
|
|
88
|
+
full codex section (config example, adapter argv, stdin-vs-`--prompt-file`
|
|
89
|
+
contrast with grok, sandbox mapping table with the no-approval-flag-in-exec
|
|
90
|
+
note, JSONL schema and usage normalization including
|
|
91
|
+
`reasoning_output_tokens`, OAuth setup with the ~8-day staleness caveat
|
|
92
|
+
and `CODEX_API_KEY`/`OPENAI_API_KEY`/`CODEX_HOME`, and the pre-1.0
|
|
93
|
+
version-pin caveat) and updated implementation-status wording (claude +
|
|
94
|
+
codex + grok implemented; gemini-only rejected). The design doc
|
|
95
|
+
`docs/designs/external-agents-adapter.md` gets a new appendix §11.4
|
|
96
|
+
recording the verified deltas between the 2026-07 research and
|
|
97
|
+
codex-cli 0.144.1 (body text left as the historical record); its header
|
|
98
|
+
status line now lists Phase 1b alongside 1a/1d.
|
|
99
|
+
`examples/providers-agent-cli.yaml` promotes codex out of the
|
|
100
|
+
not-implemented preview into a documented (still commented-out, so
|
|
101
|
+
copying the file doesn't require codex to be installed) block with
|
|
102
|
+
`model: gpt-5.5`, `paid: false` for subscription OAuth, and
|
|
103
|
+
`passthrough_env: []`; the preview section at the bottom is retitled to
|
|
104
|
+
Phase 1c (gemini only), with codex removed from it.
|
|
105
|
+
|
|
12
106
|
## [v2.7.8] — 2026-07-10 (agent_cli Phase 1d: grok)
|
|
13
107
|
|
|
14
|
-
Adds the second target to the `agent_cli` adapter: `agent: grok` (the xAI
|
|
108
|
+
**PR #68**. Adds the second target to the `agent_cli` adapter: `agent: grok` (the xAI
|
|
15
109
|
Grok CLI) is now implemented alongside `claude` from Phase 1a. The
|
|
16
110
|
implementation was verified against the real CLI (grok v0.2.93, [stable]
|
|
17
111
|
channel, 2026-07-10), and several design-time assumptions from the 2026-07
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: coderouter-cli
|
|
3
|
-
Version: 2.7.
|
|
3
|
+
Version: 2.7.9
|
|
4
4
|
Summary: Local-first, free-first, fallback-built-in LLM router. Claude Code / OpenAI compatible.
|
|
5
5
|
Project-URL: Homepage, https://github.com/zephel01/CodeRouter
|
|
6
6
|
Project-URL: Repository, https://github.com/zephel01/CodeRouter
|
|
@@ -18,15 +18,24 @@ CodeRouter merely performs the one conversion. Following the
|
|
|
18
18
|
``openai_compat`` precedent, a *single* adapter class fronts *multiple*
|
|
19
19
|
target agents, dispatched on the ``agent`` field.
|
|
20
20
|
|
|
21
|
-
Implemented agents (Phase 1a + 1d)
|
|
22
|
-
|
|
21
|
+
Implemented agents (Phase 1a + 1b + 1d)
|
|
22
|
+
========================================
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
Three targets are implemented here:
|
|
25
25
|
|
|
26
26
|
* ``claude`` (Claude Code CLI, Phase 1a) — the most stable CLI, the most
|
|
27
27
|
fine-grained safety controls, and the only one that emits
|
|
28
28
|
``total_cost_usd`` directly (making it the reference implementation for
|
|
29
29
|
the parser / cost path).
|
|
30
|
+
* ``codex`` (codex CLI, Phase 1b) — headless one-shot via ``codex exec
|
|
31
|
+
--json``, prompt on stdin (like claude). The CLI is pre-1.0 and emits
|
|
32
|
+
defensively-parsed JSONL (one event per line); usage comes from
|
|
33
|
+
``turn.completed`` events, normalized per design §5.1.6 with
|
|
34
|
+
``cached_input_tokens`` kept as a *subset* of ``input_tokens`` (unlike
|
|
35
|
+
claude, which folds its cache buckets into ``prompt_tokens``).
|
|
36
|
+
``--skip-git-repo-check`` and ``--ephemeral`` are always passed — the
|
|
37
|
+
isolated workdir is not a git repo, and the adapter never wants
|
|
38
|
+
session persistence.
|
|
30
39
|
* ``grok`` (grok CLI, Phase 1d) — headless one-shot via ``--prompt-file`` +
|
|
31
40
|
``--output-format json``. The CLI emits no token/cost figures, so usage
|
|
32
41
|
is reported as zeros (cost stays 0 unless the operator sets
|
|
@@ -34,9 +43,9 @@ Two targets are implemented here:
|
|
|
34
43
|
so a user-level cross-session memory setting cannot leak state between
|
|
35
44
|
requests.
|
|
36
45
|
|
|
37
|
-
The remaining
|
|
38
|
-
providers.yaml is forward-compatible, but constructing an adapter for
|
|
39
|
-
raises a clear ``AdapterError`` until
|
|
46
|
+
The remaining agent (gemini) is declared in the config schema so
|
|
47
|
+
providers.yaml is forward-compatible, but constructing an adapter for it
|
|
48
|
+
raises a clear ``AdapterError`` until its phase (1c) lands.
|
|
40
49
|
|
|
41
50
|
Security (design §6, non-negotiable)
|
|
42
51
|
====================================
|
|
@@ -44,10 +53,11 @@ Security (design §6, non-negotiable)
|
|
|
44
53
|
* **allowlist argv only** — the child is launched with
|
|
45
54
|
:func:`asyncio.create_subprocess_exec` and a list argv. ``shell=True`` is
|
|
46
55
|
never used; the prompt is never subject to shell interpretation (claude
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
56
|
+
and codex read it from stdin — codex's argv carries a trailing ``-``
|
|
57
|
+
sentinel making that explicit; grok reads it from a private ``0600``
|
|
58
|
+
prompt file inside the resolved workdir — its ``-p`` requires the prompt
|
|
59
|
+
as an argv value, and argv would both hit Linux's ~128KiB
|
|
60
|
+
``MAX_ARG_STRLEN`` on huge prompts and leak the text into ``ps`` output).
|
|
51
61
|
* **default read-only** — ``allow_file_writes=False`` /
|
|
52
62
|
``sandbox_mode="read_only"`` are the defaults, mapped to claude's
|
|
53
63
|
``--permission-mode plan``. Writes require explicit opt-in and the sandbox
|
|
@@ -127,6 +137,17 @@ _GROK_SANDBOX_ARGS = {
|
|
|
127
137
|
"full_auto": ["--sandbox", "workspace", "--always-approve"],
|
|
128
138
|
}
|
|
129
139
|
|
|
140
|
+
# sandbox_mode → codex ``-s/--sandbox`` value (design §5.4, codex-cli
|
|
141
|
+
# 0.144.1, verified via facts-codex.md). ``codex exec`` has NO approval
|
|
142
|
+
# flag at all (non-interactive, so there is no prompt to approve/skip), so
|
|
143
|
+
# ``full_auto`` collapses onto the same ``workspace-write`` value as
|
|
144
|
+
# ``edit`` — there is nothing further to "auto" beyond granting writes.
|
|
145
|
+
_CODEX_SANDBOX_ARGS = {
|
|
146
|
+
"read_only": ["-s", "read-only"],
|
|
147
|
+
"edit": ["-s", "workspace-write"],
|
|
148
|
+
"full_auto": ["-s", "workspace-write"],
|
|
149
|
+
}
|
|
150
|
+
|
|
130
151
|
|
|
131
152
|
def _chunk_text(text: str, size: int = _STREAM_CHUNK_CHARS) -> Iterator[str]:
|
|
132
153
|
"""Split ``text`` into ``size``-char pieces for the pseudo-stream."""
|
|
@@ -135,7 +156,7 @@ def _chunk_text(text: str, size: int = _STREAM_CHUNK_CHARS) -> Iterator[str]:
|
|
|
135
156
|
|
|
136
157
|
|
|
137
158
|
class AgentCliAdapter(BaseAdapter):
|
|
138
|
-
"""Invoke an external coding-agent CLI one-shot (claude + grok).
|
|
159
|
+
"""Invoke an external coding-agent CLI one-shot (claude + codex + grok).
|
|
139
160
|
|
|
140
161
|
The ``agent`` field selects the argv builder / output parser via the
|
|
141
162
|
dispatch tables built in :meth:`__init__`, mirroring how
|
|
@@ -145,10 +166,10 @@ class AgentCliAdapter(BaseAdapter):
|
|
|
145
166
|
def __init__(self, config: ProviderConfig) -> None:
|
|
146
167
|
"""Bind to a ``ProviderConfig`` and reject unsupported agents.
|
|
147
168
|
|
|
148
|
-
Constructing an adapter for an agent other than ``claude`` /
|
|
149
|
-
raises a non-retryable :class:`AdapterError` —
|
|
150
|
-
declared in the schema but not
|
|
151
|
-
(design §9).
|
|
169
|
+
Constructing an adapter for an agent other than ``claude`` /
|
|
170
|
+
``codex`` / ``grok`` raises a non-retryable :class:`AdapterError` —
|
|
171
|
+
the remaining target (gemini) is declared in the schema but not
|
|
172
|
+
implemented until its phase (design §9).
|
|
152
173
|
"""
|
|
153
174
|
super().__init__(config)
|
|
154
175
|
if config.agent_cli is None: # pragma: no cover - schema enforces this
|
|
@@ -158,30 +179,34 @@ class AgentCliAdapter(BaseAdapter):
|
|
|
158
179
|
retryable=False,
|
|
159
180
|
)
|
|
160
181
|
self.acfg: AgentCliConfig = config.agent_cli
|
|
161
|
-
if self.acfg.agent not in ("claude", "grok"):
|
|
182
|
+
if self.acfg.agent not in ("claude", "codex", "grok"):
|
|
162
183
|
raise AdapterError(
|
|
163
184
|
f"agent {self.acfg.agent!r} is not implemented yet "
|
|
164
|
-
f"(implemented: claude, grok). Wait for Phase
|
|
185
|
+
f"(implemented: claude, codex, grok). Wait for Phase 1c.",
|
|
165
186
|
provider=config.name,
|
|
166
187
|
retryable=False,
|
|
167
188
|
)
|
|
168
189
|
# agent → argv builder / output parser dispatch tables. claude landed
|
|
169
|
-
# in Phase 1a, grok in Phase 1d; Phase
|
|
190
|
+
# in Phase 1a, codex in Phase 1b, grok in Phase 1d; Phase 1c adds
|
|
191
|
+
# gemini.
|
|
170
192
|
self._builders = {
|
|
171
193
|
"claude": self._build_claude_argv,
|
|
194
|
+
"codex": self._build_codex_argv,
|
|
172
195
|
"grok": self._build_grok_argv,
|
|
173
196
|
}
|
|
174
197
|
self._parsers = {
|
|
175
198
|
"claude": self._parse_claude,
|
|
199
|
+
"codex": self._parse_codex,
|
|
176
200
|
"grok": self._parse_grok,
|
|
177
201
|
}
|
|
178
|
-
# Prompt delivery is per-agent: claude
|
|
179
|
-
# from stdin (10MB cap
|
|
202
|
+
# Prompt delivery is per-agent: claude and codex read their prompt
|
|
203
|
+
# from stdin (10MB cap; codex's argv carries a trailing "-" sentinel
|
|
204
|
+
# making that explicit), which keeps argv free of the (potentially
|
|
180
205
|
# huge) prompt text. grok's ``-p`` REQUIRES the prompt as its argv
|
|
181
206
|
# value (piped stdin is only appended as extra context, verified on
|
|
182
207
|
# v0.2.93), so grok gets the prompt via ``--prompt-file`` instead —
|
|
183
208
|
# see ``_write_prompt_file`` for the rationale.
|
|
184
|
-
self._uses_stdin = self.acfg.agent
|
|
209
|
+
self._uses_stdin = self.acfg.agent in ("claude", "codex")
|
|
185
210
|
|
|
186
211
|
# ------------------------------------------------------------------
|
|
187
212
|
# BaseAdapter contract
|
|
@@ -465,6 +490,178 @@ class AgentCliAdapter(BaseAdapter):
|
|
|
465
490
|
usage["duration_ms"] = data["duration_ms"]
|
|
466
491
|
return usage
|
|
467
492
|
|
|
493
|
+
# ------------------------------------------------------------------
|
|
494
|
+
# codex argv builder + output parser (Phase 1b)
|
|
495
|
+
# ------------------------------------------------------------------
|
|
496
|
+
|
|
497
|
+
def _build_codex_argv(self, workdir: str, prompt_file: str | None = None) -> list[str]:
|
|
498
|
+
"""Assemble the ``codex exec`` argv (design §5.1.5 / §5.4, verified
|
|
499
|
+
against codex-cli 0.144.1 — see ``_codex/facts-codex.md``).
|
|
500
|
+
|
|
501
|
+
Shape::
|
|
502
|
+
|
|
503
|
+
codex exec --json --skip-git-repo-check --ephemeral
|
|
504
|
+
-m <model> -C <workdir> -s <read-only|workspace-write> -
|
|
505
|
+
|
|
506
|
+
The prompt is fed on stdin (not argv), so it never appears here and
|
|
507
|
+
``prompt_file`` is ignored (it exists only to keep the builder
|
|
508
|
+
signature uniform across agents) — the trailing ``-`` makes the
|
|
509
|
+
stdin intent explicit to ``codex exec``, which otherwise treats a
|
|
510
|
+
bare invocation with no PROMPT arg the same way but reads more
|
|
511
|
+
ambiguously in a fixed argv list. ``--skip-git-repo-check`` is
|
|
512
|
+
ALWAYS passed because the isolated workdir is not a git repository
|
|
513
|
+
(without it the CLI exits 1). ``--ephemeral`` is ALWAYS passed so no
|
|
514
|
+
session state persists to disk, matching the adapter's stateless
|
|
515
|
+
one-shot ethos (the same rationale as grok's ``--no-memory``). codex
|
|
516
|
+
has no ``--max-turns`` equivalent, so ``AgentCliConfig.max_turns`` is
|
|
517
|
+
silently ignored here (documented in the schema).
|
|
518
|
+
"""
|
|
519
|
+
del prompt_file # codex takes the prompt on stdin, not from a file.
|
|
520
|
+
model = self.acfg.model or self.config.model
|
|
521
|
+
argv = [
|
|
522
|
+
self.acfg.command,
|
|
523
|
+
"exec",
|
|
524
|
+
"--json",
|
|
525
|
+
"--skip-git-repo-check",
|
|
526
|
+
"--ephemeral",
|
|
527
|
+
"-m",
|
|
528
|
+
model,
|
|
529
|
+
"-C",
|
|
530
|
+
workdir,
|
|
531
|
+
]
|
|
532
|
+
argv += self._codex_sandbox_args()
|
|
533
|
+
argv += ["-"]
|
|
534
|
+
return argv
|
|
535
|
+
|
|
536
|
+
def _codex_sandbox_args(self) -> list[str]:
|
|
537
|
+
"""Map ``sandbox_mode`` → codex ``-s/--sandbox`` flags, clamped.
|
|
538
|
+
|
|
539
|
+
Same clamp as claude/grok (design §5.4): when ``allow_file_writes``
|
|
540
|
+
is False the effective mode is forced to ``read_only`` regardless of
|
|
541
|
+
``sandbox_mode``, so writes always require the explicit opt-in.
|
|
542
|
+
``codex exec`` has no approval flag in 0.144.1 (non-interactive, so
|
|
543
|
+
there is nothing to approve), so ``full_auto`` maps onto the same
|
|
544
|
+
``workspace-write`` value as ``edit`` —
|
|
545
|
+
``--dangerously-bypass-approvals-and-sandbox`` is never used.
|
|
546
|
+
"""
|
|
547
|
+
mode = self.acfg.sandbox_mode if self.acfg.allow_file_writes else "read_only"
|
|
548
|
+
return list(_CODEX_SANDBOX_ARGS[mode])
|
|
549
|
+
|
|
550
|
+
def _parse_codex(
|
|
551
|
+
self, stdout: bytes, stderr: bytes
|
|
552
|
+
) -> tuple[str, dict[str, Any], dict[str, Any]]:
|
|
553
|
+
"""Parse codex ``exec --json`` JSONL output (verified codex-cli
|
|
554
|
+
0.144.1, ``_codex/facts-codex.md``).
|
|
555
|
+
|
|
556
|
+
Real-run shape (one JSON object per line, newline-delimited)::
|
|
557
|
+
|
|
558
|
+
{"type":"thread.started","thread_id":"<uuid>"}
|
|
559
|
+
{"type":"turn.started"}
|
|
560
|
+
{"type":"item.completed","item":{"id":"item_0","type":"agent_message","text":"2"}}
|
|
561
|
+
{"type":"turn.completed","usage":{"input_tokens":13810,"cached_input_tokens":9984,"output_tokens":5,"reasoning_output_tokens":0}}
|
|
562
|
+
|
|
563
|
+
The CLI is pre-1.0 and its JSON schema is not frozen, so parsing is
|
|
564
|
+
deliberately defensive at every level: individual lines that fail to
|
|
565
|
+
parse (or parse to something other than a JSON object) are SKIPPED
|
|
566
|
+
rather than aborting the whole parse — stray non-JSON noise on
|
|
567
|
+
stdout should not sink an otherwise-valid answer. The final answer
|
|
568
|
+
is the LAST ``item.completed`` event whose ``item`` is an
|
|
569
|
+
``agent_message`` with a string ``text``. If a completed answer was
|
|
570
|
+
found, it is returned even when a later ``error`` / ``turn.failed``
|
|
571
|
+
event also appears (a completed answer beats a trailing error); if
|
|
572
|
+
no answer was found, an ``error`` / ``turn.failed`` event (or the
|
|
573
|
+
total absence of any agent_message) raises a retryable
|
|
574
|
+
:class:`AdapterError`.
|
|
575
|
+
"""
|
|
576
|
+
text = stdout.decode("utf-8", "replace")
|
|
577
|
+
if not text.strip():
|
|
578
|
+
raise AdapterError(
|
|
579
|
+
"codex produced no stdout to parse",
|
|
580
|
+
provider=self.name,
|
|
581
|
+
retryable=True,
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
final_text: str | None = None
|
|
585
|
+
thread_id: str | None = None
|
|
586
|
+
failure_event: dict[str, Any] | None = None
|
|
587
|
+
prompt_tokens = 0
|
|
588
|
+
completion_tokens = 0
|
|
589
|
+
cached_tokens = 0
|
|
590
|
+
reasoning_tokens = 0
|
|
591
|
+
|
|
592
|
+
for line in text.splitlines():
|
|
593
|
+
line = line.strip()
|
|
594
|
+
if not line:
|
|
595
|
+
continue
|
|
596
|
+
try:
|
|
597
|
+
event = json.loads(line)
|
|
598
|
+
except json.JSONDecodeError:
|
|
599
|
+
# Defensive against stray non-JSON noise (progress text that
|
|
600
|
+
# leaked onto stdout, partial writes, etc.) — skip the line.
|
|
601
|
+
continue
|
|
602
|
+
if not isinstance(event, dict):
|
|
603
|
+
continue
|
|
604
|
+
|
|
605
|
+
etype = event.get("type")
|
|
606
|
+
if etype == "thread.started":
|
|
607
|
+
tid = event.get("thread_id")
|
|
608
|
+
if isinstance(tid, str):
|
|
609
|
+
thread_id = tid
|
|
610
|
+
elif etype == "item.completed":
|
|
611
|
+
item = event.get("item")
|
|
612
|
+
if (
|
|
613
|
+
isinstance(item, dict)
|
|
614
|
+
and item.get("type") == "agent_message"
|
|
615
|
+
and isinstance(item.get("text"), str)
|
|
616
|
+
):
|
|
617
|
+
final_text = item["text"]
|
|
618
|
+
elif etype == "turn.completed":
|
|
619
|
+
usage = event.get("usage")
|
|
620
|
+
usage = usage if isinstance(usage, dict) else {}
|
|
621
|
+
|
|
622
|
+
def _int(key: str, _usage: dict[str, Any] = usage) -> int:
|
|
623
|
+
value = _usage.get(key)
|
|
624
|
+
return int(value) if isinstance(value, (int, float)) else 0
|
|
625
|
+
|
|
626
|
+
prompt_tokens += _int("input_tokens")
|
|
627
|
+
completion_tokens += _int("output_tokens")
|
|
628
|
+
cached_tokens += _int("cached_input_tokens")
|
|
629
|
+
reasoning_tokens += _int("reasoning_output_tokens")
|
|
630
|
+
elif etype in ("error", "turn.failed"):
|
|
631
|
+
failure_event = event
|
|
632
|
+
|
|
633
|
+
if final_text is None:
|
|
634
|
+
if failure_event is not None:
|
|
635
|
+
raise AdapterError(
|
|
636
|
+
f"codex reported {failure_event.get('type')}: {failure_event!r}"[:500],
|
|
637
|
+
provider=self.name,
|
|
638
|
+
retryable=True,
|
|
639
|
+
)
|
|
640
|
+
raise AdapterError(
|
|
641
|
+
"codex JSONL output contained no agent_message",
|
|
642
|
+
provider=self.name,
|
|
643
|
+
retryable=True,
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
# cached_input_tokens is a SUBSET of input_tokens (not additive) —
|
|
647
|
+
# verified sample: input 13810 ⊇ cached 9984 — so it is preserved
|
|
648
|
+
# under prompt_tokens_details rather than folded into prompt_tokens
|
|
649
|
+
# (this differs from claude's normalization, design §5.1.6).
|
|
650
|
+
usage_out: dict[str, Any] = {
|
|
651
|
+
"prompt_tokens": prompt_tokens,
|
|
652
|
+
"completion_tokens": completion_tokens,
|
|
653
|
+
"total_tokens": prompt_tokens + completion_tokens,
|
|
654
|
+
}
|
|
655
|
+
if cached_tokens > 0:
|
|
656
|
+
usage_out["prompt_tokens_details"] = {"cached_tokens": cached_tokens}
|
|
657
|
+
if reasoning_tokens > 0:
|
|
658
|
+
usage_out["completion_tokens_details"] = {"reasoning_tokens": reasoning_tokens}
|
|
659
|
+
|
|
660
|
+
meta: dict[str, Any] = {}
|
|
661
|
+
if thread_id is not None:
|
|
662
|
+
meta["coderouter_session_id"] = thread_id
|
|
663
|
+
return final_text, usage_out, meta
|
|
664
|
+
|
|
468
665
|
# ------------------------------------------------------------------
|
|
469
666
|
# grok argv builder + output parser (Phase 1d)
|
|
470
667
|
# ------------------------------------------------------------------
|
|
@@ -171,10 +171,10 @@ class AgentCliConfig(BaseModel):
|
|
|
171
171
|
invokes an external coding-agent CLI (codex / gemini / grok / claude)
|
|
172
172
|
in a single one-shot ``exec`` and returns the final answer as one
|
|
173
173
|
``prompt in → text out`` transformation. ``claude`` (Claude Code CLI,
|
|
174
|
-
Phase 1a)
|
|
175
|
-
|
|
176
|
-
forward-compatible, but the adapter rejects
|
|
177
|
-
lands.
|
|
174
|
+
Phase 1a), ``codex`` (codex CLI, Phase 1b) and ``grok`` (grok CLI,
|
|
175
|
+
Phase 1d) are implemented; ``gemini`` is declared at the schema level
|
|
176
|
+
so configs are forward-compatible, but the adapter rejects it until its
|
|
177
|
+
phase (1c) lands.
|
|
178
178
|
|
|
179
179
|
Auth note (grok): the grok CLI uses OAuth credentials stored under
|
|
180
180
|
``~/.grok`` (``grok login``), which the adapter's HOME inheritance
|
|
@@ -182,6 +182,14 @@ class AgentCliConfig(BaseModel):
|
|
|
182
182
|
``GROK_CODE_XAI_API_KEY`` in ``passthrough_env`` (this is grok's key
|
|
183
183
|
env var — NOT ``XAI_API_KEY``).
|
|
184
184
|
|
|
185
|
+
Auth note (codex): the codex CLI uses a ChatGPT-plan OAuth login stored
|
|
186
|
+
under ``~/.codex`` (``codex login``), which the adapter's HOME
|
|
187
|
+
inheritance already covers — the credentials go stale after roughly 8
|
|
188
|
+
days and are auto-refreshed on use, so no extra config is needed for
|
|
189
|
+
interactive/subscription setups. For CI / API-key setups, list
|
|
190
|
+
``CODEX_API_KEY`` (exec-only) or ``OPENAI_API_KEY`` (general) in
|
|
191
|
+
``passthrough_env``.
|
|
192
|
+
|
|
185
193
|
Follows the ``extra="forbid"`` convention used across this module so a
|
|
186
194
|
typo'd key fails at config-load rather than being silently ignored.
|
|
187
195
|
"""
|
|
@@ -191,8 +199,9 @@ class AgentCliConfig(BaseModel):
|
|
|
191
199
|
agent: Literal["codex", "gemini", "grok", "claude"] = Field(
|
|
192
200
|
...,
|
|
193
201
|
description=(
|
|
194
|
-
"External coding-agent CLI to invoke. 'claude' (Phase 1a)
|
|
195
|
-
"'grok' (Phase 1d) are implemented;
|
|
202
|
+
"External coding-agent CLI to invoke. 'claude' (Phase 1a), "
|
|
203
|
+
"'codex' (Phase 1b) and 'grok' (Phase 1d) are implemented; "
|
|
204
|
+
"'gemini' (Phase 1c) pending."
|
|
196
205
|
),
|
|
197
206
|
)
|
|
198
207
|
command: str | None = Field(
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> 日本語版: [`external-agents.md`](./external-agents.md)
|
|
4
4
|
|
|
5
|
-
`kind: "agent_cli"` is an adapter that registers an external coding-agent CLI, such as the Claude Code CLI, as a single CodeRouter provider. It was newly added in v2.7.7 (Phase 1a: claude),
|
|
5
|
+
`kind: "agent_cli"` is an adapter that registers an external coding-agent CLI, such as the Claude Code CLI, as a single CodeRouter provider. It was newly added in v2.7.7 (Phase 1a: claude), v2.7.8 added grok (Phase 1d), and v2.7.9 added codex (Phase 1b). See [`docs/designs/external-agents-adapter.md`](../designs/external-agents-adapter.md) for the full design.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -11,16 +11,16 @@
|
|
|
11
11
|
A coding-agent CLI is normally a stateful control loop that runs many turns autonomously while editing files — a poor fit for CodeRouter's "one request = one transformation" ethos. `agent_cli` reconciles the two by collapsing the CLI into a **one-shot `exec`** (prompt in → final answer text out). Orchestration (multi-turn control, tool execution) stays entirely inside the agent CLI process; from CodeRouter's side it looks like just another provider that answers a single exchange.
|
|
12
12
|
|
|
13
13
|
- **Supported CLIs**: the `agent` field can declare `claude` / `codex` / `gemini` / `grok`.
|
|
14
|
-
- **Implementation status (as of v2.7.
|
|
14
|
+
- **Implementation status (as of v2.7.9)**: **`claude` (Claude Code CLI, Phase 1a), `codex` (OpenAI Codex CLI, Phase 1b), and `grok` (Grok CLI, Phase 1d) are implemented**. Only `gemini` can be written into `providers.yaml` at the schema level, but constructing the adapter always rejects it (Phase 1c is not yet implemented). The error message looks roughly like the following (the exact wording may differ between versions):
|
|
15
15
|
|
|
16
16
|
```
|
|
17
|
-
AdapterError: agent '
|
|
18
|
-
Wait for the agent's phase (
|
|
17
|
+
AdapterError: agent 'gemini' is not implemented yet (implemented: claude, codex, grok).
|
|
18
|
+
Wait for the agent's phase (1c).
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
This rejection is `retryable=False` — even if other providers exist in the fallback chain, it stops immediately as a configuration error.
|
|
22
22
|
|
|
23
|
-
- The shared parts of this document (authentication design, configuration reference, limitations) are written against the `claude` target; grok-specific behavior
|
|
23
|
+
- The shared parts of this document (authentication design, configuration reference, limitations) are written against the `claude` target; codex- and grok-specific behavior are collected in the [codex (OpenAI Codex CLI)](#codex-openai-codex-cli) and [grok (Grok CLI)](#grok-grok-cli) sections, respectively. Example configs for `gemini` exist only as a commented-out preview at the bottom of `examples/providers-agent-cli.yaml` and do not work.
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
@@ -96,14 +96,14 @@ All fields of the `agent_cli:` sub-config (`AgentCliConfig`) in `providers.yaml`
|
|
|
96
96
|
|
|
97
97
|
| Field | Type | Default | Description |
|
|
98
98
|
|---|---|---|---|
|
|
99
|
-
| `agent` | `"claude" \| "codex" \| "gemini" \| "grok"` | (required) | Which CLI to invoke. **As of v2.7.
|
|
99
|
+
| `agent` | `"claude" \| "codex" \| "gemini" \| "grok"` | (required) | Which CLI to invoke. **As of v2.7.9, `claude`, `codex`, and `grok` are implemented; `gemini` is rejected when the adapter is constructed** |
|
|
100
100
|
| `command` | `str \| null` | `null` (defaults to the same name as `agent`) | CLI executable name or absolute path, resolved via `PATH` |
|
|
101
101
|
| `workdir` | `str \| null` | `null` (defaults to `~/.coderouter/agents/<provider name>`) | Working directory for the one-shot exec. `~` / env-var expansion is applied; a path containing `..` is rejected |
|
|
102
102
|
| `exec_timeout_s` | `float` | `600.0` (range `1.0`–`1800.0`) | Forced timeout (seconds) for the whole exec. **Separate** from `ProviderConfig.timeout_s` (the latter is not used by agent_cli) |
|
|
103
103
|
| `allow_file_writes` | `bool` | `false` | Whether to allow file writes. When `false`, the effective mode is clamped to read-only regardless of `sandbox_mode` |
|
|
104
|
-
| `sandbox_mode` | `"read_only" \| "edit" \| "full_auto"` | `"read_only"` | Maps to each CLI's sandbox/approval flags (claude: [table below](#sandbox_mode--permission-mode-mapping-claude); grok: [grok section](#sandbox_mode--grok-flag-mapping)) |
|
|
105
|
-
| `model` | `str \| null` | `null` (defaults to `ProviderConfig.model`) | Model name passed to the CLI's `--model` / `-m` (claude: `opus` / `sonnet` / `haiku` / `fable` etc.; grok: `grok-4.5` etc.) |
|
|
106
|
-
| `max_turns` | `int \| null` | `8` (range `1`–`50`) | Turn cap inside the CLI. Passed as `--max-turns` |
|
|
104
|
+
| `sandbox_mode` | `"read_only" \| "edit" \| "full_auto"` | `"read_only"` | Maps to each CLI's sandbox/approval flags (claude: [table below](#sandbox_mode--permission-mode-mapping-claude); codex: [codex section](#sandbox_mode--codex-flag-mapping); grok: [grok section](#sandbox_mode--grok-flag-mapping)) |
|
|
105
|
+
| `model` | `str \| null` | `null` (defaults to `ProviderConfig.model`) | Model name passed to the CLI's `--model` / `-m` (claude: `opus` / `sonnet` / `haiku` / `fable` etc.; codex: `gpt-5.5` etc.; grok: `grok-4.5` etc.) |
|
|
106
|
+
| `max_turns` | `int \| null` | `8` (range `1`–`50`) | Turn cap inside the CLI. Passed as `--max-turns`. **codex has no corresponding CLI flag, so this is always ignored** (for codex, `exec_timeout_s` + process-group kill is the only time bound) |
|
|
107
107
|
| `passthrough_env` | `list[str]` | `[]` | Allowlist of environment variable names forwarded from the parent process into the child. `ANTHROPIC_API_KEY` is not forwarded unless listed here |
|
|
108
108
|
| `agent_depth_limit` | `int` | `2` (range `1`–`4`) | Recursion nesting cap. When `CODEROUTER_AGENT_DEPTH` reaches or exceeds this, the call stops immediately with `AdapterError(retryable=False)` |
|
|
109
109
|
|
|
@@ -123,6 +123,104 @@ The `agent-claude` provider in the example config `examples/providers-agent-cli.
|
|
|
123
123
|
|
|
124
124
|
---
|
|
125
125
|
|
|
126
|
+
## codex (OpenAI Codex CLI)
|
|
127
|
+
|
|
128
|
+
v2.7.9 (Phase 1b) implements `agent: codex`. Like claude, it delivers the prompt via **stdin** (unlike grok's file-based delivery). It has its own behavior around JSONL output, `--ephemeral`, and running outside a git repository. Everything below is based on codex CLI **0.144.1** (field-verified on the author's Mac, 2026-07-11).
|
|
129
|
+
|
|
130
|
+
### Example configuration
|
|
131
|
+
|
|
132
|
+
```yaml
|
|
133
|
+
providers:
|
|
134
|
+
- name: agent-codex
|
|
135
|
+
kind: agent_cli
|
|
136
|
+
model: gpt-5.5 # a current frontier-model example; the default depends on the environment/plan, so set it explicitly
|
|
137
|
+
paid: false # ChatGPT-plan subscription OAuth = zero metered cost
|
|
138
|
+
capabilities:
|
|
139
|
+
streaming: false
|
|
140
|
+
tools: false
|
|
141
|
+
agent_cli:
|
|
142
|
+
agent: codex
|
|
143
|
+
command: codex
|
|
144
|
+
workdir: ~/.coderouter/agents/codex
|
|
145
|
+
exec_timeout_s: 600
|
|
146
|
+
allow_file_writes: false
|
|
147
|
+
sandbox_mode: read_only
|
|
148
|
+
max_turns: 8 # ignored by codex — no corresponding flag (see below)
|
|
149
|
+
passthrough_env: [] # OAuth reads ~/.codex/auth.json via the inherited HOME, so empty is fine.
|
|
150
|
+
# Only list CODEX_API_KEY (exec-only) or OPENAI_API_KEY
|
|
151
|
+
# when using an API key in CI
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### The argv the adapter builds
|
|
155
|
+
|
|
156
|
+
With `sandbox_mode: read_only` (the default), the adapter builds the following argv.
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
codex exec --json --skip-git-repo-check --ephemeral -m <model> -C <workdir> -s read-only -
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### The prompt is delivered via stdin (same as claude, unlike grok)
|
|
163
|
+
|
|
164
|
+
codex's `exec` subcommand reads the prompt from stdin when the PROMPT argument is omitted (or explicitly given as `-`). The adapter places an explicit trailing `-` on the argv to force this path. Unlike grok, there's no need for a temp file (`--prompt-file`) inside the isolated workdir — this is the same stdin scheme as claude.
|
|
165
|
+
|
|
166
|
+
### Why `--skip-git-repo-check` is always passed
|
|
167
|
+
|
|
168
|
+
CodeRouter's isolated workdir is not a git repository. By default, codex runs a "trusted directory" check and, outside a git repo in an unrecognized directory, fails immediately with exit 1 and stderr `Not inside a trusted directory and --skip-git-repo-check was not specified.` (field-verified). The adapter always passes this flag, so this error message should never appear in normal operation.
|
|
169
|
+
|
|
170
|
+
### Why `--ephemeral` is always passed
|
|
171
|
+
|
|
172
|
+
`--ephemeral` prevents the session from being persisted to disk. For the same reason as grok's `--no-memory` — keeping with CodeRouter's "one request = one stateless transformation" ethos — the adapter always passes this flag (it cannot be turned off in config).
|
|
173
|
+
|
|
174
|
+
### `sandbox_mode` → codex flag mapping
|
|
175
|
+
|
|
176
|
+
As with claude/grok, when `allow_file_writes=false` the effective mode is clamped to `read_only` regardless of `sandbox_mode`.
|
|
177
|
+
|
|
178
|
+
| `sandbox_mode` | codex flags | Notes |
|
|
179
|
+
|---|---|---|
|
|
180
|
+
| `read_only` (default) | `-s read-only` | No file changes. Always clamped to this mode when `allow_file_writes=false` |
|
|
181
|
+
| `edit` | `-s workspace-write` | Permits file edits inside the workspace-write sandbox |
|
|
182
|
+
| `full_auto` | `-s workspace-write` | **`codex exec` has no approval flag (`-a` / `--ask-for-approval`)** — it's absent from `exec --help` in 0.144.1, since non-interactive execution has no approval prompt to control in the first place. So this maps identically to `edit`. `--dangerously-bypass-approvals-and-sandbox` is never used |
|
|
183
|
+
|
|
184
|
+
### No `--max-turns` / `--timeout` exist
|
|
185
|
+
|
|
186
|
+
codex exec has neither `--max-turns` nor `--timeout`. Consequently `AgentCliConfig.max_turns` is **ignored for codex**. The only time bound is the existing `exec_timeout_s` + process-group `SIGKILL`.
|
|
187
|
+
|
|
188
|
+
### JSONL output and usage normalization
|
|
189
|
+
|
|
190
|
+
`--json` output is JSONL (one event per line); progress goes to stderr, and the event stream (including the final answer) goes to stdout (confirmed by both the official docs and the real CLI). A verified one-shot run:
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
$ codex exec --json --skip-git-repo-check "What's 1+1? Answer with just the digit"
|
|
194
|
+
{"type":"thread.started","thread_id":"019f4e74-08fd-77b2-9cc6-9afa744df130"}
|
|
195
|
+
{"type":"turn.started"}
|
|
196
|
+
{"type":"item.completed","item":{"id":"item_0","type":"agent_message","text":"2"}}
|
|
197
|
+
{"type":"turn.completed","usage":{"input_tokens":13810,"cached_input_tokens":9984,"output_tokens":5,"reasoning_output_tokens":0}}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
- Final answer: the **last** `item.completed` whose `item.type=="agent_message"`, taking `item.text`.
|
|
201
|
+
- Usage normalization: `turn.completed.usage`'s `input_tokens` → `prompt_tokens`, `output_tokens` → `completion_tokens`, and their sum → `total_tokens`. `cached_input_tokens` is a **subset** of `input_tokens` (measured: input 13810 ⊃ cached 9984), so it is not added on top — it's kept as `prompt_tokens_details.cached_tokens`. When `reasoning_output_tokens` is greater than 0, it's kept as `completion_tokens_details.reasoning_tokens` (defensive). Multiple `turn.completed` events, if they occur, are summed.
|
|
202
|
+
- `thread_id` (from `thread.started`) is surfaced as the `coderouter_session_id` response metadata.
|
|
203
|
+
- If an `error` event or `turn.failed` is seen, or no agent_message is ever produced / stdout is empty / every line is non-JSON, this raises a retryable `AdapterError` and the fallback chain advances to the next provider. Individual non-JSON lines in the JSONL stream don't stop processing of the remaining lines (defense against stray stderr-like output mixed in).
|
|
204
|
+
|
|
205
|
+
### Authentication (ChatGPT-plan subscription OAuth / API key)
|
|
206
|
+
|
|
207
|
+
The codex CLI supports ChatGPT-plan OAuth login. Credentials are stored at `~/.codex/auth.json` (or the OS keyring), and the adapter's `HOME` inheritance makes it work with `passthrough_env: []`.
|
|
208
|
+
|
|
209
|
+
1. Run `codex login` to complete login. `codex login status` exiting 0 confirms you're logged in.
|
|
210
|
+
2. The OAuth token goes **stale after about 8 days**. It auto-refreshes on use, but a setup that doesn't call codex for a long stretch can fail while stale — running codex occasionally, or re-logging in, is recommended.
|
|
211
|
+
|
|
212
|
+
For CI or metered API-key billing, list `CODEX_API_KEY` (**exec-only**) or `OPENAI_API_KEY` (general) in `passthrough_env`. `CODEX_HOME` can also override the config/credentials directory itself.
|
|
213
|
+
|
|
214
|
+
### Error reporting
|
|
215
|
+
|
|
216
|
+
The codex CLI exits 0 on success, and on failure exits non-zero with the error text on stderr (e.g. the git-repo-check message). JSONL may also carry an `error` event or `turn.failed`; both of those also become a retryable `AdapterError` and the fallback chain advances to the next provider.
|
|
217
|
+
|
|
218
|
+
### Pre-1.0 caveat
|
|
219
|
+
|
|
220
|
+
The codex CLI is pre-1.0 and releases nearly daily. `--json`'s alias is still `--experimental-json`, and the JSONL schema is not frozen. **Version pinning is recommended** (you can point `command` at a pinned binary's full path). If the schema does change, defensive parsing turns it into a retryable `AdapterError` and the fallback chain demotes to the next provider.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
126
224
|
## grok (Grok CLI)
|
|
127
225
|
|
|
128
226
|
v2.7.8 (Phase 1d) implements `agent: grok`. It uses the same one-shot exec scheme as claude, but grok has its own behavior around prompt delivery, cross-session memory disabling, and usage reporting. Everything below is based on grok CLI **v0.2.93** ([stable] channel, field-verified on 2026-07-10).
|
|
@@ -222,7 +320,9 @@ The grok CLI is early beta (v0.2.93 [stable] channel as of 2026-07-10). Its JSON
|
|
|
222
320
|
| Requests are rejected / not routed, effectively "paid gate blocked" | The `agent_cli` provider has `paid: true` but `ALLOW_PAID` isn't set | For subscription usage, set `paid: false`. For metered API-key billing, set `ALLOW_PAID=true` when starting CodeRouter |
|
|
223
321
|
| `claude exited 1: ...` shows a specific reason | The claude CLI sometimes reports auth/API errors as an `is_error: true` JSON document on **stdout** (with stderr left empty) and exits with code 1 | v2.7.7's `_error_detail()` now prefers the `result` field of that stdout `is_error` JSON (the actual error text, e.g. `Not logged in · Please run /login`) even when stderr is empty, and includes it in the raised error message — use the message shown as your lead |
|
|
224
322
|
| Fails with `grok exited 1: ...` | The grok CLI exits with code 1 on auth/network/runtime errors, with the error text on stderr | The adapter includes a tail of stderr in the `AdapterError`, so use the message shown as your lead. For auth errors, re-run `grok login` and smoke-check that `grok models` works |
|
|
225
|
-
|
|
|
323
|
+
| Fails with `codex exited 1: ...` (suspect stale OAuth) | codex's OAuth token goes stale after about 8 days. It auto-refreshes on use, but a long gap between calls can leave it stale and failing | Check login state with `codex login status` and re-run `codex login` if needed. Running codex occasionally helps avoid staleness |
|
|
324
|
+
| `Not inside a trusted directory and --skip-git-repo-check was not specified.` appears | This should never happen — the adapter always passes `--skip-git-repo-check` | If you see this, it likely indicates a bug in CodeRouter's argv construction. Check your version and file an issue if it reproduces |
|
|
325
|
+
| CLI fails to launch (`failed to launch ...`) | `command` (defaults to the same name as `agent`) isn't on `PATH` | Confirm `claude --version` / `codex --version` / `grok --version` works. You can also point `command` at a full path |
|
|
226
326
|
|
|
227
327
|
---
|
|
228
328
|
|