Run a command with a JSON Schema and decode the final agent message.
run/4 takes a schema as a map, owns the temporary --output-schema
file for the life of the process, runs the command with --json, and
decodes the final agent message. decode/2 does the decoding step for a
%CodexWrapper.Result{} the caller already holds, for example from a run
that set an explicit schema path. The explicit-path setters
(Exec.output_schema/2 and friends) are unchanged.
schema = %{
"type" => "object",
"properties" => %{"name" => %{"type" => "string"}},
"required" => ["name"],
"additionalProperties" => false
}
{:ok, %{"name" => name}, %{result: _result, events: _events}} =
CodexWrapper.Exec.new("Extract the name from: Hi, I'm Ada Lovelace.")
|> CodexWrapper.Structured.run(schema, CodexWrapper.Config.new())Schema enforcement
The schema is enforced by codex (--output-schema). This module does not
re-validate the decoded value against it. It checks only that the final
message is a JSON object or array; the optional :parse function is the
caller-side check that the value has the expected domain shape.
Final message selection
The decoded message is the last completed agent_message item after the
last turn.started event. Every parsed event is returned alongside, so
earlier messages remain available. The turn outcome is the last
turn.completed or turn.failed event. A top-level error event fails
the run only when no terminal turn event follows it.
Outcomes
Success is {:ok, value, detail} where detail is
%{result: Result.t(), events: [JsonLineEvent.t()], text: String.t()}.
Failures after the process ran carry the same detail map (plus the
fields noted) so the native output is never lost:
{:turn_failed, detail}-- the turn failed;detail.erroris the native error object or message{:process_failed, detail}-- codex exited non-zero, including after aturn.completed{:no_terminal, detail}-- noturn.completedwas emitted{:no_structured_output, detail}-- no final agent message, or its text was empty ornull{:invalid_json, detail}-- the final message was not JSON;detail.reasonis theJason.DecodeError{:invalid_structured_output, detail}-- the decoded value was not a JSON object or array{:parse_failed, reason, detail}--:parsereturned{:error, reason}{:invalid_parse_result, value, detail}--:parsereturned neither{:ok, _}nor{:error, _}
Failures before spawning are {:schema_encode, reason} and
{:schema_file, reason}. Runner errors such as {:timeout, ms} and
{:unsupported, capability} are returned unchanged.
Summary
Functions
Decode the final agent message of a --json run the caller already made.
Run command with schema and decode its final agent message.
Types
@type command() :: CodexWrapper.Exec.t() | CodexWrapper.ExecResume.t() | CodexWrapper.ExecFork.t() | CodexWrapper.Review.t()
@type detail() :: %{ :result => CodexWrapper.Result.t(), :events => [CodexWrapper.JsonLineEvent.t()], optional(atom()) => term() }
@type error() :: {:turn_failed, detail()} | {:process_failed, detail()} | {:no_terminal, detail()} | {:no_structured_output, detail()} | {:invalid_json, detail()} | {:invalid_structured_output, detail()} | {:parse_failed, term(), detail()} | {:invalid_parse_result, term(), detail()} | {:schema_encode, term()} | {:schema_file, term()} | term()
Functions
@spec decode(CodexWrapper.Result.t(), keyword()) :: {:ok, term(), detail()} | {:error, error()}
Decode the final agent message of a --json run the caller already made.
Accepts the same :parse option as run/4 and returns the same outcomes.
It never starts a process.
@spec run(command(), map() | list(), CodexWrapper.Config.t(), keyword()) :: {:ok, term(), detail()} | {:error, error()}
Run command with schema and decode its final agent message.
Options:
:parse-- a 1-arity function mapping the decoded value to{:ok, value}or{:error, reason}. Defaults to returning the value.
Any other options are passed to the command's execute/3, so observed
execution options work as they do there.
The schema file is created with mode 0600 in System.tmp_dir!/0 and
removed after the process exits, whether it succeeded, failed or raised.