v0.0.67 - Add agent self-context subscriptions, tools, aliases, and prompt template sections
This commit is contained in:
@@ -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.66 — Complete tools refactor, move all tools-named sources/headers into src/tools, and update build wiring
|
||||
> Last release update: v0.0.67 — Add 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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
10355
context.log.md
10355
context.log.md
File diff suppressed because one or more lines are too long
@@ -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
204
plans/agent_self_context.md
Normal 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.
|
||||
@@ -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);
|
||||
|
||||
54
src/config.c
54
src/config.c
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)");
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user