Packages
claude_gleam
1.0.0
Gleam SDK for the Anthropic Claude API with agentic tool-use loop and BEAM concurrency
Current section
Files
Jump to
Current section
Files
src/claude@agent@tool_runner.erl
-module(claude@agent@tool_runner).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/claude/agent/tool_runner.gleam").
-export([execute_concurrent/3]).
-export_type([tool_message/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 tool_message() :: {tool_success,
binary(),
{ok, binary()} | {error, binary()}} |
{tool_crashed, gleam@erlang@process:exit_reason()}.
-file("src/claude/agent/tool_runner.gleam", 96).
?DOC(" Format an exit reason into a human-readable string.\n").
-spec format_exit_reason(gleam@erlang@process:exit_reason()) -> binary().
format_exit_reason(Reason) ->
case Reason of
normal ->
<<"normal exit"/utf8>>;
killed ->
<<"killed"/utf8>>;
{abnormal, Reason@1} ->
<<"abnormal: "/utf8, (gleam@string:inspect(Reason@1))/binary>>
end.
-file("src/claude/agent/tool_runner.gleam", 29).
?DOC(
" Execute tool calls concurrently using BEAM processes.\n"
"\n"
" Spawns one unlinked, monitored process per tool call, then collects\n"
" results with a timeout. Results are returned in the same order as the\n"
" input tool_calls. Non-ToolUse blocks in the input list are silently\n"
" ignored.\n"
"\n"
" If a tool handler exceeds the timeout, an Error(\"Tool execution timed out\")\n"
" is returned for that tool. If a tool handler crashes, the monitor detects\n"
" the exit and an Error describing the crash reason is returned immediately\n"
" rather than waiting for the full timeout.\n"
).
-spec execute_concurrent(
list(claude@types@content:content_block()),
claude@types@tool:registry(),
integer()
) -> list({binary(), {ok, binary()} | {error, binary()}}).
execute_concurrent(Tool_calls, Tools, Timeout) ->
Calls = gleam@list:filter_map(Tool_calls, fun(Block) -> case Block of
{tool_use, Id, Name, Input} ->
{ok, {Id, Name, Input}};
_ ->
{error, nil}
end end),
Tasks = gleam@list:map(
Calls,
fun(Call) ->
{Id@1, Name@1, Input@1} = Call,
Subject = gleam@erlang@process:new_subject(),
Pid = proc_lib:spawn(
fun() ->
Result = claude@types@tool:dispatch(Tools, Name@1, Input@1),
gleam@erlang@process:send(Subject, {Id@1, Result})
end
),
Mon = gleam@erlang@process:monitor(Pid),
{Id@1, Subject, Mon}
end
),
gleam@list:map(
Tasks,
fun(Task) ->
{Id@2, Subject@1, Mon@1} = Task,
Selector = begin
_pipe = gleam_erlang_ffi:new_selector(),
_pipe@1 = gleam@erlang@process:select_map(
_pipe,
Subject@1,
fun(Msg) ->
{Tool_id, Tool_result} = Msg,
{tool_success, Tool_id, Tool_result}
end
),
gleam@erlang@process:select_specific_monitor(
_pipe@1,
Mon@1,
fun(Down) -> {tool_crashed, erlang:element(4, Down)} end
)
end,
Result@1 = case gleam_erlang_ffi:select(Selector, Timeout) of
{ok, {tool_success, _, Tool_result@1}} ->
{Id@2, Tool_result@1};
{ok, {tool_crashed, Reason}} ->
{Id@2,
{error,
<<"Tool execution crashed: "/utf8,
(format_exit_reason(Reason))/binary>>}};
{error, nil} ->
{Id@2, {error, <<"Tool execution timed out"/utf8>>}}
end,
gleam@erlang@process:demonitor_process(Mon@1),
Result@1
end
).