@ably/ai-transport 0.0.1 → 0.1.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/README.md +54 -47
- package/dist/ably-ai-transport.js +1006 -539
- package/dist/ably-ai-transport.js.map +1 -1
- package/dist/ably-ai-transport.umd.cjs +1 -1
- package/dist/ably-ai-transport.umd.cjs.map +1 -1
- package/dist/constants.d.ts +4 -0
- package/dist/core/codec/types.d.ts +19 -2
- package/dist/core/transport/decode-history.d.ts +8 -6
- package/dist/core/transport/headers.d.ts +4 -2
- package/dist/core/transport/index.d.ts +4 -1
- package/dist/core/transport/pipe-stream.d.ts +3 -2
- package/dist/core/transport/stream-router.d.ts +11 -1
- package/dist/core/transport/tree.d.ts +171 -0
- package/dist/core/transport/turn-manager.d.ts +4 -1
- package/dist/core/transport/types.d.ts +270 -119
- package/dist/core/transport/view.d.ts +166 -0
- package/dist/errors.d.ts +19 -2
- package/dist/index.d.ts +3 -1
- package/dist/react/ably-ai-transport-react.js +1019 -486
- package/dist/react/ably-ai-transport-react.js.map +1 -1
- package/dist/react/ably-ai-transport-react.umd.cjs +1 -1
- package/dist/react/ably-ai-transport-react.umd.cjs.map +1 -1
- package/dist/react/contexts/transport-context.d.ts +31 -0
- package/dist/react/contexts/transport-provider.d.ts +49 -0
- package/dist/react/create-transport-hooks.d.ts +124 -0
- package/dist/react/index.d.ts +14 -8
- package/dist/react/use-ably-messages.d.ts +14 -8
- package/dist/react/use-active-turns.d.ts +7 -3
- package/dist/react/use-client-transport.d.ts +78 -5
- package/dist/react/use-create-view.d.ts +22 -0
- package/dist/react/use-tree.d.ts +20 -0
- package/dist/react/use-view.d.ts +79 -0
- package/dist/vercel/ably-ai-transport-vercel.js +1478 -842
- package/dist/vercel/ably-ai-transport-vercel.js.map +1 -1
- package/dist/vercel/ably-ai-transport-vercel.umd.cjs +1 -1
- package/dist/vercel/ably-ai-transport-vercel.umd.cjs.map +1 -1
- package/dist/vercel/codec/tool-transitions.d.ts +50 -0
- package/dist/vercel/index.d.ts +3 -0
- package/dist/vercel/react/ably-ai-transport-vercel-react.js +9099 -852
- package/dist/vercel/react/ably-ai-transport-vercel-react.js.map +1 -1
- package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs +45 -1
- package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs.map +1 -1
- package/dist/vercel/react/contexts/chat-transport-context.d.ts +32 -0
- package/dist/vercel/react/contexts/chat-transport-provider.d.ts +84 -0
- package/dist/vercel/react/index.d.ts +5 -0
- package/dist/vercel/react/use-chat-transport.d.ts +61 -20
- package/dist/vercel/react/use-message-sync.d.ts +41 -9
- package/dist/vercel/react/use-staged-add-tool-approval-response.d.ts +30 -0
- package/dist/vercel/tool-approvals.d.ts +124 -0
- package/dist/vercel/tool-events.d.ts +26 -0
- package/dist/vercel/transport/chat-transport.d.ts +33 -11
- package/dist/vercel/transport/index.d.ts +5 -2
- package/package.json +23 -17
- package/src/constants.ts +6 -0
- package/src/core/codec/encoder.ts +10 -1
- package/src/core/codec/types.ts +19 -3
- package/src/core/transport/client-transport.ts +382 -364
- package/src/core/transport/decode-history.ts +229 -81
- package/src/core/transport/headers.ts +6 -2
- package/src/core/transport/index.ts +13 -5
- package/src/core/transport/pipe-stream.ts +8 -5
- package/src/core/transport/server-transport.ts +212 -58
- package/src/core/transport/stream-router.ts +21 -3
- package/src/core/transport/{conversation-tree.ts → tree.ts} +192 -77
- package/src/core/transport/turn-manager.ts +28 -10
- package/src/core/transport/types.ts +318 -139
- package/src/core/transport/view.ts +840 -0
- package/src/errors.ts +21 -1
- package/src/index.ts +10 -5
- package/src/react/contexts/transport-context.ts +37 -0
- package/src/react/contexts/transport-provider.tsx +164 -0
- package/src/react/create-transport-hooks.ts +144 -0
- package/src/react/index.ts +15 -8
- package/src/react/use-ably-messages.ts +34 -16
- package/src/react/use-active-turns.ts +28 -17
- package/src/react/use-client-transport.ts +184 -24
- package/src/react/use-create-view.ts +68 -0
- package/src/react/use-tree.ts +53 -0
- package/src/react/use-view.ts +233 -0
- package/src/react/vite.config.ts +4 -1
- package/src/vercel/codec/accumulator.ts +64 -79
- package/src/vercel/codec/decoder.ts +11 -8
- package/src/vercel/codec/encoder.ts +68 -54
- package/src/vercel/codec/index.ts +0 -2
- package/src/vercel/codec/tool-transitions.ts +122 -0
- package/src/vercel/index.ts +17 -0
- package/src/vercel/react/contexts/chat-transport-context.ts +40 -0
- package/src/vercel/react/contexts/chat-transport-provider.tsx +122 -0
- package/src/vercel/react/index.ts +14 -0
- package/src/vercel/react/use-chat-transport.ts +164 -42
- package/src/vercel/react/use-message-sync.ts +77 -19
- package/src/vercel/react/use-staged-add-tool-approval-response.ts +87 -0
- package/src/vercel/react/vite.config.ts +4 -2
- package/src/vercel/tool-approvals.ts +380 -0
- package/src/vercel/tool-events.ts +53 -0
- package/src/vercel/transport/chat-transport.ts +225 -79
- package/src/vercel/transport/index.ts +14 -3
- package/dist/core/transport/conversation-tree.d.ts +0 -9
- package/dist/react/use-conversation-tree.d.ts +0 -20
- package/dist/react/use-edit.d.ts +0 -7
- package/dist/react/use-history.d.ts +0 -19
- package/dist/react/use-messages.d.ts +0 -7
- package/dist/react/use-regenerate.d.ts +0 -7
- package/dist/react/use-send.d.ts +0 -7
- package/src/react/use-conversation-tree.ts +0 -71
- package/src/react/use-edit.ts +0 -24
- package/src/react/use-history.ts +0 -111
- package/src/react/use-messages.ts +0 -32
- package/src/react/use-regenerate.ts +0 -24
- package/src/react/use-send.ts +0 -25
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ClientTransport } from '../../../core/transport/types.js';
|
|
2
|
+
import { ChatTransport } from '../../transport/chat-transport.js';
|
|
3
|
+
import type * as Ably from 'ably';
|
|
4
|
+
import type * as AI from 'ai';
|
|
5
|
+
/**
|
|
6
|
+
* A single entry in the chat transport registry, holding both the
|
|
7
|
+
* underlying {@link ClientTransport} and the {@link ChatTransport} wrapping it.
|
|
8
|
+
*/
|
|
9
|
+
export interface ChatTransportSlot {
|
|
10
|
+
/** The underlying client transport used to create the chat transport. */
|
|
11
|
+
readonly transport: ClientTransport<AI.UIMessageChunk, AI.UIMessage>;
|
|
12
|
+
/** Construction error from the underlying {@link ClientTransport}, or `undefined` on success. */
|
|
13
|
+
readonly transportError: Ably.ErrorInfo | undefined;
|
|
14
|
+
/** The chat transport adapter for use with Vercel's useChat hook. */
|
|
15
|
+
readonly chatTransport: ChatTransport;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The shape of the single {@link ChatTransportContext} value.
|
|
19
|
+
* Combines the nearest slot with the full registry in one context object.
|
|
20
|
+
*/
|
|
21
|
+
export interface ChatTransportContextValue {
|
|
22
|
+
/** The slot from the nearest {@link ChatTransportProvider} in the tree. */
|
|
23
|
+
readonly nearest: ChatTransportSlot | undefined;
|
|
24
|
+
/** All registered slots, keyed by channelName. */
|
|
25
|
+
readonly providers: Readonly<Record<string, ChatTransportSlot>>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Context that carries both the nearest {@link ChatTransportSlot} and the full registry of
|
|
29
|
+
* registered slots keyed by channelName. Populated by {@link ChatTransportProvider};
|
|
30
|
+
* read by {@link useChatTransport}.
|
|
31
|
+
*/
|
|
32
|
+
export declare const ChatTransportContext: import('react').Context<ChatTransportContextValue>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { PropsWithChildren, ReactNode } from 'react';
|
|
2
|
+
import { TransportProviderProps } from '../../../react/index.js';
|
|
3
|
+
import { ChatTransportOptions } from '../../transport/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* ChatTransportProvider: creates a ChatTransport from a ClientTransport and makes it
|
|
6
|
+
* available to descendants via ChatTransportContext.
|
|
7
|
+
*
|
|
8
|
+
* Wraps children with TransportProvider (using UIMessageCodec) so the Ably channel
|
|
9
|
+
* lifecycle is managed in one place. An inner component reads the ClientTransport
|
|
10
|
+
* from NearestTransportContext and creates the ChatTransport once on first render
|
|
11
|
+
* (via useRef).
|
|
12
|
+
*
|
|
13
|
+
* The ChatTransport is NOT closed on unmount — the underlying ClientTransport
|
|
14
|
+
* lifecycle is managed by the wrapping TransportProvider. Auto-closing would break
|
|
15
|
+
* React Strict Mode, and ChatTransport.close() delegates to ClientTransport.close()
|
|
16
|
+
* which TransportProvider already calls.
|
|
17
|
+
*
|
|
18
|
+
* Multiple ChatTransportProviders can be nested using distinct channelNames.
|
|
19
|
+
* Each provider merges its transport into the parent registry, so descendants
|
|
20
|
+
* can access all registered transports via useChatTransport({ channelName }).
|
|
21
|
+
*/
|
|
22
|
+
import type * as AI from 'ai';
|
|
23
|
+
export declare const TransportProvider: import('react').ComponentType<TransportProviderProps<AI.UIMessageChunk, AI.UIMessage<unknown, AI.UIDataTypes, AI.UITools>>>, useAblyMessages: (props?: {
|
|
24
|
+
transport?: import('../../../index.js').ClientTransport<AI.UIMessageChunk, AI.UIMessage<unknown, AI.UIDataTypes, AI.UITools>> | undefined;
|
|
25
|
+
skip?: boolean;
|
|
26
|
+
} | undefined) => import('ably').InboundMessage[], useActiveTurns: (props?: {
|
|
27
|
+
transport?: import('../../../index.js').ClientTransport<AI.UIMessageChunk, AI.UIMessage<unknown, AI.UIDataTypes, AI.UITools>> | null | undefined;
|
|
28
|
+
} | undefined) => Map<string, Set<string>>, useClientTransport: (props?: {
|
|
29
|
+
channelName?: string;
|
|
30
|
+
skip?: boolean;
|
|
31
|
+
onError?: (error: import('ably').ErrorInfo) => void;
|
|
32
|
+
}) => import('../../../react/use-client-transport.js').ClientTransportHandle<AI.UIMessageChunk, AI.UIMessage<unknown, AI.UIDataTypes, AI.UITools>>, useCreateView: (props?: {
|
|
33
|
+
transport?: import('../../../index.js').ClientTransport<AI.UIMessageChunk, AI.UIMessage<unknown, AI.UIDataTypes, AI.UITools>> | null | undefined;
|
|
34
|
+
limit?: number;
|
|
35
|
+
skip?: boolean;
|
|
36
|
+
} | undefined) => import('../../../react/use-view.js').ViewHandle<AI.UIMessageChunk, AI.UIMessage<unknown, AI.UIDataTypes, AI.UITools>>, useTree: (props?: {
|
|
37
|
+
transport?: import('../../../index.js').ClientTransport<AI.UIMessageChunk, AI.UIMessage<unknown, AI.UIDataTypes, AI.UITools>> | undefined;
|
|
38
|
+
} | undefined) => import('../../../react/use-tree.js').TreeHandle<AI.UIMessage<unknown, AI.UIDataTypes, AI.UITools>>, useView: (props?: {
|
|
39
|
+
transport?: import('../../../index.js').ClientTransport<AI.UIMessageChunk, AI.UIMessage<unknown, AI.UIDataTypes, AI.UITools>> | null | undefined;
|
|
40
|
+
view?: import('../../../index.js').View<AI.UIMessageChunk, AI.UIMessage<unknown, AI.UIDataTypes, AI.UITools>> | null | undefined;
|
|
41
|
+
limit?: number;
|
|
42
|
+
skip?: boolean;
|
|
43
|
+
} | undefined) => import('../../../react/use-view.js').ViewHandle<AI.UIMessageChunk, AI.UIMessage<unknown, AI.UIDataTypes, AI.UITools>>;
|
|
44
|
+
type CoreTransportProviderProps = Omit<TransportProviderProps<AI.UIMessageChunk, AI.UIMessage>, 'codec' | 'api'> & Partial<Pick<TransportProviderProps<AI.UIMessageChunk, AI.UIMessage>, 'api'>>;
|
|
45
|
+
/**
|
|
46
|
+
* Props for {@link ChatTransportProvider}.
|
|
47
|
+
*
|
|
48
|
+
* All {@link TransportProviderProps} for Vercel types except `codec` (baked as UIMessageCodec),
|
|
49
|
+
* plus `chatOptions` for customizing chat request construction.
|
|
50
|
+
*/
|
|
51
|
+
export interface ChatTransportProviderProps extends CoreTransportProviderProps {
|
|
52
|
+
/**
|
|
53
|
+
* Optional hooks for customizing chat request construction (e.g. prepareSendMessagesRequest).
|
|
54
|
+
* Must be stable across renders — wrap in `useMemo` or define outside the component.
|
|
55
|
+
* A new object reference triggers ChatTransport recreation.
|
|
56
|
+
*/
|
|
57
|
+
chatOptions?: ChatTransportOptions;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Provide a {@link ChatTransport} and its underlying {@link ClientTransport} to descendant components.
|
|
61
|
+
*
|
|
62
|
+
* Wraps children with Ably's `ChannelProvider` (via `TransportProvider`) using `channelName`,
|
|
63
|
+
* creates a {@link ClientTransport} with UIMessageCodec, wraps it in a {@link ChatTransport},
|
|
64
|
+
* and registers the full slot in `ChatTransportContext` under `channelName`. Descendants call
|
|
65
|
+
* {@link useChatTransport} with the same `channelName` to access both transports.
|
|
66
|
+
*
|
|
67
|
+
* `useClientTransport` is also available inside this provider's subtree.
|
|
68
|
+
*
|
|
69
|
+
* ```tsx
|
|
70
|
+
* <ChatTransportProvider channelName="ai:demo">
|
|
71
|
+
* <Chat />
|
|
72
|
+
* </ChatTransportProvider>
|
|
73
|
+
*
|
|
74
|
+
* // Inside Chat:
|
|
75
|
+
* const { chatTransport, transport } = useChatTransport();
|
|
76
|
+
* const { transport } = useClientTransport(); // also available
|
|
77
|
+
* ```
|
|
78
|
+
* @param props - Provider configuration including `channelName`, optional `chatOptions`, and all other transport options.
|
|
79
|
+
* @param props.chatOptions - Optional hooks for customizing chat request construction. Must be stable (memoized) — a new reference recreates the ChatTransport.
|
|
80
|
+
* @param props.children - Descendant components that consume the transport via hooks.
|
|
81
|
+
* @returns A React element wrapping children with ChannelProvider, TransportContext, and ChatTransportContext.
|
|
82
|
+
*/
|
|
83
|
+
export declare const ChatTransportProvider: ({ chatOptions, children, ...transportProps }: ChatTransportProviderProps & PropsWithChildren) => ReactNode;
|
|
84
|
+
export {};
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
export type { ChatTransport } from '../transport/chat-transport.js';
|
|
2
|
+
export type { ChatTransportProviderProps } from './contexts/chat-transport-provider.js';
|
|
3
|
+
export { ChatTransportProvider, TransportProvider, useAblyMessages, useActiveTurns, useClientTransport, useCreateView, useTree, useView, } from './contexts/chat-transport-provider.js';
|
|
4
|
+
export type { ChatTransportHandle, UseChatTransportOptions } from './use-chat-transport.js';
|
|
2
5
|
export { useChatTransport } from './use-chat-transport.js';
|
|
6
|
+
export type { UseMessageSyncOptions } from './use-message-sync.js';
|
|
3
7
|
export { useMessageSync } from './use-message-sync.js';
|
|
8
|
+
export { useStagedAddToolApprovalResponse } from './use-staged-add-tool-approval-response.js';
|
|
@@ -1,29 +1,70 @@
|
|
|
1
1
|
import { ClientTransport } from '../../core/transport/types.js';
|
|
2
|
-
import { ChatTransport
|
|
3
|
-
import { VercelClientTransportOptions } from '../transport/index.js';
|
|
2
|
+
import { ChatTransport } from '../transport/index.js';
|
|
4
3
|
/**
|
|
5
|
-
* useChatTransport:
|
|
6
|
-
*
|
|
4
|
+
* useChatTransport: reads a ChatTransport and its underlying ClientTransport from
|
|
5
|
+
* the nearest ChatTransportProvider.
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* The transport is created by ChatTransportProvider, which also wraps the subtree
|
|
8
|
+
* with TransportProvider and Ably's ChannelProvider. This hook is a thin context
|
|
9
|
+
* reader — it does not create or manage any transport state.
|
|
11
10
|
*
|
|
12
|
-
*
|
|
13
|
-
* (e.g.
|
|
14
|
-
*
|
|
15
|
-
* The hook does NOT auto-close the transport on unmount. Channel lifecycle is
|
|
16
|
-
* managed by the Ably provider (useChannel). Auto-closing would break React
|
|
17
|
-
* Strict Mode. Call chatTransport.close() explicitly if needed.
|
|
11
|
+
* Pass `channelName` to look up a specific provider by name. Omit to use the nearest
|
|
12
|
+
* provider in the tree. Pass `skip: true` to defer (e.g. when auth is not yet resolved)
|
|
13
|
+
* — returns stub transports whose properties throw with a descriptive error.
|
|
18
14
|
*/
|
|
15
|
+
import * as Ably from 'ably';
|
|
19
16
|
import type * as AI from 'ai';
|
|
17
|
+
/** Options for {@link useChatTransport}. */
|
|
18
|
+
export interface UseChatTransportOptions {
|
|
19
|
+
/** Channel name to look up; omit to use the nearest {@link ChatTransportProvider}. */
|
|
20
|
+
channelName?: string;
|
|
21
|
+
/** When `true`, return stub transports that throw on any access. */
|
|
22
|
+
skip?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The value returned by {@link useChatTransport}.
|
|
26
|
+
* Provides both the underlying {@link ClientTransport} and the {@link ChatTransport}
|
|
27
|
+
* adapter for Vercel's useChat hook.
|
|
28
|
+
*/
|
|
29
|
+
export interface ChatTransportHandle {
|
|
30
|
+
/**
|
|
31
|
+
* The underlying client transport, also available via {@link useClientTransport}.
|
|
32
|
+
* A throwing stub when `skip` is `true`, when no matching {@link TransportProvider}
|
|
33
|
+
* was found in the tree, or when transport construction failed. Check `transportError` before use.
|
|
34
|
+
*/
|
|
35
|
+
transport: ClientTransport<AI.UIMessageChunk, AI.UIMessage>;
|
|
36
|
+
/**
|
|
37
|
+
* The chat transport adapter for use with Vercel's `useChat` hook.
|
|
38
|
+
*
|
|
39
|
+
* A throwing stub when `skip` is `true`, when no matching
|
|
40
|
+
* {@link ChatTransportProvider} was found in the tree, or when the underlying
|
|
41
|
+
* {@link ClientTransport} construction failed. Check both `chatTransportError`
|
|
42
|
+
* and `transportError` before use.
|
|
43
|
+
*/
|
|
44
|
+
chatTransport: ChatTransport;
|
|
45
|
+
/**
|
|
46
|
+
* Set when no matching {@link TransportProvider} was found, when transport
|
|
47
|
+
* construction failed, and `skip` is `false`.
|
|
48
|
+
* `undefined` when the transport resolved successfully or when `skip` is `true`.
|
|
49
|
+
*/
|
|
50
|
+
transportError?: Ably.ErrorInfo | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* Set when no matching {@link ChatTransportProvider} was found or when transport
|
|
53
|
+
* construction failed, and `skip` is `false`.
|
|
54
|
+
* `undefined` when the transport resolved successfully or when `skip` is `true`.
|
|
55
|
+
*/
|
|
56
|
+
chatTransportError?: Ably.ErrorInfo | undefined;
|
|
57
|
+
}
|
|
20
58
|
/**
|
|
21
|
-
*
|
|
59
|
+
* Access a {@link ChatTransport} and {@link ClientTransport} from the nearest {@link ChatTransportProvider}.
|
|
22
60
|
*
|
|
23
|
-
*
|
|
24
|
-
* `
|
|
25
|
-
* @
|
|
26
|
-
*
|
|
27
|
-
* @
|
|
61
|
+
* When `channelName` is omitted, the innermost `ChatTransportProvider` in the tree is used.
|
|
62
|
+
* When `skip` is `true`, returns stub transports whose every property and method throws
|
|
63
|
+
* an {@link Ably.ErrorInfo} — safe to hold in state before conditions are ready.
|
|
64
|
+
* When no provider is found, returns stubs with `chatTransportError` set instead of throwing.
|
|
65
|
+
* @param props - Options for selecting the transport.
|
|
66
|
+
* @param props.channelName - The channel name passed to the enclosing `ChatTransportProvider`. Omit to use the nearest.
|
|
67
|
+
* @param props.skip - When `true`, return stubs that throw on any access instead of reading from context.
|
|
68
|
+
* @returns The `ChatTransportHandle` containing both the chat transport adapter and the underlying client transport.
|
|
28
69
|
*/
|
|
29
|
-
export declare const useChatTransport: (
|
|
70
|
+
export declare const useChatTransport: ({ channelName, skip }?: UseChatTransportOptions) => ChatTransportHandle;
|
|
@@ -1,19 +1,51 @@
|
|
|
1
|
-
import { ClientTransport } from '../../core/transport/types.js';
|
|
2
1
|
/**
|
|
3
2
|
* useMessageSync: wires transport message lifecycle events into useChat's setMessages.
|
|
4
3
|
*
|
|
5
|
-
* Subscribes to the transport's '
|
|
6
|
-
* with the
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Subscribes to the transport view's 'update' event and replaces messages state
|
|
5
|
+
* with the view's authoritative message list.
|
|
6
|
+
*
|
|
7
|
+
* When a ChatTransport is provided (resolved from the nearest ChatTransportProvider),
|
|
8
|
+
* setMessages calls are gated during active own-turn streams. This prevents the
|
|
9
|
+
* push/replace ID mismatch in useChat's write() function. When the stream finishes,
|
|
10
|
+
* the gate opens and an immediate sync fires to pick up any observer messages that
|
|
11
|
+
* arrived during the stream.
|
|
12
|
+
*
|
|
13
|
+
* All dependencies are resolved from the nearest ChatTransportProvider via
|
|
14
|
+
* useChatTransport(). Pass channelName to select a specific provider; omit to use
|
|
15
|
+
* the nearest. Pass skip: true to pause all subscriptions.
|
|
9
16
|
*
|
|
10
17
|
* Returns the unsubscribe function in the useEffect cleanup so handlers
|
|
11
18
|
* are removed on unmount or when dependencies change.
|
|
12
19
|
*/
|
|
13
20
|
import type * as AI from 'ai';
|
|
21
|
+
/** Options for {@link useMessageSync}. */
|
|
22
|
+
export interface UseMessageSyncOptions {
|
|
23
|
+
/**
|
|
24
|
+
* The `setMessages` updater function from `useChat()`. Required.
|
|
25
|
+
* Called with a function that replaces the previous message list with the
|
|
26
|
+
* transport's current authoritative message list.
|
|
27
|
+
*/
|
|
28
|
+
setMessages: (updater: (prev: AI.UIMessage[]) => AI.UIMessage[]) => void;
|
|
29
|
+
/**
|
|
30
|
+
* Channel name of the {@link ChatTransportProvider} to observe.
|
|
31
|
+
* Omit to use the nearest provider in the tree.
|
|
32
|
+
*/
|
|
33
|
+
channelName?: string;
|
|
34
|
+
/**
|
|
35
|
+
* When `true`, skip all subscriptions and do nothing.
|
|
36
|
+
* Use when the hook's dependencies are not yet resolved (e.g. auth pending).
|
|
37
|
+
*/
|
|
38
|
+
skip?: boolean;
|
|
39
|
+
}
|
|
14
40
|
/**
|
|
15
|
-
* Wire transport message updates into useChat's `setMessages` updater.
|
|
16
|
-
*
|
|
17
|
-
*
|
|
41
|
+
* Wire transport message updates into `useChat()`'s `setMessages` updater.
|
|
42
|
+
*
|
|
43
|
+
* Resolves both the transport view and the streaming gate from the nearest
|
|
44
|
+
* `ChatTransportProvider`. Pass `channelName` to target a specific provider.
|
|
45
|
+
* Pass `skip: true` to pause all subscriptions.
|
|
46
|
+
* @param options - Hook options.
|
|
47
|
+
* @param options.setMessages - The `setMessages` function from `useChat()`. Required.
|
|
48
|
+
* @param options.channelName - Channel name of the provider to observe; defaults to nearest.
|
|
49
|
+
* @param options.skip - When `true`, skip all subscriptions.
|
|
18
50
|
*/
|
|
19
|
-
export declare const useMessageSync: (
|
|
51
|
+
export declare const useMessageSync: ({ setMessages, channelName, skip }: UseMessageSyncOptions) => void;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ChatAddToolApproveResponseFunction } from 'ai';
|
|
2
|
+
import { ClientTransport } from '../../core/transport/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* useStagedAddToolApprovalResponse — wrap useChat's `addToolApprovalResponse`
|
|
5
|
+
* so the approval response is also applied to the transport tree
|
|
6
|
+
* synchronously at click time.
|
|
7
|
+
*
|
|
8
|
+
* Patching the tree at click time eliminates the useChat↔tree divergence
|
|
9
|
+
* the ChatTransport would otherwise have to reconcile via a history
|
|
10
|
+
* overlay, and closes the observer-turn race that could wipe the
|
|
11
|
+
* approval state between `addToolApprovalResponse` and
|
|
12
|
+
* `sendAutomaticallyWhen`'s evaluation.
|
|
13
|
+
*
|
|
14
|
+
* Use this in place of useChat's raw `addToolApprovalResponse` wherever
|
|
15
|
+
* you wire Approve / Deny buttons.
|
|
16
|
+
*/
|
|
17
|
+
import type * as AI from 'ai';
|
|
18
|
+
/**
|
|
19
|
+
* Returns a function with the same signature as useChat's
|
|
20
|
+
* `addToolApprovalResponse`, but additionally applies the approval
|
|
21
|
+
* response to the transport tree via `stageMessage` before delegating.
|
|
22
|
+
*
|
|
23
|
+
* If the tool call identified by `opts.id` isn't found in the tree,
|
|
24
|
+
* the tree update is skipped and the raw function is still called —
|
|
25
|
+
* matches useChat's tolerant behavior for stale approval ids.
|
|
26
|
+
* @param transport - The client transport whose tree to patch.
|
|
27
|
+
* @param addToolApprovalResponse - The raw function from `useChat()`.
|
|
28
|
+
* @returns A drop-in replacement that patches the tree then delegates.
|
|
29
|
+
*/
|
|
30
|
+
export declare const useStagedAddToolApprovalResponse: (transport: ClientTransport<AI.UIMessageChunk, AI.UIMessage>, addToolApprovalResponse: ChatAddToolApproveResponseFunction) => ChatAddToolApproveResponseFunction;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { MessageNode, StreamResponseOptions, StreamResult, Turn } from '../core/transport/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Server-side helpers for processing a tool-approval turn.
|
|
4
|
+
*
|
|
5
|
+
* When a Vercel AI SDK tool is marked `needsApproval`, `streamText` pauses
|
|
6
|
+
* after emitting a `dynamic-tool` part in state `approval-requested`. To
|
|
7
|
+
* resume, the server must:
|
|
8
|
+
*
|
|
9
|
+
* 1. Patch the UIMessage history so the pending tool part reflects the
|
|
10
|
+
* user's decision (`approval-responded` or `output-denied`).
|
|
11
|
+
* 2. Strip the client-appended "Approved: …" user message, because
|
|
12
|
+
* `streamText`'s multi-step loop only auto-executes pending tool
|
|
13
|
+
* calls when the conversation ends on a tool/assistant message.
|
|
14
|
+
* 3. Disable `needsApproval` on just-approved tools so the multi-step
|
|
15
|
+
* loop doesn't immediately pause again on the same tool.
|
|
16
|
+
* 4. Redirect the resulting `tool-output-available` / `tool-output-error`
|
|
17
|
+
* chunks back to the ORIGINAL assistant message (the one that held
|
|
18
|
+
* the `approval-requested` part) via `x-ably-amend`, instead of
|
|
19
|
+
* letting them land on the new assistant message this turn produces.
|
|
20
|
+
*
|
|
21
|
+
* `prepareApprovalTurn` covers steps 1–3; `streamResponseWithApprovalRedirect`
|
|
22
|
+
* covers step 4.
|
|
23
|
+
*/
|
|
24
|
+
import type * as AI from 'ai';
|
|
25
|
+
/**
|
|
26
|
+
* A user's decision on a pending tool approval. The client ships an array of
|
|
27
|
+
* these to the server in the POST body; the server feeds them to
|
|
28
|
+
* `prepareApprovalTurn` (to patch history) and
|
|
29
|
+
* `streamResponseWithApprovalRedirect` (to route tool outputs back to the
|
|
30
|
+
* original assistant message).
|
|
31
|
+
*
|
|
32
|
+
* Intentionally does not carry `toolName` or `input` — those are redundant
|
|
33
|
+
* with what's already on the UIMessage history part.
|
|
34
|
+
*/
|
|
35
|
+
export interface ToolApprovalDecision {
|
|
36
|
+
/**
|
|
37
|
+
* The `toolCallId` of the pending `dynamic-tool` part being approved/denied.
|
|
38
|
+
* Must match a part already in the history; decisions that don't match any
|
|
39
|
+
* part are ignored by {@link applyToolApprovalsToHistory}.
|
|
40
|
+
*/
|
|
41
|
+
toolCallId: string;
|
|
42
|
+
/** Whether the user approved or denied the tool call. */
|
|
43
|
+
approved: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* The `x-ably-msg-id` of the assistant message whose `dynamic-tool` part
|
|
46
|
+
* is being responded to. When approved and the tool executes successfully,
|
|
47
|
+
* the output is published cross-turn targeting this message.
|
|
48
|
+
*/
|
|
49
|
+
targetMsgId: string;
|
|
50
|
+
/** Optional reason accompanying the response. */
|
|
51
|
+
reason?: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Patch `dynamic-tool` parts in the history to reflect a batch of approval
|
|
55
|
+
* decisions. Pure — returns a new array; input is not mutated.
|
|
56
|
+
*
|
|
57
|
+
* Approved decisions transition the matching part to `approval-responded`,
|
|
58
|
+
* which `convertToModelMessages` will expand into a `tool-approval-response`
|
|
59
|
+
* model message for `streamText`'s multi-step loop. Denied decisions
|
|
60
|
+
* transition to `output-denied`.
|
|
61
|
+
*
|
|
62
|
+
* Messages and parts whose `toolCallId` is not referenced by any decision
|
|
63
|
+
* are passed through by reference.
|
|
64
|
+
* @param messages - The UIMessage history (user + assistant messages).
|
|
65
|
+
* @param decisions - Approval decisions keyed by `toolCallId`.
|
|
66
|
+
* @returns A new array with matching tool parts transitioned.
|
|
67
|
+
*/
|
|
68
|
+
export declare const applyToolApprovalsToHistory: (messages: AI.UIMessage[], decisions: ToolApprovalDecision[]) => AI.UIMessage[];
|
|
69
|
+
/** Options for {@link prepareApprovalTurn}. */
|
|
70
|
+
export interface PrepareApprovalTurnOptions<T extends Record<string, object>> {
|
|
71
|
+
/** The full UIMessage history (user + assistant messages for this conversation). */
|
|
72
|
+
messages: AI.UIMessage[];
|
|
73
|
+
/** The user's approval decisions for this request, if any. */
|
|
74
|
+
decisions: ToolApprovalDecision[] | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* The tool dictionary that will be passed to `streamText`. Typed with a
|
|
77
|
+
* structural `object` value constraint so it accepts `Record<string, Tool>`
|
|
78
|
+
* regardless of which copy of the `ai` peer dep typed it.
|
|
79
|
+
*/
|
|
80
|
+
tools: T;
|
|
81
|
+
}
|
|
82
|
+
/** Result of {@link prepareApprovalTurn}. */
|
|
83
|
+
export interface PrepareApprovalTurnResult<T extends Record<string, object>> {
|
|
84
|
+
/** Model-format messages ready to pass to `streamText({ messages })`. */
|
|
85
|
+
modelMessages: AI.ModelMessage[];
|
|
86
|
+
/** Tools with `needsApproval` disabled for any tool that was just approved. */
|
|
87
|
+
tools: T;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* One-shot transform to ready a history + tool dict for a `streamText` call
|
|
91
|
+
* on an approval turn. Returns the patched model-message array and the
|
|
92
|
+
* effective tools dict.
|
|
93
|
+
*
|
|
94
|
+
* When `decisions` is absent or empty, this is a thin wrapper around
|
|
95
|
+
* `convertToModelMessages(messages)` that returns the original tools — so
|
|
96
|
+
* callers can use it uniformly regardless of whether the request carries
|
|
97
|
+
* approvals.
|
|
98
|
+
* @param options - See {@link PrepareApprovalTurnOptions}.
|
|
99
|
+
* @returns See {@link PrepareApprovalTurnResult}.
|
|
100
|
+
*/
|
|
101
|
+
export declare const prepareApprovalTurn: <T extends Record<string, object>>(options: PrepareApprovalTurnOptions<T>) => Promise<PrepareApprovalTurnResult<T>>;
|
|
102
|
+
/** Options for {@link streamResponseWithApprovalRedirect}. */
|
|
103
|
+
export interface StreamResponseWithApprovalRedirectOptions extends StreamResponseOptions<AI.UIMessageChunk> {
|
|
104
|
+
/**
|
|
105
|
+
* The approval decisions this turn is resolving. Only approved decisions
|
|
106
|
+
* redirect tool outputs — denied decisions have already been reflected
|
|
107
|
+
* in the history and produce no tool output to capture.
|
|
108
|
+
*/
|
|
109
|
+
decisions: ToolApprovalDecision[] | undefined;
|
|
110
|
+
}
|
|
111
|
+
export declare const streamResponseWithApprovalRedirect: (turn: Turn<AI.UIMessageChunk, AI.UIMessage>, stream: ReadableStream<AI.UIMessageChunk>, options: StreamResponseWithApprovalRedirectOptions) => Promise<StreamResult>;
|
|
112
|
+
/**
|
|
113
|
+
* Walk the conversation history and synthesize a {@link ToolApprovalDecision}
|
|
114
|
+
* for each `dynamic-tool` part in `approval-responded` (approved) or
|
|
115
|
+
* `output-denied` (denied) state.
|
|
116
|
+
*
|
|
117
|
+
* Use in server routes where the client flips the tool part state directly
|
|
118
|
+
* (via useChat's `addToolApprovalResponse` and our
|
|
119
|
+
* `useStagedAddToolApprovalResponse`) and ships it through the history
|
|
120
|
+
* overlay instead of a separate `toolApprovals` body field.
|
|
121
|
+
* @param history - The conversation history nodes from the POST body.
|
|
122
|
+
* @returns Approval decisions derived from the history, in walk order.
|
|
123
|
+
*/
|
|
124
|
+
export declare const extractApprovalDecisionsFromHistory: (history: readonly MessageNode<AI.UIMessage>[]) => ToolApprovalDecision[];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { EventsNode, MessageNode } from '../core/transport/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Server-side helper for folding client-shipped events into an in-memory
|
|
4
|
+
* history array before handing it to `convertToModelMessages` / `streamText`.
|
|
5
|
+
*
|
|
6
|
+
* When a client-executed tool resolves, the client stages the resulting
|
|
7
|
+
* `tool-output-available` / `tool-output-error` chunk via
|
|
8
|
+
* `transport.stageEvents(msgId, [...])`. The next send flushes it into the
|
|
9
|
+
* POST body's `events` field. The server republishes the event on the
|
|
10
|
+
* channel via `turn.addEvents`, and must also merge it into the in-memory
|
|
11
|
+
* history so the LLM sees the tool result this turn.
|
|
12
|
+
*/
|
|
13
|
+
import type * as AI from 'ai';
|
|
14
|
+
/**
|
|
15
|
+
* Fold a batch of client-shipped events into an in-memory history array.
|
|
16
|
+
*
|
|
17
|
+
* Mirrors the optimistic tree update in
|
|
18
|
+
* `DefaultClientTransport._internalSend` (src/core/transport/client-transport.ts)
|
|
19
|
+
* so the server can rebuild the same message state before handing it to
|
|
20
|
+
* `convertToModelMessages` / `streamText`.
|
|
21
|
+
* @param events - The events shipped by the client.
|
|
22
|
+
* @param nodes - The history messages from the POST body.
|
|
23
|
+
* @returns A new array with tool-result events applied to the matching
|
|
24
|
+
* messages. Non-targeted messages are passed through unchanged.
|
|
25
|
+
*/
|
|
26
|
+
export declare const applyToolEventsToHistory: (events: EventsNode<AI.UIMessageChunk>[], nodes: MessageNode<AI.UIMessage>[]) => MessageNode<AI.UIMessage>[];
|
|
@@ -8,12 +8,18 @@ import { ClientTransport, CloseOptions } from '../../core/transport/types.js';
|
|
|
8
8
|
* to the core transport's send/cancel methods.
|
|
9
9
|
*
|
|
10
10
|
* useChat manages message state before calling sendMessages:
|
|
11
|
-
* - submit-message: appends the new user message, passes the full array
|
|
11
|
+
* - submit-message (new): appends the new user message, passes the full array
|
|
12
|
+
* - submit-message (edit): truncates after the edited message, replaces it,
|
|
13
|
+
* passes the truncated array with messageId set
|
|
12
14
|
* - regenerate-message: truncates after the target, passes the truncated array
|
|
13
15
|
*
|
|
14
16
|
* The adapter uses `trigger` to determine the history/messages split:
|
|
15
17
|
* - submit-message: last message is new (publish to channel), rest is history
|
|
16
18
|
* - regenerate-message: no new messages, entire array is history
|
|
19
|
+
*
|
|
20
|
+
* When messageId is set (edit or regeneration), the adapter computes fork
|
|
21
|
+
* metadata (forkOf/parent) from the conversation tree so the server can
|
|
22
|
+
* place the response on the correct branch.
|
|
17
23
|
*/
|
|
18
24
|
import type * as AI from 'ai';
|
|
19
25
|
/**
|
|
@@ -22,12 +28,14 @@ import type * as AI from 'ai';
|
|
|
22
28
|
*/
|
|
23
29
|
export interface SendMessagesRequestContext {
|
|
24
30
|
/** Chat session ID (from useChat's id). */
|
|
25
|
-
|
|
31
|
+
chatId?: string;
|
|
26
32
|
/** What triggered the request: user sent a message, or requested regeneration. */
|
|
27
33
|
trigger: 'submit-message' | 'regenerate-message';
|
|
28
34
|
/**
|
|
29
|
-
* The message ID for regeneration requests.
|
|
30
|
-
* message to regenerate.
|
|
35
|
+
* The message ID for edit or regeneration requests. For regeneration,
|
|
36
|
+
* identifies the assistant message to regenerate. For edits (submit-message
|
|
37
|
+
* with messageId), identifies the user message being replaced. Undefined
|
|
38
|
+
* when submitting a new message.
|
|
31
39
|
*/
|
|
32
40
|
messageId?: string;
|
|
33
41
|
/** Previous messages in the conversation (context for the LLM). */
|
|
@@ -37,7 +45,7 @@ export interface SendMessagesRequestContext {
|
|
|
37
45
|
/** The msg-id of the message being forked (regenerated or edited). */
|
|
38
46
|
forkOf?: string;
|
|
39
47
|
/** The msg-id of the predecessor in the conversation thread. */
|
|
40
|
-
parent?: string
|
|
48
|
+
parent?: string;
|
|
41
49
|
}
|
|
42
50
|
/** Options for customizing the ChatTransport behavior. */
|
|
43
51
|
export interface ChatTransportOptions {
|
|
@@ -75,7 +83,8 @@ interface ChatRequestOptions {
|
|
|
75
83
|
*
|
|
76
84
|
* Structurally compatible with the AI SDK's internal `ChatTransport<UIMessage>`
|
|
77
85
|
* interface. Extended with `close()` for releasing the underlying Ably transport
|
|
78
|
-
* resources
|
|
86
|
+
* resources and `streaming` / `onStreamingChange` for coordinating with
|
|
87
|
+
* useMessageSync.
|
|
79
88
|
*/
|
|
80
89
|
export interface ChatTransport {
|
|
81
90
|
/** Send messages and return a streaming response of UIMessageChunk events. */
|
|
@@ -101,15 +110,28 @@ export interface ChatTransport {
|
|
|
101
110
|
} & ChatRequestOptions) => Promise<ReadableStream<AI.UIMessageChunk> | null>;
|
|
102
111
|
/** Close the underlying transport, releasing all resources. */
|
|
103
112
|
close(options?: CloseOptions): Promise<void>;
|
|
113
|
+
/** Whether an own-turn stream is currently being consumed by useChat. */
|
|
114
|
+
readonly streaming: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Subscribe to streaming state changes. The callback fires when the
|
|
117
|
+
* ChatTransport transitions between streaming and idle. Used by
|
|
118
|
+
* useMessageSync to gate setMessages calls during active streams.
|
|
119
|
+
* @param callback - Called with `true` when a stream starts, `false` when it ends.
|
|
120
|
+
* @returns Unsubscribe function.
|
|
121
|
+
*/
|
|
122
|
+
onStreamingChange(callback: (streaming: boolean) => void): () => void;
|
|
104
123
|
}
|
|
105
124
|
/**
|
|
106
125
|
* Create a Vercel ChatTransport from a core ClientTransport.
|
|
107
126
|
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
127
|
+
* Exposes a `streaming` flag and `onStreamingChange` callback so that
|
|
128
|
+
* `useMessageSync` can gate `setMessages` calls during active own-turn
|
|
129
|
+
* streams, preventing the push/replace ID mismatch in useChat's `write()`.
|
|
130
|
+
*
|
|
131
|
+
* Note: concurrent `sendMessage` calls from the same user are a useChat
|
|
132
|
+
* limitation that cannot be fixed from the transport layer. The
|
|
133
|
+
* developer must respect useChat's `status` and only call `sendMessage`
|
|
134
|
+
* when status is `'ready'`.
|
|
113
135
|
* @param transport - The core client transport to wrap.
|
|
114
136
|
* @param chatOptions - Optional hooks for customizing request construction.
|
|
115
137
|
* @returns A {@link ChatTransport} compatible with Vercel's useChat hook.
|
|
@@ -14,10 +14,13 @@ import { ClientTransport, ClientTransportOptions, ServerTransport, ServerTranspo
|
|
|
14
14
|
export type { ChatTransport, ChatTransportOptions, SendMessagesRequestContext } from './chat-transport.js';
|
|
15
15
|
export { createChatTransport } from './chat-transport.js';
|
|
16
16
|
import type * as AI from 'ai';
|
|
17
|
-
/**
|
|
18
|
-
|
|
17
|
+
/** Core client transport options with Vercel AI SDK types pre-applied. */
|
|
18
|
+
type CoreClientOpts = ClientTransportOptions<AI.UIMessageChunk, AI.UIMessage>;
|
|
19
|
+
/** Options for creating a Vercel client transport. Same as core options but without the codec field, and with `api` optional (defaults to `"/api/chat"`). */
|
|
20
|
+
export type VercelClientTransportOptions = Omit<CoreClientOpts, 'codec' | 'api'> & Partial<Pick<CoreClientOpts, 'api'>>;
|
|
19
21
|
/** Options for creating a Vercel server transport. Same as core options but without the codec field. */
|
|
20
22
|
export type VercelServerTransportOptions = Omit<ServerTransportOptions<AI.UIMessageChunk, AI.UIMessage>, 'codec'>;
|
|
23
|
+
export declare const DEFAULT_VERCEL_API = "/api/chat";
|
|
21
24
|
/**
|
|
22
25
|
* Create a client-side transport pre-configured with the Vercel AI SDK codec.
|
|
23
26
|
*
|