@gravity-ui/aikit 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/build/cjs/components/organisms/AssistantMessage/defaultMessageTypeRegistry.js +6 -0
  2. package/build/cjs/components/organisms/AssistantMessage/defaultMessageTypeRegistry.js.map +1 -1
  3. package/build/cjs/hooks/index.d.ts +2 -0
  4. package/build/cjs/hooks/index.js +2 -0
  5. package/build/cjs/hooks/index.js.map +1 -1
  6. package/build/cjs/hooks/useToolResultContinuation.d.ts +31 -0
  7. package/build/cjs/hooks/useToolResultContinuation.js +91 -0
  8. package/build/cjs/hooks/useToolResultContinuation.js.map +1 -0
  9. package/build/cjs/hooks/useToolset.d.ts +24 -0
  10. package/build/cjs/hooks/useToolset.js +26 -0
  11. package/build/cjs/hooks/useToolset.js.map +1 -0
  12. package/build/cjs/package.json +1 -1
  13. package/build/cjs/utils/index.d.ts +1 -0
  14. package/build/cjs/utils/index.js +1 -0
  15. package/build/cjs/utils/index.js.map +1 -1
  16. package/build/cjs/utils/messageTypeRegistry.d.ts +8 -1
  17. package/build/cjs/utils/messageTypeRegistry.js +14 -2
  18. package/build/cjs/utils/messageTypeRegistry.js.map +1 -1
  19. package/build/cjs/utils/toolset/i18n/en.json +3 -0
  20. package/build/cjs/utils/toolset/i18n/index.d.ts +13 -0
  21. package/build/cjs/utils/toolset/i18n/index.js +10 -0
  22. package/build/cjs/utils/toolset/i18n/index.js.map +1 -0
  23. package/build/cjs/utils/toolset/i18n/ru.json +3 -0
  24. package/build/cjs/utils/toolset/index.d.ts +142 -0
  25. package/build/cjs/utils/toolset/index.js +183 -0
  26. package/build/cjs/utils/toolset/index.js.map +1 -0
  27. package/build/esm/components/organisms/AssistantMessage/defaultMessageTypeRegistry.js +6 -0
  28. package/build/esm/components/organisms/AssistantMessage/defaultMessageTypeRegistry.js.map +1 -1
  29. package/build/esm/hooks/index.d.ts +2 -0
  30. package/build/esm/hooks/index.js +2 -0
  31. package/build/esm/hooks/index.js.map +1 -1
  32. package/build/esm/hooks/useToolResultContinuation.d.ts +31 -0
  33. package/build/esm/hooks/useToolResultContinuation.js +87 -0
  34. package/build/esm/hooks/useToolResultContinuation.js.map +1 -0
  35. package/build/esm/hooks/useToolset.d.ts +24 -0
  36. package/build/esm/hooks/useToolset.js +23 -0
  37. package/build/esm/hooks/useToolset.js.map +1 -0
  38. package/build/esm/package.json +1 -1
  39. package/build/esm/utils/index.d.ts +1 -0
  40. package/build/esm/utils/index.js +1 -0
  41. package/build/esm/utils/index.js.map +1 -1
  42. package/build/esm/utils/messageTypeRegistry.d.ts +8 -1
  43. package/build/esm/utils/messageTypeRegistry.js +14 -2
  44. package/build/esm/utils/messageTypeRegistry.js.map +1 -1
  45. package/build/esm/utils/toolset/i18n/en.json +3 -0
  46. package/build/esm/utils/toolset/i18n/index.d.ts +13 -0
  47. package/build/esm/utils/toolset/i18n/index.js +6 -0
  48. package/build/esm/utils/toolset/i18n/index.js.map +1 -0
  49. package/build/esm/utils/toolset/i18n/ru.json +3 -0
  50. package/build/esm/utils/toolset/index.d.ts +142 -0
  51. package/build/esm/utils/toolset/index.js +176 -0
  52. package/build/esm/utils/toolset/index.js.map +1 -0
  53. package/docs/GENUI.md +627 -0
  54. package/docs/HOOKS.md +65 -11
  55. package/llms.txt +2 -1
  56. package/package.json +11 -1
