Skip to content

Commit 9936c60

Browse files
authored
Merge branch 'main' into fix/env-not-found-404
2 parents 5b71063 + f91b96e commit 9936c60

17 files changed

Lines changed: 1476 additions & 314 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: feature
4+
---
5+
6+
Expose `is_warm_start` in the TRQL `runs` schema so warm vs cold start data can be queried and visualized in Dashboards.

apps/webapp/app/v3/querySchemas.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,11 +176,19 @@ export const runsSchema: TableSchema = {
176176
idempotency_key: {
177177
name: "idempotency_key",
178178
clickhouseName: "idempotency_key_user",
179-
...column("String", { description: "Idempotency key (available from 4.3.3)", example: "user-123-action-456" }),
179+
...column("String", {
180+
description: "Idempotency key (available from 4.3.3)",
181+
example: "user-123-action-456",
182+
}),
180183
},
181184
idempotency_key_scope: {
182185
name: "idempotency_key_scope",
183-
...column("String", { description: "The idempotency key scope determines whether a task should be considered unique within a parent run, a specific attempt, or globally. An empty value means there's no idempotency key set (available from 4.3.3).", example: "run", allowedValues: ["global", "run", "attempt"], }),
186+
...column("String", {
187+
description:
188+
"The idempotency key scope determines whether a task should be considered unique within a parent run, a specific attempt, or globally. An empty value means there's no idempotency key set (available from 4.3.3).",
189+
example: "run",
190+
allowedValues: ["global", "run", "attempt"],
191+
}),
184192
},
185193
region: {
186194
name: "region",
@@ -404,6 +412,13 @@ export const runsSchema: TableSchema = {
404412
...column("UInt8", { description: "Whether this is a test run (0 or 1)", example: "0" }),
405413
expression: "if(is_test > 0, true, false)",
406414
},
415+
is_warm_start: {
416+
name: "is_warm_start",
417+
...column("Nullable(UInt8)", {
418+
description: "Whether this run used a warm start vs a cold start.",
419+
example: "1",
420+
}),
421+
},
407422
concurrency_key: {
408423
name: "concurrency_key",
409424
...column("String", {

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/build/package.json

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@
3131
"./extensions/typescript": "./src/extensions/typescript.ts",
3232
"./extensions/puppeteer": "./src/extensions/puppeteer.ts",
3333
"./extensions/playwright": "./src/extensions/playwright.ts",
34-
"./extensions/lightpanda": "./src/extensions/lightpanda.ts",
35-
"./extensions/secureExec": "./src/extensions/secureExec.ts"
34+
"./extensions/lightpanda": "./src/extensions/lightpanda.ts"
3635
},
3736
"sourceDialects": [
3837
"@triggerdotdev/source"
@@ -66,9 +65,6 @@
6665
],
6766
"extensions/lightpanda": [
6867
"dist/commonjs/extensions/lightpanda.d.ts"
69-
],
70-
"extensions/secureExec": [
71-
"dist/commonjs/extensions/secureExec.d.ts"
7268
]
7369
}
7470
},
@@ -211,17 +207,6 @@
211207
"types": "./dist/commonjs/extensions/lightpanda.d.ts",
212208
"default": "./dist/commonjs/extensions/lightpanda.js"
213209
}
214-
},
215-
"./extensions/secureExec": {
216-
"import": {
217-
"@triggerdotdev/source": "./src/extensions/secureExec.ts",
218-
"types": "./dist/esm/extensions/secureExec.d.ts",
219-
"default": "./dist/esm/extensions/secureExec.js"
220-
},
221-
"require": {
222-
"types": "./dist/commonjs/extensions/secureExec.d.ts",
223-
"default": "./dist/commonjs/extensions/secureExec.js"
224-
}
225210
}
226211
},
227212
"main": "./dist/commonjs/index.js",

packages/build/src/extensions/secureExec.ts

Lines changed: 0 additions & 172 deletions
This file was deleted.

packages/core/src/v3/apiClient/runStream.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -233,8 +233,10 @@ export class SSEStreamSubscription implements StreamSubscription {
233233
// reset the timer naturally.
234234
stallTimeoutMs?: number;
235235
// HTTP statuses that should NOT be retried — fail the stream
236-
// permanently. `404` (stream gone) and `410` (session closed)
237-
// are sensible defaults; tune per-caller for other 4xx.
236+
// permanently. Defaults cover the permanent client-error set:
237+
// `400` (bad request), `404` (stream gone), `409` (conflict),
238+
// `410` (session closed), `422` (unprocessable). Tune per-caller
239+
// for other 4xx.
238240
nonRetryableStatuses?: readonly number[];
239241
// Optional fetch override. Used by transports that need to route
240242
// the SSE connect through a custom path (proxy, custom headers,
@@ -249,7 +251,9 @@ export class SSEStreamSubscription implements StreamSubscription {
249251
this.retryJitter = options.retryJitter ?? 0.5;
250252
this.fetchTimeoutMs = options.fetchTimeoutMs ?? 30_000;
251253
this.stallTimeoutMs = options.stallTimeoutMs ?? 0;
252-
this.nonRetryableStatuses = new Set(options.nonRetryableStatuses ?? [404, 410]);
254+
this.nonRetryableStatuses = new Set(
255+
options.nonRetryableStatuses ?? [400, 404, 409, 410, 422]
256+
);
253257
}
254258

255259
/**

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)