@butlerbot/sdk 0.0.27 → 0.0.28

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.
@@ -12,9 +12,14 @@
12
12
  * { messageId, message: "Good day to you", completed } // the whole value: replace
13
13
  * { messageId, delta: " to you", completed: false } // what was added: append
14
14
  *
15
- * Callers should not have to care. This puts the message back together, so
16
- * `payload.message` is the whole message so far exactly as it always was, and keeps
17
- * `payload.delta` for anyone who would rather append than re-render.
15
+ * Deltas are also sent bare: no metadata block, since it is the same on every frame of a
16
+ * message and several times the size of the few characters a delta carries. The frame that
17
+ * opens a message brings it, and the one that finishes it brings it again.
18
+ *
19
+ * Callers should not have to care about any of that. This puts the message back together,
20
+ * so `payload.message` is the whole message so far exactly as it always was, restores the
21
+ * metadata onto every event, and keeps `payload.delta` for anyone who would rather append
22
+ * than re-render.
18
23
  *
19
24
  * Whole values arrive for the last event of a message and for anything replaying after a
20
25
  * reconnect, and they replace rather than extend. That is what makes a reconnect cheap and
@@ -23,7 +28,7 @@
23
28
  /**
24
29
  * Rebuilds whole values from a stream of pieces.
25
30
  *
26
- * Stateful, and one per stream: it holds the text of every message the stream is still
27
- * writing, so a turn and a progress stream never see each other's.
31
+ * Stateful, and one per stream: it holds what every message the stream is still writing
32
+ * has said so far, so a turn and a progress stream never see each other's.
28
33
  */
29
34
  export declare function createStreamAccumulator(): (payload: unknown) => unknown;
@@ -13,9 +13,14 @@
13
13
  * { messageId, message: "Good day to you", completed } // the whole value: replace
14
14
  * { messageId, delta: " to you", completed: false } // what was added: append
15
15
  *
16
- * Callers should not have to care. This puts the message back together, so
17
- * `payload.message` is the whole message so far exactly as it always was, and keeps
18
- * `payload.delta` for anyone who would rather append than re-render.
16
+ * Deltas are also sent bare: no metadata block, since it is the same on every frame of a
17
+ * message and several times the size of the few characters a delta carries. The frame that
18
+ * opens a message brings it, and the one that finishes it brings it again.
19
+ *
20
+ * Callers should not have to care about any of that. This puts the message back together,
21
+ * so `payload.message` is the whole message so far exactly as it always was, restores the
22
+ * metadata onto every event, and keeps `payload.delta` for anyone who would rather append
23
+ * than re-render.
19
24
  *
20
25
  * Whole values arrive for the last event of a message and for anything replaying after a
21
26
  * reconnect, and they replace rather than extend. That is what makes a reconnect cheap and
@@ -31,10 +36,11 @@ const STREAMED_FIELDS = {
31
36
  /**
32
37
  * Rebuilds whole values from a stream of pieces.
33
38
  *
34
- * Stateful, and one per stream: it holds the text of every message the stream is still
35
- * writing, so a turn and a progress stream never see each other's.
39
+ * Stateful, and one per stream: it holds what every message the stream is still writing
40
+ * has said so far, so a turn and a progress stream never see each other's.
36
41
  */
37
42
  function createStreamAccumulator() {
43
+ /** Text so far and the metadata it was opened with, per streamed id. */
38
44
  const values = new Map();
39
45
  return (payload) => {
40
46
  const response = payload;
@@ -52,19 +58,26 @@ function createStreamAccumulator() {
52
58
  const whole = event.payload[fields.text];
53
59
  if (typeof delta !== "string" && typeof whole !== "string")
54
60
  return payload;
55
- const text = typeof delta === "string" ? (values.get(id) ?? "") + delta : whole;
61
+ const held = values.get(id);
62
+ const text = typeof delta === "string" ? (held?.text ?? "") + delta : whole;
63
+ // Deltas are sent without metadata, because it is identical on every frame of a
64
+ // message and many times the size of the text. The frame that opened the message
65
+ // carried it, so it is remembered here and handed back on every event — a caller
66
+ // sees it throughout, exactly as when the server repeated it a thousand times.
67
+ const metadata = event.metadata ?? held?.metadata;
56
68
  // A finished value is the last anyone will hear of that id. Holding it would only
57
69
  // leak, and ids are reused across the steps of a turn.
58
70
  if (event.payload.completed)
59
71
  values.delete(id);
60
72
  else
61
- values.set(id, text);
73
+ values.set(id, { text, metadata });
62
74
  return {
63
75
  ...response,
64
76
  data: {
65
77
  ...response.data,
66
78
  response: {
67
79
  ...event,
80
+ ...(metadata !== undefined ? { metadata } : {}),
68
81
  payload: {
69
82
  ...event.payload,
70
83
  [fields.text]: text,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@butlerbot/sdk",
3
- "version": "0.0.27",
3
+ "version": "0.0.28",
4
4
  "description": "The official ButlerBot SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/readme.md CHANGED
@@ -41,15 +41,19 @@ const { text } = await convo.ask("Hey there Alfred!");
41
41
 
42
42
  ### Streaming
43
43
 
44
- Alfred streams a reply as it writes it. Each event carries either the whole message so far
45
- or just the piece it added, never both:
44
+ Alfred streams a reply as it writes it. A message opens with a whole value, is extended a
45
+ piece at a time, and finishes whole again. No event carries both:
46
46
 
47
47
  ```jsonc
48
- { "messageId": "m1", "message": "Good day to you", "completed": false } // whole: replace
49
- { "messageId": "m1", "delta": " to you", "completed": false } // added: append
48
+ { "messageId": "m1", "message": "Good day", "completed": false, "metadata": {...} }
49
+ { "messageId": "m1", "delta": " to you", "completed": false }
50
+ { "messageId": "m1", "message": "Good day to you", "completed": true, "metadata": {...} }
50
51
  ```
51
52
 
52
- The SDK puts them back together, so `payload.message` is always the whole message:
53
+ Deltas travel bare no metadata block, since it is identical on every frame of a message
54
+ and many times the size of the few characters a delta carries. The SDK remembers it from
55
+ the frame that opened the message and puts it back, so **every event you receive has both
56
+ the whole message and its metadata**, exactly as it always did:
53
57
 
54
58
  ```typescript
55
59
  convo.send("Tell me a story", (res) => {
@@ -68,9 +72,9 @@ stuttering one — append it when it is there, and replace with `message` when i
68
72
  after a reconnect. Do not read `completed` to tell the two apart — a whole message arrives
69
73
  with `completed: false` whenever you are being caught up mid-answer.
70
74
 
71
- `accumulateStream: false` hands you the wire payloads untouched, where a piece arrives as
72
- `delta` with no `message` beside it. Only worth it if you are appending anyway and want
73
- nothing between you and the socket.
75
+ `accumulateStream: false` hands you the wire payloads untouched: deltas with no `message`
76
+ beside them and no metadata. Only worth it if you are appending anyway and want nothing
77
+ between you and the socket.
74
78
 
75
79
  ## Link
76
80