diff --git a/README.md b/README.md index dba67e1..21e3fbd 100644 --- a/README.md +++ b/README.md @@ -51,11 +51,11 @@ Agents learn capabilities through skills — Nostr events that any agent can di Didactyl will support local inference, which is very privacy preserving. Remote inference does however have it's advantages, and in those cases Didactyl supports using Bitcoin Lightning and eCash inference providers. -## Current Status — v0.0.22 +## Current Status — v0.0.23 **Active build — this project is barely working. Experiment at your own risk.** -> Last release update: v0.0.22 — Add relay-waited --test-tool flow, longform markdown post tool, and README CLI debugger docs +> Last release update: v0.0.23 — Add tool_list runtime tool introspection (name/description/schema) and validate via CLI test mode - Connects to configured relays with auto-reconnect and relay state transition logging - Publishes configured startup events per relay as each relay becomes connected diff --git a/src/main.h b/src/main.h index 55ef674..b732df9 100644 --- a/src/main.h +++ b/src/main.h @@ -12,8 +12,8 @@ // Using DIDACTYL_ prefix to avoid conflicts with nostr_core_lib VERSION macros #define DIDACTYL_VERSION_MAJOR 0 #define DIDACTYL_VERSION_MINOR 0 -#define DIDACTYL_VERSION_PATCH 22 -#define DIDACTYL_VERSION "v0.0.22" +#define DIDACTYL_VERSION_PATCH 23 +#define DIDACTYL_VERSION "v0.0.23" // Agent metadata #define DIDACTYL_NAME "Didactyl" diff --git a/src/tools.c b/src/tools.c index 8969707..8ccb74f 100644 --- a/src/tools.c +++ b/src/tools.c @@ -1692,6 +1692,21 @@ char* tools_build_openai_schema_json(const tools_context_t* ctx) { cJSON_AddItemToObject(t28, "function", t28_fn); cJSON_AddItemToArray(tools, t28); + cJSON* t29 = cJSON_CreateObject(); + cJSON* t29_fn = cJSON_CreateObject(); + cJSON* t29_params = cJSON_CreateObject(); + cJSON* t29_props = cJSON_CreateObject(); + + cJSON_AddStringToObject(t29, "type", "function"); + cJSON_AddStringToObject(t29_fn, "name", "tool_list"); + cJSON_AddStringToObject(t29_fn, "description", "List available tools with name, description, and JSON parameter schema"); + cJSON_AddStringToObject(t29_params, "type", "object"); + cJSON_AddItemToObject(t29_params, "properties", t29_props); + + cJSON_AddItemToObject(t29_fn, "parameters", t29_params); + cJSON_AddItemToObject(t29, "function", t29_fn); + cJSON_AddItemToArray(tools, t29); + char* out = cJSON_PrintUnformatted(tools); cJSON_Delete(tools); return out; @@ -4176,6 +4191,79 @@ static char* execute_file_write(tools_context_t* ctx, const char* args_json) { return json; } +static char* execute_tool_list(tools_context_t* ctx, const char* args_json) { + (void)args_json; + if (!ctx) { + return json_error("tool context unavailable"); + } + + char* schema_json = tools_build_openai_schema_json(ctx); + if (!schema_json) { + return json_error("failed to build tool schemas"); + } + + cJSON* schema_arr = cJSON_Parse(schema_json); + free(schema_json); + if (!schema_arr || !cJSON_IsArray(schema_arr)) { + cJSON_Delete(schema_arr); + return json_error("failed to parse tool schemas"); + } + + cJSON* out = cJSON_CreateObject(); + cJSON* tools = cJSON_CreateArray(); + if (!out || !tools) { + cJSON_Delete(schema_arr); + cJSON_Delete(out); + cJSON_Delete(tools); + return json_error("out of memory"); + } + + cJSON* item = NULL; + cJSON_ArrayForEach(item, schema_arr) { + cJSON* fn = cJSON_GetObjectItemCaseSensitive(item, "function"); + if (!fn || !cJSON_IsObject(fn)) { + continue; + } + + cJSON* name = cJSON_GetObjectItemCaseSensitive(fn, "name"); + if (!name || !cJSON_IsString(name) || !name->valuestring) { + continue; + } + + cJSON* row = cJSON_CreateObject(); + if (!row) { + continue; + } + + cJSON_AddStringToObject(row, "name", name->valuestring); + + cJSON* description = cJSON_GetObjectItemCaseSensitive(fn, "description"); + if (description && cJSON_IsString(description) && description->valuestring) { + cJSON_AddStringToObject(row, "description", description->valuestring); + } else { + cJSON_AddStringToObject(row, "description", ""); + } + + cJSON* parameters = cJSON_GetObjectItemCaseSensitive(fn, "parameters"); + if (parameters) { + cJSON_AddItemToObject(row, "parameters", cJSON_Duplicate(parameters, 1)); + } else { + cJSON_AddItemToObject(row, "parameters", cJSON_CreateObject()); + } + + cJSON_AddItemToArray(tools, row); + } + + cJSON_AddBoolToObject(out, "success", 1); + cJSON_AddNumberToObject(out, "count", (double)cJSON_GetArraySize(tools)); + cJSON_AddItemToObject(out, "tools", tools); + + char* result = cJSON_PrintUnformatted(out); + cJSON_Delete(out); + cJSON_Delete(schema_arr); + return result; +} + static char* execute_trigger_list(tools_context_t* ctx, const char* args_json) { (void)args_json; if (!ctx || !ctx->trigger_manager) { @@ -4269,6 +4357,9 @@ char* tools_execute(tools_context_t* ctx, const char* tool_name, const char* arg if (strcmp(tool_name, "trigger_list") == 0) { return execute_trigger_list(ctx, args_json); } + if (strcmp(tool_name, "tool_list") == 0) { + return execute_tool_list(ctx, args_json); + } if (strcmp(tool_name, "nostr_post_readme") == 0) { return execute_nostr_post_readme(ctx, args_json);