@assistant-ui/react 0.11.6 → 0.11.7

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.
Files changed (89) hide show
  1. package/dist/legacy-runtime/runtime/ThreadRuntime.d.ts +8 -1
  2. package/dist/legacy-runtime/runtime/ThreadRuntime.d.ts.map +1 -1
  3. package/dist/legacy-runtime/runtime/ThreadRuntime.js +5 -1
  4. package/dist/legacy-runtime/runtime/ThreadRuntime.js.map +1 -1
  5. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.d.ts +12 -0
  6. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.d.ts.map +1 -0
  7. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js +52 -0
  8. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js.map +1 -0
  9. package/dist/legacy-runtime/runtime-cores/assistant-transport/index.d.ts +3 -0
  10. package/dist/legacy-runtime/runtime-cores/assistant-transport/index.d.ts.map +1 -0
  11. package/dist/legacy-runtime/runtime-cores/assistant-transport/index.js +6 -0
  12. package/dist/legacy-runtime/runtime-cores/assistant-transport/index.js.map +1 -0
  13. package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.d.ts +11 -0
  14. package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.d.ts.map +1 -0
  15. package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js +55 -0
  16. package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js.map +1 -0
  17. package/dist/legacy-runtime/runtime-cores/assistant-transport/types.d.ts +66 -0
  18. package/dist/legacy-runtime/runtime-cores/assistant-transport/types.d.ts.map +1 -0
  19. package/dist/legacy-runtime/runtime-cores/assistant-transport/types.js +1 -0
  20. package/dist/legacy-runtime/runtime-cores/assistant-transport/types.js.map +1 -0
  21. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.d.ts +7 -0
  22. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -0
  23. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +185 -0
  24. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +1 -0
  25. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.d.ts +3 -0
  26. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.d.ts.map +1 -0
  27. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js +12 -0
  28. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js.map +1 -0
  29. package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.d.ts +4 -0
  30. package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.d.ts.map +1 -0
  31. package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.js +13 -0
  32. package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.js.map +1 -0
  33. package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.d.ts +13 -0
  34. package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.d.ts.map +1 -0
  35. package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.js +138 -0
  36. package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.js.map +1 -0
  37. package/dist/legacy-runtime/runtime-cores/assistant-transport/utils.d.ts +13 -0
  38. package/dist/legacy-runtime/runtime-cores/assistant-transport/utils.d.ts.map +1 -0
  39. package/dist/legacy-runtime/runtime-cores/assistant-transport/utils.js +32 -0
  40. package/dist/legacy-runtime/runtime-cores/assistant-transport/utils.js.map +1 -0
  41. package/dist/legacy-runtime/runtime-cores/core/BaseThreadRuntimeCore.d.ts +1 -0
  42. package/dist/legacy-runtime/runtime-cores/core/BaseThreadRuntimeCore.d.ts.map +1 -1
  43. package/dist/legacy-runtime/runtime-cores/core/BaseThreadRuntimeCore.js.map +1 -1
  44. package/dist/legacy-runtime/runtime-cores/core/ThreadRuntimeCore.d.ts +2 -1
  45. package/dist/legacy-runtime/runtime-cores/core/ThreadRuntimeCore.d.ts.map +1 -1
  46. package/dist/legacy-runtime/runtime-cores/external-store/ExternalStoreAdapter.d.ts +3 -1
  47. package/dist/legacy-runtime/runtime-cores/external-store/ExternalStoreAdapter.d.ts.map +1 -1
  48. package/dist/legacy-runtime/runtime-cores/external-store/ExternalStoreThreadRuntimeCore.d.ts +3 -2
  49. package/dist/legacy-runtime/runtime-cores/external-store/ExternalStoreThreadRuntimeCore.d.ts.map +1 -1
  50. package/dist/legacy-runtime/runtime-cores/external-store/ExternalStoreThreadRuntimeCore.js +9 -2
  51. package/dist/legacy-runtime/runtime-cores/external-store/ExternalStoreThreadRuntimeCore.js.map +1 -1
  52. package/dist/legacy-runtime/runtime-cores/external-store/ThreadMessageLike.d.ts.map +1 -1
  53. package/dist/legacy-runtime/runtime-cores/external-store/ThreadMessageLike.js +2 -2
  54. package/dist/legacy-runtime/runtime-cores/external-store/ThreadMessageLike.js.map +1 -1
  55. package/dist/legacy-runtime/runtime-cores/index.d.ts +1 -0
  56. package/dist/legacy-runtime/runtime-cores/index.d.ts.map +1 -1
  57. package/dist/legacy-runtime/runtime-cores/index.js +1 -0
  58. package/dist/legacy-runtime/runtime-cores/index.js.map +1 -1
  59. package/dist/legacy-runtime/runtime-cores/local/LocalThreadRuntimeCore.d.ts +1 -0
  60. package/dist/legacy-runtime/runtime-cores/local/LocalThreadRuntimeCore.d.ts.map +1 -1
  61. package/dist/legacy-runtime/runtime-cores/local/LocalThreadRuntimeCore.js +5 -0
  62. package/dist/legacy-runtime/runtime-cores/local/LocalThreadRuntimeCore.js.map +1 -1
  63. package/dist/legacy-runtime/runtime-cores/remote-thread-list/EMPTY_THREAD_CORE.d.ts.map +1 -1
  64. package/dist/legacy-runtime/runtime-cores/remote-thread-list/EMPTY_THREAD_CORE.js +3 -0
  65. package/dist/legacy-runtime/runtime-cores/remote-thread-list/EMPTY_THREAD_CORE.js.map +1 -1
  66. package/dist/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListHookInstanceManager.d.ts +2 -0
  67. package/dist/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  68. package/dist/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListThreadListRuntimeCore.d.ts +2 -0
  69. package/dist/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  70. package/package.json +1 -1
  71. package/src/legacy-runtime/runtime/ThreadRuntime.ts +14 -2
  72. package/src/legacy-runtime/runtime-cores/assistant-transport/commandQueue.ts +62 -0
  73. package/src/legacy-runtime/runtime-cores/assistant-transport/index.ts +2 -0
  74. package/src/legacy-runtime/runtime-cores/assistant-transport/runManager.ts +67 -0
  75. package/src/legacy-runtime/runtime-cores/assistant-transport/types.ts +96 -0
  76. package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransport.spec.md +125 -0
  77. package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.tsx +232 -0
  78. package/src/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.ts +18 -0
  79. package/src/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.ts +9 -0
  80. package/src/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.ts +173 -0
  81. package/src/legacy-runtime/runtime-cores/assistant-transport/utils.ts +42 -0
  82. package/src/legacy-runtime/runtime-cores/core/BaseThreadRuntimeCore.tsx +1 -0
  83. package/src/legacy-runtime/runtime-cores/core/ThreadRuntimeCore.tsx +2 -1
  84. package/src/legacy-runtime/runtime-cores/external-store/ExternalStoreAdapter.tsx +3 -0
  85. package/src/legacy-runtime/runtime-cores/external-store/ExternalStoreThreadRuntimeCore.tsx +13 -2
  86. package/src/legacy-runtime/runtime-cores/external-store/ThreadMessageLike.tsx +2 -5
  87. package/src/legacy-runtime/runtime-cores/index.ts +1 -0
  88. package/src/legacy-runtime/runtime-cores/local/LocalThreadRuntimeCore.tsx +6 -0
  89. package/src/legacy-runtime/runtime-cores/remote-thread-list/EMPTY_THREAD_CORE.tsx +4 -0
