v0.0.67 - Add agent self-context subscriptions, tools, aliases, and prompt template sections

This commit is contained in:
Your Name
2026-03-10 17:33:35 -04:00
parent 6dbd952023
commit 5b8b8310f7
18 changed files with 11387 additions and 9 deletions

View File

@@ -55,11 +55,11 @@ Skills support context modes (`inject`, `full`, `override`) and per-skill LLM fa
Didactyl will support local inference, which is very privacy preserving. Remote inference does however have it's advantages, and in those cases Didactyl supports using Bitcoin Lightning and eCash inference providers.
## Current Status — v0.0.66
## Current Status — v0.0.67
**Active build — this project is barely working. Experiment at your own risk.**
> Last release update: v0.0.66Complete tools refactor, move all tools-named sources/headers into src/tools, and update build wiring
> Last release update: v0.0.67Add agent self-context subscriptions, tools, aliases, and prompt template sections
- Connects to configured relays with auto-reconnect and relay state transition logging
- Publishes configured startup events per relay as each relay becomes connected

View File

@@ -31,6 +31,25 @@
"temperature": 0.7 // sampling temperature (0.0 2.0)
},
// ─── Tools & Runtime Limits ───────────────────────────────────────
// Centralized turn and timeout limits used by DM agent loops,
// triggered skills, HTTP API prompt runs, and local HTTP fetch tool.
"tools": {
"enabled": true,
"max_turns": 8, // default max turns for normal agent tool loops
"trigger_max_turns": 6, // max turns for triggered-skill executions
"api_default_max_turns": 4, // default when HTTP body omits max_turns
"api_max_turns_ceiling": 16, // hard cap applied to API-provided max_turns
"local_http_fetch_default_timeout_seconds": 20,
"local_http_fetch_max_timeout_seconds": 120,
"shell": {
"enabled": true,
"timeout_seconds": 30,
"max_output_bytes": 65536,
"working_directory": "."
}
},
// ─── Security Tiers ───────────────────────────────────────────────
// Controls who can interact with the agent and what they can do.
"security": {

File diff suppressed because one or more lines are too long

View File

@@ -26,6 +26,31 @@
tool: nostr_admin_notes
skip_if_empty: true
- section: agent_identity
role: system
tool: agent_identity
skip_if_empty: true
- section: agent_profile
role: system
tool: nostr_agent_profile
skip_if_empty: true
- section: agent_contacts
role: system
tool: nostr_agent_contacts
skip_if_empty: true
- section: agent_relays
role: system
tool: nostr_agent_relays
skip_if_empty: true
- section: agent_notes
role: system
tool: nostr_agent_notes
skip_if_empty: true
- section: tasks
role: system
tool: task_list

204
plans/agent_self_context.md Normal file
View File

@@ -0,0 +1,204 @@
# Agent Self-Context: Know Thyself
## Problem
When the agent is asked about itself — its profile, contacts, relays, or recent notes — it has no idea. The context template currently injects **administrator** identity/profile/contacts/relays/notes into the system prompt, but nothing equivalent for the **agent's own** Nostr identity beyond a bare pubkey+npub from `agent_identity`.
The agent publishes its own kind 0 (profile), kind 3 (contacts), kind 10002 (relays), and kind 1 (notes) at startup, but never subscribes to or caches those events for self-awareness.
## Solution Overview
Mirror the admin context pattern for the agent itself:
1. **Subscribe** to the agent's own kind 0/3/10002/1 events from relays
2. **Cache** them in `nostr_handler.c` (same pattern as `g_admin_kind0_json` etc.)
3. **Expose** them via new `nostr_handler_get_agent_*` API functions
4. **Create context tools** that format the cached data into context blocks
5. **Add to context template** so the agent always knows about itself
6. **Add callable tool aliases** like `my_kind0_profile` for on-demand use
```mermaid
flowchart TD
A[Startup: publish kind 0/3/10002/1] --> B[Subscribe to own events]
B --> C[Cache in nostr_handler globals]
C --> D[Context tools read cache]
D --> E[Context template injects into system prompt]
D --> F[LLM can call tools on-demand]
subgraph Admin Context - existing
G[g_admin_kind0_json]
H[g_admin_kind3 contacts]
I[g_admin_kind10002_json]
J[g_admin_kind1_notes]
end
subgraph Agent Context - new
K[g_agent_kind0_json]
L[g_agent_kind3 contacts]
M[g_agent_kind10002_json]
N[g_agent_kind1_notes]
end
```
## Detailed Changes
### 1. nostr_handler.c — Agent Self-Context Cache
Add new static globals mirroring the admin pattern:
```c
static char* g_agent_kind0_json = NULL; // kind 0 profile JSON
static char* g_agent_kind10002_json = NULL; // kind 10002 relay list JSON
static char** g_agent_kind3_contacts = NULL; // kind 3 contact pubkeys
static int g_agent_kind3_contact_count = 0;
static admin_kind1_note_t* g_agent_kind1_notes = NULL; // reuse struct
static int g_agent_kind1_note_count = 0;
```
### 2. nostr_handler.c — Agent Self-Context Subscription
Create `nostr_handler_subscribe_agent_context()` that subscribes to kinds 0, 3, 10002, 1 filtered by the agent's own pubkey. This is separate from the self-skills subscription (which handles 31123/31124/10123).
The callback `on_agent_context_event()` will parse and cache events using the same logic as `on_admin_context_event()`.
**Alternative considered:** Expanding `nostr_handler_subscribe_self_skills()` to include these kinds. Rejected because the self-skills sub has different EOSE handling and a callback for skill loading. Keeping them separate is cleaner.
### 3. nostr_handler.h — New API Functions
```c
int nostr_handler_subscribe_agent_context(void);
char* nostr_handler_get_agent_kind0_context(void);
char* nostr_handler_get_agent_kind3_context(void);
char* nostr_handler_get_agent_kind10002_context(void);
char* nostr_handler_get_agent_kind1_notes_context(void);
```
### 4. main.c — Call Agent Context Subscription at Startup
Add `nostr_handler_subscribe_agent_context()` call after admin context subscription, before self-skills subscription.
### 5. tool_agent.c — New Context Tools
Create four new tool functions following the exact pattern from `tool_admin.c`:
| Tool Name | Description | Data Source |
|-----------|-------------|-------------|
| `nostr_agent_profile` | Agent's kind 0 profile metadata | `nostr_handler_get_agent_kind0_context()` |
| `nostr_agent_contacts` | Agent's kind 3 contact list | `nostr_handler_get_agent_kind3_context()` |
| `nostr_agent_relays` | Agent's kind 10002 relay list | `nostr_handler_get_agent_kind10002_context()` |
| `nostr_agent_notes` | Agent's recent kind 1 notes | `nostr_handler_get_agent_kind1_notes_context()` |
Each returns a `content` field with markdown-formatted context, e.g.:
```
## Agent Kind 0 Profile (source: nostr kind 0)
Agent kind 0 profile content (JSON): {"name":"Didactyl Agent","display_name":"Didactyl",...}
```
### 6. tools_internal.h — Declare New Functions
```c
char* execute_nostr_agent_profile(tools_context_t* ctx, const char* args_json);
char* execute_nostr_agent_contacts(tools_context_t* ctx, const char* args_json);
char* execute_nostr_agent_relays(tools_context_t* ctx, const char* args_json);
char* execute_nostr_agent_notes(tools_context_t* ctx, const char* args_json);
```
### 7. tools_dispatch.c — Register + Aliases
Add dispatch entries:
```c
if (strcmp(tool_name, "nostr_agent_profile") == 0) return execute_nostr_agent_profile(ctx, args_json);
if (strcmp(tool_name, "nostr_agent_contacts") == 0) return execute_nostr_agent_contacts(ctx, args_json);
if (strcmp(tool_name, "nostr_agent_relays") == 0) return execute_nostr_agent_relays(ctx, args_json);
if (strcmp(tool_name, "nostr_agent_notes") == 0) return execute_nostr_agent_notes(ctx, args_json);
// Friendly aliases
if (strcmp(tool_name, "my_kind0_profile") == 0) return execute_nostr_agent_profile(ctx, args_json);
if (strcmp(tool_name, "my_contacts") == 0) return execute_nostr_agent_contacts(ctx, args_json);
if (strcmp(tool_name, "my_relays") == 0) return execute_nostr_agent_relays(ctx, args_json);
if (strcmp(tool_name, "my_notes") == 0) return execute_nostr_agent_notes(ctx, args_json);
```
### 8. tools_schema.c — OpenAI Function Schemas
Add 8 new tool schemas (4 canonical + 4 aliases), all with empty parameters (no-arg tools), following the pattern of `admin_identity`/`nostr_admin_profile` etc.
### 9. Context Template Update
Update the kind 31120 soul content in `config.jsonc` and `context_template.md` to add agent sections **after** admin sections:
```yaml
- section: admin_notes
role: system
tool: nostr_admin_notes
skip_if_empty: true
# NEW: Agent self-context sections
- section: agent_identity
role: system
tool: agent_identity
skip_if_empty: true
- section: agent_profile
role: system
tool: nostr_agent_profile
skip_if_empty: true
- section: agent_contacts
role: system
tool: nostr_agent_contacts
skip_if_empty: true
- section: agent_relays
role: system
tool: nostr_agent_relays
skip_if_empty: true
- section: agent_notes
role: system
tool: nostr_agent_notes
skip_if_empty: true
- section: tasks
role: system
tool: task_list
skip_if_empty: true
```
### 10. nostr_handler.c Cleanup
Add cleanup for agent context globals in `nostr_handler_cleanup()`, mirroring `free_admin_context_locked()`.
## File Change Summary
| File | Change Type | Description |
|------|-------------|-------------|
| `src/nostr_handler.h` | Modify | Add 5 new function declarations |
| `src/nostr_handler.c` | Modify | Add agent context cache globals, subscription, event handler, getter functions, cleanup |
| `src/main.c` | Modify | Call `nostr_handler_subscribe_agent_context()` at startup |
| `src/tools/tool_agent.c` | Modify | Add 4 new context tool execute functions |
| `src/tools/tools_internal.h` | Modify | Declare 4 new execute functions |
| `src/tools/tools_dispatch.c` | Modify | Add 8 dispatch entries (4 tools + 4 aliases) |
| `src/tools/tools_schema.c` | Modify | Add 8 OpenAI function schemas |
| `config.jsonc` | Modify | Update kind 31120 template section |
| `config.jsonc.example` | Modify | Update kind 31120 template section |
| `context_template.md` | Modify | Add agent_* sections after admin_* sections |
## Context Token Impact
Each agent context section adds roughly the same token count as its admin counterpart:
- agent_identity: ~40 tokens (already exists, just adding to template)
- agent_profile: ~80-150 tokens (depends on profile richness)
- agent_contacts: ~50-200 tokens (depends on contact count)
- agent_relays: ~50-100 tokens
- agent_notes: ~100-300 tokens (depends on note count/length)
Total additional context: ~320-790 tokens. With `skip_if_empty: true`, empty sections cost 0 tokens.
## Design Decisions
1. **Separate subscription vs expanding self-skills sub**: Separate is cleaner — different EOSE semantics, different callback needs.
2. **Cache from relay vs read from config**: Cache from relay is more accurate (reflects what's actually published, not just what config says). The startup_events in config are the *intent*; the relay data is the *reality*.
3. **Alias naming**: `my_kind0_profile` matches the existing `my_npub`/`my_pubkey` pattern. Also adding `my_contacts`, `my_relays`, `my_notes` for consistency.
4. **No new config section needed**: The agent context subscription is unconditional — an agent should always know about itself. No `agent_context.enabled` toggle needed.

View File

@@ -1987,7 +1987,9 @@ void agent_on_trigger(const char* skill_d_tag,
free(system_prompt);
free(user_prompt);
int max_turns = g_cfg->tools.max_turns > 0 ? g_cfg->tools.max_turns : 6;
int max_turns = g_cfg->tools.trigger_max_turns > 0
? g_cfg->tools.trigger_max_turns
: (g_cfg->tools.max_turns > 0 ? g_cfg->tools.max_turns : 8);
for (int turn = 0; turn < max_turns; turn++) {
char* messages_json = cJSON_PrintUnformatted(messages);

View File

@@ -222,12 +222,36 @@ static int parse_tools_config(cJSON* root, didactyl_config_t* config) {
cJSON* enabled = cJSON_GetObjectItemCaseSensitive(tools, "enabled");
cJSON* max_turns = cJSON_GetObjectItemCaseSensitive(tools, "max_turns");
cJSON* trigger_max_turns = cJSON_GetObjectItemCaseSensitive(tools, "trigger_max_turns");
cJSON* api_default_max_turns = cJSON_GetObjectItemCaseSensitive(tools, "api_default_max_turns");
cJSON* api_max_turns_ceiling = cJSON_GetObjectItemCaseSensitive(tools, "api_max_turns_ceiling");
cJSON* local_http_fetch_default_timeout_seconds =
cJSON_GetObjectItemCaseSensitive(tools, "local_http_fetch_default_timeout_seconds");
cJSON* local_http_fetch_max_timeout_seconds =
cJSON_GetObjectItemCaseSensitive(tools, "local_http_fetch_max_timeout_seconds");
if (enabled && cJSON_IsBool(enabled)) {
config->tools.enabled = cJSON_IsTrue(enabled) ? 1 : 0;
}
if (max_turns && cJSON_IsNumber(max_turns)) {
config->tools.max_turns = (int)max_turns->valuedouble;
}
if (trigger_max_turns && cJSON_IsNumber(trigger_max_turns)) {
config->tools.trigger_max_turns = (int)trigger_max_turns->valuedouble;
}
if (api_default_max_turns && cJSON_IsNumber(api_default_max_turns)) {
config->tools.api_default_max_turns = (int)api_default_max_turns->valuedouble;
}
if (api_max_turns_ceiling && cJSON_IsNumber(api_max_turns_ceiling)) {
config->tools.api_max_turns_ceiling = (int)api_max_turns_ceiling->valuedouble;
}
if (local_http_fetch_default_timeout_seconds && cJSON_IsNumber(local_http_fetch_default_timeout_seconds)) {
config->tools.local_http_fetch_default_timeout_seconds =
(int)local_http_fetch_default_timeout_seconds->valuedouble;
}
if (local_http_fetch_max_timeout_seconds && cJSON_IsNumber(local_http_fetch_max_timeout_seconds)) {
config->tools.local_http_fetch_max_timeout_seconds =
(int)local_http_fetch_max_timeout_seconds->valuedouble;
}
cJSON* shell = cJSON_GetObjectItemCaseSensitive(tools, "shell");
if (!shell || !cJSON_IsObject(shell)) {
@@ -255,6 +279,31 @@ static int parse_tools_config(cJSON* root, didactyl_config_t* config) {
return -1;
}
if (config->tools.max_turns < 1) {
config->tools.max_turns = 8;
}
if (config->tools.trigger_max_turns < 1) {
config->tools.trigger_max_turns = config->tools.max_turns;
}
if (config->tools.api_default_max_turns < 1) {
config->tools.api_default_max_turns = config->tools.max_turns;
}
if (config->tools.api_max_turns_ceiling < 1) {
config->tools.api_max_turns_ceiling = 16;
}
if (config->tools.api_default_max_turns > config->tools.api_max_turns_ceiling) {
config->tools.api_default_max_turns = config->tools.api_max_turns_ceiling;
}
if (config->tools.local_http_fetch_default_timeout_seconds < 1) {
config->tools.local_http_fetch_default_timeout_seconds = 20;
}
if (config->tools.local_http_fetch_max_timeout_seconds < 1) {
config->tools.local_http_fetch_max_timeout_seconds = 120;
}
if (config->tools.local_http_fetch_default_timeout_seconds > config->tools.local_http_fetch_max_timeout_seconds) {
config->tools.local_http_fetch_default_timeout_seconds = config->tools.local_http_fetch_max_timeout_seconds;
}
return 0;
}
@@ -746,6 +795,11 @@ int config_load(const char* path, didactyl_config_t* config) {
config->tools.enabled = 1;
config->tools.max_turns = 8;
config->tools.trigger_max_turns = 6;
config->tools.api_default_max_turns = 4;
config->tools.api_max_turns_ceiling = 16;
config->tools.local_http_fetch_default_timeout_seconds = 20;
config->tools.local_http_fetch_max_timeout_seconds = 120;
config->tools.shell.enabled = 1;
config->tools.shell.timeout_seconds = 30;
config->tools.shell.max_output_bytes = 65536;

View File

@@ -47,6 +47,11 @@ typedef struct {
typedef struct {
int enabled;
int max_turns;
int trigger_max_turns;
int api_default_max_turns;
int api_max_turns_ceiling;
int local_http_fetch_default_timeout_seconds;
int local_http_fetch_max_timeout_seconds;
shell_tools_config_t shell;
} tools_config_t;

View File

@@ -755,8 +755,13 @@ static cJSON* run_prompt_with_tools_convo(cJSON* convo,
const char* tool_limit_message) {
if (!convo || !cJSON_IsArray(convo)) return NULL;
int max_ceiling = 16;
if (g_api_ctx.cfg && g_api_ctx.cfg->tools.api_max_turns_ceiling > 0) {
max_ceiling = g_api_ctx.cfg->tools.api_max_turns_ceiling;
}
if (max_turns < 1) max_turns = 1;
if (max_turns > 16) max_turns = 16;
if (max_turns > max_ceiling) max_turns = max_ceiling;
char* tools_json = tools_build_openai_schema_json(g_api_ctx.tools_ctx);
if (!tools_json) {
@@ -888,6 +893,9 @@ static cJSON* run_prompt_with_tools(cJSON* body) {
}
int max_turns = 4;
if (g_api_ctx.cfg && g_api_ctx.cfg->tools.api_default_max_turns > 0) {
max_turns = g_api_ctx.cfg->tools.api_default_max_turns;
}
cJSON* max_turns_node = cJSON_GetObjectItemCaseSensitive(body, "max_turns");
if (max_turns_node && cJSON_IsNumber(max_turns_node)) {
max_turns = (int)max_turns_node->valuedouble;
@@ -1104,6 +1112,9 @@ static void handle_prompt_agent(struct mg_connection* c, const struct mg_http_me
}
int max_turns = 4;
if (g_api_ctx.cfg && g_api_ctx.cfg->tools.api_default_max_turns > 0) {
max_turns = g_api_ctx.cfg->tools.api_default_max_turns;
}
cJSON* max_turns_node = cJSON_GetObjectItemCaseSensitive(body, "max_turns");
if (max_turns_node && cJSON_IsNumber(max_turns_node)) {
max_turns = (int)max_turns_node->valuedouble;

View File

@@ -284,6 +284,12 @@ int main(int argc, char** argv) {
}
DEBUG_INFO("[didactyl] startup phase: subscribe admin context end");
DEBUG_INFO("[didactyl] startup phase: subscribe agent self context begin");
if (nostr_handler_subscribe_agent_context() != 0) {
DEBUG_WARN("[didactyl] startup phase: subscribe agent self context failed (continuing)");
}
DEBUG_INFO("[didactyl] startup phase: subscribe agent self context end");
DEBUG_INFO("[didactyl] startup phase: subscribe self skill cache begin");
if (nostr_handler_subscribe_self_skills() != 0) {
DEBUG_WARN("[didactyl] startup phase: subscribe self skill cache failed (continuing)");

View File

@@ -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 66
#define DIDACTYL_VERSION "v0.0.66"
#define DIDACTYL_VERSION_PATCH 67
#define DIDACTYL_VERSION "v0.0.67"
// Agent metadata
#define DIDACTYL_NAME "Didactyl"

View File

@@ -42,6 +42,13 @@ typedef struct {
static admin_kind1_note_t* g_admin_kind1_notes = NULL;
static int g_admin_kind1_note_count = 0;
static char* g_agent_kind0_json = NULL;
static char* g_agent_kind10002_json = NULL;
static char** g_agent_contacts = NULL;
static int g_agent_contact_count = 0;
static admin_kind1_note_t* g_agent_kind1_notes = NULL;
static int g_agent_kind1_note_count = 0;
static pthread_mutex_t g_admin_ctx_mutex = PTHREAD_MUTEX_INITIALIZER;
static cJSON* g_self_skill_events = NULL;
@@ -269,6 +276,11 @@ static void on_admin_context_event(cJSON* event, const char* relay_url, void* us
static int parse_kind3_wot_contacts(cJSON* tags);
static int parse_kind10002_relays(cJSON* tags);
static void upsert_kind1_note(time_t created_at, const char* content);
static void free_agent_context_locked(void);
static int parse_kind3_agent_contacts(cJSON* tags);
static int parse_kind10002_agent_relays(cJSON* tags);
static void upsert_agent_kind1_note(time_t created_at, const char* content);
static void on_agent_context_event(cJSON* event, const char* relay_url, void* user_data);
static int startup_self_kind1_exists(void);
static void load_startup_display_name(void);
static void build_startup_kind1_content(char* out, size_t out_size, const char* fallback);
@@ -1114,6 +1126,31 @@ static void free_admin_context_locked(void) {
g_admin_kind1_note_count = 0;
}
static void free_agent_context_locked(void) {
free(g_agent_kind0_json);
g_agent_kind0_json = NULL;
free(g_agent_kind10002_json);
g_agent_kind10002_json = NULL;
if (g_agent_contacts) {
for (int i = 0; i < g_agent_contact_count; i++) {
free(g_agent_contacts[i]);
}
free(g_agent_contacts);
}
g_agent_contacts = NULL;
g_agent_contact_count = 0;
if (g_agent_kind1_notes) {
for (int i = 0; i < g_agent_kind1_note_count; i++) {
free(g_agent_kind1_notes[i].content);
}
free(g_agent_kind1_notes);
}
g_agent_kind1_notes = NULL;
g_agent_kind1_note_count = 0;
}
static int parse_kind3_wot_contacts(cJSON* tags) {
if (!tags || !cJSON_IsArray(tags)) {
return 0;
@@ -1242,6 +1279,134 @@ static void upsert_kind1_note(time_t created_at, const char* content) {
}
}
static int parse_kind3_agent_contacts(cJSON* tags) {
if (!tags || !cJSON_IsArray(tags)) {
return 0;
}
if (g_agent_contacts) {
for (int i = 0; i < g_agent_contact_count; i++) {
free(g_agent_contacts[i]);
}
free(g_agent_contacts);
g_agent_contacts = NULL;
g_agent_contact_count = 0;
}
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) || !key->valuestring || !val->valuestring) {
continue;
}
if (strcmp(key->valuestring, "p") != 0 || strlen(val->valuestring) != 64U) {
continue;
}
char* dup = strdup(val->valuestring);
if (!dup) {
return -1;
}
char** grown = (char**)realloc(g_agent_contacts, (size_t)(g_agent_contact_count + 1) * sizeof(char*));
if (!grown) {
free(dup);
return -1;
}
g_agent_contacts = grown;
g_agent_contacts[g_agent_contact_count++] = dup;
}
return 0;
}
static int parse_kind10002_agent_relays(cJSON* tags) {
if (!tags || !cJSON_IsArray(tags)) {
free(g_agent_kind10002_json);
g_agent_kind10002_json = strdup("[]");
return g_agent_kind10002_json ? 0 : -1;
}
cJSON* relays = cJSON_CreateArray();
if (!relays) {
return -1;
}
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) || !key->valuestring || !val->valuestring) {
continue;
}
if (strcmp(key->valuestring, "r") != 0 || val->valuestring[0] == '\0') {
continue;
}
cJSON_AddItemToArray(relays, cJSON_CreateString(val->valuestring));
}
char* relays_json = cJSON_PrintUnformatted(relays);
cJSON_Delete(relays);
if (!relays_json) {
return -1;
}
free(g_agent_kind10002_json);
g_agent_kind10002_json = relays_json;
return 0;
}
static void upsert_agent_kind1_note(time_t created_at, const char* content) {
if (!content) {
return;
}
int limit = g_cfg && g_cfg->admin_context.kind_1_limit > 0 ? g_cfg->admin_context.kind_1_limit : 10;
if (limit > 256) {
limit = 256;
}
char* dup = strdup(content);
if (!dup) {
return;
}
admin_kind1_note_t* grown = (admin_kind1_note_t*)realloc(g_agent_kind1_notes,
(size_t)(g_agent_kind1_note_count + 1) * sizeof(admin_kind1_note_t));
if (!grown) {
free(dup);
return;
}
g_agent_kind1_notes = grown;
g_agent_kind1_notes[g_agent_kind1_note_count].created_at = created_at;
g_agent_kind1_notes[g_agent_kind1_note_count].content = dup;
g_agent_kind1_note_count++;
while (g_agent_kind1_note_count > limit) {
free(g_agent_kind1_notes[0].content);
memmove(&g_agent_kind1_notes[0],
&g_agent_kind1_notes[1],
(size_t)(g_agent_kind1_note_count - 1) * sizeof(admin_kind1_note_t));
g_agent_kind1_note_count--;
}
}
static void on_admin_context_event(cJSON* event, const char* relay_url, void* user_data) {
(void)relay_url;
(void)user_data;
@@ -1287,6 +1452,51 @@ static void on_admin_context_event(cJSON* event, const char* relay_url, void* us
pthread_mutex_unlock(&g_admin_ctx_mutex);
}
static void on_agent_context_event(cJSON* event, const char* relay_url, void* user_data) {
(void)relay_url;
(void)user_data;
if (!event || !g_cfg) {
return;
}
if (g_cfg->security.verify_signatures && nostr_verify_event_signature(event) != 0) {
return;
}
cJSON* kind = cJSON_GetObjectItemCaseSensitive(event, "kind");
cJSON* pubkey = cJSON_GetObjectItemCaseSensitive(event, "pubkey");
cJSON* content = cJSON_GetObjectItemCaseSensitive(event, "content");
cJSON* tags = cJSON_GetObjectItemCaseSensitive(event, "tags");
cJSON* created_at = cJSON_GetObjectItemCaseSensitive(event, "created_at");
if (!kind || !pubkey || !cJSON_IsNumber(kind) || !cJSON_IsString(pubkey) || !pubkey->valuestring) {
return;
}
if (strcmp(pubkey->valuestring, g_cfg->keys.public_key_hex) != 0) {
return;
}
int k = (int)kind->valuedouble;
pthread_mutex_lock(&g_admin_ctx_mutex);
if (k == 0 && content && cJSON_IsString(content) && content->valuestring) {
free(g_agent_kind0_json);
g_agent_kind0_json = strdup(content->valuestring);
} else if (k == 3 && tags && cJSON_IsArray(tags)) {
(void)parse_kind3_agent_contacts(tags);
} else if (k == 10002 && tags && cJSON_IsArray(tags)) {
(void)parse_kind10002_agent_relays(tags);
} else if (k == 1 && content && cJSON_IsString(content) && content->valuestring) {
time_t ts = (created_at && cJSON_IsNumber(created_at)) ? (time_t)created_at->valuedouble : time(NULL);
upsert_agent_kind1_note(ts, content->valuestring);
}
pthread_mutex_unlock(&g_admin_ctx_mutex);
}
static void on_self_skill_event(cJSON* event, const char* relay_url, void* user_data) {
(void)relay_url;
(void)user_data;
@@ -1469,6 +1679,96 @@ int nostr_handler_subscribe_admin_context(void) {
return rc;
}
int nostr_handler_subscribe_agent_context(void) {
if (!g_cfg || !g_pool) {
return -1;
}
int rc = 0;
cJSON* profile_filter = cJSON_CreateObject();
cJSON* profile_kinds = cJSON_CreateArray();
cJSON* profile_authors = cJSON_CreateArray();
if (!profile_filter || !profile_kinds || !profile_authors) {
cJSON_Delete(profile_filter);
cJSON_Delete(profile_kinds);
cJSON_Delete(profile_authors);
return -1;
}
cJSON_AddItemToArray(profile_kinds, cJSON_CreateNumber(0));
cJSON_AddItemToArray(profile_kinds, cJSON_CreateNumber(3));
cJSON_AddItemToArray(profile_kinds, cJSON_CreateNumber(10002));
cJSON_AddItemToObject(profile_filter, "kinds", profile_kinds);
cJSON_AddItemToArray(profile_authors, cJSON_CreateString(g_cfg->keys.public_key_hex));
cJSON_AddItemToObject(profile_filter, "authors", profile_authors);
cJSON_AddNumberToObject(profile_filter, "limit", 64);
nostr_pool_subscription_t* profile_sub = nostr_relay_pool_subscribe(
g_pool,
(const char**)g_cfg->relays,
g_cfg->relay_count,
profile_filter,
on_agent_context_event,
on_eose,
NULL,
0,
1,
NOSTR_POOL_EOSE_FULL_SET,
30,
120);
cJSON_Delete(profile_filter);
if (!profile_sub) {
rc = -1;
}
cJSON* notes_filter = cJSON_CreateObject();
cJSON* notes_kinds = cJSON_CreateArray();
cJSON* notes_authors = cJSON_CreateArray();
if (!notes_filter || !notes_kinds || !notes_authors) {
cJSON_Delete(notes_filter);
cJSON_Delete(notes_kinds);
cJSON_Delete(notes_authors);
return -1;
}
int kind1_limit = g_cfg->admin_context.kind_1_limit > 0 ? g_cfg->admin_context.kind_1_limit : 10;
if (kind1_limit > 256) {
kind1_limit = 256;
}
cJSON_AddItemToArray(notes_kinds, cJSON_CreateNumber(1));
cJSON_AddItemToObject(notes_filter, "kinds", notes_kinds);
cJSON_AddItemToArray(notes_authors, cJSON_CreateString(g_cfg->keys.public_key_hex));
cJSON_AddItemToObject(notes_filter, "authors", notes_authors);
cJSON_AddNumberToObject(notes_filter, "limit", kind1_limit);
nostr_pool_subscription_t* notes_sub = nostr_relay_pool_subscribe(
g_pool,
(const char**)g_cfg->relays,
g_cfg->relay_count,
notes_filter,
on_agent_context_event,
on_eose,
NULL,
0,
1,
NOSTR_POOL_EOSE_FULL_SET,
30,
120);
cJSON_Delete(notes_filter);
if (!notes_sub) {
rc = -1;
}
if (rc == 0) {
DEBUG_INFO("[didactyl] agent self-context subscriptions active for pubkey %.16s...", g_cfg->keys.public_key_hex);
}
return rc;
}
void nostr_handler_set_trigger_manager(struct trigger_manager* trigger_manager) {
g_trigger_manager = trigger_manager;
}
@@ -2603,6 +2903,89 @@ char* nostr_handler_get_admin_kind1_notes_context(void) {
return out;
}
char* nostr_handler_get_agent_kind0_context(void) {
if (!g_cfg) {
return NULL;
}
pthread_mutex_lock(&g_admin_ctx_mutex);
char* out = g_agent_kind0_json ? strdup(g_agent_kind0_json) : NULL;
pthread_mutex_unlock(&g_admin_ctx_mutex);
return out;
}
char* nostr_handler_get_agent_kind3_context(void) {
if (!g_cfg) {
return NULL;
}
pthread_mutex_lock(&g_admin_ctx_mutex);
cJSON* out = cJSON_CreateArray();
if (!out) {
pthread_mutex_unlock(&g_admin_ctx_mutex);
return NULL;
}
for (int i = 0; i < g_agent_contact_count; i++) {
const char* pk = g_agent_contacts[i];
if (!pk || pk[0] == '\0') continue;
cJSON_AddItemToArray(out, cJSON_CreateString(pk));
}
pthread_mutex_unlock(&g_admin_ctx_mutex);
char* out_json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return out_json;
}
char* nostr_handler_get_agent_kind10002_context(void) {
if (!g_cfg) {
return NULL;
}
pthread_mutex_lock(&g_admin_ctx_mutex);
char* out = g_agent_kind10002_json ? strdup(g_agent_kind10002_json) : NULL;
pthread_mutex_unlock(&g_admin_ctx_mutex);
return out;
}
char* nostr_handler_get_agent_kind1_notes_context(void) {
if (!g_cfg) {
return NULL;
}
pthread_mutex_lock(&g_admin_ctx_mutex);
if (g_agent_kind1_note_count <= 0 || !g_agent_kind1_notes) {
pthread_mutex_unlock(&g_admin_ctx_mutex);
return NULL;
}
size_t total = strlen("Agent recent public notes:\n") + 1U;
for (int i = 0; i < g_agent_kind1_note_count; i++) {
total += strlen("- ") + strlen(g_agent_kind1_notes[i].content ? g_agent_kind1_notes[i].content : "") + 1U;
}
char* out = (char*)malloc(total);
if (!out) {
pthread_mutex_unlock(&g_admin_ctx_mutex);
return NULL;
}
out[0] = '\0';
strcat(out, "Agent recent public notes:\n");
for (int i = 0; i < g_agent_kind1_note_count; i++) {
strcat(out, "- ");
strcat(out, g_agent_kind1_notes[i].content ? g_agent_kind1_notes[i].content : "");
strcat(out, "\n");
}
pthread_mutex_unlock(&g_admin_ctx_mutex);
return out;
}
int nostr_handler_is_wot_contact(const char* pubkey_hex) {
if (!pubkey_hex || strlen(pubkey_hex) != 64U) {
return 0;
@@ -2695,6 +3078,7 @@ void nostr_handler_cleanup(void) {
pthread_mutex_lock(&g_admin_ctx_mutex);
free_admin_context_locked();
free_agent_context_locked();
pthread_mutex_unlock(&g_admin_ctx_mutex);
pthread_mutex_lock(&g_self_skill_mutex);

View File

@@ -35,6 +35,7 @@ typedef void (*nostr_self_skill_eose_cb_t)(int event_count, void* user_data);
int nostr_handler_init(didactyl_config_t* config);
void nostr_handler_set_trigger_manager(struct trigger_manager* trigger_manager);
int nostr_handler_subscribe_admin_context(void);
int nostr_handler_subscribe_agent_context(void);
int nostr_handler_subscribe_self_skills(void);
char* nostr_handler_get_self_events_by_kind_json(int kind);
void nostr_handler_set_self_skill_eose_callback(nostr_self_skill_eose_cb_t callback, void* user_data);
@@ -66,6 +67,10 @@ char* nostr_handler_get_admin_kind0_context(void);
char* nostr_handler_get_admin_kind3_context(void);
char* nostr_handler_get_admin_kind10002_context(void);
char* nostr_handler_get_admin_kind1_notes_context(void);
char* nostr_handler_get_agent_kind0_context(void);
char* nostr_handler_get_agent_kind3_context(void);
char* nostr_handler_get_agent_kind10002_context(void);
char* nostr_handler_get_agent_kind1_notes_context(void);
int nostr_handler_is_wot_contact(const char* pubkey_hex);
char* nostr_handler_relay_status_json(void);
char* nostr_handler_relay_info_json(const char* relay_url);

View File

@@ -3,10 +3,12 @@
#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) {
@@ -76,6 +78,162 @@ char* execute_agent_identity(tools_context_t* ctx, const char* args_json) {
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, "agent_kind0_json", profile_json);
size_t content_len = strlen("## Agent Kind 0 Profile (source: nostr kind 0)\n\nAgent kind 0 profile content (JSON): ") + strlen(profile_json) + 1U;
char* content = (char*)malloc(content_len);
if (!content) {
cJSON_Delete(out);
free(kind0);
return NULL;
}
snprintf(content,
content_len,
"## Agent Kind 0 Profile (source: nostr kind 0)\n\nAgent kind 0 profile content (JSON): %s",
profile_json);
cJSON_AddStringToObject(out, "content", content);
free(content);
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, "agent_kind3_json", contacts_json);
size_t content_len = strlen("## Agent Kind 3 Contacts (source: nostr kind 3)\n\nAgent contacts (JSON): ") + strlen(contacts_json) + 1U;
char* content = (char*)malloc(content_len);
if (!content) {
cJSON_Delete(out);
free(kind3);
return NULL;
}
snprintf(content,
content_len,
"## Agent Kind 3 Contacts (source: nostr kind 3)\n\nAgent contacts (JSON): %s",
contacts_json);
cJSON_AddStringToObject(out, "content", content);
free(content);
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, "agent_kind10002_json", relays_json);
size_t content_len = strlen("## Agent Kind 10002 Relays (source: nostr kind 10002)\n\nAgent relay list (JSON): ") + strlen(relays_json) + 1U;
char* content = (char*)malloc(content_len);
if (!content) {
cJSON_Delete(out);
free(kind10002);
return NULL;
}
snprintf(content,
content_len,
"## Agent Kind 10002 Relays (source: nostr kind 10002)\n\nAgent relay list (JSON): %s",
relays_json);
cJSON_AddStringToObject(out, "content", content);
free(content);
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, "agent_notes_content", notes_text);
size_t content_len = strlen("## Agent Recent Kind 1 Notes\n\n") + strlen(notes_text) + 1U;
char* content = (char*)malloc(content_len);
if (!content) {
cJSON_Delete(out);
free(notes);
return NULL;
}
snprintf(content,
content_len,
"## Agent Recent Kind 1 Notes\n\n%s",
notes_text);
cJSON_AddStringToObject(out, "content", content);
free(content);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
free(notes);
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");

View File

@@ -194,9 +194,19 @@ char* execute_local_http_fetch(tools_context_t* ctx, const char* args_json) {
return json_error_local("local_http_fetch GET requests cannot include body");
}
int timeout_seconds = (timeout && cJSON_IsNumber(timeout)) ? (int)timeout->valuedouble : 20;
if (timeout_seconds <= 0) timeout_seconds = 20;
if (timeout_seconds > 120) timeout_seconds = 120;
int default_timeout = ctx->cfg->tools.local_http_fetch_default_timeout_seconds > 0
? ctx->cfg->tools.local_http_fetch_default_timeout_seconds
: 20;
int max_timeout = ctx->cfg->tools.local_http_fetch_max_timeout_seconds > 0
? ctx->cfg->tools.local_http_fetch_max_timeout_seconds
: 120;
if (default_timeout > max_timeout) {
default_timeout = max_timeout;
}
int timeout_seconds = (timeout && cJSON_IsNumber(timeout)) ? (int)timeout->valuedouble : default_timeout;
if (timeout_seconds <= 0) timeout_seconds = default_timeout;
if (timeout_seconds > max_timeout) timeout_seconds = max_timeout;
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;

View File

@@ -174,6 +174,30 @@ char* tools_execute_legacy(tools_context_t* ctx, const char* tool_name, const ch
if (strcmp(tool_name, "agent_identity") == 0) {
return execute_agent_identity(ctx, args_json);
}
if (strcmp(tool_name, "nostr_agent_profile") == 0) {
return execute_nostr_agent_profile(ctx, args_json);
}
if (strcmp(tool_name, "nostr_agent_contacts") == 0) {
return execute_nostr_agent_contacts(ctx, args_json);
}
if (strcmp(tool_name, "nostr_agent_relays") == 0) {
return execute_nostr_agent_relays(ctx, args_json);
}
if (strcmp(tool_name, "nostr_agent_notes") == 0) {
return execute_nostr_agent_notes(ctx, args_json);
}
if (strcmp(tool_name, "my_kind0_profile") == 0) {
return execute_nostr_agent_profile(ctx, args_json);
}
if (strcmp(tool_name, "my_contacts") == 0) {
return execute_nostr_agent_contacts(ctx, args_json);
}
if (strcmp(tool_name, "my_relays") == 0) {
return execute_nostr_agent_relays(ctx, args_json);
}
if (strcmp(tool_name, "my_notes") == 0) {
return execute_nostr_agent_notes(ctx, args_json);
}
return json_error("unknown tool");
}

View File

@@ -13,6 +13,10 @@ char* execute_trigger_list(tools_context_t* ctx, const char* args_json);
char* execute_message_current(tools_context_t* ctx, const char* args_json);
char* execute_agent_identity(tools_context_t* ctx, const char* args_json);
char* execute_agent_version(const char* args_json);
char* execute_nostr_agent_profile(tools_context_t* ctx, const char* args_json);
char* execute_nostr_agent_contacts(tools_context_t* ctx, const char* args_json);
char* execute_nostr_agent_relays(tools_context_t* ctx, const char* args_json);
char* execute_nostr_agent_notes(tools_context_t* ctx, const char* args_json);
char* execute_model_get(const char* args_json);
char* execute_model_set(tools_context_t* ctx, const char* args_json);
char* execute_model_list(const char* args_json);

View File

@@ -1066,6 +1066,118 @@ char* tools_build_openai_schema_json_legacy(const tools_context_t* ctx) {
cJSON_AddItemToObject(t41, "function", t41_fn);
cJSON_AddItemToArray(tools, t41);
cJSON* t41b = cJSON_CreateObject();
cJSON* t41b_fn = cJSON_CreateObject();
cJSON* t41b_params = cJSON_CreateObject();
cJSON* t41b_props = cJSON_CreateObject();
cJSON_AddStringToObject(t41b, "type", "function");
cJSON_AddStringToObject(t41b_fn, "name", "nostr_agent_profile");
cJSON_AddStringToObject(t41b_fn, "description", "Build agent profile context block from cached kind 0 metadata");
cJSON_AddStringToObject(t41b_params, "type", "object");
cJSON_AddItemToObject(t41b_params, "properties", t41b_props);
cJSON_AddItemToObject(t41b_fn, "parameters", t41b_params);
cJSON_AddItemToObject(t41b, "function", t41b_fn);
cJSON_AddItemToArray(tools, t41b);
cJSON* t41c = cJSON_CreateObject();
cJSON* t41c_fn = cJSON_CreateObject();
cJSON* t41c_params = cJSON_CreateObject();
cJSON* t41c_props = cJSON_CreateObject();
cJSON_AddStringToObject(t41c, "type", "function");
cJSON_AddStringToObject(t41c_fn, "name", "nostr_agent_contacts");
cJSON_AddStringToObject(t41c_fn, "description", "Build agent contacts context block from cached kind 3 contact list");
cJSON_AddStringToObject(t41c_params, "type", "object");
cJSON_AddItemToObject(t41c_params, "properties", t41c_props);
cJSON_AddItemToObject(t41c_fn, "parameters", t41c_params);
cJSON_AddItemToObject(t41c, "function", t41c_fn);
cJSON_AddItemToArray(tools, t41c);
cJSON* t41d = cJSON_CreateObject();
cJSON* t41d_fn = cJSON_CreateObject();
cJSON* t41d_params = cJSON_CreateObject();
cJSON* t41d_props = cJSON_CreateObject();
cJSON_AddStringToObject(t41d, "type", "function");
cJSON_AddStringToObject(t41d_fn, "name", "nostr_agent_relays");
cJSON_AddStringToObject(t41d_fn, "description", "Build agent relay context block from cached kind 10002 data");
cJSON_AddStringToObject(t41d_params, "type", "object");
cJSON_AddItemToObject(t41d_params, "properties", t41d_props);
cJSON_AddItemToObject(t41d_fn, "parameters", t41d_params);
cJSON_AddItemToObject(t41d, "function", t41d_fn);
cJSON_AddItemToArray(tools, t41d);
cJSON* t41e = cJSON_CreateObject();
cJSON* t41e_fn = cJSON_CreateObject();
cJSON* t41e_params = cJSON_CreateObject();
cJSON* t41e_props = cJSON_CreateObject();
cJSON_AddStringToObject(t41e, "type", "function");
cJSON_AddStringToObject(t41e_fn, "name", "nostr_agent_notes");
cJSON_AddStringToObject(t41e_fn, "description", "Build agent notes context block from cached kind 1 notes");
cJSON_AddStringToObject(t41e_params, "type", "object");
cJSON_AddItemToObject(t41e_params, "properties", t41e_props);
cJSON_AddItemToObject(t41e_fn, "parameters", t41e_params);
cJSON_AddItemToObject(t41e, "function", t41e_fn);
cJSON_AddItemToArray(tools, t41e);
cJSON* t41f = cJSON_CreateObject();
cJSON* t41f_fn = cJSON_CreateObject();
cJSON* t41f_params = cJSON_CreateObject();
cJSON* t41f_props = cJSON_CreateObject();
cJSON_AddStringToObject(t41f, "type", "function");
cJSON_AddStringToObject(t41f_fn, "name", "my_kind0_profile");
cJSON_AddStringToObject(t41f_fn, "description", "Alias for nostr_agent_profile: return this agent's kind 0 profile context");
cJSON_AddStringToObject(t41f_params, "type", "object");
cJSON_AddItemToObject(t41f_params, "properties", t41f_props);
cJSON_AddItemToObject(t41f_fn, "parameters", t41f_params);
cJSON_AddItemToObject(t41f, "function", t41f_fn);
cJSON_AddItemToArray(tools, t41f);
cJSON* t41g = cJSON_CreateObject();
cJSON* t41g_fn = cJSON_CreateObject();
cJSON* t41g_params = cJSON_CreateObject();
cJSON* t41g_props = cJSON_CreateObject();
cJSON_AddStringToObject(t41g, "type", "function");
cJSON_AddStringToObject(t41g_fn, "name", "my_contacts");
cJSON_AddStringToObject(t41g_fn, "description", "Alias for nostr_agent_contacts: return this agent's kind 3 contacts context");
cJSON_AddStringToObject(t41g_params, "type", "object");
cJSON_AddItemToObject(t41g_params, "properties", t41g_props);
cJSON_AddItemToObject(t41g_fn, "parameters", t41g_params);
cJSON_AddItemToObject(t41g, "function", t41g_fn);
cJSON_AddItemToArray(tools, t41g);
cJSON* t41h = cJSON_CreateObject();
cJSON* t41h_fn = cJSON_CreateObject();
cJSON* t41h_params = cJSON_CreateObject();
cJSON* t41h_props = cJSON_CreateObject();
cJSON_AddStringToObject(t41h, "type", "function");
cJSON_AddStringToObject(t41h_fn, "name", "my_relays");
cJSON_AddStringToObject(t41h_fn, "description", "Alias for nostr_agent_relays: return this agent's kind 10002 relay context");
cJSON_AddStringToObject(t41h_params, "type", "object");
cJSON_AddItemToObject(t41h_params, "properties", t41h_props);
cJSON_AddItemToObject(t41h_fn, "parameters", t41h_params);
cJSON_AddItemToObject(t41h, "function", t41h_fn);
cJSON_AddItemToArray(tools, t41h);
cJSON* t41i = cJSON_CreateObject();
cJSON* t41i_fn = cJSON_CreateObject();
cJSON* t41i_params = cJSON_CreateObject();
cJSON* t41i_props = cJSON_CreateObject();
cJSON_AddStringToObject(t41i, "type", "function");
cJSON_AddStringToObject(t41i_fn, "name", "my_notes");
cJSON_AddStringToObject(t41i_fn, "description", "Alias for nostr_agent_notes: return this agent's recent kind 1 notes context");
cJSON_AddStringToObject(t41i_params, "type", "object");
cJSON_AddItemToObject(t41i_params, "properties", t41i_props);
cJSON_AddItemToObject(t41i_fn, "parameters", t41i_params);
cJSON_AddItemToObject(t41i, "function", t41i_fn);
cJSON_AddItemToArray(tools, t41i);
cJSON* t42 = cJSON_CreateObject();
cJSON* t42_fn = cJSON_CreateObject();
cJSON* t42_params = cJSON_CreateObject();