@@ -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,176 @@
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
+ import { i18n } from "./i18n/index.js";
5
+ /**
6
+ * Wrap a typed tool definition into an erased runtime entry that the
7
+ * toolset renderer can dispatch by `toolName`. The literal `name` is
8
+ * preserved as a type parameter so `createToolset` can derive its keys.
9
+ */
10
+ export function defineTool(definition) {
11
+ return {
12
+ name: definition.name,
13
+ description: definition.description,
14
+ parameters: definition.parameters,
15
+ validate: definition.schema.validate,
16
+ Renderer: definition.component,
17
+ execute: ({ args, result, toolCallId }) => definition.execute
18
+ ? definition.execute({
19
+ args: args,
20
+ result: result,
21
+ toolCallId,
22
+ })
23
+ : result,
24
+ };
25
+ }
26
+ /**
27
+ * Build a `Toolset` from a list of `defineTool(...)` results. Keys are
28
+ * derived from `definition.name`, so the call site cannot drift between
29
+ * a literal-object key and the embedded `name`. The return type narrows
30
+ * its keys to the union of literal tool names, giving name-autocomplete
31
+ * on lookups. Throws on duplicates.
32
+ */
33
+ export function createToolset(...tools) {
34
+ const result = {};
35
+ for (const tool of tools) {
36
+ if (Object.prototype.hasOwnProperty.call(result, tool.name)) {
37
+ throw new Error(`createToolset: duplicate tool name "${tool.name}"`);
38
+ }
39
+ result[tool.name] = tool;
40
+ }
41
+ return result;
42
+ }
43
+ /**
44
+ * Map a `Toolset` to the OpenAI `tools[]` shape so chat clients can pass
45
+ * tool definitions to the model without reimplementing the conversion.
46
+ */
47
+ export function toolsetToOpenAIDefinitions(toolset) {
48
+ return Object.values(toolset).map((tool) => ({
49
+ type: 'function',
50
+ function: {
51
+ name: tool.name,
52
+ description: tool.description,
53
+ parameters: tool.parameters,
54
+ },
55
+ }));
56
+ }
57
+ const OUTCOME_KEYS = new Set(['status', 'result', 'error']);
58
+ function isToolExecutionOutcome(value) {
59
+ if (!value || typeof value !== 'object')
60
+ return false;
61
+ const status = value.status;
62
+ if (status !== 'success' && status !== 'error' && status !== 'cancelled')
63
+ return false;
64
+ // A bare TResult may coincidentally carry a `status` field. Require all
65
+ // own keys to be in {status, result, error} so e.g. `{status: 'success',
66
+ // id: '123'}` is treated as a result, not a (result-less) outcome.
67
+ for (const key of Object.keys(value)) {
68
+ if (!OUTCOME_KEYS.has(key))
69
+ return false;
70
+ }
71
+ return true;
72
+ }
73
+ function normalizeOutcome(value) {
74
+ if (isToolExecutionOutcome(value))
75
+ return value;
76
+ return { status: 'success', result: value };
77
+ }
78
+ /**
79
+ * Build a `MessageRendererRegistry` whose `tool` renderer dispatches
80
+ * by `toolName` into the provided toolset. Unknown tools and invalid
81
+ * args fall back to a generic `<ToolMessage status="error" />`. Errors
82
+ * thrown inside `execute` are surfaced via an `error` outcome.
83
+ */
84
+ export function createToolsetRenderer(toolset, options) {
85
+ var _a;
86
+ const { onToolResult } = options;
87
+ const registry = Object.assign({}, ((_a = options.registry) !== null && _a !== void 0 ? _a : createMessageRendererRegistry()));
88
+ const toolRenderer = {
89
+ component: ({ part }) => {
90
+ const toolPart = part.data;
91
+ const toolDef = toolset[toolPart.toolName];
92
+ if (!toolDef) {
93
+ return (_jsx(ToolMessage, { toolName: toolPart.toolName, status: "error", expandable: true, initialExpanded: true, bodyContent: i18n('fallback-unknown-tool', {
94
+ toolName: toolPart.toolName,
95
+ }) }));
96
+ }
97
+ const validation = toolDef.validate(toolPart.args);
98
+ if (!validation.success) {
99
+ return (_jsx(ToolMessage, { toolName: toolPart.toolName, status: "error", expandable: true, initialExpanded: true, bodyContent: validation.error.message }));
100
+ }
101
+ const submitResult = (result) => {
102
+ Promise.resolve()
103
+ .then(() => toolDef.execute({
104
+ args: validation.data,
105
+ result,
106
+ toolCallId: toolPart.toolCallId,
107
+ }))
108
+ .then((raw) => normalizeOutcome(raw))
109
+ .catch((err) => ({
110
+ status: 'error',
111
+ error: { message: err instanceof Error ? err.message : String(err) },
112
+ }))
113
+ .then((outcome) => {
114
+ onToolResult({
115
+ toolCallId: toolPart.toolCallId,
116
+ toolName: toolPart.toolName,
117
+ status: outcome.status,
118
+ result: outcome.result,
119
+ error: 'error' in outcome ? outcome.error : undefined,
120
+ });
121
+ });
122
+ };
123
+ const { Renderer } = toolDef;
124
+ return (_jsx(Renderer, { args: validation.data, result: toolPart.result, submitResult: submitResult }));
125
+ },
126
+ };
127
+ registerMessageRenderer(registry, 'tool', toolRenderer);
128
+ return registry;
129
+ }
130
+ function isToolPartContent(part) {
131
+ return part.type === 'tool' && typeof part.data === 'object' && part.data !== null;
132
+ }
133
+ function toContentParts(content) {
134
+ if (typeof content === 'string') {
135
+ return content ? [{ type: 'text', data: { text: content } }] : [];
136
+ }
137
+ return Array.isArray(content) ? content : [content];
138
+ }
139
+ /**
140
+ * Merge a tool result into the matching `tool` part of the chat history.
141
+ * Honors `event.status` so the part reflects success / error / cancelled.
142
+ * For backward compatibility, a missing `status` is treated as `'success'`.
143
+ * Returns the original array reference when nothing matched, so React state
144
+ * setters can skip needless updates.
145
+ */
146
+ export function applyToolResult(messages, event) {
147
+ var _a;
148
+ let changed = false;
149
+ const status = (_a = event.status) !== null && _a !== void 0 ? _a : 'success';
150
+ const isError = status === 'error';
151
+ const next = messages.map((msg) => {
152
+ if (msg.role !== 'assistant')
153
+ return msg;
154
+ const parts = toContentParts(msg.content);
155
+ const hasMatch = parts.some((part) => isToolPartContent(part) && part.data.toolCallId === event.toolCallId);
156
+ if (!hasMatch)
157
+ return msg;
158
+ changed = true;
159
+ const updatedParts = parts.map((part) => {
160
+ var _a;
161
+ if (!isToolPartContent(part) || part.data.toolCallId !== event.toolCallId) {
162
+ return part;
163
+ }
164
+ const nextData = Object.assign(Object.assign({}, part.data), { status, result: event.result });
165
+ if (isError && ((_a = event.error) === null || _a === void 0 ? void 0 : _a.message)) {
166
+ nextData.bodyContent = event.error.message;
167
+ nextData.expandable = true;
168
+ nextData.initialExpanded = true;
169
+ }
170
+ return Object.assign(Object.assign({}, part), { data: nextData });
171
+ });
172
+ return Object.assign(Object.assign({}, msg), { content: updatedParts });
173
+ });
174
+ return changed ? next : messages;
175
+ }
176
+ //# 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 MessageRenderer,\n type MessageRendererRegistry,\n createMessageRendererRegistry,\n registerMessageRenderer,\n} from '../messageTypeRegistry';\n\nimport {i18n} from './i18n';\n\nexport type ToolSchemaResult<TArgs> =\n | {success: true; data: TArgs}\n | {success: false; error: {message: string}};\n\nexport type ToolSchema<TArgs> = {\n validate: (input: unknown) => ToolSchemaResult<TArgs>;\n};\n\nexport type ToolComponentProps<TArgs, TResult> = {\n args: TArgs;\n result?: TResult;\n submitResult: (result: TResult) => void;\n};\n\nexport type ToolExecutionStatus = 'success' | 'error' | 'cancelled';\n\n/**\n * Discriminated outcome returned by a tool's `execute` callback.\n * A tool may also return a bare `TResult`; it is then treated as\n * `{status: 'success', result}` to keep simple cases boilerplate-free.\n */\nexport type ToolExecutionOutcome<TResult> =\n | {status: 'success'; result: TResult}\n | {status: 'error'; result?: TResult; error?: {message: string}}\n | {status: 'cancelled'; result?: TResult};\n\nexport type ToolDefinition<TArgs, TResult, TName extends string = string> = {\n name: TName;\n description: string;\n parameters: Record<string, unknown>;\n schema: ToolSchema<TArgs>;\n component: ComponentType<ToolComponentProps<TArgs, TResult>>;\n /**\n * Run after the user submits a result inside the tool component.\n * Return:\n * - `TResult` (or a Promise of one) for a successful execution;\n * - a `ToolExecutionOutcome<TResult>` to explicitly report `error`\n * or `cancelled`. A thrown error is surfaced as `{status: 'error'}`.\n */\n execute?: (params: {\n args: TArgs;\n result: TResult;\n toolCallId: string;\n }) =>\n | TResult\n | ToolExecutionOutcome<TResult>\n | Promise<TResult | ToolExecutionOutcome<TResult>>;\n};\n\nexport type RuntimeToolDefinition<TName extends string = string> = {\n name: TName;\n description: string;\n parameters: Record<string, unknown>;\n validate: (input: unknown) => ToolSchemaResult<unknown>;\n /**\n * Rendered as `<Renderer ... />` JSX. Hooks placed at the top of this\n * component work normally because it is a real React component, not a\n * function called inside another render path.\n */\n Renderer: ComponentType<ToolComponentProps<unknown, unknown>>;\n execute: (params: {\n args: unknown;\n result: unknown;\n toolCallId: string;\n }) =>\n | unknown\n | ToolExecutionOutcome<unknown>\n | Promise<unknown | ToolExecutionOutcome<unknown>>;\n};\n\nexport type Toolset = Record<string, RuntimeToolDefinition>;\n\nexport type ToolPartContentData<TArgs = unknown, TResult = unknown> = ToolMessageContentData & {\n toolCallId: string;\n args?: TArgs;\n result?: TResult;\n};\n\nexport type ToolPartContent<TArgs = unknown, TResult = unknown> = TMessageContent<\n 'tool',\n ToolPartContentData<TArgs, TResult>\n>;\n\nexport type ToolsetResultEvent = {\n toolCallId: string;\n toolName: string;\n status: ToolExecutionStatus;\n result: unknown;\n error?: {message: string};\n};\n\n/**\n * Wrap a typed tool definition into an erased runtime entry that the\n * toolset renderer can dispatch by `toolName`. The literal `name` is\n * preserved as a type parameter so `createToolset` can derive its keys.\n */\nexport function defineTool<TArgs, TResult, const TName extends string>(\n definition: ToolDefinition<TArgs, TResult, TName>,\n): RuntimeToolDefinition<TName> {\n return {\n name: definition.name,\n description: definition.description,\n parameters: definition.parameters,\n validate: definition.schema.validate as (input: unknown) => ToolSchemaResult<unknown>,\n Renderer: definition.component as ComponentType<ToolComponentProps<unknown, unknown>>,\n execute: ({args, result, toolCallId}) =>\n definition.execute\n ? definition.execute({\n args: args as TArgs,\n result: result as TResult,\n toolCallId,\n })\n : result,\n };\n}\n\n/**\n * Build a `Toolset` from a list of `defineTool(...)` results. Keys are\n * derived from `definition.name`, so the call site cannot drift between\n * a literal-object key and the embedded `name`. The return type narrows\n * its keys to the union of literal tool names, giving name-autocomplete\n * on lookups. Throws on duplicates.\n */\nexport function createToolset<const T extends readonly RuntimeToolDefinition<string>[]>(\n ...tools: T\n): {[K in T[number]['name']]: RuntimeToolDefinition<T[number]['name']>} {\n const result: Record<string, RuntimeToolDefinition> = {};\n for (const tool of tools) {\n if (Object.prototype.hasOwnProperty.call(result, tool.name)) {\n throw new Error(`createToolset: duplicate tool name \"${tool.name}\"`);\n }\n result[tool.name] = tool;\n }\n return result as {[K in T[number]['name']]: RuntimeToolDefinition<T[number]['name']>};\n}\n\n/**\n * Map a `Toolset` to the OpenAI `tools[]` shape so chat clients can pass\n * tool definitions to the model without reimplementing the conversion.\n */\nexport function toolsetToOpenAIDefinitions(toolset: Toolset): Array<{\n type: 'function';\n function: {name: string; description: string; parameters: Record<string, unknown>};\n}> {\n return Object.values(toolset).map((tool) => ({\n type: 'function' as const,\n function: {\n name: tool.name,\n description: tool.description,\n parameters: tool.parameters,\n },\n }));\n}\n\nexport type CreateToolsetRendererOptions = {\n onToolResult: (event: ToolsetResultEvent) => void;\n /**\n * Existing registry whose entries should be preserved. A shallow copy\n * is taken; the input reference is never mutated.\n */\n registry?: MessageRendererRegistry;\n};\n\nconst OUTCOME_KEYS = new Set(['status', 'result', 'error']);\n\nfunction isToolExecutionOutcome<TResult>(value: unknown): value is ToolExecutionOutcome<TResult> {\n if (!value || typeof value !== 'object') return false;\n const status = (value as {status?: unknown}).status;\n if (status !== 'success' && status !== 'error' && status !== 'cancelled') return false;\n // A bare TResult may coincidentally carry a `status` field. Require all\n // own keys to be in {status, result, error} so e.g. `{status: 'success',\n // id: '123'}` is treated as a result, not a (result-less) outcome.\n for (const key of Object.keys(value)) {\n if (!OUTCOME_KEYS.has(key)) return false;\n }\n return true;\n}\n\nfunction normalizeOutcome<TResult>(value: unknown): ToolExecutionOutcome<TResult> {\n if (isToolExecutionOutcome<TResult>(value)) return value;\n return {status: 'success', result: value as TResult};\n}\n\n/**\n * Build a `MessageRendererRegistry` whose `tool` renderer dispatches\n * by `toolName` into the provided toolset. Unknown tools and invalid\n * args fall back to a generic `<ToolMessage status=\"error\" />`. Errors\n * thrown inside `execute` are surfaced via an `error` outcome.\n */\nexport function createToolsetRenderer(\n toolset: Toolset,\n options: CreateToolsetRendererOptions,\n): MessageRendererRegistry {\n const {onToolResult} = options;\n const registry: MessageRendererRegistry = {\n ...(options.registry ?? createMessageRendererRegistry()),\n };\n\n const toolRenderer: MessageRenderer<ToolPartContent> = {\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 registerMessageRenderer<ToolPartContent>(registry, 'tool', toolRenderer);\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"]}