@alexkroman1/aai-ui 6.10.0 → 6.11.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/dist/index.js CHANGED
@@ -16,9 +16,8 @@ import clsx from "clsx";
16
16
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
17
17
  import { createContext, createElement, useCallback, useContext, useEffect, useId, useRef, useState } from "react";
18
18
  import { errorMessage, isTerminal, isTerminal as isTerminal$1 } from "@alexkroman1/aai";
19
- import { isRecord, omitUndefined, safeJsonParse as safeJsonParse$1 } from "@alexkroman1/aai/utils";
20
- import { createWorkflowApiClient } from "@alexkroman1/aai/workflow-api";
21
- import { createParser } from "eventsource-parser";
19
+ import { isRecord, omitUndefined } from "@alexkroman1/aai/utils";
20
+ import { createWorkflowApiClient, readEventStream } from "@alexkroman1/aai/workflow-api";
22
21
  import { createEpoch } from "@alexkroman1/aai/internal";
23
22
  //#region components/_form-readiness.ts
24
23
  /**
@@ -1210,83 +1209,6 @@ function repeatUntil(intervalMs, step) {
1210
1209
  };
1211
1210
  }
1212
1211
  //#endregion
1213
- //#region _sse.ts
1214
- /**
1215
- * The server-sent-event parser both workflow streams read through.
1216
- *
1217
- * Split out when the second stream arrived: `workflow-events.ts` watches a run's
1218
- * STATE and `use-workflow-progress.ts` reads what the run WROTE, and they parse
1219
- * the identical wire format. A second copy of a stream parser is the kind of
1220
- * duplication that goes wrong quietly — the two would drift on exactly the edges
1221
- * documented below, and the symptom is a page that silently stops updating.
1222
- *
1223
- * @internal
1224
- */
1225
- /**
1226
- * Parse an SSE byte stream into frames, with `eventsource-parser`.
1227
- *
1228
- * The parser is `aai-studio-client`'s already (`src/api-events.ts`), and it is
1229
- * catalogued — plus a transitive dependency of `@ai-sdk/provider-utils`, so it
1230
- * is in this package's tree either way. Adopting it retired a hand-rolled line
1231
- * splitter justified on the subset in use being "small and fixed" — true of our
1232
- * own server, and not of what sits between it and the page:
1233
- *
1234
- * - It split on `"\n\n"` only. The spec permits `\n`, `\r\n` and `\r`, and a
1235
- * CRLF stream is `\r\n\r\n` — no two adjacent `\n`, so **not one frame ever
1236
- * parsed** and `pump` fell through to `"fallback"` on the clean end. Silently
1237
- * dropping to the poll is the exact cost the run-watch stream exists to avoid,
1238
- * and an intermediary re-terminating lines is not our choice to make.
1239
- * - `line.startsWith("event: ")` required the space the spec makes optional.
1240
- * - It kept only the LAST `data:` line rather than joining a multi-line one.
1241
- *
1242
- * Those three are what `workflow-events.test.ts` pins, and they are the three
1243
- * that DISCRIMINATE — checked by running the specs against the old parser.
1244
- * Comment frames and a leading BOM were already fine and are not credited here:
1245
- * a heartbeat has no `event:` line, so the old parser dropped it anyway, and
1246
- * `TextDecoder` strips the BOM before either parser sees a byte.
1247
- *
1248
- * Three properties of the parser this leans on. `feed` invokes `onEvent`
1249
- * SYNCHRONOUSLY for every complete event in the chunk, so a batch is collected
1250
- * per read and yielded in arrival order — the generator shape, and therefore
1251
- * every caller, is unchanged. An event with no `data:` line at all is not
1252
- * dispatched (also per spec); every frame these routes emit carries one, since
1253
- * `workflow-api-events.ts` and `workflow-api-stream.ts` write `event:` and
1254
- * `data:` together. And a chunk ending in a lone `\r` holds that byte back,
1255
- * because it may yet turn out to be the first half of a `\r\n` — so a CR-ONLY
1256
- * stream chunked per frame dispatches one frame behind, and its last frame not
1257
- * at all (it would need `reset({ consume: true })`, which would also consume a
1258
- * genuinely truncated frame as if it were whole). Nothing emits CR-only endings,
1259
- * and the outcome if anything did is the safe one for both readers: a stream
1260
- * that ends with no final frame is read as a dropped connection, which the run
1261
- * watch answers by falling back to the poll and the progress reader by
1262
- * re-opening.
1263
- */
1264
- async function* sseFrames(body, signal) {
1265
- const reader = body.getReader();
1266
- const decoder = new TextDecoder();
1267
- let batch = [];
1268
- const parser = createParser({ onEvent: ({ event, data }) => {
1269
- if (event === void 0) return;
1270
- batch.push({
1271
- event,
1272
- data: safeJsonParse$1(data)
1273
- });
1274
- } });
1275
- try {
1276
- while (!signal.aborted) {
1277
- const { done, value } = await reader.read();
1278
- if (done) return;
1279
- parser.feed(decoder.decode(value, { stream: true }));
1280
- if (batch.length === 0) continue;
1281
- const frames = batch;
1282
- batch = [];
1283
- for (const frame of frames) yield frame;
1284
- }
1285
- } finally {
1286
- reader.cancel().catch(() => void 0);
1287
- }
1288
- }
1289
- //#endregion
1290
1212
  //#region workflow-events.ts
