@@ -2229,6 +2229,9 @@ impl Interpreter {
22292229 | "sha256" | "sha512" | "base64_encode" | "base64_decode"
22302230 // LLM builtins (Anthropic API)
22312231 | "llm_call" | "llm_chat" | "llm_embed"
2232+ | "batch_llm_call" | "batch_llm_chat"
2233+ // HTTP builtins
2234+ | "http_get" | "http_post" | "http_post_json" | "http_put" | "http_delete"
22322235 | "now_iso" | "now_unix" | "format_time" | "parse_time"
22332236 // Arrays
22342237 | "arr_new" | "arr_from_range" | "arr_len" | "arr_get" | "arr_set"
@@ -2311,6 +2314,8 @@ impl Interpreter {
23112314 | "now_ms" | "to_int" | "int" | "to_float" | "float"
23122315 | "to_string" | "string" | "len" | "type_of" | "error"
23132316 | "defined_functions" | "call"
2317+ // Introspection builtins
2318+ | "list_defined_fns" | "fn_arity" | "fn_source" | "get_scope_vars"
23142319 // Python-idiom builtins (forgiving aliases for users new to OMC)
23152320 | "range" | "getenv" | "to_hex" | "from_hex"
23162321 | "parse_int" | "parse_float"
@@ -2331,6 +2336,11 @@ impl Interpreter {
23312336 | "eval_omc" | "eval_omc_fresh" | "eval_omc_ctx" | "omc_source"
23322337 // Native LLM builtins
23332338 | "llm_call" | "llm_chat" | "llm_embed" | "llm_models"
2339+ | "batch_llm_call" | "batch_llm_chat"
2340+ // Process execution builtins
2341+ | "omc_spawn" | "omc_pipe"
2342+ // Native HTTP builtins
2343+ | "http_get" | "http_post" | "http_post_json" | "http_put" | "http_delete"
23342344 )
23352345 }
23362346
@@ -4843,6 +4853,60 @@ impl Interpreter {
48434853 names.into_iter().map(Value::String).collect(),
48444854 )))
48454855 }
4856+ // list_defined_fns() — alias for defined_functions; returns sorted array of user fn names
4857+ "list_defined_fns" => {
4858+ let mut names: Vec<String> = self.functions.keys()
4859+ .filter(|n| !n.starts_with("__lambda_") && !n.starts_with("__rt_lambda_"))
4860+ .cloned()
4861+ .collect();
4862+ names.sort();
4863+ Ok(Value::Array(HArray::from_vec(
4864+ names.into_iter().map(Value::String).collect(),
4865+ )))
4866+ }
4867+ // fn_arity(name) → int — parameter count of a user-defined function, or null
4868+ "fn_arity" => {
4869+ if args.is_empty() {
4870+ return Err("fn_arity requires (fn_name)".to_string());
4871+ }
4872+ let name_v = self.eval_expr(&args[0])?;
4873+ let fn_name = match &name_v {
4874+ Value::String(s) => s.clone(),
4875+ _ => return Err("fn_arity: argument must be a string".to_string()),
4876+ };
4877+ if let Some((params, _body)) = self.functions.get(&fn_name) {
4878+ Ok(Value::HInt(HInt::new(params.len() as i64)))
4879+ } else {
4880+ Ok(Value::Null)
4881+ }
4882+ }
4883+ // fn_source(name) → string — reconstructed signature of a user-defined function
4884+ "fn_source" => {
4885+ if args.is_empty() {
4886+ return Err("fn_source requires (fn_name)".to_string());
4887+ }
4888+ let name_v = self.eval_expr(&args[0])?;
4889+ let fn_name = match &name_v {
4890+ Value::String(s) => s.clone(),
4891+ _ => return Err("fn_source: argument must be a string".to_string()),
4892+ };
4893+ if let Some((params, _body)) = self.functions.get(&fn_name) {
4894+ let param_str = params.join(", ");
4895+ Ok(Value::String(format!("fn {}({}) {{ ... }}", fn_name, param_str)))
4896+ } else {
4897+ Ok(Value::Null)
4898+ }
4899+ }
4900+ // get_scope_vars() → dict — copy of all global variables currently in scope
4901+ "get_scope_vars" => {
4902+ let mut map: std::collections::BTreeMap<String, Value> = std::collections::BTreeMap::new();
4903+ for (k, v) in &self.globals {
4904+ if !k.starts_with("__") {
4905+ map.insert(k.clone(), v.clone());
4906+ }
4907+ }
4908+ Ok(Value::dict_from(map))
4909+ }
48464910 // call(fn_or_name, args_array) — dispatch a function value
48474911 // (or function-name string) with an arbitrary argument list
48484912 // unpacked from an array. Complements the HOFs (which fix
@@ -9375,6 +9439,134 @@ impl Interpreter {
93759439 "llm_models" => {
93769440 Ok(crate::llm_builtins::llm_models())
93779441 }
9442+ // batch_llm_call(prompts, model?, concurrency?) -> string[]
9443+ // Send multiple prompts in sequence, return all replies.
9444+ // `prompts` is an array of strings or dicts {prompt, system?, model?}.
9445+ // `model` overrides the default for all calls; per-prompt model takes precedence.
9446+ // `concurrency` is accepted but calls are currently sequential.
9447+ "batch_llm_call" => {
9448+ if args.is_empty() {
9449+ return Err("batch_llm_call requires (prompts: array)".to_string());
9450+ }
9451+ let prompts_val = self.eval_expr(&args[0])?;
9452+ let default_model = if args.len() > 1 {
9453+ let v = self.eval_expr(&args[1])?;
9454+ match v {
9455+ Value::Null => None,
9456+ other => Some(other.to_display_string()),
9457+ }
9458+ } else {
9459+ None
9460+ };
9461+ let concurrency = if args.len() > 2 {
9462+ match self.eval_expr(&args[2])? {
9463+ Value::HInt(n) => n.value as usize,
9464+ _ => 3,
9465+ }
9466+ } else {
9467+ 3
9468+ };
9469+ crate::llm_builtins::batch_llm_call(
9470+ &prompts_val,
9471+ default_model.as_deref(),
9472+ concurrency,
9473+ )
9474+ }
9475+ // batch_llm_chat(messages_array, model?, concurrency?) -> string[]
9476+ // Send multiple chat conversations in sequence, return all replies.
9477+ // `messages_array` is an array of arrays (each inner array is one chat's messages).
9478+ "batch_llm_chat" => {
9479+ if args.is_empty() {
9480+ return Err("batch_llm_chat requires (messages_array: array)".to_string());
9481+ }
9482+ let messages_array_val = self.eval_expr(&args[0])?;
9483+ let default_model = if args.len() > 1 {
9484+ let v = self.eval_expr(&args[1])?;
9485+ match v {
9486+ Value::Null => None,
9487+ other => Some(other.to_display_string()),
9488+ }
9489+ } else {
9490+ None
9491+ };
9492+ let concurrency = if args.len() > 2 {
9493+ match self.eval_expr(&args[2])? {
9494+ Value::HInt(n) => n.value as usize,
9495+ _ => 3,
9496+ }
9497+ } else {
9498+ 3
9499+ };
9500+ crate::llm_builtins::batch_llm_chat(
9501+ &messages_array_val,
9502+ default_model.as_deref(),
9503+ concurrency,
9504+ )
9505+ }
9506+ // ---- Process execution: omc_spawn / omc_pipe ----------------
9507+ //
9508+ // omc_spawn(cmd, args?, env_vars?, timeout_ms?) -> dict
9509+ // Spawns a subprocess and waits for it to complete.
9510+ // Returns {stdout, stderr, exit_code, ok}.
9511+ // Critical for self-improvement: OMC can run omc itself.
9512+ //
9513+ // omc_pipe(commands) -> dict
9514+ // Pipes multiple commands together like shell pipes.
9515+ // commands is an array of arrays: [[cmd, arg, ...], ...].
9516+ // Returns {stdout, stderr, exit_code, ok}.
9517+ "omc_spawn" => {
9518+ let eval_args: Vec<Value> = args.iter()
9519+ .map(|a| self.eval_expr(a))
9520+ .collect::<Result<Vec<_>, _>>()?;
9521+ crate::process_builtins::omc_spawn(&eval_args)
9522+ }
9523+ "omc_pipe" => {
9524+ let eval_args: Vec<Value> = args.iter()
9525+ .map(|a| self.eval_expr(a))
9526+ .collect::<Result<Vec<_>, _>>()?;
9527+ crate::process_builtins::omc_pipe(&eval_args)
9528+ }
9529+ // ---- Native HTTP builtins -------------------------------------------
9530+ //
9531+ // http_get(url, headers?) -> {status, body, ok}
9532+ // http_post(url, body, headers?) -> {status, body, ok}
9533+ // http_post_json(url, data, headers?) -> {status, body, ok, json}
9534+ // http_put(url, body, headers?) -> {status, body, ok}
9535+ // http_delete(url, headers?) -> {status, body, ok}
9536+ //
9537+ // headers is an optional dict of {header_name: header_value}.
9538+ // Passing null for headers is accepted.
9539+ // ok is true when 200 <= status < 300.
9540+ "http_get" => {
9541+ let eargs: Vec<Value> = args.iter()
9542+ .map(|a| self.eval_expr(a))
9543+ .collect::<Result<_, _>>()?;
9544+ crate::http_builtins::http_get(&eargs)
9545+ }
9546+ "http_post" => {
9547+ let eargs: Vec<Value> = args.iter()
9548+ .map(|a| self.eval_expr(a))
9549+ .collect::<Result<_, _>>()?;
9550+ crate::http_builtins::http_post(&eargs)
9551+ }
9552+ "http_post_json" => {
9553+ let eargs: Vec<Value> = args.iter()
9554+ .map(|a| self.eval_expr(a))
9555+ .collect::<Result<_, _>>()?;
9556+ crate::http_builtins::http_post_json(&eargs)
9557+ }
9558+ "http_put" => {
9559+ let eargs: Vec<Value> = args.iter()
9560+ .map(|a| self.eval_expr(a))
9561+ .collect::<Result<_, _>>()?;
9562+ crate::http_builtins::http_put(&eargs)
9563+ }
9564+ "http_delete" => {
9565+ let eargs: Vec<Value> = args.iter()
9566+ .map(|a| self.eval_expr(a))
9567+ .collect::<Result<_, _>>()?;
9568+ crate::http_builtins::http_delete(&eargs)
9569+ }
93789570 // ---- Substrate-signed messaging (LLM ↔ LLM protocol) ---
93799571 //
93809572 // omc_msg_sign(content, sender_id, kind) — produces a dict
@@ -14153,6 +14345,9 @@ pub(crate) const HEAL_BUILTIN_NAMES: &[&str] = &[
1415314345 "sha256", "sha512", "base64_encode", "base64_decode",
1415414346 // LLM builtins (Anthropic API — enabled with llm-builtins feature)
1415514347 "llm_call", "llm_chat", "llm_embed",
14348+ "batch_llm_call", "batch_llm_chat",
14349+ // Native HTTP builtins
14350+ "http_get", "http_post", "http_post_json", "http_put", "http_delete",
1415614351 "now_iso", "now_unix", "format_time", "parse_time",
1415714352 // Arrays
1415814353 "arr_new", "arr_from_range", "arr_len", "arr_get", "arr_set",
@@ -14233,6 +14428,7 @@ pub(crate) const HEAL_BUILTIN_NAMES: &[&str] = &[
1423314428 "to_int", "int", "to_float", "float",
1423414429 "to_string", "string", "len", "type_of", "error",
1423514430 "defined_functions", "call",
14431+ "list_defined_fns", "fn_arity", "fn_source", "get_scope_vars",
1423614432 "test_record_failure", "test_failure_count",
1423714433 "test_get_failures", "test_clear_failures",
1423814434 "test_set_current", "test_get_current",
@@ -14243,6 +14439,11 @@ pub(crate) const HEAL_BUILTIN_NAMES: &[&str] = &[
1424314439 "omc_predict_files", "omc_corpus_size",
1424414440 // LLM I/O builtins
1424514441 "llm_call", "llm_chat", "llm_embed", "llm_models",
14442+ "batch_llm_call", "batch_llm_chat",
14443+ // Process execution builtins
14444+ "omc_spawn", "omc_pipe",
14445+ // Native HTTP builtins
14446+ "http_get", "http_post", "http_post_json", "http_put", "http_delete",
1424614447 // Language literals. These are parsed as Variable(...) but get
1424714448 // special-cased at runtime — they must never be typo-corrected
1424814449 // (a "var_typo" rewriting `null` to a close-spelled name would
0 commit comments