Skip to content

Commit f91b96e

Browse files
authored
feat(sdk,core): preserve chat.agent context after cancel / OOM / crash (#3671)
1 parent 12d2125 commit f91b96e

8 files changed

Lines changed: 1393 additions & 107 deletions

File tree

apps/webapp/test/replay-after-crash.test.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,19 @@ function textTurn(id: string, text: string): UIMessageChunk[] {
5555
* via the webapp's real `generatePresignedUrl` (so snapshot reads
5656
* hit a real S3-compatible backend).
5757
* - `readSessionStreamRecords` returns the canonical
58-
* `{ records: [{ data, id, seqNum }] }` shape — `data` is the
59-
* JSON-encoded chunk body, mirroring the webapp's S2 record shape.
58+
* `{ records: [{ data, id, seqNum }] }` shape. `data` is the parsed
59+
* chunk OBJECT — the SDK writer puts the chunk object directly into
60+
* the record envelope and the webapp route forwards it as-is, so
61+
* the schema now declares `data: z.unknown()` and consumers use it
62+
* without an extra `JSON.parse` step.
6063
*/
6164
function stubApiClient(opts: {
6265
projectRef: string;
6366
envSlug: string;
6467
sessionOutChunks: unknown[];
6568
}) {
6669
const records = opts.sessionOutChunks.map((chunk, i) => ({
67-
data: typeof chunk === "string" ? chunk : JSON.stringify(chunk),
70+
data: chunk,
6871
id: `evt-${i + 1}`,
6972
seqNum: i + 1,
7073
}));

packages/core/src/v3/schemas/api.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1995,14 +1995,21 @@ export type SendInputStreamResponseBody = z.infer<typeof SendInputStreamResponse
19951995
* Response body for `GET /realtime/v1/sessions/:id/:io/records`. A non-SSE,
19961996
* `wait=0` drain of a session channel — used at run boot for snapshot
19971997
* replay where the SSE long-poll tax (~1s on empty streams) was the
1998-
* dominant cost. The shape mirrors the webapp's internal `StreamRecord`
1999-
* type (`apps/webapp/app/services/realtime/types.ts`); each record's
2000-
* `data` is a JSON-encoded chunk body that callers parse client-side.
1998+
* dominant cost.
1999+
*
2000+
* `data` is the parsed chunk body (the SDK writer puts the chunk object
2001+
* directly into the S2 record envelope; the route unwraps the envelope
2002+
* and forwards the inner object as-is). Callers use it directly — no
2003+
* additional JSON.parse step. Schema is `z.unknown()` because chunk
2004+
* shape varies by `chunk.type` (the AI SDK's `UIMessageChunk`
2005+
* discriminated union plus Trigger control records); consumers
2006+
* already runtime-check on the discriminator and tolerate malformed
2007+
* records by skipping them.
20012008
*/
20022009
export const ReadSessionStreamRecordsResponseBody = z.object({
20032010
records: z.array(
20042011
z.object({
2005-
data: z.string(),
2012+
data: z.unknown(),
20062013
id: z.string(),
20072014
seqNum: z.number(),
20082015
})

0 commit comments

Comments
 (0)