1291
1213
  /**
1292
1214
  * Watching a run over server-sent events — the PUSH half of `useWorkflowRun`.
@@ -1336,7 +1258,7 @@ function watchRunEvents(getClient, runId, onRun, onSettled, onFallback) {
1336
1258
  const pump = async () => {
1337
1259
  const res = await getClient().watch(runId, controller.signal);
1338
1260
  if (!(res.ok && res.body)) return "fallback";
1339
- for await (const frame of sseFrames(res.body, controller.signal)) {
1261
+ for await (const frame of readEventStream(res.body, controller.signal)) {
1340
1262
  if (frame.event === "run" && frame.data) onRun(frame.data);
1341
1263
  const outcome = endingFor(frame.event);
1342
1264
  if (outcome) return outcome;
@@ -1845,7 +1767,7 @@ const DEFAULT_PROGRESS_POLL_MS = 1e3;
1845
1767
  async function consumeFrames(body, signal) {
1846
1768
  const chunks = [];
1847
1769
  let ending = "partial";
1848
- for await (const frame of sseFrames(body, signal)) if (frame.event === "chunk") chunks.push(frame.data);
1770
+ for await (const frame of readEventStream(body, signal)) if (frame.event === "chunk") chunks.push(frame.data);
1849
1771
  else if (frame.event === "done") ending = frame.data?.complete ? "complete" : "partial";
1850
1772
  else if (frame.event === "missing") return {
1851
1773
  ending: "complete",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alexkroman1/aai-ui",
3
- "version": "6.10.0",
3
+ "version": "6.11.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -23,13 +23,12 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "clsx": "^2.1.1",
26
- "eventsource-parser": "^4.0.0",
27
26
  "partysocket": "^1.3.0",
28
27
  "react-markdown": "^10.1.0",
29
28
  "remark-gfm": "^4.0.1",
30
29
  "use-stick-to-bottom": "^1.1.6",
31
30
  "use-sync-external-store": "^1.6.0",
32
- "@alexkroman1/aai": "6.10.0"
31
+ "@alexkroman1/aai": "6.11.0"
33
32
  },
34
33
  "peerDependencies": {
35
34
  "react": "^19.0.0",
package/dist/_sse.d.ts DELETED
@@ -1,56 +0,0 @@
1
- /**
2
- * The server-sent-event parser both workflow streams read through.
3
- *
4
- * Split out when the second stream arrived: `workflow-events.ts` watches a run's
5
- * STATE and `use-workflow-progress.ts` reads what the run WROTE, and they parse
6
- * the identical wire format. A second copy of a stream parser is the kind of
7
- * duplication that goes wrong quietly — the two would drift on exactly the edges
8
- * documented below, and the symptom is a page that silently stops updating.
9
- *
10
- * @internal
11
- */
12
- /** One parsed SSE frame. Comment frames (heartbeats) are skipped, not yielded. */
13
- export type SseFrame = {
14
- event: string;
15
- data: unknown;
16
- };
17
- /**
18
- * Parse an SSE byte stream into frames, with `eventsource-parser`.
19
- *
20
- * The parser is `aai-studio-client`'s already (`src/api-events.ts`), and it is
21
- * catalogued — plus a transitive dependency of `@ai-sdk/provider-utils`, so it
22
- * is in this package's tree either way. Adopting it retired a hand-rolled line
23
- * splitter justified on the subset in use being "small and fixed" — true of our
24
- * own server, and not of what sits between it and the page:
25
- *
26
- * - It split on `"\n\n"` only. The spec permits `\n`, `\r\n` and `\r`, and a
27
- * CRLF stream is `\r\n\r\n` — no two adjacent `\n`, so **not one frame ever
28
- * parsed** and `pump` fell through to `"fallback"` on the clean end. Silently
29
- * dropping to the poll is the exact cost the run-watch stream exists to avoid,
30
- * and an intermediary re-terminating lines is not our choice to make.
31
- * - `line.startsWith("event: ")` required the space the spec makes optional.
32
- * - It kept only the LAST `data:` line rather than joining a multi-line one.
33
- *
34
- * Those three are what `workflow-events.test.ts` pins, and they are the three
35
- * that DISCRIMINATE — checked by running the specs against the old parser.
36
- * Comment frames and a leading BOM were already fine and are not credited here:
37
- * a heartbeat has no `event:` line, so the old parser dropped it anyway, and
38
- * `TextDecoder` strips the BOM before either parser sees a byte.
39
- *
40
- * Three properties of the parser this leans on. `feed` invokes `onEvent`
41
- * SYNCHRONOUSLY for every complete event in the chunk, so a batch is collected
42
- * per read and yielded in arrival order — the generator shape, and therefore
43
- * every caller, is unchanged. An event with no `data:` line at all is not
44
- * dispatched (also per spec); every frame these routes emit carries one, since
45
- * `workflow-api-events.ts` and `workflow-api-stream.ts` write `event:` and
46
- * `data:` together. And a chunk ending in a lone `\r` holds that byte back,
47
- * because it may yet turn out to be the first half of a `\r\n` — so a CR-ONLY
48
- * stream chunked per frame dispatches one frame behind, and its last frame not
49
- * at all (it would need `reset({ consume: true })`, which would also consume a
50
- * genuinely truncated frame as if it were whole). Nothing emits CR-only endings,
51
- * and the outcome if anything did is the safe one for both readers: a stream
52
- * that ends with no final frame is read as a dropped connection, which the run
53
- * watch answers by falling back to the poll and the progress reader by
54
- * re-opening.
55
- */
56
- export declare function sseFrames(body: ReadableStream<Uint8Array>, signal: AbortSignal): AsyncGenerator<SseFrame>;