@gravity-ui/aikit 2.0.5 → 2.0.6-beta.86aa50f22bec20e0cae460b5d3be7112f9d4efd2.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 +26 -0
- package/build/cjs/hooks/useToolset.js +34 -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/toolset.d.ts +85 -0
- package/build/cjs/utils/toolset.js +104 -0
- package/build/cjs/utils/toolset.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 +26 -0
- package/build/esm/hooks/useToolset.js +31 -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/toolset.d.ts +85 -0
- package/build/esm/utils/toolset.js +99 -0
- package/build/esm/utils/toolset.js.map +1 -0
- package/docs/GENUI.md +637 -0
- package/package.json +1 -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,26 @@
|
|
|
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.js";
|
|
5
|
+
export type UseToolsetOptions<TCustom extends TMessageContent = never> = {
|
|
6
|
+
/**
|
|
7
|
+
* Called after the tool result has been merged into history.
|
|
8
|
+
* Typical use: forward the updated transcript to the model.
|
|
9
|
+
*/
|
|
10
|
+
onAfterResult?: (messages: TChatMessage<TCustom>[]) => void;
|
|
11
|
+
/** Existing registry to extend instead of starting from a fresh one. */
|
|
12
|
+
registry?: MessageRendererRegistry;
|
|
13
|
+
};
|
|
14
|
+
export type UseToolsetReturn = {
|
|
15
|
+
messageRendererRegistry: MessageRendererRegistry;
|
|
16
|
+
handleToolResult: (event: ToolsetResultEvent) => void;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Wire a toolset into the chat: returns a `MessageRendererRegistry` that
|
|
20
|
+
* renders tool parts via the toolset and a `handleToolResult` callback that
|
|
21
|
+
* merges results into history and notifies via `onAfterResult`.
|
|
22
|
+
*
|
|
23
|
+
* Pass the same `toolset` reference across renders (e.g. via `useMemo`) so
|
|
24
|
+
* the registry stays stable.
|
|
25
|
+
*/
|
|
26
|
+
export declare function useToolset<TCustom extends TMessageContent = never>(toolset: Toolset, setMessages: Dispatch<SetStateAction<TChatMessage<TCustom>[]>>, options?: UseToolsetOptions<TCustom>): UseToolsetReturn;
|
|
@@ -0,0 +1,34 @@
|
|
|
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.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`) so
|
|
12
|
+
* the registry stays stable.
|
|
13
|
+
*/
|
|
14
|
+
function useToolset(toolset, setMessages, options) {
|
|
15
|
+
const onAfterResultRef = (0, react_1.useRef)(options === null || options === void 0 ? void 0 : options.onAfterResult);
|
|
16
|
+
(0, react_1.useEffect)(() => {
|
|
17
|
+
onAfterResultRef.current = options === null || options === void 0 ? void 0 : options.onAfterResult;
|
|
18
|
+
});
|
|
19
|
+
const handleToolResult = (0, react_1.useCallback)((event) => {
|
|
20
|
+
setMessages((prev) => {
|
|
21
|
+
const updated = (0, toolset_1.applyToolResult)(prev, event);
|
|
22
|
+
if (updated !== prev) {
|
|
23
|
+
queueMicrotask(() => { var _a; return (_a = onAfterResultRef.current) === null || _a === void 0 ? void 0 : _a.call(onAfterResultRef, updated); });
|
|
24
|
+
}
|
|
25
|
+
return updated;
|
|
26
|
+
});
|
|
27
|
+
}, [setMessages]);
|
|
28
|
+
const messageRendererRegistry = (0, react_1.useMemo)(() => (0, toolset_1.createToolsetRenderer)(toolset, {
|
|
29
|
+
onToolResult: handleToolResult,
|
|
30
|
+
registry: options === null || options === void 0 ? void 0 : options.registry,
|
|
31
|
+
}), [toolset, handleToolResult, options === null || options === void 0 ? void 0 : options.registry]);
|
|
32
|
+
return { messageRendererRegistry, handleToolResult };
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=useToolset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useToolset.js","sourceRoot":"../../../src","sources":["hooks/useToolset.ts"],"names":[],"mappings":";;AAmCA,gCAiCC;AApED,iCAA8D;AAK9D,iDAK0B;AAiB1B;;;;;;;GAOG;AACH,SAAgB,UAAU,CACtB,OAAgB,EAChB,WAA8D,EAC9D,OAAoC;IAEpC,MAAM,gBAAgB,GAAG,IAAA,cAAM,EAAC,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,aAAa,CAAC,CAAC;IACxD,IAAA,iBAAS,EAAC,GAAG,EAAE;QACX,gBAAgB,CAAC,OAAO,GAAG,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,aAAa,CAAC;IACtD,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,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,EAAE,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,QAAQ;KAC9B,CAAC,EACN,CAAC,OAAO,EAAE,gBAAgB,EAAE,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,QAAQ,CAAC,CACjD,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 /**\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`) so\n * the registry stays stable.\n */\nexport function useToolset<TCustom extends TMessageContent = never>(\n toolset: Toolset,\n setMessages: Dispatch<SetStateAction<TChatMessage<TCustom>[]>>,\n options?: UseToolsetOptions<TCustom>,\n): UseToolsetReturn {\n const onAfterResultRef = useRef(options?.onAfterResult);\n useEffect(() => {\n onAfterResultRef.current = options?.onAfterResult;\n });\n\n const handleToolResult = useCallback(\n (event: ToolsetResultEvent) => {\n setMessages((prev) => {\n const updated = applyToolResult(prev, event);\n if (updated !== prev) {\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: options?.registry,\n }),\n [toolset, handleToolResult, options?.registry],\n );\n\n return {messageRendererRegistry, handleToolResult};\n}\n"]}
|
package/build/cjs/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"2.0.
|
|
1
|
+
{"version":"2.0.6-beta.86aa50f22bec20e0cae460b5d3be7112f9d4efd2.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.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,uDAA0B","sourcesContent":["// Export utilities\nexport * from './chatUtils';\nexport * from './messageUtils';\nexport * from './validation';\nexport * from './messageTypeRegistry';\nexport * from './clipboardUtils';\nexport * from './toolset';\n"]}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { ComponentType, ReactNode } 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 ToolDefinition<TArgs, TResult> = {
|
|
22
|
+
name: string;
|
|
23
|
+
description: string;
|
|
24
|
+
parameters: Record<string, unknown>;
|
|
25
|
+
schema: ToolSchema<TArgs>;
|
|
26
|
+
component: ComponentType<ToolComponentProps<TArgs, TResult>>;
|
|
27
|
+
execute?: (params: {
|
|
28
|
+
args: TArgs;
|
|
29
|
+
result: TResult;
|
|
30
|
+
toolCallId: string;
|
|
31
|
+
}) => TResult | Promise<TResult>;
|
|
32
|
+
};
|
|
33
|
+
export type RuntimeToolDefinition = {
|
|
34
|
+
name: string;
|
|
35
|
+
description: string;
|
|
36
|
+
parameters: Record<string, unknown>;
|
|
37
|
+
validate: (input: unknown) => ToolSchemaResult<unknown>;
|
|
38
|
+
render: (props: {
|
|
39
|
+
args: unknown;
|
|
40
|
+
result?: unknown;
|
|
41
|
+
submitResult: (result: unknown) => void;
|
|
42
|
+
}) => ReactNode;
|
|
43
|
+
execute: (params: {
|
|
44
|
+
args: unknown;
|
|
45
|
+
result: unknown;
|
|
46
|
+
toolCallId: string;
|
|
47
|
+
}) => unknown | Promise<unknown>;
|
|
48
|
+
};
|
|
49
|
+
export type Toolset = Record<string, RuntimeToolDefinition>;
|
|
50
|
+
export type ToolPartContentData<TArgs = unknown, TResult = unknown> = ToolMessageContentData & {
|
|
51
|
+
toolCallId: string;
|
|
52
|
+
args?: TArgs;
|
|
53
|
+
result?: TResult;
|
|
54
|
+
};
|
|
55
|
+
export type ToolPartContent<TArgs = unknown, TResult = unknown> = TMessageContent<'tool', ToolPartContentData<TArgs, TResult>>;
|
|
56
|
+
export type ToolsetResultEvent = {
|
|
57
|
+
toolCallId: string;
|
|
58
|
+
toolName: string;
|
|
59
|
+
result: unknown;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Wrap a typed tool definition into an erased runtime entry that the
|
|
63
|
+
* toolset renderer can dispatch by `toolName`.
|
|
64
|
+
*/
|
|
65
|
+
export declare function defineTool<TArgs, TResult>(definition: ToolDefinition<TArgs, TResult>): RuntimeToolDefinition;
|
|
66
|
+
export type CreateToolsetRendererOptions = {
|
|
67
|
+
onToolResult: (event: ToolsetResultEvent) => void;
|
|
68
|
+
/**
|
|
69
|
+
* Existing registry to extend instead of starting from a fresh one.
|
|
70
|
+
* The `tool` renderer is overridden; other types are preserved.
|
|
71
|
+
*/
|
|
72
|
+
registry?: MessageRendererRegistry;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Build a `MessageRendererRegistry` whose `tool` renderer dispatches
|
|
76
|
+
* by `toolName` into the provided toolset. Unknown tools and invalid
|
|
77
|
+
* args fall back to a generic `<ToolMessage status="error" />`.
|
|
78
|
+
*/
|
|
79
|
+
export declare function createToolsetRenderer(toolset: Toolset, options: CreateToolsetRendererOptions): MessageRendererRegistry;
|
|
80
|
+
/**
|
|
81
|
+
* Merge a tool result into the matching `tool` part of the chat history.
|
|
82
|
+
* Returns the original array reference when nothing matched, so React state
|
|
83
|
+
* setters can skip needless updates.
|
|
84
|
+
*/
|
|
85
|
+
export declare function applyToolResult<TCustom extends TMessageContent = never>(messages: TChatMessage<TCustom>[], event: ToolsetResultEvent): TChatMessage<TCustom>[];
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.defineTool = defineTool;
|
|
4
|
+
exports.createToolsetRenderer = createToolsetRenderer;
|
|
5
|
+
exports.applyToolResult = applyToolResult;
|
|
6
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
7
|
+
const ToolMessage_1 = require("../components/organisms/ToolMessage/index.js");
|
|
8
|
+
const messageTypeRegistry_1 = require("./messageTypeRegistry.js");
|
|
9
|
+
/**
|
|
10
|
+
* Wrap a typed tool definition into an erased runtime entry that the
|
|
11
|
+
* toolset renderer can dispatch by `toolName`.
|
|
12
|
+
*/
|
|
13
|
+
function defineTool(definition) {
|
|
14
|
+
const Component = definition.component;
|
|
15
|
+
return {
|
|
16
|
+
name: definition.name,
|
|
17
|
+
description: definition.description,
|
|
18
|
+
parameters: definition.parameters,
|
|
19
|
+
validate: definition.schema.validate,
|
|
20
|
+
render: ({ args, result, submitResult }) => ((0, jsx_runtime_1.jsx)(Component, { args: args, result: result, submitResult: submitResult })),
|
|
21
|
+
execute: ({ args, result, toolCallId }) => definition.execute
|
|
22
|
+
? definition.execute({
|
|
23
|
+
args: args,
|
|
24
|
+
result: result,
|
|
25
|
+
toolCallId,
|
|
26
|
+
})
|
|
27
|
+
: result,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Build a `MessageRendererRegistry` whose `tool` renderer dispatches
|
|
32
|
+
* by `toolName` into the provided toolset. Unknown tools and invalid
|
|
33
|
+
* args fall back to a generic `<ToolMessage status="error" />`.
|
|
34
|
+
*/
|
|
35
|
+
function createToolsetRenderer(toolset, options) {
|
|
36
|
+
const { onToolResult, registry = (0, messageTypeRegistry_1.createMessageRendererRegistry)() } = options;
|
|
37
|
+
(0, messageTypeRegistry_1.registerMessageRenderer)(registry, 'tool', {
|
|
38
|
+
component: ({ part }) => {
|
|
39
|
+
const toolPart = part.data;
|
|
40
|
+
const toolDef = toolset[toolPart.toolName];
|
|
41
|
+
if (!toolDef) {
|
|
42
|
+
return ((0, jsx_runtime_1.jsx)(ToolMessage_1.ToolMessage, Object.assign({}, toolPart, { status: "error", expandable: true, initialExpanded: true, bodyContent: `Unknown tool: ${toolPart.toolName}` })));
|
|
43
|
+
}
|
|
44
|
+
const validation = toolDef.validate(toolPart.args);
|
|
45
|
+
if (!validation.success) {
|
|
46
|
+
return ((0, jsx_runtime_1.jsx)(ToolMessage_1.ToolMessage, Object.assign({}, toolPart, { status: "error", expandable: true, initialExpanded: true, bodyContent: validation.error.message })));
|
|
47
|
+
}
|
|
48
|
+
const submitResult = (result) => {
|
|
49
|
+
Promise.resolve(toolDef.execute({
|
|
50
|
+
args: validation.data,
|
|
51
|
+
result,
|
|
52
|
+
toolCallId: toolPart.toolCallId,
|
|
53
|
+
})).then((finalResult) => {
|
|
54
|
+
onToolResult({
|
|
55
|
+
toolCallId: toolPart.toolCallId,
|
|
56
|
+
toolName: toolPart.toolName,
|
|
57
|
+
result: finalResult,
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
return toolDef.render({
|
|
62
|
+
args: validation.data,
|
|
63
|
+
result: toolPart.result,
|
|
64
|
+
submitResult,
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
return registry;
|
|
69
|
+
}
|
|
70
|
+
function isToolPartContent(part) {
|
|
71
|
+
return part.type === 'tool' && typeof part.data === 'object' && part.data !== null;
|
|
72
|
+
}
|
|
73
|
+
function toContentParts(content) {
|
|
74
|
+
if (typeof content === 'string') {
|
|
75
|
+
return content ? [{ type: 'text', data: { text: content } }] : [];
|
|
76
|
+
}
|
|
77
|
+
return Array.isArray(content) ? content : [content];
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Merge a tool result into the matching `tool` part of the chat history.
|
|
81
|
+
* Returns the original array reference when nothing matched, so React state
|
|
82
|
+
* setters can skip needless updates.
|
|
83
|
+
*/
|
|
84
|
+
function applyToolResult(messages, event) {
|
|
85
|
+
let changed = false;
|
|
86
|
+
const next = messages.map((msg) => {
|
|
87
|
+
if (msg.role !== 'assistant')
|
|
88
|
+
return msg;
|
|
89
|
+
const parts = toContentParts(msg.content);
|
|
90
|
+
const hasMatch = parts.some((part) => isToolPartContent(part) && part.data.toolCallId === event.toolCallId);
|
|
91
|
+
if (!hasMatch)
|
|
92
|
+
return msg;
|
|
93
|
+
changed = true;
|
|
94
|
+
const updatedParts = parts.map((part) => {
|
|
95
|
+
if (!isToolPartContent(part) || part.data.toolCallId !== event.toolCallId) {
|
|
96
|
+
return part;
|
|
97
|
+
}
|
|
98
|
+
return Object.assign(Object.assign({}, part), { data: Object.assign(Object.assign({}, part.data), { status: 'success', result: event.result }) });
|
|
99
|
+
});
|
|
100
|
+
return Object.assign(Object.assign({}, msg), { content: updatedParts });
|
|
101
|
+
});
|
|
102
|
+
return changed ? next : messages;
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=toolset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolset.js","sourceRoot":"../../../src","sources":["utils/toolset.tsx"],"names":[],"mappings":"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","sourcesContent":["import type {ComponentType, ReactNode} from 'react';\n\nimport {ToolMessage} from '../components/organisms/ToolMessage';\nimport type {\n TAssistantMessage,\n TChatMessage,\n TMessageContent,\n ToolMessageContentData,\n} from '../types/messages';\n\nimport {\n type MessageRendererRegistry,\n createMessageRendererRegistry,\n registerMessageRenderer,\n} from './messageTypeRegistry';\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 ToolDefinition<TArgs, TResult> = {\n name: string;\n description: string;\n parameters: Record<string, unknown>;\n schema: ToolSchema<TArgs>;\n component: ComponentType<ToolComponentProps<TArgs, TResult>>;\n execute?: (params: {\n args: TArgs;\n result: TResult;\n toolCallId: string;\n }) => TResult | Promise<TResult>;\n};\n\nexport type RuntimeToolDefinition = {\n name: string;\n description: string;\n parameters: Record<string, unknown>;\n validate: (input: unknown) => ToolSchemaResult<unknown>;\n render: (props: {\n args: unknown;\n result?: unknown;\n submitResult: (result: unknown) => void;\n }) => ReactNode;\n execute: (params: {\n args: unknown;\n result: unknown;\n toolCallId: string;\n }) => unknown | Promise<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 result: unknown;\n};\n\n/**\n * Wrap a typed tool definition into an erased runtime entry that the\n * toolset renderer can dispatch by `toolName`.\n */\nexport function defineTool<TArgs, TResult>(\n definition: ToolDefinition<TArgs, TResult>,\n): RuntimeToolDefinition {\n const Component = definition.component;\n\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 render: ({args, result, submitResult}) => (\n <Component\n args={args as TArgs}\n result={result as TResult | undefined}\n submitResult={submitResult as (result: TResult) => void}\n />\n ),\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\nexport type CreateToolsetRendererOptions = {\n onToolResult: (event: ToolsetResultEvent) => void;\n /**\n * Existing registry to extend instead of starting from a fresh one.\n * The `tool` renderer is overridden; other types are preserved.\n */\n registry?: MessageRendererRegistry;\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\" />`.\n */\nexport function createToolsetRenderer(\n toolset: Toolset,\n options: CreateToolsetRendererOptions,\n): MessageRendererRegistry {\n const {onToolResult, registry = createMessageRendererRegistry()} = options;\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 {...toolPart}\n status=\"error\"\n expandable\n initialExpanded\n bodyContent={`Unknown tool: ${toolPart.toolName}`}\n />\n );\n }\n\n const validation = toolDef.validate(toolPart.args);\n if (!validation.success) {\n return (\n <ToolMessage\n {...toolPart}\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 toolDef.execute({\n args: validation.data,\n result,\n toolCallId: toolPart.toolCallId,\n }),\n ).then((finalResult) => {\n onToolResult({\n toolCallId: toolPart.toolCallId,\n toolName: toolPart.toolName,\n result: finalResult,\n });\n });\n };\n\n return toolDef.render({\n args: validation.data,\n result: toolPart.result,\n submitResult,\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 * 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\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 return {\n ...part,\n data: {\n ...part.data,\n status: 'success' as const,\n result: event.result,\n },\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,26 @@
|
|
|
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.js";
|
|
5
|
+
export type UseToolsetOptions<TCustom extends TMessageContent = never> = {
|
|
6
|
+
/**
|
|
7
|
+
* Called after the tool result has been merged into history.
|
|
8
|
+
* Typical use: forward the updated transcript to the model.
|
|
9
|
+
*/
|
|
10
|
+
onAfterResult?: (messages: TChatMessage<TCustom>[]) => void;
|
|
11
|
+
/** Existing registry to extend instead of starting from a fresh one. */
|
|
12
|
+
registry?: MessageRendererRegistry;
|
|
13
|
+
};
|
|
14
|
+
export type UseToolsetReturn = {
|
|
15
|
+
messageRendererRegistry: MessageRendererRegistry;
|
|
16
|
+
handleToolResult: (event: ToolsetResultEvent) => void;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Wire a toolset into the chat: returns a `MessageRendererRegistry` that
|
|
20
|
+
* renders tool parts via the toolset and a `handleToolResult` callback that
|
|
21
|
+
* merges results into history and notifies via `onAfterResult`.
|
|
22
|
+
*
|
|
23
|
+
* Pass the same `toolset` reference across renders (e.g. via `useMemo`) so
|
|
24
|
+
* the registry stays stable.
|
|
25
|
+
*/
|
|
26
|
+
export declare function useToolset<TCustom extends TMessageContent = never>(toolset: Toolset, setMessages: Dispatch<SetStateAction<TChatMessage<TCustom>[]>>, options?: UseToolsetOptions<TCustom>): UseToolsetReturn;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import { applyToolResult, createToolsetRenderer, } from "../utils/toolset.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`) so
|
|
9
|
+
* the registry stays stable.
|
|
10
|
+
*/
|
|
11
|
+
export function useToolset(toolset, setMessages, options) {
|
|
12
|
+
const onAfterResultRef = useRef(options === null || options === void 0 ? void 0 : options.onAfterResult);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
onAfterResultRef.current = options === null || options === void 0 ? void 0 : options.onAfterResult;
|
|
15
|
+
});
|
|
16
|
+
const handleToolResult = useCallback((event) => {
|
|
17
|
+
setMessages((prev) => {
|
|
18
|
+
const updated = applyToolResult(prev, event);
|
|
19
|
+
if (updated !== prev) {
|
|
20
|
+
queueMicrotask(() => { var _a; return (_a = onAfterResultRef.current) === null || _a === void 0 ? void 0 : _a.call(onAfterResultRef, updated); });
|
|
21
|
+
}
|
|
22
|
+
return updated;
|
|
23
|
+
});
|
|
24
|
+
}, [setMessages]);
|
|
25
|
+
const messageRendererRegistry = useMemo(() => createToolsetRenderer(toolset, {
|
|
26
|
+
onToolResult: handleToolResult,
|
|
27
|
+
registry: options === null || options === void 0 ? void 0 : options.registry,
|
|
28
|
+
}), [toolset, handleToolResult, options === null || options === void 0 ? void 0 : options.registry]);
|
|
29
|
+
return { messageRendererRegistry, handleToolResult };
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=useToolset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useToolset.js","sourceRoot":"../../../src","sources":["hooks/useToolset.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAC,MAAM,OAAO,CAAC;AAK9D,OAAO,EAGH,eAAe,EACf,qBAAqB,GACxB,4BAAyB;AAiB1B;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CACtB,OAAgB,EAChB,WAA8D,EAC9D,OAAoC;IAEpC,MAAM,gBAAgB,GAAG,MAAM,CAAC,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,aAAa,CAAC,CAAC;IACxD,SAAS,CAAC,GAAG,EAAE;QACX,gBAAgB,CAAC,OAAO,GAAG,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,aAAa,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,MAAM,gBAAgB,GAAG,WAAW,CAChC,CAAC,KAAyB,EAAE,EAAE;QAC1B,WAAW,CAAC,CAAC,IAAI,EAAE,EAAE;YACjB,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAC7C,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;gBACnB,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,OAAO,CACnC,GAAG,EAAE,CACD,qBAAqB,CAAC,OAAO,EAAE;QAC3B,YAAY,EAAE,gBAAgB;QAC9B,QAAQ,EAAE,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,QAAQ;KAC9B,CAAC,EACN,CAAC,OAAO,EAAE,gBAAgB,EAAE,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,QAAQ,CAAC,CACjD,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 /**\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`) so\n * the registry stays stable.\n */\nexport function useToolset<TCustom extends TMessageContent = never>(\n toolset: Toolset,\n setMessages: Dispatch<SetStateAction<TChatMessage<TCustom>[]>>,\n options?: UseToolsetOptions<TCustom>,\n): UseToolsetReturn {\n const onAfterResultRef = useRef(options?.onAfterResult);\n useEffect(() => {\n onAfterResultRef.current = options?.onAfterResult;\n });\n\n const handleToolResult = useCallback(\n (event: ToolsetResultEvent) => {\n setMessages((prev) => {\n const updated = applyToolResult(prev, event);\n if (updated !== prev) {\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: options?.registry,\n }),\n [toolset, handleToolResult, options?.registry],\n );\n\n return {messageRendererRegistry, handleToolResult};\n}\n"]}
|
package/build/esm/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"2.0.
|
|
1
|
+
{"version":"2.0.6-beta.86aa50f22bec20e0cae460b5d3be7112f9d4efd2.0","type":"module","sideEffects":["*.css","*.scss"]}
|
package/build/esm/utils/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"../../../src","sources":["utils/index.ts"],"names":[],"mappings":"AAAA,mBAAmB;AACnB,+BAA4B;AAC5B,kCAA+B;AAC/B,gCAA6B;AAC7B,yCAAsC;AACtC,oCAAiC","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,+BAA4B;AAC5B,kCAA+B;AAC/B,gCAA6B;AAC7B,yCAAsC;AACtC,oCAAiC;AACjC,6BAA0B","sourcesContent":["// Export utilities\nexport * from './chatUtils';\nexport * from './messageUtils';\nexport * from './validation';\nexport * from './messageTypeRegistry';\nexport * from './clipboardUtils';\nexport * from './toolset';\n"]}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { ComponentType, ReactNode } 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 ToolDefinition<TArgs, TResult> = {
|
|
22
|
+
name: string;
|
|
23
|
+
description: string;
|
|
24
|
+
parameters: Record<string, unknown>;
|
|
25
|
+
schema: ToolSchema<TArgs>;
|
|
26
|
+
component: ComponentType<ToolComponentProps<TArgs, TResult>>;
|
|
27
|
+
execute?: (params: {
|
|
28
|
+
args: TArgs;
|
|
29
|
+
result: TResult;
|
|
30
|
+
toolCallId: string;
|
|
31
|
+
}) => TResult | Promise<TResult>;
|
|
32
|
+
};
|
|
33
|
+
export type RuntimeToolDefinition = {
|
|
34
|
+
name: string;
|
|
35
|
+
description: string;
|
|
36
|
+
parameters: Record<string, unknown>;
|
|
37
|
+
validate: (input: unknown) => ToolSchemaResult<unknown>;
|
|
38
|
+
render: (props: {
|
|
39
|
+
args: unknown;
|
|
40
|
+
result?: unknown;
|
|
41
|
+
submitResult: (result: unknown) => void;
|
|
42
|
+
}) => ReactNode;
|
|
43
|
+
execute: (params: {
|
|
44
|
+
args: unknown;
|
|
45
|
+
result: unknown;
|
|
46
|
+
toolCallId: string;
|
|
47
|
+
}) => unknown | Promise<unknown>;
|
|
48
|
+
};
|
|
49
|
+
export type Toolset = Record<string, RuntimeToolDefinition>;
|
|
50
|
+
export type ToolPartContentData<TArgs = unknown, TResult = unknown> = ToolMessageContentData & {
|
|
51
|
+
toolCallId: string;
|
|
52
|
+
args?: TArgs;
|
|
53
|
+
result?: TResult;
|
|
54
|
+
};
|
|
55
|
+
export type ToolPartContent<TArgs = unknown, TResult = unknown> = TMessageContent<'tool', ToolPartContentData<TArgs, TResult>>;
|
|
56
|
+
export type ToolsetResultEvent = {
|
|
57
|
+
toolCallId: string;
|
|
58
|
+
toolName: string;
|
|
59
|
+
result: unknown;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Wrap a typed tool definition into an erased runtime entry that the
|
|
63
|
+
* toolset renderer can dispatch by `toolName`.
|
|
64
|
+
*/
|
|
65
|
+
export declare function defineTool<TArgs, TResult>(definition: ToolDefinition<TArgs, TResult>): RuntimeToolDefinition;
|
|
66
|
+
export type CreateToolsetRendererOptions = {
|
|
67
|
+
onToolResult: (event: ToolsetResultEvent) => void;
|
|
68
|
+
/**
|
|
69
|
+
* Existing registry to extend instead of starting from a fresh one.
|
|
70
|
+
* The `tool` renderer is overridden; other types are preserved.
|
|
71
|
+
*/
|
|
72
|
+
registry?: MessageRendererRegistry;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Build a `MessageRendererRegistry` whose `tool` renderer dispatches
|
|
76
|
+
* by `toolName` into the provided toolset. Unknown tools and invalid
|
|
77
|
+
* args fall back to a generic `<ToolMessage status="error" />`.
|
|
78
|
+
*/
|
|
79
|
+
export declare function createToolsetRenderer(toolset: Toolset, options: CreateToolsetRendererOptions): MessageRendererRegistry;
|
|
80
|
+
/**
|
|
81
|
+
* Merge a tool result into the matching `tool` part of the chat history.
|
|
82
|
+
* Returns the original array reference when nothing matched, so React state
|
|
83
|
+
* setters can skip needless updates.
|
|
84
|
+
*/
|
|
85
|
+
export declare function applyToolResult<TCustom extends TMessageContent = never>(messages: TChatMessage<TCustom>[], event: ToolsetResultEvent): TChatMessage<TCustom>[];
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { ToolMessage } from "../components/organisms/ToolMessage/index.js";
|
|
3
|
+
import { createMessageRendererRegistry, registerMessageRenderer, } from "./messageTypeRegistry.js";
|
|
4
|
+
/**
|
|
5
|
+
* Wrap a typed tool definition into an erased runtime entry that the
|
|
6
|
+
* toolset renderer can dispatch by `toolName`.
|
|
7
|
+
*/
|
|
8
|
+
export function defineTool(definition) {
|
|
9
|
+
const Component = definition.component;
|
|
10
|
+
return {
|
|
11
|
+
name: definition.name,
|
|
12
|
+
description: definition.description,
|
|
13
|
+
parameters: definition.parameters,
|
|
14
|
+
validate: definition.schema.validate,
|
|
15
|
+
render: ({ args, result, submitResult }) => (_jsx(Component, { args: args, result: result, submitResult: submitResult })),
|
|
16
|
+
execute: ({ args, result, toolCallId }) => definition.execute
|
|
17
|
+
? definition.execute({
|
|
18
|
+
args: args,
|
|
19
|
+
result: result,
|
|
20
|
+
toolCallId,
|
|
21
|
+
})
|
|
22
|
+
: result,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Build a `MessageRendererRegistry` whose `tool` renderer dispatches
|
|
27
|
+
* by `toolName` into the provided toolset. Unknown tools and invalid
|
|
28
|
+
* args fall back to a generic `<ToolMessage status="error" />`.
|
|
29
|
+
*/
|
|
30
|
+
export function createToolsetRenderer(toolset, options) {
|
|
31
|
+
const { onToolResult, registry = createMessageRendererRegistry() } = options;
|
|
32
|
+
registerMessageRenderer(registry, 'tool', {
|
|
33
|
+
component: ({ part }) => {
|
|
34
|
+
const toolPart = part.data;
|
|
35
|
+
const toolDef = toolset[toolPart.toolName];
|
|
36
|
+
if (!toolDef) {
|
|
37
|
+
return (_jsx(ToolMessage, Object.assign({}, toolPart, { status: "error", expandable: true, initialExpanded: true, bodyContent: `Unknown tool: ${toolPart.toolName}` })));
|
|
38
|
+
}
|
|
39
|
+
const validation = toolDef.validate(toolPart.args);
|
|
40
|
+
if (!validation.success) {
|
|
41
|
+
return (_jsx(ToolMessage, Object.assign({}, toolPart, { status: "error", expandable: true, initialExpanded: true, bodyContent: validation.error.message })));
|
|
42
|
+
}
|
|
43
|
+
const submitResult = (result) => {
|
|
44
|
+
Promise.resolve(toolDef.execute({
|
|
45
|
+
args: validation.data,
|
|
46
|
+
result,
|
|
47
|
+
toolCallId: toolPart.toolCallId,
|
|
48
|
+
})).then((finalResult) => {
|
|
49
|
+
onToolResult({
|
|
50
|
+
toolCallId: toolPart.toolCallId,
|
|
51
|
+
toolName: toolPart.toolName,
|
|
52
|
+
result: finalResult,
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
return toolDef.render({
|
|
57
|
+
args: validation.data,
|
|
58
|
+
result: toolPart.result,
|
|
59
|
+
submitResult,
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
return registry;
|
|
64
|
+
}
|
|
65
|
+
function isToolPartContent(part) {
|
|
66
|
+
return part.type === 'tool' && typeof part.data === 'object' && part.data !== null;
|
|
67
|
+
}
|
|
68
|
+
function toContentParts(content) {
|
|
69
|
+
if (typeof content === 'string') {
|
|
70
|
+
return content ? [{ type: 'text', data: { text: content } }] : [];
|
|
71
|
+
}
|
|
72
|
+
return Array.isArray(content) ? content : [content];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Merge a tool result into the matching `tool` part of the chat history.
|
|
76
|
+
* Returns the original array reference when nothing matched, so React state
|
|
77
|
+
* setters can skip needless updates.
|
|
78
|
+
*/
|
|
79
|
+
export function applyToolResult(messages, event) {
|
|
80
|
+
let changed = false;
|
|
81
|
+
const next = messages.map((msg) => {
|
|
82
|
+
if (msg.role !== 'assistant')
|
|
83
|
+
return msg;
|
|
84
|
+
const parts = toContentParts(msg.content);
|
|
85
|
+
const hasMatch = parts.some((part) => isToolPartContent(part) && part.data.toolCallId === event.toolCallId);
|
|
86
|
+
if (!hasMatch)
|
|
87
|
+
return msg;
|
|
88
|
+
changed = true;
|
|
89
|
+
const updatedParts = parts.map((part) => {
|
|
90
|
+
if (!isToolPartContent(part) || part.data.toolCallId !== event.toolCallId) {
|
|
91
|
+
return part;
|
|
92
|
+
}
|
|
93
|
+
return Object.assign(Object.assign({}, part), { data: Object.assign(Object.assign({}, part.data), { status: 'success', result: event.result }) });
|
|
94
|
+
});
|
|
95
|
+
return Object.assign(Object.assign({}, msg), { content: updatedParts });
|
|
96
|
+
});
|
|
97
|
+
return changed ? next : messages;
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=toolset.js.map
|