@gravity-ui/aikit 2.2.0 → 2.2.1-beta.3d35003a4aef5cafaa55b118fb4b4beb685dcc51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/build/cjs/hooks/index.d.ts +1 -0
  2. package/build/cjs/hooks/index.js +1 -0
  3. package/build/cjs/hooks/index.js.map +1 -1
  4. package/build/cjs/hooks/useToolset.d.ts +28 -0
  5. package/build/cjs/hooks/useToolset.js +41 -0
  6. package/build/cjs/hooks/useToolset.js.map +1 -0
  7. package/build/cjs/package.json +1 -1
  8. package/build/cjs/utils/index.d.ts +1 -0
  9. package/build/cjs/utils/index.js +1 -0
  10. package/build/cjs/utils/index.js.map +1 -1
  11. package/build/cjs/utils/messageTypeRegistry.js +5 -0
  12. package/build/cjs/utils/messageTypeRegistry.js.map +1 -1
  13. package/build/cjs/utils/toolset/i18n/en.json +3 -0
  14. package/build/cjs/utils/toolset/i18n/index.d.ts +13 -0
  15. package/build/cjs/utils/toolset/i18n/index.js +10 -0
  16. package/build/cjs/utils/toolset/i18n/index.js.map +1 -0
  17. package/build/cjs/utils/toolset/i18n/ru.json +3 -0
  18. package/build/cjs/utils/toolset/index.d.ts +142 -0
  19. package/build/cjs/utils/toolset/index.js +182 -0
  20. package/build/cjs/utils/toolset/index.js.map +1 -0
  21. package/build/esm/hooks/index.d.ts +1 -0
  22. package/build/esm/hooks/index.js +1 -0
  23. package/build/esm/hooks/index.js.map +1 -1
  24. package/build/esm/hooks/useToolset.d.ts +28 -0
  25. package/build/esm/hooks/useToolset.js +38 -0
  26. package/build/esm/hooks/useToolset.js.map +1 -0
  27. package/build/esm/package.json +1 -1
  28. package/build/esm/utils/index.d.ts +1 -0
  29. package/build/esm/utils/index.js +1 -0
  30. package/build/esm/utils/index.js.map +1 -1
  31. package/build/esm/utils/messageTypeRegistry.js +5 -0
  32. package/build/esm/utils/messageTypeRegistry.js.map +1 -1
  33. package/build/esm/utils/toolset/i18n/en.json +3 -0
  34. package/build/esm/utils/toolset/i18n/index.d.ts +13 -0
  35. package/build/esm/utils/toolset/i18n/index.js +6 -0
  36. package/build/esm/utils/toolset/i18n/index.js.map +1 -0
  37. package/build/esm/utils/toolset/i18n/ru.json +3 -0
  38. package/build/esm/utils/toolset/index.d.ts +142 -0
  39. package/build/esm/utils/toolset/index.js +175 -0
  40. package/build/esm/utils/toolset/index.js.map +1 -0
  41. package/docs/GENUI.md +613 -0
  42. package/docs/HOOKS.md +41 -11
  43. package/llms.txt +2 -1
  44. package/package.json +11 -1
@@ -5,3 +5,4 @@ export * from "./useScrollPreservation.js";
5
5
  export * from "./useAutoCollapseOnSuccess.js";
6
6
  export * from "./useAutoCollapseOnCancelled.js";
7
7
  export * from "./useFileUploadStore.js";
8
+ export * from "./useToolset.js";
@@ -8,4 +8,5 @@ tslib_1.__exportStar(require("./useScrollPreservation.js"), exports);
8
8
  tslib_1.__exportStar(require("./useAutoCollapseOnSuccess.js"), exports);
9
9
  tslib_1.__exportStar(require("./useAutoCollapseOnCancelled.js"), exports);
10
10
  tslib_1.__exportStar(require("./useFileUploadStore.js"), exports);
