# fx complete documentation --- --- > Every fx documentation page in one machine-readable file. --- --- --- title: "Quick start" description: "Install fx, run a first request, and learn the commands worth knowing." canonical_url: https://fx.sh/docs markdown_url: https://fx.sh/docs.md --- # Quick start ## Install and sign in ```bash curl -fsSL https://fx.sh/setup.sh | bash ``` The installer places `fx` in `~/.local/bin`. Read [Installation](https://fx.sh/docs/getting-started/installation.md) before piping the script to a shell, or if `fx` is not on your `PATH` afterward. Sign in with Vercel: ```bash fx login ``` `fx login` opens the Vercel authorization flow and saves the session for later runs. An AI Gateway API key works too; see [Authentication](https://fx.sh/docs/getting-started/authentication.md). ## Run your first request Start fx from the project you want to work on. The launch directory becomes the primary workspace: ```bash cd path/to/project ``` ```bash fx ``` Type a request that names real files or commands, then press enter: ```text Read src/ and tell me how requests are routed. Then add a test for the error path in the router and run the test suite. ``` fx streams its reply and shows the tools it runs. To redirect work already in progress, type a follow-up and press enter. Press escape to cancel, or ctrl+o to inspect the full transcript. Ctrl+c clears a nonempty draft; with an empty draft, it cancels the active turn. ## What fx checks before it acts fx starts in `auto` permission mode. It applies your saved rules, runs routine actions, and reviews actions that need a closer look. If the review raises a concern or cannot complete, fx holds the action and returns guidance to the agent. Use `ask` mode if you want approval prompts for unresolved sensitive actions. Reading files within the workspace does not normally need approval; changing files, running commands, and accessing external paths are subject to the permission policy. Switch modes with `/permissions` at any time, and see [Permissions](https://fx.sh/docs/configure-fx/permissions.md) for rules, modes, and the cost of automatic review. > **Approve deliberately** > > Read the scope shown in an approval prompt before accepting it. Full access mode disables fx permission checks; use it only in an environment you trust. ## Things to know In an interactive session, type `/` to open the available commands. See [Slash commands](https://fx.sh/docs/using-fx/slash-commands.md) for the full reference. | Need | Use | | --- | --- | | Open available commands | Type `/` | | Find a file | Type `@` | | Find a skill | Type `$` | | Inspect the current model, workspace, permissions, and session | `/status` | | Choose a model | `/models` | | Change the permission mode | `/permissions` | | Start a new session | `/new` | | Attach an image | `/image ./path.png` | | View local usage | `/usage` | | Turn completion sounds on or off | `/sound on` or `/sound off` | | Share feedback | `/feedback` | | Create a private diagnostic trace | `/trace` | Every interactive command is listed in [Slash commands](https://fx.sh/docs/using-fx/slash-commands.md). ## Shortcuts | Action | Shortcut | | --- | --- | | Insert a newline | shift+enter, alt+enter, or backslash then enter | | Move through prompt history | up or down at the edge of the draft | | Steer the active turn with a follow-up | enter | | Interrupt the current turn | escape, or ctrl+c with an empty draft | | Open Review and Full transcript | ctrl+o, then left or right | ## Continue your work Open the session picker to choose a saved session: ```bash fx -r ``` Resume the latest session for the current workspace directly: ```bash fx resume last ``` See [Sessions](https://fx.sh/docs/using-fx/sessions.md) for recovery and compaction. For a single noninteractive request, use [`fx ask`](https://fx.sh/docs/using-fx/fx-ask.md). If something does not work as described here, start with [Troubleshooting](https://fx.sh/docs/using-fx/troubleshooting.md). --- [Browse all fx documentation](https://fx.sh/llms.txt) --- --- title: "Installation" description: "Install fx, review what the installer does, and verify the local binary." canonical_url: https://fx.sh/docs/getting-started/installation markdown_url: https://fx.sh/docs/getting-started/installation.md --- # Installation fx supports macOS and Linux on x86_64 and arm64. The installer needs `curl` or `wget`, plus `tar`. ## Install the latest release Run the installer: ```bash curl -fsSL https://fx.sh/setup.sh | bash ``` `https://fx.sh/setup.sh` is the canonical installer. Nothing else needs to be trusted to install fx. ## Review the installer before running it Piping a script to a shell runs whatever the server returns. If you or your organization require a review first, download it, read it, and run the copy you read: ```bash curl -fsSL https://fx.sh/setup.sh -o setup.sh ``` ```bash less setup.sh ``` ```bash bash setup.sh ``` What the script does, in order: detect your platform, resolve the latest published version, download and unpack that release archive, install the binary, and offer to put it on your `PATH`. Specifically: - It installs to `~/.local/bin`. Set `FX_INSTALL_DIR` to install somewhere else. - If the install directory is not already on your `PATH`, it appends a `PATH` line to your shell profile: `~/.zshrc`, `~/.bash_profile` or `~/.bashrc`, or `~/.config/fish/config.fish`. It skips that edit when the directory is already mentioned in the file. - Release archives are downloaded over HTTPS from Vercel's release storage. The script does not check a signature or a published checksum, so an audited install should read the script, fetch the archive it names, and verify that artifact with your own process. For automation, pass a version so a run is reproducible instead of tracking the latest release: ```bash curl -fsSL https://fx.sh/setup.sh | bash -s -- ``` ## Put fx on your PATH If your shell cannot find `fx` after installing, add the install directory to the current shell: ```bash export PATH="$HOME/.local/bin:$PATH" ``` Restarting the shell picks up the line the installer added to your profile. ## Verify the install Print the installed version: ```bash fx --version ``` Run local health checks: ```bash fx doctor ``` `fx doctor` reports the workspace, configuration, authentication, resolved startup settings, local session state, and Git integrations without starting an agent turn. ## Upgrade Upgrade to the latest release on the selected channel: ```bash fx upgrade ``` Add `--channel stable` or `--channel dev` to select and remember a release channel. The interactive shell also reports available updates and can install them in place. Set `FX_AUTO_UPGRADE=0` to skip automatic upgrade checks for one process. When an automatic update is installed, the footer offers `ctrl+g` to reload. Press it with an empty composer and no active response or focused view. fx relaunches the installed binary and resumes the current session. If the session cannot be handed off safely, fx stays open and explains what must finish or close first. ## Build from source Building from source requires Zig 0.16 or newer. Clone the repository, build a release binary, then verify it: ```bash git clone https://github.com/vercel-labs/fx.git cd fx zig build -Doptimize=ReleaseSafe ./zig-out/bin/fx --version ``` ## Optional tools - [`gh`](https://cli.github.com/) is required only to [publish drafts with `fx pr --create` or `fx issue --create`](https://fx.sh/docs/using-fx/cli.md#run-fx). - [`tmux`](https://github.com/tmux/tmux/wiki/Installing) is required only for the terminal end-to-end tests described in the repository's [contributing guide](https://github.com/vercel-labs/fx/blob/main/CONTRIBUTING.md#testing-typescript). > **Install only what you can verify** > > Fetch the installer from `fx.sh` over HTTPS, read it when your policy requires review, and pin a version in automation. fx itself never needs elevated privileges to install or run. Next, [authenticate](https://fx.sh/docs/getting-started/authentication.md) and follow the [quick start](https://fx.sh/docs.md). If the install did not work, see [Troubleshooting](https://fx.sh/docs/using-fx/troubleshooting.md). --- [Browse all fx documentation](https://fx.sh/llms.txt) --- --- title: "Authentication" description: "Choose how fx accesses AI models." canonical_url: https://fx.sh/docs/getting-started/authentication markdown_url: https://fx.sh/docs/getting-started/authentication.md --- # Authentication fx uses one provider at a time. [Vercel AI Gateway](https://vercel.com/docs/ai-gateway) is the default; sign in with Vercel or use an [API key](https://vercel.com/docs/ai-gateway/authentication-and-byok). You can also connect an eligible Codex or Grok subscription. > **Keep credentials out of project config** > > Use [`fx login`](#sign-in) or [`fx setup`](#use-an-ai-gateway-api-key) for built-in provider credentials. Custom connections read their configured environment variable. Use your CI provider's secret manager for automation. Do not put secrets in `.fx.json`. ## Model providers | Provider | Requires | Sign in | | --- | --- | --- | | Vercel AI Gateway | A Vercel account, or AI Gateway billing | `fx login` or `fx setup` | | Codex | An eligible ChatGPT subscription | `fx login codex` | | Grok | An eligible Grok subscription | `fx login grok` | Switch providers with `fx provider`, or open `/provider` inside fx. `/setup` and `/login` open the same provider picker: ```bash fx provider codex ``` > **Switching to a subscription signs you in** > > Selecting Codex or Grok starts browser sign-in if no saved session is available. Switching to Gateway requires a configured Gateway credential. A credential authorizes only its own provider. A Gateway key cannot serve Codex, and a Codex session cannot serve Gateway, so the provider you select decides which credential fx looks for. Each provider carries its own model catalog, and `/models` and `fx models` list the models of the active provider. `fx credits` reports a balance on Gateway only; on a subscription provider it answers that credits are unavailable. ## Custom endpoints and BYOK The [custom model connections preview](https://fx.sh/docs/configure-fx/custom-model-connections.md) adds profile-defined endpoints for Ollama, vLLM, OpenRouter, and other compatible Chat Completions servers. It requires a build containing that preview; the built-in sign-in flows above are unchanged. A custom connection can be anonymous or read its own bearer key from a named environment variable. `fx setup` still configures a Gateway key, not a custom-provider key. The interactive sign-in picker lists built-in providers; select a configured connection with `fx provider ` or `FX_PROVIDER`. See [Bring your own key](https://fx.sh/docs/configure-fx/custom-model-connections.md#bring-your-own-key) for credential handling and [connection examples](https://fx.sh/docs/configure-fx/custom-model-connections.md#ollama) before sending a request. ## Sign in Sign in with Vercel: ```bash fx login ``` `fx login` opens the Vercel authorization flow. The OAuth session is saved in `~/.fx/auth.json` and refreshed when needed. Pass a provider to sign in to a subscription instead: `fx login codex` or `fx login grok`. Either one also makes that provider active, so signing in and switching are the same step. In a headless environment, set `FX_NO_OPEN_BROWSER=1` before `fx login` to print the authorization URL instead of trying to open it. ## Use an AI Gateway API key `fx setup` prompts you to paste an API key without displaying it, then saves the key in the platform credential store: ```bash fx setup ``` On macOS, fx stores the key in Keychain. On Linux, it stores the key in `~/.fx/api-key` and makes the file readable only by your user. For CI, store the key in your CI provider's secret manager and expose it as `AI_GATEWAY_API_KEY` only to the job that runs fx. Do not write the key directly in the workflow. ## Credential selection On Gateway, unless you choose a source with [`/setup`](https://fx.sh/docs/using-fx/slash-commands.md#account-model-and-runtime), fx uses the first available credential in this order: 1. [`VERCEL_OIDC_TOKEN`](https://vercel.com/docs/ai-gateway/authentication-and-byok/oidc), when provided automatically by a Vercel runtime 2. `AI_GATEWAY_API_KEY`, set for the current process 3. a saved `fx login` session 4. an API key saved with `fx setup` Choosing a source in `/provider` makes it the default across restarts. If that source is unavailable, fx does not fall back to another credential. Repair it or choose a different source explicitly. Codex and Grok use their own saved subscription sessions. ## Change Vercel team An `fx login` session can switch between the Vercel teams available to your account: ```bash fx teams ``` The picker saves the selected team in the login session. That team scopes AI Gateway requests, the model catalog, and Gateway credit checks, so model availability can differ between teams. Run `/status` to inspect the active `gateway_team`. `fx teams` requires an `fx login` session. ## Inspect or remove credentials Run `fx status` to inspect the active model, credential source and team, permission mode, workspace, and update channel. When the active provider is not Gateway it also reports `model_source` and `connected_providers`: ```bash fx status ``` Remove the saved Vercel login: ```bash fx logout ``` `fx logout` removes the saved OAuth session and revokes it with Vercel. It does not remove a stored API key. Pass a provider to sign out of a subscription: `fx logout grok` revokes its token as well, while `fx logout codex` removes the session from this machine only. To withdraw fx's access to a ChatGPT account, remove it from the connected applications in that account. --- [Browse all fx documentation](https://fx.sh/llms.txt) --- --- title: "Sessions" description: "Save, resume, recover, compact, and inspect fx sessions." canonical_url: https://fx.sh/docs/using-fx/sessions markdown_url: https://fx.sh/docs/using-fx/sessions.md --- # Sessions fx saves interactive conversations under `~/.fx/sessions/`. Starting plain `fx` creates a fresh session. Use `fx sessions` to find the ID of a saved conversation. ## List and inspect List sessions for the current workspace: ```bash fx sessions ``` The default list is scoped to the current workspace. Use `--all` for every workspace, `--limit <1-100>` to change the page size, and `--cursor ` for the next page. Inspect the latest workspace session as JSON. The response includes its session ID, timestamps, and saved conversation history: ```bash fx session last --json ``` ```bash fx session --id --json ``` `--id` forces the value to be read as an exact session ID instead of the `last` keyword or a subcommand such as `migrate` or `recover`. Every `fx session` form accepts it. ## Resume Open the interactive session picker: ```bash fx -r ``` Resume the latest workspace session directly: ```bash fx resume last ``` Copy an ID from `fx sessions` to resume a specific session: ```bash fx resume ``` An explicit ID can be rebound to the current workspace. `last` remains workspace-scoped. `-c`, `--continue`, and `--resume-last` are equivalent leading flags for the latest workspace session. `fx ask` can continue the same conversation without opening the shell: ```bash fx ask --resume last "continue with the tests" ``` ## Recover interrupted work fx persists partial model responses, tool progress, and recovery checkpoints. In an interactive session, `/continue` resumes a paused response. For a headless run: ```bash fx ask --resume last --continue-recovery ``` To create a separate recoverable copy while leaving the source session unchanged, copy the ID from `fx sessions`: ```bash fx session recover ``` `fx doctor` inspects saved sessions and prints the exact recovery command when it finds a problem it can fix. See [Troubleshooting](https://fx.sh/docs/using-fx/troubleshooting.md#a-session-will-not-open-or-resume). ## Migrate an older session ```bash fx session migrate ``` Migration rewrites a saved session into the current format. Add `--allow-large` only after verifying an oversized legacy snapshot. ## Compact long sessions When a model request reaches 80% of its usable input capacity, fx summarizes older context and continues the same turn in a fresh context window. It keeps recent tool exchanges intact and preserves the full saved transcript. Run `/compact` to summarize the context now and wait for your next prompt: ```bash /compact ``` The summary remains available after you resume the session. If compaction is cancelled or fails, fx keeps the previous context. --- [Browse all fx documentation](https://fx.sh/llms.txt) --- --- title: "fx ask" description: "Run noninteractive requests, return JSON, and continue saved sessions." canonical_url: https://fx.sh/docs/using-fx/fx-ask markdown_url: https://fx.sh/docs/using-fx/fx-ask.md --- # fx ask `fx ask` runs one noninteractive request and exits. Use it in scripts, continuous integration, or whenever you do not need the interactive shell. ```bash fx ask "explain what this repository does" ``` ## Pass a prompt to `fx ask` Pass the prompt as command arguments. In a shell script, `printf` can send generated prompt text to `fx ask` through standard input (`stdin`): ```bash printf "summarize src/core\n" | fx ask ``` Use `--image ` to attach an image. Repeat the flag to attach multiple images. ```bash fx ask --image ./ui.png "describe this interface" ``` See [Vision](https://fx.sh/docs/capabilities/vision.md) for supported formats and image routing. ## Choose the model, effort, and speed `--model ` overrides the model for the run, `--effort ` overrides the reasoning effort, and `--fast` or `--no-fast` turn [fast mode](https://fx.sh/docs/configure-fx/models.md#fast-mode) on or off where the model supports it: ```bash fx ask --model openai/gpt-5.4 --effort high --fast "summarize the current changes" ``` These flags apply to the run only and never change your saved defaults. With `--resume`, they win over the resumed session's saved preferences for that run. Overriding the model drops the default-enabled fast mode unless you pass `--fast` to restore it. ## Use `fx ask` in scripts For shell scripts and CI, redirect standard output (`stdout`) to receive raw assistant Markdown. Progress and diagnostics remain on standard error (`stderr`). Use `--json` when a program needs structured fields instead of Markdown: ```bash fx ask --json "summarize the current changes" ``` The command returns one JSON object: ```json { "output": "Assistant Markdown", "final_output": "Final assistant response", "exit_code": 0, "model": "provider/model-id", "session_id": "session-id", "steps": 1, "usage": { "input_tokens": 1200, "output_tokens": 450 }, "tool_calls": [ { "name": "read_file", "status": "success" } ] } ``` Failures use a nonzero `exit_code` and can include an `error` field. Tool calls always include `name` and `status`; some tools include additional result fields. `output` includes the assistant text produced during the request. `final_output` contains the completed final response, or an empty string when no final response completed. `usage.input_tokens` and `usage.output_tokens` sum the counts reported by main-agent completions, including usage recorded before an error. A count is `null` when no completion reported it; reported zero remains `0`. These totals exclude subagent, helper-model, and provider-tool usage. Use [`fx usage`](https://fx.sh/docs/using-fx/usage-and-costs.md) for recorded local usage and costs. Parse stdout while keeping progress and diagnostics visible on stderr: ```bash fx ask --json "inspect this repository" | jq -r .output ``` Add `--no-save` for a run that should not create a session. In JSON output, `session_id` is an empty string. `--no-save` cannot be combined with `--resume`. ## Continue a session with `fx ask` Use `--resume last` to continue the latest session for the current workspace: ```bash fx ask --resume last "now add tests" ``` You can pass a session ID instead of `last`. See [Sessions](https://fx.sh/docs/using-fx/sessions.md) to inspect sessions or recover an interrupted response. ## Handle permissions in noninteractive runs By default, `fx ask` does not prompt for approval. Saved rules still apply. In `auto` mode, an action that raises a concern is held and returned to the agent with guidance; automatic review does not open a human approval prompt. Use `--prompt-permissions` when running from a terminal if you want configured approval prompts. They appear on stderr, leaving JSON stdout parseable. Piped or redirected input remains noninteractive, and an action that needs human approval fails instead of waiting. `--full-access` disables fx permission checks for the run. Use it only in an environment you trust. `--yolo` remains a backward-compatible alias. An interrupted run exits with code `130`. See [Troubleshooting](https://fx.sh/docs/using-fx/troubleshooting.md#fx-stops-before-running-a-tool) when a run ends before the tool executes. Run `fx ask --help` for the complete flag reference. --- [Browse all fx documentation](https://fx.sh/llms.txt) --- --- title: "CLI commands" description: "Reference for top-level fx commands and global flags." canonical_url: https://fx.sh/docs/using-fx/cli markdown_url: https://fx.sh/docs/using-fx/cli.md --- # CLI commands Run `fx` with no command to start a fresh interactive session. Use the commands below to run requests, continue sessions, inspect local state, and configure fx. `fx --help` prints the exact option list for one command. Interactive `/` commands are documented separately in [Slash commands](https://fx.sh/docs/using-fx/slash-commands.md). These commands accept `--json` for machine-readable output: `ask`, `status`, `doctor`, `permissions`, `models`, `workspace`, `session`, `sessions`, `background`, `usage`, `credits`, and `upgrade`. The others print text only. ## Run fx | Command | Purpose | | --- | --- | | `fx` | Start a fresh interactive session. | | `fx ask ` | Run one noninteractive request. See [`fx ask`](https://fx.sh/docs/using-fx/fx-ask.md). | | `fx resume [last\|]` | Continue a saved interactive session; `--id ` forces an exact ID. | | `fx pr [context]` | Draft a pull request; add `--create` to publish with `gh`. | | `fx issue [context]` | Draft an issue; add `--create` to publish with `gh`. | | `fx acp` | Start an ACP server over stdio. See [ACP server](https://fx.sh/docs/using-fx/acp.md). | `fx pr` and `fx issue` accept `--auto` to review unresolved permission requests automatically, and must run inside a Git repository. ## Sessions and local records | Command | Purpose | | --- | --- | | `fx sessions` | List sessions for the current workspace. Accepts `--all`, `--limit <1-100>`, and `--cursor `. | | `fx session ` | Inspect one session. `--id ` forces an exact ID. | | `fx session migrate ` | Migrate a saved session to the current format; `--allow-large` permits an oversized session. | | `fx session recover ` | Copy a recoverable corrupt session without changing the source. | | `fx background [last\|]` | List or inspect background commands. | | `fx usage [--period <24h\|7d\|30d>]` | Show token usage and spend recorded by fx on this machine. | See [Sessions](https://fx.sh/docs/using-fx/sessions.md) for the full workflow. ## Account and configuration | Command | Purpose | | --- | --- | | `fx login [vercel\|codex\|grok]` | Sign in with Vercel, or with a Codex or Grok subscription. | | `fx logout [vercel\|codex\|grok]` | Sign out of the saved session for that provider. | | `fx provider ` | Choose the provider fx uses for models. | | `fx setup` | Configure an AI Gateway API key. | | `fx teams` | Choose the Vercel team used by AI Gateway. | | `fx credits` / `fx balance` | Show the AI Gateway credit balance for the active credential. Gateway only. | | `fx models` | List the models of the active provider. | | `fx permissions` | Show the permission mode and rules. | | `fx workspace [list\|add PATH\|remove PATH\|clear]` | Manage additional workspace directories. | `fx login`, `fx logout`, `fx setup`, and `fx teams` are interactive. Selecting a built-in subscription provider can start sign-in when it has no saved session. See [Authentication](https://fx.sh/docs/getting-started/authentication.md). The [custom model connections preview](https://fx.sh/docs/configure-fx/custom-model-connections.md) also accepts `fx provider `. Define the connection in your private profile first. This selection does not open a custom-provider login flow; bearer credentials come from the connection's named environment variable. Use `FX_PROVIDER=` for a process-only selection. ## MCP management Top-level MCP commands operate without opening the interactive shell or contacting AI Gateway: | Command | Purpose | | --- | --- | | `fx mcp add [args...]` | Add or replace a local stdio server in the private profile. | | `fx mcp add --transport http ` | Add or replace a Streamable HTTP server in the private profile. | | `fx mcp list` | Inspect profile and project configuration plus stored authentication without connecting servers. | | `fx mcp list --connect` | Connect configured servers, run discovery, and show live health. | | `fx mcp auth ` | Run the remote OAuth flow. | | `fx mcp logout ` | Remove stored credentials and attempt remote revocation when supported. | | `fx mcp path` | Print the private profile path. | | `fx mcp remove ` | Remove a server from the private profile. | | `fx mcp trust approve\|reject ` | Approve or reject one project server for the current workspace. | | `fx mcp trust approve-all` | Approve every server in the current workspace `.mcp.json`. | | `fx mcp trust reset` | Clear the current workspace's project MCP choices. | These commands print text rather than structured JSON. See [MCP](https://fx.sh/docs/capabilities/mcp.md) for configuration shapes, interactive commands, and project trust behavior. ## Diagnostics and maintenance | Command | Purpose | | --- | --- | | `fx status` | Show configuration and runtime information. | | `fx doctor` | Run local health and preflight checks. | | `fx upgrade [--channel ]` | Upgrade fx and optionally remember a release channel. | | `fx help` | Show top-level help. `-h` and `--help` are aliases. | ## Global flags Global flags are leading flags: place them before a command. - `--full-access` disables fx permission checks for the run. `--yolo` remains a backward-compatible alias. See [Permissions](https://fx.sh/docs/configure-fx/permissions.md) before using it. - `--context-limit ` overrides one context limit and can be repeated. - `--add-dir ` adds a process-only workspace directory and can be repeated. - `--no-additional-dirs` ignores saved additional directories for this process. - `--provider ` overrides the model provider for an interactive session: `gateway`, `codex`, `grok`, or a [configured provider](https://fx.sh/docs/configure-fx/custom-model-connections.md) name. The saved provider in settings is unchanged. - `--model ` overrides the model for an interactive session. - `--effort ` overrides the reasoning effort for an interactive session. - `--fast` and `--no-fast` turn fast mode on or off for an interactive session. See [Models](https://fx.sh/docs/configure-fx/models.md#fast-mode). - `-r` opens the interactive session picker. - `-c`, `--continue`, and `--resume-last` resume the latest workspace session. - `--resume [last|]` resumes the latest session when no target is provided, while `--resume-` resumes an exact session ID. - `-h`, `--help` prints help; `-v`, `--version` prints the version. See [Additional workspaces](https://fx.sh/docs/configure-fx/additional-workspaces.md) and [Context limits](https://fx.sh/docs/configure-fx/context-limits.md) for the full behavior of those repeatable global flags. ## Environment overrides `FX_MODEL`, `FX_PERMISSION_MODE`, `FX_MAX_AGENT_STEPS`, and the other supported variables are listed in [Configuration](https://fx.sh/docs/configure-fx/configuration.md#environment-variables). They affect only the current process and are never written back to settings. If a command fails or does not report what you expect, see [Troubleshooting](https://fx.sh/docs/using-fx/troubleshooting.md). --- [Browse all fx documentation](https://fx.sh/llms.txt) --- --- title: "Slash commands" description: "Reference for commands inside the interactive shell." canonical_url: https://fx.sh/docs/using-fx/slash-commands markdown_url: https://fx.sh/docs/using-fx/slash-commands.md --- # Slash commands Type `/` in the interactive shell to search commands. Anything else is sent to the model as a prompt. Commands that run outside the shell are listed in [CLI commands](https://fx.sh/docs/using-fx/cli.md). ## Sessions and shell | Command | Purpose | | --- | --- | | `/help` | Show interactive help. | | `/clear` | Start a fresh session and keep workspace background processes. `/new` does the same. | | `/reset` | Start a fresh session, then stop and forget workspace background processes. | | `/resume` | Open the saved-session picker. | | `/continue` | Continue a paused model response. | | `/rename` | Rename the current session. Provide a title. | | `/compact` | Compact older conversation turns now. | | `/quit` | Exit fx. `/exit` is an alias. | `/clear` and `/new` preserve background work for the current workspace. `/reset` stops and forgets that work. See [Sessions](https://fx.sh/docs/using-fx/sessions.md) for saving, recovery, and compaction. ## Account, model, and runtime | Command | Purpose | | --- | --- | | `/provider` | Choose a provider, credential source, and Vercel team. | | `/login` | Open the provider picker, or sign in to a named provider. | | `/logout` | Sign out of the active or named provider. | | `/setup` | Open the same [provider picker](https://fx.sh/docs/getting-started/authentication.md) as `/provider`. | | `/models` | Open the model catalog. | | `/model` | Select a model by ID or query. | | `/fast` | Toggle fast mode when supported. | | `/permissions` | Inspect or change the permission mode. | | `/allowlist` | Inspect or change persistent permission rules. | Use `/permissions full-access` to disable fx permission checks. `/permissions full access` and the legacy `/permissions yolo` are also accepted. See [Permissions](https://fx.sh/docs/configure-fx/permissions.md) for permission rules and modes. ## Inspection and settings | Command | Purpose | | --- | --- | | `/status` | Show model, workspace, permissions, and session state. | | `/stats` | Show current-session statistics. | | `/usage` | Open local usage and spend. `/cost` is an alias. | | `/credits` | Query AI Gateway credit balance. `/balance` is an alias. | | `/settings` | Open settings or change startup scrollback. | | `/statusline` | Toggle footer fields. | | `/sound` | Set completion sounds. | | `/version` | Show the installed version. | ## Tools and local data | Command | Purpose | | --- | --- | | `/background` | List or manage background commands. | | `/image` | Attach an image. `/img` is an alias. | | `/images` | Inspect or clear pending images. | | `/paste` | Attach a clipboard image when supported. | | `/mcp` | Browse [MCP](https://fx.sh/docs/capabilities/mcp.md) servers, tools, resources, and prompts. Direct subcommands manage configuration, authentication, and project trust. | | `/skills` | Browse and manage [skills](https://fx.sh/docs/capabilities/skills.md). | | `/workspace` | Manage saved additional directories. | | `/undo` | Undo the most recent tracked file operation. | | `/copy` | Copy the latest assistant response. | | `/feedback` | Open `fx.sh/feedback`, which forwards to the fx bug report form. | | `/trace` | Create and copy a private diagnostic trace. | Additional workspace roots are active after they are saved or passed with leading `--add-dir` flags. See [Additional workspaces](https://fx.sh/docs/configure-fx/additional-workspaces.md). `/feedback` and `/trace` are covered in [Share feedback](https://fx.sh/docs/using-fx/share-feedback.md). ### Undo tracked file changes `/undo` reverses the most recent tracked file change. Run it again to undo older file changes. It does not undo shell commands, Git history, or changes made outside fx's file tools. --- [Browse all fx documentation](https://fx.sh/llms.txt) --- --- title: "Usage and costs" description: "Inspect local token usage and AI Gateway balance." canonical_url: https://fx.sh/docs/using-fx/usage-and-costs markdown_url: https://fx.sh/docs/using-fx/usage-and-costs.md --- # Usage and costs ## Inspect local usage Inspect usage recorded by fx on this machine: ```bash fx usage ``` Choose a period or return structured output: ```bash fx usage --period 24h --json ``` Valid periods are `24h`, `7d`, and `30d`. Reports include requests, tokens, recorded spend, and usage by model. They also indicate when tracking covers only part of the selected period or totals may be incomplete. Inside the shell, `/usage` opens the dashboard. `/cost` is an alias. > **Usage is local** > > `fx usage` only includes requests recorded by fx on this machine. It does not query team-wide AI Gateway usage. > > For Gateway requests, view provider-side usage in your team's [AI Gateway dashboard](https://vercel.com/ai-gateway). Codex and Grok subscription requests go directly to their providers and are billed under those subscriptions. ## Additional model requests Permission review and vision fallback can make requests in addition to the main conversation: | Feature | Helper model | When it runs | | --- | --- | --- | | [Automatic permission review](https://fx.sh/docs/configure-fx/permissions.md#automatic-review-configuration-and-cost) | `moonshotai/kimi-k3` on Vercel AI Gateway, `gpt-5.4-mini` on Codex, the session model on Grok | An unresolved sensitive tool call in `auto` mode. | | [Vision fallback](https://fx.sh/docs/capabilities/vision.md#vision-fallback) | `google/gemini-2.5-flash` | Attached images cannot be sent to the selected model natively. | These requests contribute to usage and cost, through whichever [provider](https://fx.sh/docs/getting-started/authentication.md#model-providers) is active. Neither helper model is configurable. Changing the selected model changes only the Grok reviewer, which runs on the session model. [Web search](https://fx.sh/docs/capabilities/web-search.md) uses the active provider's search integration. Search calls can add cost to the generation that invokes them. ## Check your AI Gateway balance To query the balance for the active AI Gateway credential: ```bash fx credits ``` `fx balance` is an alias. Inside the shell, use `/credits` or `/balance`. --- [Browse all fx documentation](https://fx.sh/llms.txt) --- --- title: "ACP server" description: "Run fx from editors and clients that support Agent Client Protocol." canonical_url: https://fx.sh/docs/using-fx/acp markdown_url: https://fx.sh/docs/using-fx/acp.md --- # ACP server Run fx as an Agent Client Protocol (ACP) server to use the fx agent from compatible editors and clients. ## Configure an ACP client Start the server from the project that should be the primary workspace: ```bash cd /absolute/path/to/project && fx acp ``` Configure the client to launch the absolute binary path when possible: ```json { "command": "/absolute/path/to/fx", "args": ["acp"] } ``` The client process working directory becomes the primary workspace. Launch a separate server process for each primary workspace. `fx acp` accepts two options: | Option | Behavior | | --- | --- | | `--model ` | Override the model for the server process, including loaded sessions. | | `--log-file ` | Write ACP diagnostics to an absolute file path. | Global workspace flags such as `--add-dir`, `--no-additional-dirs`, and `--context-limit` must appear before `acp`. ## Authentication and fx settings ACP uses the selected fx provider and saved credentials. Complete [Authentication](https://fx.sh/docs/getting-started/authentication.md) before the client starts the server. The server uses the same settings, project instructions, skills, sessions, permissions, and tools as interactive fx. ## Supported ACP methods The client must call `initialize` first. fx responds with ACP protocol version `1` and supports these methods: | Method | Behavior | | --- | --- | | `initialize` | Negotiate protocol capabilities and initialize the connection. | | `session/new` | Create and activate a saved session. | | `session/load` | Load an exact session ID and replay its history. | | `session/resume` | Reconnect to a saved session without replaying its history. | | `session/close` | Close the active session. | | `session/list` | List sessions for the primary workspace. | | `session/prompt` | Run one turn in the active session. | | `session/cancel` | Cancel the active prompt. | | `session/set_config_option` | Change the active model or mode. | | `session/set_mode` | Change the active mode. | Each connection has one active session and one active prompt. ## Sessions, models, and permissions New and loaded sessions expose model and mode selectors. Model changes are saved to the active session, while a process-level `--model` override takes precedence over the model stored in a loaded session. | Mode | Permission behavior | | --- | --- | | `ask` | Request approval for unresolved sensitive tool calls. | | `code` | Automatically review unresolved sensitive tool calls. | Both modes expose the available runtime tools. Before the client selects a mode, fx uses the permission mode from the active configuration. An **Allow for this session** approval remains active only for the current session. It is not written to settings or restored when the session is loaded again. ## Prompt and MCP support `session/prompt` accepts text, embedded resources, and inline image blocks. Images follow the same native-vision or fallback routing as other fx requests; see [Vision](https://fx.sh/docs/capabilities/vision.md). Audio blocks are not supported. Clients receive streamed user and agent messages, tool status updates, and permission requests. ACP sessions combine client-supplied `mcpServers` with approved servers from the workspace `.mcp.json`. A client entry wins a same-name project entry. Pending or rejected project servers remain unavailable, and ACP never inherits servers from `~/.fx/mcp.json`. Clients can provide stdio, HTTP, or SSE servers. ## Protocol limits ACP uses newline-delimited JSON-RPC 2.0 over stdin and stdout. Each input message is limited to 8 MiB. > **Protect the protocol stream** > > Stdout is reserved for ACP messages. Write diagnostics to `--log-file` or `FX_TRACE_LOG` instead. --- [Browse all fx documentation](https://fx.sh/llms.txt) --- --- title: "Troubleshooting" description: "Diagnose install, authentication, model, permission, session, and terminal problems." canonical_url: https://fx.sh/docs/using-fx/troubleshooting markdown_url: https://fx.sh/docs/using-fx/troubleshooting.md --- # Troubleshooting Start here when fx does not behave the way the rest of these docs describe. Most answers come from one command: ```bash fx doctor ``` `fx doctor` checks the workspace, configuration, authentication, resolved startup settings, local session state, and Git integrations without starting an agent turn. When fx can recover a session problem, the output includes the exact command to run. ## Diagnostic commands | Question | Command | | --- | --- | | Is my environment healthy? | `fx doctor` | | What is fx actually using right now? | `fx status` or `fx status --json` | | Which permission rules are in effect? | `fx permissions --json` | | Which directories can tools reach? | `fx workspace --json` | | Which models can I select? | `fx models --json` | | What did fx do internally? | `/trace` inside the shell | | Why does the terminal look wrong? | `FX_DEBUG_RECORD=1 fx`, then `fx replay ` | ## `fx: command not found` The installer places the binary in `~/.local/bin`, or in `FX_INSTALL_DIR` when you set it. If that directory is not on your `PATH`, the installer appends a `PATH` line to your shell profile, which takes effect in new shells. Add it to the current shell: ```bash export PATH="$HOME/.local/bin:$PATH" ``` See [Installation](https://fx.sh/docs/getting-started/installation.md) for the full list of what the installer changes. ## fx cannot reach AI Gateway `Fx needs access to Vercel AI Gateway.` means no usable credential was found. Sign in, save an API key, or provide one for the process: ```bash fx login ``` ```bash fx setup ``` ```bash fx status ``` Without an explicit choice, fx tries `VERCEL_OIDC_TOKEN`, `AI_GATEWAY_API_KEY`, a saved login session, then a saved API key. A source selected in `/provider` stays selected even if it becomes unavailable. Repair that credential or select another source; fx will not silently switch accounts. See [Credential selection](https://fx.sh/docs/getting-started/authentication.md#credential-selection). A few specific cases: - **The browser never opens.** On a headless machine or over SSH, set `FX_NO_OPEN_BROWSER=1` before `fx login` to print the authorization URL instead. - **`Fx could not read the stored API key`.** Repair the saved key or choose another credential source through `/provider`. Set `FX_TRACE_LOG` if you need a trace of the failure. - **`fx setup` refuses to save a key.** `FX_DISABLE_KEYCHAIN=1` disables the native macOS key store, and `fx setup` cannot save a key while it is set. - **The session expired.** `fx doctor` reports the auth check as a warning. `fx login` refreshes it. ## A custom model connection fails If you use the [custom connections preview](https://fx.sh/docs/configure-fx/custom-model-connections.md), first confirm that the binary includes that feature. Then run `fx status --json` and check `provider_endpoint` and the selected model before sending another request. Connection definitions belong under top-level `providers` in `~/.fx/settings.json`, not project `.fx.json`. Check the API prefix, the exact served model ID, and the environment variable named by `auth.env`. `fx setup` does not save a custom-provider key, and fx does not fall back to a Gateway key for that connection. If text works but tools fail, check the model's function-tool support and the server's tool-call parser. A rejected resume can mean that the saved endpoint or credential slot changed. See [custom connection troubleshooting](https://fx.sh/docs/configure-fx/custom-model-connections.md#compatibility-and-troubleshooting) for these cases and the adapter's limits. ## The model is not the one I chose `fx status` and `/status` print the effective model. fx resolves it in this order: `FX_MODEL` for the process, a legacy workspace override if one still exists, your user default in `~/.fx/settings.json`, then the compiled default. Project `.fx.json` cannot set `model`, so a repository never changes your selection. See [Models](https://fx.sh/docs/configure-fx/models.md). ## A model is missing from the catalog The catalog depends on the active credential and the selected Vercel team, so availability can differ between teams. `/status` prints the active `gateway_team`. ```bash fx teams ``` ```bash fx models ``` Before authentication the catalog shows only public models. `fx teams` requires an `fx login` session. ## fx stops before running a tool In `auto` mode, fx applies saved rules and reviews actions that need a closer look. A concern or unavailable review holds the action and returns guidance to the agent; it does not open an approval prompt. If an action needs your approval, use `ask` mode in the interactive shell. `fx ask` is noninteractive by default; `--prompt-permissions` allows configured approval prompts only when stdin is a terminal. For repeated work: 1. Add an allow rule for the exact action with `/allowlist`. 2. Use `auto` mode when you want automatic review of unresolved actions. 3. Use `--full-access` only in a trusted environment; it disables fx permission checks for that run. The legacy `--yolo` flag remains an alias. An interrupted headless run exits with code `130`. See [Permissions](https://fx.sh/docs/configure-fx/permissions.md) and [`fx ask`](https://fx.sh/docs/using-fx/fx-ask.md). ## fx ignores my AGENTS.md - Only the primary workspace contributes project instructions. Additional directories do not. - `context: false` in `.fx.json` or user settings disables project context entirely. - Instruction files are bounded by `project_instruction_file_bytes` and `project_instructions_total_bytes`. Truncated or omitted context is reported to the runtime instead of being treated as complete, so raise the limit when a file is larger. See [Context limits](https://fx.sh/docs/configure-fx/context-limits.md). - The narrowest applicable `AGENTS.md` wins, so a nested file can override the repository root for calls inside its directory. ## A session will not open or resume `fx doctor` inspects saved sessions and names the remediation for each problem it finds. To make a separate resumable copy without touching the original: ```bash fx session recover ``` To inspect one session without starting a turn: ```bash fx session --id --json ``` A paused response can be continued with `/continue` in the shell, or with `fx ask --resume last --continue-recovery` in a headless run. See [Sessions](https://fx.sh/docs/using-fx/sessions.md). ## An MCP server is missing ```bash fx mcp list ``` The default command reads configuration and stored authentication without opening a transport. Add `--connect` when you need live startup, discovery, and health. Inside the interactive shell, `/mcp list` shows the active runtime. - Native sessions combine the private `~/.fx/mcp.json` profile with the workspace `.mcp.json`. A profile entry wins a same-name project entry. - Project servers stay disconnected until you approve them with `/mcp trust approve ` or `fx mcp trust approve `. Approval also unlocks project environment expansion. - A missing required `${VAR}` in an approved project entry skips that server without exposing the value. `fx status`, `fx doctor`, and MCP listings report the configuration issue. - Optional servers can fail without blocking the rest of fx. Set `"required": true` when the first request must wait for a ready server. - `/mcp reload` validates a replacement before publishing it. Invalid configuration or a failed required server keeps the previous runtime. - Slow servers receive 30 seconds to start by default. Raise `startup_timeout_ms` for a known longer cold start. - ACP sessions combine client-supplied servers with approved project servers, but inherit nothing from your profile. See [MCP](https://fx.sh/docs/capabilities/mcp.md) for configuration and [MCP protocol reference](https://fx.sh/docs/capabilities/mcp/protocol.md) for transport behavior. ## The terminal renders incorrectly Force terminal synchronized updates on or off to test a compatibility problem: ```bash FX_SYNC_UPDATES=off fx ``` If the problem persists, capture it so it can be reproduced: ```bash FX_DEBUG_RECORD=1 fx ``` ```bash fx replay ./repro.fxtape ``` Recordings are written under `~/.fx/recordings/`, and `FX_RECORD=./repro.fxtape` chooses an explicit path. Add `FX_DEBUG_RECORD_SILENT_BANNER=1` when the recording notice should stay hidden from the inline transcript during a screen share; capture continues and Ctrl-O still shows the notice. Replay a recording before sharing it. See [Share feedback](https://fx.sh/docs/using-fx/share-feedback.md). ## Costs are higher than expected `fx usage` reports only what fx recorded on this machine. Automatic permission review and vision fallback can add model requests; their routing and cost depend on the provider. Web search can add provider-tool costs too. See [Usage and costs](https://fx.sh/docs/using-fx/usage-and-costs.md). > **Redact before you share** > > `/trace` output and `.fxtape` recordings stay local until you share them, and they can contain prompts, code, paths, commands, model output, or secrets. Review them first. Still stuck? [Share feedback](https://fx.sh/docs/using-fx/share-feedback.md) with a reviewed trace attached. --- [Browse all fx documentation](https://fx.sh/llms.txt) --- --- title: "Data and privacy" description: "Understand model requests, AI Gateway controls, and local data." canonical_url: https://fx.sh/docs/using-fx/data-and-privacy markdown_url: https://fx.sh/docs/using-fx/data-and-privacy.md --- # Data and privacy ## Model request data Each model request includes your prompt and the context assembled for that turn. Context can include conversation history, applicable `AGENTS.md` and skill instructions, attached images, and file or tool content already loaded into the session. fx does not automatically package or upload your workspace, Git history, session files, traces, or recordings. That data can still leave the machine when it is loaded into model context or sent by a networked tool. Web search, web fetch, remote MCP servers, and other networked tools send inputs to the services they call when invoked. ## Inference APIs With AI Gateway, fx sends requests to Gateway, which routes them to the selected model provider. Codex and Grok subscription requests go directly to those providers under their own policies. ## AI Gateway controls AI Gateway does not retain prompts, outputs, or sensitive data after a request completes. It does record request metadata such as the model, token counts, latency, and cost for billing and observability. Model and tool providers can have separate retention policies. Pro and Enterprise teams can enable [team-wide Zero Data Retention](https://vercel.com/docs/ai-gateway/security-and-compliance/zdr), which routes requests only to ZDR-compliant providers and also disallows prompt training. fx uses the AI Gateway settings associated with the active Vercel team or API key. ## Local credentials and sessions Native fx stores private runtime state under `~/.fx/`, including settings, the saved Vercel login, sessions, prompt history, usage records, MCP configuration and credentials, managed skills, default debug trace logs, and opt-in debug recordings. On macOS, a saved API key lives in Keychain. On Linux, it lives in `~/.fx/api-key` with `0600` permissions. Session files remain local, but fx sends the relevant conversation context again when you continue a session. Use [`fx ask --no-save`](https://fx.sh/docs/using-fx/fx-ask.md#use-fx-ask-in-scripts) when a one-off request should not create a session. ## Product telemetry fx does not send product telemetry or usage analytics to a separate fx service. `fx usage` reads local usage records, and `/trace` assembles diagnostics locally. ## Update checks Automatic updates are on by default. Native fx reads static release metadata after startup and every 30 minutes until an update is ready. The request contains no fx-generated machine or installation identifier. Set `FX_AUTO_UPGRADE=0` to turn automatic updates off. ## Local inference fx can use compatible loopback endpoints for model discovery and generation. This supports a fully hermetic setup: with outbound networking blocked, prompts and model context stay local and networked tools cannot make requests. ## Sharing diagnostics [`/trace`](https://fx.sh/docs/using-fx/share-feedback.md#create-a-private-diagnostic-trace) creates a local diagnostic report and copies it to your clipboard when supported. If clipboard copying fails, fx leaves the report in a local temporary file. [`/feedback`](https://fx.sh/docs/using-fx/share-feedback.md#report-a-problem) opens the feedback form without uploading diagnostic data. Review and redact prompts, code, paths, commands, model output, and secrets before sharing a trace or recording. See [Share feedback](https://fx.sh/docs/using-fx/share-feedback.md) for details. --- [Browse all fx documentation](https://fx.sh/llms.txt) --- --- title: "Share feedback" description: "Report an fx problem and collect private diagnostics for terminal issues." canonical_url: https://fx.sh/docs/using-fx/share-feedback markdown_url: https://fx.sh/docs/using-fx/share-feedback.md --- # Share feedback fx is under active development. Report issues and share diagnostic data to help improve fx. ## Report a problem Inside the interactive shell: ```bash /feedback ``` `/feedback` opens `fx.sh/feedback`, which forwards to the fx bug report form on GitHub. It does not generate diagnostics and does not change the clipboard. The form asks for the issue, the expected behavior, and steps to reproduce it. It also has an optional Trace field for the output of `/trace`, and it requires you to confirm that you removed secrets and other sensitive data first. ## Create a private diagnostic trace Inside the interactive shell: ```bash /trace ``` `/trace` creates a private Markdown file with logs, session context, runtime state, permissions, recent tools, and renderer information. On macOS, fx copies the `.md` file to the clipboard. On other platforms, it saves the file and prints its path. The command does not open or link to the report form; run `/feedback` separately when you are ready to attach the reviewed trace. Review and redact the trace before sharing it. It remains local unless you share it yourself. ## Record a terminal issue Recordings help the fx team reproduce rendering and interaction bugs. ```bash FX_DEBUG_RECORD=1 fx ``` fx prints the recording path and writes a private `.fxtape` file under `~/.fx/recordings/`. Choose an explicit path with `FX_RECORD`: ```bash FX_RECORD=./repro.fxtape fx ``` Add `FX_DEBUG_RECORD_SILENT_BANNER=1` when the recording notice should stay out of the inline transcript during a screen share. Ctrl-O still shows the notice, and recording remains active. Recordings capture visible terminal output, resizes, interrupts, and markers. Set `FX_RECORD_INPUT=1` only when the fx team needs raw terminal input to reproduce the issue. ### Replay a recording Replay a recording before sharing it to confirm that it captures the issue: ```bash fx replay ./repro.fxtape ``` ## Check your setup first `fx doctor` checks the workspace, configuration, authentication, startup settings, local session state, and Git integrations without starting an agent turn: ```bash fx doctor ``` When fx can recover a session problem, the output includes the command to run. [Troubleshooting](https://fx.sh/docs/using-fx/troubleshooting.md) covers the problems it reports most often. > **Review before sharing** > > Traces and recordings remain local unless you share them yourself. They may contain prompts, code, paths, commands, model output, or secrets. Raw input recording also captures terminal input. --- [Browse all fx documentation](https://fx.sh/llms.txt) --- --- title: "Embed fx" description: "Build an application with the libfx agent runtime." canonical_url: https://fx.sh/docs/lib markdown_url: https://fx.sh/docs/lib.md --- # Embed fx `libfx` runs an agent inside your application. It uses a native addon on Node.js and WebAssembly in the browser. You provide the interface, credentials, instructions, and tools. Start with the [examples](https://fx.sh/docs/lib/examples.md) for a readline chat, a browser agent, or an app built with Next.js or Nuxt. ## Install ```sh npm install libfx ``` ## Run an agent Create an [`Agent`](https://fx.sh/docs/lib/api.md#agent), send a prompt, and read the [`Turn`](https://fx.sh/docs/lib/api.md#turn) as it produces text: ```js import { createFxAgent } from 'libfx' const agent = await createFxAgent({ apiKey: process.env.AI_GATEWAY_API_KEY, }) try { const turn = agent.prompt('Explain closures in two sentences.') for await (const event of turn) { if (event.type === 'text_delta') process.stdout.write(event.delta) } await turn.result } finally { await agent.close() } ``` One agent owns one conversation. Keep it open for follow-up prompts, or save a [checkpoint](https://fx.sh/docs/lib/api.md#agentcheckpoint) before closing it. ## Choose a guide - [Node SDK](https://fx.sh/docs/lib/node.md): prompting, tools, checkpoints, and backend selection. - [WebAssembly SDK](https://fx.sh/docs/lib/webassembly.md): run the agent in the browser. - [Examples](https://fx.sh/docs/lib/examples.md): complete applications with source and live demos. - [API reference](https://fx.sh/docs/lib/api.md): interfaces, options, methods, and events. ## Embed the terminal Embedding the fx terminal is a separate use case. The [Terminal embedding guide](https://fx.sh/docs/lib/terminal.md) covers `createFxTerminal()`, xterm.js, storage, login, and workspace adapters. To launch the native CLI or connect an editor instead, see the [CLI reference](https://fx.sh/docs/using-fx/cli.md) or [ACP guide](https://fx.sh/docs/using-fx/acp.md). ## Version compatibility These docs describe `libfx@0.0.10`, published from [fx v0.0.10](https://github.com/vercel-labs/fx/tree/v0.0.10/sdk). Install that version explicitly with `npm install libfx@0.0.10`, or use the command above for the latest release. For libfx 0.0.7, use its [versioned SDK README](https://github.com/vercel-labs/fx/blob/cef08aa0f178537e552a931c7863dc4c1487e4a0/sdk/README.md). It uses `agent.createSession()` and `session.prompt()`, not the new agent API shown here. In the v0.0.10 SDK, one agent is one in-memory conversation with `prompt()`, `checkpoint()`, and `close()`. The host supplies credentials, instructions, tools, and durable checkpoint storage. The terminal remains a separate API with its own session and configuration stores. See [Migrating from 0.0.7](https://fx.sh/docs/lib/node.md#migrating-from-007). --- [Browse all fx documentation](https://fx.sh/llms.txt) --- --- title: "Node SDK" description: "Embed the fx agent in a Node application through the native addon." canonical_url: https://fx.sh/docs/lib/node markdown_url: https://fx.sh/docs/lib/node.md --- # Node SDK The `libfx` package embeds the fx agent in a Node application. It runs as a native Node addon and falls back to WebAssembly when no compatible addon is available. Start with the [readline chat or framework examples](https://fx.sh/docs/lib/examples.md) for a runnable application. For method signatures, options, return types, and events, see the [libfx API reference](https://fx.sh/docs/lib/api.md). ## Install ```bash npm install libfx ``` `libfx` requires Node 20 or later and has no runtime dependencies. > **Check your libfx version** > > These examples use libfx 0.0.10. If you are upgrading from 0.0.7, see the [migration guide](#migrating-from-007). ## Run a headless agent One agent owns one in-memory conversation with three methods: `prompt()`, `checkpoint()`, and `close()`. Set `AI_GATEWAY_API_KEY` to an [AI Gateway API key](https://vercel.com/docs/ai-gateway/authentication-and-byok) before running this example. ```js import { createFxAgent } from 'libfx' const agent = await createFxAgent({ apiKey: process.env.AI_GATEWAY_API_KEY, }) try { const turn = agent.prompt('Explain how a database index speeds up a query.') for await (const event of turn) { if (event.type === 'text_delta') process.stdout.write(event.delta) } console.log(await turn.result) // { stopReason, usage } } finally { await agent.close() } ``` `apiKey` is required. `model` is optional and defaults to the built-in model. Agent options are named fields; `env` is only for `createFxTerminal()`. A prompt accepts a string or text/resource blocks. Its stream emits `text_delta`, `reasoning_delta` when available, `tool_start`, and `tool_end`. Runtime diagnostics go to the optional `onEvent` callback, separately from model output. Consume the stream before awaiting `turn.result`. Output is lossless and backpressured: a slow consumer pauses production. Waiting only for the result can stall on unread output. If you do not need events, drain them with `for await (const _ of turn) {}`. Only one prompt can run at a time, and a turn has one event consumer. Breaking out of the iterator cancels the turn. You can also call `turn.cancel()`, close the agent, or pass an `AbortSignal`: ```js const controller = new AbortController() const turn = agent.prompt('Wait for more instructions.', { signal: controller.signal, }) controller.abort() for await (const _ of turn) {} console.log((await turn.result).stopReason) // 'cancelled' ``` An already-aborted signal makes no model request and does not change history. Transport or decoding failures reject the result. libfx retries a retryable transport failure at most once, only before it delivers model output or performs tool actions. Cancellation prevents retries. ## Save and restore a conversation Call `checkpoint()` when the agent is idle. It returns opaque, versioned bytes containing conversation history and usage. Your application owns storage: ```js const checkpoint = await agent.checkpoint() await agent.close() const restored = await createFxAgent({ apiKey: process.env.AI_GATEWAY_API_KEY, checkpoint, }) // Continue with restored.prompt(...), then close the restored agent. ``` Restore only into a new agent. Credentials, model selection, instructions, tools, MCP clients, and skills are not in the checkpoint; resupply them when restoring. The terminal's `sessionStore` and `configStore` are not agent options. ## What the embedded agent can do The embedded agent does not inherit the CLI's filesystem, shell, secret store, or built-in tools. Supply JavaScript tools explicitly; the same descriptors work on native and WebAssembly backends: ```js const agent = await createFxAgent({ apiKey: process.env.AI_GATEWAY_API_KEY, instructions: 'Use lookup to answer questions about product codes.', tools: [{ name: 'lookup', description: 'Look up a product code.', inputSchema: { type: 'object', properties: { code: { type: 'string' } }, required: ['code'], }, async execute(input, { signal }) { return database.lookup(input.code, { signal }) }, }], }) ``` `database` is your application's data client. Your application must validate and authorize actions inside `execute()`; do not rely on the CLI permission flow to approve host tools. Cancellation aborts the tool's signal and stops waiting for its result. Tool callbacks must stop their own work when cancelled; late results and rejections are ignored. `instructions` is the complete host-owned system context, limited to 64 KiB of UTF-8 including adapter text. libfx adds no hidden base prompt. Without instructions, it sends no system message. > **The embedded core is not the CLI** > > Loading a native addon does not grant built-in operating-system access. Any filesystem, process, or network authority in a JavaScript tool belongs to your host application. ## Connect MCP and skills `libfx/mcp` adapts an already-connected, host-owned MCP client: ```js import { createMcpAdapter } from 'libfx/mcp' const mcp = await createMcpAdapter(client, { prefix: 'github_', resources: ['repo://instructions'], prompts: ['review'], }) const agent = await createFxAgent({ apiKey: process.env.AI_GATEWAY_API_KEY, tools: mcp.tools, instructions: mcp.instructions, }) // Run prompts, then close the agent and adapter. await agent.close() await mcp.close() ``` Your app owns `client`, its transport, authentication, elicitation, and connection cleanup. The adapter uses the MCP TypeScript SDK v1 `callTool(params, resultSchema?, options?)` signature, including cancellation in the third argument. Tool catalogs support pagination up to 64 tools; text and structured results reach the model together. Supported tool images reach image-capable models, with an omission notice otherwise. Use `libfx/skills` for loaded skill records, or explicitly load a file in Node or Bun: ```js import { loadSkillFile } from 'libfx/skills/node' import { createSkillsAdapter } from 'libfx/skills' const record = await loadSkillFile('./skills/review/SKILL.md') const skills = createSkillsAdapter([record]) const agent = await createFxAgent({ apiKey: process.env.AI_GATEWAY_API_KEY, ...skills, }) ``` The host chooses which skills to load; the agent does not scan directories automatically. ## Next.js and Vercel Native agents support Next.js 15 with webpack and Next.js 16 with webpack or Turbopack, including standalone builds. The v0.0.8 package handles native assets without `serverExternalPackages` or manual native-file inclusion. ```js import { createFxAgent } from 'libfx' export async function POST(request) { const { prompt } = await request.json() const agent = await createFxAgent({ apiKey: process.env.AI_GATEWAY_API_KEY }) try { let text = '' const turn = agent.prompt(prompt, { signal: request.signal }) for await (const event of turn) { if (event.type === 'text_delta') text += event.delta } await turn.result return Response.json({ text }) } finally { await agent.close() } } ``` Add your application's authentication, input validation, and request limits around the route. If you choose the WebAssembly backend, check its [runtime requirements](https://fx.sh/docs/lib/webassembly.md#check-runtime-support). Standalone builds must supply WebAssembly assets separately because the standalone tracer excludes `.wasm` files. ## Choose an entry point | Import | Environment | Loads | | --- | --- | --- | | `libfx` | Node or browser | Resolves to the entry point for the current environment | | `libfx/node` | Node | Native addon first, WebAssembly fallback | | `libfx/browser` | Browser | WebAssembly | | `libfx/wasm` | Node or browser | The WebAssembly host layer directly | The main entry points export [`createFxAgent()`](https://fx.sh/docs/lib/api.md#createfxagent), [`listModels()`](https://fx.sh/docs/lib/api.md#listmodels), and [`supportsJspi()`](https://fx.sh/docs/lib/api.md#supportsjspi). The Node entry point also exports [`getBackendInfo()`](https://fx.sh/docs/lib/api.md#getbackendinfo). Node applications can use `require('libfx')`. The separate [terminal API and xterm.js helpers](https://fx.sh/docs/lib/terminal.md) are also available from these entry points. Optional adapters are separate imports: `libfx/mcp`, `libfx/skills`, and `libfx/skills/node`. Importing the main package does not connect to MCP servers, scan skills, spawn processes, or read workspace files. ## Choose a backend The native addon implements only the agent core, so `createFxAgent()` runs natively while `createFxTerminal()` runs on WebAssembly and needs [JavaScript Promise Integration (JSPI)](https://fx.sh/docs/lib/webassembly.md#check-runtime-support), even in the same Node process. | Call | `auto` | `native` | | --- | --- | --- | | `createFxAgent()` | Native addon | Native addon | | `createFxTerminal()` | WebAssembly | Fails | A `backend` of `wasm` runs WebAssembly for both calls, and every WebAssembly path needs JSPI. `backend` defaults to `auto`, which prefers a compatible addon and falls back to WebAssembly. Set it when you would rather fail than run on the backend you did not plan for: ```js import { createFxAgent } from 'libfx' const agent = await createFxAgent({ apiKey: process.env.AI_GATEWAY_API_KEY, backend: 'native', }) ``` The loader selects `libfx.-.node` for the current platform. The package ships addons for `linux-x64`, `linux-arm64`, `darwin-x64`, and `darwin-arm64`. Linux native addons require glibc 2.34 or newer. Windows has no packaged addon, so Node on Windows uses WebAssembly. Native agents do not require JSPI or experimental Node flags. To use an addon you built yourself, pass it with `nativeAddon`, which accepts a module, a path, or a URL. ## Discover models ```js import { listModels } from 'libfx' const models = await listModels({ apiKey: process.env.AI_GATEWAY_API_KEY }) ``` `listModels()` returns sorted, unique language-model IDs with one Gateway request. It accepts an optional `fetch` override and does not create an agent or load native or WebAssembly artifacts. Agent creation does not fetch the model catalog; prompting may resolve model metadata through the supplied `fetch`. ## Handle a missing backend Use `getBackendInfo()` to check whether a backend can load without creating an agent or terminal: ```js import { getBackendInfo } from 'libfx' const info = await getBackendInfo({ surface: 'agent', backend: 'auto' }) console.log(info.backend) // 'native', 'wasm-jspi', or 'unavailable' console.log(info.attempts) ``` Set `surface: 'terminal'` to check terminal support. The probe loads and validates a native module or compiles WebAssembly; it does not validate credentials or make a model request. A remote WebAssembly asset can still require a fetch. Each attempt reports availability and a reason when unavailable. Reasons distinguish unsupported platforms, missing or incompatible native artifacts, missing native surfaces, disabled native loading, unavailable JSPI, and WebAssembly load failures. Invalid options reject with `TypeError`. If startup fails with `LIBFX_JSPI_REQUIRED`, the fallback needs JSPI. `LIBFX_NATIVE_UNAVAILABLE` can indicate that the addon does not implement the requested agent or terminal. Native loading or initialization errors may propagate their own codes. On Node versions that require it, enable JSPI with `--experimental-wasm-jspi`, or use a supported native addon. Check `supportsJspi()` instead of relying on the runtime version. ## Migrating from 0.0.7 libfx 0.0.7 uses `env`, `agent.createSession()`, and `session.prompt()`. It does not expose the new agent methods or MCP and skills imports described above. For that version, use its [versioned SDK README](https://github.com/vercel-labs/fx/blob/cef08aa0f178537e552a931c7863dc4c1487e4a0/sdk/README.md). When moving to the v0.0.8 SDK: - Pass `apiKey` and `model` directly, not through `env`. - Call `agent.prompt()` instead of creating a session first. - Read normalized events and await `turn.result`, not `turn.stopReason`. - Replace agent session/config stores and session setters with host-owned checkpoints and options on a fresh agent. - Supply tools and instructions explicitly instead of relying on CLI capabilities or a permission callback. The interactive terminal remains a separate API and still accepts `env` and its storage adapters. ## Security boundaries Your application controls credentials, tool execution, and network access. Keep credentials on the server, validate and authorize tool inputs, and honor cancellation in your callbacks. Only load native addons and provide network adapters that your application trusts. --- [Browse all fx documentation](https://fx.sh/llms.txt) --- --- title: "WebAssembly SDK" description: "Run a libfx agent in the browser through WebAssembly." canonical_url: https://fx.sh/docs/lib/webassembly markdown_url: https://fx.sh/docs/lib/webassembly.md --- # WebAssembly SDK [`createFxAgent()`](https://fx.sh/docs/lib/api.md#createfxagent) runs the agent in the browser using `fx-core.wasm`. Your application provides the interface and controls model requests. For a complete HTML page and server route, start with the [browser example](https://fx.sh/docs/lib/examples.md#browser-agent). On Node.js, use the [native SDK](https://fx.sh/docs/lib/node.md). ```sh npm install libfx ``` ## Check runtime support The SDK requires [JavaScript Promise Integration (JSPI)](https://v8.dev/blog/jspi). Chrome and Edge include JSPI starting in version 137. WebKit added it in [Safari 27](https://webkit.org/blog/17967/news-from-wwdc26-webkit-in-safari-27-beta/) and [Safari Technology Preview 238](https://webkit.org/blog/17848/release-notes-for-safari-technology-preview-238/). In Node.js 24, enable it with `--experimental-wasm-jspi`. Call `supportsJspi()` before loading WebAssembly instead of checking the runtime name or version. ## Run a headless agent Use `createFxAgent()` when your app renders its own interface. This example passes an AI Gateway key for local development: ```js import { createFxAgent, supportsJspi } from 'libfx/browser' if (!supportsJspi()) { throw new Error('fx requires JSPI support') } const agent = await createFxAgent({ apiKey: '', }) try { const turn = agent.prompt('Explain this project') for await (const event of turn) { if (event.type === 'text_delta') console.log(event.delta) } const { stopReason, usage } = await turn.result console.log(stopReason, usage) } finally { await agent.close() } ``` Do not embed a long-lived API key in client code. For production, proxy AI Gateway requests through your backend. The browser entry point resolves `fx-core.wasm` relative to the installed package. If your bundler does not serve it, copy it to a public asset directory and pass its URL as [`wasm`](https://fx.sh/docs/lib/api.md#agentoptions). ## Add persistence, login, and commands Use [checkpoints](https://fx.sh/docs/lib/node.md#save-and-restore-a-conversation) to save and restore a conversation. Supply [JavaScript tools, MCP, and skills](https://fx.sh/docs/lib/node.md#what-the-embedded-agent-can-do) for the capabilities your app needs. An embedded agent does not inherit the CLI’s shell, filesystem, or stored credentials. Its tools run in your application and have only the access you give them. ## WebAssembly runtime limits WebAssembly cannot start native processes or access a native keychain or filesystem. It also requires JSPI support. Each agent has its own memory and conversation; stable WebAssembly sources reuse the compiled module. ## Embed the terminal The interactive terminal is a separate API. See [Terminal embedding](https://fx.sh/docs/lib/terminal.md) for xterm.js, storage, login, and workspace adapters. --- [Browse all fx documentation](https://fx.sh/llms.txt) --- --- title: "Examples" description: "Build with libfx in Node.js, the browser, Next.js, and Nuxt." canonical_url: https://fx.sh/docs/lib/examples markdown_url: https://fx.sh/docs/lib/examples.md --- # Examples Four small applications built with libfx. Each has a working demo, the source, and a prompt you can copy with all the code and setup instructions. The source lives in the [fx examples](https://github.com/vercel-labs/fx/tree/b761df3ffbe17f49eee81e517723a0379cf34fbe/examples). These examples use libfx 0.0.8 and Node.js 24. You’ll need an [AI Gateway API key](https://vercel.com/docs/ai-gateway/authentication-and-byok) to run them locally. ## Node.js readline chat A command-line chat built with Node.js and [libfx](https://fx.sh/docs/lib/node.md). It keeps the conversation open for follow-up questions and prints replies as they arrive. The live demo is a single-prompt browser version. > Build a Node.js readline chat with libfx. Keep one agent for the conversation, stream text to stdout, and close it on exit. Use the files below as a starting point. # Node.js readline chat [Source](https://github.com/vercel-labs/fx/tree/1ca4e4f4e7946161e86db1ec177f964c52dc266d/examples/node-chat) · [Live demo](https://fx-demo-node-chat.vercel.app) Use Node.js 24. Save the files at the paths shown below, including examples/shared. Create examples/node-chat/.env.local with your server-side AI_GATEWAY_API_KEY. ```sh cd examples/node-chat npm install npm run chat ``` ### examples/node-chat/package.json ```json { "name": "fx-demo-node-chat", "private": true, "type": "module", "scripts": { "chat": "node --env-file-if-exists=.env.local chat.mjs", "dev": "node --env-file-if-exists=.env.local server.mjs" }, "dependencies": { "libfx": "0.0.8" }, "engines": { "node": "24.x" } } ``` ### examples/node-chat/chat.mjs ```js import { createInterface } from 'node:readline' import { stdin, stdout } from 'node:process' import { createFxAgent } from 'libfx' const agent = await createFxAgent({ apiKey: process.env.AI_GATEWAY_API_KEY, model: 'openai/gpt-4.1-nano', }) const input = createInterface({ input: stdin, output: stdout, prompt: 'You: ' }) try { input.prompt() for await (const prompt of input) { if (prompt.trim() === '/exit') break if (!prompt.trim()) { input.prompt(); continue } stdout.write('Agent: ') const turn = agent.prompt(prompt) for await (const event of turn) { if (event.type === 'text_delta') stdout.write(event.delta) } await turn.result stdout.write('\n\n') input.prompt() } } finally { input.close() await agent.close() } ``` ### examples/node-chat/handler.mjs ```js import { createFxAgent } from 'libfx' import { model } from '../shared/model.mjs' import { errorResponse, gatewayFetch, readPrompt } from '../shared/gateway.mjs' export async function POST(request) { try { const prompt = await readPrompt(request) const agent = await createFxAgent({ apiKey: process.env.AI_GATEWAY_API_KEY, model, fetch: gatewayFetch }) async function* reply() { try { const turn = agent.prompt(prompt, { signal: request.signal }) for await (const event of turn) { if (event.type === 'text_delta') yield event.delta } await turn.result } finally { await agent.close() } } return new Response(ReadableStream.from(reply()).pipeThrough(new TextEncoderStream()), { headers: { 'content-type': 'text/plain; charset=utf-8', 'cache-control': 'no-store' }, }) } catch (error) { return errorResponse(error) } } ``` ### examples/node-chat/server.mjs ```js import { createServer } from 'node:http' import { readFile } from 'node:fs/promises' import { Readable } from 'node:stream' import { pipeline } from 'node:stream/promises' import { POST } from './handler.mjs' const html = await readFile(new URL('./index.html', import.meta.url)) const replyReader = await readFile(new URL('../shared/read-reply.mjs', import.meta.url)) createServer(async (incoming, outgoing) => { if (incoming.method === 'GET' && incoming.url === '/read-reply.mjs') { outgoing.writeHead(200, { 'content-type': 'text/javascript; charset=utf-8' }).end(replyReader) return } if (incoming.method === 'GET' && incoming.url === '/') { outgoing.writeHead(200, { 'content-type': 'text/html; charset=utf-8' }).end(html) return } if (incoming.method !== 'POST' || incoming.url !== '/api/chat') { outgoing.writeHead(404).end('Not found') return } const controller = new AbortController() outgoing.on('close', () => controller.abort()) try { const request = new Request('http://localhost/api/chat', { method: 'POST', headers: incoming.headers, body: Readable.toWeb(incoming), duplex: 'half', signal: controller.signal, }) const response = await POST(request) outgoing.writeHead(response.status, Object.fromEntries(response.headers)) await pipeline(Readable.fromWeb(response.body), outgoing) } catch { outgoing.destroy() } }).listen(Number(process.env.PORT ?? 3000)) ``` ### examples/node-chat/index.html ```html Node.js agent · fx examples

