@tanstack/ai-client 0.33.2 → 0.34.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/dist/esm/chat-client.d.ts +9 -1
- package/dist/esm/chat-client.js +92 -3
- package/dist/esm/chat-client.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/types.d.ts +84 -7
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/ui/selectors.js +2 -1
- package/dist/esm/ui/selectors.js.map +1 -1
- package/dist/esm/ui/types.d.ts +53 -10
- package/dist/esm/ui.d.ts +1 -1
- package/package.json +2 -2
- package/src/chat-client.ts +139 -2
- package/src/index.ts +7 -0
- package/src/types.ts +122 -6
- package/src/ui/selectors.ts +1 -0
- package/src/ui/types.ts +119 -11
- package/src/ui.ts +7 -0
package/src/chat-client.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
EventType,
|
|
2
3
|
StreamProcessor,
|
|
3
4
|
convertSchemaToJsonSchema,
|
|
4
5
|
generateMessageId,
|
|
@@ -72,6 +73,7 @@ import type {
|
|
|
72
73
|
QueueStrategy,
|
|
73
74
|
QueuedMessage,
|
|
74
75
|
SendMessageOptions,
|
|
76
|
+
SubagentHandle,
|
|
75
77
|
ToolCallPart,
|
|
76
78
|
UIMessage,
|
|
77
79
|
WhenBusy,
|
|
@@ -330,11 +332,54 @@ const REJOIN_REBUILD_TRIGGERS = new Set<string>([
|
|
|
330
332
|
'TEXT_MESSAGE_CONTENT',
|
|
331
333
|
'TOOL_CALL_START',
|
|
332
334
|
'MESSAGES_SNAPSHOT',
|
|
335
|
+
// Drop the hydrated card before this chunk creates it again. A subagent
|
|
336
|
+
// turn may have no parent text, so the text triggers arrive too late.
|
|
337
|
+
'SUBAGENT_STARTED',
|
|
333
338
|
])
|
|
334
339
|
|
|
340
|
+
type SubagentCard = Extract<UIMessage['parts'][number], { type: 'subagent' }>
|
|
341
|
+
|
|
342
|
+
/** Every subagent card in the messages, nested cards included. */
|
|
343
|
+
function collectSubagentParts(
|
|
344
|
+
messages: ReadonlyArray<UIMessage>,
|
|
345
|
+
): Array<SubagentCard> {
|
|
346
|
+
const cards: Array<SubagentCard> = []
|
|
347
|
+
for (const message of messages) {
|
|
348
|
+
for (const part of message.parts) {
|
|
349
|
+
if (part.type !== 'subagent') continue
|
|
350
|
+
cards.push(part)
|
|
351
|
+
cards.push(...collectSubagentParts(part.subagent.messages))
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return cards
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function readSubagentRunId(chunk: StreamChunk) {
|
|
358
|
+
if ('subagentRunId' in chunk && typeof chunk.subagentRunId === 'string') {
|
|
359
|
+
return chunk.subagentRunId
|
|
360
|
+
}
|
|
361
|
+
return undefined
|
|
362
|
+
}
|
|
363
|
+
|
|
335
364
|
// Cap parent-chain walks so a cyclic replayed parentRunId cannot loop.
|
|
336
365
|
const MAX_RUN_LINEAGE_DEPTH = 64
|
|
337
366
|
|
|
367
|
+
/**
|
|
368
|
+
* A deep copy of plain data. Dates stay Dates. Function-valued properties are
|
|
369
|
+
* dropped.
|
|
370
|
+
*/
|
|
371
|
+
function copyForDevtools<T>(value: T): T
|
|
372
|
+
function copyForDevtools(value: unknown): unknown {
|
|
373
|
+
if (Array.isArray(value)) return value.map(copyForDevtools)
|
|
374
|
+
if (value instanceof Date) return new Date(value.getTime())
|
|
375
|
+
if (value === null || typeof value !== 'object') return value
|
|
376
|
+
const out: Record<string, unknown> = {}
|
|
377
|
+
for (const [key, item] of Object.entries(value)) {
|
|
378
|
+
if (typeof item !== 'function') out[key] = copyForDevtools(item)
|
|
379
|
+
}
|
|
380
|
+
return out
|
|
381
|
+
}
|
|
382
|
+
|
|
338
383
|
export class ChatClient<
|
|
339
384
|
TTools extends ReadonlyArray<AnyClientTool> = any,
|
|
340
385
|
TContext = unknown,
|
|
@@ -342,6 +387,8 @@ export class ChatClient<
|
|
|
342
387
|
any,
|
|
343
388
|
> {
|
|
344
389
|
private readonly processor: StreamProcessor
|
|
390
|
+
private readonly subagentHandles = new Map<string, SubagentHandle>()
|
|
391
|
+
private readonly stoppedSubagentIds = new Set<string>()
|
|
345
392
|
private connection: SubscribeConnectionAdapter
|
|
346
393
|
private uniqueId: string
|
|
347
394
|
private threadId: string
|
|
@@ -669,9 +716,16 @@ export class ChatClient<
|
|
|
669
716
|
: {}),
|
|
670
717
|
...(initialMessages ? { initialMessages } : {}),
|
|
671
718
|
events: {
|
|
672
|
-
onMessagesChange: (messages
|
|
719
|
+
onMessagesChange: (messages) => {
|
|
720
|
+
// Restored or replaced messages bring their own cards. Give each one
|
|
721
|
+
// its live handle before anyone reads the messages.
|
|
722
|
+
this.syncSubagentHandles()
|
|
673
723
|
this.persistor?.notifyMessagesChanged(messages)
|
|
674
724
|
this.callbacksRef.current.onMessagesChange(messages)
|
|
725
|
+
// The bridge only snapshots on status changes, and the panel draws a
|
|
726
|
+
// child card from the snapshot, so without this the card would update
|
|
727
|
+
// only when the parent run ends.
|
|
728
|
+
this.queueDevtoolsSnapshot()
|
|
675
729
|
},
|
|
676
730
|
onStreamStart: () => {
|
|
677
731
|
this.setStatus('streaming')
|
|
@@ -887,6 +941,8 @@ export class ChatClient<
|
|
|
887
941
|
},
|
|
888
942
|
},
|
|
889
943
|
})
|
|
944
|
+
// `initialMessages` do not fire a change event. Give their cards handles.
|
|
945
|
+
this.syncSubagentHandles()
|
|
890
946
|
|
|
891
947
|
this.persistor?.hydrateAsync(persistedState)
|
|
892
948
|
|
|
@@ -1693,7 +1749,9 @@ export class ChatClient<
|
|
|
1693
1749
|
|
|
1694
1750
|
private getDevtoolsSnapshot(): AIDevtoolsChatSnapshot {
|
|
1695
1751
|
return {
|
|
1696
|
-
|
|
1752
|
+
// The devtools store keeps the objects it gets, and a subagent handle
|
|
1753
|
+
// changes in place. A copy lets the panel see each new status and part.
|
|
1754
|
+
messages: copyForDevtools(this.processor.getMessages()),
|
|
1697
1755
|
status: this.status,
|
|
1698
1756
|
isLoading: this.isLoading,
|
|
1699
1757
|
isSubscribed: this.isSubscribed,
|
|
@@ -1970,7 +2028,18 @@ export class ChatClient<
|
|
|
1970
2028
|
}
|
|
1971
2029
|
this.callbacksRef.current.onChunk(chunk)
|
|
1972
2030
|
this.devtoolsBridge.observeChunk(chunk)
|
|
2031
|
+
const attributedId = readSubagentRunId(chunk)
|
|
2032
|
+
// A resumed child starts again under the same id, so its start lifts the stop.
|
|
2033
|
+
if (attributedId && chunk.type === EventType.SUBAGENT_STARTED) {
|
|
2034
|
+
this.stoppedSubagentIds.delete(attributedId)
|
|
2035
|
+
}
|
|
2036
|
+
if (attributedId && this.stoppedSubagentIds.has(attributedId)) {
|
|
2037
|
+
this.updateRunLifecycle(chunk)
|
|
2038
|
+
this.resolveJoinedRun(chunk)
|
|
2039
|
+
return
|
|
2040
|
+
}
|
|
1973
2041
|
this.processor.processChunk(chunk)
|
|
2042
|
+
this.syncSubagentHandles()
|
|
1974
2043
|
this.updateRunLifecycle(chunk)
|
|
1975
2044
|
this.observeInterruptState(chunk)
|
|
1976
2045
|
// Live path: yield a macrotask so the UI can paint. Skip when the page is
|
|
@@ -2678,6 +2747,7 @@ export class ChatClient<
|
|
|
2678
2747
|
*/
|
|
2679
2748
|
stop(): void {
|
|
2680
2749
|
// Invalidate deferred work from the stopped continuation.
|
|
2750
|
+
// This aborts the local request. A durable server run keeps going.
|
|
2681
2751
|
this.continuationGeneration++
|
|
2682
2752
|
const hadLocalStream = this.abortController !== null
|
|
2683
2753
|
this.cancelInFlightStream({ setReadyStatus: true })
|
|
@@ -2715,6 +2785,7 @@ export class ChatClient<
|
|
|
2715
2785
|
this.resetHistoryPaging()
|
|
2716
2786
|
this.discardPendingSends()
|
|
2717
2787
|
this.persistor?.remove()
|
|
2788
|
+
this.stoppedSubagentIds.clear()
|
|
2718
2789
|
this.lastResume = null
|
|
2719
2790
|
this.interruptManager.reset()
|
|
2720
2791
|
this.pendingResumeThreadId = null
|
|
@@ -2974,6 +3045,72 @@ export class ChatClient<
|
|
|
2974
3045
|
return this.processor.getMessages() as Array<UIMessage<TTools>>
|
|
2975
3046
|
}
|
|
2976
3047
|
|
|
3048
|
+
getSubagents() {
|
|
3049
|
+
return [...this.subagentHandles.values()]
|
|
3050
|
+
}
|
|
3051
|
+
|
|
3052
|
+
private devtoolsSnapshotQueued = false
|
|
3053
|
+
|
|
3054
|
+
/** One devtools snapshot per microtask, however many chunks arrive. */
|
|
3055
|
+
private queueDevtoolsSnapshot(): void {
|
|
3056
|
+
if (this.devtoolsSnapshotQueued) return
|
|
3057
|
+
this.devtoolsSnapshotQueued = true
|
|
3058
|
+
queueMicrotask(() => {
|
|
3059
|
+
this.devtoolsSnapshotQueued = false
|
|
3060
|
+
this.devtoolsBridge.emitSnapshot()
|
|
3061
|
+
})
|
|
3062
|
+
}
|
|
3063
|
+
|
|
3064
|
+
private syncSubagentHandles(): void {
|
|
3065
|
+
// Every card, nested ones included, gets a handle.
|
|
3066
|
+
const cards = collectSubagentParts(this.processor.getMessages())
|
|
3067
|
+
const present = new Set(cards.map((part) => part.subagent.id))
|
|
3068
|
+
// A card that left the messages (clear, reload) loses its handle.
|
|
3069
|
+
for (const id of this.subagentHandles.keys()) {
|
|
3070
|
+
if (!present.has(id)) this.subagentHandles.delete(id)
|
|
3071
|
+
}
|
|
3072
|
+
for (const part of cards) {
|
|
3073
|
+
const id = part.subagent.id
|
|
3074
|
+
const existing = this.subagentHandles.get(id)
|
|
3075
|
+
if (existing) {
|
|
3076
|
+
if (part.subagent === existing) continue
|
|
3077
|
+
// Keep the same live object, with exactly the card's fields. The
|
|
3078
|
+
// wire reads this object, so a field the card dropped goes too.
|
|
3079
|
+
const { stop } = existing
|
|
3080
|
+
for (const key of Object.keys(existing)) {
|
|
3081
|
+
Reflect.deleteProperty(existing, key)
|
|
3082
|
+
}
|
|
3083
|
+
Object.assign(existing, part.subagent, { stop })
|
|
3084
|
+
part.subagent = existing
|
|
3085
|
+
continue
|
|
3086
|
+
}
|
|
3087
|
+
const handle: SubagentHandle = {
|
|
3088
|
+
...part.subagent,
|
|
3089
|
+
stop: () => {
|
|
3090
|
+
this.stopSubagent(id)
|
|
3091
|
+
},
|
|
3092
|
+
}
|
|
3093
|
+
this.subagentHandles.set(id, handle)
|
|
3094
|
+
part.subagent = handle
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
|
|
3098
|
+
private stopSubagent(id: string): void {
|
|
3099
|
+
const handle = this.subagentHandles.get(id)
|
|
3100
|
+
if (!handle || handle.status !== 'running') return
|
|
3101
|
+
this.stoppedSubagentIds.add(id)
|
|
3102
|
+
this.processor.processChunk({
|
|
3103
|
+
type: EventType.SUBAGENT_ERROR,
|
|
3104
|
+
subagentRunId: id,
|
|
3105
|
+
message: 'Stopped',
|
|
3106
|
+
timestamp: Date.now(),
|
|
3107
|
+
})
|
|
3108
|
+
this.syncSubagentHandles()
|
|
3109
|
+
handle.status = 'error'
|
|
3110
|
+
handle.error = { message: 'Stopped' }
|
|
3111
|
+
this.abortController?.abort()
|
|
3112
|
+
}
|
|
3113
|
+
|
|
2977
3114
|
/**
|
|
2978
3115
|
* True when the last hydrate or older-page response said more messages exist.
|
|
2979
3116
|
*/
|
package/src/index.ts
CHANGED
|
@@ -33,6 +33,13 @@ export type {
|
|
|
33
33
|
ToolResultPart,
|
|
34
34
|
ThinkingPart,
|
|
35
35
|
StructuredOutputPart,
|
|
36
|
+
SubagentPart,
|
|
37
|
+
SubagentPartOf,
|
|
38
|
+
SubagentHandle,
|
|
39
|
+
SubagentHandleOf,
|
|
40
|
+
SubagentHandles,
|
|
41
|
+
SubagentClientAgent,
|
|
42
|
+
SubagentStatus,
|
|
36
43
|
// Client configuration types
|
|
37
44
|
ChatClientPersistence,
|
|
38
45
|
ChatPersistedState,
|
package/src/types.ts
CHANGED
|
@@ -22,6 +22,8 @@ import type {
|
|
|
22
22
|
SchemaInput,
|
|
23
23
|
StreamChunk,
|
|
24
24
|
StructuredOutputPart,
|
|
25
|
+
SubagentHandleData,
|
|
26
|
+
SubagentStatus,
|
|
25
27
|
UIResourcePart,
|
|
26
28
|
VideoPart,
|
|
27
29
|
} from '@tanstack/ai/client'
|
|
@@ -514,6 +516,20 @@ export interface TextPart {
|
|
|
514
516
|
content: string
|
|
515
517
|
}
|
|
516
518
|
|
|
519
|
+
/**
|
|
520
|
+
* Tool-call part for a bare `{ name }` tool, such as a server tool
|
|
521
|
+
* definition a subagent carries. No `approval` field.
|
|
522
|
+
*/
|
|
523
|
+
type ToolCallPartForNamedTool<T extends { name: string }> = {
|
|
524
|
+
type: 'tool-call'
|
|
525
|
+
id: string
|
|
526
|
+
name: T['name']
|
|
527
|
+
arguments: string
|
|
528
|
+
input?: InferToolInput<T>
|
|
529
|
+
state: ToolCallState
|
|
530
|
+
output?: InferToolOutput<T>
|
|
531
|
+
}
|
|
532
|
+
|
|
517
533
|
/**
|
|
518
534
|
* Helper type that creates a tool-call part for a specific tool.
|
|
519
535
|
* This is a conditional type to enable proper distribution over union types,
|
|
@@ -549,7 +565,9 @@ type ToolCallPartForTool<T> = T extends AnyClientTool
|
|
|
549
565
|
: // Tools without `needsApproval: true` never carry an approval field.
|
|
550
566
|
// `& unknown` is a no-op intersection (adds nothing).
|
|
551
567
|
unknown)
|
|
552
|
-
:
|
|
568
|
+
: T extends { name: string }
|
|
569
|
+
? ToolCallPartForNamedTool<T>
|
|
570
|
+
: never
|
|
553
571
|
|
|
554
572
|
/**
|
|
555
573
|
* Fallback tool-call part type when tools are not typed
|
|
@@ -581,14 +599,14 @@ type UntypedToolCallPart = {
|
|
|
581
599
|
* }
|
|
582
600
|
* ```
|
|
583
601
|
*/
|
|
584
|
-
export type ToolCallPart<TTools extends ReadonlyArray<
|
|
602
|
+
export type ToolCallPart<TTools extends ReadonlyArray<{ name: string }> = any> =
|
|
585
603
|
// Check if we have a concrete tools array (not 'any' or 'never')
|
|
586
604
|
[TTools] extends [never]
|
|
587
605
|
? UntypedToolCallPart
|
|
588
606
|
: unknown extends TTools
|
|
589
607
|
? UntypedToolCallPart
|
|
590
608
|
: TTools extends ReadonlyArray<infer Tool>
|
|
591
|
-
? Tool extends
|
|
609
|
+
? Tool extends { name: string }
|
|
592
610
|
? ToolCallPartForTool<Tool>
|
|
593
611
|
: UntypedToolCallPart
|
|
594
612
|
: UntypedToolCallPart
|
|
@@ -610,9 +628,97 @@ export interface ThinkingPart {
|
|
|
610
628
|
content: string
|
|
611
629
|
}
|
|
612
630
|
|
|
631
|
+
export type { SubagentStatus }
|
|
632
|
+
|
|
633
|
+
export interface SubagentHandle extends SubagentHandleData {
|
|
634
|
+
/**
|
|
635
|
+
* Set by ChatClient on every card it holds, streamed, restored, or initial,
|
|
636
|
+
* nested cards included. Calling it marks the card `error` (`Stopped`),
|
|
637
|
+
* ignores that child's later chunks until the same child starts again, and
|
|
638
|
+
* aborts the local request, parent stream included. A durable server run
|
|
639
|
+
* keeps going.
|
|
640
|
+
*/
|
|
641
|
+
stop?: () => void
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
export interface SubagentPart {
|
|
645
|
+
type: 'subagent'
|
|
646
|
+
subagent: SubagentHandle
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* The slice of a server `defineAgent` result that the client reads for types.
|
|
651
|
+
* The client does not call `run`. Put tool definitions (from
|
|
652
|
+
* `toolDefinition`) in `tools` to type the child's tool calls and approvals.
|
|
653
|
+
*/
|
|
654
|
+
export type SubagentClientAgent = {
|
|
655
|
+
name: string
|
|
656
|
+
description?: string
|
|
657
|
+
tools?: ReadonlyArray<{ name: string }>
|
|
658
|
+
interrupts?: ReadonlyArray<InterruptDefinition<any, any, any, any>>
|
|
659
|
+
outputSchema?: SchemaInput
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
type AgentToolList<TAgent> = TAgent extends { tools?: infer TTools }
|
|
663
|
+
? [undefined] extends [TTools]
|
|
664
|
+
? [TTools] extends [undefined]
|
|
665
|
+
? any
|
|
666
|
+
: Exclude<TTools, undefined> extends ReadonlyArray<infer TTool>
|
|
667
|
+
? [TTool] extends [never]
|
|
668
|
+
? any
|
|
669
|
+
: Exclude<TTools, undefined>
|
|
670
|
+
: any
|
|
671
|
+
: TTools extends ReadonlyArray<infer TTool>
|
|
672
|
+
? [TTool] extends [never]
|
|
673
|
+
? any
|
|
674
|
+
: TTools
|
|
675
|
+
: any
|
|
676
|
+
: any
|
|
677
|
+
|
|
678
|
+
type AgentOutputData<TAgent> = TAgent extends { outputSchema?: infer TSchema }
|
|
679
|
+
? Exclude<TSchema, undefined> extends SchemaInput
|
|
680
|
+
? [Exclude<TSchema, undefined>] extends [never]
|
|
681
|
+
? unknown
|
|
682
|
+
: InferSchemaType<Exclude<TSchema, undefined>>
|
|
683
|
+
: unknown
|
|
684
|
+
: unknown
|
|
685
|
+
|
|
686
|
+
/** One child handle. `name` is the discriminant. */
|
|
687
|
+
export type SubagentHandleOf<TAgent extends SubagentClientAgent> = Omit<
|
|
688
|
+
SubagentHandle,
|
|
689
|
+
'name' | 'messages'
|
|
690
|
+
> & {
|
|
691
|
+
name: TAgent['name']
|
|
692
|
+
messages: Array<UIMessage<AgentToolList<TAgent>, AgentOutputData<TAgent>>>
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
export type SubagentHandles<
|
|
696
|
+
TAgents extends ReadonlyArray<SubagentClientAgent> | undefined,
|
|
697
|
+
> = [TAgents] extends [undefined]
|
|
698
|
+
? SubagentHandle
|
|
699
|
+
: unknown extends TAgents
|
|
700
|
+
? SubagentHandle
|
|
701
|
+
: TAgents extends ReadonlyArray<infer TAgent>
|
|
702
|
+
? TAgent extends SubagentClientAgent
|
|
703
|
+
? SubagentHandleOf<TAgent>
|
|
704
|
+
: SubagentHandle
|
|
705
|
+
: SubagentHandle
|
|
706
|
+
|
|
707
|
+
export type SubagentPartOf<
|
|
708
|
+
TAgents extends ReadonlyArray<SubagentClientAgent> | undefined,
|
|
709
|
+
> = [TAgents] extends [undefined]
|
|
710
|
+
? SubagentPart
|
|
711
|
+
: unknown extends TAgents
|
|
712
|
+
? SubagentPart
|
|
713
|
+
: {
|
|
714
|
+
type: 'subagent'
|
|
715
|
+
subagent: SubagentHandles<TAgents>
|
|
716
|
+
}
|
|
717
|
+
|
|
613
718
|
export type MessagePart<
|
|
614
|
-
TTools extends ReadonlyArray<
|
|
719
|
+
TTools extends ReadonlyArray<{ name: string }> = any,
|
|
615
720
|
TData = unknown,
|
|
721
|
+
TSubagents extends ReadonlyArray<SubagentClientAgent> | undefined = undefined,
|
|
616
722
|
> =
|
|
617
723
|
| TextPart
|
|
618
724
|
| ImagePart
|
|
@@ -624,6 +730,7 @@ export type MessagePart<
|
|
|
624
730
|
| ThinkingPart
|
|
625
731
|
| StructuredOutputPart<TData>
|
|
626
732
|
| UIResourcePart
|
|
733
|
+
| SubagentPartOf<TSubagents>
|
|
627
734
|
|
|
628
735
|
/**
|
|
629
736
|
* UIMessage - Domain-specific message format optimized for building chat UIs
|
|
@@ -638,13 +745,14 @@ export type MessagePart<
|
|
|
638
745
|
* is typed without manual casts.
|
|
639
746
|
*/
|
|
640
747
|
export interface UIMessage<
|
|
641
|
-
TTools extends ReadonlyArray<
|
|
748
|
+
TTools extends ReadonlyArray<{ name: string }> = any,
|
|
642
749
|
TData = unknown,
|
|
750
|
+
TSubagents extends ReadonlyArray<SubagentClientAgent> | undefined = undefined,
|
|
643
751
|
> {
|
|
644
752
|
id: string
|
|
645
753
|
role: 'system' | 'user' | 'assistant'
|
|
646
754
|
name?: string
|
|
647
|
-
parts: Array<MessagePart<TTools, TData>>
|
|
755
|
+
parts: Array<MessagePart<TTools, TData, TSubagents>>
|
|
648
756
|
createdAt?: Date
|
|
649
757
|
/**
|
|
650
758
|
* Optional AG-UI metadata bag. TanStack writes the `tanstack` key.
|
|
@@ -895,6 +1003,14 @@ export interface ChatClientBaseOptions<
|
|
|
895
1003
|
*/
|
|
896
1004
|
byok?: ByokClient
|
|
897
1005
|
|
|
1006
|
+
/**
|
|
1007
|
+
* The agents you pass to `chat({ subagents: { agents } })`, for types only.
|
|
1008
|
+
* The client does not call `run`. `createChatHook` from
|
|
1009
|
+
* `@tanstack/ai-react/ui` requires a `subagentsComponents` entry for every
|
|
1010
|
+
* agent name.
|
|
1011
|
+
*/
|
|
1012
|
+
subagents?: ReadonlyArray<SubagentClientAgent>
|
|
1013
|
+
|
|
898
1014
|
/**
|
|
899
1015
|
* Optional provider id for this chat. If it returns a provider slug,
|
|
900
1016
|
* only that key is prepared and sent. Otherwise the merged `provider`
|
package/src/ui/selectors.ts
CHANGED
|
@@ -25,6 +25,7 @@ const PART_KEY_BY_TYPE: Record<string, ChatUIPartKey> = {
|
|
|
25
25
|
'tool-result': 'toolResult',
|
|
26
26
|
'structured-output': 'structuredOutput',
|
|
27
27
|
'ui-resource': 'uiResource',
|
|
28
|
+
subagent: 'subagent',
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
export function partTypeToKey(type: string): ChatUIPartKey | string {
|
package/src/ui/types.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
ChatInterrupt,
|
|
11
11
|
MessagePart,
|
|
12
12
|
RegisteredGenericInterrupt,
|
|
13
|
+
SubagentClientAgent,
|
|
13
14
|
StructuredOutputPart,
|
|
14
15
|
ToolApprovalInterrupt,
|
|
15
16
|
ToolCallPart,
|
|
@@ -28,6 +29,7 @@ export type ChatUIPartKey =
|
|
|
28
29
|
| 'toolResult'
|
|
29
30
|
| 'structuredOutput'
|
|
30
31
|
| 'uiResource'
|
|
32
|
+
| 'subagent'
|
|
31
33
|
|
|
32
34
|
export type ChatUIPartTypeByKey = {
|
|
33
35
|
text: 'text'
|
|
@@ -40,6 +42,7 @@ export type ChatUIPartTypeByKey = {
|
|
|
40
42
|
toolResult: 'tool-result'
|
|
41
43
|
structuredOutput: 'structured-output'
|
|
42
44
|
uiResource: 'ui-resource'
|
|
45
|
+
subagent: 'subagent'
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
export type ChatUIPartOf<
|
|
@@ -60,7 +63,7 @@ export type ChatUIToolsOf<TOptions> = TOptions extends {
|
|
|
60
63
|
: any
|
|
61
64
|
: any
|
|
62
65
|
|
|
63
|
-
|
|
66
|
+
type RootInterruptsOf<TOptions> = TOptions extends {
|
|
64
67
|
interrupts: infer TInterrupts
|
|
65
68
|
}
|
|
66
69
|
? TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>>
|
|
@@ -68,6 +71,23 @@ export type ChatUIInterruptsOf<TOptions> = TOptions extends {
|
|
|
68
71
|
: readonly []
|
|
69
72
|
: readonly []
|
|
70
73
|
|
|
74
|
+
type AgentInterrupts<TOptions> = ConcreteItems<
|
|
75
|
+
AgentField<ChatUISubagentsOf<TOptions>[number], 'interrupts', readonly []>
|
|
76
|
+
>
|
|
77
|
+
|
|
78
|
+
/** The root `interrupts` plus every child agent's `interrupts`. */
|
|
79
|
+
export type ChatUIInterruptsOf<TOptions> = [AgentInterrupts<TOptions>] extends [
|
|
80
|
+
never,
|
|
81
|
+
]
|
|
82
|
+
? RootInterruptsOf<TOptions>
|
|
83
|
+
: ReadonlyArray<
|
|
84
|
+
| RootInterruptsOf<TOptions>[number]
|
|
85
|
+
| Extract<
|
|
86
|
+
AgentInterrupts<TOptions>,
|
|
87
|
+
InterruptDefinition<any, any, any, any>
|
|
88
|
+
>
|
|
89
|
+
>
|
|
90
|
+
|
|
71
91
|
export type ChatUISchemaOf<TOptions> = TOptions extends {
|
|
72
92
|
outputSchema: infer TSchema
|
|
73
93
|
}
|
|
@@ -114,14 +134,105 @@ export type ChatUIHasNamedInterrupts<TOptions> = [
|
|
|
114
134
|
? false
|
|
115
135
|
: true
|
|
116
136
|
|
|
137
|
+
/** The agents in `options.subagents`, or an empty list. */
|
|
138
|
+
export type ChatUISubagentsOf<TOptions> = TOptions extends {
|
|
139
|
+
subagents?: infer TAgents
|
|
140
|
+
}
|
|
141
|
+
? Exclude<TAgents, undefined> extends ReadonlyArray<SubagentClientAgent>
|
|
142
|
+
? Exclude<TAgents, undefined>
|
|
143
|
+
: readonly []
|
|
144
|
+
: readonly []
|
|
145
|
+
|
|
146
|
+
export type ChatUISubagentName<TOptions> =
|
|
147
|
+
ChatUISubagentsOf<TOptions>[number]['name']
|
|
148
|
+
|
|
149
|
+
/** The agent in `options.subagents` with this name. */
|
|
150
|
+
export type ChatUISubagentOf<TOptions, TName> = Extract<
|
|
151
|
+
ChatUISubagentsOf<TOptions>[number],
|
|
152
|
+
{ name: TName }
|
|
153
|
+
>
|
|
154
|
+
|
|
155
|
+
type AgentField<TAgent, TKey extends string, TFallback> = TAgent extends {
|
|
156
|
+
[K in TKey]?: infer TValue
|
|
157
|
+
}
|
|
158
|
+
? [Exclude<TValue, undefined>] extends [never]
|
|
159
|
+
? TFallback
|
|
160
|
+
: Exclude<TValue, undefined>
|
|
161
|
+
: TFallback
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* One child agent seen as chat options, so the part, tool, and interrupt
|
|
165
|
+
* types read that agent's `tools`, `interrupts`, and `outputSchema`. An
|
|
166
|
+
* unknown name gives untyped options.
|
|
167
|
+
*/
|
|
168
|
+
export type ChatUISubagentOptions<TOptions, TName> = [
|
|
169
|
+
ChatUISubagentOf<TOptions, TName>,
|
|
170
|
+
] extends [never]
|
|
171
|
+
? {}
|
|
172
|
+
: ChatUISubagentOf<TOptions, TName> extends infer TAgent
|
|
173
|
+
? {
|
|
174
|
+
tools: AgentField<TAgent, 'tools', readonly []>
|
|
175
|
+
interrupts: AgentField<TAgent, 'interrupts', readonly []>
|
|
176
|
+
outputSchema: AgentField<TAgent, 'outputSchema', undefined>
|
|
177
|
+
}
|
|
178
|
+
: {}
|
|
179
|
+
|
|
180
|
+
type ConcreteItems<TList> = unknown extends TList
|
|
181
|
+
? never
|
|
182
|
+
: TList extends ReadonlyArray<infer TItem>
|
|
183
|
+
? TItem
|
|
184
|
+
: never
|
|
185
|
+
|
|
186
|
+
type AgentTools<TOptions> = ConcreteItems<
|
|
187
|
+
AgentField<ChatUISubagentsOf<TOptions>[number], 'tools', readonly []>
|
|
188
|
+
>
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* The tools whose approvals can reach this chat: the root `tools` plus every
|
|
192
|
+
* child agent's `tools`.
|
|
193
|
+
*/
|
|
194
|
+
export type ChatUIApprovalToolsOf<TOptions> = [
|
|
195
|
+
ConcreteItems<ChatUIToolsOf<TOptions>> | AgentTools<TOptions>,
|
|
196
|
+
] extends [never]
|
|
197
|
+
? ChatUIToolsOf<TOptions>
|
|
198
|
+
: ReadonlyArray<
|
|
199
|
+
Extract<
|
|
200
|
+
ConcreteItems<ChatUIToolsOf<TOptions>> | AgentTools<TOptions>,
|
|
201
|
+
AnyClientTool
|
|
202
|
+
>
|
|
203
|
+
>
|
|
204
|
+
|
|
205
|
+
export type ChatUIApprovalToolName<TOptions> =
|
|
206
|
+
ChatUIApprovalToolsOf<TOptions>[number] extends infer TTool
|
|
207
|
+
? TTool extends AnyClientTool
|
|
208
|
+
? TTool['name']
|
|
209
|
+
: string
|
|
210
|
+
: string
|
|
211
|
+
|
|
212
|
+
export type ChatUIHasNamedSubagents<TOptions> = [
|
|
213
|
+
ChatUISubagentName<TOptions>,
|
|
214
|
+
] extends [never]
|
|
215
|
+
? false
|
|
216
|
+
: [string] extends [ChatUISubagentName<TOptions>]
|
|
217
|
+
? false
|
|
218
|
+
: true
|
|
219
|
+
|
|
117
220
|
export type ChatUINamedInterruptId<TOptions> = Exclude<
|
|
118
221
|
ChatUIRegisteredInterruptId<TOptions>,
|
|
119
222
|
'fallback'
|
|
120
223
|
>
|
|
121
224
|
|
|
225
|
+
type ChatUIHasNamedApprovalTools<TOptions> = [
|
|
226
|
+
ChatUIApprovalToolName<TOptions>,
|
|
227
|
+
] extends [never]
|
|
228
|
+
? false
|
|
229
|
+
: [string] extends [ChatUIApprovalToolName<TOptions>]
|
|
230
|
+
? false
|
|
231
|
+
: true
|
|
232
|
+
|
|
122
233
|
export type ChatUIInterruptName<TOptions> =
|
|
123
|
-
| (
|
|
124
|
-
?
|
|
234
|
+
| (ChatUIHasNamedApprovalTools<TOptions> extends true
|
|
235
|
+
? ChatUIApprovalToolName<TOptions>
|
|
125
236
|
: never)
|
|
126
237
|
| (ChatUIHasNamedInterrupts<TOptions> extends true
|
|
127
238
|
? ChatUINamedInterruptId<TOptions>
|
|
@@ -136,9 +247,9 @@ export type ChatUIInterrupt = ChatInterrupt | ToolApprovalInterrupt
|
|
|
136
247
|
|
|
137
248
|
export type ChatUIToolApproval<
|
|
138
249
|
TOptions,
|
|
139
|
-
TName extends
|
|
250
|
+
TName extends string = ChatUIApprovalToolName<TOptions>,
|
|
140
251
|
> = Extract<
|
|
141
|
-
ChatInterrupt<
|
|
252
|
+
ChatInterrupt<ChatUIApprovalToolsOf<TOptions>, ChatUIInterruptsOf<TOptions>>,
|
|
142
253
|
{ kind: 'tool-approval'; toolName: TName }
|
|
143
254
|
>
|
|
144
255
|
|
|
@@ -149,10 +260,7 @@ export type ChatUIToolPart<
|
|
|
149
260
|
key: 'toolCall'
|
|
150
261
|
part: Extract<ToolCallPart<ChatUIToolsOf<TOptions>>, { name: TName }>
|
|
151
262
|
result?: ToolResultPart
|
|
152
|
-
interrupt?:
|
|
153
|
-
ChatInterrupt<ChatUIToolsOf<TOptions>, ChatUIInterruptsOf<TOptions>>,
|
|
154
|
-
{ kind: 'tool-approval'; toolName: TName }
|
|
155
|
-
>
|
|
263
|
+
interrupt?: ChatUIToolApproval<TOptions, TName>
|
|
156
264
|
input?: InferToolInput<ToolByName<TOptions, TName>>
|
|
157
265
|
output?: InferToolOutput<ToolByName<TOptions, TName>>
|
|
158
266
|
}
|
|
@@ -172,8 +280,8 @@ export type ChatUIInterruptOf<
|
|
|
172
280
|
> = [TName] extends [never]
|
|
173
281
|
? ChatUIInterrupt
|
|
174
282
|
: TName extends (
|
|
175
|
-
|
|
176
|
-
?
|
|
283
|
+
ChatUIHasNamedApprovalTools<TOptions> extends true
|
|
284
|
+
? ChatUIApprovalToolName<TOptions>
|
|
177
285
|
: never
|
|
178
286
|
)
|
|
179
287
|
? ChatUIToolApproval<TOptions, TName>
|
package/src/ui.ts
CHANGED
|
@@ -6,8 +6,11 @@ export {
|
|
|
6
6
|
selectMessageUI,
|
|
7
7
|
} from './ui/selectors'
|
|
8
8
|
export type {
|
|
9
|
+
ChatUIApprovalToolName,
|
|
10
|
+
ChatUIApprovalToolsOf,
|
|
9
11
|
ChatUIData,
|
|
10
12
|
ChatUIHasNamedInterrupts,
|
|
13
|
+
ChatUIHasNamedSubagents,
|
|
11
14
|
ChatUIHasNamedTools,
|
|
12
15
|
ChatUIInterrupt,
|
|
13
16
|
ChatUIInterruptName,
|
|
@@ -28,6 +31,10 @@ export type {
|
|
|
28
31
|
ChatUISelectedResultPart,
|
|
29
32
|
ChatUISelectedToolPart,
|
|
30
33
|
ChatUISelection,
|
|
34
|
+
ChatUISubagentName,
|
|
35
|
+
ChatUISubagentOf,
|
|
36
|
+
ChatUISubagentOptions,
|
|
37
|
+
ChatUISubagentsOf,
|
|
31
38
|
ChatUIStructuredPart,
|
|
32
39
|
ChatUIToolApproval,
|
|
33
40
|
ChatUIToolName,
|