@tanstack/ai-react 0.27.3 → 0.29.1
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/dist/esm/chat-ui/create-ui.d.ts +37 -4
- package/dist/esm/chat-ui/create-ui.js +94 -4
- package/dist/esm/chat-ui/create-ui.js.map +1 -1
- package/dist/esm/create-chat-hook.d.ts +8 -4
- package/dist/esm/create-chat-hook.js.map +1 -1
- package/dist/esm/index.d.ts +3 -3
- package/dist/esm/index.js +3 -3
- package/dist/esm/types.d.ts +19 -5
- package/dist/esm/ui.d.ts +1 -1
- package/dist/esm/use-chat.d.ts +3 -1
- package/dist/esm/use-chat.js +1 -0
- package/dist/esm/use-chat.js.map +1 -1
- package/dist/esm/use-web-mcp-tools.d.ts +34 -7
- package/dist/esm/use-web-mcp-tools.js +42 -5
- package/dist/esm/use-web-mcp-tools.js.map +1 -1
- package/package.json +4 -4
- package/src/chat-ui/create-ui.tsx +250 -8
- package/src/create-chat-hook.ts +13 -3
- package/src/index.ts +15 -2
- package/src/types.ts +24 -2
- package/src/ui.ts +2 -0
- package/src/use-chat.ts +11 -4
- package/src/use-web-mcp-tools.ts +69 -10
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createContext, memo, useContext } from 'react'
|
|
1
|
+
import { createContext, memo, useContext, useMemo } from 'react'
|
|
2
2
|
import type { ComponentProps, ComponentType, Context, ReactNode } from 'react'
|
|
3
3
|
import {
|
|
4
4
|
automaticPartsForMessage,
|
|
@@ -8,8 +8,10 @@ import {
|
|
|
8
8
|
selectMessageUI,
|
|
9
9
|
} from '@tanstack/ai-client/ui'
|
|
10
10
|
import type {
|
|
11
|
+
ChatUIApprovalToolName,
|
|
11
12
|
ChatUIData,
|
|
12
13
|
ChatUIHasNamedInterrupts,
|
|
14
|
+
ChatUIHasNamedSubagents,
|
|
13
15
|
ChatUIHasNamedTools,
|
|
14
16
|
ChatUIInterrupt,
|
|
15
17
|
ChatUIInterruptName,
|
|
@@ -22,6 +24,9 @@ import type {
|
|
|
22
24
|
ChatUISchemaOf,
|
|
23
25
|
ChatUISelectedPart,
|
|
24
26
|
ChatUISelectedPartOf,
|
|
27
|
+
ChatUISubagentName,
|
|
28
|
+
ChatUISubagentOf,
|
|
29
|
+
ChatUISubagentOptions,
|
|
25
30
|
ChatUIToolApproval,
|
|
26
31
|
ChatUIToolName,
|
|
27
32
|
ChatUIToolsOf,
|
|
@@ -29,6 +34,8 @@ import type {
|
|
|
29
34
|
import type {
|
|
30
35
|
MessagePart,
|
|
31
36
|
QueuedMessage,
|
|
37
|
+
SubagentHandle,
|
|
38
|
+
SubagentHandles,
|
|
32
39
|
ToolCallPart,
|
|
33
40
|
ToolResultPart,
|
|
34
41
|
UIMessage,
|
|
@@ -56,6 +63,7 @@ export type LayoutProps<
|
|
|
56
63
|
Messages: ComponentType
|
|
57
64
|
Interrupts: ComponentType
|
|
58
65
|
Queue: ComponentType
|
|
66
|
+
Subagents: ComponentType
|
|
59
67
|
readonly __ui?: TOptions
|
|
60
68
|
} & (TInput extends ComponentType<any> ? { Input: ComponentType } : {})
|
|
61
69
|
|
|
@@ -77,6 +85,38 @@ export type PartProps<TOptions, TKey extends ChatUIPartKey = ChatUIPartKey> = {
|
|
|
77
85
|
part: ChatUIPartOf<TOptions, TKey>
|
|
78
86
|
}
|
|
79
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Widgets for one subagent card. Each entry replaces the enclosing entry of
|
|
90
|
+
* the same key: the root map at top level, the outer card's map when nested.
|
|
91
|
+
* It applies to this card and the children nested in it. An unset key keeps
|
|
92
|
+
* the enclosing entry. Tool names and tool props come from that agent's
|
|
93
|
+
* `tools`.
|
|
94
|
+
*/
|
|
95
|
+
export type SubagentPartsProps<
|
|
96
|
+
TOptions,
|
|
97
|
+
TName extends ChatUISubagentName<TOptions> = ChatUISubagentName<TOptions>,
|
|
98
|
+
> = {
|
|
99
|
+
partsComponents?: ChatUIPartsComponents<
|
|
100
|
+
ChatUISubagentOptions<TOptions, TName>
|
|
101
|
+
>
|
|
102
|
+
toolsComponents?: {
|
|
103
|
+
[K in ChatUIToolName<
|
|
104
|
+
ChatUISubagentOptions<TOptions, TName>
|
|
105
|
+
>]?: ComponentType<ToolProps<ChatUISubagentOptions<TOptions, TName>, K>>
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export type SubagentProps<
|
|
110
|
+
TOptions,
|
|
111
|
+
TName extends ChatUISubagentName<TOptions> = ChatUISubagentName<TOptions>,
|
|
112
|
+
> = {
|
|
113
|
+
subagent: [ChatUISubagentOf<TOptions, TName>] extends [never]
|
|
114
|
+
? SubagentHandle & { name: TName }
|
|
115
|
+
: SubagentHandles<ReadonlyArray<ChatUISubagentOf<TOptions, TName>>>
|
|
116
|
+
Parts: ComponentType<SubagentPartsProps<TOptions, TName>>
|
|
117
|
+
readonly __ui?: TOptions
|
|
118
|
+
}
|
|
119
|
+
|
|
80
120
|
export type ToolProps<
|
|
81
121
|
TOptions,
|
|
82
122
|
TName extends ChatUIToolName<TOptions> = ChatUIToolName<TOptions>,
|
|
@@ -108,7 +148,7 @@ type GenericInterruptComponents<TOptions> =
|
|
|
108
148
|
}
|
|
109
149
|
|
|
110
150
|
type ToolApprovalMap<TOptions> = {
|
|
111
|
-
[K in
|
|
151
|
+
[K in ChatUIApprovalToolName<TOptions>]?: ComponentType<
|
|
112
152
|
InterruptProps<TOptions, K & ChatUIInterruptName<TOptions>>
|
|
113
153
|
>
|
|
114
154
|
}
|
|
@@ -161,6 +201,21 @@ export type ChatUIComponents<
|
|
|
161
201
|
tools?: ToolApprovalMap<TOptions>
|
|
162
202
|
generic?: GenericInterruptComponents<TOptions>
|
|
163
203
|
}
|
|
204
|
+
}) &
|
|
205
|
+
(ChatUIHasNamedSubagents<TOptions> extends true
|
|
206
|
+
? {
|
|
207
|
+
subagentsComponents: {
|
|
208
|
+
[K in ChatUISubagentName<TOptions>]: ComponentType<
|
|
209
|
+
SubagentProps<TOptions, K>
|
|
210
|
+
>
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
: {
|
|
214
|
+
subagentsComponents?: {
|
|
215
|
+
[K in ChatUISubagentName<TOptions>]?: ComponentType<
|
|
216
|
+
SubagentProps<TOptions, K>
|
|
217
|
+
>
|
|
218
|
+
}
|
|
164
219
|
})
|
|
165
220
|
|
|
166
221
|
/** Scoped contexts, for widgets in other files or nested chat trees. */
|
|
@@ -191,7 +246,7 @@ type PartMixins<TOptions> = {
|
|
|
191
246
|
type InterruptMixins<TOptions> = {
|
|
192
247
|
[K in ChatUINamedInterruptId<TOptions>]: BoundWidget
|
|
193
248
|
} & {
|
|
194
|
-
[K in
|
|
249
|
+
[K in ChatUIApprovalToolName<TOptions>]?: BoundWidget
|
|
195
250
|
} & {
|
|
196
251
|
fallback?: BoundWidget
|
|
197
252
|
Render: BoundWidget
|
|
@@ -227,6 +282,32 @@ function queueItemEqual(
|
|
|
227
282
|
)
|
|
228
283
|
}
|
|
229
284
|
|
|
285
|
+
// The handle is one live object that the client updates in place, so a row
|
|
286
|
+
// gets its changing card fields as separate props. Messages are not one of
|
|
287
|
+
// them: `SubagentMessages` reads them from the chat context, so new child
|
|
288
|
+
// text shows without a row render.
|
|
289
|
+
type SubagentRowProps = {
|
|
290
|
+
handle: SubagentHandle
|
|
291
|
+
status: SubagentHandle['status']
|
|
292
|
+
error: SubagentHandle['error']
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function subagentListItemEqual(prev: SubagentRowProps, next: SubagentRowProps) {
|
|
296
|
+
return (
|
|
297
|
+
prev.handle.id === next.handle.id &&
|
|
298
|
+
prev.handle.stop === next.handle.stop &&
|
|
299
|
+
prev.status === next.status &&
|
|
300
|
+
prev.error === next.error
|
|
301
|
+
)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function subagentMessagesEqual(
|
|
305
|
+
prev: { messages: SubagentHandle['messages'] },
|
|
306
|
+
next: { messages: SubagentHandle['messages'] },
|
|
307
|
+
) {
|
|
308
|
+
return prev.messages === next.messages
|
|
309
|
+
}
|
|
310
|
+
|
|
230
311
|
function selectedPartPropsEqual(
|
|
231
312
|
prev: { selected: ChatUISelectedPart },
|
|
232
313
|
next: { selected: ChatUISelectedPart },
|
|
@@ -254,6 +335,18 @@ function isSelectedPart(
|
|
|
254
335
|
return 'key' in value && 'part' in value
|
|
255
336
|
}
|
|
256
337
|
|
|
338
|
+
/** `base` with each set entry of `overrides`. An unset entry keeps `base`. */
|
|
339
|
+
function withWidgets(
|
|
340
|
+
base: Record<string, ComponentType<any> | undefined>,
|
|
341
|
+
overrides: Record<string, ComponentType<any> | undefined> | undefined,
|
|
342
|
+
) {
|
|
343
|
+
const out = { ...base }
|
|
344
|
+
for (const [key, component] of Object.entries(overrides ?? {})) {
|
|
345
|
+
if (component) out[key] = component
|
|
346
|
+
}
|
|
347
|
+
return out
|
|
348
|
+
}
|
|
349
|
+
|
|
257
350
|
function bindMap(
|
|
258
351
|
map: Record<string, ComponentType<any> | undefined> | undefined,
|
|
259
352
|
bind: (component: ComponentType<any>) => BoundWidget,
|
|
@@ -288,12 +381,14 @@ export function createChatUI<
|
|
|
288
381
|
partsComponents: parts,
|
|
289
382
|
toolsComponents: tools,
|
|
290
383
|
interruptsComponents: interrupts,
|
|
384
|
+
subagentsComponents: subagentComponents,
|
|
291
385
|
} = config as ChatUIFactoryConfig<TOptions, TInput> & {
|
|
292
386
|
toolsComponents?: Record<string, ComponentType<any> | undefined>
|
|
293
387
|
interruptsComponents?: {
|
|
294
388
|
tools?: Record<string, ComponentType<any> | undefined>
|
|
295
389
|
generic?: Record<string, ComponentType<any> | undefined>
|
|
296
390
|
}
|
|
391
|
+
subagentsComponents?: Record<string, ComponentType<any> | undefined>
|
|
297
392
|
}
|
|
298
393
|
const {
|
|
299
394
|
layout: Layout,
|
|
@@ -445,6 +540,7 @@ export function createChatUI<
|
|
|
445
540
|
Messages: Messages as ComponentType,
|
|
446
541
|
Interrupts: Interrupts as ComponentType,
|
|
447
542
|
Queue: Queue as ComponentType,
|
|
543
|
+
Subagents: Subagents as ComponentType,
|
|
448
544
|
Input: (InputComponent ?? MissingInput) as ComponentType,
|
|
449
545
|
}
|
|
450
546
|
|
|
@@ -525,6 +621,19 @@ export function createChatUI<
|
|
|
525
621
|
inlineToolNames: ReadonlyArray<string>
|
|
526
622
|
}
|
|
527
623
|
const MessageRenderContext = createContext<MessageRenderValue | null>(null)
|
|
624
|
+
const SubagentRenderContext = createContext<{
|
|
625
|
+
handle: SubagentHandle
|
|
626
|
+
/** A `Subagents` row. Its messages come from the live chat state. */
|
|
627
|
+
listed?: true
|
|
628
|
+
} | null>(null)
|
|
629
|
+
// The widgets that dispatch uses. A subagent card's `Parts` can replace
|
|
630
|
+
// entries for its subtree. Outside a card these are the factory maps.
|
|
631
|
+
type WidgetMap = Record<string, ComponentType<any> | undefined>
|
|
632
|
+
const rootWidgets: { parts: WidgetMap; tools: WidgetMap } = {
|
|
633
|
+
parts: parts as WidgetMap,
|
|
634
|
+
tools: tools ?? {},
|
|
635
|
+
}
|
|
636
|
+
const WidgetsContext = createContext(rootWidgets)
|
|
528
637
|
|
|
529
638
|
function Parts() {
|
|
530
639
|
const scope = useContext(MessageRenderContext)
|
|
@@ -588,7 +697,7 @@ export function createChatUI<
|
|
|
588
697
|
interrupts,
|
|
589
698
|
inlineToolNames: names,
|
|
590
699
|
}: {
|
|
591
|
-
message:
|
|
700
|
+
message: UIMessage
|
|
592
701
|
interrupts: ReadonlyArray<ChatUIInterrupt>
|
|
593
702
|
inlineToolNames: ReadonlyArray<string>
|
|
594
703
|
}) {
|
|
@@ -610,9 +719,10 @@ export function createChatUI<
|
|
|
610
719
|
}: {
|
|
611
720
|
selected: ChatUISelectedPart
|
|
612
721
|
}) {
|
|
722
|
+
const widgets = useContext(WidgetsContext)
|
|
613
723
|
if (selected.key === 'toolCall') {
|
|
614
724
|
const name = selected.part.name
|
|
615
|
-
const Tool = tools
|
|
725
|
+
const Tool = widgets.tools[name] as
|
|
616
726
|
| ComponentType<ToolProps<TOptions>>
|
|
617
727
|
| undefined
|
|
618
728
|
if (!Tool) {
|
|
@@ -628,9 +738,30 @@ export function createChatUI<
|
|
|
628
738
|
)
|
|
629
739
|
}
|
|
630
740
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
741
|
+
if (selected.key === 'subagent' && selected.part.type === 'subagent') {
|
|
742
|
+
const name = selected.part.subagent.name
|
|
743
|
+
const Subagent = subagentComponents?.[name] as
|
|
744
|
+
| ComponentType<SubagentProps<TOptions>>
|
|
745
|
+
| undefined
|
|
746
|
+
if (!Subagent) {
|
|
747
|
+
throw new Error(`[tanstack-ai-ui] Missing subagentsComponents.${name}`)
|
|
748
|
+
}
|
|
749
|
+
return (
|
|
750
|
+
<SubagentRenderContext.Provider
|
|
751
|
+
value={{ handle: selected.part.subagent }}
|
|
752
|
+
>
|
|
753
|
+
<Subagent
|
|
754
|
+
Parts={SubagentMessages}
|
|
755
|
+
subagent={
|
|
756
|
+
selected.part.subagent as SubagentProps<TOptions>['subagent']
|
|
757
|
+
}
|
|
758
|
+
/>
|
|
759
|
+
</SubagentRenderContext.Provider>
|
|
760
|
+
)
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
const PartComponent = (widgets.parts[selected.key] ??
|
|
764
|
+
widgets.parts.fallback) as ComponentType<PartProps<TOptions>> | undefined
|
|
634
765
|
if (!PartComponent) {
|
|
635
766
|
warn(
|
|
636
767
|
`part:${selected.key}`,
|
|
@@ -746,6 +877,115 @@ export function createChatUI<
|
|
|
746
877
|
)
|
|
747
878
|
})
|
|
748
879
|
|
|
880
|
+
function readSubagentMessages() {
|
|
881
|
+
const scoped = useContext(SubagentRenderContext)
|
|
882
|
+
const chat = useChatContext()
|
|
883
|
+
if (scoped?.listed) {
|
|
884
|
+
const live = chat.subagents.find((item) => item.id === scoped.handle.id)
|
|
885
|
+
return (live ?? scoped.handle).messages
|
|
886
|
+
}
|
|
887
|
+
if (scoped) return scoped.handle.messages
|
|
888
|
+
const selected = useContext(PartContext)
|
|
889
|
+
if (selected?.key === 'subagent' && selected.part.type === 'subagent') {
|
|
890
|
+
return selected.part.subagent.messages
|
|
891
|
+
}
|
|
892
|
+
throw new Error(
|
|
893
|
+
'`Parts` must be rendered by a subagent component or `Subagents` item.',
|
|
894
|
+
)
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
const SubagentMessagesBody = memo(function SubagentMessagesBody({
|
|
898
|
+
messages,
|
|
899
|
+
}: {
|
|
900
|
+
messages: SubagentHandle['messages']
|
|
901
|
+
}) {
|
|
902
|
+
const chat = useChatContext()
|
|
903
|
+
const interrupts = readInterrupts(chat)
|
|
904
|
+
return (
|
|
905
|
+
<>
|
|
906
|
+
{messages.map((message) => (
|
|
907
|
+
<AutomaticParts
|
|
908
|
+
key={message.id}
|
|
909
|
+
inlineToolNames={inlineToolNames}
|
|
910
|
+
interrupts={interrupts}
|
|
911
|
+
message={message}
|
|
912
|
+
/>
|
|
913
|
+
))}
|
|
914
|
+
</>
|
|
915
|
+
)
|
|
916
|
+
}, subagentMessagesEqual)
|
|
917
|
+
|
|
918
|
+
function SubagentMessages({
|
|
919
|
+
partsComponents,
|
|
920
|
+
toolsComponents,
|
|
921
|
+
}: SubagentPartsProps<TOptions>) {
|
|
922
|
+
const messages = readSubagentMessages()
|
|
923
|
+
const outer = useContext(WidgetsContext)
|
|
924
|
+
const widgets = useMemo(
|
|
925
|
+
() =>
|
|
926
|
+
partsComponents || toolsComponents
|
|
927
|
+
? {
|
|
928
|
+
parts: withWidgets(outer.parts, partsComponents),
|
|
929
|
+
tools: withWidgets(outer.tools, toolsComponents),
|
|
930
|
+
}
|
|
931
|
+
: outer,
|
|
932
|
+
[outer, partsComponents, toolsComponents],
|
|
933
|
+
)
|
|
934
|
+
// ponytail: root `<Interrupts />` still lists an approval for a tool call
|
|
935
|
+
// inside a card, so it shows in both places. Hiding it needs `selectChatUI`
|
|
936
|
+
// to collect tool-call ids from nested subagent messages (its `callIds` is
|
|
937
|
+
// top-level only) plus the card tool names in `inlineToolNames`.
|
|
938
|
+
return (
|
|
939
|
+
<WidgetsContext.Provider value={widgets}>
|
|
940
|
+
<SubagentMessagesBody messages={messages} />
|
|
941
|
+
</WidgetsContext.Provider>
|
|
942
|
+
)
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
const SubagentListItem = memo(function SubagentListItem({
|
|
946
|
+
handle,
|
|
947
|
+
}: SubagentRowProps) {
|
|
948
|
+
const Subagent = subagentComponents?.[handle.name] as
|
|
949
|
+
| ComponentType<SubagentProps<TOptions>>
|
|
950
|
+
| undefined
|
|
951
|
+
if (!Subagent) {
|
|
952
|
+
throw new Error(
|
|
953
|
+
`[tanstack-ai-ui] Missing subagentsComponents.${handle.name}`,
|
|
954
|
+
)
|
|
955
|
+
}
|
|
956
|
+
return (
|
|
957
|
+
<SubagentRenderContext.Provider value={{ handle, listed: true }}>
|
|
958
|
+
<Subagent
|
|
959
|
+
Parts={SubagentMessages}
|
|
960
|
+
subagent={handle as SubagentProps<TOptions>['subagent']}
|
|
961
|
+
/>
|
|
962
|
+
</SubagentRenderContext.Provider>
|
|
963
|
+
)
|
|
964
|
+
}, subagentListItemEqual)
|
|
965
|
+
|
|
966
|
+
function Subagents({
|
|
967
|
+
children,
|
|
968
|
+
}: {
|
|
969
|
+
children?: (subagents: Array<SubagentHandle>) => ReactNode
|
|
970
|
+
} = {}) {
|
|
971
|
+
const chat = useChatContext()
|
|
972
|
+
const live = chat.subagents
|
|
973
|
+
if (children) return <>{children(live)}</>
|
|
974
|
+
if (live.length === 0) return null
|
|
975
|
+
return (
|
|
976
|
+
<>
|
|
977
|
+
{live.map((handle) => (
|
|
978
|
+
<SubagentListItem
|
|
979
|
+
key={handle.id}
|
|
980
|
+
handle={handle}
|
|
981
|
+
status={handle.status}
|
|
982
|
+
error={handle.error}
|
|
983
|
+
/>
|
|
984
|
+
))}
|
|
985
|
+
</>
|
|
986
|
+
)
|
|
987
|
+
}
|
|
988
|
+
|
|
749
989
|
return {
|
|
750
990
|
Chat,
|
|
751
991
|
Provider,
|
|
@@ -755,6 +995,8 @@ export function createChatUI<
|
|
|
755
995
|
Interrupts,
|
|
756
996
|
Interrupt,
|
|
757
997
|
Queue,
|
|
998
|
+
Subagents,
|
|
999
|
+
SubagentMessages,
|
|
758
1000
|
useChatContext,
|
|
759
1001
|
Input: InputComponent,
|
|
760
1002
|
}
|
package/src/create-chat-hook.ts
CHANGED
|
@@ -12,6 +12,7 @@ type ChatHookOverrides<
|
|
|
12
12
|
TSchema extends SchemaInput | undefined,
|
|
13
13
|
TContext,
|
|
14
14
|
TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>>,
|
|
15
|
+
TSubagents extends ReadonlyArray<{ name: string }> | undefined,
|
|
15
16
|
> = {
|
|
16
17
|
threadId?: string
|
|
17
18
|
live?: boolean
|
|
@@ -21,7 +22,8 @@ type ChatHookOverrides<
|
|
|
21
22
|
TTools,
|
|
22
23
|
TSchema,
|
|
23
24
|
TContext,
|
|
24
|
-
TInterrupts
|
|
25
|
+
TInterrupts,
|
|
26
|
+
TSubagents
|
|
25
27
|
>['initialMessages']
|
|
26
28
|
}
|
|
27
29
|
|
|
@@ -53,9 +55,17 @@ export function createChatHook<
|
|
|
53
55
|
const TInterrupts extends ReadonlyArray<
|
|
54
56
|
InterruptDefinition<any, any, any, any>
|
|
55
57
|
> = readonly [],
|
|
56
|
-
|
|
58
|
+
const TSubagents extends ReadonlyArray<{ name: string }> | undefined =
|
|
59
|
+
undefined,
|
|
60
|
+
>(options: UseChatOptions<TTools, TSchema, TContext, TInterrupts, TSubagents>) {
|
|
57
61
|
function useChat(
|
|
58
|
-
overrides?: ChatHookOverrides<
|
|
62
|
+
overrides?: ChatHookOverrides<
|
|
63
|
+
TTools,
|
|
64
|
+
TSchema,
|
|
65
|
+
TContext,
|
|
66
|
+
TInterrupts,
|
|
67
|
+
TSubagents
|
|
68
|
+
>,
|
|
59
69
|
) {
|
|
60
70
|
if (!overrides) {
|
|
61
71
|
return useUnboundChat(options)
|
package/src/index.ts
CHANGED
|
@@ -4,8 +4,16 @@ export { useByok } from './use-byok'
|
|
|
4
4
|
export { useRealtimeChat } from './use-realtime-chat'
|
|
5
5
|
export { useMcpAppBridge } from './use-mcp-app-bridge'
|
|
6
6
|
export type { UseMcpAppBridgeOptions } from './use-mcp-app-bridge'
|
|
7
|
-
export {
|
|
8
|
-
|
|
7
|
+
export {
|
|
8
|
+
usePageWebMCPTools,
|
|
9
|
+
useRegisterWebMCPTools,
|
|
10
|
+
useWebMCPTools,
|
|
11
|
+
} from './use-web-mcp-tools'
|
|
12
|
+
export type {
|
|
13
|
+
UsePageWebMCPToolsOptions,
|
|
14
|
+
UseRegisterWebMCPToolsOptions,
|
|
15
|
+
UseWebMCPToolsOptions,
|
|
16
|
+
} from './use-web-mcp-tools'
|
|
9
17
|
export type {
|
|
10
18
|
DeepPartial,
|
|
11
19
|
UseChatOptions,
|
|
@@ -105,6 +113,11 @@ export {
|
|
|
105
113
|
PartialJSONParser,
|
|
106
114
|
defaultJSONParser,
|
|
107
115
|
registerWebMCPTools,
|
|
116
|
+
getWebMCPTools,
|
|
117
|
+
subscribeWebMCPTools,
|
|
118
|
+
type GetWebMCPToolsOptions,
|
|
119
|
+
type SubscribeWebMCPToolsOptions,
|
|
120
|
+
type WebMCPPageTool,
|
|
108
121
|
clientTools,
|
|
109
122
|
fetchServerSentEvents,
|
|
110
123
|
localStoragePersistence,
|
package/src/types.ts
CHANGED
|
@@ -27,6 +27,8 @@ import type {
|
|
|
27
27
|
SendMessageOptions,
|
|
28
28
|
UIMessage,
|
|
29
29
|
WhenBusy,
|
|
30
|
+
SubagentClientAgent,
|
|
31
|
+
SubagentHandles,
|
|
30
32
|
} from '@tanstack/ai-client'
|
|
31
33
|
|
|
32
34
|
// Re-export types from ai-client
|
|
@@ -86,6 +88,7 @@ export type UseChatOptions<
|
|
|
86
88
|
TContext = InferredClientContext<TTools>,
|
|
87
89
|
TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>> =
|
|
88
90
|
readonly [],
|
|
91
|
+
TSubagents extends ReadonlyArray<SubagentClientAgent> | undefined = undefined,
|
|
89
92
|
> = DistributedOmit<
|
|
90
93
|
ChatClientOptions<TTools, TContext, TInterrupts>,
|
|
91
94
|
| 'onMessagesChange'
|
|
@@ -100,6 +103,7 @@ export type UseChatOptions<
|
|
|
100
103
|
| 'onRunIdChange'
|
|
101
104
|
| 'context'
|
|
102
105
|
| 'devtools'
|
|
106
|
+
| 'subagents'
|
|
103
107
|
> & {
|
|
104
108
|
/** Display options for TanStack AI Devtools. */
|
|
105
109
|
devtools?: AIDevtoolsDisplayOptions
|
|
@@ -115,6 +119,12 @@ export type UseChatOptions<
|
|
|
115
119
|
* against the schema passed to `chat({ outputSchema })` on the server route.
|
|
116
120
|
*/
|
|
117
121
|
outputSchema?: TSchema
|
|
122
|
+
/**
|
|
123
|
+
* Agents from `defineAgent`. This types `messages` parts and `subagents`.
|
|
124
|
+
* The hook does not call `run` and does not send the agents to the server.
|
|
125
|
+
* Pass the same array you give to `chat({ subagents: { agents } })`.
|
|
126
|
+
*/
|
|
127
|
+
subagents?: TSubagents
|
|
118
128
|
} & ClientContextOptionFromTools<TTools, TContext>
|
|
119
129
|
|
|
120
130
|
/**
|
|
@@ -127,10 +137,12 @@ export type UseChatReturn<
|
|
|
127
137
|
TSchema extends SchemaInput | undefined = undefined,
|
|
128
138
|
TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>> =
|
|
129
139
|
readonly [],
|
|
140
|
+
TSubagents extends ReadonlyArray<SubagentClientAgent> | undefined = undefined,
|
|
130
141
|
> = BaseUseChatReturn<
|
|
131
142
|
TTools,
|
|
132
143
|
TSchema extends SchemaInput ? InferSchemaType<TSchema> : unknown,
|
|
133
|
-
TInterrupts
|
|
144
|
+
TInterrupts,
|
|
145
|
+
TSubagents
|
|
134
146
|
> &
|
|
135
147
|
(TSchema extends SchemaInput
|
|
136
148
|
? {
|
|
@@ -155,13 +167,23 @@ interface BaseUseChatReturn<
|
|
|
155
167
|
TData = unknown,
|
|
156
168
|
TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>> =
|
|
157
169
|
readonly [],
|
|
170
|
+
TSubagents extends ReadonlyArray<SubagentClientAgent> | undefined = undefined,
|
|
158
171
|
> {
|
|
159
172
|
/**
|
|
160
173
|
* Current messages in the conversation. When `outputSchema` is supplied,
|
|
161
174
|
* `messages[i].parts.find(p => p.type === 'structured-output')` is typed
|
|
162
175
|
* with the schema's inferred shape — `data: T`, `partial: DeepPartial<T>`.
|
|
176
|
+
* When `subagents` is supplied, a `type: 'subagent'` part narrows on
|
|
177
|
+
* `subagent.name`, and that child's `messages` use the agent's tools.
|
|
178
|
+
*/
|
|
179
|
+
messages: Array<UIMessage<TTools, TData, TSubagents>>
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Live child-agent invocations, nested cards included. Each entry is the
|
|
183
|
+
* same object as the matching `part.subagent`, including `stop()`.
|
|
184
|
+
* When `subagents` is supplied, `name` is one of those agent names.
|
|
163
185
|
*/
|
|
164
|
-
|
|
186
|
+
subagents: Array<SubagentHandles<TSubagents>>
|
|
165
187
|
|
|
166
188
|
/**
|
|
167
189
|
* Send a message and get a response.
|
package/src/ui.ts
CHANGED
package/src/use-chat.ts
CHANGED
|
@@ -40,9 +40,11 @@ export function useChat<
|
|
|
40
40
|
const TInterrupts extends ReadonlyArray<
|
|
41
41
|
InterruptDefinition<any, any, any, any>
|
|
42
42
|
> = readonly [],
|
|
43
|
+
const TSubagents extends ReadonlyArray<{ name: string }> | undefined =
|
|
44
|
+
undefined,
|
|
43
45
|
>(
|
|
44
|
-
options: UseChatOptions<TTools, TSchema, TContext, TInterrupts>,
|
|
45
|
-
): UseChatReturn<TTools, TSchema, TInterrupts> {
|
|
46
|
+
options: UseChatOptions<TTools, TSchema, TContext, TInterrupts, TSubagents>,
|
|
47
|
+
): UseChatReturn<TTools, TSchema, TInterrupts, TSubagents> {
|
|
46
48
|
// The hook's identity is its `threadId`. Reload with the same `threadId`
|
|
47
49
|
// restores the same conversation. `hookId` is only a React recreation key
|
|
48
50
|
// when no `threadId` is given. It is never sent on the wire.
|
|
@@ -94,7 +96,9 @@ export function useChat<
|
|
|
94
96
|
|
|
95
97
|
// Track current options in a ref to avoid recreating client when options change
|
|
96
98
|
const optionsRef =
|
|
97
|
-
useRef<UseChatOptions<TTools, TSchema, TContext, TInterrupts>>(
|
|
99
|
+
useRef<UseChatOptions<TTools, TSchema, TContext, TInterrupts, TSubagents>>(
|
|
100
|
+
options,
|
|
101
|
+
)
|
|
98
102
|
optionsRef.current = options
|
|
99
103
|
|
|
100
104
|
const syncResumeState = useCallback((target: ChatClient | null) => {
|
|
@@ -609,12 +613,15 @@ export function useChat<
|
|
|
609
613
|
return activeStructuredPart.data as Final
|
|
610
614
|
}, [activeStructuredPart])
|
|
611
615
|
|
|
616
|
+
const subagents = useMemo(() => client.getSubagents(), [renderedMessages])
|
|
617
|
+
|
|
612
618
|
// The runtime shape unconditionally exposes partial/final; the public
|
|
613
619
|
// return type hides them when no outputSchema was supplied. TS can't
|
|
614
620
|
// structurally narrow across that conditional, so the `as` is the seam.
|
|
615
621
|
// oxlint-disable-next-line eslint-js/no-restricted-syntax -- hook return shape diverges from generic UseChatReturn<TTools, TSchema> due to conditional type on TSchema; TS can't structurally narrow
|
|
616
622
|
return {
|
|
617
623
|
messages: renderedMessages,
|
|
624
|
+
subagents,
|
|
618
625
|
sendMessage,
|
|
619
626
|
append,
|
|
620
627
|
reload,
|
|
@@ -645,5 +652,5 @@ export function useChat<
|
|
|
645
652
|
resumeInterrupts,
|
|
646
653
|
partial,
|
|
647
654
|
final,
|
|
648
|
-
} as unknown as UseChatReturn<TTools, TSchema, TInterrupts>
|
|
655
|
+
} as unknown as UseChatReturn<TTools, TSchema, TInterrupts, TSubagents>
|
|
649
656
|
}
|