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.
Files changed (262) hide show
  1. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/CHANGELOG.md +95 -1
  2. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/PKG-INFO +1 -1
  3. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/adapters/agent_cli.py +218 -21
  4. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/config/schemas.py +15 -6
  5. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/external-agents.en.md +110 -10
  6. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/external-agents.md +110 -10
  7. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/designs/external-agents-adapter.md +20 -1
  8. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/examples/providers-agent-cli.yaml +64 -32
  9. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/pyproject.toml +1 -1
  10. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_agent_cli.py +382 -12
  11. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/.gitignore +0 -0
  12. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/LICENSE +0 -0
  13. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/README.en.md +0 -0
  14. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/README.md +0 -0
  15. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/__init__.py +0 -0
  16. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/__main__.py +0 -0
  17. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/adapters/__init__.py +0 -0
  18. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/adapters/anthropic_native.py +0 -0
  19. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/adapters/base.py +0 -0
  20. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/adapters/openai_compat.py +0 -0
  21. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/adapters/registry.py +0 -0
  22. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/cli.py +0 -0
  23. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/cli_stats.py +0 -0
  24. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/config/__init__.py +0 -0
  25. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/config/capability_registry.py +0 -0
  26. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/config/env_file.py +0 -0
  27. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/config/loader.py +0 -0
  28. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/cost.py +0 -0
  29. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/data/__init__.py +0 -0
  30. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/data/model-capabilities.yaml +0 -0
  31. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/doctor.py +0 -0
  32. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/doctor_apply.py +0 -0
  33. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/env_security.py +0 -0
  34. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/errors.py +0 -0
  35. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/gguf_introspect.py +0 -0
  36. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/guards/__init__.py +0 -0
  37. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/guards/_fingerprint.py +0 -0
  38. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/guards/backend_health.py +0 -0
  39. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/guards/context_budget.py +0 -0
  40. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/guards/continuous_probe.py +0 -0
  41. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/guards/drift_actions.py +0 -0
  42. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/guards/drift_detection.py +0 -0
  43. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/guards/memory_budget.py +0 -0
  44. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/guards/memory_pressure.py +0 -0
  45. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/guards/self_healing.py +0 -0
  46. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/guards/tool_loop.py +0 -0
  47. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/hardware.py +0 -0
  48. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/ingress/__init__.py +0 -0
  49. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/ingress/anthropic_routes.py +0 -0
  50. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/ingress/app.py +0 -0
  51. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/ingress/dashboard_routes.py +0 -0
  52. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/ingress/launcher_routes.py +0 -0
  53. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/ingress/metrics_routes.py +0 -0
  54. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/ingress/openai_routes.py +0 -0
  55. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/language_tax.py +0 -0
  56. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/launcher_speculative.py +0 -0
  57. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/logging.py +0 -0
  58. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/metrics/__init__.py +0 -0
  59. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/metrics/collector.py +0 -0
  60. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/metrics/prometheus.py +0 -0
  61. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/output_filters.py +0 -0
  62. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/plugins/__init__.py +0 -0
  63. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/plugins/base.py +0 -0
  64. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/plugins/loader.py +0 -0
  65. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/plugins/registry.py +0 -0
  66. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/routing/__init__.py +0 -0
  67. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/routing/adaptive.py +0 -0
  68. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/routing/auto_router.py +0 -0
  69. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/routing/budget.py +0 -0
  70. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/routing/capability.py +0 -0
  71. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/routing/fallback.py +0 -0
  72. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/state/__init__.py +0 -0
  73. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/state/audit_log.py +0 -0
  74. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/state/replay.py +0 -0
  75. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/state/request_log.py +0 -0
  76. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/state/store.py +0 -0
  77. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/state/suggest_rules.py +0 -0
  78. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/token_estimation.py +0 -0
  79. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/token_estimation_accurate.py +0 -0
  80. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/translation/__init__.py +0 -0
  81. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/translation/anthropic.py +0 -0
  82. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/translation/convert.py +0 -0
  83. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/coderouter/translation/tool_repair.py +0 -0
  84. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/README.en.md +0 -0
  85. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/README.md +0 -0
  86. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/assets/dashboard-demo.png +0 -0
  87. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/claude-code-llamacpp-vllm.en.md +0 -0
  88. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/claude-code-llamacpp-vllm.md +0 -0
  89. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/gguf_dl.en.md +0 -0
  90. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/gguf_dl.md +0 -0
  91. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/hf-ollama-models.en.md +0 -0
  92. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/hf-ollama-models.md +0 -0
  93. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/install-backends.en.md +0 -0
  94. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/install-backends.md +0 -0
  95. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/launcher-quickstart.en.md +0 -0
  96. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/launcher-quickstart.md +0 -0
  97. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/launcher.en.md +0 -0
  98. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/launcher.md +0 -0
  99. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/llamacpp-direct.en.md +0 -0
  100. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/llamacpp-direct.md +0 -0
  101. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/lmstudio-direct.en.md +0 -0
  102. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/lmstudio-direct.md +0 -0
  103. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/backends/verify-ollama-0.23.1.md +0 -0
  104. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/concepts/architecture.en.md +0 -0
  105. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/concepts/architecture.md +0 -0
  106. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/concepts/context-budget.en.md +0 -0
  107. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/concepts/context-budget.md +0 -0
  108. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/concepts/continuous-probing.en.md +0 -0
  109. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/concepts/continuous-probing.md +0 -0
  110. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/concepts/drift-detection.en.md +0 -0
  111. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/concepts/drift-detection.md +0 -0
  112. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/concepts/partial-stitch.en.md +0 -0
  113. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/concepts/partial-stitch.md +0 -0
  114. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/designs/v1.5-dashboard-mockup.html +0 -0
  115. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/designs/v1.6-auto-router-verification.md +0 -0
  116. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/designs/v1.6-auto-router.md +0 -0
  117. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/guides/free-tier-guide.en.md +0 -0
  118. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/guides/free-tier-guide.md +0 -0
  119. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/guides/language-tax.en.md +0 -0
  120. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/guides/language-tax.md +0 -0
  121. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/guides/remote-access.en.md +0 -0
  122. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/guides/remote-access.md +0 -0
  123. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/guides/security.en.md +0 -0
  124. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/guides/security.md +0 -0
  125. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/guides/troubleshooting.en.md +0 -0
  126. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/guides/troubleshooting.md +0 -0
  127. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/guides/usage-guide.en.md +0 -0
  128. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/guides/usage-guide.md +0 -0
  129. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/low-memory-integration.en.md +0 -0
  130. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/low-memory-integration.md +0 -0
  131. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/openrouter-roster/CHANGES.md +0 -0
  132. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/openrouter-roster/README.md +0 -0
  133. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/openrouter-roster/latest.json +0 -0
  134. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/retrospectives/v0.4.md +0 -0
  135. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/retrospectives/v0.5-verify.md +0 -0
  136. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/retrospectives/v0.5.md +0 -0
  137. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/retrospectives/v0.6.md +0 -0
  138. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/retrospectives/v0.7.md +0 -0
  139. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/retrospectives/v1.0-verify.md +0 -0
  140. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/retrospectives/v1.0.md +0 -0
  141. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/start/quickstart.en.md +0 -0
  142. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/start/quickstart.md +0 -0
  143. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/start/when-do-i-need-coderouter.en.md +0 -0
  144. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/docs/start/when-do-i-need-coderouter.md +0 -0
  145. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/examples/.env.example +0 -0
  146. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/examples/README.md +0 -0
  147. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/examples/providers-multiagent.yaml +0 -0
  148. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/examples/providers.context-budget-test.yaml +0 -0
  149. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/examples/providers.llamacpp-vllm.yaml +0 -0
  150. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/examples/providers.nvidia-nim.yaml +0 -0
  151. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/examples/providers.ollama-auto-custom.yaml +0 -0
  152. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/examples/providers.ollama-auto.yaml +0 -0
  153. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/examples/providers.ollama-free-chain.yaml +0 -0
  154. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/examples/providers.raspberrypi.yaml +0 -0
  155. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/examples/providers.yaml +0 -0
  156. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/scripts/demo_traffic.sh +0 -0
  157. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/scripts/openrouter_roster_diff.py +0 -0
  158. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/scripts/smoke_v2_2.sh +0 -0
  159. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/scripts/verify-providers.yaml +0 -0
  160. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/scripts/verify_ollama_0_23.py +0 -0
  161. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/scripts/verify_v0_5.sh +0 -0
  162. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/scripts/verify_v1_0.sh +0 -0
  163. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/__init__.py +0 -0
  164. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/conftest.py +0 -0
  165. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_adapter_anthropic.py +0 -0
  166. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_audit_log.py +0 -0
  167. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_auto_router.py +0 -0
  168. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_auto_router_cjk.py +0 -0
  169. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_backend_health.py +0 -0
  170. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_budget.py +0 -0
  171. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_capability.py +0 -0
  172. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_capability_degraded_payload.py +0 -0
  173. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_capability_registry.py +0 -0
  174. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_capability_registry_cache_control.py +0 -0
  175. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_capability_tool_choice.py +0 -0
  176. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_claude_code_suitability.py +0 -0
  177. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_cli.py +0 -0
  178. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_cli_bind_warning.py +0 -0
  179. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_cli_stats.py +0 -0
  180. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_config.py +0 -0
  181. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_config_shim_actions.py +0 -0
  182. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_context_budget.py +0 -0
  183. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_continuous_probe.py +0 -0
  184. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_dashboard_endpoint.py +0 -0
  185. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_doctor.py +0 -0
  186. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_doctor_apply.py +0 -0
  187. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_doctor_cache_probe.py +0 -0
  188. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_drift_actions.py +0 -0
  189. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_drift_detection.py +0 -0
  190. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_drift_detection_integration.py +0 -0
  191. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_env_file.py +0 -0
  192. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_env_security.py +0 -0
  193. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_errors.py +0 -0
  194. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_examples_yaml.py +0 -0
  195. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fallback.py +0 -0
  196. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fallback_anthropic.py +0 -0
  197. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fallback_cache_control.py +0 -0
  198. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fallback_cache_observed.py +0 -0
  199. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fallback_cache_strip.py +0 -0
  200. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fallback_empty_response.py +0 -0
  201. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fallback_misconfig_warn.py +0 -0
  202. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fallback_paid_gate.py +0 -0
  203. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fallback_thinking.py +0 -0
  204. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fallback_tool_choice_emulate.py +0 -0
  205. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fix_h1_prometheus_drift.py +0 -0
  206. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fix_h2_h6_translation.py +0 -0
  207. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fix_h3_client_reuse.py +0 -0
  208. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fix_h4_h5_guards.py +0 -0
  209. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fix_h7_probe_restore.py +0 -0
  210. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fix_h8_launcher_auth.py +0 -0
  211. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fix_mA_routing.py +0 -0
  212. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fix_mB_adapters.py +0 -0
  213. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fix_mC_convert.py +0 -0
  214. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fix_mD_logging_metrics.py +0 -0
  215. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fix_mE_config_validation.py +0 -0
  216. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_fix_mF_ingress.py +0 -0
  217. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_gguf_introspect.py +0 -0
  218. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_guards_tool_loop.py +0 -0
  219. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_hardware.py +0 -0
  220. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_ingress_anthropic.py +0 -0
  221. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_ingress_count_tokens.py +0 -0
  222. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_ingress_profile.py +0 -0
  223. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_language_tax.py +0 -0
  224. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_language_tax_integration.py +0 -0
  225. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_launcher_mtp.py +0 -0
  226. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_launcher_mtp_fallback.py +0 -0
  227. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_launcher_provider_sync.py +0 -0
  228. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_memory_budget.py +0 -0
  229. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_memory_pressure.py +0 -0
  230. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_metrics_cache.py +0 -0
  231. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_metrics_collector.py +0 -0
  232. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_metrics_cost.py +0 -0
  233. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_metrics_endpoint.py +0 -0
  234. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_metrics_jsonl.py +0 -0
  235. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_metrics_prometheus.py +0 -0
  236. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_metrics_prometheus_cache.py +0 -0
  237. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_models_passthrough.py +0 -0
  238. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_openai_compat.py +0 -0
  239. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_openrouter_roster_diff.py +0 -0
  240. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_output_filters.py +0 -0
  241. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_output_filters_adapters.py +0 -0
  242. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_partial_stitch.py +0 -0
  243. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_plugins_integration.py +0 -0
  244. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_plugins_loader.py +0 -0
  245. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_plugins_registry.py +0 -0
  246. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_reasoning_strip.py +0 -0
  247. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_repair_byte_fallback.py +0 -0
  248. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_request_log.py +0 -0
  249. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_role_normalization.py +0 -0
  250. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_routing_adaptive.py +0 -0
  251. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_self_healing.py +0 -0
  252. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_setup_sh.py +0 -0
  253. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_state_store.py +0 -0
  254. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_token_estimation.py +0 -0
  255. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_token_estimation_accurate.py +0 -0
  256. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_tokens_saved_metric.py +0 -0
  257. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_tool_repair.py +0 -0
  258. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_tool_repair_lenient.py +0 -0
  259. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_tool_repair_r4.py +0 -0
  260. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_toolrepair_bench.py +0 -0
  261. {coderouter_cli-2.7.8 → coderouter_cli-2.7.9}/tests/test_translation_anthropic.py +0 -0
  262. {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.8
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
- Two targets are implemented here:
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 agents (codex / gemini) are declared in the config schema so
38
- providers.yaml is forward-compatible, but constructing an adapter for them
39
- raises a clear ``AdapterError`` until their phase (1b / 1c) lands.
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
- reads it from stdin; grok reads it from a private ``0600`` prompt file
48
- inside the resolved workdir its ``-p`` requires the prompt as an argv
49
- value, and argv would both hit Linux's ~128KiB ``MAX_ARG_STRLEN`` on huge
50
- prompts and leak the text into ``ps`` output).
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`` / ``grok``
149
- raises a non-retryable :class:`AdapterError` — the other targets are
150
- declared in the schema but not implemented until their phase
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 1b/1c.",
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 1b/1c add codex / gemini.
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 reads its print-mode prompt
179
- # from stdin (10MB cap), which keeps argv free of the (potentially
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 == "claude"
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) and ``grok`` (grok CLI, Phase 1d) are implemented;
175
- codex / gemini are declared at the schema level so configs are
176
- forward-compatible, but the adapter rejects them until their phase
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) and "
195
- "'grok' (Phase 1d) are implemented; 'codex' / 'gemini' pending."
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), and v2.7.8 added grok (Phase 1d). See [`docs/designs/external-agents-adapter.md`](../designs/external-agents-adapter.md) for the full design.
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.8)**: **`claude` (Claude Code CLI, Phase 1a) and `grok` (Grok CLI, Phase 1d) are implemented**. `codex` / `gemini` can be written into `providers.yaml` at the schema level, but constructing the adapter always rejects them (Phase 1b/1c are not yet implemented). The error message looks roughly like the following (the exact wording may differ between versions):
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 'codex' is not implemented yet (implemented: claude, grok).
18
- Wait for the agent's phase (1b/1c).
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 is collected in the [grok (Grok CLI)](#grok-grok-cli) section. Example configs for `codex` / `gemini` exist only as a commented-out preview at the bottom of `examples/providers-agent-cli.yaml` and do not work.
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.8, `claude` and `grok` are implemented; `codex` / `gemini` are rejected when the adapter is constructed** |
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
- | CLI fails to launch (`failed to launch ...`) | `command` (defaults to the same name as `agent`) isn't on `PATH` | Confirm `claude --version` / `grok --version` works. You can also point `command` at a full path |
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