Terminal embedding
Use createFxTerminal() to put the fx terminal inside your application. It runs fx-term.wasm and renders through a terminal component such as xterm.js. The try page uses this API.
If you are building your own agent interface, start with the examples or the Node SDK instead.
Install libfx and check the WebAssembly runtime requirements before loading the terminal.
npm install libfx
Embed the terminal
Install xterm.js for the terminal interface:
npm install @xterm/xterm
The example below loads the terminal build that ships with the package:
<div id="terminal" style="height: 600px"></div>
import { Terminal } from '@xterm/xterm'
import '@xterm/xterm/css/xterm.css'
import { createFxTerminal, supportsJspi, xtermAdapter } from 'libfx/browser'
const container = document.querySelector('#terminal')
if (!(container instanceof HTMLElement)) {
throw new Error('Missing #terminal element')
}
if (!supportsJspi()) {
throw new Error('fx requires JSPI support')
}
const terminal = new Terminal()
terminal.open(container)
const runtime = await createFxTerminal({
terminal: xtermAdapter(terminal),
})
await runtime.interactive
runtime.exited.then((exitCode) => {
console.log(`fx exited with code ${exitCode}`)
})
Await runtime.interactive before sending input. Use write(), resize(), and abort() to control the terminal; abort() stops it and releases subscriptions. The exited promise resolves when fx stops.
The browser entry point resolves both WebAssembly assets relative to the installed package. If your bundler does not serve those files, copy them to a public asset location and pass wasm explicitly. It accepts a URL, Response, bytes, or a compiled WebAssembly.Module.
Connect host adapters
The adapters below belong to the terminal. Agents use named options and checkpoints; only the fetch option is shared. Add the adapters your application needs.
Choose an adapter
| Option | Used by | Purpose |
|---|---|---|
configStore | Terminal | Restore accepted model and mode settings. |
promptHistoryStore | Terminal | Preserve terminal input history. |
sessionStore | Terminal | Persist conversations. |
openUrl and oauthSessionStore | Terminal | Complete browser device login. |
workspace | Terminal | Add completion-only shell.run. |
fetch | Both | Control network requests. |
Config store
Use configStore to restore settings such as the active model and mode:
const configStore = {
get(id) {
return localStorage.getItem(`fx.core.config.${id}`)
},
set(id, value) {
localStorage.setItem(`fx.core.config.${id}`, value)
},
}
get(id) returns a string or null. fx calls set(id, value) only after it accepts the new value.
Prompt history store
Use promptHistoryStore to preserve terminal input history across page loads:
type PromptHistoryStore = {
load(
workspaceRoot: string,
limit: number,
): string[] | Promise<string[]>
append(
workspaceRoot: string,
value: string,
timestampMs: number,
):
| void
| 'duplicate'
| 'record_too_large'
| Promise<void | 'duplicate' | 'record_too_large'>
clear(workspaceRoot: string): void | Promise<void>
}
load() returns prompts from oldest to newest. Return duplicate when the latest stored prompt already matches, or record_too_large when the app declines to store it.
Session store
Use sessionStore to persist conversations. Pass args: ['--resume', 'last'] to createFxTerminal() to resume the conversation with the latest updatedAtMs value.
type SessionStore = {
load(id: string):
| { bytes: Uint8Array; revision: string }
| null
| Promise<{ bytes: Uint8Array; revision: string } | null>
commit(
id: string,
bytes: Uint8Array,
expectedRevision: string | undefined,
): { revision: string } | Promise<{ revision: string }>
list():
| Array<{ id: string; updatedAtMs: number }>
| Promise<Array<{ id: string; updatedAtMs: number }>>
remove(id: string): void | Promise<void>
}
Treat bytes as opaque data. commit() receives undefined for the first revision and must return a new revision after every write.
If expectedRevision is stale, throw an error whose code is FX_SESSION_REVISION_CONFLICT. The conflict prevents the stale snapshot from overwriting the current record. For agent persistence, use checkpoints, not this terminal store.
Device login
createFxTerminal() supports browser device login through openUrl and oauthSessionStore:
const terminal = await createFxTerminal({
wasm: './fx-term.wasm',
terminal: terminalAdapter,
openUrl(url) {
return window.open(url, '_blank', 'noopener,noreferrer') !== null
},
oauthSessionStore,
})
Return false from openUrl() when the app cannot open the verification page. fx still prints the URL and verification code in the terminal.
The OAuth store uses the following contract:
type OAuthSessionStore = {
load():
| { bytes: Uint8Array; revision: string }
| null
| Promise<{ bytes: Uint8Array; revision: string } | null>
commit(
bytes: Uint8Array,
expectedRevision: string | undefined,
): { revision: string } | Promise<{ revision: string }>
remove(expectedRevision: string | undefined):
| void
| boolean
| 'missing'
| Promise<void | boolean | 'missing'>
}
Store the OAuth bytes without inspecting or logging them. Return false or missing when there is no record to remove. Reject a stale commit or removal with FX_OAUTH_SESSION_REVISION_CONFLICT.
Browser workspace
The optional workspace adapter adds completion-only shell.run, implemented by your app. The model-facing schema is exactly { action: 'run', command }; native shell profiles, TTYs, and managed running handles are unavailable:
type WorkspaceAdapter = {
info: {
version: 1
root: string
cwd: string
home: string
gitAvailable: false
ephemeral: true
}
permission: 'allow-sandboxed' | 'prompt'
exec(input: {
command: string
cwd: string
signal: AbortSignal
timeoutMs: number
outputLimitBytes: number
}):
| { stdout: string; stderr: string; exitCode: number }
| Promise<{ stdout: string; stderr: string; exitCode: number }>
}
root, cwd, and home must be normalized absolute paths without NUL bytes. Version 1 also requires:
cwdequalsroot.gitAvailableisfalse.ephemeralistrue.permissionisallow-sandboxedorprompt.
permission declares how far your app vouches for its own execution boundary. With allow-sandboxed, fx runs every command the adapter accepts without asking, because your sandbox is the boundary. With prompt, command calls go through the normal fx permission flow of rules, session grants, and the active mode.
exec() returns { stdout, stderr, exitCode }. Commands are limited to 64 KiB of UTF-8, combined output previews to 64 KiB, and execution to 30 seconds. The adapter must stop work when its AbortSignal is cancelled. Without a workspace adapter, the WebAssembly terminal does not expose shell.run.
The try page implements this adapter with an in-memory bash environment.
Network requests
The SDK uses globalThis.fetch by default. Pass a fetch function to createFxTerminal() or createFxAgent() when your app needs to proxy requests, add authentication, or apply its own network policy. Browser Cross-Origin Resource Sharing (CORS) and Content Security Policy (CSP) rules still apply.