Current section
Files
Jump to
Current section
Files
lib/adk/tool/context.ex
defmodule ADK.Tool.Context do
@moduledoc """
Context passed to tool execution.
Wraps a `CallbackContext` and adds `function_call_id` and its own
`Actions`. Each tool call gets its own `ToolContext` with independent
actions that are merged after all tool calls complete.
"""
alias ADK.Agent.CallbackContext
alias ADK.Artifact.InMemory, as: ArtifactService
alias ADK.Event.Actions
@type t :: %__MODULE__{
callback_context: CallbackContext.t(),
function_call_id: String.t() | nil,
actions: Actions.t()
}
defstruct [
:callback_context,
:function_call_id,
actions: %Actions{}
]
@doc "Creates a new tool context from a callback context and function call ID."
@spec new(CallbackContext.t(), String.t() | nil) :: t()
def new(%CallbackContext{} = cb_ctx, function_call_id \\ nil) do
%__MODULE__{
callback_context: cb_ctx,
function_call_id: function_call_id,
actions: %Actions{}
}
end
@doc "Gets a value from session state, checking tool actions then callback actions then session."
@spec get_state(t(), String.t()) :: any()
def get_state(%__MODULE__{actions: actions, callback_context: cb_ctx}, key) do
case Map.fetch(actions.state_delta, key) do
{:ok, value} -> value
:error -> CallbackContext.get_state(cb_ctx, key)
end
end
@doc "Sets a value in the tool actions state_delta."
@spec set_state(t(), String.t(), any()) :: t()
def set_state(%__MODULE__{actions: actions} = ctx, key, value) do
new_delta = Map.put(actions.state_delta, key, value)
%{ctx | actions: %{actions | state_delta: new_delta}}
end
@doc "Returns the agent name from the underlying callback context."
@spec agent_name(t()) :: String.t() | nil
def agent_name(%__MODULE__{callback_context: cb_ctx}) do
CallbackContext.agent_name(cb_ctx)
end
@doc "Searches memory for entries matching the query."
@spec search_memory(t(), String.t()) :: {:ok, [ADK.Memory.Entry.t()]}
def search_memory(%__MODULE__{callback_context: cb_ctx}, query) do
CallbackContext.search_memory(cb_ctx, query)
end
@doc """
Saves an artifact and tracks it in the tool's artifact_delta.
Returns `{:ok, version, updated_context}` or `{:error, reason}`.
"""
@spec save_artifact(t(), String.t(), ADK.Types.Part.t()) ::
{:ok, non_neg_integer(), t()} | {:error, term()}
def save_artifact(%__MODULE__{} = ctx, filename, part) do
inv_ctx = ctx.callback_context.invocation_context
if inv_ctx.artifact_service do
save_opts = [
app_name: inv_ctx.session.app_name,
user_id: inv_ctx.session.user_id,
session_id: inv_ctx.session.id,
filename: filename,
part: part
]
case ArtifactService.save(inv_ctx.artifact_service, save_opts) do
{:ok, version} ->
updated_delta = Map.put(ctx.actions.artifact_delta, filename, version)
updated_ctx = %{ctx | actions: %{ctx.actions | artifact_delta: updated_delta}}
{:ok, version, updated_ctx}
{:error, _} = err ->
err
end
else
{:error, :no_artifact_service}
end
end
@doc "Loads an artifact by filename. Version 0 (default) loads the latest."
@spec load_artifact(t(), String.t(), non_neg_integer()) ::
{:ok, ADK.Types.Part.t()} | {:error, term()}
def load_artifact(%__MODULE__{} = ctx, filename, version \\ 0) do
inv_ctx = ctx.callback_context.invocation_context
if inv_ctx.artifact_service do
ArtifactService.load(inv_ctx.artifact_service,
app_name: inv_ctx.session.app_name,
user_id: inv_ctx.session.user_id,
session_id: inv_ctx.session.id,
filename: filename,
version: version
)
else
{:error, :no_artifact_service}
end
end
@doc "Lists artifact filenames for the current session."
@spec list_artifacts(t()) :: {:ok, [String.t()]} | {:error, term()}
def list_artifacts(%__MODULE__{} = ctx) do
inv_ctx = ctx.callback_context.invocation_context
if inv_ctx.artifact_service do
ArtifactService.list(inv_ctx.artifact_service,
app_name: inv_ctx.session.app_name,
user_id: inv_ctx.session.user_id,
session_id: inv_ctx.session.id
)
else
{:error, :no_artifact_service}
end
end
end