Skip to main content

JavaScript SDK

The Foil JavaScript SDK provides full-featured tracing, logging, and feedback collection for Node.js applications.
Full examples: Browse runnable examples at github.com/getfoil/foil-examples — including hello-foil (auto-instrumentation) and customer-support-agent (SDK with tools and feedback).

Installation

Requirements: Node.js 18 or higher
Or with yarn / pnpm:

Configuration

Get your API key from the Foil Dashboard under Settings > API Keys.
Never hardcode API keys in your source code. Use environment variables instead.

Configuration Options

Debug Mode

Enable debug mode to see detailed logs of all SDK operations:
Or set the environment variable:
Debug output shows span start/end events with IDs, nesting depth visualization, timing information, and API call results.

Wizard

The Foil wizard automatically instruments your project. It scans your code, detects LLM providers and application patterns, then adds the right tracing setup.
Options: The wizard creates a foil.js (or foil.mjs for ESM) config file, adds the Foil import to your entry point, and wraps your agent logic with the appropriate tracing pattern.

Quick Start

The core concept is the trace. A trace represents a complete unit of work (like handling a user request) and contains one or more spans (individual operations like LLM calls, tool executions, or retrieval steps).

Nested Spans

The key benefit of ctx.llmCall() is that other spans automatically nest under it, giving you a clear tree in the dashboard:

Convenience Methods

The TraceContext provides shorthand methods for common span types. Each wraps your async function in a span and automatically records the return value as output:

Tool Tracing

Foil provides two ways to trace tool calls, depending on whether the LLM decides which tools to run or your code decides. Use this when your agent uses OpenAI function calling. The LLM decides which tools to call at runtime — you define the available tools and their implementations, then executeTools() handles everything:
  1. Reads tool_calls from the OpenAI response
  2. Executes each tool function with the LLM-provided arguments
  3. Creates a traced TOOL span for each call (name, input, output, duration)
  4. Returns formatted tool messages ready to feed back to the next OpenAI call
This produces:
You don’t manually specify tool names or decide which tools to call — the LLM does. The SDK reads tool names and arguments directly from response.choices[0].message.tool_calls.

ctx.tool() — Code-Driven (Manual)

Use this for fixed pipeline steps that always run regardless of what the LLM says — like a mandatory database write, a config lookup, or a preprocessing step.

When to Use Which

For most AI agents, ctx.executeTools() is the right choice. Use ctx.tool() only for operations that aren’t driven by LLM decisions.

What Gets Captured

Streaming

Streaming is fully supported:

Auto-Instrumentation

Foil supports automatic instrumentation of LLM calls via OpenLLMetry. Pass instrumentModules to the Foil constructor and all calls to supported providers are traced automatically — no manual wrapping needed.
Auto-instrumentation is an optional enhancement. For most use cases, ctx.llmCall() is the recommended approach — it works with any LLM provider and gives you nested span trees (tools under LLM calls). Auto-instrumentation captures LLM calls automatically; combine it with ctx.executeTools() to also capture tool calls driven by OpenAI function calling.

Basic Setup

With foil.trace()

Wrap auto-instrumented calls in foil.trace() to group them under a single trace:
Don’t combine instrumentModules with ctx.llmCall() for the same provider. Using both creates duplicate spans — one from auto-instrumentation and one from ctx.llmCall(). Choose one approach per provider.

With Tool Calling (executeTools)

Auto-instrumentation captures LLM calls automatically, but tool execution still needs ctx.executeTools(). This is the recommended pattern for agentic tool-calling loops with auto-instrumentation:
This produces:

Supported Libraries

Graceful Shutdown

Always shut down gracefully to flush pending spans:
For full control over the OpenTelemetry pipeline:

OTEL Module

Manual FoilSpanProcessor

FoilSpanProcessor Options

Custom Attributes

  • Check that your API key is correct
  • Ensure Foil is constructed with instrumentModules before any LLM calls
  • Call await foil.flush() before process exit
  • Enable debug logging to see export status
  • Ensure new Foil({ instrumentModules }) is called at the top of your app, before importing LLM libraries
  • Check that the LLM library is supported
  • Some details require specific library versions
  • Don’t use instrumentModules and ctx.llmCall() for the same LLM calls
  • Don’t initialize both Foil.init() from @getfoil/foil-js/otel and new Foil({ instrumentModules })

Signals and Feedback

Record custom metrics and user feedback tied to your traces:

Span Types

Foil supports several span types to categorize different operations:

Error Handling

Errors are automatically captured when spans fail:

Complete Example

Advanced Patterns

For advanced flows where you need explicit control over parent-child relationships:
When one agent delegates to another, use span.createChildContext() to create nested agent spans:
Fetch completed traces for debugging or analysis:
Pass trace context to external services via headers:
For non-critical telemetry without full tracing:
ctx.llmCall() works with any LLM provider — not just OpenAI:
Add custom properties to spans for filtering and analysis: