-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\": \"\", \"offset\": , \"limit\": }"/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\": \"\", \"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\": \"\"}"/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\": \"\"}"/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\": \"\", \"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\": \"\"}"/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\": \"\"}"/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\": \"\", \"path\": \"\", \"include\": \"\", \"max_results\": }"/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)].