Current section
Files
Jump to
Current section
Files
lib/codex/config/overrides.ex
defmodule Codex.Config.Overrides do
@moduledoc false
alias Codex.Options
alias Codex.Thread.Options, as: ThreadOptions
@spec derived_overrides(Options.t(), ThreadOptions.t() | nil) :: [{String.t(), term()}]
def derived_overrides(%Options{} = codex_opts, thread_opts) do
[]
|> maybe_put_override("model_provider", thread_value(thread_opts, :model_provider))
|> maybe_put_override("base_instructions", thread_value(thread_opts, :base_instructions))
|> maybe_put_override(
"developer_instructions",
thread_value(thread_opts, :developer_instructions)
)
|> maybe_put_override(
"model_reasoning_summary",
pick(
thread_value(thread_opts, :model_reasoning_summary),
codex_opts.model_reasoning_summary
)
)
|> maybe_put_override(
"model_verbosity",
pick(thread_value(thread_opts, :model_verbosity), codex_opts.model_verbosity)
)
|> maybe_put_override(
"model_context_window",
pick(thread_value(thread_opts, :model_context_window), codex_opts.model_context_window)
)
|> maybe_put_override(
"model_supports_reasoning_summaries",
pick(
thread_value(thread_opts, :model_supports_reasoning_summaries),
codex_opts.model_supports_reasoning_summaries
)
)
|> maybe_put_override(
"history.persistence",
pick(thread_value(thread_opts, :history_persistence), codex_opts.history_persistence)
)
|> maybe_put_override(
"history.max_bytes",
pick(thread_value(thread_opts, :history_max_bytes), codex_opts.history_max_bytes)
)
|> maybe_put_override(
"features.apply_patch_freeform",
thread_value(thread_opts, :apply_patch_freeform_enabled)
)
|> maybe_put_override(
"features.view_image_tool",
thread_value(thread_opts, :view_image_tool_enabled)
)
|> maybe_put_override(
"features.unified_exec",
thread_value(thread_opts, :unified_exec_enabled)
)
|> maybe_put_override("features.skills", thread_value(thread_opts, :skills_enabled))
|> maybe_put_override_true(
"features.web_search_request",
thread_value(thread_opts, :web_search_enabled)
)
|> add_shell_environment_policy_overrides(
thread_value(thread_opts, :shell_environment_policy)
)
|> add_sandbox_policy_overrides(thread_value(thread_opts, :sandbox_policy))
|> add_provider_tuning_overrides(thread_opts)
end
@spec merge_config(map() | nil, Options.t(), ThreadOptions.t()) :: map() | nil
def merge_config(config, %Options{} = codex_opts, %ThreadOptions{} = thread_opts) do
base = normalize_config_map(config)
overrides = derived_overrides(codex_opts, thread_opts)
merged =
Enum.reduce(overrides, base, fn {key, value}, acc ->
if has_config_key?(acc, key) do
acc
else
Map.put(acc, key, value)
end
end)
case merged do
%{} = map when map_size(map) == 0 -> nil
%{} = map -> map
_ -> nil
end
end
@spec cli_args([ThreadOptions.config_override()]) :: [String.t()]
def cli_args(overrides) do
overrides
|> List.wrap()
|> Enum.flat_map(&config_override_arg/1)
end
defp config_override_arg({key, value}) do
["--config", "#{key}=#{encode_override_value(value)}"]
end
defp config_override_arg(value) when is_binary(value) and value != "" do
["--config", value]
end
defp config_override_arg(_), do: []
defp encode_override_value(value) when is_binary(value), do: inspect(value)
defp encode_override_value(value) when is_boolean(value), do: to_string(value)
defp encode_override_value(value) when is_integer(value) or is_float(value),
do: to_string(value)
defp encode_override_value(value) when is_list(value) do
"[" <> Enum.map_join(value, ",", &encode_override_value/1) <> "]"
end
defp encode_override_value(%{} = value) do
"{" <>
Enum.map_join(value, ",", fn {key, entry} ->
"#{encode_override_key(key)}=#{encode_override_value(entry)}"
end) <> "}"
end
defp encode_override_value(value), do: inspect(value)
defp encode_override_key(key), do: inspect(to_string(key))
defp maybe_put_override(overrides, _key, nil), do: overrides
defp maybe_put_override(overrides, _key, ""), do: overrides
defp maybe_put_override(overrides, key, value), do: overrides ++ [{key, value}]
defp maybe_put_override_true(overrides, key, true), do: overrides ++ [{key, true}]
defp maybe_put_override_true(overrides, _key, _value), do: overrides
defp pick(nil, fallback), do: fallback
defp pick(value, _fallback), do: value
defp normalize_config_map(nil), do: %{}
defp normalize_config_map(%{} = config), do: config
defp normalize_config_map(_), do: %{}
defp has_config_key?(%{} = config, key) when is_binary(key) do
Map.has_key?(config, key) ||
Map.has_key?(config, String.to_atom(key)) ||
has_nested_key?(config, String.split(key, "."))
end
defp has_config_key?(_config, _key), do: false
defp has_nested_key?(_config, []), do: false
defp has_nested_key?(config, [segment]) do
case fetch_key(config, segment) do
nil -> false
_ -> true
end
end
defp has_nested_key?(config, [segment | rest]) do
case fetch_key(config, segment) do
%{} = nested -> has_nested_key?(nested, rest)
_ -> false
end
end
defp fetch_key(map, segment) when is_map(map) and is_binary(segment) do
cond do
Map.has_key?(map, segment) ->
Map.get(map, segment)
Map.has_key?(map, String.to_atom(segment)) ->
Map.get(map, String.to_atom(segment))
true ->
nil
end
end
defp add_shell_environment_policy_overrides(overrides, nil), do: overrides
defp add_shell_environment_policy_overrides(overrides, policy) do
overrides
|> maybe_put_override(
"shell_environment_policy.inherit",
fetch_any(policy, ["inherit", :inherit])
)
|> maybe_put_override(
"shell_environment_policy.ignore_default_excludes",
fetch_any(policy, ["ignore_default_excludes", :ignore_default_excludes])
)
|> maybe_put_override(
"shell_environment_policy.exclude",
fetch_any(policy, ["exclude", :exclude])
)
|> maybe_put_override(
"shell_environment_policy.include_only",
fetch_any(policy, ["include_only", :include_only])
)
|> maybe_put_override("shell_environment_policy.set", fetch_any(policy, ["set", :set]))
end
defp add_sandbox_policy_overrides(overrides, nil), do: overrides
defp add_sandbox_policy_overrides(overrides, policy) do
policy = normalize_sandbox_policy(policy)
type = normalize_sandbox_policy_type(fetch_any(policy, ["type", :type]))
network_access =
normalize_network_access(
fetch_any(policy, ["network_access", :network_access, "networkAccess", :networkAccess])
)
overrides =
overrides
|> maybe_put_override(
"sandbox_workspace_write.writable_roots",
fetch_any(policy, ["writable_roots", :writable_roots, "writableRoots", :writableRoots])
)
|> maybe_put_override(
"sandbox_workspace_write.exclude_tmpdir_env_var",
fetch_any(policy, [
"exclude_tmpdir_env_var",
:exclude_tmpdir_env_var,
"excludeTmpdirEnvVar",
:excludeTmpdirEnvVar
])
)
|> maybe_put_override(
"sandbox_workspace_write.exclude_slash_tmp",
fetch_any(policy, [
"exclude_slash_tmp",
:exclude_slash_tmp,
"excludeSlashTmp",
:excludeSlashTmp
])
)
case {type, network_access} do
{"external-sandbox", value} when is_boolean(value) ->
maybe_put_override(overrides, "sandbox_external.network_access", value)
{_type, value} when is_boolean(value) ->
maybe_put_override(overrides, "sandbox_workspace_write.network_access", value)
_ ->
overrides
end
end
defp normalize_sandbox_policy(%{} = policy), do: policy
defp normalize_sandbox_policy(list) when is_list(list), do: Map.new(list)
defp normalize_sandbox_policy(policy) when is_atom(policy), do: %{type: policy}
defp normalize_sandbox_policy(policy) when is_binary(policy), do: %{type: policy}
defp normalize_sandbox_policy(_), do: %{}
defp normalize_sandbox_policy_type(nil), do: nil
defp normalize_sandbox_policy_type(:external_sandbox), do: "external-sandbox"
defp normalize_sandbox_policy_type("external_sandbox"), do: "external-sandbox"
defp normalize_sandbox_policy_type("externalSandbox"), do: "external-sandbox"
defp normalize_sandbox_policy_type("external-sandbox"), do: "external-sandbox"
defp normalize_sandbox_policy_type(value) when is_atom(value), do: Atom.to_string(value)
defp normalize_sandbox_policy_type(value) when is_binary(value), do: value
defp normalize_sandbox_policy_type(_), do: nil
defp normalize_network_access(nil), do: nil
defp normalize_network_access(value) when is_boolean(value), do: value
defp normalize_network_access(:enabled), do: true
defp normalize_network_access(:restricted), do: false
defp normalize_network_access("enabled"), do: true
defp normalize_network_access("restricted"), do: false
defp normalize_network_access(_), do: nil
defp add_provider_tuning_overrides(overrides, %ThreadOptions{} = thread_opts) do
provider_id = provider_id_for_tuning(thread_opts)
overrides
|> maybe_put_override(
"model_providers.#{provider_id}.request_max_retries",
thread_opts.request_max_retries
)
|> maybe_put_override(
"model_providers.#{provider_id}.stream_max_retries",
thread_opts.stream_max_retries
)
|> maybe_put_override(
"model_providers.#{provider_id}.stream_idle_timeout_ms",
thread_opts.stream_idle_timeout_ms
)
end
defp add_provider_tuning_overrides(overrides, _), do: overrides
defp provider_id_for_tuning(%ThreadOptions{model_provider: provider})
when is_binary(provider) and provider != "" do
provider
end
defp provider_id_for_tuning(%ThreadOptions{oss: true, local_provider: provider})
when is_binary(provider) and provider != "" do
provider
end
defp provider_id_for_tuning(_), do: "openai"
defp thread_value(nil, _field), do: nil
defp thread_value(%ThreadOptions{} = thread_opts, field), do: Map.get(thread_opts, field)
defp fetch_any(map, keys) when is_map(map) and is_list(keys) do
Enum.reduce_while(keys, nil, fn key, _acc ->
case Map.fetch(map, key) do
{:ok, value} -> {:halt, value}
:error -> {:cont, nil}
end
end)
end
end