Current section
Files
Jump to
Current section
Files
lib/codex/thread/options.ex
defmodule Codex.Thread.Options do
@moduledoc """
Per-thread configuration options.
"""
alias Codex.FileSearch
@enforce_keys []
defstruct metadata: %{},
labels: %{},
auto_run: false,
transport: :exec,
approval_policy: nil,
approval_hook: nil,
approval_timeout_ms: 30_000,
sandbox: :default,
working_directory: nil,
additional_directories: [],
skip_git_repo_check: false,
network_access_enabled: nil,
web_search_enabled: false,
ask_for_approval: nil,
attachments: [],
file_search: nil
@type transport :: :exec | {:app_server, pid()}
@type network_access :: :enabled | :restricted
@type sandbox ::
:default
| :strict
| :permissive
| :read_only
| :workspace_write
| :danger_full_access
| :external_sandbox
| {:external_sandbox, network_access()}
| String.t()
@type t :: %__MODULE__{
metadata: map(),
labels: map(),
auto_run: boolean(),
transport: transport(),
approval_policy: module() | nil,
approval_hook: module() | nil,
approval_timeout_ms: pos_integer(),
sandbox: sandbox(),
working_directory: String.t() | nil,
additional_directories: [String.t()],
skip_git_repo_check: boolean(),
network_access_enabled: boolean() | nil,
web_search_enabled: boolean(),
ask_for_approval: :untrusted | :on_failure | :on_request | :never | String.t() | nil,
attachments: [map()] | [],
file_search: FileSearch.t() | nil
}
@doc """
Builds a thread options struct from various inputs.
"""
@spec new(map() | keyword() | t()) :: {:ok, t()} | {:error, term()}
def new(%__MODULE__{} = opts), do: {:ok, opts}
def new(attrs) when is_list(attrs), do: attrs |> Map.new() |> new()
def new(attrs) when is_map(attrs) do
metadata = Map.get(attrs, :metadata, Map.get(attrs, "metadata", %{}))
labels = Map.get(attrs, :labels, Map.get(attrs, "labels", %{}))
auto_run = Map.get(attrs, :auto_run, Map.get(attrs, "auto_run", false))
transport = Map.get(attrs, :transport, Map.get(attrs, "transport"))
approval_policy = Map.get(attrs, :approval_policy, Map.get(attrs, "approval_policy"))
approval_hook = Map.get(attrs, :approval_hook, Map.get(attrs, "approval_hook"))
approval_timeout_ms =
Map.get(attrs, :approval_timeout_ms, Map.get(attrs, "approval_timeout_ms", 30_000))
sandbox = Map.get(attrs, :sandbox, Map.get(attrs, "sandbox", :default))
working_directory =
Map.get(attrs, :working_directory, Map.get(attrs, "working_directory", Map.get(attrs, :cd)))
additional_directories =
Map.get(attrs, :additional_directories, Map.get(attrs, "additional_directories", []))
skip_git_repo_check =
Map.get(attrs, :skip_git_repo_check, Map.get(attrs, "skip_git_repo_check", false))
network_access_enabled =
Map.get(attrs, :network_access_enabled, Map.get(attrs, "network_access_enabled"))
web_search_enabled =
Map.get(attrs, :web_search_enabled, Map.get(attrs, "web_search_enabled", false))
ask_for_approval = Map.get(attrs, :ask_for_approval, Map.get(attrs, "ask_for_approval"))
attachments = Map.get(attrs, :attachments, Map.get(attrs, "attachments", []))
file_search = Map.get(attrs, :file_search, Map.get(attrs, "file_search"))
with {:ok, metadata} <- ensure_map(metadata, :metadata),
{:ok, labels} <- ensure_map(labels, :labels),
{:ok, attachments} <- ensure_list(attachments, :attachments),
{:ok, file_search} <- FileSearch.new(file_search),
{:ok, transport} <- normalize_transport(transport),
{:ok, sandbox} <- normalize_sandbox(sandbox),
true <- is_boolean(auto_run) or {:error, {:invalid_auto_run, auto_run}},
:ok <- validate_optional_string(working_directory, :working_directory),
{:ok, additional_directories} <-
normalize_string_list(additional_directories, :additional_directories),
true <-
is_boolean(skip_git_repo_check) or
{:error, {:invalid_skip_git_repo_check, skip_git_repo_check}},
{:ok, network_access_enabled} <-
validate_optional_boolean(network_access_enabled, :network_access_enabled),
true <-
is_boolean(web_search_enabled) or
{:error, {:invalid_web_search_enabled, web_search_enabled}},
{:ok, ask_for_approval} <- normalize_ask_for_approval(ask_for_approval),
true <-
(is_integer(approval_timeout_ms) and approval_timeout_ms > 0) or
{:error, {:invalid_timeout, approval_timeout_ms}} do
{:ok,
%__MODULE__{
metadata: metadata,
labels: labels,
auto_run: auto_run,
transport: transport,
approval_policy: approval_policy,
approval_hook: approval_hook,
approval_timeout_ms: approval_timeout_ms,
sandbox: sandbox,
working_directory: working_directory,
additional_directories: additional_directories,
skip_git_repo_check: skip_git_repo_check,
network_access_enabled: network_access_enabled,
web_search_enabled: web_search_enabled,
ask_for_approval: ask_for_approval,
attachments: attachments,
file_search: file_search
}}
else
{:error, _} = error -> error
end
end
defp ensure_map(value, _field) when is_map(value), do: {:ok, value}
defp ensure_map(nil, _field), do: {:ok, %{}}
defp ensure_map(value, field), do: {:error, {:invalid_map, field, value}}
defp ensure_list(value, _field) when is_list(value), do: {:ok, value}
defp ensure_list(nil, _field), do: {:ok, []}
defp ensure_list(value, field), do: {:error, {:invalid_list, field, value}}
defp validate_optional_string(nil, _field), do: :ok
defp validate_optional_string(value, _field) when is_binary(value), do: :ok
defp validate_optional_string(value, field), do: {:error, {:"invalid_#{field}", value}}
defp validate_optional_boolean(nil, _field), do: {:ok, nil}
defp validate_optional_boolean(value, _field) when is_boolean(value), do: {:ok, value}
defp validate_optional_boolean(value, field), do: {:error, {:"invalid_#{field}", value}}
defp normalize_string_list(list, _field) when list in [nil, []], do: {:ok, []}
defp normalize_string_list(list, field) when is_list(list) do
if Enum.all?(list, &is_binary/1) do
{:ok, list}
else
{:error, {:invalid_list, field, list}}
end
end
defp normalize_string_list(value, field), do: {:error, {:invalid_list, field, value}}
defp normalize_transport(nil) do
{:ok, Application.get_env(:codex_sdk, :default_transport, :exec)}
end
defp normalize_transport(:exec), do: {:ok, :exec}
defp normalize_transport("exec"), do: {:ok, :exec}
defp normalize_transport({:app_server, pid}) when is_pid(pid), do: {:ok, {:app_server, pid}}
defp normalize_transport(value), do: {:error, {:invalid_transport, value}}
defp normalize_sandbox(nil), do: {:ok, :default}
defp normalize_sandbox(:default), do: {:ok, :default}
defp normalize_sandbox(:strict), do: {:ok, :strict}
defp normalize_sandbox(:permissive), do: {:ok, :permissive}
defp normalize_sandbox(:read_only), do: {:ok, :read_only}
defp normalize_sandbox(:workspace_write), do: {:ok, :workspace_write}
defp normalize_sandbox(:danger_full_access), do: {:ok, :danger_full_access}
defp normalize_sandbox(:external_sandbox), do: {:ok, :external_sandbox}
defp normalize_sandbox({:external_sandbox, :enabled}), do: {:ok, {:external_sandbox, :enabled}}
defp normalize_sandbox({:external_sandbox, :restricted}),
do: {:ok, {:external_sandbox, :restricted}}
defp normalize_sandbox("read-only"), do: {:ok, :read_only}
defp normalize_sandbox("workspace-write"), do: {:ok, :workspace_write}
defp normalize_sandbox("danger-full-access"), do: {:ok, :danger_full_access}
defp normalize_sandbox("external-sandbox"), do: {:ok, :external_sandbox}
defp normalize_sandbox(value) when is_binary(value) do
# Accept arbitrary strings for forward compatibility with the upstream CLI, but
# keep validating common values.
{:ok, value}
end
defp normalize_sandbox(value), do: {:error, {:invalid_sandbox, value}}
defp normalize_ask_for_approval(nil), do: {:ok, nil}
defp normalize_ask_for_approval(:untrusted), do: {:ok, :untrusted}
defp normalize_ask_for_approval(:on_failure), do: {:ok, :on_failure}
defp normalize_ask_for_approval(:on_request), do: {:ok, :on_request}
defp normalize_ask_for_approval(:never), do: {:ok, :never}
defp normalize_ask_for_approval("untrusted"), do: {:ok, :untrusted}
defp normalize_ask_for_approval("on-failure"), do: {:ok, :on_failure}
defp normalize_ask_for_approval("on-request"), do: {:ok, :on_request}
defp normalize_ask_for_approval("never"), do: {:ok, :never}
defp normalize_ask_for_approval(value) when is_binary(value), do: {:ok, value}
defp normalize_ask_for_approval(value), do: {:error, {:invalid_ask_for_approval, value}}
end