diff --git a/README.md b/README.md index 3213a23..8e2ed65 100644 --- a/README.md +++ b/README.md @@ -99,13 +99,13 @@ turn's `exec resume` command. `:dangerously_bypass_approvals_and_sandbox`, `:skip_git_repo_check`, `:ephemeral`, `:config_overrides`, `:enabled_features`, `:disabled_features`, - `:images` + `:images`, `:output_schema` These settings are forwarded on fresh and resumed turns. Sandbox and approval policy become supported `-c` overrides on resume. `:working_dir` / `:cwd` remains the subprocess directory on both turns. Options that the resume command cannot preserve (`:cd`, `:add_dirs`, -`:search`, `:output_schema`) fail at session startup with +`:search`) fail at session startup with `{:error, {:unsupported_resume_option, option}}`. **Backend-only:** diff --git a/lib/gen_agent/backends/codex.ex b/lib/gen_agent/backends/codex.ex index 2ec429f..2b5fd58 100644 --- a/lib/gen_agent/backends/codex.ex +++ b/lib/gen_agent/backends/codex.ex @@ -2,7 +2,7 @@ defmodule GenAgent.Backends.Codex do @moduledoc """ `GenAgent.Backend` implementation backed by `CodexWrapper`. - CodexWrapper 0.5.2 streams NDJSON while closing CLI stdin. This + CodexWrapper 0.5.3 streams NDJSON while closing CLI stdin. This backend forwards translated events as they arrive, so `handle_stream_event/2` can observe progress during a turn. @@ -34,10 +34,10 @@ defmodule GenAgent.Backends.Codex do `:dangerously_bypass_approvals_and_sandbox`, `:skip_git_repo_check`, `:ephemeral`, `:config_overrides`, `:enabled_features`, `:disabled_features`, - `:images` + `:images`, `:output_schema` Options that cannot be preserved on `exec resume` (`:cd`, - `:add_dirs`, `:search`, `:output_schema`) are rejected by + `:add_dirs`, `:search`) are rejected by `start_session/1`. Use `:working_dir` / `:cwd` for a directory that persists across turns. Session options are translated into supported resume arguments; `:sandbox` and `:approval_policy` use config @@ -71,7 +71,8 @@ defmodule GenAgent.Backends.Codex do :config_overrides, :enabled_features, :disabled_features, - :images + :images, + :output_schema ] defstruct [ @@ -186,6 +187,9 @@ defmodule GenAgent.Backends.Codex do {:images, v}, e -> Enum.reduce(v, e, &Exec.image(&2, &1)) + {:output_schema, v}, e -> + Exec.output_schema(e, v) + _other, e -> e end) @@ -229,6 +233,9 @@ defmodule GenAgent.Backends.Codex do {:images, values}, r -> Enum.reduce(values, r, &ExecResume.image(&2, &1)) + {:output_schema, value}, r -> + ExecResume.output_schema(r, value) + _other, r -> r end) diff --git a/mix.exs b/mix.exs index f337273..3a980ba 100644 --- a/mix.exs +++ b/mix.exs @@ -32,7 +32,7 @@ defmodule GenAgentCodex.MixProject do defp deps do [ {:gen_agent, "~> 0.2.0"}, - {:codex_wrapper, "~> 0.5.2"}, + {:codex_wrapper, "~> 0.5.3"}, {:ex_doc, "~> 0.35", only: :dev, runtime: false}, {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false} diff --git a/mix.lock b/mix.lock index 5568b2b..0f3f22e 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, - "codex_wrapper": {:hex, :codex_wrapper, "0.5.2", "8c91e2972e0055cf437f3fbc9ac4842a41dd5c073a575c02c41cea570fe47a74", [:mix], [{:forcola, "~> 0.3.5", [hex: :forcola, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c58693fc249decc101f71b1aee7d30d5bdb64b072729ffc7548a66f8aa7d75e6"}, + "codex_wrapper": {:hex, :codex_wrapper, "0.5.3", "278806a6a0e4b91a6ac5398edcfc3524162f2220e2ee85917cd12bb0ecef747a", [:mix], [{:forcola, "~> 0.3.5", [hex: :forcola, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a93b0a27b2520e34fc5d90d91c208509ed4081fa678c7f0af25750f90893d9a7"}, "credo": {:hex, :credo, "1.7.19", "cc52129665fc7c15143d47838fda0f9cd6dac9ceced7bf4da6f85fcbfe64b12a", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "2d8bc95d5a7bb99dd2613621d4f08c6a3575c3fd4b62e6a2b48a100352a557b8"}, "dialyxir": {:hex, :dialyxir, "1.4.8", "7ef671a8aff9948b091d8c30f09467fbb16e77305cda451bce48109a0f5e021c", [:mix], [{:erlex, ">= 0.2.8", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "cbd5a851571e5dfeb32aaf2e840bfa98b7864cb3071bf2ef5d95d1276b12e072"}, "earmark_parser": {:hex, :earmark_parser, "1.4.46", "67607a0532e810c6f630a515c548d0b24949643f168cc556303bee4cf96105c7", [:mix], [], "hexpm", "9c44636e8a1c68c62f526b2dcd85d941dbbcee7ab82cf64ba06ce28bef8e89f5"}, diff --git a/test/gen_agent/backends/codex_executable_conformance_test.exs b/test/gen_agent/backends/codex_executable_conformance_test.exs index 23e51b5..74a06cb 100644 --- a/test/gen_agent/backends/codex_executable_conformance_test.exs +++ b/test/gen_agent/backends/codex_executable_conformance_test.exs @@ -90,6 +90,7 @@ defmodule GenAgent.Backends.CodexExecutableConformanceTest do enabled_features: ["fixture_feature"], disabled_features: ["other_feature"], images: ["/fixture/image.png"], + output_schema: "/fixture/response.json", env: [{"GEN_AGENT_FIXTURE", "configured"}] ) @@ -120,6 +121,10 @@ defmodule GenAgent.Backends.CodexExecutableConformanceTest do assert "fixture-model" in fresh_args assert "read-only" in fresh_args assert "first prompt" == List.last(fresh_args) + + assert Enum.chunk_every(fresh_args, 2, 1, :discard) + |> Enum.member?(["--output-schema", "/fixture/response.json"]) + assert File.read!(Path.join(context.directory, "fresh.env")) == "configured\n" assert {:ok, second} = GenAgent.ask(name, "follow-up prompt") @@ -133,6 +138,10 @@ defmodule GenAgent.Backends.CodexExecutableConformanceTest do assert "fixture_feature" in resume_args assert "other_feature" in resume_args assert "/fixture/image.png" in resume_args + + assert Enum.chunk_every(resume_args, 2, 1, :discard) + |> Enum.member?(["--output-schema", "/fixture/response.json"]) + assert "follow-up prompt" == List.last(resume_args) assert Enum.any?(resume_args, &String.contains?(&1, "approval_policy")) assert Enum.any?(resume_args, &String.contains?(&1, "mcp_servers.fixture.enabled=false")) diff --git a/test/gen_agent/backends/codex_streaming_test.exs b/test/gen_agent/backends/codex_streaming_test.exs index aafe4bf..fb667cc 100644 --- a/test/gen_agent/backends/codex_streaming_test.exs +++ b/test/gen_agent/backends/codex_streaming_test.exs @@ -79,7 +79,7 @@ defmodule GenAgent.Backends.CodexStreamingTest do end test "options that cannot be preserved on resume fail at startup" do - for option <- [:cd, :add_dirs, :search, :output_schema] do + for option <- [:cd, :add_dirs, :search] do assert {:error, {:unsupported_resume_option, ^option}} = Codex.start_session([{option, "fixture"}]) end