Agent payloads use schema allowlists, not blocklist sanitization
Define client and persistence payloads with explicit contracts — never strip internal AI response fields with a deny list.
nestjs-realtime-stream-tool-output-schema-required
Why it matters
| Stake | If ignored |
|---|---|
| Data leak |
|
| Unclear failure |
|
| API drift |
|
How to fix
Every tool declares outputSchema and returns only schema-validated output from execute. Project AI response stream events into your RunEvent contract (related structured-events rule). Provider-only metadata stays in the LLM adapter. No deny lists, no destructuring internal fields off a raw response object. Code blocks below are illustrative, not prescriptive.
Examples
ts
const INTERNAL_KEYS = ['providerMetadata', 'thoughtSignature'];
export function sanitizeRunResult(msgs: AgentMessage[]) {
return msgs.map((m) => stripKeys(m, INTERNAL_KEYS));
}
await this.runs.save({ parts: aiResponse.parts });
export const searchSiteTool = {
execute: async (input) => this.searchService.find(input.query),
};ts
export const searchSiteTool = defineTool({
outputSchema: z.object({ matches: z.array(z.object({ id: z.string(), title: z.string() })) }),
execute: async ({ query }) =>
searchSiteTool.outputSchema.parse(await this.searchService.find(query)),
});
function toRunEvent(chunk: AgentStreamEvent, out: Map<string, unknown>): RunEvent | null {
if (chunk.type === 'text-delta') return { type: 'text-delta', delta: chunk.delta };
if (chunk.type === 'tool-completed') {
return { type: 'tool-call-completed', toolCallId: chunk.id, result: out.get(chunk.id)! };
}
return null;
}