Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions apps/webapp/test/replay-after-crash.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,19 @@ function textTurn(id: string, text: string): UIMessageChunk[] {
* via the webapp's real `generatePresignedUrl` (so snapshot reads
* hit a real S3-compatible backend).
* - `readSessionStreamRecords` returns the canonical
* `{ records: [{ data, id, seqNum }] }` shape — `data` is the
* JSON-encoded chunk body, mirroring the webapp's S2 record shape.
* `{ records: [{ data, id, seqNum }] }` shape. `data` is the parsed
* chunk OBJECT — the SDK writer puts the chunk object directly into
* the record envelope and the webapp route forwards it as-is, so
* the schema now declares `data: z.unknown()` and consumers use it
* without an extra `JSON.parse` step.
*/
function stubApiClient(opts: {
projectRef: string;
envSlug: string;
sessionOutChunks: unknown[];
}) {
const records = opts.sessionOutChunks.map((chunk, i) => ({
data: typeof chunk === "string" ? chunk : JSON.stringify(chunk),
data: chunk,
id: `evt-${i + 1}`,
seqNum: i + 1,
}));
Expand Down
15 changes: 11 additions & 4 deletions packages/core/src/v3/schemas/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1995,14 +1995,21 @@ export type SendInputStreamResponseBody = z.infer<typeof SendInputStreamResponse
* Response body for `GET /realtime/v1/sessions/:id/:io/records`. A non-SSE,
* `wait=0` drain of a session channel — used at run boot for snapshot
* replay where the SSE long-poll tax (~1s on empty streams) was the
* dominant cost. The shape mirrors the webapp's internal `StreamRecord`
* type (`apps/webapp/app/services/realtime/types.ts`); each record's
* `data` is a JSON-encoded chunk body that callers parse client-side.
* dominant cost.
*
* `data` is the parsed chunk body (the SDK writer puts the chunk object
* directly into the S2 record envelope; the route unwraps the envelope
* and forwards the inner object as-is). Callers use it directly — no
* additional JSON.parse step. Schema is `z.unknown()` because chunk
* shape varies by `chunk.type` (the AI SDK's `UIMessageChunk`
* discriminated union plus Trigger control records); consumers
* already runtime-check on the discriminator and tolerate malformed
* records by skipping them.
*/
export const ReadSessionStreamRecordsResponseBody = z.object({
records: z.array(
z.object({
data: z.string(),
data: z.unknown(),
Comment thread
ericallam marked this conversation as resolved.
id: z.string(),
seqNum: z.number(),
})
Expand Down
Loading
Loading