You have a chat() that isn't behaving as expected — a missing chunk, a middleware that doesn't seem to fire, a tool call with wrong args. By the end of this guide, you'll have turned on debug logging and will see every chunk, middleware transform, and tool call flowing through your call.
Add debug: true to any activity call:
import { chat } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
const stream = chat({
adapter: openaiText("gpt-4o"),
messages: [{ role: "user", content: "Hello" }],
debug: true,
});import { chat } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
const stream = chat({
adapter: openaiText("gpt-4o"),
messages: [{ role: "user", content: "Hello" }],
debug: true,
});Every internal event now prints to the console with a [tanstack-ai:<category>] prefix:
[tanstack-ai:request] activity=chat provider=openai model=gpt-4o messages=1 tools=0 stream=true
[tanstack-ai:agentLoop] run started
[tanstack-ai:provider] provider=openai type=response.output_text.delta
[tanstack-ai:output] type=TEXT_MESSAGE_CONTENT
...[tanstack-ai:request] activity=chat provider=openai model=gpt-4o messages=1 tools=0 stream=true
[tanstack-ai:agentLoop] run started
[tanstack-ai:provider] provider=openai type=response.output_text.delta
[tanstack-ai:output] type=TEXT_MESSAGE_CONTENT
...Pass a DebugConfig object instead of true. Every unspecified category defaults to true, so toggle by setting specific flags to false:
chat({
adapter: openaiText("gpt-4o"),
messages,
debug: { middleware: false }, // everything except middleware
});chat({
adapter: openaiText("gpt-4o"),
messages,
debug: { middleware: false }, // everything except middleware
});If you want to see ONLY a specific set of categories, set the rest to false explicitly. Errors default to true — keep them on unless you really want total silence:
chat({
adapter: openaiText("gpt-4o"),
messages,
debug: {
provider: true,
output: true,
middleware: false,
tools: false,
agentLoop: false,
config: false,
errors: true, // keep errors on — they're cheap and important
request: false,
},
});chat({
adapter: openaiText("gpt-4o"),
messages,
debug: {
provider: true,
output: true,
middleware: false,
tools: false,
agentLoop: false,
config: false,
errors: true, // keep errors on — they're cheap and important
request: false,
},
});Pass a Logger implementation and all debug output flows through it instead of console:
import type { Logger } from "@tanstack/ai";
import pino from "pino";
const pinoLogger = pino();
const logger: Logger = {
debug: (msg, meta) => pinoLogger.debug(meta, msg),
info: (msg, meta) => pinoLogger.info(meta, msg),
warn: (msg, meta) => pinoLogger.warn(meta, msg),
error: (msg, meta) => pinoLogger.error(meta, msg),
};
chat({
adapter: openaiText("gpt-4o"),
messages,
debug: { logger }, // all categories on, piped to pino
});import type { Logger } from "@tanstack/ai";
import pino from "pino";
const pinoLogger = pino();
const logger: Logger = {
debug: (msg, meta) => pinoLogger.debug(meta, msg),
info: (msg, meta) => pinoLogger.info(meta, msg),
warn: (msg, meta) => pinoLogger.warn(meta, msg),
error: (msg, meta) => pinoLogger.error(meta, msg),
};
chat({
adapter: openaiText("gpt-4o"),
messages,
debug: { logger }, // all categories on, piped to pino
});The default logger is exported as ConsoleLogger if you want to wrap it:
import { ConsoleLogger } from "@tanstack/ai";import { ConsoleLogger } from "@tanstack/ai";If your Logger implementation throws — a cyclic-meta JSON.stringify, a transport that rejects synchronously, a typo in a bound this — the exception is swallowed so it never masks the real error that triggered the log call (for example, a provider SDK failure inside the chat stream). You won't see the log line, but the pipeline error still surfaces through thrown exceptions and RUN_ERROR chunks.
If you need to know when your own logger is failing, guard inside your implementation:
const logger: Logger = {
debug: (msg, meta) => {
try {
pinoLogger.debug(meta, msg);
} catch (err) {
// surface to wherever you track infra errors
process.stderr.write(`logger failed: ${String(err)}\n`);
}
},
// ... info, warn, error
};const logger: Logger = {
debug: (msg, meta) => {
try {
pinoLogger.debug(meta, msg);
} catch (err) {
// surface to wherever you track infra errors
process.stderr.write(`logger failed: ${String(err)}\n`);
}
},
// ... info, warn, error
};| Category | Logs | Applies to |
|---|---|---|
| request | Outgoing call to a provider (model, message count, tool count) | All activities |
| provider | Every raw chunk/frame received from a provider SDK | Streaming activities (chat, realtime) |
| output | Every chunk or result yielded to the caller | All activities |
| middleware | Inputs and outputs around every middleware hook | chat() only |
| tools | Before/after tool call execution | chat() only |
| agentLoop | Agent-loop iterations and phase transitions | chat() only |
| config | Config transforms returned by middleware onConfig hooks | chat() only |
| errors | Every caught error anywhere in the pipeline | All activities |
Errors flow through the logger unconditionally — even when you omit debug:
chat({ adapter, messages }); // still prints [tanstack-ai:errors] ... on failurechat({ adapter, messages }); // still prints [tanstack-ai:errors] ... on failureTo fully silence (including errors), set debug: false or debug: { errors: false }. Errors also always reach the caller via thrown exceptions or RUN_ERROR stream chunks — the logger is additive, not the only surface.
The same debug option works on every activity:
summarize({ adapter, text, debug: true });
generateImage({ adapter, prompt: "a cat", debug: { logger } });
generateSpeech({ adapter, text, debug: { request: true } });summarize({ adapter, text, debug: true });
generateImage({ adapter, prompt: "a cat", debug: { logger } });
generateSpeech({ adapter, text, debug: { request: true } });The chat-only categories (middleware, tools, agentLoop, config) simply never fire for these activities because those concepts don't exist in their pipelines.
If you're building middleware and want to see chunks flow through it, debug: { middleware: true } is faster than writing a logging middleware. See Middleware for writing your own middleware, or Observability for the programmatic event client.