@copilotkit/shared 1.73.0 → 1.73.2-canary.1790068297

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.
Files changed (74) hide show
  1. package/README.md +22 -0
  2. package/dist/attachments/content.cjs +17 -0
  3. package/dist/attachments/content.cjs.map +1 -0
  4. package/dist/attachments/content.d.cts +9 -0
  5. package/dist/attachments/content.d.cts.map +1 -0
  6. package/dist/attachments/content.d.mts +9 -0
  7. package/dist/attachments/content.d.mts.map +1 -0
  8. package/dist/attachments/content.mjs +16 -0
  9. package/dist/attachments/content.mjs.map +1 -0
  10. package/dist/event-transforms/index.cjs +15 -0
  11. package/dist/event-transforms/index.d.cts +4 -0
  12. package/dist/event-transforms/index.d.mts +4 -0
  13. package/dist/event-transforms/index.mjs +5 -0
  14. package/dist/event-transforms/open-generative-ui-middleware.cjs +286 -0
  15. package/dist/event-transforms/open-generative-ui-middleware.cjs.map +1 -0
  16. package/dist/event-transforms/open-generative-ui-middleware.d.cts +60 -0
  17. package/dist/event-transforms/open-generative-ui-middleware.d.cts.map +1 -0
  18. package/dist/event-transforms/open-generative-ui-middleware.d.mts +60 -0
  19. package/dist/event-transforms/open-generative-ui-middleware.d.mts.map +1 -0
  20. package/dist/event-transforms/open-generative-ui-middleware.mjs +283 -0
  21. package/dist/event-transforms/open-generative-ui-middleware.mjs.map +1 -0
  22. package/dist/event-transforms/recorded-events.cjs +59 -0
  23. package/dist/event-transforms/recorded-events.cjs.map +1 -0
  24. package/dist/event-transforms/recorded-events.d.cts +23 -0
  25. package/dist/event-transforms/recorded-events.d.cts.map +1 -0
  26. package/dist/event-transforms/recorded-events.d.mts +23 -0
  27. package/dist/event-transforms/recorded-events.d.mts.map +1 -0
  28. package/dist/event-transforms/recorded-events.mjs +58 -0
  29. package/dist/event-transforms/recorded-events.mjs.map +1 -0
  30. package/dist/index.cjs +8 -0
  31. package/dist/index.cjs.map +1 -1
  32. package/dist/index.d.cts +3 -1
  33. package/dist/index.d.cts.map +1 -1
  34. package/dist/index.d.mts +3 -1
  35. package/dist/index.d.mts.map +1 -1
  36. package/dist/index.mjs +3 -1
  37. package/dist/index.mjs.map +1 -1
  38. package/dist/index.umd.js +224 -1
  39. package/dist/index.umd.js.map +1 -1
  40. package/dist/package.cjs +1 -1
  41. package/dist/package.mjs +1 -1
  42. package/dist/types/message.d.cts +3 -3
  43. package/dist/types/message.d.cts.map +1 -1
  44. package/dist/types/message.d.mts +3 -3
  45. package/dist/types/message.d.mts.map +1 -1
  46. package/dist/utils/index.cjs +1 -0
  47. package/dist/utils/index.cjs.map +1 -1
  48. package/dist/utils/index.d.cts +1 -0
  49. package/dist/utils/index.d.cts.map +1 -1
  50. package/dist/utils/index.d.mts +1 -0
  51. package/dist/utils/index.d.mts.map +1 -1
  52. package/dist/utils/index.mjs +1 -0
  53. package/dist/utils/index.mjs.map +1 -1
  54. package/dist/utils/row-render-keys.cjs +206 -0
  55. package/dist/utils/row-render-keys.cjs.map +1 -0
  56. package/dist/utils/row-render-keys.d.cts +65 -0
  57. package/dist/utils/row-render-keys.d.cts.map +1 -0
  58. package/dist/utils/row-render-keys.d.mts +65 -0
  59. package/dist/utils/row-render-keys.d.mts.map +1 -0
  60. package/dist/utils/row-render-keys.mjs +201 -0
  61. package/dist/utils/row-render-keys.mjs.map +1 -0
  62. package/package.json +12 -1
  63. package/src/__tests__/root-entry-browser-safety.test.ts +7 -1
  64. package/src/attachments/content.test.ts +48 -0
  65. package/src/attachments/content.ts +16 -0
  66. package/src/attachments/index.ts +2 -0
  67. package/src/event-transforms/index.ts +5 -0
  68. package/src/event-transforms/open-generative-ui-middleware.ts +392 -0
  69. package/src/event-transforms/recorded-events.test.ts +166 -0
  70. package/src/event-transforms/recorded-events.ts +74 -0
  71. package/src/utils/__tests__/row-render-keys.test.ts +388 -0
  72. package/src/utils/index.ts +1 -0
  73. package/src/utils/row-render-keys.ts +263 -0
  74. package/tsdown.config.ts +7 -4
package/dist/package.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
2
  //#region package.json
3
- var version = "1.73.0";
3
+ var version = "1.73.2-canary.1790068297";
4
4
 
5
5
  //#endregion
