Request Metadata and Usage Correlation
Completion and streaming requests accept JSON-safe request metadata and an optional request identifier:
const response = await client.complete({
messages: [{ role: 'user', content: 'Summarize this ticket.' }],
metadata: { tenantPlan: 'pro', feature: 'ticket-summary' },
requestId: 'http-request-123',
});The values are copied to the corresponding UsageEvent sent to the configured usage logger. Metadata is application context; it is not sent to providers. requestId is also copied to v3 stream events.
The client also assigns an independent stable eventId to each usage event. Unlike requestId, it is unique per provider step, so automatic tool rounds can share one request ID without colliding in idempotent persistence.
The built-in console and Postgres usage loggers remove credentials, prompts, messages, explicit tool data, raw request/response payloads, transcripts, and media payloads from this metadata while preserving benign attribution such as feature labels. A custom UsageLogger receives the original UsageEvent and must implement its own logging allowlist or sanitization policy.
If usageLoggerFailureMode: 'strict' is enabled, logger rejection is reported as UsageLoggerError. Its callback/error receipt contains only safe operation, event, model/provider/request identifiers and canonical numeric usage. It never contains request metadata, prompts, tool arguments, media, raw provider data, credentials, or the underlying logger exception.
Metadata is validated and cloned before dispatch. It may contain only null, booleans, strings, finite numbers, arrays, and plain or null-prototype objects. Undefined values, BigInt, symbols, functions, non-finite numbers, cycles, custom prototypes, symbol keys, accessors, and excessively deep graphs reject with a typed HTTP-400-compatible ProviderCapabilityError. Accessors are inspected without invoking getters, and later caller mutation cannot change the captured request metadata.
Conversation turns accept the same options:
await conversation.send('Summarize this ticket.', {
metadata: { feature: 'ticket-summary' },
requestId: 'http-request-123',
});
for await (const chunk of conversation.sendStream('Stream the summary.', {
metadata: { feature: 'ticket-summary' },
requestId: 'http-request-124',
})) {
render(chunk);
}The caller-supplied request ID is passed to every per-step context callback and every provider attempt in an automatic tool loop. If no ID is supplied, context callbacks retain the deterministic sessionId:toolRound fallback.
The Session API accepts both fields in POST /sessions/{id}/message requests and forwards them to the underlying conversation and usage logger. Metadata must contain JSON values only. Secrets, credentials, prompts, tool payloads, transcripts, and media should never be placed in request metadata, even when a built-in logger is configured.