diff --git a/README.md b/README.md index ad9ad28c..96e5a14b 100644 --- a/README.md +++ b/README.md @@ -115,8 +115,8 @@ yarn add @mcp-ui/server @mcp-ui/client return ( { - console.log('Action:', tool, params); + onUiAction={(result) => { + console.log('Action:', result); return { status: 'ok' }; }} /> diff --git a/docs/src/guide/client/html-resource.md b/docs/src/guide/client/html-resource.md index f20204b3..3dee89ed 100644 --- a/docs/src/guide/client/html-resource.md +++ b/docs/src/guide/client/html-resource.md @@ -9,16 +9,21 @@ import type { Resource } from '@modelcontextprotocol/sdk/types'; export interface HtmlResourceProps { resource: Partial; - onUiAction?: ( - tool: string, - params: Record, - ) => Promise; + onUiAction?: (result: UiActionResult) => Promise; style?: React.CSSProperties; } ``` - **`resource`**: The resource object from an `HtmlResourceBlock`. It should include `uri`, `mimeType`, and either `text` or `blob`. -- **`onUiAction`**: An optional callback that fires when the iframe content (for `ui://` resources) posts a message to your app. The message should look like `{ tool: string, params: Record }`. +- **`onUiAction`**: An optional callback that fires when the iframe content (for `ui://` resources) posts a message to your app. The message should look like: + ```typescript + { type: 'tool', payload: { toolName: string, params: Record } } | + { type: 'intent', payload: { intent: string, params: Record } } | + { type: 'prompt', payload: { prompt: string } } | + { type: 'notification', payload: { message: string } } | + { type: 'link', payload: { url: string } } | + ``` + If you don't provide a callback for a specific type, the default handler will be used. - **`style`** (optional): Custom styles for the iframe. ## How It Works diff --git a/docs/src/guide/client/usage-examples.md b/docs/src/guide/client/usage-examples.md index 1df56517..2b522649 100644 --- a/docs/src/guide/client/usage-examples.md +++ b/docs/src/guide/client/usage-examples.md @@ -14,7 +14,7 @@ pnpm add @mcp-ui/client react @modelcontextprotocol/sdk ```tsx import React, { useState } from 'react'; -import { HtmlResource } from '@mcp-ui/client'; +import { HtmlResource, UiActionResult } from '@mcp-ui/client'; // Simulate fetching an MCP resource block const fetchMcpResource = async (id: string): Promise => { @@ -24,12 +24,12 @@ const fetchMcpResource = async (id: string): Promise => { resource: { uri: 'ui://example/direct-html', mimeType: 'text/html', - text: "

Direct HTML via Text

Content loaded directly.

", + text: "

Direct HTML via Text

Content loaded directly.

", }, }; } else if (id === 'blob') { const html = - "

HTML from Blob

Content was Base64 encoded.

"; + "

HTML from Blob

Content was Base64 encoded.

"; return { type: 'resource', resource: { @@ -72,12 +72,23 @@ const App: React.FC = () => { setLoading(false); }; - const handleGenericMcpAction = async ( - tool: string, - params: Record, - ) => { - console.log(`Action received in host app - Tool: ${tool}, Params:`, params); - setLastAction({ tool, params }); + const handleGenericMcpAction = async (result: UiActionResult) => { + if (result.type === 'tool') { + console.log(`Action received in host app - Tool: ${result.payload.toolName}, Params:`, result.payload.params); + setLastAction({ tool: result.payload.toolName, params: result.payload.params }); + } else if (result.type === 'prompt') { + console.log(`Prompt received in host app:`, result.payload.prompt); + setLastAction({ prompt: result.payload.prompt }); + } else if (result.type === 'link') { + console.log(`Link received in host app:`, result.payload.url); + setLastAction({ url: result.payload.url }); + } else if (result.type === 'intent') { + console.log(`Intent received in host app:`, result.payload.intent); + setLastAction({ intent: result.payload.intent }); + } else if (result.type === 'notification') { + console.log(`Notification received in host app:`, result.payload.message); + setLastAction({ message: result.payload.message }); + } return { status: 'Action handled by host application', receivedParams: params, diff --git a/docs/src/guide/getting-started.md b/docs/src/guide/getting-started.md index f1edddd3..88b6c030 100644 --- a/docs/src/guide/getting-started.md +++ b/docs/src/guide/getting-started.md @@ -101,11 +101,18 @@ function App() { setMcpData(fakeMcpResponse); }, []); - const handleResourceAction = async ( - tool: string, - params: Record, - ) => { - console.log(`Action from resource (tool: ${tool}):`, params); + const handleResourceAction = async (result: UiActionResult) => { + if (result.type === 'tool') { + console.log(`Action from resource (tool: ${result.payload.toolName}):`, result.payload.params); + } else if (result.type === 'prompt') { + console.log(`Prompt from resource:`, result.payload.prompt); + } else if (result.type === 'link') { + console.log(`Link from resource:`, result.payload.url); + } else if (result.type === 'intent') { + console.log(`Intent from resource:`, result.payload.intent); + } else if (result.type === 'notification') { + console.log(`Notification from resource:`, result.payload.message); + } // Add your handling logic (e.g., initiate followup tool call) return { status: 'Action received by client' }; }; diff --git a/docs/src/guide/protocol-details.md b/docs/src/guide/protocol-details.md index d1c3398e..784bf429 100644 --- a/docs/src/guide/protocol-details.md +++ b/docs/src/guide/protocol-details.md @@ -100,7 +100,10 @@ For `ui://` resources, you can use `window.parent.postMessage` to send data or a const data = { action: 'formData', value: 'someValue' }; // IMPORTANT: Always specify the targetOrigin for security! // Use '*' only if the parent origin is unknown or variable and security implications are understood. - window.parent.postMessage({ tool: 'myCustomTool', params: data }, '*'); + window.parent.postMessage( + { type: 'tool', payload: { toolName: 'myCustomTool', params: data } }, + '*', + ); } ``` diff --git a/examples/server/README.md b/examples/server/README.md index 720bedaf..68adfc94 100644 --- a/examples/server/README.md +++ b/examples/server/README.md @@ -115,8 +115,8 @@ yarn add @mcp-ui/server @mcp-ui/client return ( { - console.log('Action:', tool, params); + onUiAction={(result) => { + console.log('Action:', result); return { status: 'ok' }; }} /> diff --git a/examples/server/app/graph/graph.tsx b/examples/server/app/graph/graph.tsx index 1a6133ff..2cf3f2f4 100644 --- a/examples/server/app/graph/graph.tsx +++ b/examples/server/app/graph/graph.tsx @@ -402,11 +402,14 @@ const CustomAvatarXAxisTick = (props: { // @ts-expect-error - window is not typed correctly if (memberInfo && window.parent) { const message = { - tool: 'show_user_status', - params: { - id: memberInfo.id, - name: memberInfo.name, - avatarUrl: memberInfo.avatarUrl, + type: 'tool', + payload: { + toolName: 'show_user_status', + params: { + id: memberInfo.id, + name: memberInfo.name, + avatarUrl: memberInfo.avatarUrl, + }, }, }; // @ts-expect-error - window is not typed correctly diff --git a/examples/server/app/user/user.tsx b/examples/server/app/user/user.tsx index 2f28841c..1c0331fd 100644 --- a/examples/server/app/user/user.tsx +++ b/examples/server/app/user/user.tsx @@ -32,9 +32,12 @@ export function User({ user }: { user: UserInfo }) { // @ts-expect-error - window is not typed correctly if (user.id && window.parent) { const message = { - tool: 'nudge_team_member', - params: { - name: user.name, + type: 'tool', + payload: { + toolName: 'nudge_team_member', + params: { + name: user.name, + }, }, }; // @ts-expect-error - window is not typed correctly diff --git a/package.json b/package.json index 58a1c2f3..66abf37a 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "@testing-library/jest-dom": "^6.1.5", "@testing-library/react": "^14.1.2", "@types/jest": "^29.5.11", - "@types/node": "^22.0.0", + "@types/node": "^22.15.18", "@typescript-eslint/eslint-plugin": "^6.13.2", "@typescript-eslint/parser": "^6.13.2", "@vitejs/plugin-react-swc": "^3.9.0", diff --git a/packages/client/README.md b/packages/client/README.md index d6454faa..98d2c9c0 100644 --- a/packages/client/README.md +++ b/packages/client/README.md @@ -115,8 +115,8 @@ yarn add @mcp-ui/server @mcp-ui/client return ( { - console.log('Action:', tool, params); + onUiAction={(result) => { + console.log('Action:', result); return { status: 'ok' }; }} /> diff --git a/packages/client/src/components/HtmlResource.tsx b/packages/client/src/components/HtmlResource.tsx index 961956b2..a922561d 100644 --- a/packages/client/src/components/HtmlResource.tsx +++ b/packages/client/src/components/HtmlResource.tsx @@ -1,12 +1,10 @@ import React, { useEffect, useRef, useState } from 'react'; import type { Resource } from '@modelcontextprotocol/sdk/types.js'; +import { UiActionResult } from '../types'; export interface RenderHtmlResourceProps { resource: Partial; - onUiAction?: ( - tool: string, - params: Record, - ) => Promise; + onUiAction?: (result: UiActionResult) => Promise; style?: React.CSSProperties; } @@ -129,13 +127,18 @@ export const HtmlResource: React.FC = ({ function handleMessage(event: MessageEvent) { // Only process the message if it came from this specific iframe if ( - onUiAction && iframeRef.current && - event.source === iframeRef.current.contentWindow && - event.data?.tool + event.source === iframeRef.current.contentWindow ) { - onUiAction(event.data.tool, event.data.params || {}).catch((err) => { - console.error('Error from onUiAction in RenderHtmlResource:', err); + const uiActionResult = event.data as UiActionResult; + if (!uiActionResult) { + return; + } + onUiAction?.(uiActionResult)?.catch((err) => { + console.error( + 'Error handling UI action result in RenderHtmlResource:', + err, + ); }); } } diff --git a/packages/client/src/components/__tests__/HtmlResource.test.tsx b/packages/client/src/components/__tests__/HtmlResource.test.tsx index 4515fc30..55204d8c 100644 --- a/packages/client/src/components/__tests__/HtmlResource.test.tsx +++ b/packages/client/src/components/__tests__/HtmlResource.test.tsx @@ -3,6 +3,7 @@ import '@testing-library/jest-dom'; import { HtmlResource, RenderHtmlResourceProps } from '../HtmlResource.js'; import { vi, Mock, MockInstance } from 'vitest'; import type { Resource } from '@modelcontextprotocol/sdk/types.js'; +import { UiActionResult } from '../../types.js'; describe('HtmlResource component', () => { const mockOnUiAction = vi.fn(); @@ -69,7 +70,9 @@ describe('HtmlResource component', () => { }; render(); expect( - screen.getByText('Resource must be of type text/html (for HTML content) or text/uri-list (for URL content).'), + screen.getByText( + 'Resource must be of type text/html (for HTML content) or text/uri-list (for URL content).', + ), ).toBeInTheDocument(); }); @@ -110,8 +113,11 @@ describe('HtmlResource component', () => { }); it('handles multiple URLs in uri-list format and uses the first one', () => { - const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); - const uriList = 'https://example.com/first\nhttps://example.com/second\nhttps://example.com/third'; + const consoleWarnSpy = vi + .spyOn(console, 'warn') + .mockImplementation(() => {}); + const uriList = + 'https://example.com/first\nhttps://example.com/second\nhttps://example.com/third'; const props: RenderHtmlResourceProps = { resource: { uri: 'ui://multi-url-test', @@ -127,7 +133,7 @@ describe('HtmlResource component', () => { expect(iframe.src).toBe('https://example.com/first'); expect(consoleWarnSpy).toHaveBeenCalledWith( 'Multiple URLs found in uri-list content. Using the first URL: "https://example.com/first". Other URLs ignored:', - ['https://example.com/second', 'https://example.com/third'] + ['https://example.com/second', 'https://example.com/third'], ); consoleWarnSpy.mockRestore(); }); @@ -167,11 +173,15 @@ https://example.com/backup onUiAction: mockOnUiAction, }; render(); - expect(screen.getByText('No valid URLs found in uri-list content.')).toBeInTheDocument(); + expect( + screen.getByText('No valid URLs found in uri-list content.'), + ).toBeInTheDocument(); }); it('supports backwards compatibility with ui-app:// URI scheme', () => { - const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const consoleWarnSpy = vi + .spyOn(console, 'warn') + .mockImplementation(() => {}); const props: RenderHtmlResourceProps = { resource: { uri: 'ui-app://legacy-external-app', @@ -186,13 +196,15 @@ https://example.com/backup ) as HTMLIFrameElement; expect(iframe.src).toBe('https://legacy.example.com/app'); expect(consoleWarnSpy).toHaveBeenCalledWith( - 'Detected legacy ui-app:// URI: "ui-app://legacy-external-app". Update server to use ui:// with mimeType: \'text/uri-list\' for future compatibility.' + 'Detected legacy ui-app:// URI: "ui-app://legacy-external-app". Update server to use ui:// with mimeType: \'text/uri-list\' for future compatibility.', ); consoleWarnSpy.mockRestore(); }); it('handles legacy ui-app:// with blob content', () => { - const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const consoleWarnSpy = vi + .spyOn(console, 'warn') + .mockImplementation(() => {}); const url = 'https://legacy.example.com/blob-app'; const encodedUrl = Buffer.from(url).toString('base64'); const props: RenderHtmlResourceProps = { @@ -209,7 +221,7 @@ https://example.com/backup ) as HTMLIFrameElement; expect(iframe.src).toBe(url); expect(consoleWarnSpy).toHaveBeenCalledWith( - 'Detected legacy ui-app:// URI: "ui-app://legacy-blob-app". Update server to use ui:// with mimeType: \'text/uri-list\' for future compatibility.' + 'Detected legacy ui-app:// URI: "ui-app://legacy-blob-app". Update server to use ui:// with mimeType: \'text/uri-list\' for future compatibility.', ); consoleWarnSpy.mockRestore(); }); @@ -235,7 +247,7 @@ const dispatchMessage = ( }; describe('HtmlResource - onUiAction', () => { - let mockOnUiAction: Mock<[string, Record], Promise>; + let mockOnUiAction: Mock<[UiActionResult], Promise>; let consoleErrorSpy: MockInstance, void>; beforeEach(() => { @@ -247,7 +259,9 @@ describe('HtmlResource - onUiAction', () => { consoleErrorSpy.mockRestore(); }); - const renderComponentForUiActionTests = (props: Partial = {}) => { + const renderComponentForUiActionTests = ( + props: Partial = {}, + ) => { return render( { 'MCP HTML Resource (Embedded Content)', )) as HTMLIFrameElement; - const eventData = { tool: 'testTool', params: { foo: 'bar' } }; + const eventData = { + type: 'tool', + payload: { toolName: 'testTool', params: { foo: 'bar' } }, + }; dispatchMessage(iframe.contentWindow, eventData); expect(mockOnUiAction).toHaveBeenCalledTimes(1); - expect(mockOnUiAction).toHaveBeenCalledWith('testTool', { foo: 'bar' }); + expect(mockOnUiAction).toHaveBeenCalledWith(eventData); }); it('should use empty params if event.data.params is missing', async () => { @@ -275,12 +292,15 @@ describe('HtmlResource - onUiAction', () => { const iframe = (await screen.findByTitle( 'MCP HTML Resource (Embedded Content)', )) as HTMLIFrameElement; - - const eventData = { tool: 'testTool' }; // No params + + const eventData = { + type: 'tool', + payload: { toolName: 'testTool' }, + }; // No params dispatchMessage(iframe.contentWindow, eventData); expect(mockOnUiAction).toHaveBeenCalledTimes(1); - expect(mockOnUiAction).toHaveBeenCalledWith('testTool', {}); + expect(mockOnUiAction).toHaveBeenCalledWith(eventData); }); it('should not call onUiAction if the message event is not from the iframe', async () => { @@ -288,24 +308,15 @@ describe('HtmlResource - onUiAction', () => { // Ensure iframe is rendered before dispatching an event from the wrong source await screen.findByTitle('MCP HTML Resource (Embedded Content)'); - const eventData = { tool: 'testTool', params: { foo: 'bar' } }; + const eventData = { + type: 'tool', + payload: { toolName: 'testTool', params: { foo: 'bar' } }, + }; dispatchMessage(window, eventData); // Source is the main window expect(mockOnUiAction).not.toHaveBeenCalled(); }); - it('should not call onUiAction if event.data.tool is missing', async () => { - renderComponentForUiActionTests(); - const iframe = (await screen.findByTitle( - 'MCP HTML Resource (Embedded Content)', - )) as HTMLIFrameElement; - - const eventData = { params: { foo: 'bar' } }; // Missing 'tool' - dispatchMessage(iframe.contentWindow, eventData); - - expect(mockOnUiAction).not.toHaveBeenCalled(); - }); - it('should not call onUiAction if event.data is null', async () => { renderComponentForUiActionTests(); const iframe = (await screen.findByTitle( @@ -316,28 +327,36 @@ describe('HtmlResource - onUiAction', () => { expect(mockOnUiAction).not.toHaveBeenCalled(); }); - + it('should work correctly and not throw if onUiAction is undefined', async () => { // Pass undefined directly to onUiAction for this specific test - renderComponentForUiActionTests({ onUiAction: undefined, resource: mockResourceBaseForUiActionTests }); + renderComponentForUiActionTests({ + onUiAction: undefined, + resource: mockResourceBaseForUiActionTests, + }); const iframe = (await screen.findByTitle( 'MCP HTML Resource (Embedded Content)', )) as HTMLIFrameElement; const eventData = { tool: 'testTool', params: { foo: 'bar' } }; - + expect(() => { dispatchMessage(iframe.contentWindow, eventData); }).not.toThrow(); // mockOnUiAction (the one from the describe block scope) should not be called // as it was effectively replaced by 'undefined' for this render. - expect(mockOnUiAction).not.toHaveBeenCalled(); + expect(mockOnUiAction).not.toHaveBeenCalled(); }); it('should log an error if onUiAction returns a rejected promise', async () => { const errorMessage = 'Async action failed'; - const specificMockForThisTest = vi.fn<[string, Record], Promise>().mockRejectedValue(new Error(errorMessage)); - renderComponentForUiActionTests({ onUiAction: specificMockForThisTest, resource: mockResourceBaseForUiActionTests }); + const specificMockForThisTest = vi + .fn<[UiActionResult], Promise>() + .mockRejectedValue(new Error(errorMessage)); + renderComponentForUiActionTests({ + onUiAction: specificMockForThisTest, + resource: mockResourceBaseForUiActionTests, + }); const iframe = (await screen.findByTitle( 'MCP HTML Resource (Embedded Content)', @@ -350,7 +369,7 @@ describe('HtmlResource - onUiAction', () => { }); await waitFor(() => { expect(consoleErrorSpy).toHaveBeenCalledWith( - 'Error from onUiAction in RenderHtmlResource:', + 'Error handling UI action result in RenderHtmlResource:', expect.objectContaining({ message: errorMessage }), ); }); @@ -358,21 +377,27 @@ describe('HtmlResource - onUiAction', () => { it('should not attempt to call onUiAction if iframeRef.current is null (e.g. resource error)', async () => { // Render with a resource that will cause an error and prevent iframe rendering - const localMockOnUiAction = vi.fn<[string, Record], Promise>(); + const localMockOnUiAction = vi.fn<[UiActionResult], Promise>(); render( - + onUiAction={localMockOnUiAction} + />, ); // Iframe should not be present - expect(screen.queryByTitle('MCP HTML Resource (Embedded Content)')).not.toBeInTheDocument(); + expect( + screen.queryByTitle('MCP HTML Resource (Embedded Content)'), + ).not.toBeInTheDocument(); // Error message should be displayed - expect(await screen.findByText('Resource must be of type text/html (for HTML content) or text/uri-list (for URL content).')).toBeInTheDocument(); - + expect( + await screen.findByText( + 'Resource must be of type text/html (for HTML content) or text/uri-list (for URL content).', + ), + ).toBeInTheDocument(); + const eventData = { tool: 'testTool', params: { foo: 'bar' } }; - dispatchMessage(window, eventData); + dispatchMessage(window, eventData); expect(localMockOnUiAction).not.toHaveBeenCalled(); expect(mockOnUiAction).not.toHaveBeenCalled(); // also check the describe-scoped one diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index b7d9091f..5b386237 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -1,2 +1,8 @@ export * from './components/HtmlResource'; +export type { + UiActionResult, + UiActionResultToolCall, + UiActionResultPrompt, + UiActionResultLink, +} from './types'; // export { useMcpClient } from './context/McpClientContext'; // If you create this diff --git a/packages/client/src/types.ts b/packages/client/src/types.ts new file mode 100644 index 00000000..f5b11825 --- /dev/null +++ b/packages/client/src/types.ts @@ -0,0 +1,45 @@ +export type UiActionType = 'tool' | 'prompt' | 'link' | 'intent' | 'notification'; + +export type UiActionResultToolCall = { + type: 'tool'; + payload: { + toolName: string; + params: Record; + }; +}; + +export type UiActionResultPrompt = { + type: 'prompt'; + payload: { + prompt: string; + }; +}; + +export type UiActionResultLink = { + type: 'link'; + payload: { + url: string; + }; +}; + +export type UiActionResultIntent = { + type: 'intent'; + payload: { + intent: string; + params: Record; + }; +}; + +export type UiActionResultNotification = { + type: 'notification'; + payload: { + message: string; + }; +}; + +export type UiActionResult = + | UiActionResultToolCall + | UiActionResultPrompt + | UiActionResultLink + | UiActionResultIntent + | UiActionResultNotification; diff --git a/packages/server/README.md b/packages/server/README.md index 44c639fb..6db63a0d 100644 --- a/packages/server/README.md +++ b/packages/server/README.md @@ -115,8 +115,8 @@ yarn add @mcp-ui/server @mcp-ui/client return ( { - console.log('Action:', tool, params); + onUiAction={(result) => { + console.log('Action:', result); return { status: 'ok' }; }} /> diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index b69eb0b1..21f2f46f 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -4,7 +4,15 @@ */ // Import types first -import { CreateHtmlResourceOptions } from './types.js'; +import { + CreateHtmlResourceOptions, + UiActionResult, + UiActionResultLink, + UiActionResultNotification, + UiActionResultPrompt, + UiActionResultIntent, + UiActionResultToolCall, +} from './types.js'; export interface HtmlResourceBlock { type: 'resource'; @@ -136,4 +144,68 @@ export function escapeAttribute(unsafe: string): string { export type { CreateHtmlResourceOptions as CreateResourceOptions, ResourceContentPayload, + UiActionResult, } from './types.js'; + +export function postUiActionResult(result: UiActionResult): void { + if (window.parent) { + window.parent.postMessage(result, '*'); + } +} + +export function uiActionResultToolCall( + toolName: string, + params: Record, +): UiActionResultToolCall { + return { + type: 'tool', + payload: { + toolName, + params, + }, + }; +} + +export function uiActionResultPrompt( + prompt: string, +): UiActionResultPrompt { + return { + type: 'prompt', + payload: { + prompt, + }, + }; +} + +export function uiActionResultLink(url: string): UiActionResultLink { + return { + type: 'link', + payload: { + url, + }, + }; +} + +export function uiActionResultIntent( + intent: string, + params: Record, +): UiActionResultIntent { + return { + type: 'intent', + payload: { + intent, + params, + }, + }; +} + +export function uiActionResultNotification( + message: string, +): UiActionResultNotification { + return { + type: 'notification', + payload: { + message, + }, + }; +} \ No newline at end of file diff --git a/packages/server/src/types.ts b/packages/server/src/types.ts index 893f216c..e007ccf0 100644 --- a/packages/server/src/types.ts +++ b/packages/server/src/types.ts @@ -8,3 +8,49 @@ export interface CreateHtmlResourceOptions { content: ResourceContentPayload; // REQUIRED. The actual content payload. delivery: 'text' | 'blob'; // REQUIRED. How the content string (htmlString or iframeUrl) should be packaged. } + +export type UiActionType = 'tool' | 'prompt' | 'link' | 'intent' | 'notification'; + +export type UiActionResultToolCall = { + type: 'tool'; + payload: { + toolName: string; + params: Record; + }; +}; + +export type UiActionResultPrompt = { + type: 'prompt'; + payload: { + prompt: string; + }; +}; + +export type UiActionResultLink = { + type: 'link'; + payload: { + url: string; + }; +}; + +export type UiActionResultIntent = { + type: 'intent'; + payload: { + intent: string; + params: Record; + }; +}; + +export type UiActionResultNotification = { + type: 'notification'; + payload: { + message: string; + }; +}; + +export type UiActionResult = + | UiActionResultToolCall + | UiActionResultPrompt + | UiActionResultLink + | UiActionResultIntent + | UiActionResultNotification; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3e468189..21684786 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,7 +33,7 @@ importers: specifier: ^29.5.11 version: 29.5.14 '@types/node': - specifier: ^22.0.0 + specifier: ^22.15.18 version: 22.15.18 '@typescript-eslint/eslint-plugin': specifier: ^6.13.2