Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6edad064c6 | ||
|
|
656d251a2a |
@@ -54,11 +54,11 @@ Skills compose by adoption-list order (`10123`) and trigger tags carry runtime e
|
||||
|
||||
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.2.25
|
||||
## Current Status — v0.2.27
|
||||
|
||||
**Active build — this project is barely working. Experiment at your own risk.**
|
||||
|
||||
> Last release update: v0.2.25 — Fix private skill interoperability: NIP-44 encode/decode for kind 31124 with runtime tag normalization
|
||||
> Last release update: v0.2.27 — Remove markdown context snapshot logging; keep only raw provider request JSON logging and simplify context debug plumbing
|
||||
|
||||
- Connects to configured relays with auto-reconnect and relay state transition logging
|
||||
- Publishes configured startup events per relay as each relay becomes connected
|
||||
|
||||
341
src/agent.c
341
src/agent.c
@@ -23,22 +23,11 @@
|
||||
#include "../../nostr_core_lib/nostr_core/nostr_core.h"
|
||||
#include "context_format.h"
|
||||
#include "context_roles.h"
|
||||
#include "json_to_markdown.h"
|
||||
|
||||
static didactyl_config_t* g_cfg = NULL;
|
||||
static tools_context_t g_tools_ctx;
|
||||
static struct trigger_manager* g_trigger_manager = NULL;
|
||||
|
||||
typedef enum {
|
||||
CONTEXT_DEBUG_OFF = 0,
|
||||
CONTEXT_DEBUG_INIT = 1,
|
||||
CONTEXT_DEBUG_FULL = 2
|
||||
} context_debug_mode_t;
|
||||
|
||||
static context_debug_mode_t g_context_debug_mode = CONTEXT_DEBUG_OFF;
|
||||
static char g_context_debug_run_stamp[32] = {0};
|
||||
static int g_context_debug_turn = 0;
|
||||
|
||||
#define AGENT_CONTEXT_PART_NAMES_MAX 512
|
||||
static char* g_context_part_names[AGENT_CONTEXT_PART_NAMES_MAX];
|
||||
static int g_context_part_names_count = 0;
|
||||
@@ -263,8 +252,6 @@ static int append_tool_result_message(cJSON* messages, const char* tool_call_id,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void append_context_log(const char* sender_pubkey_hex, const char* phase, const char* context_payload);
|
||||
|
||||
static char* local_json_error(const char* msg) {
|
||||
cJSON* out = cJSON_CreateObject();
|
||||
if (!out) return NULL;
|
||||
@@ -848,7 +835,6 @@ static int handle_slash_command(const char* sender_pubkey_hex, const char* messa
|
||||
message,
|
||||
result_json ? result_json : "",
|
||||
dm_payload ? dm_payload : "");
|
||||
append_context_log(sender_pubkey_hex, "direct_tool_exec", log_payload);
|
||||
free(log_payload);
|
||||
}
|
||||
|
||||
@@ -915,125 +901,6 @@ const char* agent_classify_message_part(cJSON* msg, int idx) {
|
||||
return detect_context_section(role_s, content_s, idx);
|
||||
}
|
||||
|
||||
static char* render_messages_json_as_markdown(const char* messages_json) {
|
||||
if (!messages_json || messages_json[0] == '\0') {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cJSON* root = cJSON_Parse(messages_json);
|
||||
if (!root || !cJSON_IsArray(root)) {
|
||||
cJSON_Delete(root);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char* out = (char*)malloc(4096);
|
||||
if (!out) {
|
||||
cJSON_Delete(root);
|
||||
return NULL;
|
||||
}
|
||||
size_t cap = 4096;
|
||||
size_t used = 0;
|
||||
out[0] = '\0';
|
||||
|
||||
int n = cJSON_GetArraySize(root);
|
||||
for (int i = 0; i < n; i++) {
|
||||
cJSON* msg = cJSON_GetArrayItem(root, i);
|
||||
if (!msg || !cJSON_IsObject(msg)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cJSON* role = cJSON_GetObjectItemCaseSensitive(msg, "role");
|
||||
const char* role_s = (role && cJSON_IsString(role) && role->valuestring) ? role->valuestring : "message";
|
||||
|
||||
if (i > 0) {
|
||||
if (append_textf_local(&out, &cap, &used, "\n---\n\n") != 0) {
|
||||
free(out);
|
||||
cJSON_Delete(root);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (append_textf_local(&out,
|
||||
&cap,
|
||||
&used,
|
||||
"**Message %d (%s)**\n\n",
|
||||
i + 1,
|
||||
role_s) != 0) {
|
||||
free(out);
|
||||
cJSON_Delete(root);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cJSON* field = NULL;
|
||||
cJSON_ArrayForEach(field, msg) {
|
||||
if (!field || !field->string) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char* key = field->string;
|
||||
if (strcmp(key, "_ts") == 0) {
|
||||
continue; // Internal timestamp metadata; not useful in human-readable snapshots
|
||||
}
|
||||
|
||||
char* raw_value = NULL;
|
||||
char* rendered_md = NULL;
|
||||
|
||||
if (strcmp(key, "content") == 0 && strcmp(role_s, "tool") == 0 && cJSON_IsString(field) && field->valuestring) {
|
||||
cJSON* tool_payload = cJSON_Parse(field->valuestring);
|
||||
if (tool_payload && cJSON_IsObject(tool_payload)) {
|
||||
cJSON* inner = cJSON_GetObjectItemCaseSensitive(tool_payload, "content");
|
||||
if (inner && cJSON_IsString(inner) && inner->valuestring) {
|
||||
cJSON* inner_json = cJSON_Parse(inner->valuestring);
|
||||
if (inner_json) {
|
||||
cJSON_ReplaceItemInObjectCaseSensitive(tool_payload, "content", inner_json);
|
||||
}
|
||||
}
|
||||
raw_value = cJSON_PrintUnformatted(tool_payload);
|
||||
cJSON_Delete(tool_payload);
|
||||
}
|
||||
}
|
||||
|
||||
if (!raw_value) {
|
||||
if (cJSON_IsString(field) && field->valuestring) {
|
||||
raw_value = strdup(field->valuestring);
|
||||
} else {
|
||||
raw_value = cJSON_PrintUnformatted(field);
|
||||
}
|
||||
}
|
||||
|
||||
if (raw_value) {
|
||||
rendered_md = json_to_markdown(raw_value, 8);
|
||||
}
|
||||
|
||||
const char* display = rendered_md ? rendered_md : (raw_value ? raw_value : "");
|
||||
if (append_textf_local(&out,
|
||||
&cap,
|
||||
&used,
|
||||
"- **%s**: %s\n",
|
||||
key,
|
||||
display) != 0) {
|
||||
free(rendered_md);
|
||||
free(raw_value);
|
||||
free(out);
|
||||
cJSON_Delete(root);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
free(rendered_md);
|
||||
free(raw_value);
|
||||
}
|
||||
|
||||
if (append_textf_local(&out, &cap, &used, "\n") != 0) {
|
||||
free(out);
|
||||
cJSON_Delete(root);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
return out;
|
||||
}
|
||||
|
||||
static void clear_context_part_names_locked(void) {
|
||||
for (int i = 0; i < g_context_part_names_count; i++) {
|
||||
free(g_context_part_names[i]);
|
||||
@@ -1082,147 +949,6 @@ static __attribute__((unused)) void template_emit_hook(const char* section_name,
|
||||
set_context_part_name(message_index, section_name ? section_name : "context_part");
|
||||
}
|
||||
|
||||
static void append_context_log(const char* sender_pubkey_hex, const char* phase, const char* context_payload) {
|
||||
(void)sender_pubkey_hex;
|
||||
|
||||
if (!phase || g_context_debug_mode == CONTEXT_DEBUG_OFF) {
|
||||
return;
|
||||
}
|
||||
|
||||
int is_initial_phase =
|
||||
(strcmp(phase, "llm_chat_with_tools_messages") == 0 ||
|
||||
strcmp(phase, "llm_trigger") == 0);
|
||||
int is_turn_phase =
|
||||
(strcmp(phase, "llm_chat_with_tools_turn") == 0 ||
|
||||
strcmp(phase, "llm_trigger_with_tools_turn") == 0);
|
||||
|
||||
if (!is_initial_phase && !is_turn_phase) {
|
||||
return;
|
||||
}
|
||||
if (g_context_debug_mode == CONTEXT_DEBUG_INIT && !is_initial_phase) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* safe_payload = context_payload ? context_payload : "";
|
||||
char* turn_markdown = NULL;
|
||||
if (is_turn_phase && context_payload && context_payload[0] == '[') {
|
||||
turn_markdown = render_messages_json_as_markdown(context_payload);
|
||||
if (turn_markdown) {
|
||||
safe_payload = turn_markdown;
|
||||
}
|
||||
}
|
||||
|
||||
int entry_len = snprintf(NULL, 0, "%s\n\n", safe_payload);
|
||||
if (entry_len < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
char* entry = (char*)malloc((size_t)entry_len + 1U);
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
snprintf(entry, (size_t)entry_len + 1U, "%s\n\n", safe_payload);
|
||||
|
||||
char* old_data = NULL;
|
||||
size_t old_len = 0;
|
||||
|
||||
FILE* in = fopen("context.log.md", "rb");
|
||||
if (in) {
|
||||
if (fseek(in, 0, SEEK_END) == 0) {
|
||||
long sz = ftell(in);
|
||||
if (sz > 0 && fseek(in, 0, SEEK_SET) == 0) {
|
||||
old_len = (size_t)sz;
|
||||
old_data = (char*)malloc(old_len);
|
||||
if (old_data) {
|
||||
size_t n = fread(old_data, 1, old_len, in);
|
||||
if (n != old_len) {
|
||||
free(old_data);
|
||||
old_data = NULL;
|
||||
old_len = 0;
|
||||
}
|
||||
} else {
|
||||
old_len = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(in);
|
||||
}
|
||||
|
||||
FILE* out = fopen("context.log.md", "wb");
|
||||
if (!out) {
|
||||
free(old_data);
|
||||
free(entry);
|
||||
return;
|
||||
}
|
||||
|
||||
(void)fwrite(entry, 1, (size_t)entry_len, out);
|
||||
if (old_data && old_len > 0) {
|
||||
(void)fwrite(old_data, 1, old_len, out);
|
||||
}
|
||||
|
||||
fclose(out);
|
||||
free(old_data);
|
||||
|
||||
if (mkdir("context.logs", 0755) != 0 && errno != EEXIST) {
|
||||
free(entry);
|
||||
return;
|
||||
}
|
||||
|
||||
time_t now = time(NULL);
|
||||
struct tm tm_info;
|
||||
localtime_r(&now, &tm_info);
|
||||
|
||||
if (g_context_debug_run_stamp[0] == '\0') {
|
||||
strftime(g_context_debug_run_stamp, sizeof(g_context_debug_run_stamp), "%Y%m%dT%H%M%S", &tm_info);
|
||||
g_context_debug_turn = 0;
|
||||
}
|
||||
|
||||
char snapshot_path[256] = {0};
|
||||
if (is_initial_phase) {
|
||||
snprintf(snapshot_path,
|
||||
sizeof(snapshot_path),
|
||||
"context.logs/%s_init.md",
|
||||
g_context_debug_run_stamp);
|
||||
} else {
|
||||
g_context_debug_turn++;
|
||||
snprintf(snapshot_path,
|
||||
sizeof(snapshot_path),
|
||||
"context.logs/%s_t%03d.md",
|
||||
g_context_debug_run_stamp,
|
||||
g_context_debug_turn);
|
||||
}
|
||||
|
||||
FILE* snapshot = fopen(snapshot_path, "wb");
|
||||
if (snapshot) {
|
||||
(void)fwrite(safe_payload, 1, strlen(safe_payload), snapshot);
|
||||
fclose(snapshot);
|
||||
}
|
||||
|
||||
free(entry);
|
||||
free(turn_markdown);
|
||||
}
|
||||
|
||||
int agent_set_context_debug_mode(const char* mode) {
|
||||
if (!mode || mode[0] == '\0' || strcmp(mode, "off") == 0) {
|
||||
g_context_debug_mode = CONTEXT_DEBUG_OFF;
|
||||
} else if (strcmp(mode, "init") == 0) {
|
||||
g_context_debug_mode = CONTEXT_DEBUG_INIT;
|
||||
} else if (strcmp(mode, "full") == 0) {
|
||||
g_context_debug_mode = CONTEXT_DEBUG_FULL;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_context_debug_run_stamp[0] = '\0';
|
||||
g_context_debug_turn = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void agent_append_context_log(const char* sender_pubkey_hex, const char* phase, const char* context_payload) {
|
||||
append_context_log(sender_pubkey_hex, phase, context_payload);
|
||||
}
|
||||
|
||||
|
||||
static __attribute__((unused)) char* build_sender_verification_text(didactyl_sender_tier_t sender_tier) {
|
||||
if (sender_tier == DIDACTYL_SENDER_ADMIN) {
|
||||
return strdup("This message has been cryptographically verified as coming from your administrator.");
|
||||
@@ -1579,6 +1305,12 @@ static void clear_adopted_skills_cache_locked(void) {
|
||||
g_adopted_skills_count = 0;
|
||||
}
|
||||
|
||||
void agent_invalidate_adopted_skills_cache(void) {
|
||||
pthread_mutex_lock(&g_adopted_skills_mutex);
|
||||
g_adopted_skills_last_refresh_at = 0;
|
||||
pthread_mutex_unlock(&g_adopted_skills_mutex);
|
||||
}
|
||||
|
||||
static int refresh_adopted_skills_cache_if_needed(void) {
|
||||
if (!g_cfg) {
|
||||
return -1;
|
||||
@@ -2047,7 +1779,6 @@ void agent_on_trigger(const char* skill_d_tag,
|
||||
return;
|
||||
}
|
||||
|
||||
append_context_log(g_cfg->admin.pubkey, "llm_trigger", full_markdown);
|
||||
context_roles_free(&roles);
|
||||
free(full_markdown);
|
||||
|
||||
@@ -2064,8 +1795,6 @@ void agent_on_trigger(const char* skill_d_tag,
|
||||
break;
|
||||
}
|
||||
|
||||
append_context_log(g_cfg->admin.pubkey, "llm_trigger_with_tools_turn", messages_json);
|
||||
|
||||
llm_response_t resp;
|
||||
int rc = llm_chat_with_tools_messages(messages_json, tools_json, "auto", &resp);
|
||||
free(messages_json);
|
||||
@@ -2165,7 +1894,10 @@ int agent_build_admin_messages_json(const char* current_user_message,
|
||||
(sender_tier == DIDACTYL_SENDER_WOT ? "wot" : "stranger"));
|
||||
cJSON_AddNumberToObject(dm_event, "created_at", (double)time(NULL));
|
||||
|
||||
const char* prev_current_user_message = g_tools_ctx.template_current_user_message;
|
||||
g_tools_ctx.template_current_user_message = current_user_message ? current_user_message : "";
|
||||
char* composed_context = build_context_from_triggers(TRIGGER_TYPE_DM, NULL, dm_event, "api");
|
||||
g_tools_ctx.template_current_user_message = prev_current_user_message;
|
||||
cJSON_Delete(dm_event);
|
||||
if (!composed_context) {
|
||||
composed_context = strdup("You are an AI agent. Respond to the message.");
|
||||
@@ -2233,7 +1965,6 @@ void agent_on_message(const char* sender_pubkey_hex,
|
||||
if (message[0] == '/') {
|
||||
if (!allow_tools) {
|
||||
const char* denied = "{\"success\":false,\"error\":\"slash commands are disabled for this sender tier\"}";
|
||||
append_context_log(sender_pubkey_hex, "direct_tool_exec", denied);
|
||||
(void)nostr_handler_send_dm_auto_with_role(sender_pubkey_hex,
|
||||
denied,
|
||||
DIDACTYL_DM_HISTORY_TOOL_RESPONSE);
|
||||
@@ -2256,24 +1987,15 @@ void agent_on_message(const char* sender_pubkey_hex,
|
||||
(tier == DIDACTYL_SENDER_WOT ? "wot" : "stranger"));
|
||||
cJSON_AddNumberToObject(dm_event, "created_at", (double)time(NULL));
|
||||
|
||||
const char* prev_current_user_message = g_tools_ctx.template_current_user_message;
|
||||
g_tools_ctx.template_current_user_message = message ? message : "";
|
||||
char* dm_context = build_context_from_triggers(TRIGGER_TYPE_DM, NULL, dm_event, "dm");
|
||||
g_tools_ctx.template_current_user_message = prev_current_user_message;
|
||||
cJSON_Delete(dm_event);
|
||||
if (!dm_context) {
|
||||
dm_context = strdup("You are an AI agent. Respond to the message.");
|
||||
}
|
||||
|
||||
size_t full_len = strlen(dm_context) + strlen("\n\nuser:\n") + strlen(message) + 1U;
|
||||
char* full_markdown = (char*)malloc(full_len);
|
||||
if (!full_markdown) {
|
||||
free(dm_context);
|
||||
return;
|
||||
}
|
||||
snprintf(full_markdown, full_len, "%s\n\nuser:\n%s", dm_context, message);
|
||||
free(dm_context);
|
||||
|
||||
context_roles_t roles;
|
||||
if (context_roles_split(full_markdown, &roles) != 0) {
|
||||
free(full_markdown);
|
||||
if (!dm_context) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2282,22 +2004,18 @@ void agent_on_message(const char* sender_pubkey_hex,
|
||||
? "You are responding to a web-of-trust contact. Keep the response helpful and concise. Tool use is disabled for this tier."
|
||||
: "You are responding in chat-only mode. Tool use is disabled.";
|
||||
|
||||
size_t ctx_len = strlen(roles.system_content ? roles.system_content : "") + strlen("\n\n") + strlen(tier_prefix) + 1U;
|
||||
size_t ctx_len = strlen(dm_context) + strlen("\n\n") + strlen(tier_prefix) + 1U;
|
||||
char* system_for_chat = (char*)malloc(ctx_len);
|
||||
if (!system_for_chat) {
|
||||
context_roles_free(&roles);
|
||||
free(full_markdown);
|
||||
free(dm_context);
|
||||
return;
|
||||
}
|
||||
|
||||
snprintf(system_for_chat, ctx_len, "%s\n\n%s", roles.system_content ? roles.system_content : "", tier_prefix);
|
||||
snprintf(system_for_chat, ctx_len, "%s\n\n%s", dm_context, tier_prefix);
|
||||
|
||||
append_context_log(sender_pubkey_hex, "llm_chat", full_markdown);
|
||||
|
||||
char* response = llm_chat(system_for_chat, roles.user_content ? roles.user_content : message);
|
||||
char* response = llm_chat(system_for_chat, message);
|
||||
free(system_for_chat);
|
||||
context_roles_free(&roles);
|
||||
free(full_markdown);
|
||||
free(dm_context);
|
||||
|
||||
if (!response) {
|
||||
const char* fallback = "I could not get a response from the LLM right now.";
|
||||
@@ -2316,20 +2034,18 @@ void agent_on_message(const char* sender_pubkey_hex,
|
||||
|
||||
char* tools_json = tools_build_openai_schema_json(&g_tools_ctx);
|
||||
if (!tools_json) {
|
||||
context_roles_free(&roles);
|
||||
free(full_markdown);
|
||||
free(dm_context);
|
||||
(void)nostr_handler_send_dm_auto(sender_pubkey_hex, "Tool schema generation failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
cJSON* messages = cJSON_CreateArray();
|
||||
if (!messages ||
|
||||
append_simple_message(messages, "system", roles.system_content ? roles.system_content : "You are an AI agent. Respond to the message.") != 0 ||
|
||||
append_simple_message(messages, "user", roles.user_content ? roles.user_content : message) != 0) {
|
||||
append_simple_message(messages, "system", dm_context) != 0 ||
|
||||
append_simple_message(messages, "user", message) != 0) {
|
||||
cJSON_Delete(messages);
|
||||
free(tools_json);
|
||||
context_roles_free(&roles);
|
||||
free(full_markdown);
|
||||
free(dm_context);
|
||||
(void)nostr_handler_send_dm_auto(sender_pubkey_hex, "Failed to initialize conversation messages.");
|
||||
return;
|
||||
}
|
||||
@@ -2348,8 +2064,6 @@ void agent_on_message(const char* sender_pubkey_hex,
|
||||
char* final_answer_owned = NULL;
|
||||
int turns_run = 0;
|
||||
|
||||
append_context_log(sender_pubkey_hex, "llm_chat_with_tools_messages", full_markdown);
|
||||
|
||||
for (int turn = 0; turn < max_turns; turn++) {
|
||||
turns_run = turn + 1;
|
||||
char* messages_json = cJSON_PrintUnformatted(messages);
|
||||
@@ -2357,8 +2071,6 @@ void agent_on_message(const char* sender_pubkey_hex,
|
||||
break;
|
||||
}
|
||||
|
||||
append_context_log(sender_pubkey_hex, "llm_chat_with_tools_turn", messages_json);
|
||||
|
||||
llm_response_t resp;
|
||||
int rc = llm_chat_with_tools_messages(messages_json, tools_json, "auto", &resp);
|
||||
free(messages_json);
|
||||
@@ -2366,8 +2078,7 @@ void agent_on_message(const char* sender_pubkey_hex,
|
||||
(void)nostr_handler_send_dm_auto(sender_pubkey_hex, "LLM request failed.");
|
||||
cJSON_Delete(messages);
|
||||
free(tools_json);
|
||||
context_roles_free(&roles);
|
||||
free(full_markdown);
|
||||
free(dm_context);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2420,8 +2131,7 @@ void agent_on_message(const char* sender_pubkey_hex,
|
||||
llm_response_free(&resp);
|
||||
cJSON_Delete(messages);
|
||||
free(tools_json);
|
||||
context_roles_free(&roles);
|
||||
free(full_markdown);
|
||||
free(dm_context);
|
||||
(void)nostr_handler_send_dm_auto(sender_pubkey_hex, "Failed to append tool result.");
|
||||
return;
|
||||
}
|
||||
@@ -2478,8 +2188,7 @@ void agent_on_message(const char* sender_pubkey_hex,
|
||||
free(final_answer_owned);
|
||||
cJSON_Delete(messages);
|
||||
free(tools_json);
|
||||
context_roles_free(&roles);
|
||||
free(full_markdown);
|
||||
free(dm_context);
|
||||
}
|
||||
|
||||
void agent_cleanup(void) {
|
||||
|
||||
@@ -23,8 +23,7 @@ int agent_build_admin_messages_json(const char* current_user_message,
|
||||
char** out_messages_json);
|
||||
tools_context_t* agent_tools_context(void);
|
||||
const char* agent_classify_message_part(cJSON* msg, int idx);
|
||||
int agent_set_context_debug_mode(const char* mode);
|
||||
void agent_append_context_log(const char* sender_pubkey_hex, const char* phase, const char* context_payload);
|
||||
void agent_invalidate_adopted_skills_cache(void);
|
||||
void agent_cleanup(void);
|
||||
|
||||
#endif
|
||||
@@ -882,18 +882,6 @@ static char* execute_slash_command_http(const char* message) {
|
||||
}
|
||||
|
||||
char* markdown_result = format_result_markdown_http(result_json);
|
||||
const char* dm_payload = markdown_result ? markdown_result : result_json;
|
||||
|
||||
size_t log_cap = strlen(message) + strlen(result_json ? result_json : "") + strlen(dm_payload ? dm_payload : "") + 192U;
|
||||
char* log_payload = (char*)malloc(log_cap);
|
||||
if (log_payload) {
|
||||
snprintf(log_payload, log_cap, "slash=%s\nresult_json=%s\nresult_markdown=%s",
|
||||
message,
|
||||
result_json ? result_json : "",
|
||||
dm_payload ? dm_payload : "");
|
||||
agent_append_context_log("http_api_agent", "direct_tool_exec", log_payload);
|
||||
free(log_payload);
|
||||
}
|
||||
|
||||
free(result_json);
|
||||
return markdown_result ? markdown_result : strdup("Command executed with no output.");
|
||||
@@ -902,7 +890,6 @@ static char* execute_slash_command_http(const char* message) {
|
||||
static cJSON* run_prompt_with_tools_convo(cJSON* convo,
|
||||
int max_turns,
|
||||
const char* log_sender,
|
||||
const char* log_phase,
|
||||
const char* tool_limit_message) {
|
||||
if (!convo || !cJSON_IsArray(convo)) return NULL;
|
||||
|
||||
@@ -945,10 +932,6 @@ static cJSON* run_prompt_with_tools_convo(cJSON* convo,
|
||||
char* messages_json = cJSON_PrintUnformatted(convo);
|
||||
if (!messages_json) break;
|
||||
|
||||
agent_append_context_log(log_sender ? log_sender : "http_api",
|
||||
log_phase ? log_phase : "llm_chat_with_tools_messages_http_api",
|
||||
messages_json);
|
||||
|
||||
llm_response_t resp;
|
||||
int rc = llm_chat_with_tools_messages(messages_json, tools_json, "auto", &resp);
|
||||
free(messages_json);
|
||||
@@ -1115,7 +1098,6 @@ static cJSON* run_prompt_with_tools(cJSON* body) {
|
||||
cJSON* root = run_prompt_with_tools_convo(convo,
|
||||
max_turns,
|
||||
"http_api",
|
||||
"llm_chat_with_tools_messages_http_api",
|
||||
"I hit my tool-use limit for this prompt run.");
|
||||
cJSON_Delete(convo);
|
||||
return root;
|
||||
@@ -1379,7 +1361,6 @@ static void handle_prompt_agent(struct mg_connection* c, const struct mg_http_me
|
||||
result = run_prompt_with_tools_convo(convo,
|
||||
max_turns,
|
||||
"http_api_agent",
|
||||
"llm_chat_with_tools_messages_agent_api",
|
||||
"I hit my tool-use limit for this request.");
|
||||
cJSON_Delete(convo);
|
||||
|
||||
|
||||
40
src/llm.c
40
src/llm.c
@@ -7,6 +7,10 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "cjson/cJSON.h"
|
||||
#include "debug.h"
|
||||
@@ -15,6 +19,38 @@
|
||||
|
||||
static llm_config_t g_cfg;
|
||||
static int g_initialized = 0;
|
||||
static pthread_mutex_t g_llm_request_log_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static unsigned long g_llm_request_log_seq = 0;
|
||||
|
||||
static void log_provider_request_body_local(const char* body) {
|
||||
if (!body || body[0] == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mkdir("context.logs", 0755) != 0 && errno != EEXIST) {
|
||||
return;
|
||||
}
|
||||
|
||||
char stamp[32] = {0};
|
||||
time_t now = time(NULL);
|
||||
struct tm tm_info;
|
||||
localtime_r(&now, &tm_info);
|
||||
strftime(stamp, sizeof(stamp), "%Y%m%dT%H%M%S", &tm_info);
|
||||
|
||||
pthread_mutex_lock(&g_llm_request_log_mutex);
|
||||
unsigned long seq = ++g_llm_request_log_seq;
|
||||
pthread_mutex_unlock(&g_llm_request_log_mutex);
|
||||
|
||||
char path[256] = {0};
|
||||
snprintf(path, sizeof(path), "context.logs/%s_req%04lu.json", stamp, seq);
|
||||
|
||||
FILE* out = fopen(path, "wb");
|
||||
if (!out) {
|
||||
return;
|
||||
}
|
||||
(void)fwrite(body, 1, strlen(body), out);
|
||||
fclose(out);
|
||||
}
|
||||
|
||||
static int url_looks_like_websocket(const char* url) {
|
||||
if (!url) return 0;
|
||||
@@ -82,6 +118,10 @@ static char* perform_http_request(const char* url, const char* body, int is_post
|
||||
DEBUG_INFO("[didactyl] llm request: method=GET url=%s", url);
|
||||
}
|
||||
|
||||
if (is_post && body) {
|
||||
log_provider_request_body_local(body);
|
||||
}
|
||||
|
||||
nostr_http_response_t resp;
|
||||
int rc = nostr_http_request(&req, &resp);
|
||||
if (rc != NOSTR_SUCCESS) {
|
||||
|
||||
24
src/main.c
24
src/main.c
@@ -107,8 +107,6 @@ static void print_usage(const char* prog) {
|
||||
" Enable HTTP API and bind address (example: 127.0.0.1).\n"
|
||||
" --debug <0-5>\n"
|
||||
" Log level (0=fatal, 1=error, 2=warn, 3=info, 4=debug, 5=trace).\n"
|
||||
" --context-debug <off|init|full>\n"
|
||||
" Context snapshot mode (off=none, init=initial only, full=initial+every turn).\n"
|
||||
" --dump-schemas\n"
|
||||
" Print tool schemas JSON and exit.\n"
|
||||
" --test-tool <name> <args_json>\n"
|
||||
@@ -117,8 +115,6 @@ static void print_usage(const char* prog) {
|
||||
"Environment:\n"
|
||||
" DIDACTYL_NSEC\n"
|
||||
" Fallback private key when --nsec is not provided.\n"
|
||||
" DIDACTYL_CONTEXT_DEBUG\n"
|
||||
" Context snapshot mode override: off, init, or full.\n"
|
||||
"\n"
|
||||
"Examples:\n"
|
||||
" 1) Genesis-based startup\n"
|
||||
@@ -914,8 +910,6 @@ int main(int argc, char** argv) {
|
||||
int dump_schemas = 0;
|
||||
const char* test_tool_name = NULL;
|
||||
const char* test_tool_args = "{}";
|
||||
const char* context_debug_mode = "off";
|
||||
|
||||
didactyl_config_t cfg;
|
||||
memset(&cfg, 0, sizeof(cfg));
|
||||
int config_preloaded = 0;
|
||||
@@ -979,8 +973,6 @@ int main(int argc, char** argv) {
|
||||
api_port_override = atoi(argv[++i]);
|
||||
} else if (strcmp(argv[i], "--api-bind") == 0 && i + 1 < argc) {
|
||||
api_bind_override = argv[++i];
|
||||
} else if (strcmp(argv[i], "--context-debug") == 0 && i + 1 < argc) {
|
||||
context_debug_mode = argv[++i];
|
||||
} else if (strcmp(argv[i], "--dump-schemas") == 0) {
|
||||
dump_schemas = 1;
|
||||
} else if (strcmp(argv[i], "--test-tool") == 0 && i + 2 < argc) {
|
||||
@@ -1043,22 +1035,6 @@ int main(int argc, char** argv) {
|
||||
DEBUG_INFO("[didactyl] startup phase: admin override applied from --admin (%.16s...)", cfg.admin.pubkey);
|
||||
}
|
||||
|
||||
{
|
||||
const char* env_context_debug = getenv("DIDACTYL_CONTEXT_DEBUG");
|
||||
if (env_context_debug && env_context_debug[0] != '\0' &&
|
||||
strcmp(context_debug_mode, "off") == 0) {
|
||||
context_debug_mode = env_context_debug;
|
||||
}
|
||||
|
||||
if (agent_set_context_debug_mode(context_debug_mode) != 0) {
|
||||
fprintf(stderr, "Invalid context debug mode '%s' (expected off, init, or full)\n", context_debug_mode);
|
||||
config_free(&cfg);
|
||||
nostr_cleanup();
|
||||
return 1;
|
||||
}
|
||||
DEBUG_INFO("[didactyl] context debug mode: %s", context_debug_mode);
|
||||
}
|
||||
|
||||
if (config_ensure_startup_skill_adoption(&cfg) != 0) {
|
||||
fprintf(stderr, "Failed to synthesize startup skill adoption events\n");
|
||||
config_free(&cfg);
|
||||
|
||||
@@ -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 2
|
||||
#define DIDACTYL_VERSION_PATCH 25
|
||||
#define DIDACTYL_VERSION "v0.2.25"
|
||||
#define DIDACTYL_VERSION_PATCH 27
|
||||
#define DIDACTYL_VERSION "v0.2.27"
|
||||
|
||||
// Agent metadata
|
||||
#define DIDACTYL_NAME "Didactyl"
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "trigger_manager.h"
|
||||
#include "nostr_block_list.h"
|
||||
#include "cashu_wallet.h"
|
||||
#include "agent.h"
|
||||
|
||||
#define NIP17_MAX_RELAYS 32
|
||||
#define NIP17_MAX_GIFT_WRAPS 8
|
||||
@@ -2445,6 +2446,8 @@ static void on_self_skill_event(cJSON* event, const char* relay_url, void* user_
|
||||
pthread_mutex_lock(&g_self_skill_mutex);
|
||||
self_skill_cache_upsert_event_locked(event);
|
||||
pthread_mutex_unlock(&g_self_skill_mutex);
|
||||
|
||||
agent_invalidate_adopted_skills_cache();
|
||||
}
|
||||
|
||||
static void on_wallet_event(cJSON* event, const char* relay_url, void* user_data) {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "../debug.h"
|
||||
#include "../nostr_handler.h"
|
||||
#include "../trigger_manager.h"
|
||||
#include "../agent.h"
|
||||
|
||||
static char* json_error_local(const char* msg) {
|
||||
cJSON* root = cJSON_CreateObject();
|
||||
@@ -1769,6 +1770,21 @@ char* execute_skill_edit(tools_context_t* ctx, const char* args_json) {
|
||||
return json;
|
||||
}
|
||||
|
||||
char* execute_skill_refresh(tools_context_t* ctx, const char* args_json) {
|
||||
(void)args_json;
|
||||
if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable");
|
||||
|
||||
agent_invalidate_adopted_skills_cache();
|
||||
|
||||
cJSON* out = cJSON_CreateObject();
|
||||
if (!out) return NULL;
|
||||
cJSON_AddBoolToObject(out, "success", 1);
|
||||
cJSON_AddStringToObject(out, "message", "adopted skills cache invalidated; next context build will reload skills");
|
||||
char* json = cJSON_PrintUnformatted(out);
|
||||
cJSON_Delete(out);
|
||||
return json;
|
||||
}
|
||||
|
||||
char* execute_skill_search(const char* args_json) {
|
||||
cJSON* args = parse_args_local(args_json);
|
||||
if (!args) return json_error_local("invalid arguments JSON");
|
||||
|
||||
@@ -152,6 +152,9 @@ char* tools_execute_legacy(tools_context_t* ctx, const char* tool_name, const ch
|
||||
if (strcmp(tool_name, "skill_edit") == 0) {
|
||||
return execute_skill_edit(ctx, args_json);
|
||||
}
|
||||
if (strcmp(tool_name, "skill_refresh") == 0) {
|
||||
return execute_skill_refresh(ctx, args_json);
|
||||
}
|
||||
if (strcmp(tool_name, "trigger_list") == 0) {
|
||||
return execute_trigger_list(ctx, args_json);
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ char* execute_skill_set(tools_context_t* ctx, const char* args_json);
|
||||
char* execute_skill_adopt(tools_context_t* ctx, const char* args_json);
|
||||
char* execute_skill_remove(tools_context_t* ctx, const char* args_json);
|
||||
char* execute_skill_edit(tools_context_t* ctx, const char* args_json);
|
||||
char* execute_skill_refresh(tools_context_t* ctx, const char* args_json);
|
||||
char* execute_skill_search(const char* args_json);
|
||||
|
||||
char* execute_memory_save(tools_context_t* ctx, const char* args_json);
|
||||
|
||||
@@ -1077,6 +1077,21 @@ char* tools_build_openai_schema_json_legacy(const tools_context_t* ctx) {
|
||||
cJSON_AddItemToObject(t25b, "function", t25b_fn);
|
||||
cJSON_AddItemToArray(tools, t25b);
|
||||
|
||||
cJSON* t25c = cJSON_CreateObject();
|
||||
cJSON* t25c_fn = cJSON_CreateObject();
|
||||
cJSON* t25c_params = cJSON_CreateObject();
|
||||
cJSON* t25c_props = cJSON_CreateObject();
|
||||
|
||||
cJSON_AddStringToObject(t25c, "type", "function");
|
||||
cJSON_AddStringToObject(t25c_fn, "name", "skill_refresh");
|
||||
cJSON_AddStringToObject(t25c_fn, "description", "Invalidate adopted skills cache so next context build reloads latest skill events from cache/relays");
|
||||
cJSON_AddStringToObject(t25c_params, "type", "object");
|
||||
cJSON_AddItemToObject(t25c_params, "properties", t25c_props);
|
||||
|
||||
cJSON_AddItemToObject(t25c_fn, "parameters", t25c_params);
|
||||
cJSON_AddItemToObject(t25c, "function", t25c_fn);
|
||||
cJSON_AddItemToArray(tools, t25c);
|
||||
|
||||
cJSON* t26 = cJSON_CreateObject();
|
||||
cJSON* t26_fn = cJSON_CreateObject();
|
||||
cJSON* t26_params = cJSON_CreateObject();
|
||||
|
||||
Reference in New Issue
Block a user