6
6
  Object.defineProperty(exports, 'version', {
package/dist/package.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "1.73.0";
2
+ var version = "1.73.2-canary.1790068297";
3
3
 
4
4
  //#endregion
5
5
  export { version };
@@ -1,5 +1,5 @@
1
1
  import * as agui from "@ag-ui/core";
2
- import { AudioInputPart, DocumentInputPart, ImageInputPart, InputContent, InputContentDataSource as InputContentDataSource$1, InputContentSource, InputContentUrlSource as InputContentUrlSource$1, TextInputContent as TextInputPart, VideoInputPart } from "@ag-ui/core";
2
+ import { AudioInputPart, DocumentInputPart, ImageInputPart, InputContent as InputContent$1, InputContentDataSource as InputContentDataSource$1, InputContentSource, InputContentUrlSource as InputContentUrlSource$1, TextInputContent as TextInputPart, VideoInputPart } from "@ag-ui/core";
3
3
 
4
4
  //#region src/types/message.d.ts
5
5
  /**
@@ -36,7 +36,7 @@ type AIMessage = agui.AssistantMessage & {
36
36
  runId?: string;
37
37
  };
38
38
  type UserMessage = agui.UserMessage;
39
- type Message = AIMessage | ToolResult | UserMessage | SystemMessage | DeveloperMessage | ActivityMessage | ReasoningMessage;
39
+ type Message$1 = AIMessage | ToolResult | UserMessage | SystemMessage | DeveloperMessage | ActivityMessage | ReasoningMessage;
40
40
  //#endregion
41
- export { AIMessage, ActivityMessage, type AudioInputPart, DeveloperMessage, type DocumentInputPart, ImageData, type ImageInputPart, type InputContent, type InputContentDataSource$1 as InputContentDataSource, type InputContentSource, type InputContentUrlSource$1 as InputContentUrlSource, Message, ReasoningMessage, Role, SystemMessage, type TextInputPart, ToolCall, ToolResult, UserMessage, type VideoInputPart };
41
+ export { AIMessage, ActivityMessage, type AudioInputPart, DeveloperMessage, type DocumentInputPart, ImageData, type ImageInputPart, type InputContent$1 as InputContent, type InputContentDataSource$1 as InputContentDataSource, type InputContentSource, type InputContentUrlSource$1 as InputContentUrlSource, Message$1 as Message, ReasoningMessage, Role, SystemMessage, type TextInputPart, ToolCall, ToolResult, UserMessage, type VideoInputPart };
42
42
  //# sourceMappingURL=message.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"message.d.cts","names":[],"sources":["../../src/types/message.ts"],"mappings":";;;;;;AAuBA;;;;;UAAiB,SAAA;EACf,MAAA;EACA,KAAA;AAAA;AAAA,KAIU,IAAA,GAAO,IAAA,CAAK,IAAA;AAAA,KACZ,aAAA,GAAgB,IAAA,CAAK,aAAA;AAAA,KACrB,gBAAA,GAAmB,IAAA,CAAK,gBAAA;AAAA,KACxB,QAAA,GAAW,IAAA,CAAK,QAAA;AAAA,KAChB,eAAA,GAAkB,IAAA,CAAK,eAAA;AAAA,KACvB,gBAAA,GAAmB,IAAA,CAAK,gBAAA;AAAA,KAGxB,UAAA,GAAa,IAAA,CAAK,WAAA;EAC5B,QAAA;AAAA;AAAA,KAGU,SAAA,GAAY,IAAA,CAAK,gBAAA;EAC3B,YAAA,IAAgB,KAAA;EAChB,oBAAA;EACA,SAAA;EACA,KAAA;EAbqB;;AACvB;;;EAkBE,KAAA,GAAQ,SAAA;EACR,KAAA;AAAA;AAAA,KAGU,WAAA,GAAc,IAAA,CAAK,WAAA;AAAA,KAEnB,OAAA,GACR,SAAA,GACA,UAAA,GACA,WAAA,GACA,aAAA,GACA,gBAAA,GACA,eAAA,GACA,gBAAA"}
1
+ {"version":3,"file":"message.d.cts","names":[],"sources":["../../src/types/message.ts"],"mappings":";;;;;;AAuBA;;;;;UAAiB,SAAA;EACf,MAAA;EACA,KAAA;AAAA;AAAA,KAIU,IAAA,GAAO,IAAA,CAAK,IAAA;AAAA,KACZ,aAAA,GAAgB,IAAA,CAAK,aAAA;AAAA,KACrB,gBAAA,GAAmB,IAAA,CAAK,gBAAA;AAAA,KACxB,QAAA,GAAW,IAAA,CAAK,QAAA;AAAA,KAChB,eAAA,GAAkB,IAAA,CAAK,eAAA;AAAA,KACvB,gBAAA,GAAmB,IAAA,CAAK,gBAAA;AAAA,KAGxB,UAAA,GAAa,IAAA,CAAK,WAAA;EAC5B,QAAA;AAAA;AAAA,KAGU,SAAA,GAAY,IAAA,CAAK,gBAAA;EAC3B,YAAA,IAAgB,KAAA;EAChB,oBAAA;EACA,SAAA;EACA,KAAA;EAbqB;;AACvB;;;EAkBE,KAAA,GAAQ,SAAA;EACR,KAAA;AAAA;AAAA,KAGU,WAAA,GAAc,IAAA,CAAK,WAAA;AAAA,KAEnB,SAAA,GACR,SAAA,GACA,UAAA,GACA,WAAA,GACA,aAAA,GACA,gBAAA,GACA,eAAA,GACA,gBAAA"}
@@ -1,5 +1,5 @@
1
1
  import * as agui from "@ag-ui/core";
2
- import { AudioInputPart, DocumentInputPart, ImageInputPart, InputContent, InputContentDataSource as InputContentDataSource$1, InputContentSource, InputContentUrlSource as InputContentUrlSource$1, TextInputContent as TextInputPart, VideoInputPart } from "@ag-ui/core";
2
+ import { AudioInputPart, DocumentInputPart, ImageInputPart, InputContent as InputContent$1, InputContentDataSource as InputContentDataSource$1, InputContentSource, InputContentUrlSource as InputContentUrlSource$1, TextInputContent as TextInputPart, VideoInputPart } from "@ag-ui/core";
3
3
 
4
4
  //#region src/types/message.d.ts
5
5
  /**
@@ -36,7 +36,7 @@ type AIMessage = agui.AssistantMessage & {
36
36
  runId?: string;
37
37
  };
38
38
  type UserMessage = agui.UserMessage;
39
- type Message = AIMessage | ToolResult | UserMessage | SystemMessage | DeveloperMessage | ActivityMessage | ReasoningMessage;
39
+ type Message$1 = AIMessage | ToolResult | UserMessage | SystemMessage | DeveloperMessage | ActivityMessage | ReasoningMessage;
40
40
  //#endregion
41
- export { AIMessage, ActivityMessage, type AudioInputPart, DeveloperMessage, type DocumentInputPart, ImageData, type ImageInputPart, type InputContent, type InputContentDataSource$1 as InputContentDataSource, type InputContentSource, type InputContentUrlSource$1 as InputContentUrlSource, Message, ReasoningMessage, Role, SystemMessage, type TextInputPart, ToolCall, ToolResult, UserMessage, type VideoInputPart };
41
+ export { AIMessage, ActivityMessage, type AudioInputPart, DeveloperMessage, type DocumentInputPart, ImageData, type ImageInputPart, type InputContent$1 as InputContent, type InputContentDataSource$1 as InputContentDataSource, type InputContentSource, type InputContentUrlSource$1 as InputContentUrlSource, Message$1 as Message, ReasoningMessage, Role, SystemMessage, type TextInputPart, ToolCall, ToolResult, UserMessage, type VideoInputPart };
42
42
  //# sourceMappingURL=message.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"message.d.mts","names":[],"sources":["../../src/types/message.ts"],"mappings":";;;;;;AAuBA;;;;;UAAiB,SAAA;EACf,MAAA;EACA,KAAA;AAAA;AAAA,KAIU,IAAA,GAAO,IAAA,CAAK,IAAA;AAAA,KACZ,aAAA,GAAgB,IAAA,CAAK,aAAA;AAAA,KACrB,gBAAA,GAAmB,IAAA,CAAK,gBAAA;AAAA,KACxB,QAAA,GAAW,IAAA,CAAK,QAAA;AAAA,KAChB,eAAA,GAAkB,IAAA,CAAK,eAAA;AAAA,KACvB,gBAAA,GAAmB,IAAA,CAAK,gBAAA;AAAA,KAGxB,UAAA,GAAa,IAAA,CAAK,WAAA;EAC5B,QAAA;AAAA;AAAA,KAGU,SAAA,GAAY,IAAA,CAAK,gBAAA;EAC3B,YAAA,IAAgB,KAAA;EAChB,oBAAA;EACA,SAAA;EACA,KAAA;EAbqB;;AACvB;;;EAkBE,KAAA,GAAQ,SAAA;EACR,KAAA;AAAA;AAAA,KAGU,WAAA,GAAc,IAAA,CAAK,WAAA;AAAA,KAEnB,OAAA,GACR,SAAA,GACA,UAAA,GACA,WAAA,GACA,aAAA,GACA,gBAAA,GACA,eAAA,GACA,gBAAA"}
1
+ {"version":3,"file":"message.d.mts","names":[],"sources":["../../src/types/message.ts"],"mappings":";;;;;;AAuBA;;;;;UAAiB,SAAA;EACf,MAAA;EACA,KAAA;AAAA;AAAA,KAIU,IAAA,GAAO,IAAA,CAAK,IAAA;AAAA,KACZ,aAAA,GAAgB,IAAA,CAAK,aAAA;AAAA,KACrB,gBAAA,GAAmB,IAAA,CAAK,gBAAA;AAAA,KACxB,QAAA,GAAW,IAAA,CAAK,QAAA;AAAA,KAChB,eAAA,GAAkB,IAAA,CAAK,eAAA;AAAA,KACvB,gBAAA,GAAmB,IAAA,CAAK,gBAAA;AAAA,KAGxB,UAAA,GAAa,IAAA,CAAK,WAAA;EAC5B,QAAA;AAAA;AAAA,KAGU,SAAA,GAAY,IAAA,CAAK,gBAAA;EAC3B,YAAA,IAAgB,KAAA;EAChB,oBAAA;EACA,SAAA;EACA,KAAA;EAbqB;;AACvB;;;EAkBE,KAAA,GAAQ,SAAA;EACR,KAAA;AAAA;AAAA,KAGU,WAAA,GAAc,IAAA,CAAK,WAAA;AAAA,KAEnB,SAAA,GACR,SAAA,GACA,UAAA,GACA,WAAA,GACA,aAAA,GACA,gBAAA,GACA,eAAA,GACA,gBAAA"}
@@ -10,6 +10,7 @@ const require_inspector_visibility = require('./inspector-visibility.cjs');
10
10
  const require_types = require('./types.cjs');
11
11
  const require_random_id = require('./random-id.cjs');
12
12
  const require_requests = require('./requests.cjs');
13
+ const require_row_render_keys = require('./row-render-keys.cjs');
13
14
  let partial_json = require("partial-json");
14
15
  partial_json = require_runtime.__toESM(partial_json);
15
16
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["PartialJSON"],"sources":["../../src/utils/index.ts"],"sourcesContent":["export * from \"./clipboard\";\nexport * from \"./conditions\";\nexport * from \"./console-styling\";\nexport * from \"./errors\";\nexport * from \"./json-schema\";\nexport * from \"./inspector-metadata\";\nexport * from \"./inspector-learning\";\nexport * from \"./inspector-visibility\";\nexport * from \"./types\";\nexport * from \"./random-id\";\nexport * from \"./requests\";\n\nimport * as PartialJSON from \"partial-json\";\n\n/**\n * Safely parses a JSON string into an object\n * @param json The JSON string to parse\n * @param fallback Optional fallback value to return if parsing fails. If not provided or set to \"unset\", returns null\n * @returns The parsed JSON object, or the fallback value (or null) if parsing fails\n */\nexport function parseJson(json: string, fallback: any = \"unset\") {\n try {\n return JSON.parse(json);\n } catch {\n return fallback === \"unset\" ? null : fallback;\n }\n}\n\n/**\n * Parses a partial/incomplete JSON string, returning as much valid data as possible.\n * Falls back to an empty object if parsing fails entirely.\n */\nexport function partialJSONParse(json: string) {\n try {\n const parsed = PartialJSON.parse(json);\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed;\n }\n return {};\n } catch {\n return {};\n }\n}\n\n/**\n * Returns an exponential backoff function suitable for Phoenix.js\n * `reconnectAfterMs` and `rejoinAfterMs` options.\n *\n * @param baseMs - Initial delay for the first retry attempt.\n * @param maxMs - Upper bound — delays are capped at this value.\n *\n * Phoenix calls the returned function with a 1-based `tries` count.\n * The delay doubles on each attempt: baseMs, 2×baseMs, 4×baseMs, …, maxMs.\n */\nexport function phoenixExponentialBackoff(\n baseMs: number,\n maxMs: number,\n): (tries: number) => number {\n return (tries: number) => Math.min(baseMs * 2 ** (tries - 1), maxMs);\n}\n\n/**\n * Maps an array of items to a new array, skipping items that throw errors during mapping\n * @param items The array to map\n * @param callback The mapping function to apply to each item\n * @returns A new array containing only the successfully mapped items\n */\nexport function tryMap<TItem, TMapped>(\n items: TItem[],\n callback: (item: TItem, index: number, array: TItem[]) => TMapped,\n): TMapped[] {\n return items.reduce<TMapped[]>((acc, item, index, array) => {\n try {\n acc.push(callback(item, index, array));\n } catch (error) {\n console.error(error);\n }\n return acc;\n }, []);\n}\n\n/**\n * Checks if the current environment is macOS\n * @returns {boolean} True if running on macOS, false otherwise\n */\nexport function isMacOS(): boolean {\n return /Mac|iMac|Macintosh/i.test(navigator.userAgent);\n}\n\n/**\n * Safely parses a JSON string into a tool arguments object.\n * Returns the parsed object only if it's a plain object (not an array, null, etc.).\n * Falls back to an empty object for any non-object JSON value or parse failure.\n */\nexport function safeParseToolArgs(raw: string): Record<string, unknown> {\n try {\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed;\n }\n console.warn(\n `[CopilotKit] Tool arguments parsed to non-object (${typeof parsed}), falling back to empty object`,\n );\n return {};\n } catch {\n console.warn(\n \"[CopilotKit] Failed to parse tool arguments, falling back to empty object\",\n );\n return {};\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,UAAU,MAAc,WAAgB,SAAS;AAC/D,KAAI;AACF,SAAO,KAAK,MAAM,KAAK;SACjB;AACN,SAAO,aAAa,UAAU,OAAO;;;;;;;AAQzC,SAAgB,iBAAiB,MAAc;AAC7C,KAAI;EACF,MAAM,SAASA,aAAY,MAAM,KAAK;AACtC,MAAI,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,OAAO,CAChE,QAAO;AAET,SAAO,EAAE;SACH;AACN,SAAO,EAAE;;;;;;;;;;;;;AAcb,SAAgB,0BACd,QACA,OAC2B;AAC3B,SAAQ,UAAkB,KAAK,IAAI,SAAS,MAAM,QAAQ,IAAI,MAAM;;;;;;;;AAStE,SAAgB,OACd,OACA,UACW;AACX,QAAO,MAAM,QAAmB,KAAK,MAAM,OAAO,UAAU;AAC1D,MAAI;AACF,OAAI,KAAK,SAAS,MAAM,OAAO,MAAM,CAAC;WAC/B,OAAO;AACd,WAAQ,MAAM,MAAM;;AAEtB,SAAO;IACN,EAAE,CAAC;;;;;;AAOR,SAAgB,UAAmB;AACjC,QAAO,sBAAsB,KAAK,UAAU,UAAU;;;;;;;AAQxD,SAAgB,kBAAkB,KAAsC;AACtE,KAAI;EACF,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,MAAI,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,OAAO,CAChE,QAAO;AAET,UAAQ,KACN,qDAAqD,OAAO,OAAO,iCACpE;AACD,SAAO,EAAE;SACH;AACN,UAAQ,KACN,4EACD;AACD,SAAO,EAAE"}
1
+ {"version":3,"file":"index.cjs","names":["PartialJSON"],"sources":["../../src/utils/index.ts"],"sourcesContent":["export * from \"./clipboard\";\nexport * from \"./conditions\";\nexport * from \"./console-styling\";\nexport * from \"./errors\";\nexport * from \"./json-schema\";\nexport * from \"./inspector-metadata\";\nexport * from \"./inspector-learning\";\nexport * from \"./inspector-visibility\";\nexport * from \"./types\";\nexport * from \"./random-id\";\nexport * from \"./requests\";\nexport * from \"./row-render-keys\";\n\nimport * as PartialJSON from \"partial-json\";\n\n/**\n * Safely parses a JSON string into an object\n * @param json The JSON string to parse\n * @param fallback Optional fallback value to return if parsing fails. If not provided or set to \"unset\", returns null\n * @returns The parsed JSON object, or the fallback value (or null) if parsing fails\n */\nexport function parseJson(json: string, fallback: any = \"unset\") {\n try {\n return JSON.parse(json);\n } catch {\n return fallback === \"unset\" ? null : fallback;\n }\n}\n\n/**\n * Parses a partial/incomplete JSON string, returning as much valid data as possible.\n * Falls back to an empty object if parsing fails entirely.\n */\nexport function partialJSONParse(json: string) {\n try {\n const parsed = PartialJSON.parse(json);\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed;\n }\n return {};\n } catch {\n return {};\n }\n}\n\n/**\n * Returns an exponential backoff function suitable for Phoenix.js\n * `reconnectAfterMs` and `rejoinAfterMs` options.\n *\n * @param baseMs - Initial delay for the first retry attempt.\n * @param maxMs - Upper bound — delays are capped at this value.\n *\n * Phoenix calls the returned function with a 1-based `tries` count.\n * The delay doubles on each attempt: baseMs, 2×baseMs, 4×baseMs, …, maxMs.\n */\nexport function phoenixExponentialBackoff(\n baseMs: number,\n maxMs: number,\n): (tries: number) => number {\n return (tries: number) => Math.min(baseMs * 2 ** (tries - 1), maxMs);\n}\n\n/**\n * Maps an array of items to a new array, skipping items that throw errors during mapping\n * @param items The array to map\n * @param callback The mapping function to apply to each item\n * @returns A new array containing only the successfully mapped items\n */\nexport function tryMap<TItem, TMapped>(\n items: TItem[],\n callback: (item: TItem, index: number, array: TItem[]) => TMapped,\n): TMapped[] {\n return items.reduce<TMapped[]>((acc, item, index, array) => {\n try {\n acc.push(callback(item, index, array));\n } catch (error) {\n console.error(error);\n }\n return acc;\n }, []);\n}\n\n/**\n * Checks if the current environment is macOS\n * @returns {boolean} True if running on macOS, false otherwise\n */\nexport function isMacOS(): boolean {\n return /Mac|iMac|Macintosh/i.test(navigator.userAgent);\n}\n\n/**\n * Safely parses a JSON string into a tool arguments object.\n * Returns the parsed object only if it's a plain object (not an array, null, etc.).\n * Falls back to an empty object for any non-object JSON value or parse failure.\n */\nexport function safeParseToolArgs(raw: string): Record<string, unknown> {\n try {\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed;\n }\n console.warn(\n `[CopilotKit] Tool arguments parsed to non-object (${typeof parsed}), falling back to empty object`,\n );\n return {};\n } catch {\n console.warn(\n \"[CopilotKit] Failed to parse tool arguments, falling back to empty object\",\n );\n return {};\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,UAAU,MAAc,WAAgB,SAAS;AAC/D,KAAI;AACF,SAAO,KAAK,MAAM,KAAK;SACjB;AACN,SAAO,aAAa,UAAU,OAAO;;;;;;;AAQzC,SAAgB,iBAAiB,MAAc;AAC7C,KAAI;EACF,MAAM,SAASA,aAAY,MAAM,KAAK;AACtC,MAAI,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,OAAO,CAChE,QAAO;AAET,SAAO,EAAE;SACH;AACN,SAAO,EAAE;;;;;;;;;;;;;AAcb,SAAgB,0BACd,QACA,OAC2B;AAC3B,SAAQ,UAAkB,KAAK,IAAI,SAAS,MAAM,QAAQ,IAAI,MAAM;;;;;;;;AAStE,SAAgB,OACd,OACA,UACW;AACX,QAAO,MAAM,QAAmB,KAAK,MAAM,OAAO,UAAU;AAC1D,MAAI;AACF,OAAI,KAAK,SAAS,MAAM,OAAO,MAAM,CAAC;WAC/B,OAAO;AACd,WAAQ,MAAM,MAAM;;AAEtB,SAAO;IACN,EAAE,CAAC;;;;;;AAOR,SAAgB,UAAmB;AACjC,QAAO,sBAAsB,KAAK,UAAU,UAAU;;;;;;;AAQxD,SAAgB,kBAAkB,KAAsC;AACtE,KAAI;EACF,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,MAAI,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,OAAO,CAChE,QAAO;AAET,UAAQ,KACN,qDAAqD,OAAO,OAAO,iCACpE;AACD,SAAO,EAAE;SACH;AACN,UAAQ,KACN,4EACD;AACD,SAAO,EAAE"}
@@ -9,6 +9,7 @@ import { InspectorVisibilityOptions, shouldEnableInspector } from "./inspector-v
9
9
  import { A2UIRuntimeInfo, AgentDescription, IntelligenceRuntimeInfo, MaybePromise, NonEmptyRecord, RUNTIME_MODE_INTELLIGENCE, RUNTIME_MODE_SSE, RuntimeEntitlementResponse, RuntimeInfo, RuntimeLicenseStatus, RuntimeMode, SingleRouteRuntimeInfo, ThreadEndpointRuntimeInfo } from "./types.cjs";
10
10
  import { dataToUUID, isValidUUID, randomId, randomUUID } from "./random-id.cjs";
11
11
  import { readBody } from "./requests.cjs";
12
+ import { RowKeyStore, commitRowKeyStore, createRowKeyStore, pruneRowKeyStore, resolveRowRenderKeys, resolveRowRenderKeysById } from "./row-render-keys.cjs";
12
13
 
13
14
  //#region src/utils/index.d.ts
14
15
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/utils/index.ts"],"mappings":";;;;;;;;;;;;;;;AAoBA;;;;iBAAgB,SAAA,CAAU,IAAA,UAAc,QAAA;AAYxC;;;;AAAA,iBAAgB,gBAAA,CAAiB,IAAA;AAsBjC;;;;;;;;;AAaA;AAbA,iBAAgB,yBAAA,CACd,MAAA,UACA,KAAA,YACE,KAAA;;;;;;;iBAUY,MAAA,gBAAA,CACd,KAAA,EAAO,KAAA,IACP,QAAA,GAAW,IAAA,EAAM,KAAA,EAAO,KAAA,UAAe,KAAA,EAAO,KAAA,OAAY,OAAA,GACzD,OAAA;;;;;iBAea,OAAA,CAAA;;;;;;iBASA,iBAAA,CAAkB,GAAA,WAAc,MAAA"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/utils/index.ts"],"mappings":";;;;;;;;;;;;;;;;AAqBA;;;;iBAAgB,SAAA,CAAU,IAAA,UAAc,QAAA;AAYxC;;;;AAAA,iBAAgB,gBAAA,CAAiB,IAAA;AAsBjC;;;;;;;;;AAaA;AAbA,iBAAgB,yBAAA,CACd,MAAA,UACA,KAAA,YACE,KAAA;;;;;;;iBAUY,MAAA,gBAAA,CACd,KAAA,EAAO,KAAA,IACP,QAAA,GAAW,IAAA,EAAM,KAAA,EAAO,KAAA,UAAe,KAAA,EAAO,KAAA,OAAY,OAAA,GACzD,OAAA;;;;;iBAea,OAAA,CAAA;;;;;;iBASA,iBAAA,CAAkB,GAAA,WAAc,MAAA"}
@@ -9,6 +9,7 @@ import { InspectorVisibilityOptions, shouldEnableInspector } from "./inspector-v
9
9
  import { A2UIRuntimeInfo, AgentDescription, IntelligenceRuntimeInfo, MaybePromise, NonEmptyRecord, RUNTIME_MODE_INTELLIGENCE, RUNTIME_MODE_SSE, RuntimeEntitlementResponse, RuntimeInfo, RuntimeLicenseStatus, RuntimeMode, SingleRouteRuntimeInfo, ThreadEndpointRuntimeInfo } from "./types.mjs";
10
10
  import { dataToUUID, isValidUUID, randomId, randomUUID } from "./random-id.mjs";
11
11
  import { readBody } from "./requests.mjs";
12
+ import { RowKeyStore, commitRowKeyStore, createRowKeyStore, pruneRowKeyStore, resolveRowRenderKeys, resolveRowRenderKeysById } from "./row-render-keys.mjs";
12
13
 
13
14
  //#region src/utils/index.d.ts
14
15
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/utils/index.ts"],"mappings":";;;;;;;;;;;;;;;AAoBA;;;;iBAAgB,SAAA,CAAU,IAAA,UAAc,QAAA;AAYxC;;;;AAAA,iBAAgB,gBAAA,CAAiB,IAAA;AAsBjC;;;;;;;;;AAaA;AAbA,iBAAgB,yBAAA,CACd,MAAA,UACA,KAAA,YACE,KAAA;;;;;;;iBAUY,MAAA,gBAAA,CACd,KAAA,EAAO,KAAA,IACP,QAAA,GAAW,IAAA,EAAM,KAAA,EAAO,KAAA,UAAe,KAAA,EAAO,KAAA,OAAY,OAAA,GACzD,OAAA;;;;;iBAea,OAAA,CAAA;;;;;;iBASA,iBAAA,CAAkB,GAAA,WAAc,MAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/utils/index.ts"],"mappings":";;;;;;;;;;;;;;;;AAqBA;;;;iBAAgB,SAAA,CAAU,IAAA,UAAc,QAAA;AAYxC;;;;AAAA,iBAAgB,gBAAA,CAAiB,IAAA;AAsBjC;;;;;;;;;AAaA;AAbA,iBAAgB,yBAAA,CACd,MAAA,UACA,KAAA,YACE,KAAA;;;;;;;iBAUY,MAAA,gBAAA,CACd,KAAA,EAAO,KAAA,IACP,QAAA,GAAW,IAAA,EAAM,KAAA,EAAO,KAAA,UAAe,KAAA,EAAO,KAAA,OAAY,OAAA,GACzD,OAAA;;;;;iBAea,OAAA,CAAA;;;;;;iBASA,iBAAA,CAAkB,GAAA,WAAc,MAAA"}
@@ -9,6 +9,7 @@ import { shouldEnableInspector } from "./inspector-visibility.mjs";
9
9
  import { RUNTIME_MODE_INTELLIGENCE, RUNTIME_MODE_SSE } from "./types.mjs";
10
10
  import { dataToUUID, isValidUUID, randomId, randomUUID } from "./random-id.mjs";
11
11
  import { readBody } from "./requests.mjs";
12
+ import { commitRowKeyStore, createRowKeyStore, pruneRowKeyStore, resolveRowRenderKeys, resolveRowRenderKeysById } from "./row-render-keys.mjs";
12
13
  import * as PartialJSON from "partial-json";
13
14
 
14
15
  //#region src/utils/index.ts
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/utils/index.ts"],"sourcesContent":["export * from \"./clipboard\";\nexport * from \"./conditions\";\nexport * from \"./console-styling\";\nexport * from \"./errors\";\nexport * from \"./json-schema\";\nexport * from \"./inspector-metadata\";\nexport * from \"./inspector-learning\";\nexport * from \"./inspector-visibility\";\nexport * from \"./types\";\nexport * from \"./random-id\";\nexport * from \"./requests\";\n\nimport * as PartialJSON from \"partial-json\";\n\n/**\n * Safely parses a JSON string into an object\n * @param json The JSON string to parse\n * @param fallback Optional fallback value to return if parsing fails. If not provided or set to \"unset\", returns null\n * @returns The parsed JSON object, or the fallback value (or null) if parsing fails\n */\nexport function parseJson(json: string, fallback: any = \"unset\") {\n try {\n return JSON.parse(json);\n } catch {\n return fallback === \"unset\" ? null : fallback;\n }\n}\n\n/**\n * Parses a partial/incomplete JSON string, returning as much valid data as possible.\n * Falls back to an empty object if parsing fails entirely.\n */\nexport function partialJSONParse(json: string) {\n try {\n const parsed = PartialJSON.parse(json);\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed;\n }\n return {};\n } catch {\n return {};\n }\n}\n\n/**\n * Returns an exponential backoff function suitable for Phoenix.js\n * `reconnectAfterMs` and `rejoinAfterMs` options.\n *\n * @param baseMs - Initial delay for the first retry attempt.\n * @param maxMs - Upper bound — delays are capped at this value.\n *\n * Phoenix calls the returned function with a 1-based `tries` count.\n * The delay doubles on each attempt: baseMs, 2×baseMs, 4×baseMs, …, maxMs.\n */\nexport function phoenixExponentialBackoff(\n baseMs: number,\n maxMs: number,\n): (tries: number) => number {\n return (tries: number) => Math.min(baseMs * 2 ** (tries - 1), maxMs);\n}\n\n/**\n * Maps an array of items to a new array, skipping items that throw errors during mapping\n * @param items The array to map\n * @param callback The mapping function to apply to each item\n * @returns A new array containing only the successfully mapped items\n */\nexport function tryMap<TItem, TMapped>(\n items: TItem[],\n callback: (item: TItem, index: number, array: TItem[]) => TMapped,\n): TMapped[] {\n return items.reduce<TMapped[]>((acc, item, index, array) => {\n try {\n acc.push(callback(item, index, array));\n } catch (error) {\n console.error(error);\n }\n return acc;\n }, []);\n}\n\n/**\n * Checks if the current environment is macOS\n * @returns {boolean} True if running on macOS, false otherwise\n */\nexport function isMacOS(): boolean {\n return /Mac|iMac|Macintosh/i.test(navigator.userAgent);\n}\n\n/**\n * Safely parses a JSON string into a tool arguments object.\n * Returns the parsed object only if it's a plain object (not an array, null, etc.).\n * Falls back to an empty object for any non-object JSON value or parse failure.\n */\nexport function safeParseToolArgs(raw: string): Record<string, unknown> {\n try {\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed;\n }\n console.warn(\n `[CopilotKit] Tool arguments parsed to non-object (${typeof parsed}), falling back to empty object`,\n );\n return {};\n } catch {\n console.warn(\n \"[CopilotKit] Failed to parse tool arguments, falling back to empty object\",\n );\n return {};\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,UAAU,MAAc,WAAgB,SAAS;AAC/D,KAAI;AACF,SAAO,KAAK,MAAM,KAAK;SACjB;AACN,SAAO,aAAa,UAAU,OAAO;;;;;;;AAQzC,SAAgB,iBAAiB,MAAc;AAC7C,KAAI;EACF,MAAM,SAAS,YAAY,MAAM,KAAK;AACtC,MAAI,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,OAAO,CAChE,QAAO;AAET,SAAO,EAAE;SACH;AACN,SAAO,EAAE;;;;;;;;;;;;;AAcb,SAAgB,0BACd,QACA,OAC2B;AAC3B,SAAQ,UAAkB,KAAK,IAAI,SAAS,MAAM,QAAQ,IAAI,MAAM;;;;;;;;AAStE,SAAgB,OACd,OACA,UACW;AACX,QAAO,MAAM,QAAmB,KAAK,MAAM,OAAO,UAAU;AAC1D,MAAI;AACF,OAAI,KAAK,SAAS,MAAM,OAAO,MAAM,CAAC;WAC/B,OAAO;AACd,WAAQ,MAAM,MAAM;;AAEtB,SAAO;IACN,EAAE,CAAC;;;;;;AAOR,SAAgB,UAAmB;AACjC,QAAO,sBAAsB,KAAK,UAAU,UAAU;;;;;;;AAQxD,SAAgB,kBAAkB,KAAsC;AACtE,KAAI;EACF,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,MAAI,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,OAAO,CAChE,QAAO;AAET,UAAQ,KACN,qDAAqD,OAAO,OAAO,iCACpE;AACD,SAAO,EAAE;SACH;AACN,UAAQ,KACN,4EACD;AACD,SAAO,EAAE"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/utils/index.ts"],"sourcesContent":["export * from \"./clipboard\";\nexport * from \"./conditions\";\nexport * from \"./console-styling\";\nexport * from \"./errors\";\nexport * from \"./json-schema\";\nexport * from \"./inspector-metadata\";\nexport * from \"./inspector-learning\";\nexport * from \"./inspector-visibility\";\nexport * from \"./types\";\nexport * from \"./random-id\";\nexport * from \"./requests\";\nexport * from \"./row-render-keys\";\n\nimport * as PartialJSON from \"partial-json\";\n\n/**\n * Safely parses a JSON string into an object\n * @param json The JSON string to parse\n * @param fallback Optional fallback value to return if parsing fails. If not provided or set to \"unset\", returns null\n * @returns The parsed JSON object, or the fallback value (or null) if parsing fails\n */\nexport function parseJson(json: string, fallback: any = \"unset\") {\n try {\n return JSON.parse(json);\n } catch {\n return fallback === \"unset\" ? null : fallback;\n }\n}\n\n/**\n * Parses a partial/incomplete JSON string, returning as much valid data as possible.\n * Falls back to an empty object if parsing fails entirely.\n */\nexport function partialJSONParse(json: string) {\n try {\n const parsed = PartialJSON.parse(json);\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed;\n }\n return {};\n } catch {\n return {};\n }\n}\n\n/**\n * Returns an exponential backoff function suitable for Phoenix.js\n * `reconnectAfterMs` and `rejoinAfterMs` options.\n *\n * @param baseMs - Initial delay for the first retry attempt.\n * @param maxMs - Upper bound — delays are capped at this value.\n *\n * Phoenix calls the returned function with a 1-based `tries` count.\n * The delay doubles on each attempt: baseMs, 2×baseMs, 4×baseMs, …, maxMs.\n */\nexport function phoenixExponentialBackoff(\n baseMs: number,\n maxMs: number,\n): (tries: number) => number {\n return (tries: number) => Math.min(baseMs * 2 ** (tries - 1), maxMs);\n}\n\n/**\n * Maps an array of items to a new array, skipping items that throw errors during mapping\n * @param items The array to map\n * @param callback The mapping function to apply to each item\n * @returns A new array containing only the successfully mapped items\n */\nexport function tryMap<TItem, TMapped>(\n items: TItem[],\n callback: (item: TItem, index: number, array: TItem[]) => TMapped,\n): TMapped[] {\n return items.reduce<TMapped[]>((acc, item, index, array) => {\n try {\n acc.push(callback(item, index, array));\n } catch (error) {\n console.error(error);\n }\n return acc;\n }, []);\n}\n\n/**\n * Checks if the current environment is macOS\n * @returns {boolean} True if running on macOS, false otherwise\n */\nexport function isMacOS(): boolean {\n return /Mac|iMac|Macintosh/i.test(navigator.userAgent);\n}\n\n/**\n * Safely parses a JSON string into a tool arguments object.\n * Returns the parsed object only if it's a plain object (not an array, null, etc.).\n * Falls back to an empty object for any non-object JSON value or parse failure.\n */\nexport function safeParseToolArgs(raw: string): Record<string, unknown> {\n try {\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed;\n }\n console.warn(\n `[CopilotKit] Tool arguments parsed to non-object (${typeof parsed}), falling back to empty object`,\n );\n return {};\n } catch {\n console.warn(\n \"[CopilotKit] Failed to parse tool arguments, falling back to empty object\",\n );\n return {};\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,UAAU,MAAc,WAAgB,SAAS;AAC/D,KAAI;AACF,SAAO,KAAK,MAAM,KAAK;SACjB;AACN,SAAO,aAAa,UAAU,OAAO;;;;;;;AAQzC,SAAgB,iBAAiB,MAAc;AAC7C,KAAI;EACF,MAAM,SAAS,YAAY,MAAM,KAAK;AACtC,MAAI,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,OAAO,CAChE,QAAO;AAET,SAAO,EAAE;SACH;AACN,SAAO,EAAE;;;;;;;;;;;;;AAcb,SAAgB,0BACd,QACA,OAC2B;AAC3B,SAAQ,UAAkB,KAAK,IAAI,SAAS,MAAM,QAAQ,IAAI,MAAM;;;;;;;;AAStE,SAAgB,OACd,OACA,UACW;AACX,QAAO,MAAM,QAAmB,KAAK,MAAM,OAAO,UAAU;AAC1D,MAAI;AACF,OAAI,KAAK,SAAS,MAAM,OAAO,MAAM,CAAC;WAC/B,OAAO;AACd,WAAQ,MAAM,MAAM;;AAEtB,SAAO;IACN,EAAE,CAAC;;;;;;AAOR,SAAgB,UAAmB;AACjC,QAAO,sBAAsB,KAAK,UAAU,UAAU;;;;;;;AAQxD,SAAgB,kBAAkB,KAAsC;AACtE,KAAI;EACF,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,MAAI,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,OAAO,CAChE,QAAO;AAET,UAAQ,KACN,qDAAqD,OAAO,OAAO,iCACpE;AACD,SAAO,EAAE;SACH;AACN,UAAQ,KACN,4EACD;AACD,SAAO,EAAE"}
@@ -0,0 +1,206 @@
1
+
2
+ //#region src/utils/row-render-keys.ts
3
+ /**
4
+ * Stable per-row view keys for chat transcripts, across backends that re-key a
5
+ * message mid-stream. Shared by every frontend package, because the defect and
6
+ * the correlation signal are in the message stream, not in any one framework.
7
+ *
8
+ * ## The problem
9
+ *
10
+ * A message's canonical `id` is not stable within a turn. LangChain stamps a
11
+ * placeholder id on the first streamed chunk when the provider didn't supply
12
+ * one (`chat_models.py`: `if chunk.message.id is None: chunk.message.id =
13
+ * "lc_run-" + "-" + run_id`), then prefers any provider-assigned id it sees
14
+ * while merging chunks (`messages/ai.py`, "Ranks are defined by the order of
15
+ * preference"). The `MESSAGES_SNAPSHOT` therefore carries the provider's final
16
+ * id (e.g. `resp_…`) for a message the client already knows as `lc_run--…`.
17
+ *
18
+ * Every framework destroys and recreates a row whose key changes, so keying
19
+ * rows by `id` turned that swap into the visible HITL chat flash, where a
20
+ * rendered approval card appears to reset during a tool's
21
+ * `executing → complete` transition.
22
+ *
23
+ * This is provider-conditional: providers that stamp an id on every chunk (so
24
+ * `chunk.message.id is None` never holds) never trigger a rename.
25
+ *
26
+ * ## The approach
27
+ *
28
+ * Tool-call ids survive the rename, so they are used as *anchors*: the first
29
+ * message seen carrying a given tool call records the key it was assigned, and
30
+ * any later message carrying that same tool call reuses it. The store is an
31
+ * override table consulted before falling back to `message.id`. Resolving only
32
+ * reads it; each render that reaches the DOM then records what it rendered:
33
+ *
34
+ * ```text
35
+ * render 1 id=lc_run--1 no tools key = message.id = lc_run--1
36
+ * commit 1 store {}
37
+ * render 2 id=lc_run--1 tc call_A key = message.id = lc_run--1
38
+ * commit 2 store { tc:call_A -> lc_run--1 }
39
+ * render 3 id=resp_1 tc call_A key = store[tc:call_A]
40
+ * = lc_run--1 (row survives)
41
+ * commit 3 store unchanged
42
+ * ```
43
+ *
44
+ * Recording the anchor at commit 2 — while the id is still stable — is what
45
+ * makes render 3 resolvable. Note the fix does not depend on render 2
46
+ * existing: a message born already carrying a tool call anchors on the first
47
+ * render that commits it, under whatever id it holds then.
48
+ *
49
+ * ## Why an override table rather than keying rows by tool-call id
50
+ *
51
+ * Deriving the key from the tool call directly (`tc:<id>` whenever a tool call
52
+ * is present) needs no state, but changes the key the moment a tool call
53
+ * *appears* — so an assistant message that streams text and then calls a tool
54
+ * is torn down on that transition, for every provider, whether or not it
55
+ * renames. That trades a conditional flash for an unconditional one. Recording
56
+ * an override keeps the key the row already had.
57
+ *
58
+ * ## Cost
59
+ *
60
+ * The store holds only `tc:<toolCallId>` entries, so a conversation with no
61
+ * tool calls keeps an empty store and behaviour byte-identical to keying by
62
+ * `id`. Size is bounded to the tool calls of the currently-rendered messages
63
+ * (see `pruneRowKeyStore`), not the conversation length. Deleting the store
64
+ * reverts to plain `id` keying.
65
+ *
66
+ * ## Known gaps
67
+ *
68
+ * - A text-only assistant message has no anchor, so it still re-keys. No
69
+ * client-side correlation signal exists for it; the fix is stable ids
70
+ * upstream.
71
+ * - If the tool call's arrival and the id swap land in the same update, the
72
+ * intermediate state never renders, the anchor is never recorded, and the
73
+ * row is recreated as before. Correlating in the event-apply layer (i.e. in
74
+ * the AG-UI client, which observes every intermediate state) would be immune.
75
+ */
76
+ const TOOL_ANCHOR_PREFIX = "tc:";
77
+ function createRowKeyStore() {
78
+ return {
79
+ overrides: /* @__PURE__ */ new Map(),
80
+ ambiguous: /* @__PURE__ */ new Set()
81
+ };
82
+ }
83
+ /**
84
+ * Anchors a message contributes. Only assistant tool calls qualify: LangChain's
85
+ * id preference applies when merging `AIMessageChunk`s, so user message ids are
86
+ * not renamed, and `role: "tool"` messages are not rendered as rows. Every tool
87
+ * call is used (not just the first) so the anchor survives tool-call reordering
88
+ * between snapshots.
89
+ */
90
+ function toolAnchorsOf(message) {
91
+ if (message?.role !== "assistant") return [];
92
+ const toolCalls = message.toolCalls;
93
+ if (!toolCalls?.length) return [];
94
+ const anchors = [];
95
+ for (const toolCall of toolCalls) if (toolCall?.id) anchors.push(`${TOOL_ANCHOR_PREFIX}${toolCall.id}`);
96
+ return anchors;
97
+ }
98
+ /**
99
+ * Resolves the row key for every message, returned by position.
100
+ *
101
+ * Pure: it reads `store` and never writes to it. Every framework here may
102
+ * evaluate a render pass whose result never reaches the DOM — an abandoned
103
+ * concurrent render in React, a discarded `computed` evaluation in Vue or
104
+ * Angular. An anchor recorded by such a pass would vend its key to the pass
105
+ * that does render, re-keying the row the user is looking at, which is the
106
+ * teardown this module exists to prevent. Anchors are recorded by
107
+ * `commitRowKeyStore`, from whichever phase each framework runs after the DOM
108
+ * is updated.
109
+ *
110
+ * Uniqueness is structural rather than assumed: a caller that does not
111
+ * deduplicate can pass two rows with the same id, and an override can vend a
112
+ * key equal to a later message's own id.
113
+ */
114
+ function resolveRowRenderKeys(store, messages) {
115
+ const keys = [];
116
+ const claimed = /* @__PURE__ */ new Set();
117
+ messages.forEach((message, index) => {
118
+ const anchors = toolAnchorsOf(message);
119
+ let key;
120
+ for (const anchor of anchors) {
121
+ if (store.ambiguous.has(anchor)) continue;
122
+ const recorded = store.overrides.get(anchor);
123
+ if (recorded !== void 0 && !claimed.has(recorded)) {
124
+ key = recorded;
125
+ break;
126
+ }
127
+ }
128
+ key ??= message?.id || `index-${index}`;
129
+ if (claimed.has(key)) {
130
+ let suffix = 2;
131
+ while (claimed.has(`${key}:${suffix}`)) suffix += 1;
132
+ key = `${key}:${suffix}`;
133
+ }
134
+ keys.push(key);
135
+ claimed.add(key);
136
+ });
137
+ return keys;
138
+ }
139
+ /**
140
+ * `message.id` → row key, for callers that render by message rather than by
141
+ * position. `messages` must be deduplicated: duplicate ids would overwrite
142
+ * each other in the returned map.
143
+ */
144
+ function resolveRowRenderKeysById(store, messages) {
145
+ const keys = resolveRowRenderKeys(store, messages);
146
+ const byId = /* @__PURE__ */ new Map();
147
+ messages.forEach((message, index) => {
148
+ const key = keys[index];
149
+ if (key !== void 0) byId.set(message.id, key);
150
+ });
151
+ return byId;
152
+ }
153
+ /**
154
+ * Records the anchors of a rendered list, then bounds the store to it. Call
155
+ * from the phase that runs after the DOM is updated, never while resolving —
156
+ * see `resolveRowRenderKeys`.
157
+ *
158
+ * Re-resolving here reproduces the keys the rows were rendered with, because
159
+ * the store cannot change between a render and its own post-render phase.
160
+ *
161
+ * An anchor carried by two rows in the same list is marked ambiguous rather
162
+ * than recorded. Recording it would give the first row's key to whichever row
163
+ * outlived the other, and with it that row's DOM and component state.
164
+ */
165
+ function commitRowKeyStore(store, messages) {
166
+ const keys = resolveRowRenderKeys(store, messages);
167
+ const anchorCounts = /* @__PURE__ */ new Map();
168
+ for (const message of messages) for (const anchor of toolAnchorsOf(message)) anchorCounts.set(anchor, (anchorCounts.get(anchor) ?? 0) + 1);
169
+ store.ambiguous.clear();
170
+ for (const [anchor, count] of anchorCounts) if (count > 1) {
171
+ store.ambiguous.add(anchor);
172
+ store.overrides.delete(anchor);
173
+ }
174
+ messages.forEach((message, index) => {
175
+ const key = keys[index];
176
+ if (key === void 0) return;
177
+ for (const anchor of toolAnchorsOf(message)) {
178
+ if (store.ambiguous.has(anchor)) continue;
179
+ if (!store.overrides.has(anchor)) store.overrides.set(anchor, key);
180
+ }
181
+ });
182
+ pruneRowKeyStore(store, messages);
183
+ }
184
+ /**
185
+ * Drops anchors no longer present in `messages`, bounding the store to the
186
+ * tool calls of the currently-rendered messages. Called by
187
+ * `commitRowKeyStore`; like it, this writes to the store and so belongs after
188
+ * the DOM is updated, never while resolving.
189
+ *
190
+ * Pruned entries are unreachable by construction: `resolveRowRenderKeys` only
191
+ * looks up anchors belonging to messages in the list it is given.
192
+ */
193
+ function pruneRowKeyStore(store, messages) {
194
+ const live = /* @__PURE__ */ new Set();
195
+ for (const message of messages) for (const anchor of toolAnchorsOf(message)) live.add(anchor);
196
+ for (const anchor of store.overrides.keys()) if (!live.has(anchor)) store.overrides.delete(anchor);
197
+ for (const anchor of store.ambiguous) if (!live.has(anchor)) store.ambiguous.delete(anchor);
198
+ }
199
+
200
+ //#endregion
201
+ exports.commitRowKeyStore = commitRowKeyStore;
202
+ exports.createRowKeyStore = createRowKeyStore;
203
+ exports.pruneRowKeyStore = pruneRowKeyStore;
204
+ exports.resolveRowRenderKeys = resolveRowRenderKeys;
205
+ exports.resolveRowRenderKeysById = resolveRowRenderKeysById;
206
+ //# sourceMappingURL=row-render-keys.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"row-render-keys.cjs","names":[],"sources":["../../src/utils/row-render-keys.ts"],"sourcesContent":["import type { AssistantMessage, Message } from \"@ag-ui/core\";\n\n/**\n * Stable per-row view keys for chat transcripts, across backends that re-key a\n * message mid-stream. Shared by every frontend package, because the defect and\n * the correlation signal are in the message stream, not in any one framework.\n *\n * ## The problem\n *\n * A message's canonical `id` is not stable within a turn. LangChain stamps a\n * placeholder id on the first streamed chunk when the provider didn't supply\n * one (`chat_models.py`: `if chunk.message.id is None: chunk.message.id =\n * \"lc_run-\" + \"-\" + run_id`), then prefers any provider-assigned id it sees\n * while merging chunks (`messages/ai.py`, \"Ranks are defined by the order of\n * preference\"). The `MESSAGES_SNAPSHOT` therefore carries the provider's final\n * id (e.g. `resp_…`) for a message the client already knows as `lc_run--…`.\n *\n * Every framework destroys and recreates a row whose key changes, so keying\n * rows by `id` turned that swap into the visible HITL chat flash, where a\n * rendered approval card appears to reset during a tool's\n * `executing → complete` transition.\n *\n * This is provider-conditional: providers that stamp an id on every chunk (so\n * `chunk.message.id is None` never holds) never trigger a rename.\n *\n * ## The approach\n *\n * Tool-call ids survive the rename, so they are used as *anchors*: the first\n * message seen carrying a given tool call records the key it was assigned, and\n * any later message carrying that same tool call reuses it. The store is an\n * override table consulted before falling back to `message.id`. Resolving only\n * reads it; each render that reaches the DOM then records what it rendered:\n *\n * ```text\n * render 1 id=lc_run--1 no tools key = message.id = lc_run--1\n * commit 1 store {}\n * render 2 id=lc_run--1 tc call_A key = message.id = lc_run--1\n * commit 2 store { tc:call_A -> lc_run--1 }\n * render 3 id=resp_1 tc call_A key = store[tc:call_A]\n * = lc_run--1 (row survives)\n * commit 3 store unchanged\n * ```\n *\n * Recording the anchor at commit 2 — while the id is still stable — is what\n * makes render 3 resolvable. Note the fix does not depend on render 2\n * existing: a message born already carrying a tool call anchors on the first\n * render that commits it, under whatever id it holds then.\n *\n * ## Why an override table rather than keying rows by tool-call id\n *\n * Deriving the key from the tool call directly (`tc:<id>` whenever a tool call\n * is present) needs no state, but changes the key the moment a tool call\n * *appears* — so an assistant message that streams text and then calls a tool\n * is torn down on that transition, for every provider, whether or not it\n * renames. That trades a conditional flash for an unconditional one. Recording\n * an override keeps the key the row already had.\n *\n * ## Cost\n *\n * The store holds only `tc:<toolCallId>` entries, so a conversation with no\n * tool calls keeps an empty store and behaviour byte-identical to keying by\n * `id`. Size is bounded to the tool calls of the currently-rendered messages\n * (see `pruneRowKeyStore`), not the conversation length. Deleting the store\n * reverts to plain `id` keying.\n *\n * ## Known gaps\n *\n * - A text-only assistant message has no anchor, so it still re-keys. No\n * client-side correlation signal exists for it; the fix is stable ids\n * upstream.\n * - If the tool call's arrival and the id swap land in the same update, the\n * intermediate state never renders, the anchor is never recorded, and the\n * row is recreated as before. Correlating in the event-apply layer (i.e. in\n * the AG-UI client, which observes every intermediate state) would be immune.\n */\n\nconst TOOL_ANCHOR_PREFIX = \"tc:\";\n\nexport interface RowKeyStore {\n /**\n * `tc:<toolCallId>` → the row key first assigned to a message carrying that\n * tool call. Populated only for assistant messages that carry tool calls.\n */\n overrides: Map<string, string>;\n /**\n * Anchors carried by more than one message in the last rendered list. Such\n * an anchor identifies no single row, so it is never used to vend a key.\n */\n ambiguous: Set<string>;\n}\n\nexport function createRowKeyStore(): RowKeyStore {\n return { overrides: new Map(), ambiguous: new Set() };\n}\n\n/**\n * Anchors a message contributes. Only assistant tool calls qualify: LangChain's\n * id preference applies when merging `AIMessageChunk`s, so user message ids are\n * not renamed, and `role: \"tool\"` messages are not rendered as rows. Every tool\n * call is used (not just the first) so the anchor survives tool-call reordering\n * between snapshots.\n */\nfunction toolAnchorsOf(message: Message | undefined): string[] {\n if (message?.role !== \"assistant\") return [];\n const toolCalls = (message as AssistantMessage).toolCalls;\n if (!toolCalls?.length) return [];\n\n const anchors: string[] = [];\n for (const toolCall of toolCalls) {\n if (toolCall?.id) anchors.push(`${TOOL_ANCHOR_PREFIX}${toolCall.id}`);\n }\n return anchors;\n}\n\n/**\n * Resolves the row key for every message, returned by position.\n *\n * Pure: it reads `store` and never writes to it. Every framework here may\n * evaluate a render pass whose result never reaches the DOM — an abandoned\n * concurrent render in React, a discarded `computed` evaluation in Vue or\n * Angular. An anchor recorded by such a pass would vend its key to the pass\n * that does render, re-keying the row the user is looking at, which is the\n * teardown this module exists to prevent. Anchors are recorded by\n * `commitRowKeyStore`, from whichever phase each framework runs after the DOM\n * is updated.\n *\n * Uniqueness is structural rather than assumed: a caller that does not\n * deduplicate can pass two rows with the same id, and an override can vend a\n * key equal to a later message's own id.\n */\nexport function resolveRowRenderKeys(\n store: RowKeyStore,\n messages: readonly (Message | undefined)[],\n): string[] {\n const keys: string[] = [];\n const claimed = new Set<string>();\n\n messages.forEach((message, index) => {\n const anchors = toolAnchorsOf(message);\n\n // Reuse the key recorded for any of this message's anchors. An override\n // pointing at a key another row already claimed this pass is skipped: two\n // messages can share a tool-call id (upstream bug, or replayed state), and\n // the later one falls back to its own id instead.\n let key: string | undefined;\n for (const anchor of anchors) {\n if (store.ambiguous.has(anchor)) continue;\n const recorded = store.overrides.get(anchor);\n if (recorded !== undefined && !claimed.has(recorded)) {\n key = recorded;\n break;\n }\n }\n\n // The index fallback covers a message with no usable id.\n key ??= message?.id || `index-${index}`;\n\n if (claimed.has(key)) {\n let suffix = 2;\n while (claimed.has(`${key}:${suffix}`)) suffix += 1;\n key = `${key}:${suffix}`;\n }\n\n keys.push(key);\n claimed.add(key);\n });\n\n return keys;\n}\n\n/**\n * `message.id` → row key, for callers that render by message rather than by\n * position. `messages` must be deduplicated: duplicate ids would overwrite\n * each other in the returned map.\n */\nexport function resolveRowRenderKeysById(\n store: RowKeyStore,\n messages: readonly Message[],\n): Map<string, string> {\n const keys = resolveRowRenderKeys(store, messages);\n const byId = new Map<string, string>();\n messages.forEach((message, index) => {\n const key = keys[index];\n if (key !== undefined) byId.set(message.id, key);\n });\n return byId;\n}\n\n/**\n * Records the anchors of a rendered list, then bounds the store to it. Call\n * from the phase that runs after the DOM is updated, never while resolving —\n * see `resolveRowRenderKeys`.\n *\n * Re-resolving here reproduces the keys the rows were rendered with, because\n * the store cannot change between a render and its own post-render phase.\n *\n * An anchor carried by two rows in the same list is marked ambiguous rather\n * than recorded. Recording it would give the first row's key to whichever row\n * outlived the other, and with it that row's DOM and component state.\n */\nexport function commitRowKeyStore(\n store: RowKeyStore,\n messages: readonly (Message | undefined)[],\n): void {\n const keys = resolveRowRenderKeys(store, messages);\n\n const anchorCounts = new Map<string, number>();\n for (const message of messages) {\n for (const anchor of toolAnchorsOf(message)) {\n anchorCounts.set(anchor, (anchorCounts.get(anchor) ?? 0) + 1);\n }\n }\n\n // Ambiguity is a property of the rendered list, so it is recomputed rather\n // than accumulated: an anchor left alone by the row that shadowed it becomes\n // usable again.\n store.ambiguous.clear();\n for (const [anchor, count] of anchorCounts) {\n if (count > 1) {\n store.ambiguous.add(anchor);\n store.overrides.delete(anchor);\n }\n }\n\n // First claimant of an anchor owns it, so a re-keyed message resolves to the\n // key the row already had rather than overwriting it.\n messages.forEach((message, index) => {\n const key = keys[index];\n if (key === undefined) return;\n for (const anchor of toolAnchorsOf(message)) {\n if (store.ambiguous.has(anchor)) continue;\n if (!store.overrides.has(anchor)) store.overrides.set(anchor, key);\n }\n });\n\n pruneRowKeyStore(store, messages);\n}\n\n/**\n * Drops anchors no longer present in `messages`, bounding the store to the\n * tool calls of the currently-rendered messages. Called by\n * `commitRowKeyStore`; like it, this writes to the store and so belongs after\n * the DOM is updated, never while resolving.\n *\n * Pruned entries are unreachable by construction: `resolveRowRenderKeys` only\n * looks up anchors belonging to messages in the list it is given.\n */\nexport function pruneRowKeyStore(\n store: RowKeyStore,\n messages: readonly (Message | undefined)[],\n): void {\n const live = new Set<string>();\n for (const message of messages) {\n for (const anchor of toolAnchorsOf(message)) live.add(anchor);\n }\n\n for (const anchor of store.overrides.keys()) {\n if (!live.has(anchor)) store.overrides.delete(anchor);\n }\n for (const anchor of store.ambiguous) {\n if (!live.has(anchor)) store.ambiguous.delete(anchor);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4EA,MAAM,qBAAqB;AAe3B,SAAgB,oBAAiC;AAC/C,QAAO;EAAE,2BAAW,IAAI,KAAK;EAAE,2BAAW,IAAI,KAAK;EAAE;;;;;;;;;AAUvD,SAAS,cAAc,SAAwC;AAC7D,KAAI,SAAS,SAAS,YAAa,QAAO,EAAE;CAC5C,MAAM,YAAa,QAA6B;AAChD,KAAI,CAAC,WAAW,OAAQ,QAAO,EAAE;CAEjC,MAAM,UAAoB,EAAE;AAC5B,MAAK,MAAM,YAAY,UACrB,KAAI,UAAU,GAAI,SAAQ,KAAK,GAAG,qBAAqB,SAAS,KAAK;AAEvE,QAAO;;;;;;;;;;;;;;;;;;AAmBT,SAAgB,qBACd,OACA,UACU;CACV,MAAM,OAAiB,EAAE;CACzB,MAAM,0BAAU,IAAI,KAAa;AAEjC,UAAS,SAAS,SAAS,UAAU;EACnC,MAAM,UAAU,cAAc,QAAQ;EAMtC,IAAI;AACJ,OAAK,MAAM,UAAU,SAAS;AAC5B,OAAI,MAAM,UAAU,IAAI,OAAO,CAAE;GACjC,MAAM,WAAW,MAAM,UAAU,IAAI,OAAO;AAC5C,OAAI,aAAa,UAAa,CAAC,QAAQ,IAAI,SAAS,EAAE;AACpD,UAAM;AACN;;;AAKJ,UAAQ,SAAS,MAAM,SAAS;AAEhC,MAAI,QAAQ,IAAI,IAAI,EAAE;GACpB,IAAI,SAAS;AACb,UAAO,QAAQ,IAAI,GAAG,IAAI,GAAG,SAAS,CAAE,WAAU;AAClD,SAAM,GAAG,IAAI,GAAG;;AAGlB,OAAK,KAAK,IAAI;AACd,UAAQ,IAAI,IAAI;GAChB;AAEF,QAAO;;;;;;;AAQT,SAAgB,yBACd,OACA,UACqB;CACrB,MAAM,OAAO,qBAAqB,OAAO,SAAS;CAClD,MAAM,uBAAO,IAAI,KAAqB;AACtC,UAAS,SAAS,SAAS,UAAU;EACnC,MAAM,MAAM,KAAK;AACjB,MAAI,QAAQ,OAAW,MAAK,IAAI,QAAQ,IAAI,IAAI;GAChD;AACF,QAAO;;;;;;;;;;;;;;AAeT,SAAgB,kBACd,OACA,UACM;CACN,MAAM,OAAO,qBAAqB,OAAO,SAAS;CAElD,MAAM,+BAAe,IAAI,KAAqB;AAC9C,MAAK,MAAM,WAAW,SACpB,MAAK,MAAM,UAAU,cAAc,QAAQ,CACzC,cAAa,IAAI,SAAS,aAAa,IAAI,OAAO,IAAI,KAAK,EAAE;AAOjE,OAAM,UAAU,OAAO;AACvB,MAAK,MAAM,CAAC,QAAQ,UAAU,aAC5B,KAAI,QAAQ,GAAG;AACb,QAAM,UAAU,IAAI,OAAO;AAC3B,QAAM,UAAU,OAAO,OAAO;;AAMlC,UAAS,SAAS,SAAS,UAAU;EACnC,MAAM,MAAM,KAAK;AACjB,MAAI,QAAQ,OAAW;AACvB,OAAK,MAAM,UAAU,cAAc,QAAQ,EAAE;AAC3C,OAAI,MAAM,UAAU,IAAI,OAAO,CAAE;AACjC,OAAI,CAAC,MAAM,UAAU,IAAI,OAAO,CAAE,OAAM,UAAU,IAAI,QAAQ,IAAI;;GAEpE;AAEF,kBAAiB,OAAO,SAAS;;;;;;;;;;;AAYnC,SAAgB,iBACd,OACA,UACM;CACN,MAAM,uBAAO,IAAI,KAAa;AAC9B,MAAK,MAAM,WAAW,SACpB,MAAK,MAAM,UAAU,cAAc,QAAQ,CAAE,MAAK,IAAI,OAAO;AAG/D,MAAK,MAAM,UAAU,MAAM,UAAU,MAAM,CACzC,KAAI,CAAC,KAAK,IAAI,OAAO,CAAE,OAAM,UAAU,OAAO,OAAO;AAEvD,MAAK,MAAM,UAAU,MAAM,UACzB,KAAI,CAAC,KAAK,IAAI,OAAO,CAAE,OAAM,UAAU,OAAO,OAAO"}
@@ -0,0 +1,65 @@
1
+ import { Message } from "@ag-ui/core";
2
+
3
+ //#region src/utils/row-render-keys.d.ts
4
+ interface RowKeyStore {
5
+ /**
6
+ * `tc:<toolCallId>` → the row key first assigned to a message carrying that
7
+ * tool call. Populated only for assistant messages that carry tool calls.
8
+ */
9
+ overrides: Map<string, string>;
10
+ /**
11
+ * Anchors carried by more than one message in the last rendered list. Such
12
+ * an anchor identifies no single row, so it is never used to vend a key.
13
+ */
14
+ ambiguous: Set<string>;
15
+ }
16
+ declare function createRowKeyStore(): RowKeyStore;
17
+ /**
18
+ * Resolves the row key for every message, returned by position.
19
+ *
20
+ * Pure: it reads `store` and never writes to it. Every framework here may
21
+ * evaluate a render pass whose result never reaches the DOM — an abandoned
22
+ * concurrent render in React, a discarded `computed` evaluation in Vue or
23
+ * Angular. An anchor recorded by such a pass would vend its key to the pass
24
+ * that does render, re-keying the row the user is looking at, which is the
25
+ * teardown this module exists to prevent. Anchors are recorded by
26
+ * `commitRowKeyStore`, from whichever phase each framework runs after the DOM
27
+ * is updated.
28
+ *
29
+ * Uniqueness is structural rather than assumed: a caller that does not
30
+ * deduplicate can pass two rows with the same id, and an override can vend a
31
+ * key equal to a later message's own id.
32
+ */
33
+ declare function resolveRowRenderKeys(store: RowKeyStore, messages: readonly (Message | undefined)[]): string[];
34
+ /**
35
+ * `message.id` → row key, for callers that render by message rather than by
36
+ * position. `messages` must be deduplicated: duplicate ids would overwrite
37
+ * each other in the returned map.
38
+ */
39
+ declare function resolveRowRenderKeysById(store: RowKeyStore, messages: readonly Message[]): Map<string, string>;
40
+ /**
41
+ * Records the anchors of a rendered list, then bounds the store to it. Call
42
+ * from the phase that runs after the DOM is updated, never while resolving —
43
+ * see `resolveRowRenderKeys`.
44
+ *
45
+ * Re-resolving here reproduces the keys the rows were rendered with, because
46
+ * the store cannot change between a render and its own post-render phase.
47
+ *
48
+ * An anchor carried by two rows in the same list is marked ambiguous rather
49
+ * than recorded. Recording it would give the first row's key to whichever row
50
+ * outlived the other, and with it that row's DOM and component state.
51
+ */
52
+ declare function commitRowKeyStore(store: RowKeyStore, messages: readonly (Message | undefined)[]): void;
53
+ /**
54
+ * Drops anchors no longer present in `messages`, bounding the store to the
55
+ * tool calls of the currently-rendered messages. Called by
56
+ * `commitRowKeyStore`; like it, this writes to the store and so belongs after
57
+ * the DOM is updated, never while resolving.
58
+ *
59
+ * Pruned entries are unreachable by construction: `resolveRowRenderKeys` only
60
+ * looks up anchors belonging to messages in the list it is given.
61
+ */
62
+ declare function pruneRowKeyStore(store: RowKeyStore, messages: readonly (Message | undefined)[]): void;
63
+ //#endregion
64
+ export { RowKeyStore, commitRowKeyStore, createRowKeyStore, pruneRowKeyStore, resolveRowRenderKeys, resolveRowRenderKeysById };
65
+ //# sourceMappingURL=row-render-keys.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"row-render-keys.d.cts","names":[],"sources":["../../src/utils/row-render-keys.ts"],"mappings":";;;UA8EiB,WAAA;;AAAjB;;;EAKE,SAAA,EAAW,GAAA;EAAX;;;;EAKA,SAAA,EAAW,GAAA;AAAA;AAAA,iBAGG,iBAAA,CAAA,GAAqB,WAAA;;;;;AAuCrC;;;;;;;;;;AA6CA;;iBA7CgB,oBAAA,CACd,KAAA,EAAO,WAAA,EACP,QAAA,YAAoB,OAAA;;;;;;iBA2CN,wBAAA,CACd,KAAA,EAAO,WAAA,EACP,QAAA,WAAmB,OAAA,KAClB,GAAA;;;;;;;AAsBH;;;;;;iBAAgB,iBAAA,CACd,KAAA,EAAO,WAAA,EACP,QAAA,YAAoB,OAAA;;;;AA6CtB;;;;;;iBAAgB,gBAAA,CACd,KAAA,EAAO,WAAA,EACP,QAAA,YAAoB,OAAA"}
@@ -0,0 +1,65 @@
1
+ import { Message } from "@ag-ui/core";
2
+
3
+ //#region src/utils/row-render-keys.d.ts
4
+ interface RowKeyStore {
5
+ /**
6
+ * `tc:<toolCallId>` → the row key first assigned to a message carrying that
7
+ * tool call. Populated only for assistant messages that carry tool calls.
8
+ */
9
+ overrides: Map<string, string>;
10
+ /**
11
+ * Anchors carried by more than one message in the last rendered list. Such
12
+ * an anchor identifies no single row, so it is never used to vend a key.
13
+ */
14
+ ambiguous: Set<string>;
15
+ }
16
+ declare function createRowKeyStore(): RowKeyStore;
17
+ /**
18
+ * Resolves the row key for every message, returned by position.
19
+ *
20
+ * Pure: it reads `store` and never writes to it. Every framework here may
21
+ * evaluate a render pass whose result never reaches the DOM — an abandoned
22
+ * concurrent render in React, a discarded `computed` evaluation in Vue or
23
+ * Angular. An anchor recorded by such a pass would vend its key to the pass
24
+ * that does render, re-keying the row the user is looking at, which is the
25
+ * teardown this module exists to prevent. Anchors are recorded by
26
+ * `commitRowKeyStore`, from whichever phase each framework runs after the DOM
27
+ * is updated.
28
+ *
29
+ * Uniqueness is structural rather than assumed: a caller that does not
30
+ * deduplicate can pass two rows with the same id, and an override can vend a
31
+ * key equal to a later message's own id.
32
+ */
33
+ declare function resolveRowRenderKeys(store: RowKeyStore, messages: readonly (Message | undefined)[]): string[];
34
+ /**
35
+ * `message.id` → row key, for callers that render by message rather than by
36
+ * position. `messages` must be deduplicated: duplicate ids would overwrite
37
+ * each other in the returned map.
38
+ */
39
+ declare function resolveRowRenderKeysById(store: RowKeyStore, messages: readonly Message[]): Map<string, string>;
40
+ /**
41
+ * Records the anchors of a rendered list, then bounds the store to it. Call
42
+ * from the phase that runs after the DOM is updated, never while resolving —
43
+ * see `resolveRowRenderKeys`.
44
+ *
45
+ * Re-resolving here reproduces the keys the rows were rendered with, because
46
+ * the store cannot change between a render and its own post-render phase.
47
+ *
48
+ * An anchor carried by two rows in the same list is marked ambiguous rather
49
+ * than recorded. Recording it would give the first row's key to whichever row
50
+ * outlived the other, and with it that row's DOM and component state.
51
+ */
52
+ declare function commitRowKeyStore(store: RowKeyStore, messages: readonly (Message | undefined)[]): void;
53
+ /**
54
+ * Drops anchors no longer present in `messages`, bounding the store to the
55
+ * tool calls of the currently-rendered messages. Called by
56
+ * `commitRowKeyStore`; like it, this writes to the store and so belongs after
57
+ * the DOM is updated, never while resolving.
58
+ *
59
+ * Pruned entries are unreachable by construction: `resolveRowRenderKeys` only
60
+ * looks up anchors belonging to messages in the list it is given.
61
+ */
62
+ declare function pruneRowKeyStore(store: RowKeyStore, messages: readonly (Message | undefined)[]): void;
63
+ //#endregion
64
+ export { RowKeyStore, commitRowKeyStore, createRowKeyStore, pruneRowKeyStore, resolveRowRenderKeys, resolveRowRenderKeysById };
65
+ //# sourceMappingURL=row-render-keys.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"row-render-keys.d.mts","names":[],"sources":["../../src/utils/row-render-keys.ts"],"mappings":";;;UA8EiB,WAAA;;AAAjB;;;EAKE,SAAA,EAAW,GAAA;EAAX;;;;EAKA,SAAA,EAAW,GAAA;AAAA;AAAA,iBAGG,iBAAA,CAAA,GAAqB,WAAA;;;;;AAuCrC;;;;;;;;;;AA6CA;;iBA7CgB,oBAAA,CACd,KAAA,EAAO,WAAA,EACP,QAAA,YAAoB,OAAA;;;;;;iBA2CN,wBAAA,CACd,KAAA,EAAO,WAAA,EACP,QAAA,WAAmB,OAAA,KAClB,GAAA;;;;;;;AAsBH;;;;;;iBAAgB,iBAAA,CACd,KAAA,EAAO,WAAA,EACP,QAAA,YAAoB,OAAA;;;;AA6CtB;;;;;;iBAAgB,gBAAA,CACd,KAAA,EAAO,WAAA,EACP,QAAA,YAAoB,OAAA"}