@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.
- package/README.md +22 -0
- package/dist/attachments/content.cjs +17 -0
- package/dist/attachments/content.cjs.map +1 -0
- package/dist/attachments/content.d.cts +9 -0
- package/dist/attachments/content.d.cts.map +1 -0
- package/dist/attachments/content.d.mts +9 -0
- package/dist/attachments/content.d.mts.map +1 -0
- package/dist/attachments/content.mjs +16 -0
- package/dist/attachments/content.mjs.map +1 -0
- package/dist/event-transforms/index.cjs +15 -0
- package/dist/event-transforms/index.d.cts +4 -0
- package/dist/event-transforms/index.d.mts +4 -0
- package/dist/event-transforms/index.mjs +5 -0
- package/dist/event-transforms/open-generative-ui-middleware.cjs +286 -0
- package/dist/event-transforms/open-generative-ui-middleware.cjs.map +1 -0
- package/dist/event-transforms/open-generative-ui-middleware.d.cts +60 -0
- package/dist/event-transforms/open-generative-ui-middleware.d.cts.map +1 -0
- package/dist/event-transforms/open-generative-ui-middleware.d.mts +60 -0
- package/dist/event-transforms/open-generative-ui-middleware.d.mts.map +1 -0
- package/dist/event-transforms/open-generative-ui-middleware.mjs +283 -0
- package/dist/event-transforms/open-generative-ui-middleware.mjs.map +1 -0
- package/dist/event-transforms/recorded-events.cjs +59 -0
- package/dist/event-transforms/recorded-events.cjs.map +1 -0
- package/dist/event-transforms/recorded-events.d.cts +23 -0
- package/dist/event-transforms/recorded-events.d.cts.map +1 -0
- package/dist/event-transforms/recorded-events.d.mts +23 -0
- package/dist/event-transforms/recorded-events.d.mts.map +1 -0
- package/dist/event-transforms/recorded-events.mjs +58 -0
- package/dist/event-transforms/recorded-events.mjs.map +1 -0
- package/dist/index.cjs +8 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +3 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3 -1
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +224 -1
- package/dist/index.umd.js.map +1 -1
- package/dist/package.cjs +1 -1
- package/dist/package.mjs +1 -1
- package/dist/types/message.d.cts +3 -3
- package/dist/types/message.d.cts.map +1 -1
- package/dist/types/message.d.mts +3 -3
- package/dist/types/message.d.mts.map +1 -1
- package/dist/utils/index.cjs +1 -0
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.cts +1 -0
- package/dist/utils/index.d.cts.map +1 -1
- package/dist/utils/index.d.mts +1 -0
- package/dist/utils/index.d.mts.map +1 -1
- package/dist/utils/index.mjs +1 -0
- package/dist/utils/index.mjs.map +1 -1
- package/dist/utils/row-render-keys.cjs +206 -0
- package/dist/utils/row-render-keys.cjs.map +1 -0
- package/dist/utils/row-render-keys.d.cts +65 -0
- package/dist/utils/row-render-keys.d.cts.map +1 -0
- package/dist/utils/row-render-keys.d.mts +65 -0
- package/dist/utils/row-render-keys.d.mts.map +1 -0
- package/dist/utils/row-render-keys.mjs +201 -0
- package/dist/utils/row-render-keys.mjs.map +1 -0
- package/package.json +12 -1
- package/src/__tests__/root-entry-browser-safety.test.ts +7 -1
- package/src/attachments/content.test.ts +48 -0
- package/src/attachments/content.ts +16 -0
- package/src/attachments/index.ts +2 -0
- package/src/event-transforms/index.ts +5 -0
- package/src/event-transforms/open-generative-ui-middleware.ts +392 -0
- package/src/event-transforms/recorded-events.test.ts +166 -0
- package/src/event-transforms/recorded-events.ts +74 -0
- package/src/utils/__tests__/row-render-keys.test.ts +388 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/row-render-keys.ts +263 -0
- package/tsdown.config.ts +7 -4
package/dist/package.cjs
CHANGED
package/dist/package.mjs
CHANGED
package/dist/types/message.d.cts
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/types/message.d.mts
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/utils/index.cjs
CHANGED
|
@@ -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
|
|
package/dist/utils/index.cjs.map
CHANGED
|
@@ -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":"
|
|
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"}
|
package/dist/utils/index.d.cts
CHANGED
|
@@ -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":"
|
|
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"}
|
package/dist/utils/index.d.mts
CHANGED
|
@@ -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":"
|
|
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"}
|
package/dist/utils/index.mjs
CHANGED
|
@@ -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
|
package/dist/utils/index.mjs.map
CHANGED
|
@@ -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":"
|
|
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"}
|