471 lines
15 KiB
C
471 lines
15 KiB
C
#define _POSIX_C_SOURCE 200809L
|
|
|
|
#include "tools_internal.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "cjson/cJSON.h"
|
|
#include "../main.h"
|
|
#include "../nostr_handler.h"
|
|
#include "../../nostr_core_lib/nostr_core/nostr_core.h"
|
|
|
|
static char* json_error_local(const char* msg) {
|
|
cJSON* root = cJSON_CreateObject();
|
|
if (!root) return NULL;
|
|
cJSON_AddBoolToObject(root, "success", 0);
|
|
cJSON_AddStringToObject(root, "error", msg ? msg : "unknown error");
|
|
char* out = cJSON_PrintUnformatted(root);
|
|
cJSON_Delete(root);
|
|
return out;
|
|
}
|
|
|
|
static cJSON* parse_args_local(const char* args_json) {
|
|
const char* raw = args_json ? args_json : "{}";
|
|
cJSON* args = cJSON_Parse(raw);
|
|
if (!args || !cJSON_IsObject(args)) {
|
|
cJSON_Delete(args);
|
|
return NULL;
|
|
}
|
|
return args;
|
|
}
|
|
|
|
static int append_text_local(char** buf, size_t* cap, size_t* used, const char* s) {
|
|
if (!buf || !cap || !used || !s) return -1;
|
|
size_t n = strlen(s);
|
|
if (*used + n + 1U > *cap) {
|
|
size_t next = *cap;
|
|
while (*used + n + 1U > next) {
|
|
next = (next == 0U) ? 256U : (next * 2U);
|
|
}
|
|
char* grown = (char*)realloc(*buf, next);
|
|
if (!grown) return -1;
|
|
*buf = grown;
|
|
*cap = next;
|
|
}
|
|
memcpy(*buf + *used, s, n);
|
|
*used += n;
|
|
(*buf)[*used] = '\0';
|
|
return 0;
|
|
}
|
|
|
|
char* execute_message_current(tools_context_t* ctx, const char* args_json) {
|
|
if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable");
|
|
|
|
cJSON* args = parse_args_local(args_json);
|
|
if (!args) return json_error_local("invalid arguments JSON");
|
|
cJSON_Delete(args);
|
|
|
|
cJSON* out = cJSON_CreateObject();
|
|
if (!out) return NULL;
|
|
cJSON_AddBoolToObject(out, "success", 1);
|
|
cJSON_AddStringToObject(out, "content", ctx->template_current_user_message ? ctx->template_current_user_message : "");
|
|
char* json = cJSON_PrintUnformatted(out);
|
|
cJSON_Delete(out);
|
|
return json;
|
|
}
|
|
|
|
char* execute_agent_identity(tools_context_t* ctx, const char* args_json) {
|
|
if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable");
|
|
|
|
cJSON* args = parse_args_local(args_json);
|
|
if (!args) return json_error_local("invalid arguments JSON");
|
|
cJSON_Delete(args);
|
|
|
|
char npub[128] = {0};
|
|
if (nostr_key_to_bech32(ctx->cfg->keys.public_key, "npub", npub) != NOSTR_SUCCESS) {
|
|
strcpy(npub, "unknown");
|
|
}
|
|
|
|
cJSON* out = cJSON_CreateObject();
|
|
if (!out) return NULL;
|
|
cJSON_AddBoolToObject(out, "success", 1);
|
|
|
|
cJSON* identity = cJSON_CreateObject();
|
|
if (!identity) {
|
|
cJSON_Delete(out);
|
|
return NULL;
|
|
}
|
|
cJSON_AddStringToObject(identity,
|
|
"pubkey",
|
|
ctx->cfg->keys.public_key_hex[0] ? ctx->cfg->keys.public_key_hex : "unknown");
|
|
cJSON_AddStringToObject(identity, "npub", npub);
|
|
char* identity_json = cJSON_PrintUnformatted(identity);
|
|
cJSON_Delete(identity);
|
|
if (!identity_json) {
|
|
cJSON_Delete(out);
|
|
return NULL;
|
|
}
|
|
cJSON_AddStringToObject(out, "content", identity_json);
|
|
free(identity_json);
|
|
|
|
char* json = cJSON_PrintUnformatted(out);
|
|
cJSON_Delete(out);
|
|
return json;
|
|
}
|
|
|
|
char* execute_nostr_agent_profile(tools_context_t* ctx, const char* args_json) {
|
|
if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable");
|
|
|
|
cJSON* args = parse_args_local(args_json);
|
|
if (!args) return json_error_local("invalid arguments JSON");
|
|
cJSON_Delete(args);
|
|
|
|
char* kind0 = nostr_handler_get_agent_kind0_context();
|
|
const char* profile_json = (kind0 && kind0[0]) ? kind0 : "{}";
|
|
|
|
cJSON* out = cJSON_CreateObject();
|
|
if (!out) {
|
|
free(kind0);
|
|
return NULL;
|
|
}
|
|
|
|
cJSON_AddBoolToObject(out, "success", 1);
|
|
|
|
cJSON_AddStringToObject(out, "content", profile_json);
|
|
|
|
char* json = cJSON_PrintUnformatted(out);
|
|
cJSON_Delete(out);
|
|
free(kind0);
|
|
return json;
|
|
}
|
|
|
|
char* execute_nostr_agent_contacts(tools_context_t* ctx, const char* args_json) {
|
|
if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable");
|
|
|
|
cJSON* args = parse_args_local(args_json);
|
|
if (!args) return json_error_local("invalid arguments JSON");
|
|
cJSON_Delete(args);
|
|
|
|
char* kind3 = nostr_handler_get_agent_kind3_context();
|
|
const char* contacts_json = (kind3 && kind3[0]) ? kind3 : "[]";
|
|
|
|
cJSON* out = cJSON_CreateObject();
|
|
if (!out) {
|
|
free(kind3);
|
|
return NULL;
|
|
}
|
|
|
|
cJSON_AddBoolToObject(out, "success", 1);
|
|
|
|
cJSON_AddStringToObject(out, "content", contacts_json);
|
|
|
|
char* json = cJSON_PrintUnformatted(out);
|
|
cJSON_Delete(out);
|
|
free(kind3);
|
|
return json;
|
|
}
|
|
|
|
char* execute_nostr_agent_relays(tools_context_t* ctx, const char* args_json) {
|
|
if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable");
|
|
|
|
cJSON* args = parse_args_local(args_json);
|
|
if (!args) return json_error_local("invalid arguments JSON");
|
|
cJSON_Delete(args);
|
|
|
|
char* kind10002 = nostr_handler_get_agent_kind10002_context();
|
|
const char* relays_json = (kind10002 && kind10002[0]) ? kind10002 : "[]";
|
|
|
|
cJSON* out = cJSON_CreateObject();
|
|
if (!out) {
|
|
free(kind10002);
|
|
return NULL;
|
|
}
|
|
|
|
cJSON_AddBoolToObject(out, "success", 1);
|
|
|
|
cJSON_AddStringToObject(out, "content", relays_json);
|
|
|
|
char* json = cJSON_PrintUnformatted(out);
|
|
cJSON_Delete(out);
|
|
free(kind10002);
|
|
return json;
|
|
}
|
|
|
|
char* execute_nostr_agent_notes(tools_context_t* ctx, const char* args_json) {
|
|
if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable");
|
|
|
|
cJSON* args = parse_args_local(args_json);
|
|
if (!args) return json_error_local("invalid arguments JSON");
|
|
cJSON_Delete(args);
|
|
|
|
char* notes = nostr_handler_get_agent_kind1_notes_context();
|
|
const char* notes_text = (notes && notes[0]) ? notes : "";
|
|
|
|
cJSON* out = cJSON_CreateObject();
|
|
if (!out) {
|
|
free(notes);
|
|
return NULL;
|
|
}
|
|
|
|
cJSON_AddBoolToObject(out, "success", 1);
|
|
|
|
cJSON_AddStringToObject(out, "content", notes_text);
|
|
|
|
char* json = cJSON_PrintUnformatted(out);
|
|
cJSON_Delete(out);
|
|
free(notes);
|
|
return json;
|
|
}
|
|
|
|
char* execute_adopted_skills(tools_context_t* ctx, const char* args_json) {
|
|
if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable");
|
|
|
|
cJSON* args = parse_args_local(args_json);
|
|
if (!args) return json_error_local("invalid arguments JSON");
|
|
cJSON_Delete(args);
|
|
|
|
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);
|
|
return NULL;
|
|
}
|
|
|
|
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(10123));
|
|
cJSON_AddItemToObject(filter, "kinds", kinds);
|
|
cJSON_AddItemToArray(authors, cJSON_CreateString(ctx->cfg->keys.public_key_hex));
|
|
cJSON_AddItemToObject(filter, "authors", authors);
|
|
cJSON_AddNumberToObject(filter, "limit", 1);
|
|
|
|
char* events_json = nostr_handler_query_json(filter, 3000);
|
|
cJSON_Delete(filter);
|
|
|
|
cJSON* out = cJSON_CreateObject();
|
|
if (!out) {
|
|
free(events_json);
|
|
return NULL;
|
|
}
|
|
cJSON_AddBoolToObject(out, "success", 1);
|
|
cJSON_AddStringToObject(out, "content", (events_json && events_json[0]) ? events_json : "[]");
|
|
|
|
char* json = cJSON_PrintUnformatted(out);
|
|
cJSON_Delete(out);
|
|
free(events_json);
|
|
return json;
|
|
}
|
|
|
|
char* execute_trigger_event(tools_context_t* ctx, const char* args_json) {
|
|
if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable");
|
|
|
|
cJSON* args = parse_args_local(args_json);
|
|
if (!args) return json_error_local("invalid arguments JSON");
|
|
|
|
const char* trigger_json = (ctx->template_trigger_event_json && ctx->template_trigger_event_json[0] != '\0')
|
|
? ctx->template_trigger_event_json
|
|
: NULL;
|
|
|
|
if (!trigger_json) {
|
|
cJSON* arg_json = cJSON_GetObjectItemCaseSensitive(args, "trigger_event_json");
|
|
if (arg_json && cJSON_IsString(arg_json) && arg_json->valuestring && arg_json->valuestring[0] != '\0') {
|
|
trigger_json = arg_json->valuestring;
|
|
}
|
|
}
|
|
|
|
cJSON_Delete(args);
|
|
|
|
if (!trigger_json) {
|
|
trigger_json = "{}";
|
|
}
|
|
|
|
cJSON* out = cJSON_CreateObject();
|
|
if (!out) return NULL;
|
|
cJSON_AddBoolToObject(out, "success", 1);
|
|
cJSON_AddStringToObject(out, "content", trigger_json);
|
|
char* json = cJSON_PrintUnformatted(out);
|
|
cJSON_Delete(out);
|
|
return json;
|
|
}
|
|
|
|
char* execute_nostr_dm_history(tools_context_t* ctx, const char* args_json) {
|
|
if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable");
|
|
|
|
cJSON* args = parse_args_local(args_json);
|
|
if (!args) return json_error_local("invalid arguments JSON");
|
|
|
|
int limit = 12;
|
|
cJSON* limit_j = cJSON_GetObjectItemCaseSensitive(args, "limit");
|
|
if (limit_j && cJSON_IsNumber(limit_j)) {
|
|
limit = (int)limit_j->valuedouble;
|
|
}
|
|
if (limit < 1) limit = 1;
|
|
if (limit > 200) limit = 200;
|
|
|
|
int include_current = 0;
|
|
cJSON* include_current_j = cJSON_GetObjectItemCaseSensitive(args, "include_current");
|
|
if (include_current_j && cJSON_IsBool(include_current_j) && cJSON_IsTrue(include_current_j)) {
|
|
include_current = 1;
|
|
}
|
|
|
|
int format_text = 0;
|
|
cJSON* format_j = cJSON_GetObjectItemCaseSensitive(args, "format");
|
|
if (format_j && cJSON_IsString(format_j) && format_j->valuestring) {
|
|
if (strcmp(format_j->valuestring, "text") == 0) {
|
|
format_text = 1;
|
|
}
|
|
}
|
|
|
|
const char* peer_pubkey = ctx->cfg->admin.pubkey;
|
|
cJSON* peer_j = cJSON_GetObjectItemCaseSensitive(args, "peer_pubkey");
|
|
if (peer_j && cJSON_IsString(peer_j) && peer_j->valuestring && peer_j->valuestring[0] != '\0') {
|
|
peer_pubkey = peer_j->valuestring;
|
|
}
|
|
|
|
char* history_json = nostr_handler_get_dm_history_json(peer_pubkey, limit);
|
|
cJSON_Delete(args);
|
|
if (!history_json) {
|
|
return json_error_local("failed to fetch dm history");
|
|
}
|
|
|
|
cJSON* parsed = cJSON_Parse(history_json);
|
|
free(history_json);
|
|
if (!parsed || !cJSON_IsArray(parsed)) {
|
|
cJSON_Delete(parsed);
|
|
parsed = cJSON_CreateArray();
|
|
if (!parsed) {
|
|
return json_error_local("failed to build dm history response");
|
|
}
|
|
}
|
|
|
|
cJSON* filtered = cJSON_CreateArray();
|
|
if (!filtered) {
|
|
cJSON_Delete(parsed);
|
|
return json_error_local("failed to build dm history response");
|
|
}
|
|
|
|
const char* current_message = (!include_current && ctx->template_current_user_message)
|
|
? ctx->template_current_user_message
|
|
: NULL;
|
|
|
|
const char* last_role = NULL;
|
|
const char* last_content = NULL;
|
|
|
|
int n = cJSON_GetArraySize(parsed);
|
|
for (int i = 0; i < n; i++) {
|
|
cJSON* item = cJSON_GetArrayItem(parsed, i);
|
|
cJSON* role = item ? cJSON_GetObjectItemCaseSensitive(item, "role") : NULL;
|
|
cJSON* content = item ? cJSON_GetObjectItemCaseSensitive(item, "content") : NULL;
|
|
cJSON* created_at = item ? cJSON_GetObjectItemCaseSensitive(item, "created_at") : NULL;
|
|
|
|
if (!role || !content || !cJSON_IsString(role) || !cJSON_IsString(content) ||
|
|
!role->valuestring || !content->valuestring) {
|
|
continue;
|
|
}
|
|
|
|
const char* role_s = role->valuestring;
|
|
const char* content_s = content->valuestring;
|
|
int role_is_user = (strcmp(role_s, "user") == 0);
|
|
if (!role_is_user && strcmp(role_s, "assistant") != 0) {
|
|
continue;
|
|
}
|
|
|
|
if (i == n - 1 && role_is_user && current_message && strcmp(content_s, current_message) == 0) {
|
|
continue;
|
|
}
|
|
|
|
if (last_role && last_content && strcmp(last_role, role_s) == 0 && strcmp(last_content, content_s) == 0) {
|
|
continue;
|
|
}
|
|
|
|
cJSON* out_item = cJSON_CreateObject();
|
|
if (!out_item) {
|
|
continue;
|
|
}
|
|
cJSON_AddStringToObject(out_item, "role", role_s);
|
|
cJSON_AddStringToObject(out_item, "content", content_s);
|
|
if (created_at && cJSON_IsNumber(created_at)) {
|
|
cJSON_AddNumberToObject(out_item, "created_at", created_at->valuedouble);
|
|
}
|
|
cJSON_AddItemToArray(filtered, out_item);
|
|
|
|
last_role = role_s;
|
|
last_content = content_s;
|
|
}
|
|
|
|
cJSON_Delete(parsed);
|
|
|
|
char* rendered = NULL;
|
|
|
|
if (format_text) {
|
|
size_t cap = 256U;
|
|
size_t used = 0U;
|
|
rendered = (char*)malloc(cap);
|
|
if (!rendered) {
|
|
cJSON_Delete(filtered);
|
|
return json_error_local("failed to serialize dm history");
|
|
}
|
|
rendered[0] = '\0';
|
|
|
|
int m = cJSON_GetArraySize(filtered);
|
|
for (int i = 0; i < m; i++) {
|
|
cJSON* item = cJSON_GetArrayItem(filtered, i);
|
|
cJSON* role = item ? cJSON_GetObjectItemCaseSensitive(item, "role") : NULL;
|
|
cJSON* content = item ? cJSON_GetObjectItemCaseSensitive(item, "content") : NULL;
|
|
if (!role || !content || !cJSON_IsString(role) || !cJSON_IsString(content) ||
|
|
!role->valuestring || !content->valuestring) {
|
|
continue;
|
|
}
|
|
|
|
const char* prefix = (strcmp(role->valuestring, "user") == 0) ? "- **Admin**: " : "- **You**: ";
|
|
if (append_text_local(&rendered, &cap, &used, prefix) != 0 ||
|
|
append_text_local(&rendered, &cap, &used, content->valuestring) != 0 ||
|
|
append_text_local(&rendered, &cap, &used, "\n") != 0) {
|
|
free(rendered);
|
|
cJSON_Delete(filtered);
|
|
return json_error_local("failed to serialize dm history");
|
|
}
|
|
}
|
|
|
|
if (used > 0U && rendered[used - 1] == '\n') {
|
|
rendered[used - 1] = '\0';
|
|
}
|
|
} else {
|
|
rendered = cJSON_PrintUnformatted(filtered);
|
|
if (!rendered) {
|
|
cJSON_Delete(filtered);
|
|
return json_error_local("failed to serialize dm history");
|
|
}
|
|
}
|
|
|
|
cJSON_Delete(filtered);
|
|
|
|
cJSON* out = cJSON_CreateObject();
|
|
if (!out) {
|
|
free(rendered);
|
|
return NULL;
|
|
}
|
|
cJSON_AddBoolToObject(out, "success", 1);
|
|
cJSON_AddStringToObject(out, "content", rendered);
|
|
free(rendered);
|
|
|
|
char* json = cJSON_PrintUnformatted(out);
|
|
cJSON_Delete(out);
|
|
return json;
|
|
}
|
|
|
|
char* execute_agent_version(const char* args_json) {
|
|
cJSON* args = parse_args_local(args_json);
|
|
if (!args) return json_error_local("invalid arguments JSON");
|
|
cJSON_Delete(args);
|
|
|
|
cJSON* out = cJSON_CreateObject();
|
|
if (!out) return NULL;
|
|
|
|
cJSON_AddBoolToObject(out, "success", 1);
|
|
cJSON_AddStringToObject(out, "name", DIDACTYL_NAME);
|
|
cJSON_AddStringToObject(out, "description", DIDACTYL_DESCRIPTION);
|
|
cJSON_AddStringToObject(out, "software", DIDACTYL_SOFTWARE);
|
|
cJSON_AddStringToObject(out, "version", DIDACTYL_VERSION);
|
|
cJSON_AddNumberToObject(out, "version_major", DIDACTYL_VERSION_MAJOR);
|
|
cJSON_AddNumberToObject(out, "version_minor", DIDACTYL_VERSION_MINOR);
|
|
cJSON_AddNumberToObject(out, "version_patch", DIDACTYL_VERSION_PATCH);
|
|
|
|
char* json = cJSON_PrintUnformatted(out);
|
|
cJSON_Delete(out);
|
|
return json;
|
|
}
|