Node.js agent

A native libfx agent. Each request starts a new conversation.




Code and setup

``` ### examples/shared/model.mjs ```js export const model = 'openai/gpt-4.1-nano' ``` ### examples/shared/gateway.mjs ```js import { model } from './model.mjs' function fail(status, message) { throw Object.assign(new Error(message), { status }) } export async function readJson(request) { const reader = request.body?.getReader() if (!reader) fail(400, 'Send a JSON request body.') const chunks = [] let size = 0 try { while (true) { const { value, done } = await reader.read() if (done) break size += value.byteLength if (size > 32768) { await reader.cancel() fail(413, 'This conversation is too long. Start a new one.') } chunks.push(value) } } finally { reader.releaseLock() } const bytes = new Uint8Array(size) let offset = 0 for (const chunk of chunks) { bytes.set(chunk, offset) offset += chunk.byteLength } try { return JSON.parse(new TextDecoder().decode(bytes)) } catch { fail(400, 'Send valid JSON.') } } export async function readPrompt(request) { const body = await readJson(request) const prompt = typeof body?.prompt === 'string' ? body.prompt.trim() : '' if (!prompt || prompt.length > 2000) fail(400, 'Enter a prompt of 1–2,000 characters.') return prompt } // All public examples share this transport policy; the agent code stays in each example. export async function gatewayFetch(input, init) { const request = new Request(input, init) const url = new URL(request.url) const catalog = url.pathname === '/coding-agent/v1/models' && request.method === 'GET' const generation = url.pathname === '/v3/ai/language-model' && request.method === 'POST' if (url.origin !== 'https://ai-gateway.vercel.sh' || url.search || (!catalog && !generation)) { fail(404, 'Unknown model endpoint.') } const key = process.env.AI_GATEWAY_API_KEY if (!key) fail(503, 'The demo is not configured yet.') let body if (generation) { const input = await readJson(request) const prompt = input?.prompt if (!Array.isArray(prompt) || !prompt.length || prompt.length > 40) fail(400, 'Invalid conversation.') for (const message of prompt) { if (!message || !['system', 'user', 'assistant'].includes(message.role)) fail(400, 'Only text messages are supported.') if (message.role === 'system' && typeof message.content === 'string') continue if (!Array.isArray(message.content) || !message.content.every( (part) => part?.type === 'text' && typeof part.text === 'string', )) fail(400, 'Only text messages are supported.') } body = JSON.stringify({ prompt, maxOutputTokens: 512 }) } const headers = new Headers({ authorization: `Bearer ${key}`, 'content-type': 'application/json' }) if (generation) { headers.set('ai-language-model-id', model) headers.set('ai-language-model-streaming', 'true') } for (const name of ['ai-gateway-protocol-version', 'ai-language-model-specification-version']) { const value = request.headers.get(name) if (value) headers.set(name, value) } return fetch(new Request(url, { method: request.method, headers, body, redirect: 'error', signal: AbortSignal.any([request.signal, AbortSignal.timeout(30000)]), })) } export function errorResponse(error) { const status = Number.isInteger(error?.status) ? error.status : 502 return new Response(status < 500 ? error.message : 'The demo could not respond. Try again shortly.', { status, headers: { 'content-type': 'text/plain; charset=utf-8', 'cache-control': 'no-store' }, }) } ``` ### examples/shared/read-reply.mjs ```js export async function* readReply(response) { const reader = response.body.getReader() const decoder = new TextDecoder() let finished = false try { while (!finished) { const { value, done } = await reader.read() finished = done const text = decoder.decode(value, { stream: !done }) if (text) yield text } } finally { if (!finished) await reader.cancel().catch(() => {}) reader.releaseLock() } } ``` For public deployment, keep the shared demo key on the server and configure an API key budget and per-IP request limits. The shared transport bounds model requests; it does not implement your application’s authentication. ## Browser agent A browser chat built with libfx and [WebAssembly](https://fx.sh/docs/lib/webassembly.md), using plain HTML without CSS or a UI framework. Use the free demo tokens or enter your own AI Gateway key to connect directly. > Build a browser agent with libfx and WebAssembly using plain HTML without CSS. Use the free-token proxy by default. Let visitors enter an AI Gateway API key to connect directly, without sending their key to the proxy or saving it. Start a new conversation when the key changes. Use the files below as a starting point. # Browser agent [Source](https://github.com/vercel-labs/fx/tree/1ca4e4f4e7946161e86db1ec177f964c52dc266d/examples/browser-agent) · [Live demo](https://fx-demo-browser-agent.vercel.app) Use Node.js 24. Save the files at the paths shown below, including examples/shared. Enter your AI Gateway API key in the page to connect directly. No server key is needed. To run the free-token proxy locally instead, set AI_GATEWAY_API_KEY in .env.local and use npx vercel dev. ```sh cd examples/browser-agent npm install npm run dev ``` ### examples/browser-agent/package.json ```json { "name": "fx-demo-browser-agent", "private": true, "type": "module", "scripts": { "dev": "vite", "build": "vite build" }, "dependencies": { "libfx": "0.0.8" }, "devDependencies": { "vite": "8.2.2" }, "engines": { "node": "24.x" } } ``` ### examples/browser-agent/index.html ```html Browser agent · fx examples

