Current section

Files

Jump to
codex_sdk examples live_app_server_approvals.exs
Raw

examples/live_app_server_approvals.exs

Mix.Task.run("app.start")
alias Codex.RunResultStreaming
alias Codex.Events
alias Codex.Items
defmodule CodexExamples.LiveAppServerApprovals do
@moduledoc false
@default_prompt "Run `pwd` and `ls -la` in the current working directory, then reply with exactly ok."
@approval_methods [
"item/commandExecution/requestApproval",
"item/fileChange/requestApproval"
]
def main(argv) do
prompt =
case argv do
[] -> @default_prompt
values -> Enum.join(values, " ")
end
codex_path = fetch_codex_path!()
ensure_app_server_supported!(codex_path)
{:ok, codex_opts} =
Codex.Options.new(%{
codex_path_override: codex_path
})
{:ok, conn} = Codex.AppServer.connect(codex_opts, init_timeout_ms: 30_000)
try do
parent = self()
{:ok, approval_pid} =
Task.start_link(fn ->
:ok = Codex.AppServer.subscribe(conn, methods: @approval_methods)
send(parent, {:approvals_ready, self()})
approval_loop(conn, parent)
end)
:ok = await_approvals_ready!(approval_pid)
{:ok, thread} =
Codex.start_thread(codex_opts, %{
transport: {:app_server, conn},
working_directory: File.cwd!(),
ask_for_approval: :untrusted
})
IO.puts("""
Streaming over app-server with manual approvals.
prompt: #{prompt}
If Codex requests approval for a command or file change, this example auto-responds.
""")
case Codex.Thread.run_streamed(thread, prompt, %{timeout_ms: 120_000}) do
{:ok, stream} ->
stream
|> RunResultStreaming.raw_events()
|> Enum.each(&print_event/1)
IO.puts("\nusage: #{inspect(RunResultStreaming.usage(stream))}")
{:error, reason} ->
Mix.raise("Streaming run failed: #{inspect(reason)}")
end
handled = drain_handled_approvals([])
if handled == [] do
IO.puts("No approval requests were observed for this prompt.")
else
IO.puts("Handled approvals: #{Enum.join(Enum.reverse(handled), ", ")}")
end
send(approval_pid, :stop)
after
:ok = Codex.AppServer.disconnect(conn)
end
end
defp approval_loop(conn, parent) do
receive do
:stop ->
:ok
{:codex_request, id, "item/commandExecution/requestApproval", params} ->
decision = command_decision(params)
:ok = Codex.AppServer.respond(conn, id, %{decision: decision})
send(parent, {:approval_handled, "commandExecution"})
approval_loop(conn, parent)
{:codex_request, id, "item/fileChange/requestApproval", _params} ->
:ok = Codex.AppServer.respond(conn, id, %{decision: "accept"})
send(parent, {:approval_handled, "fileChange"})
approval_loop(conn, parent)
_other ->
approval_loop(conn, parent)
end
end
defp command_decision(%{"proposedExecpolicyAmendment" => argv})
when is_list(argv) and argv != [] do
%{"acceptWithExecpolicyAmendment" => %{"execpolicyAmendment" => argv}}
end
defp command_decision(_params), do: "acceptForSession"
defp await_approvals_ready!(task_pid) when is_pid(task_pid) do
receive do
{:approvals_ready, ^task_pid} -> :ok
after
5_000 -> Mix.raise("Timed out waiting for approval subscription")
end
end
defp drain_handled_approvals(acc) do
receive do
{:approval_handled, kind} ->
drain_handled_approvals([kind | acc])
after
0 ->
acc
end
end
defp print_event(%Events.ItemAgentMessageDelta{item: %{"text" => delta}})
when is_binary(delta) do
IO.write(delta)
end
defp print_event(%Events.ItemCompleted{item: %Items.AgentMessage{text: text}}) do
if is_binary(text) and String.trim(text) != "" do
IO.puts("\n\n[agent_message.completed]\n#{text}\n")
end
end
defp print_event(%Events.TurnCompleted{status: status}) do
IO.puts("\n[turn.completed] status=#{inspect(status)}")
end
defp print_event(_other), do: :ok
defp fetch_codex_path! do
System.get_env("CODEX_PATH") ||
System.find_executable("codex") ||
Mix.raise("""
Unable to locate the `codex` CLI.
Install the Codex CLI and ensure it is on your PATH or set CODEX_PATH.
""")
end
defp ensure_app_server_supported!(codex_path) do
{_output, status} = System.cmd(codex_path, ["app-server", "--help"], stderr_to_stdout: true)
if status != 0 do
Mix.raise("""
Your `codex` CLI does not appear to support `codex app-server`.
Upgrade via `npm install -g @openai/codex` and retry.
""")
end
end
end
CodexExamples.LiveAppServerApprovals.main(System.argv())