@blocksdiy/react-common 1.47.0 → 1.49.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.
@@ -1,45 +1,77 @@
1
1
  /**
2
- * Which agent run (turn) produced each message of a conversation.
2
+ * Which agent run (turn) produced each message of a conversation, and when that run
3
+ * started.
3
4
  *
4
5
  * A row an app agent writes records the run that wrote it (`agentRunId`). On
5
6
  * `agent/connect` the server replays the transcript and then sends one AG-UI CUSTOM
6
7
  * event, `blocks.message_run_ids`, carrying `{ runIds: { [messageId]: runId } }` for
7
- * the replayed messages it can attribute (older messages have none). The run id
8
+ * the replayed messages it can attribute (older messages have none), plus
9
+ * `runStartedAt: { [runId]: epochMs }` for the runs it has a time for. The run id
8
10
  * rides in an event rather than on the messages because the AG-UI client validates
9
11
  * snapshot messages against a strict schema that drops unknown fields.
12
+ *
13
+ * A LIVE run is attributed here, from its own events: RUN_STARTED names the run and
14
+ * is its time, the messages it adds are its. The server records the same run id and
15
+ * its own clock, so a reload agrees with what the session showed.
10
16
  */
11
17
  export const MESSAGE_RUN_IDS_EVENT_NAME = "blocks.message_run_ids";
12
18
  export const EMPTY_MESSAGE_RUN_IDS = new Map();
19
+ export const EMPTY_RUN_STARTED_AT = new Map();
13
20
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
14
- /** The `{ messageId: runId }` payload of a run-ids CUSTOM event, or null for any other event. */
15
- export const readMessageRunIdsEvent = (event) => {
21
+ /**
22
+ * One map of the run-ids CUSTOM event, keeping the entries whose value `isValue`; null for
23
+ * any other event, or when the event lacks that map (a server, or a thread, from before it).
24
+ */
25
+ const readEventMap = (event, key, isValue) => {
16
26
  if (!isRecord(event) || event.name !== MESSAGE_RUN_IDS_EVENT_NAME || !isRecord(event.value)) {
17
27
  return null;
18
28
  }
19
- const runIds = event.value.runIds;
20
- if (!isRecord(runIds)) {
29
+ const entries = event.value[key];
30
+ if (!isRecord(entries)) {
21
31
  return null;
22
32
  }
23
33
  const out = {};
24
- for (const [messageId, runId] of Object.entries(runIds)) {
25
- if (messageId && typeof runId === "string" && runId) {
26
- out[messageId] = runId;
34
+ for (const [entryKey, value] of Object.entries(entries)) {
35
+ if (entryKey && isValue(value)) {
36
+ out[entryKey] = value;
27
37
  }
28
38
  }
29
39
  return out;
30
40
  };
41
+ /** The `{ messageId: runId }` payload of a run-ids CUSTOM event, or null for any other event. */
42
+ export const readMessageRunIdsEvent = (event) => readEventMap(event, "runIds", (value) => typeof value === "string" && value !== "");
43
+ /** The `{ runId: epochMs }` payload of a run-ids CUSTOM event, or null when it carries none. */
44
+ export const readRunStartedAtEvent = (event) => readEventMap(event, "runStartedAt", (value) => typeof value === "number" && Number.isFinite(value));
31
45
  /** `previous` plus `incoming`; returns `previous` itself when nothing changes (no re-render). */
32
- export const mergeMessageRunIds = (previous, incoming) => {
46
+ export const mergeEntries = (previous, incoming) => {
33
47
  let next = null;
34
- for (const [messageId, runId] of Object.entries(incoming)) {
35
- if (previous.get(messageId) === runId) {
48
+ for (const [key, value] of Object.entries(incoming)) {
49
+ if (previous.get(key) === value) {
36
50
  continue;
37
51
  }
38
52
  next ??= new Map(previous);
39
- next.set(messageId, runId);
53
+ next.set(key, value);
40
54
  }
41
55
  return next ?? previous;
42
56
  };
57
+ /**
58
+ * The messages a run that just started takes as its input — the unattributed ones after
59
+ * the last assistant message (the user's message, a tool's answer). Unattributed messages
60
+ * before that are older history and stay so. The server's `claim_run_input` rule.
61
+ */
62
+ export const claimRunInput = (messages, known, runId) => {
63
+ const out = {};
64
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
65
+ const message = messages[index];
66
+ if (message.role === "assistant") {
67
+ break;
68
+ }
69
+ if (!known.has(message.id)) {
70
+ out[message.id] = runId;
71
+ }
72
+ }
73
+ return out;
74
+ };
43
75
  /** Ids of the messages `runId` produced, in transcript order (empty when the run isn't in it). */
44
76
  export const messageIdsOfRun = (messages, runIds, runId) => {
45
77
  if (!runId) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocksdiy/react-common",
3
- "version": "1.47.0",
3
+ "version": "1.49.0",
4
4
  "type": "module",
5
5
  "description": "React common",
6
6
  "keywords": [],
@@ -39,7 +39,7 @@
39
39
  "penpal": "^8.0.0",
40
40
  "react": "^19.2.8",
41
41
  "use-stick-to-bottom": "1.1.4",
42
- "@blocksdiy/blocks-client-api": "1.25.0"
42
+ "@blocksdiy/blocks-client-api": "1.5.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/react": "^19.2.18",