@gravity-ui/aikit 2.2.0 → 2.2.1-beta.c34166a0895bc049a23b2ba68f233d0ea0e55a8b.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.
- package/build/cjs/hooks/index.d.ts +1 -0
- package/build/cjs/hooks/index.js +1 -0
- package/build/cjs/hooks/index.js.map +1 -1
- package/build/cjs/hooks/useToolset.d.ts +28 -0
- package/build/cjs/hooks/useToolset.js +41 -0
- package/build/cjs/hooks/useToolset.js.map +1 -0
- package/build/cjs/package.json +1 -1
- package/build/cjs/utils/index.d.ts +1 -0
- package/build/cjs/utils/index.js +1 -0
- package/build/cjs/utils/index.js.map +1 -1
- package/build/cjs/utils/messageTypeRegistry.js +5 -0
- package/build/cjs/utils/messageTypeRegistry.js.map +1 -1
- package/build/cjs/utils/toolset/i18n/en.json +3 -0
- package/build/cjs/utils/toolset/i18n/index.d.ts +13 -0
- package/build/cjs/utils/toolset/i18n/index.js +10 -0
- package/build/cjs/utils/toolset/i18n/index.js.map +1 -0
- package/build/cjs/utils/toolset/i18n/ru.json +3 -0
- package/build/cjs/utils/toolset/index.d.ts +142 -0
- package/build/cjs/utils/toolset/index.js +172 -0
- package/build/cjs/utils/toolset/index.js.map +1 -0
- package/build/esm/hooks/index.d.ts +1 -0
- package/build/esm/hooks/index.js +1 -0
- package/build/esm/hooks/index.js.map +1 -1
- package/build/esm/hooks/useToolset.d.ts +28 -0
- package/build/esm/hooks/useToolset.js +38 -0
- package/build/esm/hooks/useToolset.js.map +1 -0
- package/build/esm/package.json +1 -1
- package/build/esm/utils/index.d.ts +1 -0
- package/build/esm/utils/index.js +1 -0
- package/build/esm/utils/index.js.map +1 -1
- package/build/esm/utils/messageTypeRegistry.js +5 -0
- package/build/esm/utils/messageTypeRegistry.js.map +1 -1
- package/build/esm/utils/toolset/i18n/en.json +3 -0
- package/build/esm/utils/toolset/i18n/index.d.ts +13 -0
- package/build/esm/utils/toolset/i18n/index.js +6 -0
- package/build/esm/utils/toolset/i18n/index.js.map +1 -0
- package/build/esm/utils/toolset/i18n/ru.json +3 -0
- package/build/esm/utils/toolset/index.d.ts +142 -0
- package/build/esm/utils/toolset/index.js +165 -0
- package/build/esm/utils/toolset/index.js.map +1 -0
- package/docs/GENUI.md +613 -0
- package/docs/HOOKS.md +41 -11
- package/docs/review-genui-phase-1.md +99 -0
- package/llms.txt +1 -0
- package/package.json +11 -1
package/build/cjs/hooks/index.js
CHANGED
|
@@ -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"]}
|
package/build/cjs/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"2.2.0","type":"commonjs","sideEffects":["*.css","*.scss"]}
|
|
1
|
+
{"version":"2.2.1-beta.c34166a0895bc049a23b2ba68f233d0ea0e55a8b.0","type":"commonjs","sideEffects":["*.css","*.scss"]}
|
package/build/cjs/utils/index.js
CHANGED
|
@@ -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,
|
|
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,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,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,172 @@
|
|
|
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
|
+
function isToolExecutionOutcome(value) {
|
|
65
|
+
if (!value || typeof value !== 'object')
|
|
66
|
+
return false;
|
|
67
|
+
const status = value.status;
|
|
68
|
+
return status === 'success' || status === 'error' || status === 'cancelled';
|
|
69
|
+
}
|
|
70
|
+
function normalizeOutcome(value) {
|
|
71
|
+
if (isToolExecutionOutcome(value))
|
|
72
|
+
return value;
|
|
73
|
+
return { status: 'success', result: value };
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Build a `MessageRendererRegistry` whose `tool` renderer dispatches
|
|
77
|
+
* by `toolName` into the provided toolset. Unknown tools and invalid
|
|
78
|
+
* args fall back to a generic `<ToolMessage status="error" />`. Errors
|
|
79
|
+
* thrown inside `execute` are surfaced via an `error` outcome.
|
|
80
|
+
*/
|
|
81
|
+
function createToolsetRenderer(toolset, options) {
|
|
82
|
+
var _a;
|
|
83
|
+
const { onToolResult } = options;
|
|
84
|
+
const registry = Object.assign({}, ((_a = options.registry) !== null && _a !== void 0 ? _a : (0, messageTypeRegistry_1.createMessageRendererRegistry)()));
|
|
85
|
+
(0, messageTypeRegistry_1.registerMessageRenderer)(registry, 'tool', {
|
|
86
|
+
component: ({ part }) => {
|
|
87
|
+
const toolPart = part.data;
|
|
88
|
+
const toolDef = toolset[toolPart.toolName];
|
|
89
|
+
if (!toolDef) {
|
|
90
|
+
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', {
|
|
91
|
+
toolName: toolPart.toolName,
|
|
92
|
+
}) }));
|
|
93
|
+
}
|
|
94
|
+
const validation = toolDef.validate(toolPart.args);
|
|
95
|
+
if (!validation.success) {
|
|
96
|
+
return ((0, jsx_runtime_1.jsx)(ToolMessage_1.ToolMessage, { toolName: toolPart.toolName, status: "error", expandable: true, initialExpanded: true, bodyContent: validation.error.message }));
|
|
97
|
+
}
|
|
98
|
+
const submitResult = (result) => {
|
|
99
|
+
Promise.resolve()
|
|
100
|
+
.then(() => toolDef.execute({
|
|
101
|
+
args: validation.data,
|
|
102
|
+
result,
|
|
103
|
+
toolCallId: toolPart.toolCallId,
|
|
104
|
+
}))
|
|
105
|
+
.then((raw) => normalizeOutcome(raw))
|
|
106
|
+
.catch((err) => ({
|
|
107
|
+
status: 'error',
|
|
108
|
+
error: { message: err instanceof Error ? err.message : String(err) },
|
|
109
|
+
}))
|
|
110
|
+
.then((outcome) => {
|
|
111
|
+
onToolResult({
|
|
112
|
+
toolCallId: toolPart.toolCallId,
|
|
113
|
+
toolName: toolPart.toolName,
|
|
114
|
+
status: outcome.status,
|
|
115
|
+
result: outcome.result,
|
|
116
|
+
error: 'error' in outcome ? outcome.error : undefined,
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
const { Renderer } = toolDef;
|
|
121
|
+
return ((0, jsx_runtime_1.jsx)(Renderer, { args: validation.data, result: toolPart.result, submitResult: submitResult }));
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
return registry;
|
|
125
|
+
}
|
|
126
|
+
function isToolPartContent(part) {
|
|
127
|
+
return part.type === 'tool' && typeof part.data === 'object' && part.data !== null;
|
|
128
|
+
}
|
|
129
|
+
function toContentParts(content) {
|
|
130
|
+
if (typeof content === 'string') {
|
|
131
|
+
return content ? [{ type: 'text', data: { text: content } }] : [];
|
|
132
|
+
}
|
|
133
|
+
return Array.isArray(content) ? content : [content];
|
|
134
|
+
}
|
|
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
|
+
function applyToolResult(messages, event) {
|
|
143
|
+
var _a;
|
|
144
|
+
let changed = false;
|
|
145
|
+
const status = (_a = event.status) !== null && _a !== void 0 ? _a : 'success';
|
|
146
|
+
const isError = status === 'error';
|
|
147
|
+
const next = messages.map((msg) => {
|
|
148
|
+
if (msg.role !== 'assistant')
|
|
149
|
+
return msg;
|
|
150
|
+
const parts = toContentParts(msg.content);
|
|
151
|
+
const hasMatch = parts.some((part) => isToolPartContent(part) && part.data.toolCallId === event.toolCallId);
|
|
152
|
+
if (!hasMatch)
|
|
153
|
+
return msg;
|
|
154
|
+
changed = true;
|
|
155
|
+
const updatedParts = parts.map((part) => {
|
|
156
|
+
var _a;
|
|
157
|
+
if (!isToolPartContent(part) || part.data.toolCallId !== event.toolCallId) {
|
|
158
|
+
return part;
|
|
159
|
+
}
|
|
160
|
+
const nextData = Object.assign(Object.assign({}, part.data), { status, result: event.result });
|
|
161
|
+
if (isError && ((_a = event.error) === null || _a === void 0 ? void 0 : _a.message)) {
|
|
162
|
+
nextData.bodyContent = event.error.message;
|
|
163
|
+
nextData.expandable = true;
|
|
164
|
+
nextData.initialExpanded = true;
|
|
165
|
+
}
|
|
166
|
+
return Object.assign(Object.assign({}, part), { data: nextData });
|
|
167
|
+
});
|
|
168
|
+
return Object.assign(Object.assign({}, msg), { content: updatedParts });
|
|
169
|
+
});
|
|
170
|
+
return changed ? next : messages;
|
|
171
|
+
}
|
|
172
|
+
//# 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\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 return status === 'success' || status === 'error' || status === 'cancelled';\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"]}
|
package/build/esm/hooks/index.js
CHANGED
|
@@ -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;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import { applyToolResult, createToolsetRenderer, } from "../utils/toolset/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Wire a toolset into the chat: returns a `MessageRendererRegistry` that
|
|
5
|
+
* renders tool parts via the toolset and a `handleToolResult` callback that
|
|
6
|
+
* merges results into history and notifies via `onAfterResult`.
|
|
7
|
+
*
|
|
8
|
+
* Pass the same `toolset` reference across renders (e.g. via `useMemo` or
|
|
9
|
+
* `createToolset`) so the registry stays stable.
|
|
10
|
+
*/
|
|
11
|
+
export function useToolset(options) {
|
|
12
|
+
const { toolset, setMessages, onAfterResult, registry } = options;
|
|
13
|
+
const onAfterResultRef = useRef(onAfterResult);
|
|
14
|
+
// Mirror the latest onAfterResult into a ref every render so the
|
|
15
|
+
// registry memo below doesn't need to depend on it (which would
|
|
16
|
+
// rebuild the registry on every parent rerender).
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
onAfterResultRef.current = onAfterResult;
|
|
19
|
+
});
|
|
20
|
+
const handleToolResult = useCallback((event) => {
|
|
21
|
+
setMessages((prev) => {
|
|
22
|
+
const updated = applyToolResult(prev, event);
|
|
23
|
+
if (updated !== prev) {
|
|
24
|
+
// Defer to a microtask: never call onAfterResult while
|
|
25
|
+
// React is still inside the setState reducer. Re-entrant
|
|
26
|
+
// setState is illegal in concurrent mode.
|
|
27
|
+
queueMicrotask(() => { var _a; return (_a = onAfterResultRef.current) === null || _a === void 0 ? void 0 : _a.call(onAfterResultRef, updated); });
|
|
28
|
+
}
|
|
29
|
+
return updated;
|
|
30
|
+
});
|
|
31
|
+
}, [setMessages]);
|
|
32
|
+
const messageRendererRegistry = useMemo(() => createToolsetRenderer(toolset, {
|
|
33
|
+
onToolResult: handleToolResult,
|
|
34
|
+
registry,
|
|
35
|
+
}), [toolset, handleToolResult, registry]);
|
|
36
|
+
return { messageRendererRegistry, handleToolResult };
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=useToolset.js.map
|