Browser agent

libfx runs in this browser through WebAssembly. Reload to start a new conversation.


Leave blank to use our proxy with some free tokens. Enter your own key to connect directly to AI Gateway using your balance. Your key is not saved. Changing it starts a new conversation.

Get an AI Gateway API key




Code and setup

``` ### examples/browser-agent/main.js ```js import { createFxAgent, supportsJspi } from 'libfx/browser' import { model } from '../shared/model.mjs' const form = document.querySelector('form') const button = document.querySelector('button') const reply = document.querySelector('#reply') const status = document.querySelector('#status') const keyInput = document.querySelector('#api-key') let agent let activeKey if (!supportsJspi()) { status.textContent = 'This browser does not support WebAssembly JSPI. Try desktop Chrome or Edge.' button.disabled = true } form.addEventListener('submit', async (event) => { event.preventDefault() const apiKey = keyInput.value.trim() button.disabled = true keyInput.disabled = true reply.value = '' status.textContent = 'Replying…' try { if (apiKey !== activeKey) { await agent?.close() agent = undefined } agent ??= await createFxAgent({ apiKey: apiKey || 'demo', model, fetch(url, init) { if (apiKey) return fetch(url, init) const path = new URL(url).pathname return fetch(`/api/gateway?path=${encodeURIComponent(path)}`, init) }, }) activeKey = apiKey const turn = agent.prompt(new FormData(form).get('prompt')) for await (const event of turn) { if (event.type === 'text_delta') reply.value += event.delta } const { stopReason } = await turn.result status.textContent = stopReason === 'refused' ? 'Request failed. See the reply for details.' : 'Reply complete.' } catch (error) { status.textContent = error.message === 'HostStreamFailed' ? 'Unable to connect to AI Gateway. Check your key and connection.' : error.message } finally { button.disabled = false keyInput.disabled = false } }) window.addEventListener('pagehide', () => { void agent?.close() }) button.disabled = !supportsJspi() ``` ### examples/browser-agent/api/gateway.js ```js import { errorResponse, gatewayFetch } from '../../shared/gateway.mjs' export default { async fetch(request) { try { const path = new URL(request.url).searchParams.get('path') const paths = ['/coding-agent/v1/models', '/v3/ai/language-model'] if (!paths.includes(path)) return new Response('Not found', { status: 404 }) const upstream = await gatewayFetch(new Request(`https://ai-gateway.vercel.sh${path}`, request)) return new Response(upstream.body, { status: upstream.status, headers: { 'content-type': upstream.headers.get('content-type') ?? 'text/plain', 'cache-control': 'no-store' }, }) } catch (error) { return errorResponse(error) } }, } ``` ### examples/shared/model.mjs ```js export const model = 'openai/gpt-4.1-nano' ``` ### examples/shared/gateway.mjs ```js import { model } from './model.mjs' function fail(status, message) { throw Object.assign(new Error(message), { status }) } export async function readJson(request) { const reader = request.body?.getReader() if (!reader) fail(400, 'Send a JSON request body.') const chunks = [] let size = 0 try { while (true) { const { value, done } = await reader.read() if (done) break size += value.byteLength if (size > 32768) { await reader.cancel() fail(413, 'This conversation is too long. Start a new one.') } chunks.push(value) } } finally { reader.releaseLock() } const bytes = new Uint8Array(size) let offset = 0 for (const chunk of chunks) { bytes.set(chunk, offset) offset += chunk.byteLength } try { return JSON.parse(new TextDecoder().decode(bytes)) } catch { fail(400, 'Send valid JSON.') } } export async function readPrompt(request) { const body = await readJson(request) const prompt = typeof body?.prompt === 'string' ? body.prompt.trim() : '' if (!prompt || prompt.length > 2000) fail(400, 'Enter a prompt of 1–2,000 characters.') return prompt } // All public examples share this transport policy; the agent code stays in each example. export async function gatewayFetch(input, init) { const request = new Request(input, init) const url = new URL(request.url) const catalog = url.pathname === '/coding-agent/v1/models' && request.method === 'GET' const generation = url.pathname === '/v3/ai/language-model' && request.method === 'POST' if (url.origin !== 'https://ai-gateway.vercel.sh' || url.search || (!catalog && !generation)) { fail(404, 'Unknown model endpoint.') } const key = process.env.AI_GATEWAY_API_KEY if (!key) fail(503, 'The demo is not configured yet.') let body if (generation) { const input = await readJson(request) const prompt = input?.prompt if (!Array.isArray(prompt) || !prompt.length || prompt.length > 40) fail(400, 'Invalid conversation.') for (const message of prompt) { if (!message || !['system', 'user', 'assistant'].includes(message.role)) fail(400, 'Only text messages are supported.') if (message.role === 'system' && typeof message.content === 'string') continue if (!Array.isArray(message.content) || !message.content.every( (part) => part?.type === 'text' && typeof part.text === 'string', )) fail(400, 'Only text messages are supported.') } body = JSON.stringify({ prompt, maxOutputTokens: 512 }) } const headers = new Headers({ authorization: `Bearer ${key}`, 'content-type': 'application/json' }) if (generation) { headers.set('ai-language-model-id', model) headers.set('ai-language-model-streaming', 'true') } for (const name of ['ai-gateway-protocol-version', 'ai-language-model-specification-version']) { const value = request.headers.get(name) if (value) headers.set(name, value) } return fetch(new Request(url, { method: request.method, headers, body, redirect: 'error', signal: AbortSignal.any([request.signal, AbortSignal.timeout(30000)]), })) } export function errorResponse(error) { const status = Number.isInteger(error?.status) ? error.status : 502 return new Response(status < 500 ? error.message : 'The demo could not respond. Try again shortly.', { status, headers: { 'content-type': 'text/plain; charset=utf-8', 'cache-control': 'no-store' }, }) } ``` For public deployment, keep the shared demo key on the server and configure an API key budget and per-IP request limits. The shared transport bounds model requests; it does not implement your application’s authentication. ## Next.js App Router A small Next.js App Router app that streams replies from a libfx agent running on the server. A React form lets you send a prompt and read the response. > Build a Next.js App Router app with a plain prompt form and a libfx agent in a server route. Stream the reply to the form and close the agent after each request. Use the files below as a starting point. # Next.js App Router [Source](https://github.com/vercel-labs/fx/tree/1ca4e4f4e7946161e86db1ec177f964c52dc266d/examples/nextjs-agent) · [Live demo](https://fx-demo-nextjs-agent.vercel.app) Use Node.js 24. Save the files at the paths shown below, including examples/shared. Create examples/nextjs-agent/.env.local with your server-side AI_GATEWAY_API_KEY. ```sh cd examples/nextjs-agent npm install npm run dev ``` ### examples/nextjs-agent/package.json ```json { "name": "fx-demo-nextjs-agent", "private": true, "type": "module", "scripts": { "dev": "next dev", "build": "next build", "start": "next start" }, "dependencies": { "libfx": "0.0.8", "next": "16.3.0", "react": "19.2.4", "react-dom": "19.2.4" }, "engines": { "node": "24.x" } } ``` ### examples/nextjs-agent/next.config.mjs ```js import { fileURLToPath } from 'node:url' const examplesRoot = fileURLToPath(new URL('..', import.meta.url)) export default { turbopack: { root: examplesRoot }, outputFileTracingRoot: examplesRoot, } ``` ### examples/nextjs-agent/app/layout.js ```js export const metadata = { title: 'Next.js agent · fx examples' } export default function Layout({ children }) { return {children} } ``` ### examples/nextjs-agent/app/page.js ```js 'use client' import { useEffect, useState } from 'react' import { readReply } from '../../shared/read-reply.mjs' export default function Page() { const [reply, setReply] = useState('') const [status, setStatus] = useState('') const [busy, setBusy] = useState(false) const [ready, setReady] = useState(false) useEffect(() => setReady(true), []) async function send(event) { event.preventDefault() const prompt = new FormData(event.currentTarget).get('prompt') setBusy(true) setReply('') setStatus('Replying…') try { const response = await fetch('/api/chat', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ prompt }), }) if (!response.ok) throw new Error(response.status === 429 ? 'Request limit reached. Try again later.' : await response.text()) for await (const text of readReply(response)) setReply((previous) => previous + text) setStatus('Reply complete.') } catch (error) { setStatus(error.message) } finally { setBusy(false) } } return

Next.js agent

A native libfx agent in an App Router route. Each request starts a new conversation.