Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dbf2c659d9 | |||
| 2b8062c55f |
@@ -3135,23 +3135,49 @@ static void jb_puts(JsonBuf* b, const char* s) {
|
|||||||
|
|
||||||
static void jb_emit_escaped(JsonBuf* b, const char* s) {
|
static void jb_emit_escaped(JsonBuf* b, const char* s) {
|
||||||
jb_putc(b, '"');
|
jb_putc(b, '"');
|
||||||
for (; *s; s++) {
|
const unsigned char* p = (const unsigned char*)s;
|
||||||
unsigned char c = (unsigned char)*s;
|
while (*p) {
|
||||||
|
unsigned char c = *p;
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case '"': jb_puts(b, "\\\""); break;
|
case '"': jb_puts(b, "\\\""); p++; break;
|
||||||
case '\\': jb_puts(b, "\\\\"); break;
|
case '\\': jb_puts(b, "\\\\"); p++; break;
|
||||||
case '\b': jb_puts(b, "\\b"); break;
|
case '\b': jb_puts(b, "\\b"); p++; break;
|
||||||
case '\f': jb_puts(b, "\\f"); break;
|
case '\f': jb_puts(b, "\\f"); p++; break;
|
||||||
case '\n': jb_puts(b, "\\n"); break;
|
case '\n': jb_puts(b, "\\n"); p++; break;
|
||||||
case '\r': jb_puts(b, "\\r"); break;
|
case '\r': jb_puts(b, "\\r"); p++; break;
|
||||||
case '\t': jb_puts(b, "\\t"); break;
|
case '\t': jb_puts(b, "\\t"); p++; break;
|
||||||
default:
|
default:
|
||||||
if (c < 0x20) {
|
if (c < 0x20) {
|
||||||
char tmp[8];
|
char tmp[8];
|
||||||
snprintf(tmp, sizeof(tmp), "\\u%04x", c);
|
snprintf(tmp, sizeof(tmp), "\\u%04x", c);
|
||||||
jb_puts(b, tmp);
|
jb_puts(b, tmp);
|
||||||
} else {
|
p++;
|
||||||
|
} else if (c < 0x80) {
|
||||||
jb_putc(b, (char)c);
|
jb_putc(b, (char)c);
|
||||||
|
p++;
|
||||||
|
} else {
|
||||||
|
/* Multi-byte UTF-8: validate sequence, pass through if valid,
|
||||||
|
* escape as \u00xx if the start byte is invalid/orphaned. */
|
||||||
|
int seq_len = 0;
|
||||||
|
if ((c & 0xE0) == 0xC0) seq_len = 2;
|
||||||
|
else if ((c & 0xF0) == 0xE0) seq_len = 3;
|
||||||
|
else if ((c & 0xF8) == 0xF0) seq_len = 4;
|
||||||
|
if (seq_len >= 2) {
|
||||||
|
int valid = 1;
|
||||||
|
for (int i = 1; i < seq_len; i++) {
|
||||||
|
if ((p[i] & 0xC0) != 0x80) { valid = 0; break; }
|
||||||
|
}
|
||||||
|
if (valid) {
|
||||||
|
for (int i = 0; i < seq_len; i++) jb_putc(b, (char)p[i]);
|
||||||
|
p += seq_len;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Invalid start byte or truncated sequence — escape it */
|
||||||
|
char tmp[8];
|
||||||
|
snprintf(tmp, sizeof(tmp), "\\u%04x", c);
|
||||||
|
jb_puts(b, tmp);
|
||||||
|
p++;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -8447,7 +8473,7 @@ static el_val_t llm_provider_request(const char* url, const char* key,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static el_val_t llm_chain_call(const char* system_str, const char* user_str) {
|
static el_val_t llm_chain_call(const char* model_pref, const char* system_str, const char* user_str) {
|
||||||
char url_key[64], key_key[64], fmt_key[64], model_key[64];
|
char url_key[64], key_key[64], fmt_key[64], model_key[64];
|
||||||
for (int i = 0; i < LLM_MAX_PROVIDERS; i++) {
|
for (int i = 0; i < LLM_MAX_PROVIDERS; i++) {
|
||||||
snprintf(url_key, sizeof(url_key), "NEURON_LLM_%d_URL", i);
|
snprintf(url_key, sizeof(url_key), "NEURON_LLM_%d_URL", i);
|
||||||
@@ -8460,6 +8486,7 @@ static el_val_t llm_chain_call(const char* system_str, const char* user_str) {
|
|||||||
const char* fmt_s = getenv(fmt_key);
|
const char* fmt_s = getenv(fmt_key);
|
||||||
int fmt = (fmt_s && strcmp(fmt_s, "anthropic") == 0) ? 1 : 0;
|
int fmt = (fmt_s && strcmp(fmt_s, "anthropic") == 0) ? 1 : 0;
|
||||||
const char* model = getenv(model_key);
|
const char* model = getenv(model_key);
|
||||||
|
if (!model || !*model) model = model_pref; /* fall back to the caller-requested model */
|
||||||
fprintf(stderr, "[llm] trying provider %d (%s)\n", i, url);
|
fprintf(stderr, "[llm] trying provider %d (%s)\n", i, url);
|
||||||
el_val_t result = llm_provider_request(url, key, fmt, model, system_str, user_str);
|
el_val_t result = llm_provider_request(url, key, fmt, model, system_str, user_str);
|
||||||
const char* t = EL_CSTR(result);
|
const char* t = EL_CSTR(result);
|
||||||
@@ -8470,7 +8497,7 @@ static el_val_t llm_chain_call(const char* system_str, const char* user_str) {
|
|||||||
const char* api_key = getenv("ANTHROPIC_API_KEY");
|
const char* api_key = getenv("ANTHROPIC_API_KEY");
|
||||||
if (!api_key || !*api_key) return http_error_json("no LLM providers configured");
|
if (!api_key || !*api_key) return http_error_json("no LLM providers configured");
|
||||||
fprintf(stderr, "[llm] using legacy ANTHROPIC_API_KEY fallback\n");
|
fprintf(stderr, "[llm] using legacy ANTHROPIC_API_KEY fallback\n");
|
||||||
return llm_provider_request(LLM_API_URL, api_key, 1, NULL, system_str, user_str);
|
return llm_provider_request(LLM_API_URL, api_key, 1, model_pref, system_str, user_str);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Legacy llm_request — kept for backward compat with agentic loop internals */
|
/* Legacy llm_request — kept for backward compat with agentic loop internals */
|
||||||
@@ -8534,14 +8561,16 @@ static el_val_t llm_extract_text(el_val_t resp_val) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
el_val_t llm_call(el_val_t model, el_val_t prompt) {
|
el_val_t llm_call(el_val_t model, el_val_t prompt) {
|
||||||
|
const char* m = EL_CSTR(model);
|
||||||
const char* u = EL_CSTR(prompt); if (!u) u = "";
|
const char* u = EL_CSTR(prompt); if (!u) u = "";
|
||||||
return llm_chain_call(NULL, u);
|
return llm_chain_call(m, NULL, u);
|
||||||
}
|
}
|
||||||
|
|
||||||
el_val_t llm_call_system(el_val_t model, el_val_t system_prompt, el_val_t user_prompt) {
|
el_val_t llm_call_system(el_val_t model, el_val_t system_prompt, el_val_t user_prompt) {
|
||||||
|
const char* m = EL_CSTR(model);
|
||||||
const char* s = EL_CSTR(system_prompt); if (!s) s = "";
|
const char* s = EL_CSTR(system_prompt); if (!s) s = "";
|
||||||
const char* u = EL_CSTR(user_prompt); if (!u) u = "";
|
const char* u = EL_CSTR(user_prompt); if (!u) u = "";
|
||||||
return llm_chain_call(s, u);
|
return llm_chain_call(m, s, u);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Tool registry for llm_call_agentic ─────────────────────────────────── */
|
/* ── Tool registry for llm_call_agentic ─────────────────────────────────── */
|
||||||
|
|||||||
+3
-43
@@ -6,55 +6,15 @@
|
|||||||
//
|
//
|
||||||
// Dependencies: runtime/string.el, runtime/json.el
|
// Dependencies: runtime/string.el, runtime/json.el
|
||||||
|
|
||||||
// --- Validation (defense in depth) ---
|
|
||||||
// el_val_t is an untyped machine word, so a wrong TYPE can't be caught here — but a
|
|
||||||
// wrong VALUE can (a tier in the node_type slot, an empty/garbage string, an int, a
|
|
||||||
// path, a model name, a cgi id). Reject loudly instead of silently writing junk.
|
|
||||||
|
|
||||||
fn engram_valid_node_type(t: String) -> Bool {
|
|
||||||
return str_eq(t, "Memory") || str_eq(t, "Knowledge") || str_eq(t, "Belief")
|
|
||||||
|| str_eq(t, "Project") || str_eq(t, "Tag") || str_eq(t, "BacklogItem")
|
|
||||||
|| str_eq(t, "Artifact") || str_eq(t, "Conversation") || str_eq(t, "ExecutionContext")
|
|
||||||
|| str_eq(t, "InternalStateEvent") || str_eq(t, "Self") || str_eq(t, "Entity")
|
|
||||||
|| str_eq(t, "Process") || str_eq(t, "ConfigEntry") || str_eq(t, "Concept") || str_eq(t, "Imprint")
|
|
||||||
|| str_eq(t, "SessionSummary")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn engram_valid_tier(t: String) -> Bool {
|
|
||||||
return str_eq(t, "Semantic") || str_eq(t, "Episodic") || str_eq(t, "Working")
|
|
||||||
|| str_eq(t, "Procedural") || str_eq(t, "Canonical") || str_eq(t, "Note") || str_eq(t, "Lesson")
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Node creation ---
|
// --- Node creation ---
|
||||||
|
|
||||||
fn engram_node(content: String, node_type: String, salience: Float) -> String {
|
fn engram_node(content: String, node_type: String, salience: Float) -> String {
|
||||||
if !engram_valid_node_type(node_type) {
|
|
||||||
__println("[engram] REJECTED node write — invalid node_type '" + node_type + "'")
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return __engram_node(content, node_type, salience)
|
return __engram_node(content, node_type, salience)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signature MUST match the C primitive __engram_node_full exactly (el_seed.h):
|
fn engram_node_full(content: String, nt: String, sal: Float, imp: Float,
|
||||||
// (content, node_type, label, salience, importance, confidence, tier, tags)
|
source: String, lang: String, ts: Int, tags: String) -> String {
|
||||||
// The previous wrapper declared a stale 8-arg schema with wrong names AND types
|
return __engram_node_full(content, nt, sal, imp, source, lang, ts, tags)
|
||||||
// (sal:Float at the label slot, ts:Int at the tier slot). Because el_val_t is an
|
|
||||||
// untyped machine word, the EL compiler coerced caller args to those wrong param
|
|
||||||
// types and then forwarded them BY POSITION into the C function — so tier received
|
|
||||||
// an int, importance/confidence received strings, label received a float, etc.
|
|
||||||
// That is the field-corruption bug. Match the contract 1:1 — no coercion, no reorder.
|
|
||||||
fn engram_node_full(content: String, node_type: String, label: String,
|
|
||||||
salience: Float, importance: Float, confidence: Float,
|
|
||||||
tier: String, tags: String) -> String {
|
|
||||||
if !engram_valid_node_type(node_type) {
|
|
||||||
__println("[engram] REJECTED node write — invalid node_type '" + node_type + "' (label=" + label + ")")
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
if !engram_valid_tier(tier) {
|
|
||||||
__println("[engram] REJECTED node write — invalid tier '" + tier + "' (node_type=" + node_type + ", label=" + label + ")")
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return __engram_node_full(content, node_type, label, salience, importance, confidence, tier, tags)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Node retrieval ---
|
// --- Node retrieval ---
|
||||||
|
|||||||
Reference in New Issue
Block a user