@tanstack/ai-sandbox-cloudflare 0.3.12 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/run-log.ts CHANGED
@@ -97,7 +97,8 @@ export interface RunEventLogReadOptions {
97
97
  * - `append` assigns the next `seq` (0, 1, 2, …) and returns it.
98
98
  * - `read` yields the backlog after `fromSeq` in order, then live-tails new
99
99
  * events, and RETURNS once the run is terminal and the cursor has caught up.
100
- * - All methods reject for an unknown `runId` except `get`, which resolves null.
100
+ * - Unknown-run behavior is method-specific: `append`/`finish`/`read` reject,
101
+ * `get` resolves null, and `update` is a no-op.
101
102
  */
102
103
  export interface RunEventLog {
103
104
  /**
@@ -120,9 +121,9 @@ export interface RunEventLog {
120
121
  error?: RunError,
121
122
  ) => Promise<void>
122
123
  /**
123
- * Patch the record's mutable fields ({@link RunRecordPatch}). Unknown `runId`
124
- * is a NO-OP (never a throw, never a create) — core's `RunStore.update`
125
- * invariant, which `runLogStore` maps onto this method.
124
+ * Patch the record's mutable fields ({@link RunRecordPatch}). Unknown and
125
+ * already-terminal runs are NO-OPs (never a throw, never a create), preserving
126
+ * the first terminal status against late driver updates.
126
127
  *
127
128
  * MUST wake blocked readers, exactly like `append`/`finish`: the record and
128
129
  * the event log share one status field here, so a driver that terminalizes
@@ -299,9 +300,49 @@ export class InMemoryRunEventLog implements RunEventLog {
299
300
  return Promise.resolve()
300
301
  }
301
302
 
303
+ touch(runId: string): Promise<void> {
304
+ const state = this.runs.get(runId)
305
+ if (!state || isTerminalRunStatus(state.record.status)) {
306
+ return Promise.resolve()
307
+ }
308
+ state.record.updatedAt = this.now()
309
+ return Promise.resolve()
310
+ }
311
+
312
+ finishIfStale(
313
+ runId: string,
314
+ cutoff: number,
315
+ chunk: Extract<StreamChunk, { type: 'RUN_ERROR' }>,
316
+ ): Promise<boolean> {
317
+ const state = this.runs.get(runId)
318
+ if (
319
+ !state ||
320
+ isTerminalRunStatus(state.record.status) ||
321
+ state.record.updatedAt >= cutoff
322
+ ) {
323
+ return Promise.resolve(false)
324
+ }
325
+
326
+ const now = this.now()
327
+ const seq = state.record.lastSeq + 1
328
+ state.chunks.push(chunk)
329
+ state.record.lastSeq = seq
330
+ state.record.status = 'failed'
331
+ state.record.error = {
332
+ message: chunk.message,
333
+ ...(chunk.code !== undefined ? { code: chunk.code } : {}),
334
+ }
335
+ state.record.finishedAt = now
336
+ state.record.updatedAt = now
337
+ this.wake(state)
338
+ return Promise.resolve(true)
339
+ }
340
+
302
341
  update(runId: string, patch: RunRecordPatch): Promise<void> {
303
342
  const state = this.runs.get(runId)
304
- if (!state) return Promise.resolve() // unknown runId is a no-op
343
+ if (!state || isTerminalRunStatus(state.record.status)) {
344
+ return Promise.resolve()
345
+ }
305
346
  state.record = { ...state.record, ...patch, updatedAt: this.now() }
306
347
  // A patch may terminalize the shared status field (core's driver writes its
307
348
  // terminal status through `RunStore.update`) — parked readers must see it.