11
+ tslib_1.__exportStar(require("./useToolset.js"), exports);
11
12
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"../../../src","sources":["hooks/index.ts"],"names":[],"mappings":";;;AAAA,sEAAmC;AACnC,8DAAiC;AACjC,8DAAiC;AACjC,qEAAwC;AACxC,wEAA2C;AAC3C,0EAA6C;AAC7C,kEAAqC","sourcesContent":["export * from './useDateFormatter';\nexport * from './useToolMessage';\nexport * from './useSmartScroll';\nexport * from './useScrollPreservation';\nexport * from './useAutoCollapseOnSuccess';\nexport * from './useAutoCollapseOnCancelled';\nexport * from './useFileUploadStore';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"../../../src","sources":["hooks/index.ts"],"names":[],"mappings":";;;AAAA,sEAAmC;AACnC,8DAAiC;AACjC,8DAAiC;AACjC,qEAAwC;AACxC,wEAA2C;AAC3C,0EAA6C;AAC7C,kEAAqC;AACrC,0DAA6B","sourcesContent":["export * from './useDateFormatter';\nexport * from './useToolMessage';\nexport * from './useSmartScroll';\nexport * from './useScrollPreservation';\nexport * from './useAutoCollapseOnSuccess';\nexport * from './useAutoCollapseOnCancelled';\nexport * from './useFileUploadStore';\nexport * from './useToolset';\n"]}
@@ -0,0 +1,28 @@
1
+ import type { Dispatch, SetStateAction } from 'react';
2
+ import type { TChatMessage, TMessageContent } from "../types/messages.js";
3
+ import type { MessageRendererRegistry } from "../utils/messageTypeRegistry.js";
4
+ import { type Toolset, type ToolsetResultEvent } from "../utils/toolset/index.js";
5
+ export type UseToolsetOptions<TCustom extends TMessageContent = never> = {
6
+ toolset: Toolset;
7
+ setMessages: Dispatch<SetStateAction<TChatMessage<TCustom>[]>>;
8
+ /**
9
+ * Called after the tool result has been merged into history.
10
+ * Typical use: forward the updated transcript to the model.
11
+ */
12
+ onAfterResult?: (messages: TChatMessage<TCustom>[]) => void;
13
+ /** Existing registry to extend instead of starting from a fresh one. */
14
+ registry?: MessageRendererRegistry;
15
+ };
16
+ export type UseToolsetReturn = {
17
+ messageRendererRegistry: MessageRendererRegistry;
18
+ handleToolResult: (event: ToolsetResultEvent) => void;
19
+ };
20
+ /**
21
+ * Wire a toolset into the chat: returns a `MessageRendererRegistry` that
22
+ * renders tool parts via the toolset and a `handleToolResult` callback that
23
+ * merges results into history and notifies via `onAfterResult`.
24
+ *
25
+ * Pass the same `toolset` reference across renders (e.g. via `useMemo` or
26
+ * `createToolset`) so the registry stays stable.
27
+ */
28
+ export declare function useToolset<TCustom extends TMessageContent = never>(options: UseToolsetOptions<TCustom>): UseToolsetReturn;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useToolset = useToolset;
4
+ const react_1 = require("react");
5
+ const toolset_1 = require("../utils/toolset/index.js");
6
+ /**
7
+ * Wire a toolset into the chat: returns a `MessageRendererRegistry` that
8
+ * renders tool parts via the toolset and a `handleToolResult` callback that
9
+ * merges results into history and notifies via `onAfterResult`.
10
+ *
11
+ * Pass the same `toolset` reference across renders (e.g. via `useMemo` or
12
+ * `createToolset`) so the registry stays stable.
13
+ */
14
+ function useToolset(options) {
15
+ const { toolset, setMessages, onAfterResult, registry } = options;
16
+ const onAfterResultRef = (0, react_1.useRef)(onAfterResult);
17
+ // Mirror the latest onAfterResult into a ref every render so the
18
+ // registry memo below doesn't need to depend on it (which would
19
+ // rebuild the registry on every parent rerender).
20
+ (0, react_1.useEffect)(() => {
21
+ onAfterResultRef.current = onAfterResult;
22
+ });
23
+ const handleToolResult = (0, react_1.useCallback)((event) => {
24
+ setMessages((prev) => {
25
+ const updated = (0, toolset_1.applyToolResult)(prev, event);
26
+ if (updated !== prev) {
27
+ // Defer to a microtask: never call onAfterResult while
28
+ // React is still inside the setState reducer. Re-entrant
29
+ // setState is illegal in concurrent mode.
30
+ queueMicrotask(() => { var _a; return (_a = onAfterResultRef.current) === null || _a === void 0 ? void 0 : _a.call(onAfterResultRef, updated); });
31
+ }
32
+ return updated;
33
+ });
34
+ }, [setMessages]);
35
+ const messageRendererRegistry = (0, react_1.useMemo)(() => (0, toolset_1.createToolsetRenderer)(toolset, {
36
+ onToolResult: handleToolResult,
37
+ registry,
38
+ }), [toolset, handleToolResult, registry]);
39
+ return { messageRendererRegistry, handleToolResult };
40
+ }
41
+ //# sourceMappingURL=useToolset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useToolset.js","sourceRoot":"../../../src","sources":["hooks/useToolset.ts"],"names":[],"mappings":";;AAqCA,gCAuCC;AA5ED,iCAA8D;AAK9D,uDAK0B;AAmB1B;;;;;;;GAOG;AACH,SAAgB,UAAU,CACtB,OAAmC;IAEnC,MAAM,EAAC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,QAAQ,EAAC,GAAG,OAAO,CAAC;IAEhE,MAAM,gBAAgB,GAAG,IAAA,cAAM,EAAC,aAAa,CAAC,CAAC;IAC/C,iEAAiE;IACjE,gEAAgE;IAChE,kDAAkD;IAClD,IAAA,iBAAS,EAAC,GAAG,EAAE;QACX,gBAAgB,CAAC,OAAO,GAAG,aAAa,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,MAAM,gBAAgB,GAAG,IAAA,mBAAW,EAChC,CAAC,KAAyB,EAAE,EAAE;QAC1B,WAAW,CAAC,CAAC,IAAI,EAAE,EAAE;YACjB,MAAM,OAAO,GAAG,IAAA,yBAAe,EAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAC7C,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;gBACnB,uDAAuD;gBACvD,yDAAyD;gBACzD,0CAA0C;gBAC1C,cAAc,CAAC,GAAG,EAAE,WAAC,OAAA,MAAA,gBAAgB,CAAC,OAAO,iEAAG,OAAO,CAAC,CAAA,EAAA,CAAC,CAAC;YAC9D,CAAC;YACD,OAAO,OAAO,CAAC;QACnB,CAAC,CAAC,CAAC;IACP,CAAC,EACD,CAAC,WAAW,CAAC,CAChB,CAAC;IAEF,MAAM,uBAAuB,GAAG,IAAA,eAAO,EACnC,GAAG,EAAE,CACD,IAAA,+BAAqB,EAAC,OAAO,EAAE;QAC3B,YAAY,EAAE,gBAAgB;QAC9B,QAAQ;KACX,CAAC,EACN,CAAC,OAAO,EAAE,gBAAgB,EAAE,QAAQ,CAAC,CACxC,CAAC;IAEF,OAAO,EAAC,uBAAuB,EAAE,gBAAgB,EAAC,CAAC;AACvD,CAAC","sourcesContent":["import {useCallback, useEffect, useMemo, useRef} from 'react';\nimport type {Dispatch, SetStateAction} from 'react';\n\nimport type {TChatMessage, TMessageContent} from '../types/messages';\nimport type {MessageRendererRegistry} from '../utils/messageTypeRegistry';\nimport {\n type Toolset,\n type ToolsetResultEvent,\n applyToolResult,\n createToolsetRenderer,\n} from '../utils/toolset';\n\nexport type UseToolsetOptions<TCustom extends TMessageContent = never> = {\n toolset: Toolset;\n setMessages: Dispatch<SetStateAction<TChatMessage<TCustom>[]>>;\n /**\n * Called after the tool result has been merged into history.\n * Typical use: forward the updated transcript to the model.\n */\n onAfterResult?: (messages: TChatMessage<TCustom>[]) => void;\n /** Existing registry to extend instead of starting from a fresh one. */\n registry?: MessageRendererRegistry;\n};\n\nexport type UseToolsetReturn = {\n messageRendererRegistry: MessageRendererRegistry;\n handleToolResult: (event: ToolsetResultEvent) => void;\n};\n\n/**\n * Wire a toolset into the chat: returns a `MessageRendererRegistry` that\n * renders tool parts via the toolset and a `handleToolResult` callback that\n * merges results into history and notifies via `onAfterResult`.\n *\n * Pass the same `toolset` reference across renders (e.g. via `useMemo` or\n * `createToolset`) so the registry stays stable.\n */\nexport function useToolset<TCustom extends TMessageContent = never>(\n options: UseToolsetOptions<TCustom>,\n): UseToolsetReturn {\n const {toolset, setMessages, onAfterResult, registry} = options;\n\n const onAfterResultRef = useRef(onAfterResult);\n // Mirror the latest onAfterResult into a ref every render so the\n // registry memo below doesn't need to depend on it (which would\n // rebuild the registry on every parent rerender).\n useEffect(() => {\n onAfterResultRef.current = onAfterResult;\n });\n\n const handleToolResult = useCallback(\n (event: ToolsetResultEvent) => {\n setMessages((prev) => {\n const updated = applyToolResult(prev, event);\n if (updated !== prev) {\n // Defer to a microtask: never call onAfterResult while\n // React is still inside the setState reducer. Re-entrant\n // setState is illegal in concurrent mode.\n queueMicrotask(() => onAfterResultRef.current?.(updated));\n }\n return updated;\n });\n },\n [setMessages],\n );\n\n const messageRendererRegistry = useMemo(\n () =>\n createToolsetRenderer(toolset, {\n onToolResult: handleToolResult,\n registry,\n }),\n [toolset, handleToolResult, registry],\n );\n\n return {messageRendererRegistry, handleToolResult};\n}\n"]}
@@ -1 +1 @@
1
- {"version":"2.2.0","type":"commonjs","sideEffects":["*.css","*.scss"]}
1
+ {"version":"2.2.1-beta.3d35003a4aef5cafaa55b118fb4b4beb685dcc51.0","type":"commonjs","sideEffects":["*.css","*.scss"]}
@@ -3,3 +3,4 @@ export * from "./messageUtils.js";
3
3
  export * from "./validation.js";
4
4
  export * from "./messageTypeRegistry.js";
5
5
  export * from "./clipboardUtils.js";
6
+ export * from "./toolset/index.js";
@@ -7,4 +7,5 @@ tslib_1.__exportStar(require("./messageUtils.js"), exports);
7
7
  tslib_1.__exportStar(require("./validation.js"), exports);
8
8
  tslib_1.__exportStar(require("./messageTypeRegistry.js"), exports);
9
9
  tslib_1.__exportStar(require("./clipboardUtils.js"), exports);
10
+ tslib_1.__exportStar(require("./toolset/index.js"), exports);
10
11
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"../../../src","sources":["utils/index.ts"],"names":[],"mappings":";;;AAAA,mBAAmB;AACnB,yDAA4B;AAC5B,4DAA+B;AAC/B,0DAA6B;AAC7B,mEAAsC;AACtC,8DAAiC","sourcesContent":["// Export utilities\nexport * from './chatUtils';\nexport * from './messageUtils';\nexport * from './validation';\nexport * from './messageTypeRegistry';\nexport * from './clipboardUtils';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"../../../src","sources":["utils/index.ts"],"names":[],"mappings":";;;AAAA,mBAAmB;AACnB,yDAA4B;AAC5B,4DAA+B;AAC/B,0DAA6B;AAC7B,mEAAsC;AACtC,8DAAiC;AACjC,6DAA0B","sourcesContent":["// Export utilities\nexport * from './chatUtils';\nexport * from './messageUtils';\nexport * from './validation';\nexport * from './messageTypeRegistry';\nexport * from './clipboardUtils';\nexport * from './toolset';\n"]}
@@ -8,6 +8,11 @@ function createMessageRendererRegistry() {
8
8
  return {};
9
9
  }
10
10
  function registerMessageRenderer(registry, contentType, renderer) {
11
+ if (process.env.NODE_ENV !== 'production' &&
12
+ Object.prototype.hasOwnProperty.call(registry, contentType)) {
13
+ // eslint-disable-next-line no-console
14
+ console.warn(`registerMessageRenderer: overwriting existing renderer for content type "${contentType}"`);
15
+ }
11
16
  Object.assign(registry, {
12
17
  [contentType]: renderer,
13
18
  });
@@ -1 +1 @@
1
- {"version":3,"file":"messageTypeRegistry.js","sourceRoot":"../../../src","sources":["utils/messageTypeRegistry.ts"],"names":[],"mappings":";;AAcA,sEAEC;AAED,0DASC;AAED,gDAKC;AAED,wEAQC;AA9BD,SAAgB,6BAA6B;IACzC,OAAO,EAAE,CAAC;AACd,CAAC;AAED,SAAgB,uBAAuB,CACnC,QAAiC,EACjC,WAA6B,EAC7B,QAAmC;IAEnC,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE;QACpB,CAAC,WAAW,CAAC,EAAE,QAAsC;KACxD,CAAC,CAAC;IACH,OAAO,QAAQ,CAAC;AACpB,CAAC;AAED,SAAgB,kBAAkB,CAC9B,QAAiC,EACjC,WAAmB;;IAEnB,OAAO,MAAA,QAAQ,CAAC,WAAW,CAAC,0CAAE,SAAS,CAAC;AAC5C,CAAC;AAED,SAAgB,8BAA8B,CAC1C,eAAwC,EACxC,cAAuC;IAEvC,uCACO,eAAe,GACf,cAAc,EACnB;AACN,CAAC","sourcesContent":["import type React from 'react';\n\nimport type {TMessageContent} from '../types/messages';\n\nexport type MessageContentComponentProps<TContent extends TMessageContent = TMessageContent> = {\n part: TContent;\n};\n\nexport type MessageRenderer<TContent extends TMessageContent = TMessageContent> = {\n component: React.ComponentType<MessageContentComponentProps<TContent>>;\n};\n\nexport type MessageRendererRegistry = Record<string, MessageRenderer>;\n\nexport function createMessageRendererRegistry(): MessageRendererRegistry {\n return {};\n}\n\nexport function registerMessageRenderer<TContent extends TMessageContent>(\n registry: MessageRendererRegistry,\n contentType: TContent['type'],\n renderer: MessageRenderer<TContent>,\n): MessageRendererRegistry {\n Object.assign(registry, {\n [contentType]: renderer as unknown as MessageRenderer,\n });\n return registry;\n}\n\nexport function getMessageRenderer(\n registry: MessageRendererRegistry,\n contentType: string,\n): React.ComponentType<MessageContentComponentProps> | undefined {\n return registry[contentType]?.component;\n}\n\nexport function mergeMessageRendererRegistries(\n defaultRegistry: MessageRendererRegistry,\n customRegistry: MessageRendererRegistry,\n): MessageRendererRegistry {\n return {\n ...defaultRegistry,\n ...customRegistry,\n };\n}\n"]}
1
+ {"version":3,"file":"messageTypeRegistry.js","sourceRoot":"../../../src","sources":["utils/messageTypeRegistry.ts"],"names":[],"mappings":";;AAcA,sEAEC;AAED,0DAkBC;AAED,gDAKC;AAED,wEAQC;AAvCD,SAAgB,6BAA6B;IACzC,OAAO,EAAE,CAAC;AACd,CAAC;AAED,SAAgB,uBAAuB,CACnC,QAAiC,EACjC,WAA6B,EAC7B,QAAmC;IAEnC,IACI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY;QACrC,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,EAC7D,CAAC;QACC,sCAAsC;QACtC,OAAO,CAAC,IAAI,CACR,4EAA4E,WAAW,GAAG,CAC7F,CAAC;IACN,CAAC;IACD,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE;QACpB,CAAC,WAAW,CAAC,EAAE,QAAsC;KACxD,CAAC,CAAC;IACH,OAAO,QAAQ,CAAC;AACpB,CAAC;AAED,SAAgB,kBAAkB,CAC9B,QAAiC,EACjC,WAAmB;;IAEnB,OAAO,MAAA,QAAQ,CAAC,WAAW,CAAC,0CAAE,SAAS,CAAC;AAC5C,CAAC;AAED,SAAgB,8BAA8B,CAC1C,eAAwC,EACxC,cAAuC;IAEvC,uCACO,eAAe,GACf,cAAc,EACnB;AACN,CAAC","sourcesContent":["import type React from 'react';\n\nimport type {TMessageContent} from '../types/messages';\n\nexport type MessageContentComponentProps<TContent extends TMessageContent = TMessageContent> = {\n part: TContent;\n};\n\nexport type MessageRenderer<TContent extends TMessageContent = TMessageContent> = {\n component: React.ComponentType<MessageContentComponentProps<TContent>>;\n};\n\nexport type MessageRendererRegistry = Record<string, MessageRenderer>;\n\nexport function createMessageRendererRegistry(): MessageRendererRegistry {\n return {};\n}\n\nexport function registerMessageRenderer<TContent extends TMessageContent>(\n registry: MessageRendererRegistry,\n contentType: TContent['type'],\n renderer: MessageRenderer<TContent>,\n): MessageRendererRegistry {\n if (\n process.env.NODE_ENV !== 'production' &&\n Object.prototype.hasOwnProperty.call(registry, contentType)\n ) {\n // eslint-disable-next-line no-console\n console.warn(\n `registerMessageRenderer: overwriting existing renderer for content type \"${contentType}\"`,\n );\n }\n Object.assign(registry, {\n [contentType]: renderer as unknown as MessageRenderer,\n });\n return registry;\n}\n\nexport function getMessageRenderer(\n registry: MessageRendererRegistry,\n contentType: string,\n): React.ComponentType<MessageContentComponentProps> | undefined {\n return registry[contentType]?.component;\n}\n\nexport function mergeMessageRendererRegistries(\n defaultRegistry: MessageRendererRegistry,\n customRegistry: MessageRendererRegistry,\n): MessageRendererRegistry {\n return {\n ...defaultRegistry,\n ...customRegistry,\n };\n}\n"]}
@@ -0,0 +1,3 @@
1
+ {
2
+ "fallback-unknown-tool": "Unknown tool: {{toolName}}"
3
+ }
@@ -0,0 +1,13 @@
1
+ export declare const i18n: ((key: "fallback-unknown-tool", params?: import("@gravity-ui/i18n").Params) => string) & {
2
+ Translation: import("react").ComponentType<{
3
+ children: (props: {
4
+ t: (key: "fallback-unknown-tool", params?: import("@gravity-ui/i18n").Params) => string;
5
+ }) => React.ReactNode;
6
+ }>;
7
+ useTranslation: () => {
8
+ t: (key: "fallback-unknown-tool", params?: import("@gravity-ui/i18n").Params) => string;
9
+ };
10
+ keysetData: {
11
+ "g-aikit-Toolset": Record<"fallback-unknown-tool", import("@gravity-ui/i18n").KeyData>;
12
+ };
13
+ };
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.i18n = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const i18n_1 = require("@gravity-ui/uikit/i18n");
6
+ const cn_1 = require("../../cn.js");
7
+ const en_json_1 = tslib_1.__importDefault(require("./en.json"));
8
+ const ru_json_1 = tslib_1.__importDefault(require("./ru.json"));
9
+ exports.i18n = (0, i18n_1.addComponentKeysets)({ en: en_json_1.default, ru: ru_json_1.default }, `${cn_1.NAMESPACE}Toolset`);
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"../../../../../src","sources":["utils/toolset/i18n/index.ts"],"names":[],"mappings":";;;;AAAA,iDAA2D;AAE3D,oCAAmC;AAEnC,gEAA2B;AAC3B,gEAA2B;AAEd,QAAA,IAAI,GAAG,IAAA,0BAAmB,EAAC,EAAC,EAAE,EAAF,iBAAE,EAAE,EAAE,EAAF,iBAAE,EAAC,EAAE,GAAG,cAAS,SAAS,CAAC,CAAC","sourcesContent":["import {addComponentKeysets} from '@gravity-ui/uikit/i18n';\n\nimport {NAMESPACE} from '../../cn';\n\nimport en from './en.json';\nimport ru from './ru.json';\n\nexport const i18n = addComponentKeysets({en, ru}, `${NAMESPACE}Toolset`);\n"]}
@@ -0,0 +1,3 @@
1
+ {
2
+ "fallback-unknown-tool": "Неизвестный инструмент: {{toolName}}"
3
+ }
@@ -0,0 +1,142 @@
1
+ import type { ComponentType } from 'react';
2
+ import type { TChatMessage, TMessageContent, ToolMessageContentData } from "../../types/messages.js";
3
+ import { type MessageRendererRegistry } from "../messageTypeRegistry.js";
4
+ export type ToolSchemaResult<TArgs> = {
5
+ success: true;
6
+ data: TArgs;
7
+ } | {
8
+ success: false;
9
+ error: {
10
+ message: string;
11
+ };
12
+ };
13
+ export type ToolSchema<TArgs> = {
14
+ validate: (input: unknown) => ToolSchemaResult<TArgs>;
15
+ };
16
+ export type ToolComponentProps<TArgs, TResult> = {
17
+ args: TArgs;
18
+ result?: TResult;
19
+ submitResult: (result: TResult) => void;
20
+ };
21
+ export type ToolExecutionStatus = 'success' | 'error' | 'cancelled';
22
+ /**
23
+ * Discriminated outcome returned by a tool's `execute` callback.
24
+ * A tool may also return a bare `TResult`; it is then treated as
25
+ * `{status: 'success', result}` to keep simple cases boilerplate-free.
26
+ */
27
+ export type ToolExecutionOutcome<TResult> = {
28
+ status: 'success';
29
+ result: TResult;
30
+ } | {
31
+ status: 'error';
32
+ result?: TResult;
33
+ error?: {
34
+ message: string;
35
+ };
36
+ } | {
37
+ status: 'cancelled';
38
+ result?: TResult;
39
+ };
40
+ export type ToolDefinition<TArgs, TResult, TName extends string = string> = {
41
+ name: TName;
42
+ description: string;
43
+ parameters: Record<string, unknown>;
44
+ schema: ToolSchema<TArgs>;
45
+ component: ComponentType<ToolComponentProps<TArgs, TResult>>;
46
+ /**
47
+ * Run after the user submits a result inside the tool component.
48
+ * Return:
49
+ * - `TResult` (or a Promise of one) for a successful execution;
50
+ * - a `ToolExecutionOutcome<TResult>` to explicitly report `error`
51
+ * or `cancelled`. A thrown error is surfaced as `{status: 'error'}`.
52
+ */
53
+ execute?: (params: {
54
+ args: TArgs;
55
+ result: TResult;
56
+ toolCallId: string;
57
+ }) => TResult | ToolExecutionOutcome<TResult> | Promise<TResult | ToolExecutionOutcome<TResult>>;
58
+ };
59
+ export type RuntimeToolDefinition<TName extends string = string> = {
60
+ name: TName;
61
+ description: string;
62
+ parameters: Record<string, unknown>;
63
+ validate: (input: unknown) => ToolSchemaResult<unknown>;
64
+ /**
65
+ * Rendered as `<Renderer ... />` JSX. Hooks placed at the top of this
66
+ * component work normally because it is a real React component, not a
67
+ * function called inside another render path.
68
+ */
69
+ Renderer: ComponentType<ToolComponentProps<unknown, unknown>>;
70
+ execute: (params: {
71
+ args: unknown;
72
+ result: unknown;
73
+ toolCallId: string;
74
+ }) => unknown | ToolExecutionOutcome<unknown> | Promise<unknown | ToolExecutionOutcome<unknown>>;
75
+ };
76
+ export type Toolset = Record<string, RuntimeToolDefinition>;
77
+ export type ToolPartContentData<TArgs = unknown, TResult = unknown> = ToolMessageContentData & {
78
+ toolCallId: string;
79
+ args?: TArgs;
80
+ result?: TResult;
81
+ };
82
+ export type ToolPartContent<TArgs = unknown, TResult = unknown> = TMessageContent<'tool', ToolPartContentData<TArgs, TResult>>;
83
+ export type ToolsetResultEvent = {
84
+ toolCallId: string;
85
+ toolName: string;
86
+ status: ToolExecutionStatus;
87
+ result: unknown;
88
+ error?: {
89
+ message: string;
90
+ };
91
+ };
92
+ /**
93
+ * Wrap a typed tool definition into an erased runtime entry that the
94
+ * toolset renderer can dispatch by `toolName`. The literal `name` is
95
+ * preserved as a type parameter so `createToolset` can derive its keys.
96
+ */
97
+ export declare function defineTool<TArgs, TResult, const TName extends string>(definition: ToolDefinition<TArgs, TResult, TName>): RuntimeToolDefinition<TName>;
98
+ /**
99
+ * Build a `Toolset` from a list of `defineTool(...)` results. Keys are
100
+ * derived from `definition.name`, so the call site cannot drift between
101
+ * a literal-object key and the embedded `name`. The return type narrows
102
+ * its keys to the union of literal tool names, giving name-autocomplete
103
+ * on lookups. Throws on duplicates.
104
+ */
105
+ export declare function createToolset<const T extends readonly RuntimeToolDefinition<string>[]>(...tools: T): {
106
+ [K in T[number]['name']]: RuntimeToolDefinition<T[number]['name']>;
107
+ };
108
+ /**
109
+ * Map a `Toolset` to the OpenAI `tools[]` shape so chat clients can pass
110
+ * tool definitions to the model without reimplementing the conversion.
111
+ */
112
+ export declare function toolsetToOpenAIDefinitions(toolset: Toolset): Array<{
113
+ type: 'function';
114
+ function: {
115
+ name: string;
116
+ description: string;
117
+ parameters: Record<string, unknown>;
118
+ };
119
+ }>;
120
+ export type CreateToolsetRendererOptions = {
121
+ onToolResult: (event: ToolsetResultEvent) => void;
122
+ /**
123
+ * Existing registry whose entries should be preserved. A shallow copy
124
+ * is taken; the input reference is never mutated.
125
+ */
126
+ registry?: MessageRendererRegistry;
127
+ };
128
+ /**
129
+ * Build a `MessageRendererRegistry` whose `tool` renderer dispatches
130
+ * by `toolName` into the provided toolset. Unknown tools and invalid
131
+ * args fall back to a generic `<ToolMessage status="error" />`. Errors
132
+ * thrown inside `execute` are surfaced via an `error` outcome.
133
+ */
134
+ export declare function createToolsetRenderer(toolset: Toolset, options: CreateToolsetRendererOptions): MessageRendererRegistry;
135
+ /**
136
+ * Merge a tool result into the matching `tool` part of the chat history.
137
+ * Honors `event.status` so the part reflects success / error / cancelled.
138
+ * For backward compatibility, a missing `status` is treated as `'success'`.
139
+ * Returns the original array reference when nothing matched, so React state
140
+ * setters can skip needless updates.
141
+ */
142
+ export declare function applyToolResult<TCustom extends TMessageContent = never>(messages: TChatMessage<TCustom>[], event: ToolsetResultEvent): TChatMessage<TCustom>[];
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defineTool = defineTool;
4
+ exports.createToolset = createToolset;
5
+ exports.toolsetToOpenAIDefinitions = toolsetToOpenAIDefinitions;
6
+ exports.createToolsetRenderer = createToolsetRenderer;
7
+ exports.applyToolResult = applyToolResult;
8
+ const jsx_runtime_1 = require("react/jsx-runtime");
9
+ const ToolMessage_1 = require("../../components/organisms/ToolMessage/index.js");
10
+ const messageTypeRegistry_1 = require("../messageTypeRegistry.js");
11
+ const i18n_1 = require("./i18n/index.js");
12
+ /**
13
+ * Wrap a typed tool definition into an erased runtime entry that the
14
+ * toolset renderer can dispatch by `toolName`. The literal `name` is
15
+ * preserved as a type parameter so `createToolset` can derive its keys.
16
+ */
17
+ function defineTool(definition) {
18
+ return {
19
+ name: definition.name,
20
+ description: definition.description,
21
+ parameters: definition.parameters,
22
+ validate: definition.schema.validate,
23
+ Renderer: definition.component,
24
+ execute: ({ args, result, toolCallId }) => definition.execute
25
+ ? definition.execute({
26
+ args: args,
27
+ result: result,
28
+ toolCallId,
29
+ })
30
+ : result,
31
+ };
32
+ }
33
+ /**
34
+ * Build a `Toolset` from a list of `defineTool(...)` results. Keys are
35
+ * derived from `definition.name`, so the call site cannot drift between
36
+ * a literal-object key and the embedded `name`. The return type narrows
37
+ * its keys to the union of literal tool names, giving name-autocomplete
38
+ * on lookups. Throws on duplicates.
39
+ */
40
+ function createToolset(...tools) {
41
+ const result = {};
42
+ for (const tool of tools) {
43
+ if (Object.prototype.hasOwnProperty.call(result, tool.name)) {
44
+ throw new Error(`createToolset: duplicate tool name "${tool.name}"`);
45
+ }
46
+ result[tool.name] = tool;
47
+ }
48
+ return result;
49
+ }
50
+ /**
51
+ * Map a `Toolset` to the OpenAI `tools[]` shape so chat clients can pass
52
+ * tool definitions to the model without reimplementing the conversion.
53
+ */
54
+ function toolsetToOpenAIDefinitions(toolset) {
55
+ return Object.values(toolset).map((tool) => ({
56
+ type: 'function',
57
+ function: {
58
+ name: tool.name,
59
+ description: tool.description,
60
+ parameters: tool.parameters,
61
+ },
62
+ }));
63
+ }
64
+ const OUTCOME_KEYS = new Set(['status', 'result', 'error']);
65
+ function isToolExecutionOutcome(value) {
66
+ if (!value || typeof value !== 'object')
67
+ return false;
68
+ const status = value.status;
69
+ if (status !== 'success' && status !== 'error' && status !== 'cancelled')
70
+ return false;
71
+ // A bare TResult may coincidentally carry a `status` field. Require all
72
+ // own keys to be in {status, result, error} so e.g. `{status: 'success',
73
+ // id: '123'}` is treated as a result, not a (result-less) outcome.
74
+ for (const key of Object.keys(value)) {
75
+ if (!OUTCOME_KEYS.has(key))
76
+ return false;
77
+ }
78
+ return true;
79
+ }
80
+ function normalizeOutcome(value) {
81
+ if (isToolExecutionOutcome(value))
82
+ return value;
83
+ return { status: 'success', result: value };
84
+ }
85
+ /**
86
+ * Build a `MessageRendererRegistry` whose `tool` renderer dispatches
87
+ * by `toolName` into the provided toolset. Unknown tools and invalid
88
+ * args fall back to a generic `<ToolMessage status="error" />`. Errors
89
+ * thrown inside `execute` are surfaced via an `error` outcome.
90
+ */
91
+ function createToolsetRenderer(toolset, options) {
92
+ var _a;
93
+ const { onToolResult } = options;
94
+ const registry = Object.assign({}, ((_a = options.registry) !== null && _a !== void 0 ? _a : (0, messageTypeRegistry_1.createMessageRendererRegistry)()));
95
+ (0, messageTypeRegistry_1.registerMessageRenderer)(registry, 'tool', {
96
+ component: ({ part }) => {
97
+ const toolPart = part.data;
98
+ const toolDef = toolset[toolPart.toolName];
99
+ if (!toolDef) {
100
+ return ((0, jsx_runtime_1.jsx)(ToolMessage_1.ToolMessage, { toolName: toolPart.toolName, status: "error", expandable: true, initialExpanded: true, bodyContent: (0, i18n_1.i18n)('fallback-unknown-tool', {
101
+ toolName: toolPart.toolName,
102
+ }) }));
103
+ }
104
+ const validation = toolDef.validate(toolPart.args);
105
+ if (!validation.success) {
106
+ return ((0, jsx_runtime_1.jsx)(ToolMessage_1.ToolMessage, { toolName: toolPart.toolName, status: "error", expandable: true, initialExpanded: true, bodyContent: validation.error.message }));
107
+ }
108
+ const submitResult = (result) => {
109
+ Promise.resolve()
110
+ .then(() => toolDef.execute({
111
+ args: validation.data,
112
+ result,
113
+ toolCallId: toolPart.toolCallId,
114
+ }))
115
+ .then((raw) => normalizeOutcome(raw))
116
+ .catch((err) => ({
117
+ status: 'error',
118
+ error: { message: err instanceof Error ? err.message : String(err) },
119
+ }))
120
+ .then((outcome) => {
121
+ onToolResult({
122
+ toolCallId: toolPart.toolCallId,
123
+ toolName: toolPart.toolName,
124
+ status: outcome.status,
125
+ result: outcome.result,
126
+ error: 'error' in outcome ? outcome.error : undefined,
127
+ });
128
+ });
129
+ };
130
+ const { Renderer } = toolDef;
131
+ return ((0, jsx_runtime_1.jsx)(Renderer, { args: validation.data, result: toolPart.result, submitResult: submitResult }));
132
+ },
133
+ });
134
+ return registry;
135
+ }
136
+ function isToolPartContent(part) {
137
+ return part.type === 'tool' && typeof part.data === 'object' && part.data !== null;
138
+ }
139
+ function toContentParts(content) {
140
+ if (typeof content === 'string') {
141
+ return content ? [{ type: 'text', data: { text: content } }] : [];
142
+ }
143
+ return Array.isArray(content) ? content : [content];
144
+ }
145
+ /**
146
+ * Merge a tool result into the matching `tool` part of the chat history.
147
+ * Honors `event.status` so the part reflects success / error / cancelled.
148
+ * For backward compatibility, a missing `status` is treated as `'success'`.
149
+ * Returns the original array reference when nothing matched, so React state
150
+ * setters can skip needless updates.
151
+ */
152
+ function applyToolResult(messages, event) {
153
+ var _a;
154
+ let changed = false;
155
+ const status = (_a = event.status) !== null && _a !== void 0 ? _a : 'success';
156
+ const isError = status === 'error';
157
+ const next = messages.map((msg) => {
158
+ if (msg.role !== 'assistant')
159
+ return msg;
160
+ const parts = toContentParts(msg.content);
161
+ const hasMatch = parts.some((part) => isToolPartContent(part) && part.data.toolCallId === event.toolCallId);
162
+ if (!hasMatch)
163
+ return msg;
164
+ changed = true;
165
+ const updatedParts = parts.map((part) => {
166
+ var _a;
167
+ if (!isToolPartContent(part) || part.data.toolCallId !== event.toolCallId) {
168
+ return part;
169
+ }
170
+ const nextData = Object.assign(Object.assign({}, part.data), { status, result: event.result });
171
+ if (isError && ((_a = event.error) === null || _a === void 0 ? void 0 : _a.message)) {
172
+ nextData.bodyContent = event.error.message;
173
+ nextData.expandable = true;
174
+ nextData.initialExpanded = true;
175
+ }
176
+ return Object.assign(Object.assign({}, part), { data: nextData });
177
+ });
178
+ return Object.assign(Object.assign({}, msg), { content: updatedParts });
179
+ });
180
+ return changed ? next : messages;
181
+ }
182
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"../../../../src","sources":["utils/toolset/index.tsx"],"names":[],"mappings":"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","sourcesContent":["import type {ComponentType} from 'react';\n\nimport {ToolMessage} from '../../components/organisms/ToolMessage';\nimport type {\n TAssistantMessage,\n TChatMessage,\n TMessageContent,\n ToolMessageContentData,\n} from '../../types/messages';\nimport {\n type MessageRendererRegistry,\n createMessageRendererRegistry,\n registerMessageRenderer,\n} from '../messageTypeRegistry';\n\nimport {i18n} from './i18n';\n\nexport type ToolSchemaResult<TArgs> =\n | {success: true; data: TArgs}\n | {success: false; error: {message: string}};\n\nexport type ToolSchema<TArgs> = {\n validate: (input: unknown) => ToolSchemaResult<TArgs>;\n};\n\nexport type ToolComponentProps<TArgs, TResult> = {\n args: TArgs;\n result?: TResult;\n submitResult: (result: TResult) => void;\n};\n\nexport type ToolExecutionStatus = 'success' | 'error' | 'cancelled';\n\n/**\n * Discriminated outcome returned by a tool's `execute` callback.\n * A tool may also return a bare `TResult`; it is then treated as\n * `{status: 'success', result}` to keep simple cases boilerplate-free.\n */\nexport type ToolExecutionOutcome<TResult> =\n | {status: 'success'; result: TResult}\n | {status: 'error'; result?: TResult; error?: {message: string}}\n | {status: 'cancelled'; result?: TResult};\n\nexport type ToolDefinition<TArgs, TResult, TName extends string = string> = {\n name: TName;\n description: string;\n parameters: Record<string, unknown>;\n schema: ToolSchema<TArgs>;\n component: ComponentType<ToolComponentProps<TArgs, TResult>>;\n /**\n * Run after the user submits a result inside the tool component.\n * Return:\n * - `TResult` (or a Promise of one) for a successful execution;\n * - a `ToolExecutionOutcome<TResult>` to explicitly report `error`\n * or `cancelled`. A thrown error is surfaced as `{status: 'error'}`.\n */\n execute?: (params: {\n args: TArgs;\n result: TResult;\n toolCallId: string;\n }) =>\n | TResult\n | ToolExecutionOutcome<TResult>\n | Promise<TResult | ToolExecutionOutcome<TResult>>;\n};\n\nexport type RuntimeToolDefinition<TName extends string = string> = {\n name: TName;\n description: string;\n parameters: Record<string, unknown>;\n validate: (input: unknown) => ToolSchemaResult<unknown>;\n /**\n * Rendered as `<Renderer ... />` JSX. Hooks placed at the top of this\n * component work normally because it is a real React component, not a\n * function called inside another render path.\n */\n Renderer: ComponentType<ToolComponentProps<unknown, unknown>>;\n execute: (params: {\n args: unknown;\n result: unknown;\n toolCallId: string;\n }) =>\n | unknown\n | ToolExecutionOutcome<unknown>\n | Promise<unknown | ToolExecutionOutcome<unknown>>;\n};\n\nexport type Toolset = Record<string, RuntimeToolDefinition>;\n\nexport type ToolPartContentData<TArgs = unknown, TResult = unknown> = ToolMessageContentData & {\n toolCallId: string;\n args?: TArgs;\n result?: TResult;\n};\n\nexport type ToolPartContent<TArgs = unknown, TResult = unknown> = TMessageContent<\n 'tool',\n ToolPartContentData<TArgs, TResult>\n>;\n\nexport type ToolsetResultEvent = {\n toolCallId: string;\n toolName: string;\n status: ToolExecutionStatus;\n result: unknown;\n error?: {message: string};\n};\n\n/**\n * Wrap a typed tool definition into an erased runtime entry that the\n * toolset renderer can dispatch by `toolName`. The literal `name` is\n * preserved as a type parameter so `createToolset` can derive its keys.\n */\nexport function defineTool<TArgs, TResult, const TName extends string>(\n definition: ToolDefinition<TArgs, TResult, TName>,\n): RuntimeToolDefinition<TName> {\n return {\n name: definition.name,\n description: definition.description,\n parameters: definition.parameters,\n validate: definition.schema.validate as (input: unknown) => ToolSchemaResult<unknown>,\n Renderer: definition.component as ComponentType<ToolComponentProps<unknown, unknown>>,\n execute: ({args, result, toolCallId}) =>\n definition.execute\n ? definition.execute({\n args: args as TArgs,\n result: result as TResult,\n toolCallId,\n })\n : result,\n };\n}\n\n/**\n * Build a `Toolset` from a list of `defineTool(...)` results. Keys are\n * derived from `definition.name`, so the call site cannot drift between\n * a literal-object key and the embedded `name`. The return type narrows\n * its keys to the union of literal tool names, giving name-autocomplete\n * on lookups. Throws on duplicates.\n */\nexport function createToolset<const T extends readonly RuntimeToolDefinition<string>[]>(\n ...tools: T\n): {[K in T[number]['name']]: RuntimeToolDefinition<T[number]['name']>} {\n const result: Record<string, RuntimeToolDefinition> = {};\n for (const tool of tools) {\n if (Object.prototype.hasOwnProperty.call(result, tool.name)) {\n throw new Error(`createToolset: duplicate tool name \"${tool.name}\"`);\n }\n result[tool.name] = tool;\n }\n return result as {[K in T[number]['name']]: RuntimeToolDefinition<T[number]['name']>};\n}\n\n/**\n * Map a `Toolset` to the OpenAI `tools[]` shape so chat clients can pass\n * tool definitions to the model without reimplementing the conversion.\n */\nexport function toolsetToOpenAIDefinitions(toolset: Toolset): Array<{\n type: 'function';\n function: {name: string; description: string; parameters: Record<string, unknown>};\n}> {\n return Object.values(toolset).map((tool) => ({\n type: 'function' as const,\n function: {\n name: tool.name,\n description: tool.description,\n parameters: tool.parameters,\n },\n }));\n}\n\nexport type CreateToolsetRendererOptions = {\n onToolResult: (event: ToolsetResultEvent) => void;\n /**\n * Existing registry whose entries should be preserved. A shallow copy\n * is taken; the input reference is never mutated.\n */\n registry?: MessageRendererRegistry;\n};\n\nconst OUTCOME_KEYS = new Set(['status', 'result', 'error']);\n\nfunction isToolExecutionOutcome<TResult>(value: unknown): value is ToolExecutionOutcome<TResult> {\n if (!value || typeof value !== 'object') return false;\n const status = (value as {status?: unknown}).status;\n if (status !== 'success' && status !== 'error' && status !== 'cancelled') return false;\n // A bare TResult may coincidentally carry a `status` field. Require all\n // own keys to be in {status, result, error} so e.g. `{status: 'success',\n // id: '123'}` is treated as a result, not a (result-less) outcome.\n for (const key of Object.keys(value)) {\n if (!OUTCOME_KEYS.has(key)) return false;\n }\n return true;\n}\n\nfunction normalizeOutcome<TResult>(value: unknown): ToolExecutionOutcome<TResult> {\n if (isToolExecutionOutcome<TResult>(value)) return value;\n return {status: 'success', result: value as TResult};\n}\n\n/**\n * Build a `MessageRendererRegistry` whose `tool` renderer dispatches\n * by `toolName` into the provided toolset. Unknown tools and invalid\n * args fall back to a generic `<ToolMessage status=\"error\" />`. Errors\n * thrown inside `execute` are surfaced via an `error` outcome.\n */\nexport function createToolsetRenderer(\n toolset: Toolset,\n options: CreateToolsetRendererOptions,\n): MessageRendererRegistry {\n const {onToolResult} = options;\n const registry: MessageRendererRegistry = {\n ...(options.registry ?? createMessageRendererRegistry()),\n };\n\n registerMessageRenderer<ToolPartContent>(registry, 'tool', {\n component: ({part}) => {\n const toolPart = part.data;\n const toolDef = toolset[toolPart.toolName];\n\n if (!toolDef) {\n return (\n <ToolMessage\n toolName={toolPart.toolName}\n status=\"error\"\n expandable\n initialExpanded\n bodyContent={i18n('fallback-unknown-tool', {\n toolName: toolPart.toolName,\n })}\n />\n );\n }\n\n const validation = toolDef.validate(toolPart.args);\n if (!validation.success) {\n return (\n <ToolMessage\n toolName={toolPart.toolName}\n status=\"error\"\n expandable\n initialExpanded\n bodyContent={validation.error.message}\n />\n );\n }\n\n const submitResult = (result: unknown) => {\n Promise.resolve()\n .then(() =>\n toolDef.execute({\n args: validation.data,\n result,\n toolCallId: toolPart.toolCallId,\n }),\n )\n .then((raw) => normalizeOutcome(raw))\n .catch(\n (err): ToolExecutionOutcome<unknown> => ({\n status: 'error',\n error: {message: err instanceof Error ? err.message : String(err)},\n }),\n )\n .then((outcome) => {\n onToolResult({\n toolCallId: toolPart.toolCallId,\n toolName: toolPart.toolName,\n status: outcome.status,\n result: outcome.result,\n error: 'error' in outcome ? outcome.error : undefined,\n });\n });\n };\n\n const {Renderer} = toolDef;\n return (\n <Renderer\n args={validation.data}\n result={toolPart.result}\n submitResult={submitResult}\n />\n );\n },\n });\n\n return registry;\n}\n\nfunction isToolPartContent(part: TMessageContent): part is ToolPartContent {\n return part.type === 'tool' && typeof part.data === 'object' && part.data !== null;\n}\n\nfunction toContentParts<TCustom extends TMessageContent>(\n content: TAssistantMessage<TCustom>['content'],\n): TMessageContent[] {\n if (typeof content === 'string') {\n return content ? [{type: 'text', data: {text: content}}] : [];\n }\n return Array.isArray(content) ? (content as TMessageContent[]) : [content as TMessageContent];\n}\n\n/**\n * Merge a tool result into the matching `tool` part of the chat history.\n * Honors `event.status` so the part reflects success / error / cancelled.\n * For backward compatibility, a missing `status` is treated as `'success'`.\n * Returns the original array reference when nothing matched, so React state\n * setters can skip needless updates.\n */\nexport function applyToolResult<TCustom extends TMessageContent = never>(\n messages: TChatMessage<TCustom>[],\n event: ToolsetResultEvent,\n): TChatMessage<TCustom>[] {\n let changed = false;\n const status: ToolExecutionStatus = event.status ?? 'success';\n const isError = status === 'error';\n\n const next = messages.map((msg): TChatMessage<TCustom> => {\n if (msg.role !== 'assistant') return msg;\n\n const parts = toContentParts<TCustom>(msg.content);\n const hasMatch = parts.some(\n (part) => isToolPartContent(part) && part.data.toolCallId === event.toolCallId,\n );\n if (!hasMatch) return msg;\n\n changed = true;\n const updatedParts = parts.map((part) => {\n if (!isToolPartContent(part) || part.data.toolCallId !== event.toolCallId) {\n return part;\n }\n const nextData: ToolPartContentData = {\n ...part.data,\n status,\n result: event.result,\n };\n if (isError && event.error?.message) {\n nextData.bodyContent = event.error.message;\n nextData.expandable = true;\n nextData.initialExpanded = true;\n }\n return {\n ...part,\n data: nextData,\n };\n });\n\n return {\n ...msg,\n content: updatedParts as TAssistantMessage<TCustom>['content'],\n };\n });\n\n return changed ? next : messages;\n}\n"]}
@@ -5,3 +5,4 @@ export * from "./useScrollPreservation.js";
5
5
  export * from "./useAutoCollapseOnSuccess.js";
6
6
  export * from "./useAutoCollapseOnCancelled.js";
7
7
  export * from "./useFileUploadStore.js";
8
+ export * from "./useToolset.js";
@@ -5,4 +5,5 @@ export * from "./useScrollPreservation.js";
5
5
  export * from "./useAutoCollapseOnSuccess.js";
6
6
  export * from "./useAutoCollapseOnCancelled.js";
7
7
  export * from "./useFileUploadStore.js";
8
+ export * from "./useToolset.js";
8
9
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"../../../src","sources":["hooks/index.ts"],"names":[],"mappings":"AAAA,4CAAmC;AACnC,oCAAiC;AACjC,oCAAiC;AACjC,2CAAwC;AACxC,8CAA2C;AAC3C,gDAA6C;AAC7C,wCAAqC","sourcesContent":["export * from './useDateFormatter';\nexport * from './useToolMessage';\nexport * from './useSmartScroll';\nexport * from './useScrollPreservation';\nexport * from './useAutoCollapseOnSuccess';\nexport * from './useAutoCollapseOnCancelled';\nexport * from './useFileUploadStore';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"../../../src","sources":["hooks/index.ts"],"names":[],"mappings":"AAAA,4CAAmC;AACnC,oCAAiC;AACjC,oCAAiC;AACjC,2CAAwC;AACxC,8CAA2C;AAC3C,gDAA6C;AAC7C,wCAAqC;AACrC,gCAA6B","sourcesContent":["export * from './useDateFormatter';\nexport * from './useToolMessage';\nexport * from './useSmartScroll';\nexport * from './useScrollPreservation';\nexport * from './useAutoCollapseOnSuccess';\nexport * from './useAutoCollapseOnCancelled';\nexport * from './useFileUploadStore';\nexport * from './useToolset';\n"]}
@@ -0,0 +1,28 @@
1
+ import type { Dispatch, SetStateAction } from 'react';
2
+ import type { TChatMessage, TMessageContent } from "../types/messages.js";
3
+ import type { MessageRendererRegistry } from "../utils/messageTypeRegistry.js";
4
+ import { type Toolset, type ToolsetResultEvent } from "../utils/toolset/index.js";
5
+ export type UseToolsetOptions<TCustom extends TMessageContent = never> = {
6
+ toolset: Toolset;
7
+ setMessages: Dispatch<SetStateAction<TChatMessage<TCustom>[]>>;
8
+ /**
9
+ * Called after the tool result has been merged into history.
10
+ * Typical use: forward the updated transcript to the model.
11
+ */
12
+ onAfterResult?: (messages: TChatMessage<TCustom>[]) => void;
13
+ /** Existing registry to extend instead of starting from a fresh one. */
14
+ registry?: MessageRendererRegistry;
15
+ };
16
+ export type UseToolsetReturn = {
17
+ messageRendererRegistry: MessageRendererRegistry;
18
+ handleToolResult: (event: ToolsetResultEvent) => void;
19
+ };
20
+ /**
21
+ * Wire a toolset into the chat: returns a `MessageRendererRegistry` that
22
+ * renders tool parts via the toolset and a `handleToolResult` callback that
23
+ * merges results into history and notifies via `onAfterResult`.
24
+ *
25
+ * Pass the same `toolset` reference across renders (e.g. via `useMemo` or
26
+ * `createToolset`) so the registry stays stable.
27
+ */
28
+ export declare function useToolset<TCustom extends TMessageContent = never>(options: UseToolsetOptions<TCustom>): UseToolsetReturn;