Current section

Files

Jump to
agent_session_manager lib asm session state.ex
Raw

lib/asm/session/state.ex

defmodule ASM.Session.State do
@moduledoc """
Aggregate state owned by `ASM.Session.Server`.
"""
alias ASM.{Provider, RuntimeAuth}
@enforce_keys [:session_id, :provider, :provider_profile, :options, :runtime_auth]
defstruct [
:session_id,
:provider,
:provider_profile,
:options,
:runtime_auth,
status: :ready,
active_runs: %{},
run_monitors: %{},
run_queue: :queue.new(),
pending_approval_index: %{},
checkpoint: nil,
cost: %{input_tokens: 0, output_tokens: 0, cost_usd: 0.0}
]
@type t :: %__MODULE__{
session_id: String.t(),
provider: Provider.t(),
provider_profile: Provider.Profile.t(),
options: keyword(),
runtime_auth: RuntimeAuth.t(),
status: :ready | :stopped,
active_runs: %{optional(String.t()) => pid()},
run_monitors: %{optional(pid()) => reference()},
# :queue is opaque to Dialyzer; keep this field generic at the type level.
run_queue: term(),
pending_approval_index: %{optional(String.t()) => pid()},
checkpoint: map() | nil,
cost: %{
required(:input_tokens) => non_neg_integer(),
required(:output_tokens) => non_neg_integer(),
required(:cost_usd) => float()
}
}
@type queued_run :: %{
required(:run_id) => String.t(),
required(:prompt) => String.t(),
required(:opts) => keyword()
}
@spec new(String.t(), Provider.t(), keyword(), RuntimeAuth.t()) :: t()
def new(session_id, %Provider{} = provider, options, %RuntimeAuth{} = runtime_auth)
when is_binary(session_id) do
%__MODULE__{
session_id: session_id,
provider: provider,
provider_profile: profile_from_options(provider.profile, options),
options: options,
runtime_auth: runtime_auth
}
end
defp profile_from_options(%Provider.Profile{} = profile, options) when is_list(options) do
%Provider.Profile{
profile
| max_concurrent_runs:
positive_integer_option(options, :max_concurrent_runs, profile.max_concurrent_runs),
max_queued_runs:
non_neg_integer_option(options, :max_queued_runs, profile.max_queued_runs)
}
end
defp positive_integer_option(options, key, default) do
case Keyword.get(options, key, default) do
value when is_integer(value) and value > 0 -> value
_other -> default
end
end
defp non_neg_integer_option(options, key, default) do
case Keyword.get(options, key, default) do
value when is_integer(value) and value >= 0 -> value
_other -> default
end
end
end