Skip to main content

incurs_codemode/
program.rs

1use crate::{ConnectorDescription, generate_types, normalize_code};
2
3/// Adapter-specific settings for the shared Code Mode JavaScript harness.
4#[derive(Debug, Clone)]
5pub struct ProgramSourceOptions {
6    /// JavaScript expression that evaluates to the asynchronous dispatch function.
7    pub dispatch: String,
8    /// JavaScript expression exposed as `codemode.executionId`.
9    pub execution_id: String,
10    /// Optional JavaScript timer timeout for hosts that provide `setTimeout`.
11    pub timeout_ms: Option<u64>,
12}
13
14/// Builds the shared body used by local and remote Code Mode executors.
15pub fn build_program_source(
16    code: &str,
17    connectors: &[ConnectorDescription],
18    options: &ProgramSourceOptions,
19) -> Result<String, String> {
20    validate_names(connectors)?;
21    let code = normalize_code(code);
22    // One lazy binding per namespace rather than a materialized object of methods.
23    // Writing the methods out required every connector's tool list, which meant
24    // describing — and so connecting to — every configured server before any
25    // program could run, including the ones the program never mentions. The proxy
26    // needs only the name; the connection opens on the first call.
27    let bindings = connectors
28        .iter()
29        .map(|connector| {
30            format!(
31                "const {} = __namespace({});",
32                connector.name,
33                serde_json::to_string(&connector.name).unwrap(),
34            )
35        })
36        .collect::<Vec<_>>()
37        .join("\n");
38    let type_comment = connectors
39        .iter()
40        .map(generate_types)
41        .collect::<Vec<_>>()
42        .join("\n\n")
43        .replace("*/", "*\\/");
44    let run = if let Some(timeout_ms) = options.timeout_ms {
45        format!(
46            r#"await Promise.race([
47      __program(),
48      new Promise((_, reject) => setTimeout(() => reject(new Error("Code execution timed out after {timeout_ms}ms")), {timeout_ms}))
49    ])"#
50        )
51    } else {
52        "await __program()".to_string()
53    };
54    Ok(format!(
55        r#"/* Model-facing declarations:
56{type_comment}
57*/
58const __logs = [];
59let __seq = 0;
60const console = {{
61  log: (...values) => __logs.push(values.map(String).join(" ")),
62  info: (...values) => __logs.push(values.map(String).join(" ")),
63  warn: (...values) => __logs.push(values.map(String).join(" ")),
64  error: (...values) => __logs.push(values.map(String).join(" "))
65}};
66const __base64Encode = (value) => {{
67  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
68  let result = "";
69  for (let index = 0; index < value.length; index += 3) {{
70    const first = value[index];
71    const second = value[index + 1];
72    const third = value[index + 2];
73    result += alphabet[first >> 2];
74    result += alphabet[((first & 3) << 4) | ((second ?? 0) >> 4)];
75    result += second === undefined ? "=" : alphabet[((second & 15) << 2) | ((third ?? 0) >> 6)];
76    result += third === undefined ? "=" : alphabet[third & 63];
77  }}
78  return result;
79}};
80const __base64Decode = (value) => {{
81  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
82  const clean = value.replace(/=+$/, "");
83  const bytes = [];
84  let buffer = 0;
85  let bits = 0;
86  for (const char of clean) {{
87    buffer = (buffer << 6) | alphabet.indexOf(char);
88    bits += 6;
89    if (bits >= 8) {{
90      bits -= 8;
91      bytes.push((buffer >> bits) & 255);
92    }}
93  }}
94  return new Uint8Array(bytes);
95}};
96const __encode = (value) => {{
97  if (value === undefined) return {{ __codemode_type: "undefined" }};
98  if (typeof value === "bigint") return {{ __codemode_type: "bigint", value: value.toString() }};
99  if (value instanceof Uint8Array) return {{ __codemode_type: "binary", value: __base64Encode(value) }};
100  if (Array.isArray(value)) return value.map(__encode);
101  if (value && typeof value === "object") {{
102    return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, __encode(child)]));
103  }}
104  return value;
105}};
106const __decode = (value) => {{
107  if (Array.isArray(value)) return value.map(__decode);
108  if (value && typeof value === "object") {{
109    if (value.__codemode_type === "undefined") return undefined;
110    if (value.__codemode_type === "bigint") return BigInt(value.value);
111    if (value.__codemode_type === "binary") return __base64Decode(value.value);
112    return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, __decode(child)]));
113  }}
114  return value;
115}};
116const __dispatch = {dispatch};
117const __unwrap = (response) => {{
118  if (response.__codemode_control__ === "pause") throw new Error("__CODEMODE_PAUSE__");
119  if (response.__codemode_control__ === "error") throw new Error(response.message);
120  return __decode(response.result);
121}};
122const __namespace = (name) => new Proxy({{}}, {{
123  get: (_target, method) => {{
124    // Only `then` is withheld, and only because a namespace must not look
125    // thenable: awaiting one, or returning it from an async function, probes
126    // `then` and would otherwise receive a dispatch function and hang. Every
127    // other name is forwarded, because anything withheld here silently deletes a
128    // tool that is legitimately called that -- `inspect` and `constructor` are
129    // both real tool names in this workspace.
130    if (typeof method !== "string" || method === "then") return undefined;
131    return async (args = {{}}) => __unwrap(await __dispatch({{
132      kind: "call",
133      seq: __seq++,
134      connector: name,
135      method,
136      arguments: __encode(args)
137    }}));
138  }},
139  has: () => true
140}});
141{bindings}
142const __callBuiltin = async (method, args) => __unwrap(await __dispatch({{
143  kind: "call",
144  seq: __seq++,
145  connector: "codemode",
146  method,
147  arguments: __encode(args)
148}}));
149const codemode = {{
150  executionId: {execution_id},
151  search: (query) => __callBuiltin("search", {{ query }}),
152  describe: (target) => __callBuiltin("describe", {{ target }}),
153  step: async (name, fn) => {{
154    const seq = __seq++;
155    const decision = await __dispatch({{ kind: "begin_step", seq, name }});
156    if (decision.kind === "pause") throw new Error("__CODEMODE_PAUSE__");
157    if (decision.kind === "replay") return __decode(decision.result);
158    const result = await fn();
159    await __dispatch({{ kind: "record_step", seq, result: __encode(result) }});
160    return result;
161  }}
162}};
163try {{
164  const __program = ({code});
165  const __result = {run};
166  return {{ result: __encode(__result), error: null, logs: __logs }};
167}} catch (error) {{
168  return {{
169    result: null,
170    error: error instanceof Error ? error.message : String(error),
171    logs: __logs
172  }};
173}}"#,
174        dispatch = options.dispatch,
175        execution_id = options.execution_id,
176    ))
177}
178
179/// Names the generated program already binds, or that JavaScript refuses as a
180/// binding.
181///
182/// A connector taking one of these produces a `const` redeclaration or a parse
183/// error inside the sandbox, which surfaces far from its cause, so it is
184/// rejected here where the name is still attributable to a connector.
185pub(crate) const RESERVED_CONNECTOR_NAMES: &[&str] = &[
186    // Bindings emitted by `build_program_source`, in declaration order.
187    "__incursDispatch",
188    "__dispatch",
189    "__encode",
190    "__decode",
191    "__unwrap",
192    "__seq",
193    "__logs",
194    "__program",
195    "__result",
196    "__base64Encode",
197    "__base64Decode",
198    "__callBuiltin",
199    "codemode",
200    "console",
201    // Globals the harness itself calls.
202    "Promise",
203    "setTimeout",
204    "Error",
205    "fetch",
206    "JSON",
207    "Object",
208    "Array",
209    "String",
210    "Number",
211    "Boolean",
212    "BigInt",
213    "Uint8Array",
214    "Math",
215    "Symbol",
216    "globalThis",
217    // Reserved words. `valid_identifier` accepts these, but `const <word> = …`
218    // is a syntax error.
219    "await",
220    "break",
221    "case",
222    "catch",
223    "class",
224    "const",
225    "continue",
226    "debugger",
227    "default",
228    "delete",
229    "do",
230    "else",
231    "enum",
232    "export",
233    "extends",
234    "false",
235    "finally",
236    "for",
237    "function",
238    "if",
239    "implements",
240    "import",
241    "in",
242    "instanceof",
243    "interface",
244    "let",
245    "new",
246    "null",
247    "package",
248    "private",
249    "protected",
250    "public",
251    "return",
252    "static",
253    "super",
254    "switch",
255    "this",
256    "throw",
257    "true",
258    "try",
259    "typeof",
260    "var",
261    "void",
262    "while",
263    "with",
264    "yield",
265];
266
267fn validate_names(connectors: &[ConnectorDescription]) -> Result<(), String> {
268    let mut seen = std::collections::BTreeSet::new();
269    for connector in connectors {
270        if RESERVED_CONNECTOR_NAMES.contains(&connector.name.as_str()) {
271            return Err(format!("Connector name \"{}\" is reserved", connector.name));
272        }
273        if !valid_identifier(&connector.name) {
274            return Err(format!(
275                "Connector name \"{}\" is not a valid JavaScript identifier",
276                connector.name
277            ));
278        }
279        if !seen.insert(&connector.name) {
280            return Err(format!("Duplicate connector name \"{}\"", connector.name));
281        }
282        let mut methods = std::collections::BTreeSet::new();
283        for tool in &connector.tools {
284            if !methods.insert(&tool.name) {
285                return Err(format!(
286                    "Duplicate tool name \"{}\" in connector \"{}\"",
287                    tool.name, connector.name
288                ));
289            }
290        }
291    }
292    Ok(())
293}
294
295fn valid_identifier(value: &str) -> bool {
296    let mut chars = value.chars();
297    chars
298        .next()
299        .is_some_and(|ch| ch == '_' || ch == '$' || ch.is_ascii_alphabetic())
300        && chars.all(|ch| ch == '_' || ch == '$' || ch.is_ascii_alphanumeric())
301}
302
303#[cfg(test)]
304mod tests {
305    use serde_json::json;
306
307    use crate::{ConnectorTool, ToolAnnotations};
308
309    use super::*;
310
311    #[test]
312    fn emits_shared_connector_and_step_harness() {
313        let source = build_program_source(
314            "state.read({ id: 1 })",
315            &[ConnectorDescription {
316                name: "state".to_string(),
317                instructions: None,
318                tools: vec![ConnectorTool {
319                    name: "read".to_string(),
320                    description: None,
321                    input_schema: json!({"type": "object"}),
322                    output_schema: None,
323                    instructions: None,
324                    examples: Vec::new(),
325                    annotations: ToolAnnotations::default(),
326                    policy: crate::ToolPolicy::default(),
327                }],
328            }],
329            &ProgramSourceOptions {
330                dispatch: "async (payload) => payload".to_string(),
331                execution_id: "\"test\"".to_string(),
332                timeout_ms: None,
333            },
334        )
335        .unwrap();
336
337        assert!(source.contains("const state"));
338        assert!(source.contains("const __dispatch = async (payload) => payload"));
339        assert!(source.contains("step: async (name, fn)"));
340    }
341
342    fn named(name: &str) -> ConnectorDescription {
343        ConnectorDescription {
344            name: name.to_string(),
345            instructions: None,
346            tools: vec![ConnectorTool {
347                name: "read".to_string(),
348                description: None,
349                input_schema: json!({"type": "object"}),
350                output_schema: None,
351                instructions: None,
352                examples: Vec::new(),
353                annotations: ToolAnnotations::default(),
354                policy: crate::ToolPolicy::default(),
355            }],
356        }
357    }
358
359    fn build(name: &str) -> Result<String, String> {
360        build_program_source(
361            "return 1",
362            &[named(name)],
363            &ProgramSourceOptions {
364                dispatch: "async (payload) => payload".to_string(),
365                execution_id: "\"test\"".to_string(),
366                timeout_ms: None,
367            },
368        )
369    }
370
371    #[test]
372    fn rejects_names_the_harness_already_binds() {
373        // These are `const`-declared by `build_program_source` itself, so a
374        // connector taking one produces a redeclaration inside the sandbox.
375        for name in ["__callBuiltin", "__base64Encode", "__base64Decode"] {
376            assert!(
377                build(name).is_err(),
378                "{name} is bound by the harness but was accepted"
379            );
380        }
381    }
382
383    #[test]
384    fn rejects_reserved_words_that_cannot_be_bound() {
385        // `valid_identifier` accepts each of these, but `const class = {…}` is
386        // a syntax error that would surface far from the offending connector.
387        for name in ["class", "default", "delete", "new", "import", "return"] {
388            assert!(
389                build(name).is_err(),
390                "reserved word {name} was accepted as a connector name"
391            );
392        }
393    }
394
395    #[test]
396    fn accepts_an_ordinary_name_that_merely_resembles_a_reserved_one() {
397        // The guard is exact-match, so renaming around a collision works.
398        for name in ["mcp_fetch", "classroom", "defaults", "newRelic"] {
399            assert!(build(name).is_ok(), "{name} should be a usable namespace");
400        }
401    }
402}