v0.0.3 - Add startup-event reconciliation and support structured content_fields encoding in startup config
This commit is contained in:
@@ -2,6 +2,6 @@
|
||||
description: "Increments and pushes the repo"
|
||||
---
|
||||
|
||||
Then run increment_and_push.sh followed in the command line with a good description of the changes that were made.
|
||||
Run increment_and_push.sh followed in the command line with a good description of the changes that were made.
|
||||
|
||||
For example: ./increment_and_push.sh "Fixed that nasty bug"
|
||||
|
||||
@@ -161,6 +161,62 @@ static int parse_tools_config(cJSON* root, didactyl_config_t* config) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_startup_events(cJSON* root, didactyl_config_t* config) {
|
||||
cJSON* arr = cJSON_GetObjectItemCaseSensitive(root, "startup_events");
|
||||
if (!arr || !cJSON_IsArray(arr)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int count = cJSON_GetArraySize(arr);
|
||||
if (count <= 0) return 0;
|
||||
|
||||
config->startup_events = (startup_event_t*)calloc((size_t)count, sizeof(startup_event_t));
|
||||
if (!config->startup_events) return -1;
|
||||
config->startup_event_count = count;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
cJSON* item = cJSON_GetArrayItem(arr, i);
|
||||
if (!item || !cJSON_IsObject(item)) return -1;
|
||||
|
||||
cJSON* kind = cJSON_GetObjectItemCaseSensitive(item, "kind");
|
||||
cJSON* content = cJSON_GetObjectItemCaseSensitive(item, "content");
|
||||
cJSON* content_fields = cJSON_GetObjectItemCaseSensitive(item, "content_fields");
|
||||
cJSON* tags = cJSON_GetObjectItemCaseSensitive(item, "tags");
|
||||
|
||||
if (!kind || !cJSON_IsNumber(kind)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
config->startup_events[i].kind = (int)kind->valuedouble;
|
||||
|
||||
if (content_fields) {
|
||||
if (!cJSON_IsObject(content_fields)) {
|
||||
return -1;
|
||||
}
|
||||
config->startup_events[i].content = cJSON_PrintUnformatted(content_fields);
|
||||
if (!config->startup_events[i].content) {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
if (!content || !cJSON_IsString(content) || !content->valuestring) {
|
||||
return -1;
|
||||
}
|
||||
config->startup_events[i].content = strdup(content->valuestring);
|
||||
if (!config->startup_events[i].content) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (tags) {
|
||||
if (!cJSON_IsArray(tags)) return -1;
|
||||
config->startup_events[i].tags_json = cJSON_PrintUnformatted(tags);
|
||||
if (!config->startup_events[i].tags_json) return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_relays(cJSON* root, didactyl_config_t* config) {
|
||||
cJSON* relays = cJSON_GetObjectItemCaseSensitive(root, "relays");
|
||||
if (!relays || !cJSON_IsArray(relays)) {
|
||||
@@ -206,6 +262,14 @@ void config_free(didactyl_config_t* config) {
|
||||
free(config->relays);
|
||||
}
|
||||
|
||||
if (config->startup_events) {
|
||||
for (int i = 0; i < config->startup_event_count; i++) {
|
||||
free(config->startup_events[i].content);
|
||||
free(config->startup_events[i].tags_json);
|
||||
}
|
||||
free(config->startup_events);
|
||||
}
|
||||
|
||||
memset(config, 0, sizeof(*config));
|
||||
}
|
||||
|
||||
@@ -308,6 +372,10 @@ int config_load(const char* path, didactyl_config_t* config) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (parse_startup_events(root, config) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (decode_private_key(config->keys.nsec, config->keys.private_key) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
@@ -50,6 +50,12 @@ typedef struct {
|
||||
shell_tools_config_t shell;
|
||||
} tools_config_t;
|
||||
|
||||
typedef struct {
|
||||
int kind;
|
||||
char* content;
|
||||
char* tags_json; // JSON array string for tags, optional
|
||||
} startup_event_t;
|
||||
|
||||
typedef struct {
|
||||
agent_profile_t profile;
|
||||
agent_keys_t keys;
|
||||
@@ -58,6 +64,8 @@ typedef struct {
|
||||
int relay_count;
|
||||
llm_config_t llm;
|
||||
tools_config_t tools;
|
||||
startup_event_t* startup_events;
|
||||
int startup_event_count;
|
||||
} didactyl_config_t;
|
||||
|
||||
int config_load(const char* path, didactyl_config_t* config);
|
||||
|
||||
+10
-25
@@ -9,7 +9,6 @@
|
||||
#include "main.h"
|
||||
#include "agent.h"
|
||||
#include "config.h"
|
||||
#include "context.h"
|
||||
#include "llm.h"
|
||||
#include "nostr_handler.h"
|
||||
#include "debug.h"
|
||||
@@ -23,19 +22,15 @@ static void signal_handler(int signum) {
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
const char* config_path = "./config.json";
|
||||
const char* context_path = "./SYSTEM.md";
|
||||
|
||||
int debug_level = DEBUG_LEVEL_TRACE;
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "--config") == 0 && i + 1 < argc) {
|
||||
config_path = argv[++i];
|
||||
} else if (strcmp(argv[i], "--context") == 0 && i + 1 < argc) {
|
||||
context_path = argv[++i];
|
||||
} else if (strcmp(argv[i], "--debug") == 0 && i + 1 < argc) {
|
||||
debug_level = atoi(argv[++i]);
|
||||
} else {
|
||||
fprintf(stderr, "Usage: %s [--config <path>] [--context <path>] [--debug <0-5>]\n", argv[0]);
|
||||
fprintf(stderr, "Usage: %s [--config <path>] [--debug <0-5>]\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -56,17 +51,8 @@ int main(int argc, char** argv) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
char* system_context = context_load(context_path);
|
||||
if (!system_context) {
|
||||
fprintf(stderr, "Failed to load context file: %s\n", context_path);
|
||||
config_free(&cfg);
|
||||
nostr_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (llm_init(&cfg.llm) != 0) {
|
||||
fprintf(stderr, "Failed to initialize llm client\n");
|
||||
context_free(system_context);
|
||||
config_free(&cfg);
|
||||
nostr_cleanup();
|
||||
return 1;
|
||||
@@ -75,28 +61,29 @@ int main(int argc, char** argv) {
|
||||
if (nostr_handler_init(&cfg) != 0) {
|
||||
fprintf(stderr, "Failed to initialize nostr handler\n");
|
||||
llm_cleanup();
|
||||
context_free(system_context);
|
||||
config_free(&cfg);
|
||||
nostr_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (nostr_handler_reconcile_startup_events() != 0) {
|
||||
DEBUG_WARN("[didactyl] startup phase: reconcile startup events failed (continuing)");
|
||||
}
|
||||
|
||||
const char* system_context = nostr_handler_get_system_context();
|
||||
if (!system_context || system_context[0] == '\0') {
|
||||
system_context = "You are Didactyl, a sovereign AI agent living on Nostr.";
|
||||
}
|
||||
|
||||
if (agent_init(&cfg, system_context) != 0) {
|
||||
fprintf(stderr, "Failed to initialize agent\n");
|
||||
nostr_handler_cleanup();
|
||||
llm_cleanup();
|
||||
context_free(system_context);
|
||||
config_free(&cfg);
|
||||
nostr_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
DEBUG_INFO("[didactyl] startup phase: publish profile begin");
|
||||
if (nostr_handler_publish_profile() != 0) {
|
||||
DEBUG_WARN("[didactyl] publish profile deferred/failed (will continue startup)");
|
||||
}
|
||||
DEBUG_INFO("[didactyl] startup phase: publish profile end");
|
||||
|
||||
DEBUG_INFO("[didactyl] startup phase: subscribe DMs begin");
|
||||
if (nostr_handler_subscribe_dms(agent_on_message, NULL) != 0) {
|
||||
DEBUG_ERROR("[didactyl] startup phase: subscribe DMs failed");
|
||||
@@ -104,7 +91,6 @@ int main(int argc, char** argv) {
|
||||
agent_cleanup();
|
||||
nostr_handler_cleanup();
|
||||
llm_cleanup();
|
||||
context_free(system_context);
|
||||
config_free(&cfg);
|
||||
nostr_cleanup();
|
||||
return 1;
|
||||
@@ -129,7 +115,6 @@ int main(int argc, char** argv) {
|
||||
agent_cleanup();
|
||||
nostr_handler_cleanup();
|
||||
llm_cleanup();
|
||||
context_free(system_context);
|
||||
config_free(&cfg);
|
||||
nostr_cleanup();
|
||||
|
||||
|
||||
+2
-2
@@ -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 2
|
||||
#define DIDACTYL_VERSION "v0.0.2"
|
||||
#define DIDACTYL_VERSION_PATCH 3
|
||||
#define DIDACTYL_VERSION "v0.0.3"
|
||||
|
||||
// Agent metadata
|
||||
#define DIDACTYL_NAME "Didactyl"
|
||||
|
||||
@@ -20,6 +20,7 @@ static int g_poll_counter = 0;
|
||||
static time_t g_start_time = 0;
|
||||
static time_t g_last_status_log_time = 0;
|
||||
static nostr_pool_relay_status_t* g_last_relay_statuses = NULL;
|
||||
static char* g_system_context = NULL;
|
||||
|
||||
#define DM_DEDUP_CACHE_SIZE 256
|
||||
|
||||
@@ -197,6 +198,32 @@ static int extract_first_p_tag(cJSON* tags, char out_pubkey_hex[65]) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
static const char* extract_d_tag_value(cJSON* tags) {
|
||||
if (!tags || !cJSON_IsArray(tags)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int n = cJSON_GetArraySize(tags);
|
||||
for (int i = 0; i < n; i++) {
|
||||
cJSON* tag = cJSON_GetArrayItem(tags, i);
|
||||
if (!tag || !cJSON_IsArray(tag) || cJSON_GetArraySize(tag) < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cJSON* key = cJSON_GetArrayItem(tag, 0);
|
||||
cJSON* val = cJSON_GetArrayItem(tag, 1);
|
||||
if (!key || !val || !cJSON_IsString(key) || !cJSON_IsString(val)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcmp(key->valuestring, "d") == 0 && val->valuestring && val->valuestring[0] != '\0') {
|
||||
return val->valuestring;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void trace_event_json(const char* prefix, cJSON* event) {
|
||||
if (g_debug_level < DEBUG_LEVEL_TRACE || !event) {
|
||||
return;
|
||||
@@ -638,6 +665,96 @@ char* nostr_handler_query_json(cJSON* filter, int timeout_ms) {
|
||||
return out;
|
||||
}
|
||||
|
||||
int nostr_handler_reconcile_startup_events(void) {
|
||||
if (!g_cfg || !g_pool) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
free(g_system_context);
|
||||
g_system_context = NULL;
|
||||
|
||||
for (int i = 0; i < g_cfg->startup_event_count; i++) {
|
||||
startup_event_t* se = &g_cfg->startup_events[i];
|
||||
if (!se->content) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cJSON* tags = NULL;
|
||||
if (se->tags_json) {
|
||||
tags = cJSON_Parse(se->tags_json);
|
||||
if (!tags || !cJSON_IsArray(tags)) {
|
||||
cJSON_Delete(tags);
|
||||
tags = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
cJSON* filter = cJSON_CreateObject();
|
||||
cJSON* kinds = cJSON_CreateArray();
|
||||
cJSON* authors = cJSON_CreateArray();
|
||||
if (!filter || !kinds || !authors) {
|
||||
cJSON_Delete(filter);
|
||||
cJSON_Delete(kinds);
|
||||
cJSON_Delete(authors);
|
||||
cJSON_Delete(tags);
|
||||
return -1;
|
||||
}
|
||||
|
||||
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(se->kind));
|
||||
cJSON_AddItemToObject(filter, "kinds", kinds);
|
||||
cJSON_AddItemToArray(authors, cJSON_CreateString(g_cfg->keys.public_key_hex));
|
||||
cJSON_AddItemToObject(filter, "authors", authors);
|
||||
cJSON_AddNumberToObject(filter, "limit", 20);
|
||||
|
||||
const char* d_tag = extract_d_tag_value(tags);
|
||||
if (d_tag) {
|
||||
cJSON* d_values = cJSON_CreateArray();
|
||||
cJSON_AddItemToArray(d_values, cJSON_CreateString(d_tag));
|
||||
cJSON_AddItemToObject(filter, "#d", d_values);
|
||||
}
|
||||
|
||||
char* events_json = nostr_handler_query_json(filter, 8000);
|
||||
cJSON_Delete(filter);
|
||||
|
||||
int found = 0;
|
||||
if (events_json) {
|
||||
cJSON* events = cJSON_Parse(events_json);
|
||||
if (events && cJSON_IsArray(events) && cJSON_GetArraySize(events) > 0) {
|
||||
found = 1;
|
||||
if (se->kind == 31120) {
|
||||
cJSON* ev = cJSON_GetArrayItem(events, 0);
|
||||
cJSON* content = ev ? cJSON_GetObjectItemCaseSensitive(ev, "content") : NULL;
|
||||
if (content && cJSON_IsString(content) && content->valuestring) {
|
||||
g_system_context = strdup(content->valuestring);
|
||||
}
|
||||
}
|
||||
}
|
||||
cJSON_Delete(events);
|
||||
free(events_json);
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
if (nostr_handler_publish_kind_event(se->kind, se->content, tags) != 0) {
|
||||
DEBUG_WARN("[didactyl] startup event publish failed for kind=%d", se->kind);
|
||||
}
|
||||
if (se->kind == 31120 && !g_system_context) {
|
||||
g_system_context = strdup(se->content);
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_Delete(tags);
|
||||
}
|
||||
|
||||
if (!g_system_context) {
|
||||
g_system_context = strdup("You are Didactyl, a sovereign AI agent living on Nostr.");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* nostr_handler_get_system_context(void) {
|
||||
return g_system_context;
|
||||
}
|
||||
|
||||
int nostr_handler_poll(int timeout_ms) {
|
||||
if (!g_pool) {
|
||||
return -1;
|
||||
@@ -670,6 +787,8 @@ void nostr_handler_cleanup(void) {
|
||||
g_cfg = NULL;
|
||||
g_dm_callback = NULL;
|
||||
g_dm_user_data = NULL;
|
||||
free(g_system_context);
|
||||
g_system_context = NULL;
|
||||
memset(g_seen_dm_ids, 0, sizeof(g_seen_dm_ids));
|
||||
g_seen_dm_count = 0;
|
||||
g_seen_dm_next = 0;
|
||||
|
||||
@@ -13,6 +13,8 @@ int nostr_handler_send_dm(const char* recipient_pubkey_hex, const char* message)
|
||||
int nostr_handler_publish_kind_event(int kind, const char* content, cJSON* tags);
|
||||
char* nostr_handler_query_json(cJSON* filter, int timeout_ms);
|
||||
int nostr_handler_poll(int timeout_ms);
|
||||
int nostr_handler_reconcile_startup_events(void);
|
||||
const char* nostr_handler_get_system_context(void);
|
||||
void nostr_handler_cleanup(void);
|
||||
|
||||
#endif
|
||||
+188
@@ -5,6 +5,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "cjson/cJSON.h"
|
||||
#include "nostr_handler.h"
|
||||
@@ -29,6 +30,33 @@ static char* json_success_with_message(const char* msg) {
|
||||
return out;
|
||||
}
|
||||
|
||||
static int is_safe_relative_path(const char* path) {
|
||||
if (!path || path[0] == '\0') return 0;
|
||||
if (path[0] == '/') return 0;
|
||||
if (strstr(path, "..") != NULL) return 0;
|
||||
if (strchr(path, '\\') != NULL) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int build_tool_path(tools_context_t* ctx, const char* rel_path, char* out, size_t out_size) {
|
||||
if (!ctx || !ctx->cfg || !rel_path || !out || out_size == 0) return -1;
|
||||
if (!is_safe_relative_path(rel_path)) return -1;
|
||||
|
||||
const char* cwd = ctx->cfg->tools.shell.working_directory[0] != '\0'
|
||||
? ctx->cfg->tools.shell.working_directory
|
||||
: ".";
|
||||
|
||||
int n = 0;
|
||||
if (strcmp(cwd, ".") == 0) {
|
||||
n = snprintf(out, out_size, "%s", rel_path);
|
||||
} else {
|
||||
n = snprintf(out, out_size, "%s/%s", cwd, rel_path);
|
||||
}
|
||||
|
||||
if (n < 0 || (size_t)n >= out_size) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tools_init(tools_context_t* ctx, didactyl_config_t* cfg) {
|
||||
if (!ctx || !cfg) return -1;
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
@@ -120,6 +148,60 @@ char* tools_build_openai_schema_json(const tools_context_t* ctx) {
|
||||
cJSON_AddItemToObject(t3, "function", t3_fn);
|
||||
cJSON_AddItemToArray(tools, t3);
|
||||
|
||||
cJSON* t4 = cJSON_CreateObject();
|
||||
cJSON* t4_fn = cJSON_CreateObject();
|
||||
cJSON* t4_params = cJSON_CreateObject();
|
||||
cJSON* t4_props = cJSON_CreateObject();
|
||||
cJSON* t4_required = cJSON_CreateArray();
|
||||
|
||||
cJSON_AddStringToObject(t4, "type", "function");
|
||||
cJSON_AddStringToObject(t4_fn, "name", "file_read");
|
||||
cJSON_AddStringToObject(t4_fn, "description", "Read a local file as text from the configured working directory");
|
||||
cJSON_AddStringToObject(t4_params, "type", "object");
|
||||
cJSON_AddItemToObject(t4_params, "properties", t4_props);
|
||||
cJSON_AddItemToObject(t4_params, "required", t4_required);
|
||||
|
||||
cJSON* p_fr_path = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_fr_path, "type", "string");
|
||||
cJSON_AddItemToObject(t4_props, "path", p_fr_path);
|
||||
cJSON* p_fr_max = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_fr_max, "type", "integer");
|
||||
cJSON_AddItemToObject(t4_props, "max_bytes", p_fr_max);
|
||||
cJSON_AddItemToArray(t4_required, cJSON_CreateString("path"));
|
||||
|
||||
cJSON_AddItemToObject(t4_fn, "parameters", t4_params);
|
||||
cJSON_AddItemToObject(t4, "function", t4_fn);
|
||||
cJSON_AddItemToArray(tools, t4);
|
||||
|
||||
cJSON* t5 = cJSON_CreateObject();
|
||||
cJSON* t5_fn = cJSON_CreateObject();
|
||||
cJSON* t5_params = cJSON_CreateObject();
|
||||
cJSON* t5_props = cJSON_CreateObject();
|
||||
cJSON* t5_required = cJSON_CreateArray();
|
||||
|
||||
cJSON_AddStringToObject(t5, "type", "function");
|
||||
cJSON_AddStringToObject(t5_fn, "name", "file_write");
|
||||
cJSON_AddStringToObject(t5_fn, "description", "Write text content to a local file in the configured working directory");
|
||||
cJSON_AddStringToObject(t5_params, "type", "object");
|
||||
cJSON_AddItemToObject(t5_params, "properties", t5_props);
|
||||
cJSON_AddItemToObject(t5_params, "required", t5_required);
|
||||
|
||||
cJSON* p_fw_path = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_fw_path, "type", "string");
|
||||
cJSON_AddItemToObject(t5_props, "path", p_fw_path);
|
||||
cJSON* p_fw_content = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_fw_content, "type", "string");
|
||||
cJSON_AddItemToObject(t5_props, "content", p_fw_content);
|
||||
cJSON* p_fw_append = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_fw_append, "type", "boolean");
|
||||
cJSON_AddItemToObject(t5_props, "append", p_fw_append);
|
||||
cJSON_AddItemToArray(t5_required, cJSON_CreateString("path"));
|
||||
cJSON_AddItemToArray(t5_required, cJSON_CreateString("content"));
|
||||
|
||||
cJSON_AddItemToObject(t5_fn, "parameters", t5_params);
|
||||
cJSON_AddItemToObject(t5, "function", t5_fn);
|
||||
cJSON_AddItemToArray(tools, t5);
|
||||
|
||||
char* out = cJSON_PrintUnformatted(tools);
|
||||
cJSON_Delete(tools);
|
||||
return out;
|
||||
@@ -249,6 +331,106 @@ static char* execute_shell_exec(tools_context_t* ctx, const char* args_json) {
|
||||
return json;
|
||||
}
|
||||
|
||||
static char* execute_file_read(tools_context_t* ctx, const char* args_json) {
|
||||
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
|
||||
|
||||
cJSON* args = cJSON_Parse(args_json ? args_json : "{}");
|
||||
if (!args) return json_error("invalid arguments JSON");
|
||||
|
||||
cJSON* path = cJSON_GetObjectItemCaseSensitive(args, "path");
|
||||
cJSON* maxb = cJSON_GetObjectItemCaseSensitive(args, "max_bytes");
|
||||
if (!path || !cJSON_IsString(path) || !path->valuestring) {
|
||||
cJSON_Delete(args);
|
||||
return json_error("file_read requires string path");
|
||||
}
|
||||
|
||||
int hard_max = ctx->cfg->tools.shell.max_output_bytes > 0 ? ctx->cfg->tools.shell.max_output_bytes : 65536;
|
||||
int max_bytes = (maxb && cJSON_IsNumber(maxb)) ? (int)maxb->valuedouble : hard_max;
|
||||
if (max_bytes <= 0 || max_bytes > hard_max) max_bytes = hard_max;
|
||||
|
||||
char file_path[PATH_MAX];
|
||||
if (build_tool_path(ctx, path->valuestring, file_path, sizeof(file_path)) != 0) {
|
||||
cJSON_Delete(args);
|
||||
return json_error("file_read path is not allowed");
|
||||
}
|
||||
|
||||
FILE* fp = fopen(file_path, "rb");
|
||||
cJSON_Delete(args);
|
||||
if (!fp) return json_error("file_read failed to open file");
|
||||
|
||||
char* buf = (char*)calloc((size_t)max_bytes + 1U, 1U);
|
||||
if (!buf) {
|
||||
fclose(fp);
|
||||
return json_error("allocation failure");
|
||||
}
|
||||
|
||||
size_t n = fread(buf, 1, (size_t)max_bytes, fp);
|
||||
int truncated = !feof(fp) ? 1 : 0;
|
||||
fclose(fp);
|
||||
buf[n] = '\0';
|
||||
|
||||
cJSON* out = cJSON_CreateObject();
|
||||
if (!out) {
|
||||
free(buf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cJSON_AddBoolToObject(out, "success", 1);
|
||||
cJSON_AddStringToObject(out, "path", file_path);
|
||||
cJSON_AddNumberToObject(out, "bytes_read", (double)n);
|
||||
cJSON_AddBoolToObject(out, "truncated", truncated);
|
||||
cJSON_AddStringToObject(out, "content", buf);
|
||||
free(buf);
|
||||
|
||||
char* json = cJSON_PrintUnformatted(out);
|
||||
cJSON_Delete(out);
|
||||
return json;
|
||||
}
|
||||
|
||||
static char* execute_file_write(tools_context_t* ctx, const char* args_json) {
|
||||
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
|
||||
|
||||
cJSON* args = cJSON_Parse(args_json ? args_json : "{}");
|
||||
if (!args) return json_error("invalid arguments JSON");
|
||||
|
||||
cJSON* path = cJSON_GetObjectItemCaseSensitive(args, "path");
|
||||
cJSON* content = cJSON_GetObjectItemCaseSensitive(args, "content");
|
||||
cJSON* append = cJSON_GetObjectItemCaseSensitive(args, "append");
|
||||
if (!path || !cJSON_IsString(path) || !path->valuestring ||
|
||||
!content || !cJSON_IsString(content) || !content->valuestring) {
|
||||
cJSON_Delete(args);
|
||||
return json_error("file_write requires string path and content");
|
||||
}
|
||||
|
||||
char file_path[PATH_MAX];
|
||||
if (build_tool_path(ctx, path->valuestring, file_path, sizeof(file_path)) != 0) {
|
||||
cJSON_Delete(args);
|
||||
return json_error("file_write path is not allowed");
|
||||
}
|
||||
|
||||
const char* content_str = content->valuestring;
|
||||
size_t len = strlen(content_str);
|
||||
int do_append = (append && cJSON_IsBool(append) && cJSON_IsTrue(append)) ? 1 : 0;
|
||||
|
||||
FILE* fp = fopen(file_path, do_append ? "ab" : "wb");
|
||||
cJSON_Delete(args);
|
||||
if (!fp) return json_error("file_write failed to open file");
|
||||
|
||||
size_t n = fwrite(content_str, 1, len, fp);
|
||||
fclose(fp);
|
||||
if (n != len) return json_error("file_write failed to write all bytes");
|
||||
|
||||
cJSON* out = cJSON_CreateObject();
|
||||
if (!out) return NULL;
|
||||
cJSON_AddBoolToObject(out, "success", 1);
|
||||
cJSON_AddStringToObject(out, "path", file_path);
|
||||
cJSON_AddNumberToObject(out, "bytes_written", (double)n);
|
||||
cJSON_AddBoolToObject(out, "append", do_append);
|
||||
char* json = cJSON_PrintUnformatted(out);
|
||||
cJSON_Delete(out);
|
||||
return json;
|
||||
}
|
||||
|
||||
char* tools_execute(tools_context_t* ctx, const char* tool_name, const char* args_json) {
|
||||
if (!tool_name) return json_error("missing tool name");
|
||||
|
||||
@@ -261,6 +443,12 @@ char* tools_execute(tools_context_t* ctx, const char* tool_name, const char* arg
|
||||
if (strcmp(tool_name, "shell_exec") == 0) {
|
||||
return execute_shell_exec(ctx, args_json);
|
||||
}
|
||||
if (strcmp(tool_name, "file_read") == 0) {
|
||||
return execute_file_read(ctx, args_json);
|
||||
}
|
||||
if (strcmp(tool_name, "file_write") == 0) {
|
||||
return execute_file_write(ctx, args_json);
|
||||
}
|
||||
|
||||
return json_error("unknown tool");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user