How to Debug Provider API Payloads in pi-ai: A Complete Guide

Use the onPayload callback in StreamOptions to intercept and inspect the exact JSON payload sent to any AI provider before the HTTP request executes.

Debugging provider API payloads in pi-ai is essential when troubleshooting request format errors or unexpected provider responses. The badlogic/pi-mono repository provides a built-in debugging hook through the onPayload callback available in all streaming options. This mechanism allows you to inspect the exact request body that gets transmitted to providers like OpenAI, Anthropic, Google, and Amazon Bedrock.

Understanding the onPayload Callback Architecture

The debugging capability centers on the onPayload optional property defined in the core type definitions. In packages/ai/src/types.ts, the StreamOptions interface declares:

export interface StreamOptions {
  // ... other options ...
  onPayload?: (payload: unknown) => void;
}

This callback receives the complete request object immediately before the provider's SDK or HTTP client transmits it. The SimpleStreamOptions class in packages/ai/src/providers/simple-options.ts propagates this callback from user-supplied options into the internal StreamOptions, ensuring all provider implementations inherit the debugging hook.

Where the Debugging Hook Lives in the Source Code

Every provider implementation in the pi-ai codebase invokes options?.onPayload?.(requestObject) immediately before executing the network request:

Provider File Path Line Reference
OpenAI Responses packages/ai/src/providers/openai-responses.ts Lines 93-94
OpenAI Completions packages/ai/src/providers/openai-completions.ts Line 108
Anthropic packages/ai/src/providers/anthropic.ts Line 239
Google Gemini CLI packages/ai/src/providers/google-gemini-cli.ts Line 363
Amazon Bedrock packages/ai/src/providers/amazon-bedrock.ts Line 148

This consistent pattern means you can use identical debugging code across all providers supported by pi-ai.

Practical Implementation: Logging Payloads

Basic Console Logging

To quickly inspect payloads during development, pass a simple logging function to the onPayload option:

import { streamSimpleOpenAIResponses } from "pi-mono/packages/ai/src/providers/openai-responses.js";

function logPayload(payload: unknown) {
  console.log("=== Provider payload ===");
  console.dir(payload, { depth: null, colors: true });
}

const options = {
  apiKey: process.env.OPENAI_API_KEY,
  onPayload: logPayload,
  maxTokens: 1024,
  temperature: 0.7,
};

const model = {
  api: "openai-responses",
  provider: "openai",
  id: "gpt-4o-mini",
  baseUrl: "https://api.openai.com/v1",
};

const context = { 
  messages: [{ role: "user", content: "Explain pi-ai debugging." }] 
};

const stream = streamSimpleOpenAIResponses(model, context, options);

for await (const event of stream) {
  if (event.type === "error") console.error(event.error);
}

When executed, this outputs the exact JSON structure sent to OpenAI, including normalized field names like max_output_tokens instead of maxTokens.

Writing Payloads to File for Analysis

For debugging production issues or sharing request payloads with team members, write the payload to a timestamped file:

import { writeFileSync } from "node:fs";
import { streamSimpleOpenAIResponses } from "pi-mono/packages/ai/src/providers/openai-responses.js";

function dumpPayloadToFile(payload: unknown) {
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
  const filename = `debug-payload-${timestamp}.json`;
  writeFileSync(filename, JSON.stringify(payload, null, 2));
  console.log(`Payload written to ${filename}`);
}

const options = {
  apiKey: process.env.OPENAI_API_KEY,
  onPayload: dumpPayloadToFile,
  maxTokens: 512,
  temperature: 0.5,
};

// ... model, context, and stream execution as shown above

This creates files like debug-payload-2024-01-15T10-30-00-000Z.json containing the complete request payload for later inspection.

Debugging Tips and Common Issues

When analyzing provider API payloads in pi-ai, look for these specific patterns:

Situation What to Inspect in the Payload
Missing field errors Compare the printed object against the provider's official API documentation. Check for required fields like model, messages, or input.
Unexpected parameter values Verify that your SimpleStreamOptions (e.g., temperature, maxTokens) are correctly mapped to provider-specific field names (e.g., max_output_tokens for OpenAI).
Provider-specific flags Ensure you pass the appropriate extended options type (e.g., OpenAIResponsesOptions) to access provider-specific parameters like service_tier.
Authentication issues While the payload doesn't contain the API key, verify that the model object includes correct baseUrl and provider identifiers.
Retry behavior If you see maxRetryDelayMs or similar retry configuration in the options, adjust these values and observe how the payload structure changes across retries.

Summary

  • The onPayload callback in StreamOptions provides a unified debugging hook across all pi-ai providers.
  • Located in packages/ai/src/types.ts and implemented consistently across provider files like openai-responses.ts and anthropic.ts, this callback receives the complete request object before transmission.
  • Pass a logging function to onPayload to inspect field mappings, verify parameter values, and troubleshoot provider-specific request formats.
  • For persistent debugging, write payloads to timestamped JSON files using Node.js fs module within your callback function.

Frequently Asked Questions

How do I enable payload debugging for all providers in pi-ai?

Pass the onPayload callback to any streaming function's options object. Since all providers in the badlogic/pi-mono repository implement this hook consistently—whether using streamOpenAIResponses, streamAnthropic, or streamAmazonBedrock—the same debugging code works across every provider without modification.

What information does the onPayload callback receive?

The callback receives the complete request payload as an unknown type, which typically contains the normalized request body ready for transmission. For OpenAI providers in packages/ai/src/providers/openai-responses.ts, this includes fields like model, input, max_output_tokens, and temperature. For Anthropic in packages/ai/src/providers/anthropic.ts, you'll see model, messages, max_tokens, and other provider-specific parameters.

Can I use onPayload in production environments?

Yes, but implement it carefully. The callback executes synchronously before every provider request, so heavy operations like file I/O or network calls will block the stream initialization. For production debugging, consider conditional logging based on environment variables, or write payloads to an async queue rather than blocking the main thread. The onPayload hook itself adds negligible overhead if your callback simply pushes data to an in-memory array or performs quick console logging.

Why don't I see authentication headers in the payload object?

The onPayload callback intentionally receives only the request body payload, not the HTTP headers or authentication configuration. This design prevents accidental logging of sensitive API keys while still allowing inspection of the data structure. To verify authentication issues, check that your apiKey is correctly passed in the options object and that the model configuration includes the correct baseUrl and provider identifiers as defined in packages/ai/src/types.ts.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →