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
context.log.md
9387
context.log.md
File diff suppressed because one or more lines are too long
4194
didactyl.log
Normal file
4194
didactyl.log
Normal file
File diff suppressed because one or more lines are too long
81
plans/nostr_core_logging_integration.md
Normal file
81
plans/nostr_core_logging_integration.md
Normal file
@@ -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
src/debug.c
34
src/debug.c
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
60
src/llm.c
60
src/llm.c
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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