Packages

A Gleam library for building and orchestrating agents on the BEAM.

Current section

Files

Jump to
pig src pig@workspace@tools.erl
Raw

src/pig@workspace@tools.erl

-module(pig@workspace@tools).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/pig/workspace/tools.gleam").
-export([read_file_tool/1, write_file_tool/1, list_directory_tool/1, delete_file_tool/1, remember_tool/1, recall_tool/1, list_keys_tool/1, grep_tool/1, all_tools/1]).
-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(
" Tool wrappers for pig workspace operations.\n"
"\n"
" This module provides tools that wrap workspace kv and vfs operations\n"
" as `pig.tool.Tool` values that can be registered with pig agents.\n"
"\n"
" Each tool closes over a `sqlight.Connection` and provides a handler\n"
" that parses JSON arguments and calls the appropriate workspace function.\n"
"\n"
" ## Tools\n"
"\n"
" - `read_file` — Read file contents with line numbers\n"
" - `write_file` — Create or replace a file\n"
" - `list_directory` — List entries in a directory\n"
" - `delete_file` — Delete a file or empty directory\n"
" - `grep` — Search file contents for a pattern\n"
" - `remember` — Store a key-value pair\n"
" - `recall` — Retrieve a stored value\n"
" - `list_keys` — List keys matching a prefix\n"
).
-file("src/pig/workspace/tools.gleam", 37).
?DOC(" Convert VFS errors to ToolError.\n").
-spec vfs_error_to_tool_error(pig@workspace@vfs:error()) -> pig@tool:tool_error().
vfs_error_to_tool_error(Err) ->
case Err of
{not_found, Path} ->
{tool_error, <<"File not found: "/utf8, Path/binary>>};
{not_empty, Path@1} ->
{tool_error, <<"Directory not empty: "/utf8, Path@1/binary>>};
{already_exists, Path@2} ->
{tool_error, <<"Already exists: "/utf8, Path@2/binary>>};
{invalid_path, Path@3} ->
{tool_error, <<"Invalid path: "/utf8, Path@3/binary>>};
{sql_error, _} ->
{tool_error, <<"Database error"/utf8>>}
end.
-file("src/pig/workspace/tools.gleam", 50).
?DOC(" Convert KV errors to ToolError.\n").
-spec kv_error_to_tool_error(pig@workspace@kv:error()) -> pig@tool:tool_error().
kv_error_to_tool_error(Err) ->
case Err of
{not_found, Key} ->
{tool_error, <<"Key not found: "/utf8, Key/binary>>};
{sql_error, _} ->
{tool_error, <<"Database error"/utf8>>}
end.
-file("src/pig/workspace/tools.gleam", 58).
?DOC(" Helper to get optional int field with default value.\n").
-spec get_optional_int(gleam@dynamic:dynamic_(), binary(), integer()) -> integer().
get_optional_int(Args, Field, Default) ->
case gleam@dynamic@decode:run(
Args,
gleam@dynamic@decode:at(
[Field],
{decoder, fun gleam@dynamic@decode:decode_int/1}
)
) of
{ok, Val} ->
Val;
{error, _} ->
Default
end.
-file("src/pig/workspace/tools.gleam", 141).
?DOC(" Format file content with line numbers (0-indexed).\n").
-spec format_with_line_numbers(binary()) -> binary().
format_with_line_numbers(Content) ->
_pipe = Content,
_pipe@1 = gleam@string:split(_pipe, <<"\n"/utf8>>),
_pipe@2 = gleam@list:index_map(
_pipe@1,
fun(Line, Index) ->
<<<<(erlang:integer_to_binary(Index))/binary, "\t"/utf8>>/binary,
Line/binary>>
end
),
gleam@string:join(_pipe@2, <<"\n"/utf8>>).
-file("src/pig/workspace/tools.gleam", 75).
?DOC(
" Create a read_file tool.\n"
"\n"
" Parameters:\n"
" - path: String (required) - file path\n"
" - offset: Int (optional) - line number to start from (0-indexed)\n"
" - limit: Int (optional) - maximum number of lines to read\n"
"\n"
" Returns file content with line numbers in \"N\\\\tline\" format.\n"
).
-spec read_file_tool(sqlight:connection()) -> pig@tool:tool().
read_file_tool(Conn) ->
{tool,
{tool_definition,
<<"read_file"/utf8>>,
<<"Read file contents with line numbers. Use offset and limit to read specific ranges."/utf8>>,
jscheam@schema:object(
[jscheam@schema:prop(<<"path"/utf8>>, jscheam@schema:string()),
jscheam@schema:optional(
jscheam@schema:prop(
<<"offset"/utf8>>,
jscheam@schema:integer()
)
),
jscheam@schema:optional(
jscheam@schema:prop(
<<"limit"/utf8>>,
jscheam@schema:integer()
)
)]
)},
fun(Args) ->
case gleam@dynamic@decode:run(
Args,
gleam@dynamic@decode:field(
<<"path"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun gleam@dynamic@decode:success/1
)
) of
{ok, Path} ->
Has_offset = begin
_pipe = gleam@dynamic@decode:run(
Args,
gleam@dynamic@decode:at(
[<<"offset"/utf8>>],
{decoder, fun gleam@dynamic@decode:decode_int/1}
)
),
gleam@result:is_ok(_pipe)
end,
Has_limit = begin
_pipe@1 = gleam@dynamic@decode:run(
Args,
gleam@dynamic@decode:at(
[<<"limit"/utf8>>],
{decoder, fun gleam@dynamic@decode:decode_int/1}
)
),
gleam@result:is_ok(_pipe@1)
end,
case Has_offset orelse Has_limit of
true ->
Offset = get_optional_int(
Args,
<<"offset"/utf8>>,
0
),
Limit = get_optional_int(Args, <<"limit"/utf8>>, 0),
Effective_limit = case Limit of
0 ->
case pig@workspace@vfs:read_file(Conn, Path) of
{ok, Content} ->
_pipe@2 = Content,
_pipe@3 = gleam@string:split(
_pipe@2,
<<"\n"/utf8>>
),
_pipe@4 = gleam@list:drop(
_pipe@3,
Offset
),
erlang:length(_pipe@4);
{error, _} ->
10000
end;
N ->
N
end,
_pipe@5 = pig@workspace@vfs:read_file_lines(
Conn,
Path,
Offset,
Effective_limit
),
_pipe@6 = gleam@result:map_error(
_pipe@5,
fun vfs_error_to_tool_error/1
),
gleam@result:map(_pipe@6, fun gleam@json:string/1);
false ->
_pipe@7 = pig@workspace@vfs:read_file(Conn, Path),
_pipe@8 = gleam@result:map_error(
_pipe@7,
fun vfs_error_to_tool_error/1
),
_pipe@9 = gleam@result:map(
_pipe@8,
fun format_with_line_numbers/1
),
gleam@result:map(_pipe@9, fun gleam@json:string/1)
end;
{error, _} ->
{error,
{tool_error,
<<"Invalid arguments: expected {\"path\": \"<path>\", \"offset\": <int>, \"limit\": <int>}"/utf8>>}}
end
end}.
-file("src/pig/workspace/tools.gleam", 155).
?DOC(
" Create a write_file tool.\n"
"\n"
" Parameters:\n"
" - path: String (required) - file path\n"
" - content: String (required) - file content\n"
"\n"
" Creates or overwrites a file.\n"
).
-spec write_file_tool(sqlight:connection()) -> pig@tool:tool().
write_file_tool(Conn) ->
{tool,
{tool_definition,
<<"write_file"/utf8>>,
<<"Create or replace a file with the given content."/utf8>>,
jscheam@schema:object(
[jscheam@schema:prop(<<"path"/utf8>>, jscheam@schema:string()),
jscheam@schema:prop(
<<"content"/utf8>>,
jscheam@schema:string()
)]
)},
fun(Args) ->
case gleam@dynamic@decode:run(
Args,
gleam@dynamic@decode:field(
<<"path"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Path) ->
gleam@dynamic@decode:field(
<<"content"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Content) ->
gleam@dynamic@decode:success({Path, Content})
end
)
end
)
) of
{ok, {Path@1, Content@1}} ->
_pipe = pig@workspace@vfs:write_file(
Conn,
Path@1,
Content@1
),
_pipe@1 = gleam@result:map_error(
_pipe,
fun vfs_error_to_tool_error/1
),
gleam@result:map(
_pipe@1,
fun(_) -> gleam@json:object([]) end
);
{error, _} ->
{error,
{tool_error,
<<"Invalid arguments: expected {\"path\": \"<path>\", \"content\": \"<content>\"}"/utf8>>}}
end
end}.
-file("src/pig/workspace/tools.gleam", 195).
?DOC(
" Create a list_directory tool.\n"
"\n"
" Parameters:\n"
" - path: String (required) - directory path\n"
"\n"
" Returns a JSON array of entry names.\n"
).
-spec list_directory_tool(sqlight:connection()) -> pig@tool:tool().
list_directory_tool(Conn) ->
{tool,
{tool_definition,
<<"list_directory"/utf8>>,
<<"List all entries in a directory."/utf8>>,
jscheam@schema:object(
[jscheam@schema:prop(<<"path"/utf8>>, jscheam@schema:string())]
)},
fun(Args) ->
case gleam@dynamic@decode:run(
Args,
gleam@dynamic@decode:field(
<<"path"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun gleam@dynamic@decode:success/1
)
) of
{ok, Path} ->
_pipe = pig@workspace@vfs:list_directory(Conn, Path),
_pipe@1 = gleam@result:map_error(
_pipe,
fun vfs_error_to_tool_error/1
),
gleam@result:map(
_pipe@1,
fun(Entries) ->
gleam@json:array(Entries, fun gleam@json:string/1)
end
);
{error, _} ->
{error,
{tool_error,
<<"Invalid arguments: expected {\"path\": \"<path>\"}"/utf8>>}}
end
end}.
-file("src/pig/workspace/tools.gleam", 225).
?DOC(
" Create a delete_file tool.\n"
"\n"
" Parameters:\n"
" - path: String (required) - file or empty directory path\n"
"\n"
" Deletes a file or empty directory.\n"
).
-spec delete_file_tool(sqlight:connection()) -> pig@tool:tool().
delete_file_tool(Conn) ->
{tool,
{tool_definition,
<<"delete_file"/utf8>>,
<<"Delete a file or empty directory."/utf8>>,
jscheam@schema:object(
[jscheam@schema:prop(<<"path"/utf8>>, jscheam@schema:string())]
)},
fun(Args) ->
case gleam@dynamic@decode:run(
Args,
gleam@dynamic@decode:field(
<<"path"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun gleam@dynamic@decode:success/1
)
) of
{ok, Path} ->
_pipe = pig@workspace@vfs:delete_file(Conn, Path),
_pipe@1 = gleam@result:map_error(
_pipe,
fun vfs_error_to_tool_error/1
),
gleam@result:map(
_pipe@1,
fun(_) -> gleam@json:object([]) end
);
{error, _} ->
{error,
{tool_error,
<<"Invalid arguments: expected {\"path\": \"<path>\"}"/utf8>>}}
end
end}.
-file("src/pig/workspace/tools.gleam", 256).
?DOC(
" Create a remember tool.\n"
"\n"
" Parameters:\n"
" - key: String (required) - key to store\n"
" - value: String (required) - value to store\n"
"\n"
" Stores a key-value pair that persists across conversations.\n"
).
-spec remember_tool(sqlight:connection()) -> pig@tool:tool().
remember_tool(Conn) ->
{tool,
{tool_definition,
<<"remember"/utf8>>,
<<"Store a value that persists across conversations. Updates if key already exists."/utf8>>,
jscheam@schema:object(
[jscheam@schema:prop(<<"key"/utf8>>, jscheam@schema:string()),
jscheam@schema:prop(
<<"value"/utf8>>,
jscheam@schema:string()
)]
)},
fun(Args) ->
case gleam@dynamic@decode:run(
Args,
gleam@dynamic@decode:field(
<<"key"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Key) ->
gleam@dynamic@decode:field(
<<"value"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Value) ->
gleam@dynamic@decode:success({Key, Value})
end
)
end
)
) of
{ok, {Key@1, Value@1}} ->
_pipe = pig@workspace@kv:remember(Conn, Key@1, Value@1),
_pipe@1 = gleam@result:map_error(
_pipe,
fun kv_error_to_tool_error/1
),
gleam@result:map(
_pipe@1,
fun(_) -> gleam@json:object([]) end
);
{error, _} ->
{error,
{tool_error,
<<"Invalid arguments: expected {\"key\": \"<key>\", \"value\": \"<value>\"}"/utf8>>}}
end
end}.
-file("src/pig/workspace/tools.gleam", 296).
?DOC(
" Create a recall tool.\n"
"\n"
" Parameters:\n"
" - key: String (required) - key to retrieve\n"
"\n"
" Returns the stored value as a JSON string.\n"
).
-spec recall_tool(sqlight:connection()) -> pig@tool:tool().
recall_tool(Conn) ->
{tool,
{tool_definition,
<<"recall"/utf8>>,
<<"Retrieve a previously stored value by key."/utf8>>,
jscheam@schema:object(
[jscheam@schema:prop(<<"key"/utf8>>, jscheam@schema:string())]
)},
fun(Args) ->
case gleam@dynamic@decode:run(
Args,
gleam@dynamic@decode:field(
<<"key"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun gleam@dynamic@decode:success/1
)
) of
{ok, Key} ->
_pipe = pig@workspace@kv:recall(Conn, Key),
_pipe@1 = gleam@result:map_error(
_pipe,
fun kv_error_to_tool_error/1
),
gleam@result:map(_pipe@1, fun gleam@json:string/1);
{error, _} ->
{error,
{tool_error,
<<"Invalid arguments: expected {\"key\": \"<key>\"}"/utf8>>}}
end
end}.
-file("src/pig/workspace/tools.gleam", 326).
?DOC(
" Create a list_keys tool.\n"
"\n"
" Parameters:\n"
" - prefix: String (required) - key prefix to match\n"
"\n"
" Returns a JSON array of matching keys.\n"
).
-spec list_keys_tool(sqlight:connection()) -> pig@tool:tool().
list_keys_tool(Conn) ->
{tool,
{tool_definition,
<<"list_keys"/utf8>>,
<<"List all stored keys matching a prefix."/utf8>>,
jscheam@schema:object(
[jscheam@schema:prop(<<"prefix"/utf8>>, jscheam@schema:string())]
)},
fun(Args) ->
case gleam@dynamic@decode:run(
Args,
gleam@dynamic@decode:field(
<<"prefix"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun gleam@dynamic@decode:success/1
)
) of
{ok, Prefix} ->
_pipe = pig@workspace@kv:list_keys(Conn, Prefix),
_pipe@1 = gleam@result:map_error(
_pipe,
fun kv_error_to_tool_error/1
),
gleam@result:map(
_pipe@1,
fun(Keys) ->
gleam@json:array(Keys, fun gleam@json:string/1)
end
);
{error, _} ->
{error,
{tool_error,
<<"Invalid arguments: expected {\"prefix\": \"<prefix>\"}"/utf8>>}}
end
end}.
-file("src/pig/workspace/tools.gleam", 360).
?DOC(
" Create a grep tool.\n"
"\n"
" Search file contents for a substring pattern. Returns matching lines\n"
" with file path and line number.\n"
"\n"
" Parameters:\n"
" - pattern: String (required) - substring to search for\n"
" - path: String (optional) - directory or file to search under (default: root)\n"
" - include: String (optional) - GLOB filter on file path (e.g. \"*.py\")\n"
" - max_results: Int (optional) - limit number of results (default: 50)\n"
).
-spec grep_tool(sqlight:connection()) -> pig@tool:tool().
grep_tool(Conn) ->
{tool,
{tool_definition,
<<"grep"/utf8>>,
<<"Search file contents for a pattern. Returns matching lines with file path and line number."/utf8>>,
jscheam@schema:object(
[jscheam@schema:prop(
<<"pattern"/utf8>>,
jscheam@schema:string()
),
jscheam@schema:optional(
jscheam@schema:prop(
<<"path"/utf8>>,
jscheam@schema:string()
)
),
jscheam@schema:optional(
jscheam@schema:prop(
<<"include"/utf8>>,
jscheam@schema:string()
)
),
jscheam@schema:optional(
jscheam@schema:prop(
<<"max_results"/utf8>>,
jscheam@schema:integer()
)
)]
)},
fun(Args) ->
case gleam@dynamic@decode:run(
Args,
gleam@dynamic@decode:field(
<<"pattern"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun gleam@dynamic@decode:success/1
)
) of
{ok, Pattern} ->
Path = case gleam@dynamic@decode:run(
Args,
gleam@dynamic@decode:at(
[<<"path"/utf8>>],
{decoder, fun gleam@dynamic@decode:decode_string/1}
)
) of
{ok, P} ->
P;
{error, _} ->
<<""/utf8>>
end,
Include = case gleam@dynamic@decode:run(
Args,
gleam@dynamic@decode:at(
[<<"include"/utf8>>],
{decoder, fun gleam@dynamic@decode:decode_string/1}
)
) of
{ok, I} ->
I;
{error, _} ->
<<""/utf8>>
end,
Max_results = get_optional_int(
Args,
<<"max_results"/utf8>>,
50
),
_pipe = pig@workspace@vfs:grep(
Conn,
Pattern,
Path,
Include,
Max_results
),
_pipe@1 = gleam@result:map_error(
_pipe,
fun vfs_error_to_tool_error/1
),
gleam@result:map(
_pipe@1,
fun(Matches) ->
gleam@json:array(
Matches,
fun(M) ->
gleam@json:object(
[{<<"path"/utf8>>,
gleam@json:string(
erlang:element(2, M)
)},
{<<"line_number"/utf8>>,
gleam@json:int(
erlang:element(3, M)
)},
{<<"line"/utf8>>,
gleam@json:string(
erlang:element(4, M)
)}]
)
end
)
end
);
{error, _} ->
{error,
{tool_error,
<<"Invalid arguments: expected {\"pattern\": \"<pattern>\", \"path\": \"<path>\", \"include\": \"<glob>\", \"max_results\": <int>}"/utf8>>}}
end
end}.
-file("src/pig/workspace/tools.gleam", 411).
?DOC(" Return all workspace tools in a list.\n").
-spec all_tools(sqlight:connection()) -> list(pig@tool:tool()).
all_tools(Conn) ->
[read_file_tool(Conn),
write_file_tool(Conn),
list_directory_tool(Conn),
delete_file_tool(Conn),
grep_tool(Conn),
remember_tool(Conn),
recall_tool(Conn),
list_keys_tool(Conn)].