diff --git a/lang/el-compiler/runtime/el_runtime.c b/lang/el-compiler/runtime/el_runtime.c index df83e95..7d47e17 100644 --- a/lang/el-compiler/runtime/el_runtime.c +++ b/lang/el-compiler/runtime/el_runtime.c @@ -3135,23 +3135,49 @@ static void jb_puts(JsonBuf* b, const char* s) { static void jb_emit_escaped(JsonBuf* b, const char* s) { jb_putc(b, '"'); - for (; *s; s++) { - unsigned char c = (unsigned char)*s; + const unsigned char* p = (const unsigned char*)s; + while (*p) { + unsigned char c = *p; switch (c) { - case '"': jb_puts(b, "\\\""); break; - case '\\': jb_puts(b, "\\\\"); break; - case '\b': jb_puts(b, "\\b"); break; - case '\f': jb_puts(b, "\\f"); break; - case '\n': jb_puts(b, "\\n"); break; - case '\r': jb_puts(b, "\\r"); break; - case '\t': jb_puts(b, "\\t"); break; + case '"': jb_puts(b, "\\\""); p++; break; + case '\\': jb_puts(b, "\\\\"); p++; break; + case '\b': jb_puts(b, "\\b"); p++; break; + case '\f': jb_puts(b, "\\f"); p++; break; + case '\n': jb_puts(b, "\\n"); p++; break; + case '\r': jb_puts(b, "\\r"); p++; break; + case '\t': jb_puts(b, "\\t"); p++; break; default: if (c < 0x20) { char tmp[8]; snprintf(tmp, sizeof(tmp), "\\u%04x", c); jb_puts(b, tmp); - } else { + p++; + } else if (c < 0x80) { 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; } @@ -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]; for (int i = 0; i < LLM_MAX_PROVIDERS; 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); int fmt = (fmt_s && strcmp(fmt_s, "anthropic") == 0) ? 1 : 0; 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); el_val_t result = llm_provider_request(url, key, fmt, model, system_str, user_str); 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"); if (!api_key || !*api_key) return http_error_json("no LLM providers configured"); 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 */ @@ -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) { + const char* m = EL_CSTR(model); 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) { + const char* m = EL_CSTR(model); const char* s = EL_CSTR(system_prompt); if (!s) s = ""; 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 ─────────────────────────────────── */