AI SDK

Options

Configure prompt and output capture (with redaction and truncation), tool input capture, cost estimation per model, and error handling during AI calls.

createAILogger(log, options?) accepts a single options bag. Every option is opt-in. The defaults stay quiet, and they stay safe: nothing a model was sent or returned reaches your drain until you ask for it by name.

OptionTypeDefaultDescription
toolInputsboolean | ToolInputsOptionsfalseCapture tool call inputs alongside their names (off by default to avoid leaking sensitive data).
promptboolean | CaptureOptionsfalseCapture the prompt sent to the first model call as ai.prompt (off by default to avoid leaking sensitive data).
outputboolean | CaptureOptionsfalseCapture the text generated by the last model call as ai.output (off by default to avoid leaking sensitive data).
costRecord<string, ModelCost>undefinedPricing map. Keys are model IDs, values are { input, output } in dollars per 1M tokens.

Tool Inputs

By default, ai.toolCalls is a string[] of tool names. Enable toolInputs to capture inputs too, which suits debugging agent behaviour or auditing what data the model reached for.

Tool inputs get large, and they carry SQL, API keys and customer PII. Reach for maxLength and transform before raw capture in production.

Capture everything

const ai = createAILogger(log, { toolInputs: true })

Truncate long inputs

const ai = createAILogger(log, { toolInputs: { maxLength: 200 } })

Redact sensitive fields

const ai = createAILogger(log, {
  toolInputs: {
    maxLength: 500,
    transform: (input, toolName) => {
      if (toolName === 'queryDB') return { sql: '***' }
      return input
    },
  },
})
Sub-optionTypeDescription
maxLengthnumberTruncate stringified inputs exceeding this character length (appends ).
transform(input, toolName) => unknownCustom transform applied before maxLength. Use to redact fields or reshape data.

When toolInputs is enabled, ai.toolCalls becomes an Array<{ name, input }> instead of a plain string array.

Prompt and Output Capture

By default, nothing the model was sent or returned reaches your drain. Two independent options turn capture on, one per direction: prompt records what was sent, output records what came back. Enable one, the other, or both.

const ai = createAILogger(log, { prompt: true, output: true })

The wide event then carries:

  • ai.prompt: the formatted text of the prompt sent to the first model call. One block per message prefixed with its role; text parts are inlined, and other part types become [tool-call name], [tool-result name] or [file] markers.
  • ai.output: the text generated by the last model call, the final answer in a multi-step run. Streamed responses accumulate from the text chunks.

Capture only the direction you need:

const ai = createAILogger(log, { prompt: true }) // ai.prompt only
const ai = createAILogger(log, { output: true }) // ai.output only
Prompts and outputs can be large, and they carry whatever your users typed. Reach for maxLength and transform before raw capture in production.

Truncate long content

Each option takes its own maxLength:

const ai = createAILogger(log, { prompt: { maxLength: 500 } })

Redact or reshape

transform receives the captured text and runs before maxLength:

const ai = createAILogger(log, {
  prompt: {
    maxLength: 500,
    transform: text => redact(text),
  },
})
Sub-optionTypeDescription
maxLengthnumberTruncate captured text exceeding this character length (appends ).
transform(content) => stringCustom transform applied before maxLength.
Prompt and output capture is a middleware capability: it works with createAILogger and createAIMiddleware. The standalone createEvlogIntegration observes telemetry events and has no access to the model call parameters, so ai.prompt and ai.output stay absent there.

Cost Estimation

Pass a cost map to compute estimated dollar cost per call. The middleware multiplies token usage by the per-million rates and sets ai.estimatedCost on the wide event.

const ai = createAILogger(log, {
  cost: {
    'claude-sonnet-4.6': { input: 3, output: 15 },
    'gpt-4o': { input: 2.5, output: 10 },
  },
})

Read the result from your handler with ai.getEstimatedCost(), which suits billing dashboards or warning users before expensive calls.

Keep your cost map in one file alongside model selection so renaming a model in production also updates pricing. Per-route maps drift the moment two routes disagree about which model they call, so keep one.

Error Handling

If a model call fails, the middleware captures the error into the wide event before re-throwing:

Wide Event
{
  "ai": {
    "calls": 1,
    "model": "claude-sonnet-4.6",
    "provider": "anthropic",
    "finishReason": "error",
    "error": "API rate limit exceeded"
  }
}

Stream errors (e.g. content filter) are also captured from the stream's error chunks. Your error-handling code (try/catch, route-level error handlers) keeps working as usual, since the middleware only observes.