@ably/ai-transport 0.1.0 → 0.3.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 +93 -111
- package/dist/ably-ai-transport.js +2401 -1387
- 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 +116 -42
- package/dist/core/agent.d.ts +44 -0
- package/dist/core/channel-options.d.ts +57 -0
- package/dist/core/codec/codec-event.d.ts +9 -0
- package/dist/core/codec/decoder.d.ts +24 -24
- package/dist/core/codec/define-codec.d.ts +100 -0
- package/dist/core/codec/encoder.d.ts +10 -12
- package/dist/core/codec/field-bag.d.ts +85 -0
- package/dist/core/codec/fields.d.ts +141 -0
- package/dist/core/codec/index.d.ts +8 -2
- package/dist/core/codec/input-descriptor-decoder.d.ts +19 -0
- package/dist/core/codec/input-descriptor-encoder.d.ts +22 -0
- package/dist/core/codec/input-descriptors.d.ts +281 -0
- package/dist/core/codec/lifecycle-tracker.d.ts +10 -9
- package/dist/core/codec/output-descriptor-decoder.d.ts +29 -0
- package/dist/core/codec/output-descriptor-encoder.d.ts +31 -0
- package/dist/core/codec/output-descriptors.d.ts +237 -0
- package/dist/core/codec/types.d.ts +470 -119
- package/dist/core/codec/well-known-inputs.d.ts +52 -0
- package/dist/core/transport/agent-session.d.ts +10 -0
- package/dist/core/transport/agent-view.d.ts +296 -0
- package/dist/core/transport/client-session.d.ts +13 -0
- package/dist/core/transport/decode-fold.d.ts +55 -0
- package/dist/core/transport/headers.d.ts +121 -14
- package/dist/core/transport/index.d.ts +5 -6
- package/dist/core/transport/internal/bounded-map.d.ts +20 -0
- package/dist/core/transport/invocation.d.ts +74 -0
- package/dist/core/transport/load-history-pages.d.ts +71 -0
- package/dist/core/transport/load-history.d.ts +44 -0
- package/dist/core/transport/pipe-stream.d.ts +9 -9
- package/dist/core/transport/run-manager.d.ts +76 -0
- package/dist/core/transport/session-support.d.ts +55 -0
- package/dist/core/transport/tree.d.ts +523 -109
- package/dist/core/transport/types/agent.d.ts +375 -0
- package/dist/core/transport/types/client.d.ts +201 -0
- package/dist/core/transport/types/shared.d.ts +24 -0
- package/dist/core/transport/types/tree.d.ts +357 -0
- package/dist/core/transport/types/view.d.ts +249 -0
- package/dist/core/transport/types.d.ts +13 -553
- package/dist/core/transport/view.d.ts +390 -84
- package/dist/core/transport/wire-log.d.ts +102 -0
- package/dist/errors.d.ts +27 -10
- package/dist/index.d.ts +8 -9
- package/dist/logger.d.ts +12 -0
- package/dist/react/ably-ai-transport-react.js +1365 -1010
- 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/client-session-context.d.ts +37 -0
- package/dist/react/contexts/client-session-provider.d.ts +56 -0
- package/dist/react/create-session-hooks.d.ts +116 -0
- package/dist/react/index.d.ts +13 -12
- package/dist/react/internal/skipped-session.d.ts +8 -0
- package/dist/react/internal/use-resolved-session.d.ts +36 -0
- package/dist/react/use-ably-messages.d.ts +17 -14
- package/dist/react/use-client-session.d.ts +81 -0
- package/dist/react/use-create-view.d.ts +14 -13
- package/dist/react/use-tree.d.ts +30 -15
- package/dist/react/use-view.d.ts +81 -50
- package/dist/utils.d.ts +48 -71
- package/dist/vercel/ably-ai-transport-vercel.js +3257 -2499
- 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/decode-lifecycle.d.ts +9 -0
- package/dist/vercel/codec/events.d.ts +50 -0
- package/dist/vercel/codec/fields.d.ts +44 -0
- package/dist/vercel/codec/fold-content.d.ts +16 -0
- package/dist/vercel/codec/fold-data.d.ts +16 -0
- package/dist/vercel/codec/fold-input.d.ts +67 -0
- package/dist/vercel/codec/fold-lifecycle.d.ts +16 -0
- package/dist/vercel/codec/fold-text.d.ts +16 -0
- package/dist/vercel/codec/fold-tool-input.d.ts +17 -0
- package/dist/vercel/codec/fold-tool-output.d.ts +16 -0
- package/dist/vercel/codec/index.d.ts +7 -20
- package/dist/vercel/codec/inputs.d.ts +11 -0
- package/dist/vercel/codec/outputs.d.ts +11 -0
- package/dist/vercel/codec/reducer-state.d.ts +121 -0
- package/dist/vercel/codec/reducer.d.ts +62 -0
- package/dist/vercel/codec/tool-transitions.d.ts +2 -8
- package/dist/vercel/codec/wire-data.d.ts +34 -0
- package/dist/vercel/index.d.ts +5 -5
- package/dist/vercel/react/ably-ai-transport-vercel-react.js +2859 -9705
- package/dist/vercel/react/ably-ai-transport-vercel-react.js.map +1 -1
- package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs +1 -45
- package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs.map +1 -1
- package/dist/vercel/react/contexts/chat-transport-context.d.ts +9 -7
- package/dist/vercel/react/contexts/chat-transport-provider.d.ts +53 -41
- package/dist/vercel/react/index.d.ts +1 -2
- package/dist/vercel/react/use-chat-transport.d.ts +30 -26
- package/dist/vercel/react/use-message-sync.d.ts +17 -30
- package/dist/vercel/run-end-reason.d.ts +84 -0
- package/dist/vercel/tool-part.d.ts +21 -0
- package/dist/vercel/transport/chat-transport.d.ts +41 -24
- package/dist/vercel/transport/index.d.ts +24 -20
- package/dist/vercel/transport/run-output-stream.d.ts +54 -0
- package/dist/version.d.ts +2 -0
- package/package.json +31 -24
- package/src/constants.ts +124 -51
- package/src/core/agent.ts +92 -0
- package/src/core/channel-options.ts +89 -0
- package/src/core/codec/codec-event.ts +27 -0
- package/src/core/codec/decoder.ts +202 -105
- package/src/core/codec/define-codec.ts +432 -0
- package/src/core/codec/encoder.ts +114 -107
- package/src/core/codec/field-bag.ts +142 -0
- package/src/core/codec/fields.ts +193 -0
- package/src/core/codec/index.ts +56 -6
- package/src/core/codec/input-descriptor-decoder.ts +97 -0
- package/src/core/codec/input-descriptor-encoder.ts +150 -0
- package/src/core/codec/input-descriptors.ts +373 -0
- package/src/core/codec/lifecycle-tracker.ts +10 -9
- package/src/core/codec/output-descriptor-decoder.ts +139 -0
- package/src/core/codec/output-descriptor-encoder.ts +101 -0
- package/src/core/codec/output-descriptors.ts +307 -0
- package/src/core/codec/types.ts +505 -126
- package/src/core/codec/well-known-inputs.ts +96 -0
- package/src/core/transport/agent-session.ts +1085 -0
- package/src/core/transport/agent-view.ts +738 -0
- package/src/core/transport/client-session.ts +780 -0
- package/src/core/transport/decode-fold.ts +101 -0
- package/src/core/transport/headers.ts +234 -22
- package/src/core/transport/index.ts +27 -27
- package/src/core/transport/internal/bounded-map.ts +27 -0
- package/src/core/transport/invocation.ts +98 -0
- package/src/core/transport/load-history-pages.ts +220 -0
- package/src/core/transport/load-history.ts +271 -0
- package/src/core/transport/pipe-stream.ts +63 -39
- package/src/core/transport/run-manager.ts +243 -0
- package/src/core/transport/session-support.ts +96 -0
- package/src/core/transport/tree.ts +1293 -308
- package/src/core/transport/types/agent.ts +434 -0
- package/src/core/transport/types/client.ts +247 -0
- package/src/core/transport/types/shared.ts +27 -0
- package/src/core/transport/types/tree.ts +393 -0
- package/src/core/transport/types/view.ts +288 -0
- package/src/core/transport/types.ts +13 -706
- package/src/core/transport/view.ts +1229 -450
- package/src/core/transport/wire-log.ts +189 -0
- package/src/errors.ts +29 -9
- package/src/event-emitter.ts +3 -2
- package/src/index.ts +86 -42
- package/src/logger.ts +14 -1
- package/src/react/contexts/client-session-context.ts +41 -0
- package/src/react/contexts/client-session-provider.tsx +222 -0
- package/src/react/create-session-hooks.ts +141 -0
- package/src/react/index.ts +24 -13
- package/src/react/internal/skipped-session.ts +62 -0
- package/src/react/internal/use-resolved-session.ts +63 -0
- package/src/react/use-ably-messages.ts +32 -22
- package/src/react/use-client-session.ts +178 -0
- package/src/react/use-create-view.ts +33 -29
- package/src/react/use-tree.ts +61 -30
- package/src/react/use-view.ts +138 -96
- package/src/utils.ts +83 -131
- package/src/vercel/codec/decode-lifecycle.ts +70 -0
- package/src/vercel/codec/events.ts +85 -0
- package/src/vercel/codec/fields.ts +58 -0
- package/src/vercel/codec/fold-content.ts +54 -0
- package/src/vercel/codec/fold-data.ts +46 -0
- package/src/vercel/codec/fold-input.ts +255 -0
- package/src/vercel/codec/fold-lifecycle.ts +85 -0
- package/src/vercel/codec/fold-text.ts +55 -0
- package/src/vercel/codec/fold-tool-input.ts +86 -0
- package/src/vercel/codec/fold-tool-output.ts +79 -0
- package/src/vercel/codec/index.ts +28 -21
- package/src/vercel/codec/inputs.ts +116 -0
- package/src/vercel/codec/outputs.ts +207 -0
- package/src/vercel/codec/reducer-state.ts +169 -0
- package/src/vercel/codec/reducer.ts +191 -0
- package/src/vercel/codec/tool-transitions.ts +3 -14
- package/src/vercel/codec/wire-data.ts +64 -0
- package/src/vercel/index.ts +7 -19
- package/src/vercel/react/contexts/chat-transport-context.ts +8 -7
- package/src/vercel/react/contexts/chat-transport-provider.tsx +87 -59
- package/src/vercel/react/index.ts +3 -5
- package/src/vercel/react/use-chat-transport.ts +44 -66
- package/src/vercel/react/use-message-sync.ts +75 -39
- package/src/vercel/run-end-reason.ts +157 -0
- package/src/vercel/tool-part.ts +25 -0
- package/src/vercel/transport/chat-transport.ts +380 -98
- package/src/vercel/transport/index.ts +38 -37
- package/src/vercel/transport/run-output-stream.ts +169 -0
- package/src/version.ts +2 -0
- package/dist/core/transport/client-transport.d.ts +0 -10
- package/dist/core/transport/decode-history.d.ts +0 -43
- package/dist/core/transport/server-transport.d.ts +0 -7
- package/dist/core/transport/stream-router.d.ts +0 -29
- package/dist/core/transport/turn-manager.d.ts +0 -37
- package/dist/react/contexts/transport-context.d.ts +0 -31
- package/dist/react/contexts/transport-provider.d.ts +0 -49
- package/dist/react/create-transport-hooks.d.ts +0 -124
- package/dist/react/use-active-turns.d.ts +0 -12
- package/dist/react/use-client-transport.d.ts +0 -80
- package/dist/vercel/codec/accumulator.d.ts +0 -21
- package/dist/vercel/codec/decoder.d.ts +0 -22
- package/dist/vercel/codec/encoder.d.ts +0 -41
- package/dist/vercel/react/use-staged-add-tool-approval-response.d.ts +0 -30
- package/dist/vercel/tool-approvals.d.ts +0 -124
- package/dist/vercel/tool-events.d.ts +0 -26
- package/src/core/transport/client-transport.ts +0 -977
- package/src/core/transport/decode-history.ts +0 -485
- package/src/core/transport/server-transport.ts +0 -612
- package/src/core/transport/stream-router.ts +0 -136
- package/src/core/transport/turn-manager.ts +0 -165
- package/src/react/contexts/transport-context.ts +0 -37
- package/src/react/contexts/transport-provider.tsx +0 -164
- package/src/react/create-transport-hooks.ts +0 -144
- package/src/react/use-active-turns.ts +0 -72
- package/src/react/use-client-transport.ts +0 -197
- package/src/vercel/codec/accumulator.ts +0 -588
- package/src/vercel/codec/decoder.ts +0 -618
- package/src/vercel/codec/encoder.ts +0 -410
- package/src/vercel/react/use-staged-add-tool-approval-response.ts +0 -87
- package/src/vercel/tool-approvals.ts +0 -380
- package/src/vercel/tool-events.ts +0 -53
|
@@ -1,92 +1,100 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ChatTransportProvider: creates a ChatTransport from a
|
|
2
|
+
* ChatTransportProvider: creates a ChatTransport from a ClientSession and makes it
|
|
3
3
|
* available to descendants via ChatTransportContext.
|
|
4
4
|
*
|
|
5
|
-
* Wraps children with
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* Wraps children with ClientSessionProvider (using UIMessageCodec). The
|
|
6
|
+
* surrounding `<AblyProvider>` supplies the Realtime client; the session
|
|
7
|
+
* resolves the channel from `channelName` itself. An inner component reads
|
|
8
|
+
* the ClientSession via useClientSession() and creates the ChatTransport
|
|
9
|
+
* via useMemo, keyed on the session and transport options.
|
|
9
10
|
*
|
|
10
|
-
* The ChatTransport is NOT closed on unmount — the underlying
|
|
11
|
-
* lifecycle is managed by the wrapping
|
|
12
|
-
* React Strict Mode, and ChatTransport.close() delegates to
|
|
13
|
-
* which
|
|
11
|
+
* The ChatTransport is NOT closed on unmount — the underlying ClientSession
|
|
12
|
+
* lifecycle is managed by the wrapping ClientSessionProvider. Auto-closing would break
|
|
13
|
+
* React Strict Mode, and ChatTransport.close() delegates to ClientSession.close()
|
|
14
|
+
* which ClientSessionProvider already calls.
|
|
14
15
|
*
|
|
15
16
|
* Multiple ChatTransportProviders can be nested using distinct channelNames.
|
|
16
|
-
* Each provider merges its
|
|
17
|
-
* can access all registered
|
|
17
|
+
* Each provider merges its session into the parent registry, so descendants
|
|
18
|
+
* can access all registered sessions via useChatTransport({ channelName }).
|
|
18
19
|
*/
|
|
19
20
|
|
|
20
21
|
import type * as AI from 'ai';
|
|
21
22
|
import { type PropsWithChildren, type ReactNode, useContext, useMemo } from 'react';
|
|
22
23
|
|
|
23
|
-
import {
|
|
24
|
-
import { UIMessageCodec } from '../../codec/index.js';
|
|
25
|
-
import {
|
|
24
|
+
import { type ClientSessionProviderProps, createSessionHooks } from '../../../react/index.js';
|
|
25
|
+
import { UIMessageCodec, type VercelInput, type VercelOutput, type VercelProjection } from '../../codec/index.js';
|
|
26
|
+
import type { ChatTransportOptions } from '../../transport/index.js';
|
|
26
27
|
import { createChatTransport } from '../../transport/index.js';
|
|
27
28
|
import type { ChatTransportSlot } from './chat-transport-context.js';
|
|
28
29
|
import { ChatTransportContext } from './chat-transport-context.js';
|
|
29
30
|
|
|
30
|
-
export const {
|
|
31
|
-
|
|
32
|
-
useAblyMessages,
|
|
33
|
-
useActiveTurns,
|
|
34
|
-
useClientTransport,
|
|
35
|
-
useCreateView,
|
|
36
|
-
useTree,
|
|
37
|
-
useView,
|
|
38
|
-
} = createTransportHooks<AI.UIMessageChunk, AI.UIMessage>();
|
|
31
|
+
export const { ClientSessionProvider, useAblyMessages, useClientSession, useCreateView, useTree, useView } =
|
|
32
|
+
createSessionHooks<VercelInput, VercelOutput, VercelProjection, AI.UIMessage>();
|
|
39
33
|
|
|
40
|
-
type
|
|
41
|
-
|
|
34
|
+
type CoreClientSessionProviderProps = Omit<
|
|
35
|
+
ClientSessionProviderProps<VercelInput, VercelOutput, VercelProjection, AI.UIMessage>,
|
|
36
|
+
'codec'
|
|
37
|
+
>;
|
|
42
38
|
|
|
43
39
|
/**
|
|
44
40
|
* Props for {@link ChatTransportProvider}.
|
|
45
41
|
*
|
|
46
|
-
* All {@link
|
|
47
|
-
* plus `
|
|
42
|
+
* All {@link ClientSessionProviderProps} for Vercel types except `codec` (baked as UIMessageCodec),
|
|
43
|
+
* plus the transport-owned invocation POST options (`api` / `credentials` / `fetch`) and
|
|
44
|
+
* `chatOptions` for customizing chat request construction.
|
|
48
45
|
*/
|
|
49
|
-
export interface ChatTransportProviderProps extends
|
|
46
|
+
export interface ChatTransportProviderProps extends CoreClientSessionProviderProps {
|
|
47
|
+
/** Endpoint the chat transport POSTs the invocation to, to wake the agent. Default `/api/chat`. */
|
|
48
|
+
api?: string;
|
|
49
|
+
/** Fetch credentials mode for the invocation POST. */
|
|
50
|
+
credentials?: RequestCredentials;
|
|
51
|
+
/** Custom fetch implementation for the invocation POST. Defaults to `globalThis.fetch`. */
|
|
52
|
+
fetch?: typeof globalThis.fetch;
|
|
50
53
|
/**
|
|
51
|
-
* Optional
|
|
54
|
+
* Optional transport options for customizing chat request construction (e.g. the `prepareSendMessagesRequest` hook).
|
|
52
55
|
* Must be stable across renders — wrap in `useMemo` or define outside the component.
|
|
53
56
|
* A new object reference triggers ChatTransport recreation.
|
|
57
|
+
* If this object also sets `api`/`credentials`/`fetch`, the dedicated top-level props of the same name take precedence.
|
|
54
58
|
*/
|
|
55
59
|
chatOptions?: ChatTransportOptions;
|
|
56
60
|
}
|
|
57
61
|
|
|
58
62
|
const ChatTransportProviderInner = ({
|
|
59
63
|
channelName,
|
|
60
|
-
|
|
64
|
+
chatTransportOptions,
|
|
61
65
|
children,
|
|
62
66
|
}: {
|
|
63
67
|
channelName: string;
|
|
64
|
-
|
|
68
|
+
chatTransportOptions: ChatTransportOptions;
|
|
65
69
|
children: ReactNode;
|
|
66
70
|
}) => {
|
|
67
|
-
const {
|
|
71
|
+
const { session, sessionError } = useClientSession();
|
|
68
72
|
const { providers: parentProviders } = useContext(ChatTransportContext);
|
|
69
|
-
const chatTransport = useMemo(
|
|
73
|
+
const chatTransport = useMemo(
|
|
74
|
+
() => createChatTransport(session, chatTransportOptions),
|
|
75
|
+
[session, chatTransportOptions],
|
|
76
|
+
);
|
|
70
77
|
const contextValue = useMemo(() => {
|
|
71
|
-
const slot: ChatTransportSlot = {
|
|
78
|
+
const slot: ChatTransportSlot = { session, sessionError, chatTransport };
|
|
72
79
|
return {
|
|
73
80
|
nearest: slot,
|
|
74
81
|
providers: { ...parentProviders, [channelName]: slot },
|
|
75
82
|
};
|
|
76
|
-
}, [channelName, parentProviders, chatTransport,
|
|
83
|
+
}, [channelName, parentProviders, chatTransport, session, sessionError]);
|
|
77
84
|
|
|
78
85
|
return <ChatTransportContext.Provider value={contextValue}>{children}</ChatTransportContext.Provider>;
|
|
79
86
|
};
|
|
80
87
|
|
|
81
88
|
/**
|
|
82
|
-
* Provide a {@link ChatTransport} and its underlying {@link
|
|
89
|
+
* Provide a {@link ChatTransport} and its underlying {@link ClientSession} to descendant components.
|
|
83
90
|
*
|
|
84
|
-
* Wraps children with
|
|
85
|
-
*
|
|
91
|
+
* Wraps children with `ClientSessionProvider` using `channelName` (the Realtime
|
|
92
|
+
* client is read from the surrounding `<AblyProvider>`), creates a
|
|
93
|
+
* {@link ClientSession} with UIMessageCodec, wraps it in a {@link ChatTransport},
|
|
86
94
|
* and registers the full slot in `ChatTransportContext` under `channelName`. Descendants call
|
|
87
|
-
* {@link useChatTransport} with the same `channelName` to access both
|
|
95
|
+
* {@link useChatTransport} with the same `channelName` to access both.
|
|
88
96
|
*
|
|
89
|
-
* `
|
|
97
|
+
* `useClientSession` is also available inside this provider's subtree.
|
|
90
98
|
*
|
|
91
99
|
* ```tsx
|
|
92
100
|
* <ChatTransportProvider channelName="ai:demo">
|
|
@@ -94,29 +102,49 @@ const ChatTransportProviderInner = ({
|
|
|
94
102
|
* </ChatTransportProvider>
|
|
95
103
|
*
|
|
96
104
|
* // Inside Chat:
|
|
97
|
-
* const { chatTransport,
|
|
98
|
-
* const {
|
|
105
|
+
* const { chatTransport, session } = useChatTransport();
|
|
106
|
+
* const { session } = useClientSession(); // also available
|
|
99
107
|
* ```
|
|
100
|
-
* @param props - Provider configuration including `channelName`, optional `chatOptions`, and all other
|
|
108
|
+
* @param props - Provider configuration including `channelName`, the invocation POST options (`api` / `credentials` / `fetch`), optional `chatOptions`, and all other session options.
|
|
109
|
+
* @param props.api - Endpoint the chat transport POSTs the invocation to. Default `/api/chat`.
|
|
110
|
+
* @param props.credentials - Fetch credentials mode for the invocation POST.
|
|
111
|
+
* @param props.fetch - Custom fetch implementation for the invocation POST.
|
|
101
112
|
* @param props.chatOptions - Optional hooks for customizing chat request construction. Must be stable (memoized) — a new reference recreates the ChatTransport.
|
|
102
|
-
* @param props.children - Descendant components that consume the transport via hooks.
|
|
103
|
-
* @returns A React element wrapping children with
|
|
113
|
+
* @param props.children - Descendant components that consume the chat transport via hooks.
|
|
114
|
+
* @returns A React element wrapping children with ClientSessionContext and ChatTransportContext.
|
|
104
115
|
*/
|
|
105
116
|
export const ChatTransportProvider = ({
|
|
117
|
+
api,
|
|
118
|
+
credentials,
|
|
119
|
+
fetch,
|
|
106
120
|
chatOptions,
|
|
107
121
|
children,
|
|
108
|
-
...
|
|
109
|
-
}: ChatTransportProviderProps & PropsWithChildren): ReactNode =>
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
122
|
+
...sessionProps
|
|
123
|
+
}: ChatTransportProviderProps & PropsWithChildren): ReactNode => {
|
|
124
|
+
// Fold the transport-owned POST options into a single ChatTransportOptions.
|
|
125
|
+
// Memoized so the ChatTransport isn't recreated each render — createChatTransport
|
|
126
|
+
// is keyed on this object's identity.
|
|
127
|
+
const chatTransportOptions = useMemo<ChatTransportOptions>(
|
|
128
|
+
() => ({
|
|
129
|
+
...chatOptions,
|
|
130
|
+
...(api !== undefined && { api }),
|
|
131
|
+
...(credentials !== undefined && { credentials }),
|
|
132
|
+
...(fetch !== undefined && { fetch }),
|
|
133
|
+
}),
|
|
134
|
+
[api, credentials, fetch, chatOptions],
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<ClientSessionProvider
|
|
139
|
+
{...sessionProps}
|
|
140
|
+
codec={UIMessageCodec}
|
|
118
141
|
>
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
142
|
+
<ChatTransportProviderInner
|
|
143
|
+
channelName={sessionProps.channelName}
|
|
144
|
+
chatTransportOptions={chatTransportOptions}
|
|
145
|
+
>
|
|
146
|
+
{children}
|
|
147
|
+
</ChatTransportProviderInner>
|
|
148
|
+
</ClientSessionProvider>
|
|
149
|
+
);
|
|
150
|
+
};
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
// Vercel-specific React hooks
|
|
1
|
+
// Vercel-specific React entry point: providers, hooks, and their types
|
|
2
2
|
export type { ChatTransport } from '../transport/chat-transport.js';
|
|
3
3
|
export type { ChatTransportProviderProps } from './contexts/chat-transport-provider.js';
|
|
4
4
|
export {
|
|
5
5
|
ChatTransportProvider,
|
|
6
|
-
|
|
6
|
+
ClientSessionProvider,
|
|
7
7
|
useAblyMessages,
|
|
8
|
-
|
|
9
|
-
useClientTransport,
|
|
8
|
+
useClientSession,
|
|
10
9
|
useCreateView,
|
|
11
10
|
useTree,
|
|
12
11
|
useView,
|
|
@@ -15,4 +14,3 @@ export type { ChatTransportHandle, UseChatTransportOptions } from './use-chat-tr
|
|
|
15
14
|
export { useChatTransport } from './use-chat-transport.js';
|
|
16
15
|
export type { UseMessageSyncOptions } from './use-message-sync.js';
|
|
17
16
|
export { useMessageSync } from './use-message-sync.js';
|
|
18
|
-
export { useStagedAddToolApprovalResponse } from './use-staged-add-tool-approval-response.js';
|
|
@@ -1,55 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* useChatTransport: reads a ChatTransport and its underlying
|
|
2
|
+
* useChatTransport: reads a ChatTransport and its underlying ClientSession from
|
|
3
3
|
* the nearest ChatTransportProvider.
|
|
4
4
|
*
|
|
5
|
-
* The transport is created by ChatTransportProvider, which
|
|
6
|
-
* with
|
|
7
|
-
*
|
|
5
|
+
* The chat transport is created by ChatTransportProvider, which wraps the subtree
|
|
6
|
+
* with ClientSessionProvider. The Ably Realtime client is read from the
|
|
7
|
+
* surrounding `<AblyProvider>`. This hook is a thin context reader — it does
|
|
8
|
+
* not create or manage any session/transport state.
|
|
8
9
|
*
|
|
9
10
|
* Pass `channelName` to look up a specific provider by name. Omit to use the nearest
|
|
10
11
|
* provider in the tree. Pass `skip: true` to defer (e.g. when auth is not yet resolved)
|
|
11
|
-
* — returns
|
|
12
|
+
* — returns stubs whose properties throw with a descriptive error.
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
import * as Ably from 'ably';
|
|
15
16
|
import type * as AI from 'ai';
|
|
16
17
|
import { useContext } from 'react';
|
|
17
18
|
|
|
18
|
-
import type {
|
|
19
|
+
import type { ClientSession } from '../../core/transport/types.js';
|
|
19
20
|
import { ErrorCode } from '../../errors.js';
|
|
21
|
+
import { makeSkippedClientSession } from '../../react/internal/skipped-session.js';
|
|
22
|
+
import type { VercelInput, VercelOutput, VercelProjection } from '../codec/index.js';
|
|
20
23
|
import type { ChatTransport } from '../transport/index.js';
|
|
21
24
|
import { ChatTransportContext } from './contexts/chat-transport-context.js';
|
|
22
25
|
|
|
23
|
-
const SKIPPED_CLIENT_TRANSPORT: ClientTransport<AI.UIMessageChunk, AI.UIMessage> = {
|
|
24
|
-
get tree(): Tree<AI.UIMessage> {
|
|
25
|
-
throw new Ably.ErrorInfo('unable to access tree; hook is skipped', ErrorCode.InvalidArgument, 400);
|
|
26
|
-
},
|
|
27
|
-
get view(): View<AI.UIMessageChunk, AI.UIMessage> {
|
|
28
|
-
throw new Ably.ErrorInfo('unable to access view; hook is skipped', ErrorCode.InvalidArgument, 400);
|
|
29
|
-
},
|
|
30
|
-
createView: (): View<AI.UIMessageChunk, AI.UIMessage> => {
|
|
31
|
-
throw new Ably.ErrorInfo('unable to create view; hook is skipped', ErrorCode.InvalidArgument, 400);
|
|
32
|
-
},
|
|
33
|
-
cancel: () => {
|
|
34
|
-
throw new Ably.ErrorInfo('unable to cancel; hook is skipped', ErrorCode.InvalidArgument, 400);
|
|
35
|
-
},
|
|
36
|
-
stageEvents: () => {
|
|
37
|
-
throw new Ably.ErrorInfo('unable to stage events; hook is skipped', ErrorCode.InvalidArgument, 400);
|
|
38
|
-
},
|
|
39
|
-
stageMessage: () => {
|
|
40
|
-
throw new Ably.ErrorInfo('unable to stage message; hook is skipped', ErrorCode.InvalidArgument, 400);
|
|
41
|
-
},
|
|
42
|
-
waitForTurn: () => {
|
|
43
|
-
throw new Ably.ErrorInfo('unable to wait for turn; hook is skipped', ErrorCode.InvalidArgument, 400);
|
|
44
|
-
},
|
|
45
|
-
on: () => {
|
|
46
|
-
throw new Ably.ErrorInfo('unable to subscribe; hook is skipped', ErrorCode.InvalidArgument, 400);
|
|
47
|
-
},
|
|
48
|
-
close: () => {
|
|
49
|
-
throw new Ably.ErrorInfo('unable to close; hook is skipped', ErrorCode.InvalidArgument, 400);
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
|
|
53
26
|
const SKIPPED_CHAT_TRANSPORT: ChatTransport = {
|
|
54
27
|
sendMessages: (): never => {
|
|
55
28
|
throw new Ably.ErrorInfo('unable to send messages; hook is skipped', ErrorCode.InvalidArgument, 400);
|
|
@@ -76,76 +49,81 @@ const SKIPPED_CHAT_TRANSPORT: ChatTransport = {
|
|
|
76
49
|
export interface UseChatTransportOptions {
|
|
77
50
|
/** Channel name to look up; omit to use the nearest {@link ChatTransportProvider}. */
|
|
78
51
|
channelName?: string;
|
|
79
|
-
/** When `true`, return
|
|
52
|
+
/** When `true`, return stubs that throw on any access. */
|
|
80
53
|
skip?: boolean;
|
|
81
54
|
}
|
|
82
55
|
|
|
83
56
|
/**
|
|
84
57
|
* The value returned by {@link useChatTransport}.
|
|
85
|
-
* Provides both the underlying {@link
|
|
58
|
+
* Provides both the underlying {@link ClientSession} and the {@link ChatTransport}
|
|
86
59
|
* adapter for Vercel's useChat hook.
|
|
87
60
|
*/
|
|
88
61
|
export interface ChatTransportHandle {
|
|
89
62
|
/**
|
|
90
|
-
* The underlying client
|
|
91
|
-
* A throwing stub when `skip` is `true`, when no matching {@link
|
|
92
|
-
* was found in the tree, or when
|
|
63
|
+
* The underlying client session, also available via {@link useClientSession}.
|
|
64
|
+
* A throwing stub when `skip` is `true`, when no matching {@link ClientSessionProvider}
|
|
65
|
+
* was found in the tree, or when session construction failed. Check `sessionError` before use.
|
|
93
66
|
*/
|
|
94
|
-
|
|
67
|
+
session: ClientSession<VercelInput, VercelOutput, VercelProjection, AI.UIMessage>;
|
|
95
68
|
|
|
96
69
|
/**
|
|
97
70
|
* The chat transport adapter for use with Vercel's `useChat` hook.
|
|
98
71
|
*
|
|
99
|
-
* A throwing stub when `skip` is `true
|
|
100
|
-
* {@link ChatTransportProvider} was found in the tree
|
|
101
|
-
* {@link
|
|
102
|
-
* and `
|
|
72
|
+
* A throwing stub when `skip` is `true` or when no matching
|
|
73
|
+
* {@link ChatTransportProvider} was found in the tree. When a provider is found
|
|
74
|
+
* but the underlying {@link ClientSession} failed to construct, this is the real
|
|
75
|
+
* transport and `sessionError` is set instead. Check `chatTransportError` and
|
|
76
|
+
* `sessionError` before use.
|
|
103
77
|
*/
|
|
104
78
|
chatTransport: ChatTransport;
|
|
105
79
|
|
|
106
80
|
/**
|
|
107
|
-
* Set when no matching {@link
|
|
81
|
+
* Set when no matching {@link ClientSessionProvider} was found, when session
|
|
108
82
|
* construction failed, and `skip` is `false`.
|
|
109
|
-
* `undefined` when the
|
|
83
|
+
* `undefined` when the session resolved successfully or when `skip` is `true`.
|
|
110
84
|
*/
|
|
111
|
-
|
|
85
|
+
sessionError?: Ably.ErrorInfo | undefined;
|
|
112
86
|
/**
|
|
113
|
-
* Set when no matching {@link ChatTransportProvider} was found
|
|
114
|
-
*
|
|
115
|
-
* `undefined` when the transport resolved successfully
|
|
87
|
+
* Set only when no matching {@link ChatTransportProvider} was found and `skip` is
|
|
88
|
+
* `false`.
|
|
89
|
+
* `undefined` when the chat transport resolved successfully (even if session
|
|
90
|
+
* construction failed — see `sessionError`) or when `skip` is `true`.
|
|
116
91
|
*/
|
|
117
92
|
chatTransportError?: Ably.ErrorInfo | undefined;
|
|
118
93
|
}
|
|
119
94
|
|
|
120
95
|
/**
|
|
121
|
-
* Access a {@link ChatTransport} and {@link
|
|
96
|
+
* Access a {@link ChatTransport} and {@link ClientSession} from the nearest {@link ChatTransportProvider}.
|
|
122
97
|
*
|
|
123
98
|
* When `channelName` is omitted, the innermost `ChatTransportProvider` in the tree is used.
|
|
124
|
-
* When `skip` is `true`, returns
|
|
99
|
+
* When `skip` is `true`, returns stubs whose every property and method throws
|
|
125
100
|
* an {@link Ably.ErrorInfo} — safe to hold in state before conditions are ready.
|
|
126
101
|
* When no provider is found, returns stubs with `chatTransportError` set instead of throwing.
|
|
127
|
-
* @param props - Options for selecting the transport.
|
|
102
|
+
* @param props - Options for selecting the chat transport.
|
|
128
103
|
* @param props.channelName - The channel name passed to the enclosing `ChatTransportProvider`. Omit to use the nearest.
|
|
129
104
|
* @param props.skip - When `true`, return stubs that throw on any access instead of reading from context.
|
|
130
|
-
* @returns The `ChatTransportHandle` containing both the chat transport adapter and the underlying client
|
|
105
|
+
* @returns The `ChatTransportHandle` containing both the chat transport adapter and the underlying client session.
|
|
131
106
|
*/
|
|
132
107
|
export const useChatTransport = ({ channelName, skip }: UseChatTransportOptions = {}): ChatTransportHandle => {
|
|
133
108
|
const { nearest, providers } = useContext(ChatTransportContext);
|
|
134
109
|
|
|
135
110
|
if (skip) {
|
|
136
|
-
return {
|
|
111
|
+
return {
|
|
112
|
+
session: makeSkippedClientSession<VercelInput, VercelOutput, VercelProjection, AI.UIMessage>(),
|
|
113
|
+
chatTransport: SKIPPED_CHAT_TRANSPORT,
|
|
114
|
+
};
|
|
137
115
|
}
|
|
138
116
|
|
|
139
117
|
if (channelName !== undefined) {
|
|
140
118
|
const slot = providers[channelName];
|
|
141
119
|
if (slot) {
|
|
142
|
-
return {
|
|
120
|
+
return { session: slot.session, chatTransport: slot.chatTransport, sessionError: slot.sessionError };
|
|
143
121
|
}
|
|
144
122
|
return {
|
|
145
|
-
|
|
123
|
+
session: makeSkippedClientSession<VercelInput, VercelOutput, VercelProjection, AI.UIMessage>(),
|
|
146
124
|
chatTransport: SKIPPED_CHAT_TRANSPORT,
|
|
147
|
-
|
|
148
|
-
`unable to use client
|
|
125
|
+
sessionError: new Ably.ErrorInfo(
|
|
126
|
+
`unable to use client session; no ClientSessionProvider found for channelName "${channelName}"`,
|
|
149
127
|
ErrorCode.BadRequest,
|
|
150
128
|
400,
|
|
151
129
|
),
|
|
@@ -159,17 +137,17 @@ export const useChatTransport = ({ channelName, skip }: UseChatTransportOptions
|
|
|
159
137
|
|
|
160
138
|
if (nearest) {
|
|
161
139
|
return {
|
|
162
|
-
|
|
140
|
+
session: nearest.session,
|
|
163
141
|
chatTransport: nearest.chatTransport,
|
|
164
|
-
|
|
142
|
+
sessionError: nearest.sessionError,
|
|
165
143
|
};
|
|
166
144
|
}
|
|
167
145
|
|
|
168
146
|
return {
|
|
169
|
-
|
|
147
|
+
session: makeSkippedClientSession<VercelInput, VercelOutput, VercelProjection, AI.UIMessage>(),
|
|
170
148
|
chatTransport: SKIPPED_CHAT_TRANSPORT,
|
|
171
|
-
|
|
172
|
-
'unable to use
|
|
149
|
+
sessionError: new Ably.ErrorInfo(
|
|
150
|
+
'unable to use session; no ClientSessionProvider found in the tree',
|
|
173
151
|
ErrorCode.BadRequest,
|
|
174
152
|
400,
|
|
175
153
|
),
|
|
@@ -1,72 +1,103 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* useMessageSync:
|
|
2
|
+
* useMessageSync: wire view updates into useChat's setMessages.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* During active own-run streams, setMessages is gated to avoid an
|
|
5
|
+
* ID-mismatch in useChat's write(). When the stream ends, the gate
|
|
6
|
+
* opens and the view is synced into useChat's overlay.
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* the gate
|
|
11
|
-
*
|
|
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.
|
|
16
|
-
*
|
|
17
|
-
* Returns the unsubscribe function in the useEffect cleanup so handlers
|
|
18
|
-
* are removed on unmount or when dependencies change.
|
|
8
|
+
* The sync is a per-message merge, not a replace: when the overlay has
|
|
9
|
+
* resolved a client-side tool locally (via addToolResult) but the
|
|
10
|
+
* tree's echo hasn't landed yet, the overlay's resolution wins.
|
|
11
|
+
* Without that, the gate-open sync would race the AI SDK's post-stream
|
|
12
|
+
* sendAutomaticallyWhen check and could clobber the resolution before
|
|
13
|
+
* the continuation publishes.
|
|
19
14
|
*/
|
|
20
15
|
|
|
21
16
|
import type * as AI from 'ai';
|
|
22
17
|
import { useEffect, useState } from 'react';
|
|
23
18
|
|
|
19
|
+
import { isToolPart, type ToolPart } from '../tool-part.js';
|
|
24
20
|
import { useChatTransport } from './use-chat-transport.js';
|
|
25
21
|
|
|
26
22
|
/** Options for {@link useMessageSync}. */
|
|
27
23
|
export interface UseMessageSyncOptions {
|
|
28
24
|
/**
|
|
29
|
-
* The `setMessages` updater function from `useChat()`.
|
|
30
|
-
*
|
|
31
|
-
* transport's current authoritative message list.
|
|
25
|
+
* The `setMessages` updater function from `useChat()`. Called with an
|
|
26
|
+
* updater that returns the next overlay.
|
|
32
27
|
*/
|
|
33
28
|
setMessages: (updater: (prev: AI.UIMessage[]) => AI.UIMessage[]) => void;
|
|
34
29
|
/**
|
|
35
30
|
* Channel name of the {@link ChatTransportProvider} to observe.
|
|
36
|
-
* Omit to use the nearest provider
|
|
31
|
+
* Omit to use the nearest provider.
|
|
37
32
|
*/
|
|
38
33
|
channelName?: string;
|
|
39
|
-
/**
|
|
40
|
-
* When `true`, skip all subscriptions and do nothing.
|
|
41
|
-
* Use when the hook's dependencies are not yet resolved (e.g. auth pending).
|
|
42
|
-
*/
|
|
34
|
+
/** When `true`, skip all subscriptions. */
|
|
43
35
|
skip?: boolean;
|
|
44
36
|
}
|
|
45
37
|
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Tool-resolution merge
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
//
|
|
42
|
+
// The merge matches tool parts by toolCallId (via the shared {@link isToolPart}
|
|
43
|
+
// guard, which accepts both the codec's `dynamic-tool` shape and the AI SDK's
|
|
44
|
+
// `tool-${name}` shape) and keeps the tree's `type` on the result so downstream
|
|
45
|
+
// consumers narrowing on `dynamic-tool` keep working.
|
|
46
|
+
|
|
47
|
+
const RESOLVED_TOOL_STATES = new Set(['output-available', 'output-error', 'approval-responded', 'output-denied']);
|
|
48
|
+
|
|
49
|
+
const mergeAssistant = (tree: AI.UIMessage, overlay: AI.UIMessage): AI.UIMessage => {
|
|
50
|
+
const overlayByCallId = new Map<string, ToolPart>();
|
|
51
|
+
for (const part of overlay.parts) {
|
|
52
|
+
if (isToolPart(part)) overlayByCallId.set(part.toolCallId, part);
|
|
53
|
+
}
|
|
54
|
+
if (overlayByCallId.size === 0) return tree;
|
|
55
|
+
|
|
56
|
+
const parts = tree.parts.map((part) => {
|
|
57
|
+
if (!isToolPart(part)) return part;
|
|
58
|
+
if (RESOLVED_TOOL_STATES.has(part.state)) return part;
|
|
59
|
+
const overlayPart = overlayByCallId.get(part.toolCallId);
|
|
60
|
+
if (!overlayPart || !RESOLVED_TOOL_STATES.has(overlayPart.state)) return part;
|
|
61
|
+
// CAST: tool-${name} and dynamic-tool share the discriminated payload schema.
|
|
62
|
+
return { ...overlayPart, type: part.type } as AI.UIMessage['parts'][number];
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const changed = parts.some((p, i) => p !== tree.parts[i]);
|
|
66
|
+
return changed ? { ...tree, parts } : tree;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const mergeMessages = (tree: AI.UIMessage[], overlay: AI.UIMessage[]): AI.UIMessage[] => {
|
|
70
|
+
if (overlay.length === 0) return tree;
|
|
71
|
+
const overlayById = new Map(overlay.map((m) => [m.id, m]));
|
|
72
|
+
return tree.map((treeMsg) => {
|
|
73
|
+
if (treeMsg.role !== 'assistant') return treeMsg;
|
|
74
|
+
const overlayMsg = overlayById.get(treeMsg.id);
|
|
75
|
+
return overlayMsg ? mergeAssistant(treeMsg, overlayMsg) : treeMsg;
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Hook
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
|
|
46
83
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* Resolves both the transport view and the streaming gate from the nearest
|
|
50
|
-
* `ChatTransportProvider`. Pass `channelName` to target a specific provider.
|
|
51
|
-
* Pass `skip: true` to pause all subscriptions.
|
|
84
|
+
* Subscribe to view updates and sync them into `useChat()`'s overlay.
|
|
52
85
|
* @param options - Hook options.
|
|
53
|
-
* @param options.setMessages - The `setMessages` function from `useChat()`.
|
|
54
|
-
* @param options.channelName - Channel name of the provider to observe; defaults to nearest.
|
|
86
|
+
* @param options.setMessages - The `setMessages` function from `useChat()`.
|
|
87
|
+
* @param options.channelName - Channel name of the provider to observe; defaults to the nearest.
|
|
55
88
|
* @param options.skip - When `true`, skip all subscriptions.
|
|
56
89
|
*/
|
|
57
90
|
export const useMessageSync = ({ setMessages, channelName, skip }: UseMessageSyncOptions): void => {
|
|
58
|
-
const {
|
|
91
|
+
const { session, chatTransport, chatTransportError } = useChatTransport({ channelName, skip });
|
|
59
92
|
|
|
60
|
-
// Only use resolved values when a provider was found and skip is false.
|
|
61
93
|
const resolved = !skip && !chatTransportError;
|
|
62
|
-
const view = resolved ?
|
|
94
|
+
const view = resolved ? session.view : undefined;
|
|
63
95
|
const resolvedChatTransport = resolved ? chatTransport : undefined;
|
|
64
96
|
|
|
65
97
|
const [gated, setGated] = useState(false);
|
|
66
98
|
|
|
67
|
-
// Subscribe to the ChatTransport's streaming state
|
|
68
|
-
//
|
|
69
|
-
// from a previous instance doesn't permanently suppress syncs.
|
|
99
|
+
// Subscribe to the ChatTransport's streaming state. Reset on transport
|
|
100
|
+
// change so a stale `true` doesn't permanently suppress syncs.
|
|
70
101
|
useEffect(() => {
|
|
71
102
|
if (!resolvedChatTransport) {
|
|
72
103
|
setGated(false);
|
|
@@ -76,15 +107,20 @@ export const useMessageSync = ({ setMessages, channelName, skip }: UseMessageSyn
|
|
|
76
107
|
return resolvedChatTransport.onStreamingChange(setGated);
|
|
77
108
|
}, [resolvedChatTransport]);
|
|
78
109
|
|
|
79
|
-
// Subscribe to view updates and sync
|
|
110
|
+
// Subscribe to view updates and sync, unless gated.
|
|
80
111
|
useEffect(() => {
|
|
81
112
|
if (!view || gated) return;
|
|
82
113
|
|
|
83
114
|
const sync = (): void => {
|
|
84
|
-
setMessages(() =>
|
|
115
|
+
setMessages((overlay) =>
|
|
116
|
+
mergeMessages(
|
|
117
|
+
view.getMessages().map((m) => m.message),
|
|
118
|
+
overlay,
|
|
119
|
+
),
|
|
120
|
+
);
|
|
85
121
|
};
|
|
86
122
|
|
|
87
|
-
// Sync immediately
|
|
123
|
+
// Sync immediately to cover gate-open and initial mount.
|
|
88
124
|
sync();
|
|
89
125
|
|
|
90
126
|
return view.on('update', sync);
|