Merge remote-tracking branch 'origin/wt/swarm-ccr' into merge-swarm-ccr-v2
El SDK CI - dev / build-and-test (pull_request) Failing after 4m13s

# Conflicts:
#	lang/runtime/el_runtime.c
#	lang/runtime/el_runtime.h
This commit is contained in:
bigmerge
2026-08-15 18:17:33 -05:00
18 changed files with 2520 additions and 0 deletions
+409
View File
@@ -17751,3 +17751,412 @@ el_val_t emit_event(el_val_t name_v, el_val_t duration_ms_v) {
return trace_span_end(h);
}
/* ── DHARMA runtime additions ────────────────────────────────────────────────
*
* Functions required by the dharma registry service. Added here so the
* released el_runtime.c includes them without requiring dharma to bundle
* its own stubs.
*
* Functions added:
* list_len alias for el_list_len (used in handlers.el)
* list_get alias for el_list_get (used in handlers.el)
* json_array_push append a pre-encoded JSON element to a JSON array string
* now_millis milliseconds since Unix epoch (alias for time_now)
* unix_timestamp_ms same as now_millis (alias)
* time_now_ms same as now_millis (alias)
* log_info stderr structured log at INFO level
* log_warn stderr structured log at WARN level
* config reads a config value from the environment
* http_patch HTTP PATCH with JSON Content-Type
* http_post_engram HTTP POST with optional X-API-Key header
* http_get_engram HTTP GET with optional X-API-Key header
* str_to_bytes encode a string as a JSON array of byte values
* bytes_to_str decode a JSON array of byte values back to a string
* hash_sha256 SHA-256 hex digest of a string
*/
/* list_len — return the number of elements in a list. */
el_val_t list_len(el_val_t list) {
return el_list_len(list);
}
/* list_get — return the element at index i in a list. */
el_val_t list_get(el_val_t list, el_val_t index) {
return el_list_get(list, index);
}
/* json_array_push — append element (a pre-encoded JSON fragment, e.g. "\"foo\""
* or "42") to the JSON array string arr. Returns a new JSON array string.
* Example: json_array_push("[]", "\"alice\"") -> "[\"alice\"]"
* json_array_push("[\"alice\"]", "\"bob\"") -> "[\"alice\",\"bob\"]" */
el_val_t json_array_push(el_val_t arr_v, el_val_t elem_v) {
const char* arr = EL_CSTR(arr_v);
const char* elem = EL_CSTR(elem_v);
if (!arr || !*arr) arr = "[]";
if (!elem || !*elem) elem = "null";
/* Trim whitespace, find the closing ']'. */
const char* p = arr;
while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') p++;
if (*p != '[') {
/* Not an array — return a single-element array. */
size_t n = strlen(elem) + 4;
char* out = el_strbuf(n);
snprintf(out, n, "[%s]", elem);
return el_wrap_str(out);
}
size_t arr_len = strlen(arr);
size_t elem_len = strlen(elem);
/* Walk from the end to find the matching ']'. */
const char* end = arr + arr_len - 1;
while (end > p && (*end == ' ' || *end == '\t' || *end == '\n' || *end == '\r')) end--;
if (*end != ']') {
/* Malformed — wrap elem in a new array. */
size_t n = elem_len + 4;
char* out = el_strbuf(n);
snprintf(out, n, "[%s]", elem);
return el_wrap_str(out);
}
/* Content between '[' and ']'. */
const char* inner_start = p + 1;
const char* inner_end = end; /* points AT ']' */
/* Check if the array is empty (only whitespace between brackets). */
const char* q = inner_start;
while (q < inner_end && (*q == ' ' || *q == '\t' || *q == '\n' || *q == '\r')) q++;
int empty = (q == inner_end);
/* Build: prefix + (comma if non-empty) + elem + "]" */
size_t prefix_len = (size_t)(inner_end - arr); /* up to but not including ']' */
size_t sep_len = empty ? 0 : 1; /* "," if non-empty */
size_t out_len = prefix_len + sep_len + elem_len + 2; /* +"]" + NUL */
char* out = el_strbuf(out_len);
memcpy(out, arr, prefix_len);
if (!empty) out[prefix_len] = ',';
memcpy(out + prefix_len + sep_len, elem, elem_len);
out[prefix_len + sep_len + elem_len] = ']';
out[prefix_len + sep_len + elem_len + 1] = '\0';
return el_wrap_str(out);
}
/* now_millis — milliseconds since Unix epoch. */
el_val_t now_millis(void) {
return time_now();
}
/* unix_timestamp_ms — same as now_millis. */
el_val_t unix_timestamp_ms(void) {
return time_now();
}
/* time_now_ms — same as now_millis. */
el_val_t time_now_ms(void) {
return time_now();
}
/* log_info — write a structured [INFO] line to stderr. */
void log_info(el_val_t msg_v) {
const char* msg = EL_CSTR(msg_v);
fprintf(stderr, "[INFO] %s\n", msg ? msg : "");
}
/* log_warn — write a structured [WARN] line to stderr. */
void log_warn(el_val_t msg_v) {
const char* msg = EL_CSTR(msg_v);
fprintf(stderr, "[WARN] %s\n", msg ? msg : "");
}
/* config — read a configuration value from the environment.
* Returns "" if the variable is not set (same as __env_get). */
el_val_t config(el_val_t key_v) {
const char* key = EL_CSTR(key_v);
if (!key || !*key) return EL_STR("");
const char* val = getenv(key);
if (!val) return EL_STR("");
return el_wrap_str(el_strdup(val));
}
#if !defined(_WIN32) || defined(HAVE_CURL)
/* http_patch — HTTP PATCH request with Content-Type: application/json.
* Returns the response body (same error convention as http_post_json). */
el_val_t http_patch(el_val_t url_v, el_val_t body_v) {
const char* url = EL_CSTR(url_v);
const char* body = EL_CSTR(body_v);
if (!url || !*url) return http_error_json("empty url");
CURL* c = curl_easy_init();
if (!c) return http_error_json("curl_easy_init failed");
HttpBuf rb; httpbuf_init(&rb);
char errbuf[CURL_ERROR_SIZE]; errbuf[0] = '\0';
struct curl_slist* h = NULL;
h = curl_slist_append(h, "Content-Type: application/json");
curl_easy_setopt(c, CURLOPT_URL, url);
curl_easy_setopt(c, CURLOPT_CUSTOMREQUEST, "PATCH");
curl_easy_setopt(c, CURLOPT_POSTFIELDS, body ? body : "");
curl_easy_setopt(c, CURLOPT_POSTFIELDSIZE, (long)(body ? strlen(body) : 0));
curl_easy_setopt(c, CURLOPT_HTTPHEADER, h);
curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, http_write_cb);
curl_easy_setopt(c, CURLOPT_WRITEDATA, &rb);
curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(c, CURLOPT_TIMEOUT_MS, el_http_timeout_ms());
curl_easy_setopt(c, CURLOPT_NOSIGNAL, 1L);
curl_easy_setopt(c, CURLOPT_ERRORBUFFER, errbuf);
curl_easy_setopt(c, CURLOPT_USERAGENT, "el-runtime/1.0");
CURLcode rc = curl_easy_perform(c);
curl_slist_free_all(h);
curl_easy_cleanup(c);
if (rc != CURLE_OK) {
free(rb.data);
const char* m = errbuf[0] ? errbuf : curl_easy_strerror(rc);
return http_error_json(m);
}
return el_wrap_str(rb.data);
}
/* http_post_engram — HTTP POST with optional X-API-Key header.
* If key is "" no authentication header is sent. */
el_val_t http_post_engram(el_val_t url_v, el_val_t key_v, el_val_t body_v) {
const char* url = EL_CSTR(url_v);
const char* key = EL_CSTR(key_v);
const char* body = EL_CSTR(body_v);
if (!url || !*url) return http_error_json("empty url");
CURL* c = curl_easy_init();
if (!c) return http_error_json("curl_easy_init failed");
HttpBuf rb; httpbuf_init(&rb);
char errbuf[CURL_ERROR_SIZE]; errbuf[0] = '\0';
struct curl_slist* h = NULL;
h = curl_slist_append(h, "Content-Type: application/json");
if (key && *key) {
size_t n = strlen(key) + 32;
char* hdr = malloc(n);
snprintf(hdr, n, "X-API-Key: %s", key);
h = curl_slist_append(h, hdr);
free(hdr);
}
curl_easy_setopt(c, CURLOPT_URL, url);
curl_easy_setopt(c, CURLOPT_POST, 1L);
curl_easy_setopt(c, CURLOPT_POSTFIELDS, body ? body : "");
curl_easy_setopt(c, CURLOPT_POSTFIELDSIZE, (long)(body ? strlen(body) : 0));
curl_easy_setopt(c, CURLOPT_HTTPHEADER, h);
curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, http_write_cb);
curl_easy_setopt(c, CURLOPT_WRITEDATA, &rb);
curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(c, CURLOPT_TIMEOUT_MS, el_http_timeout_ms());
curl_easy_setopt(c, CURLOPT_NOSIGNAL, 1L);
curl_easy_setopt(c, CURLOPT_ERRORBUFFER, errbuf);
curl_easy_setopt(c, CURLOPT_USERAGENT, "el-runtime/1.0");
CURLcode rc = curl_easy_perform(c);
curl_slist_free_all(h);
curl_easy_cleanup(c);
if (rc != CURLE_OK) {
free(rb.data);
const char* m = errbuf[0] ? errbuf : curl_easy_strerror(rc);
return http_error_json(m);
}
return el_wrap_str(rb.data);
}
/* http_get_engram — HTTP GET with optional X-API-Key header. */
el_val_t http_get_engram(el_val_t url_v, el_val_t key_v) {
const char* url = EL_CSTR(url_v);
const char* key = EL_CSTR(key_v);
if (!url || !*url) return http_error_json("empty url");
CURL* c = curl_easy_init();
if (!c) return http_error_json("curl_easy_init failed");
HttpBuf rb; httpbuf_init(&rb);
char errbuf[CURL_ERROR_SIZE]; errbuf[0] = '\0';
struct curl_slist* h = NULL;
if (key && *key) {
size_t n = strlen(key) + 32;
char* hdr = malloc(n);
snprintf(hdr, n, "X-API-Key: %s", key);
h = curl_slist_append(h, hdr);
free(hdr);
}
curl_easy_setopt(c, CURLOPT_URL, url);
curl_easy_setopt(c, CURLOPT_HTTPGET, 1L);
if (h) curl_easy_setopt(c, CURLOPT_HTTPHEADER, h);
curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, http_write_cb);
curl_easy_setopt(c, CURLOPT_WRITEDATA, &rb);
curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(c, CURLOPT_TIMEOUT_MS, el_http_timeout_ms());
curl_easy_setopt(c, CURLOPT_NOSIGNAL, 1L);
curl_easy_setopt(c, CURLOPT_ERRORBUFFER, errbuf);
curl_easy_setopt(c, CURLOPT_USERAGENT, "el-runtime/1.0");
CURLcode rc = curl_easy_perform(c);
if (h) curl_slist_free_all(h);
curl_easy_cleanup(c);
if (rc != CURLE_OK) {
free(rb.data);
const char* m = errbuf[0] ? errbuf : curl_easy_strerror(rc);
return http_error_json(m);
}
return el_wrap_str(rb.data);
}
#endif /* HAVE_CURL */
/* str_to_bytes — encode a string as a JSON array of unsigned byte values.
* "hello" -> "[104,101,108,108,111]"
* Used by db.el to store binary content in Engram JSON nodes. */
el_val_t str_to_bytes(el_val_t sv) {
const char* s = EL_CSTR(sv);
if (!s || !*s) return el_wrap_str(el_strdup("[]"));
size_t n = strlen(s);
/* Worst case: each byte is 3 digits + comma = 4 chars, plus "[]" + NUL. */
char* out = el_strbuf(n * 4 + 3);
size_t pos = 0;
out[pos++] = '[';
for (size_t i = 0; i < n; i++) {
unsigned char b = (unsigned char)s[i];
if (i > 0) out[pos++] = ',';
/* Write decimal representation of b. */
if (b >= 100) {
out[pos++] = (char)('0' + b / 100);
out[pos++] = (char)('0' + (b / 10) % 10);
out[pos++] = (char)('0' + b % 10);
} else if (b >= 10) {
out[pos++] = (char)('0' + b / 10);
out[pos++] = (char)('0' + b % 10);
} else {
out[pos++] = (char)('0' + b);
}
}
out[pos++] = ']';
out[pos] = '\0';
return el_wrap_str(out);
}
/* bytes_to_str — decode a JSON array of integer byte values back to a string.
* "[104,101,108,108,111]" -> "hello"
* Inverse of str_to_bytes. */
el_val_t bytes_to_str(el_val_t arr_v) {
const char* s = EL_CSTR(arr_v);
if (!s) return el_wrap_str(el_strdup(""));
/* Skip whitespace, expect '['. */
while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++;
if (*s != '[') return el_wrap_str(el_strdup(""));
s++;
/* Count elements to size the output buffer. */
int64_t n = (int64_t)json_array_len(arr_v);
if (n <= 0) return el_wrap_str(el_strdup(""));
char* out = el_strbuf((size_t)n + 1);
size_t pos = 0;
/* Walk the array, parse each integer, store as a byte. */
while (*s) {
while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++;
if (*s == ']' || *s == '\0') break;
/* Parse decimal integer. */
char* end_ptr;
long v = strtol(s, &end_ptr, 10);
if (end_ptr == s) break; /* parse failure */
s = end_ptr;
if (v >= 0 && v <= 255) out[pos++] = (char)(unsigned char)v;
while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++;
if (*s == ',') { s++; continue; }
if (*s == ']' || *s == '\0') break;
}
out[pos] = '\0';
return el_wrap_str(out);
}
/* hash_sha256 — return the SHA-256 hex digest of a string.
* Uses the built-in el_sha256_oneshot implementation (no OpenSSL required). */
el_val_t hash_sha256(el_val_t sv) {
const char* s = EL_CSTR(sv);
if (!s) s = "";
unsigned char digest[32];
el_sha256_oneshot((const unsigned char*)s, strlen(s), digest);
return el_hex_encode(digest, 32);
}
/* HTTP client aliases — require curl; defined inside #ifdef HAVE_CURL below
* with a matching stub in the #ifndef HAVE_CURL block. */
#if !defined(_WIN32) || defined(HAVE_CURL)
/* __http_do also lives in el_seed.c; marked weak so el_seed.c's definition
* wins when both translation units are linked together (the real product build). */
__attribute__((weak)) el_val_t __http_do(el_val_t method, el_val_t url, el_val_t body,
el_val_t headers_map, el_val_t timeout_ms) {
/* timeout_ms is accepted for API compatibility but ignored here;
* el_runtime's http_do uses the EL_HTTP_TIMEOUT_MS env var instead. */
(void)timeout_ms;
struct curl_slist* h = headers_from_map(headers_map);
el_val_t r = http_do(EL_CSTR(method), EL_CSTR(url), EL_CSTR(body), h);
if (h) curl_slist_free_all(h);
return r;
}
/* __http_do_map — same as __http_do but headers_map arg is a JSON-string
* rather than an ElMap. Parse it first, then delegate. */
el_val_t __http_do_map(el_val_t method, el_val_t url, el_val_t body,
el_val_t headers_json, el_val_t timeout_ms) {
(void)timeout_ms;
/* Build a curl_slist from a JSON object {"Header":"value",...}. */
const char* hj = EL_CSTR(headers_json);
struct curl_slist* h = NULL;
if (hj && *hj && *hj == '{') {
/* Walk the JSON pairs with a simple parser reusing json_get_string logic. */
/* For correctness we just call the existing json_get iteration path.
* We duplicate the key-extraction loop from headers_from_map but driven
* by JSON rather than ElMap. Use json_get_raw to iterate is not easy
* without knowing keys, so accept the JSON string and build a tmp map. */
el_val_t map = json_parse(EL_STR(hj));
h = headers_from_map(map);
}
el_val_t r = http_do(EL_CSTR(method), EL_CSTR(url), EL_CSTR(body), h);
if (h) curl_slist_free_all(h);
return r;
}
/* __http_do_map_to_file — same as __http_do_map but streams response body
* to a local file path rather than returning it as a string. */
el_val_t __http_do_map_to_file(el_val_t method, el_val_t url, el_val_t body,
el_val_t headers_json, el_val_t output_path) {
const char* hj = EL_CSTR(headers_json);
struct curl_slist* h = NULL;
if (hj && *hj && *hj == '{') {
el_val_t map = json_parse(EL_STR(hj));
h = headers_from_map(map);
}
el_val_t r = http_do_to_file(EL_CSTR(method), EL_CSTR(url), EL_CSTR(body),
h, EL_CSTR(output_path));
if (h) curl_slist_free_all(h);
return r;
}
#endif /* HAVE_CURL */
#if defined(_WIN32) && !defined(HAVE_CURL)
/* ── HAVE_CURL=0 stubs — compile without -lcurl for the elc CLI binary. ───── *
* These return a JSON error string so El programs get a clear message if they
* call HTTP/LLM functions in a curl-less build. */
static el_val_t _no_curl_err(void) {
return el_wrap_str(el_strdup("{\"error\":\"not built with HAVE_CURL\"}"));
}
el_val_t http_get(el_val_t url) { (void)url; return _no_curl_err(); }
el_val_t http_post(el_val_t url, el_val_t body) { (void)url; (void)body; return _no_curl_err(); }
el_val_t http_post_json(el_val_t url, el_val_t body) { (void)url; (void)body; return _no_curl_err(); }
el_val_t http_get_with_headers(el_val_t url, el_val_t h) { (void)url; (void)h; return _no_curl_err(); }
el_val_t http_post_with_headers(el_val_t url, el_val_t b, el_val_t h) { (void)url; (void)b; (void)h; return _no_curl_err(); }
el_val_t http_post_json_with_headers(el_val_t url, el_val_t h, el_val_t b) { (void)url; (void)h; (void)b; return _no_curl_err(); }
el_val_t http_post_form_auth(el_val_t url, el_val_t b, el_val_t a) { (void)url; (void)b; (void)a; return _no_curl_err(); }
el_val_t http_delete(el_val_t url) { (void)url; return _no_curl_err(); }
el_val_t http_patch(el_val_t url, el_val_t body) { (void)url; (void)body; return _no_curl_err(); }
el_val_t http_get_to_file(el_val_t url, el_val_t h, el_val_t p) { (void)url; (void)h; (void)p; return _no_curl_err(); }
el_val_t http_post_to_file(el_val_t url, el_val_t b, el_val_t h, el_val_t p) { (void)url; (void)b; (void)h; (void)p; return _no_curl_err(); }
el_val_t http_post_engram(el_val_t url, el_val_t k, el_val_t b) { (void)url; (void)k; (void)b; return _no_curl_err(); }
el_val_t http_get_engram(el_val_t url, el_val_t k) { (void)url; (void)k; return _no_curl_err(); }
el_val_t llm_call(el_val_t m, el_val_t p) { (void)m; (void)p; return _no_curl_err(); }
el_val_t llm_call_system(el_val_t m, el_val_t s, el_val_t u) { (void)m; (void)s; (void)u; return _no_curl_err(); }
el_val_t llm_call_agentic(el_val_t m, el_val_t s, el_val_t u, el_val_t t) { (void)m; (void)s; (void)u; (void)t; return _no_curl_err(); }
el_val_t llm_vision(el_val_t m, el_val_t s, el_val_t p, el_val_t i) { (void)m; (void)s; (void)p; (void)i; return _no_curl_err(); }
el_val_t llm_models(void) { return el_list_empty(); }
void llm_register_tool(el_val_t n, el_val_t f) { (void)n; (void)f; }
/* __ HTTP stubs (no-curl build) */
el_val_t __http_do(el_val_t m, el_val_t u, el_val_t b, el_val_t h, el_val_t t) { (void)m; (void)u; (void)b; (void)h; (void)t; return _no_curl_err(); }
el_val_t __http_do_map(el_val_t m, el_val_t u, el_val_t b, el_val_t h, el_val_t t) { (void)m; (void)u; (void)b; (void)h; (void)t; return _no_curl_err(); }
el_val_t __http_do_map_to_file(el_val_t m, el_val_t u, el_val_t b, el_val_t h, el_val_t p) { (void)m; (void)u; (void)b; (void)h; (void)p; return _no_curl_err(); }
#endif /* !HAVE_CURL */
+111
View File
@@ -275,6 +275,10 @@ el_val_t json_set(el_val_t json_str, el_val_t key, el_val_t value);
el_val_t json_array_len(el_val_t json_str);
el_val_t json_array_get(el_val_t json_str, el_val_t index);
el_val_t json_array_get_string(el_val_t json_str, el_val_t index);
el_val_t json_escape_string(el_val_t sv);
el_val_t json_build_object(el_val_t kvs);
el_val_t json_build_array(el_val_t items);
el_val_t json_array_push(el_val_t arr_v, el_val_t elem_v); /* defined in el_runtime.c */
/* ── Time ────────────────────────────────────────────────────────────────── */
@@ -301,6 +305,8 @@ el_val_t time_diff(el_val_t ts1, el_val_t ts2, el_val_t unit);
el_val_t el_now_instant(void);
el_val_t now(void);
el_val_t now_millis(void); /* wall-clock milliseconds (defined in el_runtime.c) */
el_val_t now_ns(void); /* wall-clock nanoseconds (defined in el_runtime.c) */
el_val_t unix_seconds(el_val_t n);
el_val_t unix_millis(el_val_t n);
el_val_t instant_from_iso8601(el_val_t s);
@@ -892,6 +898,111 @@ el_val_t trace_span_start(el_val_t name);
el_val_t trace_span_end(el_val_t span_handle);
el_val_t emit_event(el_val_t name, el_val_t duration_ms);
el_val_t __thread_create(el_val_t fn_name_v, el_val_t arg_v);
el_val_t __thread_join(el_val_t tid_v);
/* Mutex + channel seed primitives (defined in el_runtime.c). Declared here so
* that compiled El programs which use runtime/thread.el's with_mutex helper or
* runtime/channel.el's Go-style channels see real prototypes instead of an
* implicit int-return declaration (which the C11 ABI mis-truncates el_val_t). */
el_val_t __mutex_new(void);
void __mutex_lock(el_val_t m_v);
void __mutex_unlock(el_val_t m_v);
el_val_t __channel_new(el_val_t capacity_v);
el_val_t __channel_send(el_val_t ch_v, el_val_t msg_v);
el_val_t __channel_recv(el_val_t ch_v);
el_val_t __channel_try_recv(el_val_t ch_v);
el_val_t __channel_close(el_val_t ch_v);
/* ── __ prefixed aliases (self-hosting compiler ABI) ─────────────────────────
* The El self-hosting compiler emits calls to __-prefixed names. These are
* forwarding wrappers around the existing el_runtime functions above. */
/* I/O */
el_val_t __println(el_val_t s);
el_val_t __print(el_val_t s);
el_val_t __readline(void);
/* String */
el_val_t __int_to_str(el_val_t n);
el_val_t __str_to_int(el_val_t s);
el_val_t __float_to_str(el_val_t f);
el_val_t __str_to_float(el_val_t s);
el_val_t __str_len(el_val_t s);
el_val_t __str_char_at(el_val_t s, el_val_t i);
el_val_t __str_cmp(el_val_t a, el_val_t b);
el_val_t __str_ncmp(el_val_t a, el_val_t b, el_val_t n);
el_val_t __str_concat_raw(el_val_t a, el_val_t b);
el_val_t __str_slice_raw(el_val_t s, el_val_t start, el_val_t end);
el_val_t __str_alloc(el_val_t n);
el_val_t __str_set_char(el_val_t s, el_val_t i, el_val_t c);
/* URL encoding */
el_val_t __url_encode(el_val_t s);
el_val_t __url_decode(el_val_t s);
/* Environment */
el_val_t __env_get(el_val_t key);
/* Subprocess */
el_val_t __exec(el_val_t cmd);
el_val_t __exec_bg(el_val_t cmd);
/* Process */
el_val_t __exit_program(el_val_t code);
/* Filesystem */
el_val_t __fs_exists(el_val_t path);
el_val_t __fs_mkdir(el_val_t path);
el_val_t __fs_read(el_val_t path);
el_val_t __fs_write(el_val_t path, el_val_t content);
el_val_t __fs_write_bytes(el_val_t path, el_val_t bytes, el_val_t n);
el_val_t __fs_list_raw(el_val_t path);
/* HTTP server */
el_val_t __http_response(el_val_t status, el_val_t headers_json, el_val_t body);
el_val_t __http_serve(el_val_t port, el_val_t handler);
el_val_t __http_serve_v2(el_val_t port, el_val_t handler);
/* HTTP conn fd / SSE (weak; overridden by el_seed.c when linked together) */
el_val_t __http_conn_fd(void);
el_val_t __http_sse_open(el_val_t conn_id);
el_val_t __http_sse_send(el_val_t conn_id, el_val_t data);
el_val_t __http_sse_close(el_val_t conn_id);
/* HTTP client (requires HAVE_CURL; stubs provided for no-curl builds) */
el_val_t __http_do(el_val_t method, el_val_t url, el_val_t body,
el_val_t headers_map, el_val_t timeout_ms);
el_val_t __http_do_map(el_val_t method, el_val_t url, el_val_t body,
el_val_t headers_json, el_val_t timeout_ms);
el_val_t __http_do_map_to_file(el_val_t method, el_val_t url, el_val_t body,
el_val_t headers_json, el_val_t output_path);
/* JSON */
el_val_t __json_array_get(el_val_t json, el_val_t index);
el_val_t __json_array_get_string(el_val_t json, el_val_t index);
el_val_t __json_array_len(el_val_t json);
el_val_t __json_get(el_val_t json, el_val_t key);
el_val_t __json_get_raw(el_val_t json, el_val_t key);
el_val_t __json_set(el_val_t json, el_val_t key, el_val_t value);
el_val_t __json_parse_map(el_val_t json_str);
el_val_t __json_stringify_val(el_val_t val);
/* Hashing */
el_val_t __sha256_hex(el_val_t s);
/* State K/V */
el_val_t __state_del(el_val_t key);
el_val_t __state_get(el_val_t key);
el_val_t __state_keys(void);
el_val_t __state_set(el_val_t key, el_val_t val);
/* UUID */
el_val_t __uuid_v4(void);
/* Args */
el_val_t __args_json(void);
#ifdef __cplusplus
}
#endif