v0.2.2 - Re-apply v0.2.0 migration after history rewrite rollback
This commit is contained in:
+39
-86
@@ -15,18 +15,14 @@
|
||||
#include "nostr_handler.h"
|
||||
#include "trigger_manager.h"
|
||||
#include "tools/tools.h"
|
||||
#include "prompt_template.h"
|
||||
#include "cjson/cJSON.h"
|
||||
#include "debug.h"
|
||||
#include "../../nostr_core_lib/nostr_core/nostr_core.h"
|
||||
|
||||
static didactyl_config_t* g_cfg = NULL;
|
||||
static char* g_system_context = NULL;
|
||||
static tools_context_t g_tools_ctx;
|
||||
static struct trigger_manager* g_trigger_manager = NULL;
|
||||
|
||||
static prompt_template_t g_prompt_template;
|
||||
static int g_has_prompt_template = 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;
|
||||
@@ -2094,20 +2090,14 @@ static cJSON* build_recent_admin_dm_history_messages(const char* current_message
|
||||
return messages;
|
||||
}
|
||||
|
||||
int agent_init(didactyl_config_t* config, const char* system_context) {
|
||||
if (!config || !system_context) {
|
||||
int agent_init(didactyl_config_t* config) {
|
||||
if (!config) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_cfg = config;
|
||||
g_system_context = strdup(system_context);
|
||||
if (!g_system_context) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (tools_init(&g_tools_ctx, g_cfg) != 0) {
|
||||
free(g_system_context);
|
||||
g_system_context = NULL;
|
||||
g_cfg = NULL;
|
||||
return -1;
|
||||
}
|
||||
@@ -2124,8 +2114,6 @@ int agent_init(didactyl_config_t* config, const char* system_context) {
|
||||
g_tools_ctx.template_skill_lookup = template_skill_lookup_callback;
|
||||
g_tools_ctx.template_skill_lookup_user_data = NULL;
|
||||
|
||||
prompt_template_free(&g_prompt_template);
|
||||
g_has_prompt_template = (prompt_template_parse(g_system_context, &g_prompt_template) == 0);
|
||||
clear_context_part_names();
|
||||
|
||||
return 0;
|
||||
@@ -2322,82 +2310,51 @@ void agent_on_trigger(const char* skill_d_tag,
|
||||
int agent_build_admin_messages_json(const char* current_user_message,
|
||||
didactyl_sender_tier_t sender_tier,
|
||||
char** out_messages_json) {
|
||||
if (!out_messages_json || !g_cfg || !g_system_context) {
|
||||
if (!out_messages_json || !g_cfg) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
*out_messages_json = NULL;
|
||||
clear_context_part_names();
|
||||
|
||||
cJSON* messages = NULL;
|
||||
cJSON* dm_event = cJSON_CreateObject();
|
||||
if (!dm_event) {
|
||||
return -1;
|
||||
}
|
||||
cJSON_AddStringToObject(dm_event, "type", "dm");
|
||||
cJSON_AddStringToObject(dm_event, "sender_pubkey", g_cfg->admin.pubkey);
|
||||
cJSON_AddStringToObject(dm_event, "message", current_user_message ? current_user_message : "");
|
||||
cJSON_AddStringToObject(dm_event,
|
||||
"tier",
|
||||
sender_tier == DIDACTYL_SENDER_ADMIN ? "admin" :
|
||||
(sender_tier == DIDACTYL_SENDER_WOT ? "wot" : "stranger"));
|
||||
cJSON_AddNumberToObject(dm_event, "created_at", (double)time(NULL));
|
||||
|
||||
if (g_has_prompt_template) {
|
||||
cJSON* dm_history = build_recent_admin_dm_history_messages(current_user_message);
|
||||
if (!dm_history) {
|
||||
dm_history = cJSON_CreateArray();
|
||||
}
|
||||
char* composed_context = build_context_from_triggers(TRIGGER_TYPE_DM, NULL, dm_event, "api");
|
||||
cJSON_Delete(dm_event);
|
||||
if (!composed_context) {
|
||||
composed_context = strdup("You are an AI agent. Respond to the message.");
|
||||
}
|
||||
|
||||
llm_config_t cfg;
|
||||
memset(&cfg, 0, sizeof(cfg));
|
||||
const char* provider_name = NULL;
|
||||
if (llm_get_config(&cfg) == 0 && cfg.provider[0] != '\0') {
|
||||
provider_name = cfg.provider;
|
||||
}
|
||||
cJSON* messages = cJSON_CreateArray();
|
||||
if (!messages ||
|
||||
append_simple_message(messages, "system", composed_context ? composed_context : "You are an AI agent. Respond to the message.") != 0 ||
|
||||
append_recent_admin_dm_history(messages, current_user_message) != 0 ||
|
||||
append_simple_message(messages, "user", current_user_message ? current_user_message : "") != 0) {
|
||||
free(composed_context);
|
||||
cJSON_Delete(messages);
|
||||
return -1;
|
||||
}
|
||||
free(composed_context);
|
||||
|
||||
g_tools_ctx.template_sender_tier = (int)sender_tier;
|
||||
g_tools_ctx.template_current_user_message = current_user_message;
|
||||
g_tools_ctx.template_trigger_event_json = NULL;
|
||||
|
||||
messages = prompt_template_build_messages(&g_prompt_template,
|
||||
provider_name,
|
||||
&g_tools_ctx,
|
||||
dm_history,
|
||||
AGENT_HISTORY_TURNS,
|
||||
template_emit_hook,
|
||||
NULL);
|
||||
cJSON_Delete(dm_history);
|
||||
if (!messages) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
} else {
|
||||
messages = cJSON_CreateArray();
|
||||
if (!messages) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
char agent_identity[192];
|
||||
snprintf(agent_identity,
|
||||
sizeof(agent_identity),
|
||||
"Agent identity\nYour pubkey (hex): %s",
|
||||
g_cfg->keys.public_key_hex[0] != '\0' ? g_cfg->keys.public_key_hex : "unknown");
|
||||
|
||||
char* sender_verification = build_sender_verification_text(sender_tier);
|
||||
if (!sender_verification ||
|
||||
append_simple_message(messages, "system", g_system_context) != 0 ||
|
||||
append_simple_message(messages, "system", agent_identity) != 0 ||
|
||||
append_simple_message(messages, "system", sender_verification) != 0 ||
|
||||
append_adopted_skills_context(messages) != 0) {
|
||||
free(sender_verification);
|
||||
cJSON_Delete(messages);
|
||||
return -1;
|
||||
}
|
||||
free(sender_verification);
|
||||
|
||||
if (append_recent_admin_dm_history(messages, current_user_message) != 0) {
|
||||
cJSON_Delete(messages);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int n = cJSON_GetArraySize(messages);
|
||||
for (int i = 0; i < n; i++) {
|
||||
cJSON* msg = cJSON_GetArrayItem(messages, i);
|
||||
cJSON* role = msg ? cJSON_GetObjectItemCaseSensitive(msg, "role") : NULL;
|
||||
cJSON* content = msg ? cJSON_GetObjectItemCaseSensitive(msg, "content") : NULL;
|
||||
const char* role_s = (role && cJSON_IsString(role) && role->valuestring) ? role->valuestring : "";
|
||||
const char* content_s = (content && cJSON_IsString(content) && content->valuestring) ? content->valuestring : "";
|
||||
set_context_part_name(i, detect_context_section(role_s, content_s, i));
|
||||
}
|
||||
int n = cJSON_GetArraySize(messages);
|
||||
for (int i = 0; i < n; i++) {
|
||||
cJSON* msg = cJSON_GetArrayItem(messages, i);
|
||||
cJSON* role = msg ? cJSON_GetObjectItemCaseSensitive(msg, "role") : NULL;
|
||||
cJSON* content = msg ? cJSON_GetObjectItemCaseSensitive(msg, "content") : NULL;
|
||||
const char* role_s = (role && cJSON_IsString(role) && role->valuestring) ? role->valuestring : "";
|
||||
const char* content_s = (content && cJSON_IsString(content) && content->valuestring) ? content->valuestring : "";
|
||||
set_context_part_name(i, detect_context_section(role_s, content_s, i));
|
||||
}
|
||||
|
||||
char* messages_json = cJSON_PrintUnformatted(messages);
|
||||
@@ -2420,7 +2377,7 @@ void agent_on_message(const char* sender_pubkey_hex,
|
||||
void* user_data) {
|
||||
(void)user_data;
|
||||
|
||||
if (!g_cfg || !g_system_context || !sender_pubkey_hex || !message) {
|
||||
if (!g_cfg || !sender_pubkey_hex || !message) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2670,8 +2627,6 @@ void agent_on_message(const char* sender_pubkey_hex,
|
||||
|
||||
void agent_cleanup(void) {
|
||||
tools_cleanup(&g_tools_ctx);
|
||||
free(g_system_context);
|
||||
g_system_context = NULL;
|
||||
g_cfg = NULL;
|
||||
g_trigger_manager = NULL;
|
||||
memset(g_seen_msgs, 0, sizeof(g_seen_msgs));
|
||||
@@ -2684,6 +2639,4 @@ void agent_cleanup(void) {
|
||||
pthread_mutex_unlock(&g_adopted_skills_mutex);
|
||||
|
||||
clear_context_part_names();
|
||||
prompt_template_free(&g_prompt_template);
|
||||
g_has_prompt_template = 0;
|
||||
}
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
struct trigger_manager;
|
||||
|
||||
int agent_init(didactyl_config_t* config, const char* system_context);
|
||||
int agent_init(didactyl_config_t* config);
|
||||
void agent_set_trigger_manager(struct trigger_manager* trigger_manager);
|
||||
void agent_on_trigger(const char* skill_d_tag,
|
||||
const char* skill_content,
|
||||
|
||||
+16
-80
@@ -1,9 +1,9 @@
|
||||
#ifndef DIDACTYL_DEFAULT_EVENTS_H
|
||||
#define DIDACTYL_DEFAULT_EVENTS_H
|
||||
|
||||
#define DIDACTYL_DEFAULT_SKILL_D_TAG "didactyl-default"
|
||||
#define DIDACTYL_DEFAULT_SKILL_D_TAG "default_admin_dm"
|
||||
#define DIDACTYL_DEFAULT_SKILL_KIND 31124
|
||||
#define DIDACTYL_DEFAULT_SKILL_TAGS_JSON "[[\"d\",\"didactyl-default\"],[\"app\",\"didactyl\"],[\"scope\",\"private\"]]"
|
||||
#define DIDACTYL_DEFAULT_SKILL_TAGS_JSON "[[\"d\",\"default_admin_dm\"],[\"app\",\"didactyl\"],[\"scope\",\"private\"],[\"description\",\"Default admin DM handler\"],[\"trigger\",\"dm\"],[\"filter\",\"{\\\"from\\\":\\\"admin\\\"}\"]]"
|
||||
|
||||
#define DIDACTYL_STARTUP_KIND_PROFILE 0
|
||||
#define DIDACTYL_STARTUP_KIND_CONTACTS 3
|
||||
@@ -31,83 +31,19 @@ static const char* DIDACTYL_DEFAULT_RELAYS[] = {
|
||||
#define DIDACTYL_DEFAULT_RELAY_COUNT ((int)(sizeof(DIDACTYL_DEFAULT_RELAYS) / sizeof(DIDACTYL_DEFAULT_RELAYS[0])))
|
||||
|
||||
static const char* DIDACTYL_DEFAULT_SKILL_TEMPLATE =
|
||||
"# %s Agent\n\n"
|
||||
"You are %s, a sovereign AI agent living on Nostr.\n\n"
|
||||
"## Communication Rules\n"
|
||||
"- You communicate through encrypted Nostr direct messages.\n"
|
||||
"- Keep responses concise and clear.\n\n"
|
||||
"## Behavior\n"
|
||||
"- Be helpful and technically accurate.\n"
|
||||
"- If unsure, state uncertainty directly.\n"
|
||||
"- Prefer actionable, practical advice.\n"
|
||||
"- Use the person's name when messaging them if you know it.\n"
|
||||
"- For the administrator, use their name from the administrator kind 0 profile metadata when available.\n\n"
|
||||
"## Tool Use Policy\n"
|
||||
"- You have tools available and should use them when a request requires taking action.\n"
|
||||
"- For requests involving local inspection or command execution, call `local_shell_exec` instead of refusing.\n"
|
||||
"- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`.\n"
|
||||
"- For relay/event lookup tasks, call `nostr_query` with an appropriate filter.\n"
|
||||
"- After a tool call, base your answer on the actual tool result.\n"
|
||||
"- Never claim a tool was run if no tool was executed.\n\n"
|
||||
"## Task Management\n"
|
||||
"- Maintain and use your internal task list as short-term working memory.\n"
|
||||
"- Break long or complex actions into clear tasks before executing them.\n"
|
||||
"- Update task status as you complete steps so your plan stays accurate.\n\n"
|
||||
"## Safety\n"
|
||||
"- Do not claim to have executed actions you did not execute.\n"
|
||||
"- You may share your public key (npub) with anyone.\n"
|
||||
"- Never reveal your private key (nsec) under any circumstance.\n\n"
|
||||
"---template---\n\n"
|
||||
"- section: admin_identity\n"
|
||||
" role: system\n"
|
||||
" tool: admin_identity\n"
|
||||
" skip_if_empty: true\n\n"
|
||||
"- section: admin_profile\n"
|
||||
" role: system\n"
|
||||
" tool: nostr_admin_profile\n"
|
||||
" skip_if_empty: true\n\n"
|
||||
"- section: admin_contacts\n"
|
||||
" role: system\n"
|
||||
" tool: nostr_admin_contacts\n"
|
||||
" skip_if_empty: true\n\n"
|
||||
"- section: admin_relays\n"
|
||||
" role: system\n"
|
||||
" tool: nostr_admin_relays\n"
|
||||
" skip_if_empty: true\n\n"
|
||||
"- section: admin_notes\n"
|
||||
" role: system\n"
|
||||
" tool: nostr_admin_notes\n"
|
||||
" skip_if_empty: true\n\n"
|
||||
"- section: agent_identity\n"
|
||||
" role: system\n"
|
||||
" tool: agent_identity\n"
|
||||
" skip_if_empty: true\n\n"
|
||||
"- section: agent_profile\n"
|
||||
" role: system\n"
|
||||
" tool: nostr_agent_profile\n"
|
||||
" skip_if_empty: true\n\n"
|
||||
"- section: agent_contacts\n"
|
||||
" role: system\n"
|
||||
" tool: nostr_agent_contacts\n"
|
||||
" skip_if_empty: true\n\n"
|
||||
"- section: agent_relays\n"
|
||||
" role: system\n"
|
||||
" tool: nostr_agent_relays\n"
|
||||
" skip_if_empty: true\n\n"
|
||||
"- section: agent_notes\n"
|
||||
" role: system\n"
|
||||
" tool: nostr_agent_notes\n"
|
||||
" skip_if_empty: true\n\n"
|
||||
"- section: tasks\n"
|
||||
" role: system\n"
|
||||
" tool: task_list\n"
|
||||
" skip_if_empty: true\n\n"
|
||||
"- section: dm_history\n"
|
||||
" role: expand\n"
|
||||
" limit: 12\n\n"
|
||||
"- section: conversation\n"
|
||||
" role: user\n"
|
||||
" tool: message_current\n"
|
||||
" skip_if_empty: true\n";
|
||||
"# Didactyl Agent\n\n"
|
||||
"You are {{my_kind0_profile}}\n\n"
|
||||
"Your npub: {{my_npub}}\n\n"
|
||||
"## Rules\n\n"
|
||||
"- Communicate through encrypted Nostr direct messages\n"
|
||||
"- Keep responses concise and clear\n"
|
||||
"- Be helpful and technically accurate\n"
|
||||
"- If unsure, state uncertainty directly\n"
|
||||
"- Use tools when a request requires taking action\n"
|
||||
"- After a tool call, base your answer on the actual tool result\n"
|
||||
"- Never claim a tool was run if no tool was executed\n"
|
||||
"- Maintain your task list as short-term working memory\n"
|
||||
"- Never reveal your private key (nsec)\n"
|
||||
"- You may share your public key (npub) with anyone";
|
||||
|
||||
#endif
|
||||
+1
-15
@@ -1245,9 +1245,6 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
} else {
|
||||
DEBUG_INFO("[didactyl] startup phase: existing-agent mode; skipping startup-event reconcile on subsequent run");
|
||||
if (nostr_handler_load_system_context_from_adopted_skills() != 0) {
|
||||
DEBUG_WARN("[didactyl] startup phase: failed to load system context from adopted skills (continuing with fallback)");
|
||||
}
|
||||
}
|
||||
|
||||
if (persist_runtime_config_to_nostr(&cfg, llm_recalled_from_nostr ? 0 : 1) != 0) {
|
||||
@@ -1258,19 +1255,8 @@ int main(int argc, char** argv) {
|
||||
|
||||
startup_step_ok(7, "Reconcile/persist startup state", NULL);
|
||||
|
||||
const char* system_context = nostr_handler_get_system_context();
|
||||
char system_context_fallback[512] = {0};
|
||||
if (!system_context || system_context[0] == '\0') {
|
||||
const char* startup_name = nostr_handler_get_startup_display_name();
|
||||
snprintf(system_context_fallback,
|
||||
sizeof(system_context_fallback),
|
||||
"You are %s, a sovereign AI agent living on Nostr.",
|
||||
(startup_name && startup_name[0] != '\0') ? startup_name : "the agent");
|
||||
system_context = system_context_fallback;
|
||||
}
|
||||
|
||||
startup_step_begin(8, "Initialize agent");
|
||||
if (agent_init(&cfg, system_context) != 0) {
|
||||
if (agent_init(&cfg) != 0) {
|
||||
startup_step_fail(8, "Initialize agent", "agent_init failed");
|
||||
fprintf(stderr, "Failed to initialize agent\n");
|
||||
nostr_block_list_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 2
|
||||
#define DIDACTYL_VERSION_PATCH 1
|
||||
#define DIDACTYL_VERSION "v0.2.1"
|
||||
#define DIDACTYL_VERSION_PATCH 2
|
||||
#define DIDACTYL_VERSION "v0.2.2"
|
||||
|
||||
// Agent metadata
|
||||
#define DIDACTYL_NAME "Didactyl"
|
||||
|
||||
@@ -25,7 +25,6 @@ static void* g_dm_user_data = NULL;
|
||||
static int g_poll_counter = 0;
|
||||
static time_t g_start_time = 0;
|
||||
static nostr_pool_relay_status_t* g_last_relay_statuses = NULL;
|
||||
static char* g_system_context = NULL;
|
||||
static unsigned char* g_startup_published = NULL;
|
||||
static int g_startup_publish_tracking_enabled = 0;
|
||||
static int g_startup_kind1_already_exists = 0;
|
||||
@@ -3121,9 +3120,6 @@ int nostr_handler_reconcile_startup_events(void) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
free(g_system_context);
|
||||
g_system_context = NULL;
|
||||
|
||||
free(g_startup_published);
|
||||
g_startup_published = NULL;
|
||||
g_startup_publish_tracking_enabled = 0;
|
||||
@@ -3137,25 +3133,12 @@ int nostr_handler_reconcile_startup_events(void) {
|
||||
g_startup_publish_tracking_enabled = 1;
|
||||
}
|
||||
|
||||
if (g_cfg->default_skill.content && g_cfg->default_skill.content[0] != '\0') {
|
||||
g_system_context = strdup(g_cfg->default_skill.content);
|
||||
}
|
||||
|
||||
load_startup_display_name();
|
||||
g_startup_kind1_already_exists = startup_self_kind1_exists();
|
||||
if (g_startup_kind1_already_exists) {
|
||||
DEBUG_INFO("[didactyl] startup phase: existing self kind-1 note found; skipping startup kind-1 publish");
|
||||
}
|
||||
|
||||
if (!g_system_context) {
|
||||
char default_ctx[512];
|
||||
snprintf(default_ctx,
|
||||
sizeof(default_ctx),
|
||||
"You are %s, a sovereign AI agent living on Nostr.",
|
||||
g_startup_display_name[0] != '\0' ? g_startup_display_name : "the agent");
|
||||
g_system_context = strdup(default_ctx);
|
||||
}
|
||||
|
||||
for (int relay_index = 0; relay_index < g_cfg->relay_count; relay_index++) {
|
||||
publish_pending_startup_events_for_relay_index(relay_index, "startup_reconcile");
|
||||
}
|
||||
@@ -3165,131 +3148,6 @@ int nostr_handler_reconcile_startup_events(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int nostr_handler_load_system_context_from_adopted_skills(void) {
|
||||
if (!g_cfg || !g_pool || g_cfg->keys.public_key_hex[0] == '\0') {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cJSON* adoption_filter = cJSON_CreateObject();
|
||||
cJSON* kinds = cJSON_CreateArray();
|
||||
cJSON* authors = cJSON_CreateArray();
|
||||
if (!adoption_filter || !kinds || !authors) {
|
||||
cJSON_Delete(adoption_filter);
|
||||
cJSON_Delete(kinds);
|
||||
cJSON_Delete(authors);
|
||||
return -1;
|
||||
}
|
||||
|
||||
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(10123));
|
||||
cJSON_AddItemToObject(adoption_filter, "kinds", kinds);
|
||||
cJSON_AddItemToArray(authors, cJSON_CreateString(g_cfg->keys.public_key_hex));
|
||||
cJSON_AddItemToObject(adoption_filter, "authors", authors);
|
||||
cJSON_AddNumberToObject(adoption_filter, "limit", 1);
|
||||
|
||||
char* adoption_events_json = nostr_handler_query_json(adoption_filter, 3000);
|
||||
cJSON_Delete(adoption_filter);
|
||||
if (!adoption_events_json) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cJSON* adoption_events = cJSON_Parse(adoption_events_json);
|
||||
free(adoption_events_json);
|
||||
if (!adoption_events || !cJSON_IsArray(adoption_events) || cJSON_GetArraySize(adoption_events) <= 0) {
|
||||
cJSON_Delete(adoption_events);
|
||||
return -1;
|
||||
}
|
||||
|
||||
cJSON* adoption = cJSON_GetArrayItem(adoption_events, 0);
|
||||
cJSON* tags = adoption ? cJSON_GetObjectItemCaseSensitive(adoption, "tags") : NULL;
|
||||
if (!tags || !cJSON_IsArray(tags)) {
|
||||
cJSON_Delete(adoption_events);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* first_addr = NULL;
|
||||
int tag_n = cJSON_GetArraySize(tags);
|
||||
for (int i = 0; i < tag_n; i++) {
|
||||
cJSON* tag = cJSON_GetArrayItem(tags, i);
|
||||
if (!tag || !cJSON_IsArray(tag) || cJSON_GetArraySize(tag) < 2) {
|
||||
continue;
|
||||
}
|
||||
cJSON* k = cJSON_GetArrayItem(tag, 0);
|
||||
cJSON* v = cJSON_GetArrayItem(tag, 1);
|
||||
if (!k || !v || !cJSON_IsString(k) || !cJSON_IsString(v) || !k->valuestring || !v->valuestring) {
|
||||
continue;
|
||||
}
|
||||
if (strcmp(k->valuestring, "a") == 0 && v->valuestring[0] != '\0') {
|
||||
first_addr = v->valuestring;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!first_addr) {
|
||||
cJSON_Delete(adoption_events);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int skill_kind = 0;
|
||||
char skill_pubkey_hex[65] = {0};
|
||||
char skill_d_tag[65] = {0};
|
||||
if (parse_skill_address_local(first_addr, &skill_kind, skill_pubkey_hex, skill_d_tag) != 0) {
|
||||
cJSON_Delete(adoption_events);
|
||||
return -1;
|
||||
}
|
||||
|
||||
cJSON* skill_filter = cJSON_CreateObject();
|
||||
cJSON* skill_kinds = cJSON_CreateArray();
|
||||
cJSON* skill_authors = cJSON_CreateArray();
|
||||
cJSON* d_tags = cJSON_CreateArray();
|
||||
if (!skill_filter || !skill_kinds || !skill_authors || !d_tags) {
|
||||
cJSON_Delete(adoption_events);
|
||||
cJSON_Delete(skill_filter);
|
||||
cJSON_Delete(skill_kinds);
|
||||
cJSON_Delete(skill_authors);
|
||||
cJSON_Delete(d_tags);
|
||||
return -1;
|
||||
}
|
||||
|
||||
cJSON_AddItemToArray(skill_kinds, cJSON_CreateNumber(skill_kind));
|
||||
cJSON_AddItemToObject(skill_filter, "kinds", skill_kinds);
|
||||
cJSON_AddItemToArray(skill_authors, cJSON_CreateString(skill_pubkey_hex));
|
||||
cJSON_AddItemToObject(skill_filter, "authors", skill_authors);
|
||||
cJSON_AddItemToArray(d_tags, cJSON_CreateString(skill_d_tag));
|
||||
cJSON_AddItemToObject(skill_filter, "#d", d_tags);
|
||||
cJSON_AddNumberToObject(skill_filter, "limit", 1);
|
||||
|
||||
char* skill_events_json = nostr_handler_query_json(skill_filter, 3000);
|
||||
cJSON_Delete(skill_filter);
|
||||
cJSON_Delete(adoption_events);
|
||||
if (!skill_events_json) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cJSON* skill_events = cJSON_Parse(skill_events_json);
|
||||
free(skill_events_json);
|
||||
if (!skill_events || !cJSON_IsArray(skill_events) || cJSON_GetArraySize(skill_events) <= 0) {
|
||||
cJSON_Delete(skill_events);
|
||||
return -1;
|
||||
}
|
||||
|
||||
cJSON* skill_event = cJSON_GetArrayItem(skill_events, 0);
|
||||
cJSON* content = skill_event ? cJSON_GetObjectItemCaseSensitive(skill_event, "content") : NULL;
|
||||
if (!content || !cJSON_IsString(content) || !content->valuestring) {
|
||||
cJSON_Delete(skill_events);
|
||||
return -1;
|
||||
}
|
||||
|
||||
free(g_system_context);
|
||||
g_system_context = strdup(content->valuestring);
|
||||
cJSON_Delete(skill_events);
|
||||
|
||||
return g_system_context ? 0 : -1;
|
||||
}
|
||||
|
||||
const char* nostr_handler_get_system_context(void) {
|
||||
return g_system_context;
|
||||
}
|
||||
|
||||
char* nostr_handler_get_self_events_by_kind_json(int kind) {
|
||||
cJSON* out = cJSON_CreateArray();
|
||||
if (!out) {
|
||||
@@ -4212,8 +4070,6 @@ 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;
|
||||
|
||||
@@ -59,10 +59,8 @@ nostr_pool_subscription_t* nostr_handler_subscribe_with_filter(
|
||||
int nostr_handler_close_subscription(nostr_pool_subscription_t* subscription);
|
||||
int nostr_handler_poll(int timeout_ms);
|
||||
int nostr_handler_reconcile_startup_events(void);
|
||||
int nostr_handler_load_system_context_from_adopted_skills(void);
|
||||
void nostr_handler_refresh_relay_statuses(void);
|
||||
int nostr_handler_sync_relays_from_config(void);
|
||||
const char* nostr_handler_get_system_context(void);
|
||||
char* nostr_handler_get_self_skill_events_json(void);
|
||||
int nostr_handler_is_event_in_self_cache(const char* event_id_hex);
|
||||
const char* nostr_handler_get_startup_display_name(void);
|
||||
|
||||
+5
-511
@@ -1,18 +1,16 @@
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
|
||||
#include "prompt_template.h"
|
||||
#include "cjson/cJSON.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static char* dup_range(const char* s, size_t n) {
|
||||
char* out = (char*)malloc(n + 1U);
|
||||
if (!out) return NULL;
|
||||
if (n > 0) {
|
||||
memcpy(out, s, n);
|
||||
}
|
||||
if (n > 0) memcpy(out, s, n);
|
||||
out[n] = '\0';
|
||||
return out;
|
||||
}
|
||||
@@ -32,52 +30,6 @@ static void rtrim_inplace(char* s) {
|
||||
}
|
||||
}
|
||||
|
||||
static int starts_with(const char* s, const char* prefix) {
|
||||
if (!s || !prefix) return 0;
|
||||
size_t n = strlen(prefix);
|
||||
return strncmp(s, prefix, n) == 0;
|
||||
}
|
||||
|
||||
static int add_line(char*** lines, int* count, int* cap, char* line) {
|
||||
if (!lines || !count || !cap) return -1;
|
||||
if (*count >= *cap) {
|
||||
int next = (*cap == 0) ? 64 : (*cap * 2);
|
||||
char** grown = (char**)realloc(*lines, (size_t)next * sizeof(char*));
|
||||
if (!grown) return -1;
|
||||
*lines = grown;
|
||||
*cap = next;
|
||||
}
|
||||
(*lines)[*count] = line;
|
||||
(*count)++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int split_lines_inplace(char* s, char*** out_lines, int* out_count) {
|
||||
if (!s || !out_lines || !out_count) return -1;
|
||||
|
||||
char** lines = NULL;
|
||||
int count = 0;
|
||||
int cap = 0;
|
||||
|
||||
char* p = s;
|
||||
while (*p) {
|
||||
char* start = p;
|
||||
while (*p && *p != '\n') p++;
|
||||
if (*p == '\n') {
|
||||
*p = '\0';
|
||||
p++;
|
||||
}
|
||||
if (add_line(&lines, &count, &cap, start) != 0) {
|
||||
free(lines);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
*out_lines = lines;
|
||||
*out_count = count;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int append_text(char** buf, size_t* cap, size_t* used, const char* s) {
|
||||
if (!buf || !cap || !used || !s) return -1;
|
||||
size_t n = strlen(s);
|
||||
@@ -105,10 +57,11 @@ static const char* map_variable_tool_name(const char* var_name) {
|
||||
if (strcmp(var_name, "admin_relays") == 0) return "nostr_admin_relays";
|
||||
if (strcmp(var_name, "adopted_skills") == 0) return "adopted_skills";
|
||||
if (strcmp(var_name, "triggering_event") == 0) return "trigger_event";
|
||||
return NULL;
|
||||
|
||||
return var_name;
|
||||
}
|
||||
|
||||
static char* resolve_inline_variables_local(const char* tpl, tools_context_t* tools_ctx) {
|
||||
char* prompt_template_resolve_inline_variables(const char* tpl, tools_context_t* tools_ctx) {
|
||||
if (!tpl) return strdup("");
|
||||
|
||||
size_t cap = strlen(tpl) + 64U;
|
||||
@@ -216,462 +169,3 @@ static char* resolve_inline_variables_local(const char* tpl, tools_context_t* to
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static void init_section_defaults(prompt_template_section_t* sec) {
|
||||
if (!sec) return;
|
||||
memset(sec->name, 0, sizeof(sec->name));
|
||||
memset(sec->role, 0, sizeof(sec->role));
|
||||
snprintf(sec->role, sizeof(sec->role), "system");
|
||||
sec->content_template = NULL;
|
||||
sec->tool_name = NULL;
|
||||
sec->tool_args = NULL;
|
||||
sec->result_field = NULL;
|
||||
sec->limit = 0;
|
||||
sec->skip_if_empty = 0;
|
||||
sec->provider_name = NULL;
|
||||
sec->provider_content_template = NULL;
|
||||
}
|
||||
|
||||
static int parse_int_or_zero(const char* s) {
|
||||
if (!s) return 0;
|
||||
while (*s && isspace((unsigned char)*s)) s++;
|
||||
return atoi(s);
|
||||
}
|
||||
|
||||
int prompt_template_parse(const char* skill_content, prompt_template_t* out_template) {
|
||||
if (!skill_content || !out_template) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(out_template, 0, sizeof(*out_template));
|
||||
|
||||
const char* marker = strstr(skill_content, PROMPT_TEMPLATE_MARKER);
|
||||
if (!marker) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t personality_len = (size_t)(marker - skill_content);
|
||||
out_template->personality = dup_range(skill_content, personality_len);
|
||||
if (!out_template->personality) {
|
||||
return -1;
|
||||
}
|
||||
rtrim_inplace(out_template->personality);
|
||||
|
||||
const char* after = marker + strlen(PROMPT_TEMPLATE_MARKER);
|
||||
while (*after == '\r' || *after == '\n') after++;
|
||||
|
||||
char* tpl = strdup(after);
|
||||
if (!tpl) {
|
||||
prompt_template_free(out_template);
|
||||
return -1;
|
||||
}
|
||||
|
||||
char** lines = NULL;
|
||||
int line_count = 0;
|
||||
if (split_lines_inplace(tpl, &lines, &line_count) != 0) {
|
||||
free(tpl);
|
||||
prompt_template_free(out_template);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int current = -1;
|
||||
int i = 0;
|
||||
while (i < line_count) {
|
||||
char* raw = lines[i];
|
||||
rtrim_inplace(raw);
|
||||
char* line = ltrim_inplace(raw);
|
||||
|
||||
if (*line == '\0') {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (starts_with(line, "- section:")) {
|
||||
if (out_template->section_count >= PROMPT_TEMPLATE_MAX_SECTIONS) {
|
||||
break;
|
||||
}
|
||||
|
||||
current = out_template->section_count;
|
||||
init_section_defaults(&out_template->sections[current]);
|
||||
out_template->section_count++;
|
||||
|
||||
char* name = line + strlen("- section:");
|
||||
name = ltrim_inplace(name);
|
||||
rtrim_inplace(name);
|
||||
snprintf(out_template->sections[current].name,
|
||||
sizeof(out_template->sections[current].name),
|
||||
"%s",
|
||||
name);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (current < 0) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (starts_with(line, "role:")) {
|
||||
char* role = line + strlen("role:");
|
||||
role = ltrim_inplace(role);
|
||||
rtrim_inplace(role);
|
||||
snprintf(out_template->sections[current].role,
|
||||
sizeof(out_template->sections[current].role),
|
||||
"%s",
|
||||
(*role) ? role : "system");
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (starts_with(line, "limit:")) {
|
||||
char* lim = line + strlen("limit:");
|
||||
out_template->sections[current].limit = parse_int_or_zero(lim);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (starts_with(line, "skip_if_empty:")) {
|
||||
char* flag = line + strlen("skip_if_empty:");
|
||||
flag = ltrim_inplace(flag);
|
||||
rtrim_inplace(flag);
|
||||
out_template->sections[current].skip_if_empty =
|
||||
(strcmp(flag, "true") == 0 || strcmp(flag, "1") == 0) ? 1 : 0;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (starts_with(line, "tool:")) {
|
||||
char* val = line + strlen("tool:");
|
||||
val = ltrim_inplace(val);
|
||||
rtrim_inplace(val);
|
||||
free(out_template->sections[current].tool_name);
|
||||
out_template->sections[current].tool_name = strdup(val);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (starts_with(line, "args:")) {
|
||||
char* val = line + strlen("args:");
|
||||
val = ltrim_inplace(val);
|
||||
rtrim_inplace(val);
|
||||
free(out_template->sections[current].tool_args);
|
||||
out_template->sections[current].tool_args = strdup((*val) ? val : "{}");
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (starts_with(line, "result_field:")) {
|
||||
char* val = line + strlen("result_field:");
|
||||
val = ltrim_inplace(val);
|
||||
rtrim_inplace(val);
|
||||
free(out_template->sections[current].result_field);
|
||||
out_template->sections[current].result_field = strdup(val);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (starts_with(line, "content:")) {
|
||||
char* val = line + strlen("content:");
|
||||
val = ltrim_inplace(val);
|
||||
|
||||
free(out_template->sections[current].content_template);
|
||||
out_template->sections[current].content_template = NULL;
|
||||
|
||||
if (strcmp(val, "|") == 0) {
|
||||
char* acc = strdup("");
|
||||
size_t cap = acc ? 1U : 0U;
|
||||
size_t used = 0U;
|
||||
if (!acc) {
|
||||
free(lines);
|
||||
free(tpl);
|
||||
prompt_template_free(out_template);
|
||||
return -1;
|
||||
}
|
||||
|
||||
i++;
|
||||
while (i < line_count) {
|
||||
char* next_raw = lines[i];
|
||||
char* next_ltrim = ltrim_inplace(next_raw);
|
||||
|
||||
if (starts_with(next_ltrim, "- section:") ||
|
||||
starts_with(next_ltrim, "role:") ||
|
||||
starts_with(next_ltrim, "limit:") ||
|
||||
starts_with(next_ltrim, "skip_if_empty:") ||
|
||||
starts_with(next_ltrim, "tool:") ||
|
||||
starts_with(next_ltrim, "args:") ||
|
||||
starts_with(next_ltrim, "result_field:") ||
|
||||
starts_with(next_ltrim, "content:") ||
|
||||
starts_with(next_ltrim, "provider:")) {
|
||||
break;
|
||||
}
|
||||
|
||||
char* piece = next_raw;
|
||||
if (strncmp(piece, " ", 4) == 0) piece += 4;
|
||||
else if (strncmp(piece, " ", 2) == 0) piece += 2;
|
||||
rtrim_inplace(piece);
|
||||
|
||||
if (append_text(&acc, &cap, &used, piece) != 0 ||
|
||||
append_text(&acc, &cap, &used, "\n") != 0) {
|
||||
free(acc);
|
||||
free(lines);
|
||||
free(tpl);
|
||||
prompt_template_free(out_template);
|
||||
return -1;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
rtrim_inplace(acc);
|
||||
out_template->sections[current].content_template = acc;
|
||||
continue;
|
||||
}
|
||||
|
||||
rtrim_inplace(val);
|
||||
out_template->sections[current].content_template = strdup(val);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (starts_with(line, "provider:")) {
|
||||
i++;
|
||||
if (i >= line_count) continue;
|
||||
|
||||
char* provider_line = ltrim_inplace(lines[i]);
|
||||
rtrim_inplace(provider_line);
|
||||
char* colon = strchr(provider_line, ':');
|
||||
if (!colon) {
|
||||
continue;
|
||||
}
|
||||
|
||||
*colon = '\0';
|
||||
char* provider_name = ltrim_inplace(provider_line);
|
||||
rtrim_inplace(provider_name);
|
||||
char* provider_val = ltrim_inplace(colon + 1);
|
||||
rtrim_inplace(provider_val);
|
||||
|
||||
free(out_template->sections[current].provider_name);
|
||||
out_template->sections[current].provider_name = strdup(provider_name);
|
||||
free(out_template->sections[current].provider_content_template);
|
||||
out_template->sections[current].provider_content_template = NULL;
|
||||
|
||||
if (strcmp(provider_val, "|") == 0) {
|
||||
char* acc = strdup("");
|
||||
size_t cap = acc ? 1U : 0U;
|
||||
size_t used = 0U;
|
||||
if (!acc) {
|
||||
free(lines);
|
||||
free(tpl);
|
||||
prompt_template_free(out_template);
|
||||
return -1;
|
||||
}
|
||||
|
||||
i++;
|
||||
while (i < line_count) {
|
||||
char* next = ltrim_inplace(lines[i]);
|
||||
if (starts_with(next, "- section:") ||
|
||||
starts_with(next, "role:") ||
|
||||
starts_with(next, "limit:") ||
|
||||
starts_with(next, "skip_if_empty:") ||
|
||||
starts_with(next, "tool:") ||
|
||||
starts_with(next, "args:") ||
|
||||
starts_with(next, "result_field:") ||
|
||||
starts_with(next, "content:") ||
|
||||
starts_with(next, "provider:")) {
|
||||
break;
|
||||
}
|
||||
char* piece = lines[i];
|
||||
if (strncmp(piece, " ", 4) == 0) piece += 4;
|
||||
else if (strncmp(piece, " ", 2) == 0) piece += 2;
|
||||
rtrim_inplace(piece);
|
||||
if (append_text(&acc, &cap, &used, piece) != 0 ||
|
||||
append_text(&acc, &cap, &used, "\n") != 0) {
|
||||
free(acc);
|
||||
free(lines);
|
||||
free(tpl);
|
||||
prompt_template_free(out_template);
|
||||
return -1;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
rtrim_inplace(acc);
|
||||
out_template->sections[current].provider_content_template = acc;
|
||||
continue;
|
||||
}
|
||||
|
||||
out_template->sections[current].provider_content_template = strdup(provider_val);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
free(lines);
|
||||
free(tpl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int append_message_object(cJSON* messages, const char* role, const char* content) {
|
||||
if (!messages || !role) return -1;
|
||||
cJSON* msg = cJSON_CreateObject();
|
||||
if (!msg) return -1;
|
||||
cJSON_AddStringToObject(msg, "role", role);
|
||||
cJSON_AddStringToObject(msg, "content", content ? content : "");
|
||||
cJSON_AddItemToArray(messages, msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char* extract_tool_result_content(const prompt_template_section_t* sec, const char* tool_result_json) {
|
||||
if (!tool_result_json) return strdup("");
|
||||
|
||||
cJSON* root = cJSON_Parse(tool_result_json);
|
||||
if (!root || !cJSON_IsObject(root)) {
|
||||
cJSON_Delete(root);
|
||||
return strdup(tool_result_json);
|
||||
}
|
||||
|
||||
cJSON* success = cJSON_GetObjectItemCaseSensitive(root, "success");
|
||||
if (success && cJSON_IsBool(success) && !cJSON_IsTrue(success)) {
|
||||
cJSON_Delete(root);
|
||||
return strdup("");
|
||||
}
|
||||
|
||||
const char* field_name = (sec && sec->result_field && sec->result_field[0] != '\0')
|
||||
? sec->result_field
|
||||
: "content";
|
||||
|
||||
cJSON* field = cJSON_GetObjectItemCaseSensitive(root, field_name);
|
||||
if (!field) {
|
||||
field = cJSON_GetObjectItemCaseSensitive(root, "content");
|
||||
}
|
||||
|
||||
char* out = NULL;
|
||||
if (field && cJSON_IsString(field) && field->valuestring) {
|
||||
out = strdup(field->valuestring);
|
||||
} else if (field) {
|
||||
out = cJSON_PrintUnformatted(field);
|
||||
} else {
|
||||
out = cJSON_PrintUnformatted(root);
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
return out ? out : strdup("");
|
||||
}
|
||||
|
||||
cJSON* prompt_template_build_messages(const prompt_template_t* tmpl,
|
||||
const char* provider_name,
|
||||
tools_context_t* tools_ctx,
|
||||
cJSON* dm_history_messages,
|
||||
int dm_history_default_limit,
|
||||
prompt_template_emit_hook_fn emit_hook,
|
||||
void* emit_hook_user_data) {
|
||||
if (!tmpl) return NULL;
|
||||
|
||||
cJSON* out = cJSON_CreateArray();
|
||||
if (!out) return NULL;
|
||||
|
||||
int out_idx = 0;
|
||||
if (tmpl->personality && tmpl->personality[0] != '\0') {
|
||||
if (append_message_object(out, "system", tmpl->personality) != 0) {
|
||||
cJSON_Delete(out);
|
||||
return NULL;
|
||||
}
|
||||
if (emit_hook) emit_hook("system_prompt", out_idx, emit_hook_user_data);
|
||||
out_idx++;
|
||||
}
|
||||
|
||||
for (int i = 0; i < tmpl->section_count; i++) {
|
||||
const prompt_template_section_t* sec = &tmpl->sections[i];
|
||||
const char* role = sec->role[0] ? sec->role : "system";
|
||||
|
||||
if (strcmp(role, "expand") == 0) {
|
||||
if (!dm_history_messages || !cJSON_IsArray(dm_history_messages)) {
|
||||
continue;
|
||||
}
|
||||
int total = cJSON_GetArraySize(dm_history_messages);
|
||||
int lim = sec->limit > 0 ? sec->limit : dm_history_default_limit;
|
||||
int start = (lim > 0 && total > lim) ? (total - lim) : 0;
|
||||
|
||||
for (int j = start; j < total; j++) {
|
||||
cJSON* item = cJSON_GetArrayItem(dm_history_messages, j);
|
||||
if (!item || !cJSON_IsObject(item)) continue;
|
||||
cJSON* dup = cJSON_Duplicate(item, 1);
|
||||
if (!dup) {
|
||||
cJSON_Delete(out);
|
||||
return NULL;
|
||||
}
|
||||
cJSON_AddItemToArray(out, dup);
|
||||
if (emit_hook) emit_hook(sec->name[0] ? sec->name : "context_part", out_idx, emit_hook_user_data);
|
||||
out_idx++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
char* resolved = NULL;
|
||||
|
||||
if (sec->tool_name && sec->tool_name[0] != '\0' && tools_ctx) {
|
||||
const char* args_json = (sec->tool_args && sec->tool_args[0] != '\0') ? sec->tool_args : "{}";
|
||||
char* tool_result = tools_execute(tools_ctx, sec->tool_name, args_json);
|
||||
resolved = extract_tool_result_content(sec, tool_result);
|
||||
free(tool_result);
|
||||
} else {
|
||||
const char* tpl = sec->content_template;
|
||||
if (provider_name && sec->provider_name && sec->provider_content_template &&
|
||||
strcmp(provider_name, sec->provider_name) == 0) {
|
||||
tpl = sec->provider_content_template;
|
||||
}
|
||||
resolved = resolve_inline_variables_local(tpl ? tpl : "", tools_ctx);
|
||||
}
|
||||
if (!resolved) {
|
||||
cJSON_Delete(out);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (sec->skip_if_empty) {
|
||||
char* chk = ltrim_inplace(resolved);
|
||||
if (chk && *chk == '\0') {
|
||||
free(resolved);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (append_message_object(out, role, resolved) != 0) {
|
||||
free(resolved);
|
||||
cJSON_Delete(out);
|
||||
return NULL;
|
||||
}
|
||||
if (emit_hook) emit_hook(sec->name[0] ? sec->name : "context_part", out_idx, emit_hook_user_data);
|
||||
out_idx++;
|
||||
free(resolved);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void prompt_template_free(prompt_template_t* tmpl) {
|
||||
if (!tmpl) return;
|
||||
|
||||
free(tmpl->personality);
|
||||
tmpl->personality = NULL;
|
||||
|
||||
for (int i = 0; i < tmpl->section_count; i++) {
|
||||
free(tmpl->sections[i].content_template);
|
||||
tmpl->sections[i].content_template = NULL;
|
||||
free(tmpl->sections[i].tool_name);
|
||||
tmpl->sections[i].tool_name = NULL;
|
||||
free(tmpl->sections[i].tool_args);
|
||||
tmpl->sections[i].tool_args = NULL;
|
||||
free(tmpl->sections[i].result_field);
|
||||
tmpl->sections[i].result_field = NULL;
|
||||
free(tmpl->sections[i].provider_name);
|
||||
tmpl->sections[i].provider_name = NULL;
|
||||
free(tmpl->sections[i].provider_content_template);
|
||||
tmpl->sections[i].provider_content_template = NULL;
|
||||
tmpl->sections[i].name[0] = '\0';
|
||||
tmpl->sections[i].role[0] = '\0';
|
||||
tmpl->sections[i].limit = 0;
|
||||
tmpl->sections[i].skip_if_empty = 0;
|
||||
}
|
||||
|
||||
tmpl->section_count = 0;
|
||||
}
|
||||
+1
-40
@@ -1,47 +1,8 @@
|
||||
#ifndef DIDACTYL_PROMPT_TEMPLATE_H
|
||||
#define DIDACTYL_PROMPT_TEMPLATE_H
|
||||
|
||||
#include "cjson/cJSON.h"
|
||||
#include "tools/tools.h"
|
||||
|
||||
#define PROMPT_TEMPLATE_MAX_SECTIONS 32
|
||||
#define PROMPT_TEMPLATE_MAX_NAME_LEN 64
|
||||
#define PROMPT_TEMPLATE_MAX_ROLE_LEN 16
|
||||
#define PROMPT_TEMPLATE_MARKER "---template---"
|
||||
|
||||
typedef struct {
|
||||
char name[PROMPT_TEMPLATE_MAX_NAME_LEN];
|
||||
char role[PROMPT_TEMPLATE_MAX_ROLE_LEN];
|
||||
char* content_template;
|
||||
char* tool_name;
|
||||
char* tool_args;
|
||||
char* result_field;
|
||||
int limit;
|
||||
int skip_if_empty;
|
||||
char* provider_name;
|
||||
char* provider_content_template;
|
||||
} prompt_template_section_t;
|
||||
|
||||
typedef struct {
|
||||
char* personality;
|
||||
prompt_template_section_t sections[PROMPT_TEMPLATE_MAX_SECTIONS];
|
||||
int section_count;
|
||||
} prompt_template_t;
|
||||
|
||||
typedef void (*prompt_template_emit_hook_fn)(const char* section_name,
|
||||
int message_index,
|
||||
void* user_data);
|
||||
|
||||
int prompt_template_parse(const char* skill_content, prompt_template_t* out_template);
|
||||
|
||||
cJSON* prompt_template_build_messages(const prompt_template_t* tmpl,
|
||||
const char* provider_name,
|
||||
tools_context_t* tools_ctx,
|
||||
cJSON* dm_history_messages,
|
||||
int dm_history_default_limit,
|
||||
prompt_template_emit_hook_fn emit_hook,
|
||||
void* emit_hook_user_data);
|
||||
|
||||
void prompt_template_free(prompt_template_t* tmpl);
|
||||
char* prompt_template_resolve_inline_variables(const char* tpl, tools_context_t* tools_ctx);
|
||||
|
||||
#endif
|
||||
+2
-2
@@ -508,7 +508,7 @@ static int configure_default_skill_for_agent(didactyl_config_t* cfg, const char*
|
||||
cfg->default_skill.kind = DIDACTYL_DEFAULT_SKILL_KIND;
|
||||
snprintf(cfg->default_skill.d_tag, sizeof(cfg->default_skill.d_tag), "%s", DIDACTYL_DEFAULT_SKILL_D_TAG);
|
||||
|
||||
int needed = snprintf(NULL, 0, DIDACTYL_DEFAULT_SKILL_TEMPLATE, agent_name, agent_name);
|
||||
int needed = snprintf(NULL, 0, "%s", DIDACTYL_DEFAULT_SKILL_TEMPLATE);
|
||||
if (needed <= 0) {
|
||||
return -1;
|
||||
}
|
||||
@@ -518,7 +518,7 @@ static int configure_default_skill_for_agent(didactyl_config_t* cfg, const char*
|
||||
return -1;
|
||||
}
|
||||
|
||||
snprintf(cfg->default_skill.content, (size_t)needed + 1U, DIDACTYL_DEFAULT_SKILL_TEMPLATE, agent_name, agent_name);
|
||||
snprintf(cfg->default_skill.content, (size_t)needed + 1U, "%s", DIDACTYL_DEFAULT_SKILL_TEMPLATE);
|
||||
|
||||
cfg->default_skill.tags_json = strdup(DIDACTYL_DEFAULT_SKILL_TAGS_JSON);
|
||||
if (!cfg->default_skill.tags_json) {
|
||||
|
||||
Reference in New Issue
Block a user