Skip to main content

incurs_codemode/
program.rs

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