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
onPayloadcallback inStreamOptionsprovides a unified debugging hook across all pi-ai providers. - Located in
packages/ai/src/types.tsand implemented consistently across provider files likeopenai-responses.tsandanthropic.ts, this callback receives the complete request object before transmission. - Pass a logging function to
onPayloadto inspect field mappings, verify parameter values, and troubleshoot provider-specific request formats. - For persistent debugging, write payloads to timestamped JSON files using Node.js
fsmodule 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →