Current section
Files
Jump to
Current section
Files
lib/codex/oauth/context.ex
defmodule Codex.OAuth.Context do
@moduledoc false
alias Codex.Auth.Store
alias Codex.Config.Defaults
alias Codex.Config.LayerStack
alias Codex.Net.CA
alias Codex.OAuth.Environment
alias Codex.OAuth.Provider.OpenAI
alias Codex.Runtime.Env, as: RuntimeEnv
@enforce_keys [
:cwd,
:codex_home,
:child_process_env,
:effective_config,
:api_base_url,
:auth_issuer,
:client_id,
:credentials_store_mode,
:ca_bundle_path,
:interactive?,
:environment,
:provider
]
defstruct [
:cwd,
:codex_home,
:child_process_env,
:effective_config,
:api_base_url,
:auth_issuer,
:client_id,
:credentials_store_mode,
:ca_bundle_path,
:interactive?,
:environment,
:provider,
storage: :auto,
presenter: nil,
browser_opener: nil
]
@type t :: %__MODULE__{
cwd: String.t() | nil,
codex_home: String.t(),
child_process_env: map(),
effective_config: map(),
api_base_url: String.t(),
auth_issuer: String.t(),
client_id: String.t(),
credentials_store_mode: Store.credentials_store_mode(),
ca_bundle_path: String.t() | nil,
interactive?: boolean(),
environment: Environment.t(),
provider: OpenAI.t(),
storage: :auto | :file | :memory,
presenter: term(),
browser_opener: term()
}
@spec resolve(keyword()) :: {:ok, t()} | {:error, term()}
def resolve(opts \\ []) when is_list(opts) do
with {:ok, child_process_env} <- resolve_child_env(opts),
{:ok, cwd} <- resolve_cwd(opts),
codex_home <- resolve_codex_home(opts, child_process_env),
{:ok, layers} <- LayerStack.load(codex_home, cwd) do
effective_config = LayerStack.effective_config(layers)
environment = resolve_environment(opts, child_process_env)
auth_issuer = resolve_auth_issuer(opts, effective_config)
client_id = Keyword.get(opts, :client_id, OpenAI.build(issuer: auth_issuer).client_id)
provider =
OpenAI.build(
issuer: auth_issuer,
client_id: client_id
)
{:ok,
%__MODULE__{
cwd: cwd,
codex_home: codex_home,
child_process_env: child_process_env,
effective_config: effective_config,
api_base_url: resolve_api_base_url(opts, effective_config, child_process_env),
auth_issuer: auth_issuer,
client_id: client_id,
credentials_store_mode: Store.credentials_store_mode(codex_home, cwd),
ca_bundle_path: CA.certificate_file(child_process_env),
interactive?: environment.interactive?,
environment: environment,
provider: provider,
storage: Keyword.get(opts, :storage, :auto),
presenter: Keyword.get(opts, :presenter),
browser_opener: Keyword.get(opts, :browser_opener)
}}
end
end
@spec resolve!(keyword()) :: t()
def resolve!(opts \\ []) do
case resolve(opts) do
{:ok, context} ->
context
{:error, reason} ->
raise ArgumentError, "failed to resolve OAuth context: #{inspect(reason)}"
end
end
defp resolve_child_env(opts) do
process_env = Keyword.get(opts, :process_env, Keyword.get(opts, :env))
with {:ok, overrides} <- RuntimeEnv.normalize_overrides(process_env) do
{:ok, Map.merge(System.get_env(), overrides)}
end
end
defp resolve_cwd(opts) do
case Keyword.get(opts, :cwd) do
nil ->
case File.cwd() do
{:ok, cwd} -> {:ok, cwd}
_ -> {:ok, nil}
end
cwd when is_binary(cwd) ->
if String.trim(cwd) == "", do: {:error, {:invalid_cwd, cwd}}, else: {:ok, cwd}
other ->
{:error, {:invalid_cwd, other}}
end
end
defp resolve_codex_home(opts, child_process_env) do
case Keyword.get(opts, :codex_home) || Map.get(child_process_env, "CODEX_HOME") do
value when is_binary(value) and value != "" ->
value
_ ->
home =
Map.get(child_process_env, "HOME") || Map.get(child_process_env, "USERPROFILE") ||
System.user_home!()
Path.join(home, ".codex")
end
end
defp resolve_environment(opts, child_process_env) do
Environment.detect(
os: Keyword.get(opts, :os),
env: child_process_env,
interactive?: Keyword.get(opts, :interactive?)
)
end
defp resolve_api_base_url(opts, effective_config, child_process_env) do
Keyword.get(opts, :api_base_url) ||
Keyword.get(opts, :openai_base_url) ||
Map.get(effective_config, "openai_base_url") ||
Map.get(child_process_env, "OPENAI_BASE_URL") ||
Defaults.openai_api_base_url()
end
defp resolve_auth_issuer(opts, effective_config) do
Keyword.get(opts, :auth_issuer) ||
Map.get(effective_config, "auth_issuer") ||
OpenAI.default_issuer()
end
end