Payload check
What the runner really sends to a model through the AI SDK, compared with the same loop written with the official SDKs. Offline, no key, no cost.
What
bun run bench:payload runs one task through two clients and keeps every request they send.
- The AI SDK side is the runner itself.
runTaskgets alanguageModelpointed at a fake server, so the requests are the ones a real run sends. - The official side is a loop written by hand with
@anthropic-ai/sdk(Messages API) andopenai(Responses API). It gets the same system prompt, task prompt and tools. Each tool's schema comes from its source: zod's own conversion for the file tools, the MCP server's own list for the rest. - A fake server answers both. The script is the same for both: the first answer calls a file tool and an MCP tool, the second answers with text. Nothing leaves the machine.
- The same run through the AI Gateway shows what the gateway receives from the AI SDK.
| File | Job |
|---|---|
packages/bench/src/payload/replay.ts | The fake server and its answers, one set per wire format |
packages/bench/src/payload/official.ts | The hand-written loops and the tools as the official SDKs see them |
packages/bench/src/payload/capture.ts | Runs both sides and keeps the requests |
packages/bench/src/payload/diff.ts | Normalizes two bodies and lists what differs |
packages/bench/src/payload/report.ts | Writes the raw bodies and diff.md |
packages/bench/src/payload/payload.test.ts | Same-words test and snapshots, in bun run check |
Why
Vektos measures how agents behave. If a library between the runner and the model rewrote the prompt or the tools, part of what we measure would be the library. This check shows the layer's effect directly instead of assuming it. The first result is in anecdote 13. The model reads the same words. The differences are in format and defaults, and none of them is a reason to leave the AI SDK.
Two tests keep it true:
- Same words. Both sides must carry the system prompt, the task prompt, every tool description and every tool result, word for word.
- Snapshots. The AI SDK's requests (Anthropic, OpenAI, gateway) are kept in
__snapshots__/payload.test.ts.snap. An upgrade ofaior a provider package that changes the wire failsbun run check, so a change cannot slip in with a version bump.
Try it
bun run bench:payload
bun run bench:payload --task kyc-form --context noneWrote results/payload-diff/2026-11-09T15-42-28/diff.mddiff.md has one table per API and request. A few rows from the Anthropic table:
| Field | AI SDK | Official SDK |
|---|---|---|
| `max_tokens` | `20000` | `16000` |
| `tool_choice` | `{"type":"auto"}` | — |
| `tools[get_component].input_schema.additionalProperties` | `false` | — |
| `tools[list_files].input_schema.$schema` | `"http://json-schema.org/draft-07/schema#"` | `"https://json-schema.org/draft/2020-12/schema"` |The folder also has the raw bodies (anthropic-ai-sdk.json, openai-official.json,
gateway.json…), to read whole.
When a snapshot fails after an upgrade:
cd packages/bench && bun test src/payload # read the diff
bun test src/payload -u # accept it, once understoodExtend it
- Another API. Add its answers to
replay.ts, a loop toofficial.tsand a family toMODELSincapture.ts. For Gemini, use@ai-sdk/googleand@google/genai. - Another context.
storybook-mcpneeds an HTTP transport on the official side, andds-mcp-check-fullneeds the raw form of its workspace tool. Both are refused for now. - A longer script.
ScriptedCall[]in the CLI sets the first answer's tool calls.
Until chapter 55 the AI SDK's row read 128000: with no cap from the runner, the Anthropic
provider sends the model's maximum. The runner now sends MAX_OUTPUT_TOKENS to every provider,
and the snapshots changed by exactly that.
Known gaps
- The gateway is a black box. We see what the AI SDK sends to the gateway, not what the
gateway sends to the provider. Checking it needs a real call with a key: the same first step
through the gateway and direct, then compare
inputTokens. - Two APIs. Anthropic Messages and OpenAI Responses. Not Gemini, not the OpenAI-compatible APIs (DeepSeek, Moonshot, Mistral).
- No streaming. The runner calls
generateText, so only plain requests are compared. - The official side is one way to write it. Another engineer could choose another
max_tokensor strict tools. The table shows the choices, not a single right answer.
Sign-in and saved conversations
Who may use the chat, how they sign in with GitHub, what is stored about them, and how a conversation is saved and read back.
CI
The GitHub Actions workflow that runs the checks, the database tests, both builds and every page of the app in Chrome on every pull request, with no secrets and no cost.