Current section

Files

Jump to
erlang_adk src tools builtin adk_load_artifacts_tool.erl
Raw

src/tools/builtin/adk_load_artifacts_tool.erl

%% @doc Built-in model-selected artifact attachment tool.
%%
%% The tool returns metadata only. The Runner resolves committed references
%% into bounded ephemeral model parts for the next request; artifact bytes are
%% never copied into the durable tool response or session state.
-module(adk_load_artifacts_tool).
-behaviour(adk_tool).
-export([schema/0, context_capabilities/0, execute/2]).
-define(MAX_ATTACHMENTS, 8).
schema() ->
#{<<"name">> => <<"load_artifacts">>,
<<"description">> =>
<<"Attach scoped artifacts to the next model request.">>,
<<"parameters">> =>
#{<<"type">> => <<"object">>,
<<"properties">> =>
#{<<"artifacts">> =>
#{<<"type">> => <<"array">>,
<<"minItems">> => 1,
<<"maxItems">> => ?MAX_ATTACHMENTS,
<<"items">> =>
#{<<"type">> => <<"object">>,
<<"properties">> =>
#{<<"name">> => #{<<"type">> => <<"string">>},
<<"version">> =>
#{<<"oneOf">> =>
[#{<<"type">> => <<"integer">>,
<<"minimum">> => 1},
#{<<"type">> => <<"string">>,
<<"enum">> => [<<"latest">>]}]}},
<<"required">> => [<<"name">>],
<<"additionalProperties">> => false}}},
<<"required">> => [<<"artifacts">>],
<<"additionalProperties">> => false}}.
context_capabilities() -> [artifact_attach].
execute(#{<<"artifacts">> := Artifacts}, Context)
when is_list(Artifacts), length(Artifacts) =< ?MAX_ATTACHMENTS ->
attach_all(Artifacts, Context, []);
execute(_Args, _Context) ->
{error, invalid_artifact_attachment_request}.
attach_all([], _Context, Acc) ->
{ok, #{<<"success">> => true,
<<"attachments">> => lists:reverse(Acc)}};
attach_all([#{<<"name">> := Name} = Request | Rest], Context, Acc)
when is_binary(Name) ->
case selector(maps:get(<<"version">>, Request, <<"latest">>)) of
{ok, Selector} ->
case adk_context:attach_artifact(Context, Name, Selector) of
{ok, Metadata} when is_map(Metadata) ->
attach_all(Rest, Context,
[public_metadata(Metadata) | Acc]);
{error, Reason} ->
{ok, #{<<"success">> => false,
<<"error">> => structural_error(Reason),
<<"attachments">> => lists:reverse(Acc)}};
Other ->
{error, {invalid_artifact_service_reply, Other}}
end;
{error, _} = Error -> Error
end;
attach_all([_ | _], _Context, _Acc) ->
{error, invalid_artifact_attachment_request}.
selector(<<"latest">>) -> {ok, latest};
selector(latest) -> {ok, latest};
selector(Version) when is_integer(Version), Version > 0 -> {ok, Version};
selector(_) -> {error, invalid_artifact_version}.
public_metadata(Metadata) ->
compact(
#{<<"name">> => maps:get(name, Metadata, undefined),
<<"version">> => maps:get(version, Metadata, undefined),
<<"mime_type">> => maps:get(mime_type, Metadata, undefined),
<<"digest">> => maps:get(digest, Metadata, undefined),
<<"size">> => maps:get(size, Metadata, undefined)}).
compact(Map) -> maps:filter(fun(_Key, Value) -> Value =/= undefined end, Map).
structural_error(Reason) when is_atom(Reason) -> atom_to_binary(Reason, utf8);
structural_error(_) -> <<"artifact_operation_failed">>.