Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/client/src/language_execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,12 @@ pub struct InlineExecutionOptions {

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum JavaScriptModuleFormat {
/// Evaluate each call as an independent root ES module. A retained context
/// preserves `globalThis`, not the module's lexical scope.
#[default]
Module,
/// Use script/CommonJS semantics, including REPL-style top-level bindings
/// that remain visible to later calls in a retained context.
CommonJs,
}

Expand Down
10 changes: 8 additions & 2 deletions docs/content/docs/javascript.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,21 @@ interpolated into source.

## Keep state between calls

Pass a `contextId` to keep globals, imports, and modules alive across calls in
one retained V8 isolate.
Pass a `contextId` to keep one V8 isolate and its global state alive across
calls.

<CodeSnippet file="examples/js-sdk-overview/src/contexts.ts" />

A context runs one operation at a time — reusing a busy `contextId` fails
immediately. Files, npm, Bash, and type checks may pass the same id, but they run
in fresh processes and never touch retained memory.

JavaScript defaults to `format: "module"`. Each call is a separate root ES
module, so its imports, top-level declarations, and exports are scoped to that
call. Put values on `globalThis` when a later call in the same context needs
them. For REPL-style script semantics where top-level lexical declarations stay
visible, pass `format: "commonjs"` consistently for that context.

Create the context explicitly before use; an unknown id fails instead of silently
starting fresh state. A context pins to the first inline language that used it,
though JavaScript and TypeScript intentionally share one isolate.
Expand Down
11 changes: 7 additions & 4 deletions examples/js-sdk-overview/src/contexts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ const runtime = await AgentOs.create();
try {
await runtime.createContext("analysis");

await runtime.javascript.execute("const answer = 40", {
await runtime.javascript.execute("globalThis.answer = 40", {
contextId: "analysis",
});

const result = await runtime.javascript.evaluate<number>("answer + 2", {
contextId: "analysis",
});
const result = await runtime.javascript.evaluate<number>(
"globalThis.answer + 2",
{
contextId: "analysis",
},
);
console.log(result.outcome === "succeeded" ? result.value : result.error); // 42

// Delete an idle context when you are done with it. `contexts.reset()`
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/language-execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ export interface InlineExecutionOptions extends LanguageExecutionOptions {
}

export interface JavaScriptExecutionOptions extends InlineExecutionOptions {
/**
* `module` (the default) evaluates each call as an independent root ES module.
* A retained context preserves `globalThis`, not the module's lexical scope.
* Use `commonjs` for REPL-style top-level bindings shared by later calls.
*/
format?: "module" | "commonjs";
filePath?: string;
}
Expand Down