v0.0.71 - Add nprofile, nevent, and naddr support to nostr_encode with argument validation
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.
|
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.70
|
## Current Status — v0.0.71
|
||||||
|
|
||||||
**Active build — this project is barely working. Experiment at your own risk.**
|
**Active build — this project is barely working. Experiment at your own risk.**
|
||||||
|
|
||||||
> Last release update: v0.0.70 — Add c_utils_lib category-based debug filtering with tests/docs and root API log tail shortcut script
|
> Last release update: v0.0.71 — Add nprofile, nevent, and naddr support to nostr_encode with argument validation
|
||||||
|
|
||||||
- Connects to configured relays with auto-reconnect and relay state transition logging
|
- Connects to configured relays with auto-reconnect and relay state transition logging
|
||||||
- Publishes configured startup events per relay as each relay becomes connected
|
- Publishes configured startup events per relay as each relay becomes connected
|
||||||
|
|||||||
+9387
File diff suppressed because one or more lines are too long
+4194
File diff suppressed because one or more lines are too long
@@ -0,0 +1,81 @@
|
|||||||
|
# Didactyl Integration Plan: Unified `nostr_core_lib` Logging into One File
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Route `nostr_core_lib` logs through didactyl logging so there is one authoritative file for errors, using existing didactyl logging in [`debug.c`](../src/debug.c:1) plus the new callback API in [`nostr_log.h`](../nostr_core_lib/nostr_core/nostr_log.h:1).
|
||||||
|
|
||||||
|
## Current State
|
||||||
|
|
||||||
|
- Didactyl writes logs to path from `DIDACTYL_LOG_FILE` or default `debug.log` in [`debug_open_log_file`](../src/debug.c:10).
|
||||||
|
- Didactyl log emission is centralized in [`debug_log`](../src/debug.c:31).
|
||||||
|
- Relay pool setup occurs in [`nostr_handler_init`](../src/nostr_handler.c:1519).
|
||||||
|
- `nostr_core_lib` now exposes callback logging API in [`nostr_log.h`](../nostr_core_lib/nostr_core/nostr_log.h:1).
|
||||||
|
|
||||||
|
## Desired End State
|
||||||
|
|
||||||
|
- `nostr_core_lib` logs are forwarded into didactyl logger via callback registration.
|
||||||
|
- One file path configured by `DIDACTYL_LOG_FILE` receives didactyl plus nostr_core_lib errors.
|
||||||
|
- Log lines preserve source component tags such as `nostr:websocket` and `nostr:nip013`.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A[nostr_core_lib emits callback log] --> B[didactyl bridge callback in nostr_handler.c]
|
||||||
|
B --> C[map nostr level to didactyl level]
|
||||||
|
C --> D[debug_log in debug.c]
|
||||||
|
D --> E[DIDACTYL_LOG_FILE single destination]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Steps
|
||||||
|
|
||||||
|
1. Add bridge function in [`nostr_handler.c`](../src/nostr_handler.c:1519)
|
||||||
|
- Create static callback `nostr_core_log_bridge level component message user_data`.
|
||||||
|
- Prefix forwarded lines with `nostr:` namespace.
|
||||||
|
|
||||||
|
2. Map levels from `nostr_core_lib` to didactyl
|
||||||
|
- `NOSTR_LOG_LEVEL_ERROR` to `DEBUG_LEVEL_ERROR`
|
||||||
|
- `NOSTR_LOG_LEVEL_WARN` to `DEBUG_LEVEL_WARN`
|
||||||
|
- `NOSTR_LOG_LEVEL_INFO` to `DEBUG_LEVEL_INFO`
|
||||||
|
- `NOSTR_LOG_LEVEL_DEBUG` to `DEBUG_LEVEL_DEBUG`
|
||||||
|
- `NOSTR_LOG_LEVEL_TRACE` to `DEBUG_LEVEL_TRACE`
|
||||||
|
|
||||||
|
3. Register callback during startup in [`nostr_handler_init`](../src/nostr_handler.c:1519)
|
||||||
|
- Before relay setup, call `nostr_set_log_callback`.
|
||||||
|
- Set threshold with `nostr_set_log_level` aligned to current didactyl debug level.
|
||||||
|
|
||||||
|
4. Enforce single-file policy
|
||||||
|
- Set `DIDACTYL_LOG_FILE` to desired final log path.
|
||||||
|
- Keep didactyl as only file owner via [`debug_open_log_file`](../src/debug.c:10).
|
||||||
|
|
||||||
|
5. Ensure error visibility
|
||||||
|
- Keep bridge forwarding at least error and warn levels even when app runs at lower verbosity.
|
||||||
|
- Optionally pin library threshold to `WARN` or `ERROR` for production profiles.
|
||||||
|
|
||||||
|
## Validation Plan
|
||||||
|
|
||||||
|
1. Start didactyl with explicit file path
|
||||||
|
- Example environment: `DIDACTYL_LOG_FILE=didactyl.log`.
|
||||||
|
|
||||||
|
2. Trigger nostr websocket activity
|
||||||
|
- Confirm `nostr:websocket` entries appear in same file.
|
||||||
|
|
||||||
|
3. Trigger nip013 path
|
||||||
|
- Confirm `nostr:nip013` entries appear in same file.
|
||||||
|
|
||||||
|
4. Trigger normal didactyl LLM call
|
||||||
|
- Confirm `[didactyl] llm request` still lands in same file.
|
||||||
|
|
||||||
|
5. Verify there is no second active file for nostr core logs.
|
||||||
|
|
||||||
|
## Rollout Notes
|
||||||
|
|
||||||
|
- Implement bridge first, then verify in development run.
|
||||||
|
- If log volume is too high, reduce callback threshold by setting `nostr_set_log_level`.
|
||||||
|
- Keep one destination file operationally stable by configuring and documenting `DIDACTYL_LOG_FILE`.
|
||||||
|
|
||||||
|
## Files to Change in Code Mode
|
||||||
|
|
||||||
|
- [`src/nostr_handler.c`](../src/nostr_handler.c)
|
||||||
|
- Optional docs update in [`README.md`](../README.md)
|
||||||
|
- Optional operational note in [`docs/TOOLS.md`](../docs/TOOLS.md)
|
||||||
+34
@@ -1,14 +1,31 @@
|
|||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
|
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
debug_level_t g_debug_level = DEBUG_LEVEL_NONE;
|
debug_level_t g_debug_level = DEBUG_LEVEL_NONE;
|
||||||
|
static FILE* g_debug_file = NULL;
|
||||||
|
|
||||||
|
static void debug_open_log_file(void) {
|
||||||
|
if (g_debug_file) return;
|
||||||
|
|
||||||
|
const char* path = getenv("DIDACTYL_LOG_FILE");
|
||||||
|
if (!path || path[0] == '\0') {
|
||||||
|
path = "debug.log";
|
||||||
|
}
|
||||||
|
|
||||||
|
g_debug_file = fopen(path, "a");
|
||||||
|
if (g_debug_file) {
|
||||||
|
setvbuf(g_debug_file, NULL, _IOLBF, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void debug_init(int level) {
|
void debug_init(int level) {
|
||||||
if (level < 0) level = 0;
|
if (level < 0) level = 0;
|
||||||
if (level > 5) level = 5;
|
if (level > 5) level = 5;
|
||||||
g_debug_level = (debug_level_t)level;
|
g_debug_level = (debug_level_t)level;
|
||||||
|
debug_open_log_file();
|
||||||
}
|
}
|
||||||
|
|
||||||
void debug_log(debug_level_t level, const char* file, int line, const char* format, ...) {
|
void debug_log(debug_level_t level, const char* file, int line, const char* format, ...) {
|
||||||
@@ -28,11 +45,17 @@ void debug_log(debug_level_t level, const char* file, int line, const char* form
|
|||||||
}
|
}
|
||||||
|
|
||||||
printf("[%s] [%s] ", timestamp, level_str);
|
printf("[%s] [%s] ", timestamp, level_str);
|
||||||
|
if (g_debug_file) {
|
||||||
|
fprintf(g_debug_file, "[%s] [%s] ", timestamp, level_str);
|
||||||
|
}
|
||||||
|
|
||||||
if (file && g_debug_level >= DEBUG_LEVEL_TRACE) {
|
if (file && g_debug_level >= DEBUG_LEVEL_TRACE) {
|
||||||
const char* filename = strrchr(file, '/');
|
const char* filename = strrchr(file, '/');
|
||||||
filename = filename ? filename + 1 : file;
|
filename = filename ? filename + 1 : file;
|
||||||
printf("[%s:%d] ", filename, line);
|
printf("[%s:%d] ", filename, line);
|
||||||
|
if (g_debug_file) {
|
||||||
|
fprintf(g_debug_file, "[%s:%d] ", filename, line);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
va_list args;
|
va_list args;
|
||||||
@@ -40,6 +63,17 @@ void debug_log(debug_level_t level, const char* file, int line, const char* form
|
|||||||
vprintf(format, args);
|
vprintf(format, args);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
|
|
||||||
|
if (g_debug_file) {
|
||||||
|
va_list args_file;
|
||||||
|
va_start(args_file, format);
|
||||||
|
vfprintf(g_debug_file, format, args_file);
|
||||||
|
va_end(args_file);
|
||||||
|
}
|
||||||
|
|
||||||
printf("\n");
|
printf("\n");
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
|
if (g_debug_file) {
|
||||||
|
fprintf(g_debug_file, "\n");
|
||||||
|
fflush(g_debug_file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "cjson/cJSON.h"
|
#include "cjson/cJSON.h"
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char* data;
|
char* data;
|
||||||
@@ -92,11 +93,9 @@ static char* perform_http_request(const char* url, const char* body, int is_post
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (url_looks_like_websocket(url)) {
|
if (url_looks_like_websocket(url)) {
|
||||||
fprintf(stderr,
|
DEBUG_ERROR("[didactyl] llm config error: base_url must be HTTP(S), got WebSocket URL: %s",
|
||||||
"[didactyl] llm config error: base_url must be HTTP(S), got WebSocket URL: %s\n",
|
url);
|
||||||
url);
|
DEBUG_WARN("[didactyl] llm hint: set llm.base_url to an OpenAI-compatible HTTPS endpoint, e.g. https://api.example.com/v1");
|
||||||
fprintf(stderr,
|
|
||||||
"[didactyl] llm hint: set llm.base_url to an OpenAI-compatible HTTPS endpoint, e.g. https://api.example.com/v1\n");
|
|
||||||
curl_easy_cleanup(curl);
|
curl_easy_cleanup(curl);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -129,14 +128,13 @@ static char* perform_http_request(const char* url, const char* body, int is_post
|
|||||||
if (is_post) {
|
if (is_post) {
|
||||||
size_t body_len = body ? strlen(body) : 0U;
|
size_t body_len = body ? strlen(body) : 0U;
|
||||||
size_t body_preview_len = body_len > 4000U ? 4000U : body_len;
|
size_t body_preview_len = body_len > 4000U ? 4000U : body_len;
|
||||||
fprintf(stderr,
|
DEBUG_INFO("[didactyl] llm request: method=POST url=%s body_bytes=%zu body_preview=%.4000s%s",
|
||||||
"[didactyl] llm request: method=POST url=%s body_bytes=%zu body_preview=%.4000s%s\n",
|
url,
|
||||||
url,
|
body_len,
|
||||||
body_len,
|
body ? body : "",
|
||||||
body ? body : "",
|
body_len > body_preview_len ? "..." : "");
|
||||||
body_len > body_preview_len ? "..." : "");
|
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr, "[didactyl] llm request: method=GET url=%s\n", url);
|
DEBUG_INFO("[didactyl] llm request: method=GET url=%s", url);
|
||||||
}
|
}
|
||||||
|
|
||||||
CURLcode res = curl_easy_perform(curl);
|
CURLcode res = curl_easy_perform(curl);
|
||||||
@@ -147,33 +145,32 @@ static char* perform_http_request(const char* url, const char* body, int is_post
|
|||||||
curl_easy_cleanup(curl);
|
curl_easy_cleanup(curl);
|
||||||
|
|
||||||
if (res != CURLE_OK) {
|
if (res != CURLE_OK) {
|
||||||
fprintf(stderr, "[didactyl] llm http request failed: curl=%s\n", curl_easy_strerror(res));
|
DEBUG_ERROR("[didactyl] llm http request failed: curl=%s", curl_easy_strerror(res));
|
||||||
if (rb.data && rb.len > 0) {
|
if (rb.data && rb.len > 0) {
|
||||||
fprintf(stderr, "[didactyl] llm partial response: %.600s%s\n",
|
DEBUG_WARN("[didactyl] llm partial response: %.600s%s",
|
||||||
rb.data,
|
rb.data,
|
||||||
rb.len > 600 ? "..." : "");
|
rb.len > 600 ? "..." : "");
|
||||||
}
|
}
|
||||||
free(rb.data);
|
free(rb.data);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status < 200 || status >= 300) {
|
if (status < 200 || status >= 300) {
|
||||||
fprintf(stderr, "[didactyl] llm http request failed: status=%ld\n", status);
|
DEBUG_ERROR("[didactyl] llm http request failed: status=%ld", status);
|
||||||
if (status == 101) {
|
if (status == 101) {
|
||||||
fprintf(stderr,
|
DEBUG_WARN("[didactyl] llm hint: received HTTP 101 (Switching Protocols), which usually means llm.base_url points to a WebSocket server instead of an HTTP LLM API");
|
||||||
"[didactyl] llm hint: received HTTP 101 (Switching Protocols), which usually means llm.base_url points to a WebSocket server instead of an HTTP LLM API\n");
|
|
||||||
}
|
}
|
||||||
if (rb.data && rb.len > 0) {
|
if (rb.data && rb.len > 0) {
|
||||||
fprintf(stderr, "[didactyl] llm error response: %.1200s%s\n",
|
DEBUG_WARN("[didactyl] llm error response: %.1200s%s",
|
||||||
rb.data,
|
rb.data,
|
||||||
rb.len > 1200 ? "..." : "");
|
rb.len > 1200 ? "..." : "");
|
||||||
}
|
}
|
||||||
free(rb.data);
|
free(rb.data);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!rb.data) {
|
if (!rb.data) {
|
||||||
fprintf(stderr, "[didactyl] llm http request failed: empty response body\n");
|
DEBUG_ERROR("[didactyl] llm http request failed: empty response body");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,9 +329,9 @@ char* llm_chat(const char* system_prompt, const char* user_message) {
|
|||||||
|
|
||||||
llm_response_t parsed;
|
llm_response_t parsed;
|
||||||
if (parse_llm_response(raw, &parsed) != 0) {
|
if (parse_llm_response(raw, &parsed) != 0) {
|
||||||
fprintf(stderr, "[didactyl] failed to parse llm response (non-tool path): %.1200s%s\n",
|
DEBUG_ERROR("[didactyl] failed to parse llm response (non-tool path): %.1200s%s",
|
||||||
raw,
|
raw,
|
||||||
strlen(raw) > 1200 ? "..." : "");
|
strlen(raw) > 1200 ? "..." : "");
|
||||||
free(raw);
|
free(raw);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -393,9 +390,8 @@ int llm_chat_with_tools_messages(const char* messages_json,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (filtered_count > 0) {
|
if (filtered_count > 0) {
|
||||||
fprintf(stderr,
|
DEBUG_INFO("[didactyl] llm request sanitizer: removed %d empty text message(s) before provider request",
|
||||||
"[didactyl] llm request sanitizer: removed %d empty text message(s) before provider request\n",
|
filtered_count);
|
||||||
filtered_count);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cJSON_AddItemToObject(root, "messages", messages);
|
cJSON_AddItemToObject(root, "messages", messages);
|
||||||
@@ -420,9 +416,9 @@ int llm_chat_with_tools_messages(const char* messages_json,
|
|||||||
|
|
||||||
int rc = parse_llm_response(raw, out_response);
|
int rc = parse_llm_response(raw, out_response);
|
||||||
if (rc != 0) {
|
if (rc != 0) {
|
||||||
fprintf(stderr, "[didactyl] failed to parse llm response (tools path): %.1200s%s\n",
|
DEBUG_ERROR("[didactyl] failed to parse llm response (tools path): %.1200s%s",
|
||||||
raw,
|
raw,
|
||||||
strlen(raw) > 1200 ? "..." : "");
|
strlen(raw) > 1200 ? "..." : "");
|
||||||
}
|
}
|
||||||
free(raw);
|
free(raw);
|
||||||
return rc;
|
return rc;
|
||||||
|
|||||||
+2
-2
@@ -12,8 +12,8 @@
|
|||||||
// Using DIDACTYL_ prefix to avoid conflicts with nostr_core_lib VERSION macros
|
// Using DIDACTYL_ prefix to avoid conflicts with nostr_core_lib VERSION macros
|
||||||
#define DIDACTYL_VERSION_MAJOR 0
|
#define DIDACTYL_VERSION_MAJOR 0
|
||||||
#define DIDACTYL_VERSION_MINOR 0
|
#define DIDACTYL_VERSION_MINOR 0
|
||||||
#define DIDACTYL_VERSION_PATCH 70
|
#define DIDACTYL_VERSION_PATCH 71
|
||||||
#define DIDACTYL_VERSION "v0.0.70"
|
#define DIDACTYL_VERSION "v0.0.71"
|
||||||
|
|
||||||
// Agent metadata
|
// Agent metadata
|
||||||
#define DIDACTYL_NAME "Didactyl"
|
#define DIDACTYL_NAME "Didactyl"
|
||||||
|
|||||||
@@ -70,6 +70,58 @@ static pthread_mutex_t g_dm_dedup_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|||||||
#define SENDER_PROTOCOL_CACHE_SIZE 128
|
#define SENDER_PROTOCOL_CACHE_SIZE 128
|
||||||
#define DM_HISTORY_RING_SIZE 256
|
#define DM_HISTORY_RING_SIZE 256
|
||||||
|
|
||||||
|
static int didactyl_debug_to_nostr_log_level(void) {
|
||||||
|
switch (g_debug_level) {
|
||||||
|
case DEBUG_LEVEL_TRACE: return NOSTR_LOG_LEVEL_TRACE;
|
||||||
|
case DEBUG_LEVEL_DEBUG: return NOSTR_LOG_LEVEL_DEBUG;
|
||||||
|
case DEBUG_LEVEL_INFO: return NOSTR_LOG_LEVEL_INFO;
|
||||||
|
case DEBUG_LEVEL_WARN: return NOSTR_LOG_LEVEL_WARN;
|
||||||
|
case DEBUG_LEVEL_ERROR: return NOSTR_LOG_LEVEL_ERROR;
|
||||||
|
case DEBUG_LEVEL_NONE:
|
||||||
|
default:
|
||||||
|
return NOSTR_LOG_LEVEL_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static debug_level_t nostr_log_to_didactyl_level(int level) {
|
||||||
|
switch (level) {
|
||||||
|
case NOSTR_LOG_LEVEL_TRACE: return DEBUG_LEVEL_TRACE;
|
||||||
|
case NOSTR_LOG_LEVEL_DEBUG: return DEBUG_LEVEL_DEBUG;
|
||||||
|
case NOSTR_LOG_LEVEL_INFO: return DEBUG_LEVEL_INFO;
|
||||||
|
case NOSTR_LOG_LEVEL_WARN: return DEBUG_LEVEL_WARN;
|
||||||
|
case NOSTR_LOG_LEVEL_ERROR:
|
||||||
|
default:
|
||||||
|
return DEBUG_LEVEL_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nostr_core_log_bridge(int level, const char* component, const char* message, void* user_data) {
|
||||||
|
(void)user_data;
|
||||||
|
|
||||||
|
debug_level_t mapped = nostr_log_to_didactyl_level(level);
|
||||||
|
const char* comp = (component && component[0] != '\0') ? component : "core";
|
||||||
|
const char* msg = message ? message : "";
|
||||||
|
|
||||||
|
switch (mapped) {
|
||||||
|
case DEBUG_LEVEL_TRACE:
|
||||||
|
DEBUG_TRACE("[didactyl] [nostr:%s] %s", comp, msg);
|
||||||
|
break;
|
||||||
|
case DEBUG_LEVEL_DEBUG:
|
||||||
|
DEBUG_LOG("[didactyl] [nostr:%s] %s", comp, msg);
|
||||||
|
break;
|
||||||
|
case DEBUG_LEVEL_INFO:
|
||||||
|
DEBUG_INFO("[didactyl] [nostr:%s] %s", comp, msg);
|
||||||
|
break;
|
||||||
|
case DEBUG_LEVEL_WARN:
|
||||||
|
DEBUG_WARN("[didactyl] [nostr:%s] %s", comp, msg);
|
||||||
|
break;
|
||||||
|
case DEBUG_LEVEL_ERROR:
|
||||||
|
default:
|
||||||
|
DEBUG_ERROR("[didactyl] [nostr:%s] %s", comp, msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char pubkey_hex[65];
|
char pubkey_hex[65];
|
||||||
dm_protocol_t protocol;
|
dm_protocol_t protocol;
|
||||||
@@ -1534,6 +1586,9 @@ int nostr_handler_init(didactyl_config_t* config) {
|
|||||||
|
|
||||||
DEBUG_INFO("[didactyl] initializing relay pool with %d relays", g_cfg->relay_count);
|
DEBUG_INFO("[didactyl] initializing relay pool with %d relays", g_cfg->relay_count);
|
||||||
|
|
||||||
|
nostr_set_log_callback(nostr_core_log_bridge, NULL);
|
||||||
|
nostr_set_log_level((nostr_log_level_t)didactyl_debug_to_nostr_log_level());
|
||||||
|
|
||||||
nostr_pool_reconnect_config_t reconnect = *nostr_pool_reconnect_config_default();
|
nostr_pool_reconnect_config_t reconnect = *nostr_pool_reconnect_config_default();
|
||||||
reconnect.enable_auto_reconnect = 1;
|
reconnect.enable_auto_reconnect = 1;
|
||||||
reconnect.ping_interval_seconds = 20;
|
reconnect.ping_interval_seconds = 20;
|
||||||
@@ -3050,6 +3105,8 @@ void nostr_handler_cleanup(void) {
|
|||||||
nostr_relay_pool_destroy(g_pool);
|
nostr_relay_pool_destroy(g_pool);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nostr_set_log_callback(NULL, NULL);
|
||||||
|
|
||||||
free(g_last_relay_statuses);
|
free(g_last_relay_statuses);
|
||||||
g_last_relay_statuses = NULL;
|
g_last_relay_statuses = NULL;
|
||||||
free(g_startup_published);
|
free(g_startup_published);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "tools_internal.h"
|
#include "tools_internal.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "cjson/cJSON.h"
|
#include "cjson/cJSON.h"
|
||||||
@@ -47,6 +48,9 @@ char* execute_nostr_encode(const char* args_json) {
|
|||||||
|
|
||||||
cJSON* type = cJSON_GetObjectItemCaseSensitive(args, "type");
|
cJSON* type = cJSON_GetObjectItemCaseSensitive(args, "type");
|
||||||
cJSON* hex = cJSON_GetObjectItemCaseSensitive(args, "hex");
|
cJSON* hex = cJSON_GetObjectItemCaseSensitive(args, "hex");
|
||||||
|
cJSON* relays = cJSON_GetObjectItemCaseSensitive(args, "relays");
|
||||||
|
cJSON* kind = cJSON_GetObjectItemCaseSensitive(args, "kind");
|
||||||
|
cJSON* identifier = cJSON_GetObjectItemCaseSensitive(args, "identifier");
|
||||||
|
|
||||||
if (!type || !cJSON_IsString(type) || !type->valuestring ||
|
if (!type || !cJSON_IsString(type) || !type->valuestring ||
|
||||||
!hex || !cJSON_IsString(hex) || !hex->valuestring || !is_hex_string_len_local(hex->valuestring, 64U)) {
|
!hex || !cJSON_IsString(hex) || !hex->valuestring || !is_hex_string_len_local(hex->valuestring, 64U)) {
|
||||||
@@ -63,28 +67,89 @@ char* execute_nostr_encode(const char* args_json) {
|
|||||||
return json_error_local("nostr_encode invalid hex");
|
return json_error_local("nostr_encode invalid hex");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char** relay_ptrs = NULL;
|
||||||
|
int relay_count = 0;
|
||||||
|
if (relays) {
|
||||||
|
if (!cJSON_IsArray(relays)) {
|
||||||
|
cJSON_Delete(args);
|
||||||
|
return json_error_local("nostr_encode relays must be an array of relay URLs");
|
||||||
|
}
|
||||||
|
relay_count = cJSON_GetArraySize(relays);
|
||||||
|
if (relay_count > 0) {
|
||||||
|
relay_ptrs = (const char**)malloc((size_t)relay_count * sizeof(char*));
|
||||||
|
if (!relay_ptrs) {
|
||||||
|
cJSON_Delete(args);
|
||||||
|
return json_error_local("memory allocation failed");
|
||||||
|
}
|
||||||
|
for (int i = 0; i < relay_count; i++) {
|
||||||
|
cJSON* relay = cJSON_GetArrayItem(relays, i);
|
||||||
|
if (!relay || !cJSON_IsString(relay) || !relay->valuestring || relay->valuestring[0] == '\0') {
|
||||||
|
free(relay_ptrs);
|
||||||
|
cJSON_Delete(args);
|
||||||
|
return json_error_local("nostr_encode relays must contain non-empty strings");
|
||||||
|
}
|
||||||
|
relay_ptrs[i] = relay->valuestring;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
char bech32[256] = {0};
|
char bech32[256] = {0};
|
||||||
|
char uri[1200] = {0};
|
||||||
int rc = NOSTR_ERROR_INVALID_INPUT;
|
int rc = NOSTR_ERROR_INVALID_INPUT;
|
||||||
|
|
||||||
if (strcmp(type_name, "npub") == 0) {
|
if (strcmp(type_name, "npub") == 0) {
|
||||||
rc = nostr_key_to_bech32(key_bytes, "npub", bech32);
|
rc = nostr_key_to_bech32(key_bytes, "npub", bech32);
|
||||||
|
if (rc == NOSTR_SUCCESS) {
|
||||||
|
snprintf(uri, sizeof(uri), "nostr:%s", bech32);
|
||||||
|
}
|
||||||
} else if (strcmp(type_name, "nsec") == 0) {
|
} else if (strcmp(type_name, "nsec") == 0) {
|
||||||
rc = nostr_key_to_bech32(key_bytes, "nsec", bech32);
|
rc = nostr_key_to_bech32(key_bytes, "nsec", bech32);
|
||||||
|
if (rc == NOSTR_SUCCESS) {
|
||||||
|
snprintf(uri, sizeof(uri), "nostr:%s", bech32);
|
||||||
|
}
|
||||||
} else if (strcmp(type_name, "note") == 0) {
|
} else if (strcmp(type_name, "note") == 0) {
|
||||||
rc = nostr_key_to_bech32(key_bytes, "note", bech32);
|
rc = nostr_key_to_bech32(key_bytes, "note", bech32);
|
||||||
|
if (rc == NOSTR_SUCCESS) {
|
||||||
|
snprintf(uri, sizeof(uri), "nostr:%s", bech32);
|
||||||
|
}
|
||||||
|
} else if (strcmp(type_name, "nprofile") == 0) {
|
||||||
|
rc = nostr_build_uri_nprofile(key_bytes, relay_ptrs, relay_count, uri, sizeof(uri));
|
||||||
|
} else if (strcmp(type_name, "nevent") == 0) {
|
||||||
|
int kind_value = -1;
|
||||||
|
if (kind) {
|
||||||
|
if (!cJSON_IsNumber(kind)) {
|
||||||
|
free(relay_ptrs);
|
||||||
|
cJSON_Delete(args);
|
||||||
|
return json_error_local("nostr_encode nevent kind must be an integer");
|
||||||
|
}
|
||||||
|
kind_value = (int)kind->valuedouble;
|
||||||
|
}
|
||||||
|
rc = nostr_build_uri_nevent(key_bytes, relay_ptrs, relay_count, NULL, kind_value, 0, uri, sizeof(uri));
|
||||||
|
} else if (strcmp(type_name, "naddr") == 0) {
|
||||||
|
if (!kind || !cJSON_IsNumber(kind)) {
|
||||||
|
free(relay_ptrs);
|
||||||
|
cJSON_Delete(args);
|
||||||
|
return json_error_local("nostr_encode naddr requires kind integer");
|
||||||
|
}
|
||||||
|
if (!identifier || !cJSON_IsString(identifier) || !identifier->valuestring || identifier->valuestring[0] == '\0') {
|
||||||
|
free(relay_ptrs);
|
||||||
|
cJSON_Delete(args);
|
||||||
|
return json_error_local("nostr_encode naddr requires non-empty identifier string");
|
||||||
|
}
|
||||||
|
rc = nostr_build_uri_naddr(identifier->valuestring, key_bytes, (int)kind->valuedouble,
|
||||||
|
relay_ptrs, relay_count, uri, sizeof(uri));
|
||||||
} else {
|
} else {
|
||||||
|
free(relay_ptrs);
|
||||||
cJSON_Delete(args);
|
cJSON_Delete(args);
|
||||||
return json_error_local("nostr_encode currently supports npub, nsec, note");
|
return json_error_local("nostr_encode currently supports npub, nsec, note, nprofile, nevent, naddr");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
free(relay_ptrs);
|
||||||
cJSON_Delete(args);
|
cJSON_Delete(args);
|
||||||
if (rc != NOSTR_SUCCESS) {
|
if (rc != NOSTR_SUCCESS) {
|
||||||
return json_error_local("nostr_encode failed");
|
return json_error_local("nostr_encode failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
char uri[320] = {0};
|
|
||||||
snprintf(uri, sizeof(uri), "nostr:%s", bech32);
|
|
||||||
|
|
||||||
cJSON* out = cJSON_CreateObject();
|
cJSON* out = cJSON_CreateObject();
|
||||||
if (!out) return NULL;
|
if (!out) return NULL;
|
||||||
cJSON_AddBoolToObject(out, "success", 1);
|
cJSON_AddBoolToObject(out, "success", 1);
|
||||||
|
|||||||
+29
-4
@@ -19,10 +19,35 @@ if [ -z "$LOG_FILE" ] || [ ! -f "$LOG_FILE" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Following API call logs from: $LOG_FILE"
|
echo "Following numbered LLM API calls from: $LOG_FILE"
|
||||||
echo "(Press Ctrl+C to stop)"
|
echo "(Press Ctrl+C to stop)"
|
||||||
|
|
||||||
# Show request/response failure lines related to outbound LLM HTTP calls.
|
# Count only outbound provider calls from src/llm.c:
|
||||||
# These are emitted by Didactyl in src/llm.c via fprintf(stderr, ...).
|
# [didactyl] llm request: method=... url=...
|
||||||
|
START_API=$(tail -n 20000 "$LOG_FILE" | grep -Ec "\\[didactyl\\] llm request:" || true)
|
||||||
|
[ -z "$START_API" ] && START_API=0
|
||||||
|
|
||||||
|
echo "Starting api_call count from: $START_API"
|
||||||
|
|
||||||
tail -n 0 -F "$LOG_FILE" \
|
tail -n 0 -F "$LOG_FILE" \
|
||||||
| stdbuf -oL grep -E --line-buffered "\\[didactyl\\] llm request:|\\[didactyl\\] llm http request failed:|\\[didactyl\\] llm error response:|\\[didactyl\\] llm partial response:|\\[didactyl\\] llm hint:"
|
| awk -v api_start="$START_API" '
|
||||||
|
BEGIN { api = api_start }
|
||||||
|
{
|
||||||
|
line = $0
|
||||||
|
if (line !~ /\[didactyl\] llm request:/) next
|
||||||
|
|
||||||
|
api++
|
||||||
|
method = "?"
|
||||||
|
url = "?"
|
||||||
|
|
||||||
|
if (match(line, /method=[A-Z]+/)) {
|
||||||
|
method = substr(line, RSTART + 7, RLENGTH - 7)
|
||||||
|
}
|
||||||
|
if (match(line, /url=[^ ]+/)) {
|
||||||
|
url = substr(line, RSTART + 4, RLENGTH - 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
printf "%d) %s %s\n", api, method, url
|
||||||
|
fflush()
|
||||||
|
}
|
||||||
|
'
|
||||||
Reference in New Issue
Block a user