Packages

A Gleam framework for building AI agents — type-safe, multi-provider, with tool calling, streaming, and simulation testing

Current section

Files

Jump to
glean src glean@providers@gemini.erl
Raw

src/glean@providers@gemini.erl

-module(glean@providers@gemini).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/glean/providers/gemini.gleam").
-export([from_config/1, new/2]).
-export_type([gemini_stream_accumulator/0, gemini_stream_tool_call/0, gemini_chunk/0, parsed_response/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.
?MODULEDOC(false).
-type gemini_stream_accumulator() :: {gemini_stream_accumulator,
gleam@string_tree:string_tree(),
list(gemini_stream_tool_call()),
glean@error:finish_reason(),
glean@stream:usage()}.
-type gemini_stream_tool_call() :: {gemini_stream_tool_call,
binary(),
binary(),
binary()}.
-type gemini_chunk() :: {gemini_chunk,
list(glean@provider:generated_content()),
binary(),
glean@stream:usage()}.
-type parsed_response() :: {parsed_response,
list(glean@provider:generated_content()),
glean@error:finish_reason(),
glean@stream:usage()}.
-file("src/glean/providers/gemini.gleam", 320).
?DOC(false).
-spec make_http_request(
glean@providers@config:provider_config(),
binary(),
binary()
) -> {ok, binary()} | {error, glean@error:glean_error()}.
make_http_request(Config, Url, Body) ->
glean@providers@http_utils:send_post(
Config,
<<"gemini"/utf8>>,
Url,
Body,
[]
).
-file("src/glean/providers/gemini.gleam", 422).
?DOC(false).
-spec split_system(list(glean@message:message())) -> {list(binary()),
list(glean@message:message())}.
split_system(Messages) ->
System = gleam@list:filter_map(Messages, fun(M) -> case M of
{system_message, Content} ->
{ok, Content};
_ ->
{error, nil}
end end),
Non_system = gleam@list:filter(Messages, fun(M@1) -> case M@1 of
{system_message, _} ->
false;
_ ->
true
end end),
{System, Non_system}.
-file("src/glean/providers/gemini.gleam", 483).
?DOC(false).
-spec encode_user_part(glean@message:user_content()) -> gleam@json:json().
encode_user_part(Content) ->
case Content of
{user_text, Text} ->
gleam@json:object([{<<"text"/utf8>>, gleam@json:string(Text)}]);
{user_file, Data, Media_type} ->
case Data of
{base64_data, B64} ->
gleam@json:object(
[{<<"inlineData"/utf8>>,
gleam@json:object(
[{<<"mimeType"/utf8>>,
gleam@json:string(Media_type)},
{<<"data"/utf8>>,
gleam@json:string(B64)}]
)}]
);
{url_data, Url} ->
gleam@json:object(
[{<<"fileData"/utf8>>,
gleam@json:object(
[{<<"mimeType"/utf8>>,
gleam@json:string(Media_type)},
{<<"fileUri"/utf8>>,
gleam@json:string(Url)}]
)}]
)
end
end.
-file("src/glean/providers/gemini.gleam", 512).
?DOC(false).
-spec encode_model_part(glean@message:assistant_content()) -> gleam@json:json().
encode_model_part(Content) ->
case Content of
{assistant_text, Text} ->
gleam@json:object([{<<"text"/utf8>>, gleam@json:string(Text)}]);
{assistant_reasoning, Text@1} ->
gleam@json:object([{<<"text"/utf8>>, gleam@json:string(Text@1)}]);
{assistant_tool_call, Id, Name, Args_json} ->
Args = glean@json_value:parse_to_json(Args_json),
Fc = gleam@json:object(
[{<<"name"/utf8>>, gleam@json:string(Name)},
{<<"args"/utf8>>, Args}]
),
Part_fields = [{<<"functionCall"/utf8>>, Fc}],
Part_fields@1 = case gleam_stdlib:string_starts_with(
Id,
<<"gemini_sig:"/utf8>>
) of
true ->
Sig = gleam@string:drop_start(
Id,
string:length(<<"gemini_sig:"/utf8>>)
),
lists:append(
Part_fields,
[{<<"thoughtSignature"/utf8>>, gleam@json:string(Sig)}]
);
false ->
Part_fields
end,
gleam@json:object(Part_fields@1)
end.
-file("src/glean/providers/gemini.gleam", 540).
?DOC(false).
-spec encode_function_response(glean@message:tool_result()) -> gleam@json:json().
encode_function_response(Result) ->
case Result of
{tool_result_ok, _, Tool_name, Output} ->
gleam@json:object(
[{<<"functionResponse"/utf8>>,
gleam@json:object(
[{<<"name"/utf8>>, gleam@json:string(Tool_name)},
{<<"response"/utf8>>,
gleam@json:object(
[{<<"result"/utf8>>,
gleam@json:string(Output)}]
)}]
)}]
);
{tool_result_error, _, Tool_name@1, Error_message} ->
gleam@json:object(
[{<<"functionResponse"/utf8>>,
gleam@json:object(
[{<<"name"/utf8>>, gleam@json:string(Tool_name@1)},
{<<"response"/utf8>>,
gleam@json:object(
[{<<"error"/utf8>>,
gleam@json:string(Error_message)}]
)}]
)}]
)
end.
-file("src/glean/providers/gemini.gleam", 448).
?DOC(false).
-spec encode_content(glean@message:message()) -> list(gleam@json:json()).
encode_content(Msg) ->
case Msg of
{system_message, _} ->
[];
{user_message, Content} ->
[gleam@json:object(
[{<<"role"/utf8>>, gleam@json:string(<<"user"/utf8>>)},
{<<"parts"/utf8>>,
gleam@json:preprocessed_array(
gleam@list:map(Content, fun encode_user_part/1)
)}]
)];
{assistant_message, Content@1} ->
[gleam@json:object(
[{<<"role"/utf8>>, gleam@json:string(<<"model"/utf8>>)},
{<<"parts"/utf8>>,
gleam@json:preprocessed_array(
gleam@list:map(
Content@1,
fun encode_model_part/1
)
)}]
)];
{tool_result_message, Results} ->
[gleam@json:object(
[{<<"role"/utf8>>, gleam@json:string(<<"user"/utf8>>)},
{<<"parts"/utf8>>,
gleam@json:preprocessed_array(
gleam@list:map(
Results,
fun encode_function_response/1
)
)}]
)]
end.
-file("src/glean/providers/gemini.gleam", 444).
?DOC(false).
-spec encode_contents(list(glean@message:message())) -> list(gleam@json:json()).
encode_contents(Messages) ->
gleam@list:flat_map(Messages, fun encode_content/1).
-file("src/glean/providers/gemini.gleam", 571).
?DOC(false).
-spec encode_tools(list(glean@provider:tool_spec())) -> list(gleam@json:json()).
encode_tools(Tools) ->
gleam@list:map(
Tools,
fun(T) ->
gleam@json:object(
[{<<"name"/utf8>>, gleam@json:string(erlang:element(2, T))},
{<<"description"/utf8>>,
gleam@json:string(erlang:element(3, T))},
{<<"parameters"/utf8>>,
glean@schema:to_json(erlang:element(4, T))}]
)
end
).
-file("src/glean/providers/gemini.gleam", 331).
?DOC(false).
-spec encode_request(glean@provider:generate_request()) -> binary().
encode_request(Gen_request) ->
{System_parts, Conversation} = split_system(erlang:element(2, Gen_request)),
Contents = encode_contents(Conversation),
Fields = [{<<"contents"/utf8>>, gleam@json:preprocessed_array(Contents)}],
Fields@1 = case System_parts of
[] ->
Fields;
Parts ->
Text = gleam@string:join(Parts, <<"\n"/utf8>>),
lists:append(
Fields,
[{<<"systemInstruction"/utf8>>,
gleam@json:object(
[{<<"parts"/utf8>>,
gleam@json:preprocessed_array(
[gleam@json:object(
[{<<"text"/utf8>>,
gleam@json:string(Text)}]
)]
)}]
)}]
)
end,
Fields@2 = case erlang:element(4, Gen_request) of
[] ->
Fields@1;
Tools ->
lists:append(
Fields@1,
[{<<"tools"/utf8>>,
gleam@json:preprocessed_array(
[gleam@json:object(
[{<<"functionDeclarations"/utf8>>,
gleam@json:preprocessed_array(
encode_tools(Tools)
)}]
)]
)}]
)
end,
Gen_config_fields = [],
Gen_config_fields@1 = case erlang:element(2, erlang:element(3, Gen_request)) of
none ->
Gen_config_fields;
{some, Max} ->
lists:append(
Gen_config_fields,
[{<<"maxOutputTokens"/utf8>>, gleam@json:int(Max)}]
)
end,
Gen_config_fields@2 = case erlang:element(3, erlang:element(3, Gen_request)) of
none ->
Gen_config_fields@1;
{some, Temp} ->
lists:append(
Gen_config_fields@1,
[{<<"temperature"/utf8>>, gleam@json:float(Temp)}]
)
end,
Gen_config_fields@3 = case erlang:element(4, erlang:element(3, Gen_request)) of
none ->
Gen_config_fields@2;
{some, P} ->
lists:append(
Gen_config_fields@2,
[{<<"topP"/utf8>>, gleam@json:float(P)}]
)
end,
Gen_config_fields@4 = case erlang:element(5, erlang:element(3, Gen_request)) of
none ->
Gen_config_fields@3;
{some, K} ->
lists:append(
Gen_config_fields@3,
[{<<"topK"/utf8>>, gleam@json:int(K)}]
)
end,
Gen_config_fields@5 = case erlang:element(6, erlang:element(3, Gen_request)) of
none ->
Gen_config_fields@4;
{some, Stops} ->
lists:append(
Gen_config_fields@4,
[{<<"stopSequences"/utf8>>,
gleam@json:array(Stops, fun gleam@json:string/1)}]
)
end,
Fields@3 = case Gen_config_fields@5 of
[] ->
Fields@2;
_ ->
lists:append(
Fields@2,
[{<<"generationConfig"/utf8>>,
gleam@json:object(Gen_config_fields@5)}]
)
end,
_pipe = gleam@json:object(Fields@3),
gleam@json:to_string(_pipe).
-file("src/glean/providers/gemini.gleam", 659).
?DOC(false).
-spec text_part_decoder() -> gleam@dynamic@decode:decoder(glean@provider:generated_content()).
text_part_decoder() ->
gleam@dynamic@decode:field(
<<"text"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Text) -> gleam@dynamic@decode:success({generated_text, Text}) end
).
-file("src/glean/providers/gemini.gleam", 664).
?DOC(false).
-spec function_call_part_decoder() -> gleam@dynamic@decode:decoder(glean@provider:generated_content()).
function_call_part_decoder() ->
gleam@dynamic@decode:field(
<<"functionCall"/utf8>>,
begin
gleam@dynamic@decode:field(
<<"name"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Name) ->
gleam@dynamic@decode:field(
<<"args"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_dynamic/1},
fun(Args) ->
Args_json = glean@json_value:dynamic_to_string(Args),
gleam@dynamic@decode:success({Name, Args_json})
end
)
end
)
end,
fun(Fc) ->
gleam@dynamic@decode:optional_field(
<<"thoughtSignature"/utf8>>,
<<""/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Thought_sig) ->
Tool_call_id = case Thought_sig of
<<""/utf8>> ->
<<<<<<"gemini_tc_"/utf8,
(erlang:element(1, Fc))/binary>>/binary,
"_"/utf8>>/binary,
(erlang:integer_to_binary(
gleam@int:random(999999999)
))/binary>>;
Sig ->
<<"gemini_sig:"/utf8, Sig/binary>>
end,
gleam@dynamic@decode:success(
{generated_tool_call,
Tool_call_id,
erlang:element(1, Fc),
erlang:element(2, Fc)}
)
end
)
end
).
-file("src/glean/providers/gemini.gleam", 264).
?DOC(false).
-spec gemini_chunk_decoder() -> gleam@dynamic@decode:decoder(gemini_chunk()).
gemini_chunk_decoder() ->
Part_decoder = gleam@dynamic@decode:one_of(
text_part_decoder(),
[function_call_part_decoder()]
),
Candidate_decoder = begin
gleam@dynamic@decode:optional_field(
<<"content"/utf8>>,
[],
begin
gleam@dynamic@decode:optional_field(
<<"parts"/utf8>>,
[],
gleam@dynamic@decode:list(Part_decoder),
fun(Parts) -> gleam@dynamic@decode:success(Parts) end
)
end,
fun(Content) ->
gleam@dynamic@decode:optional_field(
<<"finishReason"/utf8>>,
<<""/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Finish_reason) ->
gleam@dynamic@decode:success({Content, Finish_reason})
end
)
end
)
end,
Usage_decoder = begin
gleam@dynamic@decode:optional_field(
<<"promptTokenCount"/utf8>>,
0,
{decoder, fun gleam@dynamic@decode:decode_int/1},
fun(Prompt) ->
gleam@dynamic@decode:optional_field(
<<"candidatesTokenCount"/utf8>>,
0,
{decoder, fun gleam@dynamic@decode:decode_int/1},
fun(Candidates) ->
gleam@dynamic@decode:success(
{usage, Prompt, Candidates}
)
end
)
end
)
end,
gleam@dynamic@decode:optional_field(
<<"candidates"/utf8>>,
[],
gleam@dynamic@decode:list(Candidate_decoder),
fun(Candidates@1) ->
gleam@dynamic@decode:optional_field(
<<"usageMetadata"/utf8>>,
{usage, 0, 0},
Usage_decoder,
fun(Usage) ->
{Content@1, Finish_reason@1} = case Candidates@1 of
[{Parts@1, Fr} | _] ->
{Parts@1, Fr};
[] ->
{[], <<""/utf8>>}
end,
gleam@dynamic@decode:success(
{gemini_chunk, Content@1, Finish_reason@1, Usage}
)
end
)
end
).
-file("src/glean/providers/gemini.gleam", 691).
?DOC(false).
-spec parse_gemini_finish_reason(binary()) -> glean@error:finish_reason().
parse_gemini_finish_reason(Reason) ->
case Reason of
<<"STOP"/utf8>> ->
stop;
<<"MAX_TOKENS"/utf8>> ->
length;
<<"SAFETY"/utf8>> ->
content_filter;
<<"RECITATION"/utf8>> ->
content_filter;
Other ->
{other, Other}
end.
-file("src/glean/providers/gemini.gleam", 177).
?DOC(false).
-spec process_gemini_chunk(
gemini_stream_accumulator(),
binary(),
fun((glean@stream:stream_event()) -> nil)
) -> gemini_stream_accumulator().
process_gemini_chunk(Acc, Data, On_event) ->
Decoder = gemini_chunk_decoder(),
case gleam@json:parse(Data, Decoder) of
{error, _} ->
Acc;
{ok, Chunk} ->
Acc@1 = gleam@list:fold(
erlang:element(2, Chunk),
Acc,
fun(A, Content) -> case Content of
{generated_text, Text} ->
On_event({text_delta, <<"gemini"/utf8>>, Text}),
{gemini_stream_accumulator,
gleam@string_tree:append(
erlang:element(2, A),
Text
),
erlang:element(3, A),
erlang:element(4, A),
erlang:element(5, A)};
{generated_reasoning, Text@1} ->
On_event(
{reasoning_delta, <<"gemini"/utf8>>, Text@1}
),
{gemini_stream_accumulator,
gleam@string_tree:append(
erlang:element(2, A),
Text@1
),
erlang:element(3, A),
erlang:element(4, A),
erlang:element(5, A)};
{generated_tool_call, Id, Name, Args} ->
On_event(
{tool_call_start, <<"gemini"/utf8>>, Id, Name}
),
On_event(
{tool_call_delta, <<"gemini"/utf8>>, Id, Args}
),
On_event({tool_call_end, <<"gemini"/utf8>>, Id}),
{gemini_stream_accumulator,
erlang:element(2, A),
lists:append(
erlang:element(3, A),
[{gemini_stream_tool_call, Id, Name, Args}]
),
erlang:element(4, A),
erlang:element(5, A)}
end end
),
Acc@2 = case erlang:element(3, Chunk) of
<<""/utf8>> ->
Acc@1;
Fr ->
{gemini_stream_accumulator,
erlang:element(2, Acc@1),
erlang:element(3, Acc@1),
parse_gemini_finish_reason(Fr),
erlang:element(5, Acc@1)}
end,
case erlang:element(4, Chunk) of
{usage, 0, 0} ->
Acc@2;
U ->
{gemini_stream_accumulator,
erlang:element(2, Acc@2),
erlang:element(3, Acc@2),
erlang:element(4, Acc@2),
U}
end
end.
-file("src/glean/providers/gemini.gleam", 157).
?DOC(false).
-spec gemini_stream_loop(
gleam@erlang@process:subject(httpp@sse:s_s_e_event()),
gleam@erlang@process:subject(httpp@sse:s_s_e_manager_message()),
gemini_stream_accumulator(),
fun((glean@stream:stream_event()) -> nil),
integer()
) -> gemini_stream_accumulator().
gemini_stream_loop(Subject, Manager, Acc, On_event, Timeout_ms) ->
case gleam@erlang@process:'receive'(Subject, Timeout_ms) of
{error, _} ->
gleam@erlang@process:send(Manager, shutdown),
Acc;
{ok, closed} ->
Acc;
{ok, {event, _, _, Data}} ->
New_acc = process_gemini_chunk(Acc, Data, On_event),
gemini_stream_loop(Subject, Manager, New_acc, On_event, Timeout_ms)
end.
-file("src/glean/providers/gemini.gleam", 90).
?DOC(false).
-spec do_stream(
glean@providers@config:provider_config(),
glean@provider:generate_request(),
fun((glean@stream:stream_event()) -> nil)
) -> {ok, glean@provider:generate_result()} | {error, glean@error:glean_error()}.
do_stream(Config, Gen_request, On_event) ->
Body = encode_request(Gen_request),
Url = <<<<<<<<<<(erlang:element(3, Config))/binary, "/models/"/utf8>>/binary,
(erlang:element(4, Config))/binary>>/binary,
":streamGenerateContent?key="/utf8>>/binary,
(erlang:element(2, Config))/binary>>/binary,
"&alt=sse"/utf8>>,
Headers = gleam@list:map(
erlang:element(5, Config),
fun(H) -> {erlang:element(1, H), erlang:element(2, H)} end
),
case glean@providers@sse:start_stream(
Url,
Headers,
Body,
erlang:element(6, Config)
) of
{error, Msg} ->
{error,
{provider_error,
<<"gemini"/utf8>>,
erlang:element(4, Config),
0,
<<"Streaming failed: "/utf8, Msg/binary>>}};
{ok, {Event_subject, Manager_subject}} ->
Acc = {gemini_stream_accumulator,
gleam@string_tree:new(),
[],
stop,
glean@stream:zero_usage()},
Final_acc = gemini_stream_loop(
Event_subject,
Manager_subject,
Acc,
On_event,
erlang:element(6, Config)
),
Text_content = case unicode:characters_to_binary(
erlang:element(2, Final_acc)
) of
<<""/utf8>> ->
[];
T ->
[{generated_text, T}]
end,
Tool_content = gleam@list:map(
erlang:element(3, Final_acc),
fun(Tc) ->
{generated_tool_call,
erlang:element(2, Tc),
erlang:element(3, Tc),
erlang:element(4, Tc)}
end
),
{ok,
{generate_result,
lists:append(Text_content, Tool_content),
erlang:element(4, Final_acc),
erlang:element(5, Final_acc),
erlang:element(4, Config)}}
end.
-file("src/glean/providers/gemini.gleam", 612).
?DOC(false).
-spec gemini_response_decoder() -> gleam@dynamic@decode:decoder(parsed_response()).
gemini_response_decoder() ->
Usage_decoder = begin
gleam@dynamic@decode:optional_field(
<<"promptTokenCount"/utf8>>,
0,
{decoder, fun gleam@dynamic@decode:decode_int/1},
fun(Prompt) ->
gleam@dynamic@decode:optional_field(
<<"candidatesTokenCount"/utf8>>,
0,
{decoder, fun gleam@dynamic@decode:decode_int/1},
fun(Candidates) ->
gleam@dynamic@decode:success(
{usage, Prompt, Candidates}
)
end
)
end
)
end,
Part_decoder = gleam@dynamic@decode:one_of(
text_part_decoder(),
[function_call_part_decoder()]
),
Candidate_decoder = begin
gleam@dynamic@decode:field(
<<"content"/utf8>>,
begin
gleam@dynamic@decode:field(
<<"parts"/utf8>>,
gleam@dynamic@decode:list(Part_decoder),
fun(Parts) -> gleam@dynamic@decode:success(Parts) end
)
end,
fun(Content) ->
gleam@dynamic@decode:optional_field(
<<"finishReason"/utf8>>,
<<"STOP"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Finish_reason) ->
gleam@dynamic@decode:success({Content, Finish_reason})
end
)
end
)
end,
gleam@dynamic@decode:field(
<<"candidates"/utf8>>,
gleam@dynamic@decode:list(Candidate_decoder),
fun(Candidates@1) ->
gleam@dynamic@decode:optional_field(
<<"usageMetadata"/utf8>>,
{usage, 0, 0},
Usage_decoder,
fun(Usage) ->
{Content@1, Finish_reason@1} = case Candidates@1 of
[{Parts@1, Fr} | _] ->
{Parts@1, parse_gemini_finish_reason(Fr)};
[] ->
{[], stop}
end,
gleam@dynamic@decode:success(
{parsed_response, Content@1, Finish_reason@1, Usage}
)
end
)
end
).
-file("src/glean/providers/gemini.gleam", 583).
?DOC(false).
-spec decode_response(binary(), binary()) -> {ok,
glean@provider:generate_result()} |
{error, glean@error:glean_error()}.
decode_response(Model_id, Body) ->
Decoder = gemini_response_decoder(),
case gleam@json:parse(Body, Decoder) of
{error, _} ->
{error,
{json_parse_error,
Body,
<<"Failed to decode Gemini response"/utf8>>}};
{ok, Parsed} ->
{ok,
{generate_result,
erlang:element(2, Parsed),
erlang:element(3, Parsed),
erlang:element(4, Parsed),
Model_id}}
end.
-file("src/glean/providers/gemini.gleam", 57).
?DOC(false).
-spec do_generate(
glean@providers@config:provider_config(),
glean@provider:generate_request()
) -> {ok, glean@provider:generate_result()} | {error, glean@error:glean_error()}.
do_generate(Config, Gen_request) ->
Body = encode_request(Gen_request),
Url = <<<<<<<<(erlang:element(3, Config))/binary, "/models/"/utf8>>/binary,
(erlang:element(4, Config))/binary>>/binary,
":generateContent?key="/utf8>>/binary,
(erlang:element(2, Config))/binary>>,
case make_http_request(Config, Url, Body) of
{error, Err} ->
{error, Err};
{ok, Response_body} ->
decode_response(erlang:element(4, Config), Response_body)
end.
-file("src/glean/providers/gemini.gleam", 42).
?DOC(false).
-spec from_config(glean@providers@config:provider_config()) -> glean@provider:provider().
from_config(Cfg) ->
Cfg@1 = case erlang:element(3, Cfg) of
<<""/utf8>> ->
glean@providers@config:base_url(
Cfg,
<<"https://generativelanguage.googleapis.com/v1beta"/utf8>>
);
_ ->
Cfg
end,
{provider,
<<"gemini"/utf8>>,
erlang:element(4, Cfg@1),
fun(Request) -> do_generate(Cfg@1, Request) end,
fun(Request@1, On_event) -> do_stream(Cfg@1, Request@1, On_event) end}.
-file("src/glean/providers/gemini.gleam", 35).
?DOC(false).
-spec new(binary(), binary()) -> glean@provider:provider().
new(Api_key, Model) ->
_pipe = glean@providers@config:new(Api_key, Model),
_pipe@1 = glean@providers@config:base_url(
_pipe,
<<"https://generativelanguage.googleapis.com/v1beta"/utf8>>
),
from_config(_pipe@1).