@@ -0,0 +1,67 @@
1
+ import { useCallback, useRef, useState } from "react";
2
+ import { useLatestRef } from "./useLatestRef";
3
+
4
+ export type RunManager = Readonly<{
5
+ isRunning: boolean;
6
+ schedule: () => void;
7
+ cancel: () => void;
8
+ }>;
9
+
10
+ export function useRunManager(config: {
11
+ onRun: (signal: AbortSignal) => Promise<void>;
12
+ onFinish?: (() => void) | undefined;
13
+ onError?: ((error: Error) => void) | undefined;
14
+ }): RunManager {
15
+ const [isRunning, setIsRunning] = useState(false);
16
+ const stateRef = useRef({
17
+ pending: false,
18
+ abortController: null as AbortController | null,
19
+ });
20
+ const onRunRef = useLatestRef(config.onRun);
21
+ const onFinishRef = useLatestRef(config.onFinish);
22
+ const onErrorRef = useLatestRef(config.onError);
23
+
24
+ const startRun = useCallback(() => {
25
+ setIsRunning(true);
26
+ stateRef.current.pending = false;
27
+ const ac = new AbortController();
28
+ stateRef.current.abortController = ac;
29
+
30
+ queueMicrotask(async () => {
31
+ try {
32
+ await onRunRef.current(ac.signal);
33
+ } catch (error) {
34
+ stateRef.current.pending = false;
35
+ onErrorRef.current?.(error as Error);
36
+ } finally {
37
+ onFinishRef.current?.();
38
+ if (stateRef.current.pending) {
39
+ startRun();
40
+ } else {
41
+ setIsRunning(false);
42
+ stateRef.current.abortController = null;
43
+ }
44
+ }
45
+ });
46
+ }, [onRunRef, onFinishRef]);
47
+
48
+ const schedule = useCallback(() => {
49
+ if (stateRef.current.abortController) {
50
+ // Coalesce multiple schedules while running into a single follow-up run.
51
+ stateRef.current.pending = true;
52
+ return;
53
+ }
54
+ startRun();
55
+ }, [startRun]);
56
+
57
+ const cancel = useCallback(() => {
58
+ stateRef.current.pending = false;
59
+ stateRef.current.abortController?.abort();
60
+ }, []);
61
+
62
+ return {
63
+ isRunning,
64
+ schedule,
65
+ cancel,
66
+ };
67
+ }
@@ -0,0 +1,96 @@
1
+ import { ReadonlyJSONValue } from "assistant-stream/utils";
2
+ import { ThreadMessage } from "../../../types";
3
+ import { AttachmentAdapter, ThreadHistoryAdapter } from "..";
4
+
5
+ // Message part types
6
+ export type TextPart = {
7
+ readonly type: "text";
8
+ readonly text: string;
9
+ };
10
+
11
+ export type ImagePart = {
12
+ readonly type: "image";
13
+ readonly image: string;
14
+ };
15
+
16
+ export type UserMessagePart = TextPart | ImagePart;
17
+
18
+ // Command types
19
+ export type AddMessageCommand = {
20
+ readonly type: "add-message";
21
+ readonly message: {
22
+ readonly role: "user";
23
+ readonly parts: readonly UserMessagePart[];
24
+ };
25
+ };
26
+
27
+ export type AddToolResultCommand = {
28
+ readonly type: "add-tool-result";
29
+ readonly toolCallId: string;
30
+ readonly toolName: string;
31
+ readonly result: ReadonlyJSONValue;
32
+ readonly isError: boolean;
33
+ readonly artifact?: ReadonlyJSONValue;
34
+ };
35
+
36
+ export type AssistantTransportCommand =
37
+ | AddMessageCommand
38
+ | AddToolResultCommand;
39
+
40
+ // State types
41
+ export type AssistantTransportState = {
42
+ readonly messages: readonly ThreadMessage[];
43
+ readonly isRunning: boolean;
44
+ };
45
+
46
+ export type AssistantTransportConnectionMetadata = {
47
+ pendingCommands: AssistantTransportCommand[];
48
+ isSending: boolean;
49
+ };
50
+
51
+ export type AssistantTransportStateConverter<T> = (
52
+ state: T,
53
+ connectionMetadata: AssistantTransportConnectionMetadata,
54
+ ) => AssistantTransportState;
55
+
56
+ // Queue types
57
+ export type CommandQueueState = {
58
+ queued: AssistantTransportCommand[];
59
+ inTransit: AssistantTransportCommand[];
60
+ };
61
+
62
+ // For now, queued items are plain commands (runConfig not supported)
63
+ export type QueuedCommand = AssistantTransportCommand;
64
+
65
+ // Task types
66
+
67
+ // Options types
68
+ export type HeadersValue = Record<string, string> | Headers;
69
+
70
+ export type AssistantTransportOptions<T> = {
71
+ initialState: T;
72
+ api: string;
73
+ resumeApi?: string;
74
+ converter: AssistantTransportStateConverter<T>;
75
+ headers: HeadersValue | (() => Promise<HeadersValue>);
76
+ body?: object;
77
+ onResponse?: (response: Response) => void;
78
+ onFinish?: () => void;
79
+ onError?: (
80
+ error: Error,
81
+ params: {
82
+ commands: AssistantTransportCommand[];
83
+ updateState: (updater: (state: T) => T) => void;
84
+ },
85
+ ) => void;
86
+ onCancel?: (params: {
87
+ commands: AssistantTransportCommand[];
88
+ updateState: (updater: (state: T) => T) => void;
89
+ }) => void;
90
+ adapters?: {
91
+ attachments?: AttachmentAdapter | undefined;
92
+ history?: ThreadHistoryAdapter | undefined;
93
+ };
94
+ };
95
+
96
+ // (no task or stream-specific types needed in this module)
@@ -0,0 +1,125 @@
1
+ useAssistantTransport
2
+
3
+ Overview
4
+
5
+ - Similar API as `useDataStreamRuntime`.
6
+ - Built on an external-store runtime; the external store issues "commands".
7
+ - Exactly one run is active at a time (single-flight).
8
+ - Runs take queued commands as input and consume an assistant stream that yields state snapshots.
9
+ - Every run flushes the entire command queue; a single run processes all pending commands.
10
+
11
+ Command Scheduling
12
+
13
+ - When commands are enqueued:
14
+ - If a run is in progress: do not start another; mark that a follow-up run is pending.
15
+ - When the current run ends: if commands were scheduled during the run, start a new run and publish them.
16
+ - If no run is in progress: start a run immediately and flush commands to the server.
17
+ - Scheduling uses `queueMicrotask` to coalesce multiple synchronous enqueues into a single run start.
18
+
19
+ Command Queue
20
+
21
+ `useCommandQueue({ onQueue() { runManager.schedule(); } })`
22
+
23
+ - `enqueue(cmd)`: Adds a command to the queue. Calls `onQueue` when transitioning from empty → non-empty (coalesced via `queueMicrotask`).
24
+ - `flush(): Command[]`: Returns all queued commands, moves them into `inTransit`, and clears the queue.
25
+ - Internal state tracks `inTransit: Command[]` and `queued: Command[]`.
26
+
27
+ Run Manager
28
+
29
+ `useRunManager({
30
+ async onRun(signal) {
31
+ const commands = commandQueue.flush();
32
+ setInTransitCommands(commands);
33
+
34
+ try {
35
+ const response = await fetch(backendUrl, { signal });
36
+ const stream = response.body
37
+ .pipeThrough(new DataStreamDecoder())
38
+ .pipeThrough(
39
+ new AssistantMessageAccumulator({
40
+ initialMessage: createInitialMessage({
41
+ unstable_state: (state.state as ReadonlyJSONValue) ?? null,
42
+ }),
43
+ }),
44
+ );
45
+
46
+ for await (const snapshot of stream) {
47
+ // Clear in-transit commands after the first response chunk.
48
+ // Use a stable empty array to avoid unnecessary re-renders.
49
+ setInTransitCommands(EMPTY_ARRAY);
50
+ setSnapshot(snapshot);
51
+ }
52
+ } catch (error) {
53
+ // Do not restore commands. Surface error to onError for state update.
54
+ callbacks.onError?.({
55
+ error,
56
+ commands: getCurrentInTransitCommands(),
57
+ updateState(updater) {
58
+ setSnapshot((prev) => updater(prev));
59
+ },
60
+ });
61
+ }
62
+
63
+ },
64
+ })`
65
+
66
+ - `schedule()`: Starts immediately if idle, or schedules at most one follow-up run to start right after the current run.
67
+ - `cancel()`: Aborts the active run via `signal` and clears any scheduled follow-up run. Does not restore commands.
68
+ - `isRunning: boolean`: Indicates whether a run is currently active (internal to scheduling).
69
+ UI-facing `isRunning` is controlled by the converter output (see Converter).
70
+ - On cancellation, invoke `callbacks.onCancel?.({ commands, updateState })` where `commands` contains all pending work at the time of cancel: `[...inTransitCommands, ...queuedCommands]`. Note: after the first snapshot arrives, `inTransitCommands` are cleared to `[]`, so cancels after first byte will not include them.
71
+ - RunConfig is not supported for now; any provided run configuration is ignored.
72
+
73
+ Converter
74
+
75
+ `useConverter({
76
+ converter,
77
+ agentState,
78
+ queuedCommands,
79
+ inTransitCommands,
80
+ })`
81
+
82
+ - Reactive pattern: do not imperatively set converted state. Maintain an `agentState` snapshot variable (updated via stream), and compute the converted UI state with a memoized converter.
83
+ - Example: `const pending = [...inTransitCommands, ...queuedCommands]; const converted = useMemo(() => converter(agentState, { pendingCommands: pending, isSending }), [agentState, pending, isSending])`
84
+ - `isSending` should be sourced from the run manager’s `isRunning` flag.
85
+ - Returns `AssistantTransportState` with `{ messages, isRunning }` derived from inputs via `converter`.
86
+ - The converter controls UI `isRunning`. Typical mapping: `isRunning = isSending`. Advanced policies are allowed (e.g., extend running while reconciling, or suppress during background tool results).
87
+ - Assistant stream deltas are applied by `AssistantMessageAccumulator`, which emits immutable full-state snapshots; no additional delta handling is required in the converter.
88
+
89
+ Tool Invocations
90
+
91
+ `useToolInvocations({
92
+ messages,
93
+ onResult(result) { commandQueue.enqueue(result); },
94
+ })`
95
+
96
+ - Uses a ToolCall differ to diff tool calls across successive snapshots (e.g., ToolCallDiffer).
97
+ - When a tool call’s argsText transitions from incomplete → complete and `result` is undefined, synthesize a tool-execution event and enqueue an `add-tool-result` command via `onResult`.
98
+ - `onResult` is for frontend function calling (client-side tool calls producing results to enqueue).
99
+ - No return value.
100
+
101
+ External Store Runtime Bridge
102
+
103
+ `useExternalStoreRuntime({
104
+ isRunning,
105
+ messages,
106
+ onNew(command) { commandQueue.enqueue(command); },
107
+ onCancel() { runManager.cancel(); },
108
+ onAddToolResult(result) { commandQueue.enqueue(result); },
109
+ })`
110
+
111
+ - `onAddToolResult` typically reflects userland-triggered results (e.g., human/tool calling) coming from the external store runtime.
112
+
113
+ Notes
114
+
115
+ - Use a stable `EMPTY_ARRAY` when clearing in-transit commands to minimize re-renders via referential equality.
116
+ - "Assistant stream" refers to the incremental response stream that yields state snapshots.
117
+
118
+ Callbacks
119
+
120
+ - `onError({ error, commands, updateState })`: invoked on network/stream errors. Commands are not restored; use `updateState(state => newState)` to reflect the error in state and/or messages. `commands` reflects the current in-transit commands at the moment of error (often `[]` after the first snapshot).
121
+ - `onCancel({ commands, updateState })`: invoked after a cancellation. Commands are not restored. `commands` contains all pending work at cancel time (`inTransitCommands` plus queued). Use `updateState` to reflect cancellation in state and/or messages. The last received snapshot remains committed.
122
+
123
+ Return Value
124
+
125
+ - `useAssistantTransport` returns the runtime object from `useExternalStoreRuntime` (e.g., `{ isRunning, messages, ... }`), rather than a custom wrapper shape.
@@ -0,0 +1,232 @@
1
+ "use client";
2
+
3
+ import {
4
+ ReadonlyJSONObject,
5
+ ReadonlyJSONValue,
6
+ asAsyncIterableStream,
7
+ } from "assistant-stream/utils";
8
+ import { AppendMessage } from "../../../types";
9
+ import { useExternalStoreRuntime } from "../external-store/useExternalStoreRuntime";
10
+ import { AssistantRuntime } from "../../runtime/AssistantRuntime";
11
+ import { AddToolResultOptions } from "../core";
12
+ import { useState, useRef, useMemo } from "react";
13
+ import {
14
+ AssistantMessageAccumulator,
15
+ DataStreamDecoder,
16
+ unstable_createInitialMessage as createInitialMessage,
17
+ } from "assistant-stream";
18
+ import {
19
+ AssistantTransportOptions,
20
+ AddMessageCommand,
21
+ AddToolResultCommand,
22
+ UserMessagePart,
23
+ QueuedCommand,
24
+ } from "./types";
25
+ import { useCommandQueue } from "./commandQueue";
26
+ import { useRunManager } from "./runManager";
27
+ import { useConvertedState } from "./useConvertedState";
28
+ import { useToolInvocations } from "./useToolInvocations";
29
+ import { toAISDKTools, getEnabledTools, createRequestHeaders } from "./utils";
30
+ import { useRemoteThreadListRuntime } from "../remote-thread-list/useRemoteThreadListRuntime";
31
+ import { InMemoryThreadListAdapter } from "../remote-thread-list/adapter/in-memory";
32
+
33
+ const useAssistantTransportThreadRuntime = <T,>(
34
+ options: AssistantTransportOptions<T>,
35
+ ): AssistantRuntime => {
36
+ const agentStateRef = useRef(options.initialState);
37
+ const [, rerender] = useState(0);
38
+ const resumeFlagRef = useRef(false);
39
+ const commandQueue = useCommandQueue({
40
+ onQueue: () => runManager.schedule(),
41
+ });
42
+
43
+ const runManager = useRunManager({
44
+ onRun: async (signal: AbortSignal) => {
45
+ const isResume = resumeFlagRef.current;
46
+ resumeFlagRef.current = false;
47
+ const commands: QueuedCommand[] = isResume ? [] : commandQueue.flush();
48
+ if (commands.length === 0 && !isResume)
49
+ throw new Error("No commands to send");
50
+
51
+ const headers = await createRequestHeaders(options.headers);
52
+ const context = runtime.thread.getModelContext();
53
+
54
+ const response = await fetch(
55
+ isResume ? options.resumeApi! : options.api,
56
+ {
57
+ method: "POST",
58
+ headers,
59
+ body: JSON.stringify({
60
+ commands,
61
+ state: agentStateRef.current,
62
+ system: context.system,
63
+ tools: context.tools
64
+ ? toAISDKTools(getEnabledTools(context.tools))
65
+ : undefined,
66
+ ...context.callSettings,
67
+ ...context.config,
68
+ ...options.body,
69
+ }),
70
+ signal,
71
+ },
72
+ );
73
+
74
+ options.onResponse?.(response);
75
+
76
+ if (!response.ok) {
77
+ throw new Error(`Status ${response.status}: ${await response.text()}`);
78
+ }
79
+
80
+ if (!response.body) {
81
+ throw new Error("Response body is null");
82
+ }
83
+
84
+ const stream = response.body
85
+ .pipeThrough(new DataStreamDecoder())
86
+ .pipeThrough(
87
+ new AssistantMessageAccumulator({
88
+ initialMessage: createInitialMessage({
89
+ unstable_state:
90
+ (agentStateRef.current as ReadonlyJSONValue) ?? null,
91
+ }),
92
+ }),
93
+ );
94
+
95
+ let markedDelivered = false;
96
+
97
+ for await (const chunk of asAsyncIterableStream(stream)) {
98
+ if (chunk.metadata.unstable_state === agentStateRef.current) continue;
99
+
100
+ if (!markedDelivered) {
101
+ commandQueue.markDelivered();
102
+ markedDelivered = true;
103
+ }
104
+
105
+ agentStateRef.current = chunk.metadata.unstable_state as T;
106
+ rerender((prev) => prev + 1);
107
+ }
108
+ },
109
+ onFinish: options.onFinish,
110
+ onError: (error) => {
111
+ if (error instanceof Error && error.name === "AbortError") {
112
+ const cmds = [
113
+ ...commandQueue.state.inTransit,
114
+ ...commandQueue.state.queued,
115
+ ];
116
+ options.onCancel?.({
117
+ commands: cmds,
118
+ updateState: (updater) => {
119
+ agentStateRef.current = updater(agentStateRef.current);
120
+ rerender((prev) => prev + 1);
121
+ },
122
+ });
123
+
124
+ commandQueue.reset();
125
+ } else {
126
+ const cmds = [...commandQueue.state.inTransit];
127
+ options.onError?.(error as Error, {
128
+ commands: cmds,
129
+ updateState: (updater) => {
130
+ agentStateRef.current = updater(agentStateRef.current);
131
+ rerender((prev) => prev + 1);
132
+ },
133
+ });
134
+ commandQueue.markDelivered();
135
+ }
136
+ },
137
+ });
138
+
139
+ // Reactive conversion of agent state + connection metadata → UI state
140
+ const pendingCommands = useMemo(
141
+ () => [...commandQueue.state.inTransit, ...commandQueue.state.queued],
142
+ [commandQueue.state],
143
+ );
144
+ const converted = useConvertedState(
145
+ options.converter,
146
+ agentStateRef.current,
147
+ pendingCommands,
148
+ runManager.isRunning,
149
+ );
150
+
151
+ // Create runtime
152
+ const runtime = useExternalStoreRuntime({
153
+ isRunning: converted.isRunning,
154
+ messages: converted.messages,
155
+ adapters: options.adapters,
156
+ onNew: async (message: AppendMessage): Promise<void> => {
157
+ // Convert AppendMessage to AddMessageCommand
158
+ const parts: UserMessagePart[] = [];
159
+
160
+ for (const content of message.content) {
161
+ if (content.type === "text") {
162
+ parts.push({ type: "text", text: content.text });
163
+ } else if (content.type === "image") {
164
+ parts.push({ type: "image", image: content.image });
165
+ }
166
+ }
167
+
168
+ const command: AddMessageCommand = {
169
+ type: "add-message",
170
+ message: {
171
+ role: "user",
172
+ parts,
173
+ },
174
+ };
175
+
176
+ commandQueue.enqueue(command);
177
+ },
178
+ onCancel: async () => {
179
+ runManager.cancel();
180
+ toolInvocations.abort();
181
+ },
182
+ onResume: async () => {
183
+ if (!options.resumeApi)
184
+ throw new Error("Must pass resumeApi to options to resume runs");
185
+
186
+ resumeFlagRef.current = true;
187
+ runManager.schedule();
188
+ },
189
+ onAddToolResult: async (
190
+ toolOptions: AddToolResultOptions,
191
+ ): Promise<void> => {
192
+ const command: AddToolResultCommand = {
193
+ type: "add-tool-result",
194
+ toolCallId: toolOptions.toolCallId,
195
+ result: toolOptions.result as ReadonlyJSONObject,
196
+ toolName: toolOptions.toolName,
197
+ isError: toolOptions.isError,
198
+ ...(toolOptions.artifact && { artifact: toolOptions.artifact }),
199
+ };
200
+
201
+ commandQueue.enqueue(command);
202
+ },
203
+ onLoadExternalState: async (state) => {
204
+ agentStateRef.current = state as T;
205
+ toolInvocations.reset();
206
+ rerender((prev) => prev + 1);
207
+ },
208
+ });
209
+
210
+ const toolInvocations = useToolInvocations({
211
+ state: converted,
212
+ getTools: () => runtime.thread.getModelContext().tools,
213
+ onResult: commandQueue.enqueue,
214
+ });
215
+
216
+ return runtime;
217
+ };
218
+
219
+ /**
220
+ * @alpha This is an experimental API that is subject to change.
221
+ */
222
+ export const useAssistantTransportRuntime = <T,>(
223
+ options: AssistantTransportOptions<T>,
224
+ ): AssistantRuntime => {
225
+ const runtime = useRemoteThreadListRuntime({
226
+ runtimeHook: function RuntimeHook() {
227
+ return useAssistantTransportThreadRuntime(options);
228
+ },
229
+ adapter: new InMemoryThreadListAdapter(),
230
+ });
231
+ return runtime;
232
+ };
@@ -0,0 +1,18 @@
1
+ import { useMemo } from "react";
2
+ import type {
3
+ AssistantTransportCommand,
4
+ AssistantTransportState,
5
+ AssistantTransportStateConverter,
6
+ } from "./types";
7
+
8
+ export function useConvertedState<T>(
9
+ converter: AssistantTransportStateConverter<T>,
10
+ agentState: T,
11
+ pendingCommands: AssistantTransportCommand[],
12
+ isSending: boolean,
13
+ ): AssistantTransportState {
14
+ return useMemo(
15
+ () => converter(agentState, { pendingCommands, isSending }),
16
+ [converter, agentState, pendingCommands, isSending],
17
+ );
18
+ }
@@ -0,0 +1,9 @@
1
+ import { useEffect, useRef } from "react";
2
+
3
+ export function useLatestRef<T>(value: T) {
4
+ const ref = useRef(value);
5
+ useEffect(() => {
6
+ ref.current = value;
7
+ }, [value]);
8
+ return ref as { current: T };
9
+ }