-module(claude@agent). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/claude/agent.gleam"). -export([extract_tool_calls/1, build_tool_results_message/1, event_loop/6, run/2, run_with_messages/2]). -export_type([agent_result/0, agent_error/0, agent_event/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -type agent_result() :: {agent_result, list(claude@types@message:message_param()), claude@types@message:message(), integer(), integer(), integer()}. -type agent_error() :: {api_call_failed, claude@types@error:api_error()} | {max_iterations_reached, list(claude@types@message:message_param()), integer()}. -type agent_event() :: {started, binary()} | {assistant_response, claude@types@message:message()} | {tool_executing, binary(), binary()} | {tool_completed, binary(), {ok, binary()} | {error, binary()}} | {done, agent_result()} | {failed, agent_error()}. -file("src/claude/agent.gleam", 50). ?DOC(" Extract tool-use blocks from a list of content blocks.\n"). -spec extract_tool_calls(list(claude@types@content:content_block())) -> list(claude@types@content:content_block()). extract_tool_calls(Content) -> gleam@list:filter(Content, fun(Block) -> case Block of {tool_use, _, _, _} -> true; _ -> false end end). -file("src/claude/agent.gleam", 62). ?DOC( " Build a user message containing tool results.\n" "\n" " Each result is a tuple of (tool_use_id, Ok(content) | Error(error_message)).\n" ). -spec build_tool_results_message( list({binary(), {ok, binary()} | {error, binary()}}) ) -> claude@types@message:message_param(). build_tool_results_message(Results) -> Blocks = gleam@list:map( Results, fun(Result) -> {Id, Outcome} = Result, case Outcome of {ok, Content} -> claude@types@content:tool_result(Id, Content, false); {error, Err} -> claude@types@content:tool_result(Id, Err, true) end end ), {message_param, user, {block_content, Blocks}}. -file("src/claude/agent.gleam", 124). ?DOC( " Recursive agent loop that emits events at each step.\n" "\n" " This is the single canonical implementation of the agent loop.\n" " Both `agent.run` (with a no-op callback) and `actor.run_with_events`\n" " delegate to this function.\n" "\n" " The `on_event` callback is called at each notable step. Pass `fn(_) { Nil }`\n" " for a silent loop, or a real callback for event-driven usage.\n" ). -spec event_loop( claude@agent@config:agent_config(), list(claude@types@message:message_param()), integer(), integer(), integer(), fun((agent_event()) -> nil) ) -> {ok, agent_result()} | {error, agent_error()}. event_loop(Config, Messages, Iteration, Total_input, Total_output, On_event) -> case Iteration >= erlang:element(9, Config) of true -> Err = {max_iterations_reached, lists:reverse(Messages), Iteration}, On_event({failed, Err}), {error, Err}; false -> case claude@messages:create( {request_params, erlang:element(2, Config), {some, erlang:element(3, Config)}, {some, erlang:element(4, Config)}, lists:reverse(Messages), erlang:element(6, Config), erlang:element(5, Config), erlang:element(7, Config), erlang:element(8, Config)} ) of {error, Api_error} -> Err@1 = {api_call_failed, Api_error}, On_event({failed, Err@1}), {error, Err@1}; {ok, Response} -> On_event({assistant_response, Response}), New_input = Total_input + erlang:element( 2, erlang:element(8, Response) ), New_output = Total_output + erlang:element( 3, erlang:element(8, Response) ), Assistant_params = claude@types@message:content_to_params( erlang:element(4, Response) ), Assistant_msg = {message_param, assistant, {block_content, Assistant_params}}, Updated_messages = [Assistant_msg | Messages], case erlang:element(6, Response) of {some, tool_use_stop} -> Tool_calls = extract_tool_calls( erlang:element(4, Response) ), gleam@list:each( Tool_calls, fun(Block) -> case Block of {tool_use, Id, Name, _} -> On_event({tool_executing, Name, Id}); _ -> nil end end ), Results = claude@agent@tool_runner:execute_concurrent( Tool_calls, erlang:element(6, Config), erlang:element(10, Config) ), gleam@list:each( Results, fun(Result) -> {Id@1, Outcome} = Result, On_event({tool_completed, Id@1, Outcome}) end ), Tool_results_msg = build_tool_results_message( Results ), Next_messages = [Tool_results_msg | Updated_messages], event_loop( Config, Next_messages, Iteration + 1, New_input, New_output, On_event ); _ -> Result@1 = {agent_result, lists:reverse(Updated_messages), Response, Iteration + 1, New_input, New_output}, On_event({done, Result@1}), {ok, Result@1} end end end. -file("src/claude/agent.gleam", 84). ?DOC( " Run the agent loop.\n" "\n" " Starts with the given prompt, sends messages to the API, and automatically\n" " handles tool-use responses by executing tools and feeding results back.\n" " Continues until the model stops with a non-tool-use reason or max_iterations\n" " is reached.\n" ). -spec run(claude@agent@config:agent_config(), binary()) -> {ok, agent_result()} | {error, agent_error()}. run(Config, Prompt) -> Initial_messages = [claude@types@message:new_user(Prompt)], event_loop( Config, lists:reverse(Initial_messages), 0, 0, 0, fun(_) -> nil end ). -file("src/claude/agent.gleam", 102). ?DOC( " Run the agent loop with an existing message history.\n" "\n" " Like `run` but allows passing in an existing conversation history.\n" ). -spec run_with_messages( claude@agent@config:agent_config(), list(claude@types@message:message_param()) ) -> {ok, agent_result()} | {error, agent_error()}. run_with_messages(Config, Messages_list) -> event_loop(Config, lists:reverse(Messages_list), 0, 0, 0, fun(_) -> nil end).