@assistant-ui/react 0.11.48 → 0.11.49
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/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.d.ts +1 -4
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.js +34 -12
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.js.map +1 -1
- package/dist/primitives/thread/ThreadViewport.d.ts +36 -0
- package/dist/primitives/thread/ThreadViewport.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadViewport.js +21 -12
- package/dist/primitives/thread/ThreadViewport.js.map +1 -1
- package/dist/primitives/thread/ThreadViewportSlack.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadViewportSlack.js +4 -1
- package/dist/primitives/thread/ThreadViewportSlack.js.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts +20 -2
- package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.js +21 -2
- package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
- package/package.json +2 -2
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.tsx +1 -1
- package/src/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.ts +40 -17
- package/src/primitives/thread/ThreadViewport.tsx +49 -18
- package/src/primitives/thread/ThreadViewportSlack.tsx +4 -1
- package/src/primitives/thread/useThreadViewportAutoScroll.tsx +48 -2
package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n type ReadonlyJSONObject,\n type ReadonlyJSONValue,\n asAsyncIterableStream,\n} from \"assistant-stream/utils\";\nimport { AppendMessage } from \"../../../types\";\nimport { useExternalStoreRuntime } from \"../external-store/useExternalStoreRuntime\";\nimport { AssistantRuntime } from \"../../runtime/AssistantRuntime\";\nimport { AddToolResultOptions } from \"../core\";\nimport { useState, useRef, useMemo } from \"react\";\nimport {\n AssistantMessageAccumulator,\n DataStreamDecoder,\n AssistantTransportDecoder,\n unstable_createInitialMessage as createInitialMessage,\n} from \"assistant-stream\";\nimport {\n AssistantTransportOptions,\n AddMessageCommand,\n AddToolResultCommand,\n UserMessagePart,\n QueuedCommand,\n AssistantTransportCommand,\n} from \"./types\";\nimport { useCommandQueue } from \"./commandQueue\";\nimport { useRunManager } from \"./runManager\";\nimport { useConvertedState } from \"./useConvertedState\";\nimport { ToolExecutionStatus, useToolInvocations } from \"./useToolInvocations\";\nimport { toAISDKTools, getEnabledTools, createRequestHeaders } from \"./utils\";\nimport { useRemoteThreadListRuntime } from \"../remote-thread-list/useRemoteThreadListRuntime\";\nimport { InMemoryThreadListAdapter } from \"../remote-thread-list/adapter/in-memory\";\nimport { useAssistantApi, useAssistantState } from \"../../../context/react\";\nimport { UserExternalState } from \"../../../augmentations\";\n\nconst symbolAssistantTransportExtras = Symbol(\"assistant-transport-extras\");\ntype AssistantTransportExtras = {\n [symbolAssistantTransportExtras]: true;\n sendCommand: (command: AssistantTransportCommand) => void;\n state: UserExternalState;\n};\n\nconst asAssistantTransportExtras = (\n extras: unknown,\n): AssistantTransportExtras => {\n if (\n typeof extras !== \"object\" ||\n extras == null ||\n !(symbolAssistantTransportExtras in extras)\n )\n throw new Error(\n \"This method can only be called when you are using useAssistantTransportRuntime\",\n );\n\n return extras as AssistantTransportExtras;\n};\n\nexport const useAssistantTransportSendCommand = () => {\n const api = useAssistantApi();\n\n return (command: AssistantTransportCommand) => {\n const extras = api.thread().getState().extras;\n const transportExtras = asAssistantTransportExtras(extras);\n transportExtras.sendCommand(command);\n };\n};\n\nexport function useAssistantTransportState(): UserExternalState;\nexport function useAssistantTransportState<T>(\n selector: (state: UserExternalState) => T,\n): T;\nexport function useAssistantTransportState<T>(\n selector: (state: UserExternalState) => T = (t) => t as T,\n): T | UserExternalState {\n return useAssistantState(({ thread }) =>\n selector(asAssistantTransportExtras(thread.extras).state),\n );\n}\n\nconst useAssistantTransportThreadRuntime = <T,>(\n options: AssistantTransportOptions<T>,\n): AssistantRuntime => {\n const agentStateRef = useRef(options.initialState);\n const [, rerender] = useState(0);\n const resumeFlagRef = useRef(false);\n const commandQueue = useCommandQueue({\n onQueue: () => runManager.schedule(),\n });\n\n const runManager = useRunManager({\n onRun: async (signal: AbortSignal) => {\n const isResume = resumeFlagRef.current;\n resumeFlagRef.current = false;\n const commands: QueuedCommand[] = isResume ? [] : commandQueue.flush();\n if (commands.length === 0 && !isResume)\n throw new Error(\"No commands to send\");\n\n const headers = await createRequestHeaders(options.headers);\n const context = runtime.thread.getModelContext();\n\n const response = await fetch(\n isResume ? options.resumeApi! : options.api,\n {\n method: \"POST\",\n headers,\n body: JSON.stringify({\n commands,\n state: agentStateRef.current,\n system: context.system,\n tools: context.tools\n ? toAISDKTools(getEnabledTools(context.tools))\n : undefined,\n ...context.callSettings,\n ...context.config,\n ...options.body,\n }),\n signal,\n },\n );\n\n options.onResponse?.(response);\n\n if (!response.ok) {\n throw new Error(`Status ${response.status}: ${await response.text()}`);\n }\n\n if (!response.body) {\n throw new Error(\"Response body is null\");\n }\n\n // Select decoder based on protocol option\n const protocol = options.protocol ?? \"data-stream\";\n const decoder =\n protocol === \"assistant-transport\"\n ? new AssistantTransportDecoder()\n : new DataStreamDecoder();\n\n let err: string | undefined;\n const stream = response.body.pipeThrough(decoder).pipeThrough(\n new AssistantMessageAccumulator({\n initialMessage: createInitialMessage({\n unstable_state:\n (agentStateRef.current as ReadonlyJSONValue) ?? null,\n }),\n throttle: isResume,\n onError: (error) => {\n err = error;\n },\n }),\n );\n\n let markedDelivered = false;\n\n for await (const chunk of asAsyncIterableStream(stream)) {\n if (chunk.metadata.unstable_state === agentStateRef.current) continue;\n\n if (!markedDelivered) {\n commandQueue.markDelivered();\n markedDelivered = true;\n }\n\n agentStateRef.current = chunk.metadata.unstable_state as T;\n rerender((prev) => prev + 1);\n }\n\n if (err) {\n throw new Error(err);\n }\n },\n onFinish: options.onFinish,\n onCancel: () => {\n const cmds = [\n ...commandQueue.state.inTransit,\n ...commandQueue.state.queued,\n ];\n\n commandQueue.reset();\n\n options.onCancel?.({\n commands: cmds,\n updateState: (updater) => {\n agentStateRef.current = updater(agentStateRef.current);\n rerender((prev) => prev + 1);\n },\n });\n },\n onError: async (error) => {\n const inTransitCmds = [...commandQueue.state.inTransit];\n const queuedCmds = [...commandQueue.state.queued];\n\n commandQueue.reset();\n\n try {\n await options.onError?.(error as Error, {\n commands: inTransitCmds,\n updateState: (updater) => {\n agentStateRef.current = updater(agentStateRef.current);\n rerender((prev) => prev + 1);\n },\n });\n } finally {\n options.onCancel?.({\n commands: queuedCmds,\n updateState: (updater) => {\n agentStateRef.current = updater(agentStateRef.current);\n rerender((prev) => prev + 1);\n },\n error: error as Error,\n });\n }\n },\n });\n\n // Tool execution status state\n const [toolStatuses, setToolStatuses] = useState<\n Record<string, ToolExecutionStatus>\n >({});\n\n // Reactive conversion of agent state + connection metadata → UI state\n const pendingCommands = useMemo(\n () => [...commandQueue.state.inTransit, ...commandQueue.state.queued],\n [commandQueue.state],\n );\n const converted = useConvertedState(\n options.converter,\n agentStateRef.current,\n pendingCommands,\n runManager.isRunning,\n toolStatuses,\n );\n\n // Create runtime\n const runtime = useExternalStoreRuntime({\n messages: converted.messages,\n state: converted.state,\n isRunning: converted.isRunning,\n adapters: options.adapters,\n extras: {\n [symbolAssistantTransportExtras]: true,\n sendCommand: (command: AssistantTransportCommand) => {\n commandQueue.enqueue(command);\n },\n state: agentStateRef.current as UserExternalState,\n } satisfies AssistantTransportExtras,\n onNew: async (message: AppendMessage): Promise<void> => {\n if (message.role !== \"user\")\n throw new Error(\"Only user messages are supported\");\n\n // Convert AppendMessage to AddMessageCommand\n const parts: UserMessagePart[] = [];\n\n const content = [\n ...message.content,\n ...(message.attachments?.flatMap((a) => a.content) ?? []),\n ];\n for (const contentPart of content) {\n if (contentPart.type === \"text\") {\n parts.push({ type: \"text\", text: contentPart.text });\n } else if (contentPart.type === \"image\") {\n parts.push({ type: \"image\", image: contentPart.image });\n }\n }\n\n const command: AddMessageCommand = {\n type: \"add-message\",\n message: {\n role: \"user\",\n parts,\n },\n };\n\n commandQueue.enqueue(command);\n },\n onCancel: async () => {\n runManager.cancel();\n toolInvocations.abort();\n },\n onResume: async () => {\n if (!options.resumeApi)\n throw new Error(\"Must pass resumeApi to options to resume runs\");\n\n resumeFlagRef.current = true;\n runManager.schedule();\n },\n onAddToolResult: async (\n toolOptions: AddToolResultOptions,\n ): Promise<void> => {\n const command: AddToolResultCommand = {\n type: \"add-tool-result\",\n toolCallId: toolOptions.toolCallId,\n result: toolOptions.result as ReadonlyJSONObject,\n toolName: toolOptions.toolName,\n isError: toolOptions.isError,\n ...(toolOptions.artifact && { artifact: toolOptions.artifact }),\n };\n\n commandQueue.enqueue(command);\n },\n onLoadExternalState: async (state) => {\n agentStateRef.current = state as T;\n toolInvocations.reset();\n rerender((prev) => prev + 1);\n },\n });\n\n const toolInvocations = useToolInvocations({\n state: converted,\n getTools: () => runtime.thread.getModelContext().tools,\n onResult: commandQueue.enqueue,\n setToolStatuses,\n });\n\n return runtime;\n};\n\n/**\n * @alpha This is an experimental API that is subject to change.\n */\nexport const useAssistantTransportRuntime = <T,>(\n options: AssistantTransportOptions<T>,\n): AssistantRuntime => {\n const runtime = useRemoteThreadListRuntime({\n runtimeHook: function RuntimeHook() {\n return useAssistantTransportThreadRuntime(options);\n },\n adapter: new InMemoryThreadListAdapter(),\n allowNesting: true,\n });\n return runtime;\n};\n"],"mappings":";;;AAEA;AAAA,EAGE;AAAA,OACK;AAEP,SAAS,+BAA+B;AAGxC,SAAS,UAAU,QAAQ,eAAe;AAC1C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA,iCAAiC;AAAA,OAC5B;AASP,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AAClC,SAA8B,0BAA0B;AACxD,SAAS,cAAc,iBAAiB,4BAA4B;AACpE,SAAS,kCAAkC;AAC3C,SAAS,iCAAiC;AAC1C,SAAS,iBAAiB,yBAAyB;AAGnD,IAAM,iCAAiC,uBAAO,4BAA4B;AAO1E,IAAM,6BAA6B,CACjC,WAC6B;AAC7B,MACE,OAAO,WAAW,YAClB,UAAU,QACV,EAAE,kCAAkC;AAEpC,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAEF,SAAO;AACT;AAEO,IAAM,mCAAmC,MAAM;AACpD,QAAM,MAAM,gBAAgB;AAE5B,SAAO,CAAC,YAAuC;AAC7C,UAAM,SAAS,IAAI,OAAO,EAAE,SAAS,EAAE;AACvC,UAAM,kBAAkB,2BAA2B,MAAM;AACzD,oBAAgB,YAAY,OAAO;AAAA,EACrC;AACF;AAMO,SAAS,2BACd,WAA4C,CAAC,MAAM,GAC5B;AACvB,SAAO;AAAA,IAAkB,CAAC,EAAE,OAAO,MACjC,SAAS,2BAA2B,OAAO,MAAM,EAAE,KAAK;AAAA,EAC1D;AACF;AAEA,IAAM,qCAAqC,CACzC,YACqB;AACrB,QAAM,gBAAgB,OAAO,QAAQ,YAAY;AACjD,QAAM,CAAC,EAAE,QAAQ,IAAI,SAAS,CAAC;AAC/B,QAAM,gBAAgB,OAAO,KAAK;AAClC,QAAM,eAAe,gBAAgB;AAAA,IACnC,SAAS,MAAM,WAAW,SAAS;AAAA,EACrC,CAAC;AAED,QAAM,aAAa,cAAc;AAAA,IAC/B,OAAO,OAAO,WAAwB;AACpC,YAAM,WAAW,cAAc;AAC/B,oBAAc,UAAU;AACxB,YAAM,WAA4B,WAAW,CAAC,IAAI,aAAa,MAAM;AACrE,UAAI,SAAS,WAAW,KAAK,CAAC;AAC5B,cAAM,IAAI,MAAM,qBAAqB;AAEvC,YAAM,UAAU,MAAM,qBAAqB,QAAQ,OAAO;AAC1D,YAAM,UAAU,QAAQ,OAAO,gBAAgB;AAE/C,YAAM,WAAW,MAAM;AAAA,QACrB,WAAW,QAAQ,YAAa,QAAQ;AAAA,QACxC;AAAA,UACE,QAAQ;AAAA,UACR;AAAA,UACA,MAAM,KAAK,UAAU;AAAA,YACnB;AAAA,YACA,OAAO,cAAc;AAAA,YACrB,QAAQ,QAAQ;AAAA,YAChB,OAAO,QAAQ,QACX,aAAa,gBAAgB,QAAQ,KAAK,CAAC,IAC3C;AAAA,YACJ,GAAG,QAAQ;AAAA,YACX,GAAG,QAAQ;AAAA,YACX,GAAG,QAAQ;AAAA,UACb,CAAC;AAAA,UACD;AAAA,QACF;AAAA,MACF;AAEA,cAAQ,aAAa,QAAQ;AAE7B,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,IAAI,MAAM,UAAU,SAAS,MAAM,KAAK,MAAM,SAAS,KAAK,CAAC,EAAE;AAAA,MACvE;AAEA,UAAI,CAAC,SAAS,MAAM;AAClB,cAAM,IAAI,MAAM,uBAAuB;AAAA,MACzC;AAGA,YAAM,WAAW,QAAQ,YAAY;AACrC,YAAM,UACJ,aAAa,wBACT,IAAI,0BAA0B,IAC9B,IAAI,kBAAkB;AAE5B,UAAI;AACJ,YAAM,SAAS,SAAS,KAAK,YAAY,OAAO,EAAE;AAAA,QAChD,IAAI,4BAA4B;AAAA,UAC9B,gBAAgB,qBAAqB;AAAA,YACnC,gBACG,cAAc,WAAiC;AAAA,UACpD,CAAC;AAAA,UACD,UAAU;AAAA,UACV,SAAS,CAAC,UAAU;AAClB,kBAAM;AAAA,UACR;AAAA,QACF,CAAC;AAAA,MACH;AAEA,UAAI,kBAAkB;AAEtB,uBAAiB,SAAS,sBAAsB,MAAM,GAAG;AACvD,YAAI,MAAM,SAAS,mBAAmB,cAAc,QAAS;AAE7D,YAAI,CAAC,iBAAiB;AACpB,uBAAa,cAAc;AAC3B,4BAAkB;AAAA,QACpB;AAEA,sBAAc,UAAU,MAAM,SAAS;AACvC,iBAAS,CAAC,SAAS,OAAO,CAAC;AAAA,MAC7B;AAEA,UAAI,KAAK;AACP,cAAM,IAAI,MAAM,GAAG;AAAA,MACrB;AAAA,IACF;AAAA,IACA,UAAU,QAAQ;AAAA,IAClB,UAAU,MAAM;AACd,YAAM,OAAO;AAAA,QACX,GAAG,aAAa,MAAM;AAAA,QACtB,GAAG,aAAa,MAAM;AAAA,MACxB;AAEA,mBAAa,MAAM;AAEnB,cAAQ,WAAW;AAAA,QACjB,UAAU;AAAA,QACV,aAAa,CAAC,YAAY;AACxB,wBAAc,UAAU,QAAQ,cAAc,OAAO;AACrD,mBAAS,CAAC,SAAS,OAAO,CAAC;AAAA,QAC7B;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IACA,SAAS,OAAO,UAAU;AACxB,YAAM,gBAAgB,CAAC,GAAG,aAAa,MAAM,SAAS;AACtD,YAAM,aAAa,CAAC,GAAG,aAAa,MAAM,MAAM;AAEhD,mBAAa,MAAM;AAEnB,UAAI;AACF,cAAM,QAAQ,UAAU,OAAgB;AAAA,UACtC,UAAU;AAAA,UACV,aAAa,CAAC,YAAY;AACxB,0BAAc,UAAU,QAAQ,cAAc,OAAO;AACrD,qBAAS,CAAC,SAAS,OAAO,CAAC;AAAA,UAC7B;AAAA,QACF,CAAC;AAAA,MACH,UAAE;AACA,gBAAQ,WAAW;AAAA,UACjB,UAAU;AAAA,UACV,aAAa,CAAC,YAAY;AACxB,0BAAc,UAAU,QAAQ,cAAc,OAAO;AACrD,qBAAS,CAAC,SAAS,OAAO,CAAC;AAAA,UAC7B;AAAA,UACA;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF,CAAC;AAGD,QAAM,CAAC,cAAc,eAAe,IAAI,SAEtC,CAAC,CAAC;AAGJ,QAAM,kBAAkB;AAAA,IACtB,MAAM,CAAC,GAAG,aAAa,MAAM,WAAW,GAAG,aAAa,MAAM,MAAM;AAAA,IACpE,CAAC,aAAa,KAAK;AAAA,EACrB;AACA,QAAM,YAAY;AAAA,IAChB,QAAQ;AAAA,IACR,cAAc;AAAA,IACd;AAAA,IACA,WAAW;AAAA,IACX;AAAA,EACF;AAGA,QAAM,UAAU,wBAAwB;AAAA,IACtC,UAAU,UAAU;AAAA,IACpB,OAAO,UAAU;AAAA,IACjB,WAAW,UAAU;AAAA,IACrB,UAAU,QAAQ;AAAA,IAClB,QAAQ;AAAA,MACN,CAAC,8BAA8B,GAAG;AAAA,MAClC,aAAa,CAAC,YAAuC;AACnD,qBAAa,QAAQ,OAAO;AAAA,MAC9B;AAAA,MACA,OAAO,cAAc;AAAA,IACvB;AAAA,IACA,OAAO,OAAO,YAA0C;AACtD,UAAI,QAAQ,SAAS;AACnB,cAAM,IAAI,MAAM,kCAAkC;AAGpD,YAAM,QAA2B,CAAC;AAElC,YAAM,UAAU;AAAA,QACd,GAAG,QAAQ;AAAA,QACX,GAAI,QAAQ,aAAa,QAAQ,CAAC,MAAM,EAAE,OAAO,KAAK,CAAC;AAAA,MACzD;AACA,iBAAW,eAAe,SAAS;AACjC,YAAI,YAAY,SAAS,QAAQ;AAC/B,gBAAM,KAAK,EAAE,MAAM,QAAQ,MAAM,YAAY,KAAK,CAAC;AAAA,QACrD,WAAW,YAAY,SAAS,SAAS;AACvC,gBAAM,KAAK,EAAE,MAAM,SAAS,OAAO,YAAY,MAAM,CAAC;AAAA,QACxD;AAAA,MACF;AAEA,YAAM,UAA6B;AAAA,QACjC,MAAM;AAAA,QACN,SAAS;AAAA,UACP,MAAM;AAAA,UACN;AAAA,QACF;AAAA,MACF;AAEA,mBAAa,QAAQ,OAAO;AAAA,IAC9B;AAAA,IACA,UAAU,YAAY;AACpB,iBAAW,OAAO;AAClB,sBAAgB,MAAM;AAAA,IACxB;AAAA,IACA,UAAU,YAAY;AACpB,UAAI,CAAC,QAAQ;AACX,cAAM,IAAI,MAAM,+CAA+C;AAEjE,oBAAc,UAAU;AACxB,iBAAW,SAAS;AAAA,IACtB;AAAA,IACA,iBAAiB,OACf,gBACkB;AAClB,YAAM,UAAgC;AAAA,QACpC,MAAM;AAAA,QACN,YAAY,YAAY;AAAA,QACxB,QAAQ,YAAY;AAAA,QACpB,UAAU,YAAY;AAAA,QACtB,SAAS,YAAY;AAAA,QACrB,GAAI,YAAY,YAAY,EAAE,UAAU,YAAY,SAAS;AAAA,MAC/D;AAEA,mBAAa,QAAQ,OAAO;AAAA,IAC9B;AAAA,IACA,qBAAqB,OAAO,UAAU;AACpC,oBAAc,UAAU;AACxB,sBAAgB,MAAM;AACtB,eAAS,CAAC,SAAS,OAAO,CAAC;AAAA,IAC7B;AAAA,EACF,CAAC;AAED,QAAM,kBAAkB,mBAAmB;AAAA,IACzC,OAAO;AAAA,IACP,UAAU,MAAM,QAAQ,OAAO,gBAAgB,EAAE;AAAA,IACjD,UAAU,aAAa;AAAA,IACvB;AAAA,EACF,CAAC;AAED,SAAO;AACT;AAKO,IAAM,+BAA+B,CAC1C,YACqB;AACrB,QAAM,UAAU,2BAA2B;AAAA,IACzC,aAAa,SAAS,cAAc;AAClC,aAAO,mCAAmC,OAAO;AAAA,IACnD;AAAA,IACA,SAAS,IAAI,0BAA0B;AAAA,IACvC,cAAc;AAAA,EAChB,CAAC;AACD,SAAO;AACT;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n type ReadonlyJSONObject,\n type ReadonlyJSONValue,\n asAsyncIterableStream,\n} from \"assistant-stream/utils\";\nimport { AppendMessage } from \"../../../types\";\nimport { useExternalStoreRuntime } from \"../external-store/useExternalStoreRuntime\";\nimport { AssistantRuntime } from \"../../runtime/AssistantRuntime\";\nimport { AddToolResultOptions } from \"../core\";\nimport { useState, useRef, useMemo } from \"react\";\nimport {\n AssistantMessageAccumulator,\n DataStreamDecoder,\n AssistantTransportDecoder,\n unstable_createInitialMessage as createInitialMessage,\n} from \"assistant-stream\";\nimport {\n AssistantTransportOptions,\n AddMessageCommand,\n AddToolResultCommand,\n UserMessagePart,\n QueuedCommand,\n AssistantTransportCommand,\n} from \"./types\";\nimport { useCommandQueue } from \"./commandQueue\";\nimport { useRunManager } from \"./runManager\";\nimport { useConvertedState } from \"./useConvertedState\";\nimport { ToolExecutionStatus, useToolInvocations } from \"./useToolInvocations\";\nimport { toAISDKTools, getEnabledTools, createRequestHeaders } from \"./utils\";\nimport { useRemoteThreadListRuntime } from \"../remote-thread-list/useRemoteThreadListRuntime\";\nimport { InMemoryThreadListAdapter } from \"../remote-thread-list/adapter/in-memory\";\nimport { useAssistantApi, useAssistantState } from \"../../../context/react\";\nimport { UserExternalState } from \"../../../augmentations\";\n\nconst symbolAssistantTransportExtras = Symbol(\"assistant-transport-extras\");\ntype AssistantTransportExtras = {\n [symbolAssistantTransportExtras]: true;\n sendCommand: (command: AssistantTransportCommand) => void;\n state: UserExternalState;\n};\n\nconst asAssistantTransportExtras = (\n extras: unknown,\n): AssistantTransportExtras => {\n if (\n typeof extras !== \"object\" ||\n extras == null ||\n !(symbolAssistantTransportExtras in extras)\n )\n throw new Error(\n \"This method can only be called when you are using useAssistantTransportRuntime\",\n );\n\n return extras as AssistantTransportExtras;\n};\n\nexport const useAssistantTransportSendCommand = () => {\n const api = useAssistantApi();\n\n return (command: AssistantTransportCommand) => {\n const extras = api.thread().getState().extras;\n const transportExtras = asAssistantTransportExtras(extras);\n transportExtras.sendCommand(command);\n };\n};\n\nexport function useAssistantTransportState(): UserExternalState;\nexport function useAssistantTransportState<T>(\n selector: (state: UserExternalState) => T,\n): T;\nexport function useAssistantTransportState<T>(\n selector: (state: UserExternalState) => T = (t) => t as T,\n): T | UserExternalState {\n return useAssistantState(({ thread }) =>\n selector(asAssistantTransportExtras(thread.extras).state),\n );\n}\n\nconst useAssistantTransportThreadRuntime = <T,>(\n options: AssistantTransportOptions<T>,\n): AssistantRuntime => {\n const agentStateRef = useRef(options.initialState);\n const [, rerender] = useState(0);\n const resumeFlagRef = useRef(false);\n const commandQueue = useCommandQueue({\n onQueue: () => runManager.schedule(),\n });\n\n const runManager = useRunManager({\n onRun: async (signal: AbortSignal) => {\n const isResume = resumeFlagRef.current;\n resumeFlagRef.current = false;\n const commands: QueuedCommand[] = isResume ? [] : commandQueue.flush();\n if (commands.length === 0 && !isResume)\n throw new Error(\"No commands to send\");\n\n const headers = await createRequestHeaders(options.headers);\n const context = runtime.thread.getModelContext();\n\n const response = await fetch(\n isResume ? options.resumeApi! : options.api,\n {\n method: \"POST\",\n headers,\n body: JSON.stringify({\n commands,\n state: agentStateRef.current,\n system: context.system,\n tools: context.tools\n ? toAISDKTools(getEnabledTools(context.tools))\n : undefined,\n ...context.callSettings,\n ...context.config,\n ...options.body,\n }),\n signal,\n },\n );\n\n options.onResponse?.(response);\n\n if (!response.ok) {\n throw new Error(`Status ${response.status}: ${await response.text()}`);\n }\n\n if (!response.body) {\n throw new Error(\"Response body is null\");\n }\n\n // Select decoder based on protocol option\n const protocol = options.protocol ?? \"data-stream\";\n const decoder =\n protocol === \"assistant-transport\"\n ? new AssistantTransportDecoder()\n : new DataStreamDecoder();\n\n let err: string | undefined;\n const stream = response.body.pipeThrough(decoder).pipeThrough(\n new AssistantMessageAccumulator({\n initialMessage: createInitialMessage({\n unstable_state:\n (agentStateRef.current as ReadonlyJSONValue) ?? null,\n }),\n throttle: isResume,\n onError: (error) => {\n err = error;\n },\n }),\n );\n\n let markedDelivered = false;\n\n for await (const chunk of asAsyncIterableStream(stream)) {\n if (chunk.metadata.unstable_state === agentStateRef.current) continue;\n\n if (!markedDelivered) {\n commandQueue.markDelivered();\n markedDelivered = true;\n }\n\n agentStateRef.current = chunk.metadata.unstable_state as T;\n rerender((prev) => prev + 1);\n }\n\n if (err) {\n throw new Error(err);\n }\n },\n onFinish: options.onFinish,\n onCancel: () => {\n const cmds = [\n ...commandQueue.state.inTransit,\n ...commandQueue.state.queued,\n ];\n\n commandQueue.reset();\n\n options.onCancel?.({\n commands: cmds,\n updateState: (updater) => {\n agentStateRef.current = updater(agentStateRef.current);\n rerender((prev) => prev + 1);\n },\n });\n },\n onError: async (error) => {\n const inTransitCmds = [...commandQueue.state.inTransit];\n const queuedCmds = [...commandQueue.state.queued];\n\n commandQueue.reset();\n\n try {\n await options.onError?.(error as Error, {\n commands: inTransitCmds,\n updateState: (updater) => {\n agentStateRef.current = updater(agentStateRef.current);\n rerender((prev) => prev + 1);\n },\n });\n } finally {\n options.onCancel?.({\n commands: queuedCmds,\n updateState: (updater) => {\n agentStateRef.current = updater(agentStateRef.current);\n rerender((prev) => prev + 1);\n },\n error: error as Error,\n });\n }\n },\n });\n\n // Tool execution status state\n const [toolStatuses, setToolStatuses] = useState<\n Record<string, ToolExecutionStatus>\n >({});\n\n // Reactive conversion of agent state + connection metadata → UI state\n const pendingCommands = useMemo(\n () => [...commandQueue.state.inTransit, ...commandQueue.state.queued],\n [commandQueue.state],\n );\n const converted = useConvertedState(\n options.converter,\n agentStateRef.current,\n pendingCommands,\n runManager.isRunning,\n toolStatuses,\n );\n\n // Create runtime\n const runtime = useExternalStoreRuntime({\n messages: converted.messages,\n state: converted.state,\n isRunning: converted.isRunning,\n adapters: options.adapters,\n extras: {\n [symbolAssistantTransportExtras]: true,\n sendCommand: (command: AssistantTransportCommand) => {\n commandQueue.enqueue(command);\n },\n state: agentStateRef.current as UserExternalState,\n } satisfies AssistantTransportExtras,\n onNew: async (message: AppendMessage): Promise<void> => {\n if (message.role !== \"user\")\n throw new Error(\"Only user messages are supported\");\n\n // Convert AppendMessage to AddMessageCommand\n const parts: UserMessagePart[] = [];\n\n const content = [\n ...message.content,\n ...(message.attachments?.flatMap((a) => a.content) ?? []),\n ];\n for (const contentPart of content) {\n if (contentPart.type === \"text\") {\n parts.push({ type: \"text\", text: contentPart.text });\n } else if (contentPart.type === \"image\") {\n parts.push({ type: \"image\", image: contentPart.image });\n }\n }\n\n const command: AddMessageCommand = {\n type: \"add-message\",\n message: {\n role: \"user\",\n parts,\n },\n };\n\n commandQueue.enqueue(command);\n },\n onCancel: async () => {\n runManager.cancel();\n await toolInvocations.abort();\n },\n onResume: async () => {\n if (!options.resumeApi)\n throw new Error(\"Must pass resumeApi to options to resume runs\");\n\n resumeFlagRef.current = true;\n runManager.schedule();\n },\n onAddToolResult: async (\n toolOptions: AddToolResultOptions,\n ): Promise<void> => {\n const command: AddToolResultCommand = {\n type: \"add-tool-result\",\n toolCallId: toolOptions.toolCallId,\n result: toolOptions.result as ReadonlyJSONObject,\n toolName: toolOptions.toolName,\n isError: toolOptions.isError,\n ...(toolOptions.artifact && { artifact: toolOptions.artifact }),\n };\n\n commandQueue.enqueue(command);\n },\n onLoadExternalState: async (state) => {\n agentStateRef.current = state as T;\n toolInvocations.reset();\n rerender((prev) => prev + 1);\n },\n });\n\n const toolInvocations = useToolInvocations({\n state: converted,\n getTools: () => runtime.thread.getModelContext().tools,\n onResult: commandQueue.enqueue,\n setToolStatuses,\n });\n\n return runtime;\n};\n\n/**\n * @alpha This is an experimental API that is subject to change.\n */\nexport const useAssistantTransportRuntime = <T,>(\n options: AssistantTransportOptions<T>,\n): AssistantRuntime => {\n const runtime = useRemoteThreadListRuntime({\n runtimeHook: function RuntimeHook() {\n return useAssistantTransportThreadRuntime(options);\n },\n adapter: new InMemoryThreadListAdapter(),\n allowNesting: true,\n });\n return runtime;\n};\n"],"mappings":"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","names":[]}
|
|
@@ -14,13 +14,10 @@ export type ToolExecutionStatus = {
|
|
|
14
14
|
type: "human";
|
|
15
15
|
payload: unknown;
|
|
16
16
|
};
|
|
17
|
-
} | {
|
|
18
|
-
type: "cancelled";
|
|
19
|
-
reason: string;
|
|
20
17
|
};
|
|
21
18
|
export declare function useToolInvocations({ state, getTools, onResult, setToolStatuses, }: UseToolInvocationsParams): {
|
|
22
19
|
reset: () => void;
|
|
23
|
-
abort: () => void
|
|
20
|
+
abort: () => Promise<void>;
|
|
24
21
|
resume: (toolCallId: string, payload: unknown) => void;
|
|
25
22
|
};
|
|
26
23
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useToolInvocations.d.ts","sourceRoot":"","sources":["../../../../src/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.ts"],"names":[],"mappings":"AACA,OAAO,EAKL,KAAK,IAAI,EACV,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EACV,yBAAyB,EACzB,uBAAuB,EACxB,MAAM,SAAS,CAAC;AAejB,KAAK,wBAAwB,GAAG;IAC9B,KAAK,EAAE,uBAAuB,CAAC;IAC/B,QAAQ,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,SAAS,CAAC;IACjD,QAAQ,EAAE,CAAC,OAAO,EAAE,yBAAyB,KAAK,IAAI,CAAC;IACvD,eAAe,EAAE,CACf,OAAO,EACH,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,GACnC,CAAC,CACC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,KACtC,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,KAC1C,IAAI,CAAC;CACX,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAC3B;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"useToolInvocations.d.ts","sourceRoot":"","sources":["../../../../src/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.ts"],"names":[],"mappings":"AACA,OAAO,EAKL,KAAK,IAAI,EACV,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EACV,yBAAyB,EACzB,uBAAuB,EACxB,MAAM,SAAS,CAAC;AAejB,KAAK,wBAAwB,GAAG;IAC9B,KAAK,EAAE,uBAAuB,CAAC;IAC/B,QAAQ,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,SAAS,CAAC;IACjD,QAAQ,EAAE,CAAC,OAAO,EAAE,yBAAyB,KAAK,IAAI,CAAC;IACvD,eAAe,EAAE,CACf,OAAO,EACH,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,GACnC,CAAC,CACC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,KACtC,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,KAC1C,IAAI,CAAC;CACX,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAC3B;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,CAAC;AAExE,wBAAgB,kBAAkB,CAAC,EACjC,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,eAAe,GAChB,EAAE,wBAAwB;;iBA6MP,OAAO,CAAC,IAAI,CAAC;yBAwBR,MAAM,WAAW,OAAO;EAgBhD"}
|
|
@@ -25,6 +25,8 @@ function useToolInvocations({
|
|
|
25
25
|
const lastToolStates = useRef({});
|
|
26
26
|
const humanInputRef = useRef(/* @__PURE__ */ new Map());
|
|
27
27
|
const acRef = useRef(new AbortController());
|
|
28
|
+
const executingCountRef = useRef(0);
|
|
29
|
+
const settledResolversRef = useRef([]);
|
|
28
30
|
const [controller] = useState(() => {
|
|
29
31
|
const [stream, controller2] = createAssistantStreamController();
|
|
30
32
|
const transform = unstable_toolResultStream(
|
|
@@ -47,6 +49,27 @@ function useToolInvocations({
|
|
|
47
49
|
}
|
|
48
50
|
}));
|
|
49
51
|
});
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
onExecutionStart: (toolCallId) => {
|
|
55
|
+
executingCountRef.current++;
|
|
56
|
+
setToolStatuses((prev) => ({
|
|
57
|
+
...prev,
|
|
58
|
+
[toolCallId]: { type: "executing" }
|
|
59
|
+
}));
|
|
60
|
+
},
|
|
61
|
+
onExecutionEnd: (toolCallId) => {
|
|
62
|
+
executingCountRef.current--;
|
|
63
|
+
setToolStatuses((prev) => {
|
|
64
|
+
const next = { ...prev };
|
|
65
|
+
delete next[toolCallId];
|
|
66
|
+
return next;
|
|
67
|
+
});
|
|
68
|
+
if (executingCountRef.current === 0) {
|
|
69
|
+
settledResolversRef.current.forEach((resolve) => resolve());
|
|
70
|
+
settledResolversRef.current = [];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
50
73
|
}
|
|
51
74
|
);
|
|
52
75
|
stream.pipeThrough(transform).pipeThrough(new AssistantMetaTransformStream()).pipeTo(
|
|
@@ -63,11 +86,6 @@ function useToolInvocations({
|
|
|
63
86
|
isError: chunk.isError,
|
|
64
87
|
...chunk.artifact && { artifact: chunk.artifact }
|
|
65
88
|
});
|
|
66
|
-
setToolStatuses((prev) => {
|
|
67
|
-
const next = { ...prev };
|
|
68
|
-
delete next[chunk.meta.toolCallId];
|
|
69
|
-
return next;
|
|
70
|
-
});
|
|
71
89
|
}
|
|
72
90
|
}
|
|
73
91
|
})
|
|
@@ -168,13 +186,18 @@ function useToolInvocations({
|
|
|
168
186
|
reject(new Error("Tool execution aborted"));
|
|
169
187
|
});
|
|
170
188
|
humanInputRef.current.clear();
|
|
171
|
-
setToolStatuses({});
|
|
172
189
|
acRef.current.abort();
|
|
173
190
|
acRef.current = new AbortController();
|
|
191
|
+
if (executingCountRef.current === 0) {
|
|
192
|
+
return Promise.resolve();
|
|
193
|
+
}
|
|
194
|
+
return new Promise((resolve) => {
|
|
195
|
+
settledResolversRef.current.push(resolve);
|
|
196
|
+
});
|
|
174
197
|
};
|
|
175
198
|
return {
|
|
176
199
|
reset: () => {
|
|
177
|
-
abort();
|
|
200
|
+
void abort();
|
|
178
201
|
isInitialState.current = true;
|
|
179
202
|
},
|
|
180
203
|
abort,
|
|
@@ -182,11 +205,10 @@ function useToolInvocations({
|
|
|
182
205
|
const handlers = humanInputRef.current.get(toolCallId);
|
|
183
206
|
if (handlers) {
|
|
184
207
|
humanInputRef.current.delete(toolCallId);
|
|
185
|
-
setToolStatuses((prev) => {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
});
|
|
208
|
+
setToolStatuses((prev) => ({
|
|
209
|
+
...prev,
|
|
210
|
+
[toolCallId]: { type: "executing" }
|
|
211
|
+
}));
|
|
190
212
|
handlers.resolve(payload);
|
|
191
213
|
} else {
|
|
192
214
|
throw new Error(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport {\n createAssistantStreamController,\n type ToolCallStreamController,\n ToolResponse,\n unstable_toolResultStream,\n type Tool,\n} from \"assistant-stream\";\nimport type {\n AssistantTransportCommand,\n AssistantTransportState,\n} from \"./types\";\nimport {\n AssistantMetaTransformStream,\n type ReadonlyJSONValue,\n} from \"assistant-stream/utils\";\n\nconst isArgsTextComplete = (argsText: string) => {\n try {\n JSON.parse(argsText);\n return true;\n } catch {\n return false;\n }\n};\n\ntype UseToolInvocationsParams = {\n state: AssistantTransportState;\n getTools: () => Record<string, Tool> | undefined;\n onResult: (command: AssistantTransportCommand) => void;\n setToolStatuses: (\n updater:\n | Record<string, ToolExecutionStatus>\n | ((\n prev: Record<string, ToolExecutionStatus>,\n ) => Record<string, ToolExecutionStatus>),\n ) => void;\n};\n\nexport type ToolExecutionStatus =\n | { type: \"executing\" }\n | { type: \"interrupt\"; payload: { type: \"human\"; payload: unknown } }\n | { type: \"cancelled\"; reason: string };\n\nexport function useToolInvocations({\n state,\n getTools,\n onResult,\n setToolStatuses,\n}: UseToolInvocationsParams) {\n const lastToolStates = useRef<\n Record<\n string,\n {\n argsText: string;\n hasResult: boolean;\n argsComplete: boolean;\n controller: ToolCallStreamController;\n }\n >\n >({});\n\n const humanInputRef = useRef<\n Map<\n string,\n {\n resolve: (payload: unknown) => void;\n reject: (reason: unknown) => void;\n }\n >\n >(new Map());\n\n const acRef = useRef<AbortController>(new AbortController());\n const [controller] = useState(() => {\n const [stream, controller] = createAssistantStreamController();\n const transform = unstable_toolResultStream(\n getTools,\n () => acRef.current?.signal ?? new AbortController().signal,\n (toolCallId: string, payload: unknown) => {\n return new Promise<unknown>((resolve, reject) => {\n // Reject previous human input request if it exists\n const previous = humanInputRef.current.get(toolCallId);\n if (previous) {\n previous.reject(\n new Error(\"Human input request was superseded by a new request\"),\n );\n }\n\n humanInputRef.current.set(toolCallId, { resolve, reject });\n setToolStatuses((prev) => ({\n ...prev,\n [toolCallId]: {\n type: \"interrupt\",\n payload: { type: \"human\", payload },\n },\n }));\n });\n },\n );\n stream\n .pipeThrough(transform)\n .pipeThrough(new AssistantMetaTransformStream())\n .pipeTo(\n new WritableStream({\n write(chunk) {\n if (chunk.type === \"result\") {\n // the tool call result was already set by the backend\n if (lastToolStates.current[chunk.meta.toolCallId]?.hasResult)\n return;\n\n onResult({\n type: \"add-tool-result\",\n toolCallId: chunk.meta.toolCallId,\n toolName: chunk.meta.toolName,\n result: chunk.result,\n isError: chunk.isError,\n ...(chunk.artifact && { artifact: chunk.artifact }),\n });\n\n // Clear status when result is set\n setToolStatuses((prev) => {\n const next = { ...prev };\n delete next[chunk.meta.toolCallId];\n return next;\n });\n }\n },\n }),\n );\n\n return controller;\n });\n\n const ignoredToolIds = useRef<Set<string>>(new Set());\n const isInitialState = useRef(true);\n\n useEffect(() => {\n const processMessages = (\n messages: readonly (typeof state.messages)[number][],\n ) => {\n messages.forEach((message) => {\n message.content.forEach((content) => {\n if (content.type === \"tool-call\") {\n if (isInitialState.current) {\n ignoredToolIds.current.add(content.toolCallId);\n } else {\n if (ignoredToolIds.current.has(content.toolCallId)) {\n return;\n }\n let lastState = lastToolStates.current[content.toolCallId];\n if (!lastState) {\n const toolCallController = controller.addToolCallPart({\n toolName: content.toolName,\n toolCallId: content.toolCallId,\n });\n lastState = {\n argsText: \"\",\n hasResult: false,\n argsComplete: false,\n controller: toolCallController,\n };\n lastToolStates.current[content.toolCallId] = lastState;\n }\n\n if (content.argsText !== lastState.argsText) {\n if (lastState.argsComplete) {\n if (process.env[\"NODE_ENV\"] !== \"production\") {\n console.warn(\n \"argsText updated after controller was closed:\",\n {\n previous: lastState.argsText,\n next: content.argsText,\n },\n );\n }\n } else {\n if (!content.argsText.startsWith(lastState.argsText)) {\n throw new Error(\n `Tool call argsText can only be appended, not updated: ${content.argsText} does not start with ${lastState.argsText}`,\n );\n }\n\n const argsTextDelta = content.argsText.slice(\n lastState.argsText.length,\n );\n lastState.controller.argsText.append(argsTextDelta);\n\n const shouldClose = isArgsTextComplete(content.argsText);\n if (shouldClose) {\n lastState.controller.argsText.close();\n }\n\n lastToolStates.current[content.toolCallId] = {\n argsText: content.argsText,\n hasResult: lastState.hasResult,\n argsComplete: shouldClose,\n controller: lastState.controller,\n };\n }\n }\n\n if (content.result !== undefined && !lastState.hasResult) {\n lastState.controller.setResponse(\n new ToolResponse({\n result: content.result as ReadonlyJSONValue,\n artifact: content.artifact as ReadonlyJSONValue | undefined,\n isError: content.isError,\n }),\n );\n lastState.controller.close();\n\n lastToolStates.current[content.toolCallId] = {\n hasResult: true,\n argsComplete: true,\n argsText: lastState.argsText,\n controller: lastState.controller,\n };\n }\n }\n\n // Recursively process nested messages\n if (content.messages) {\n processMessages(content.messages);\n }\n }\n });\n });\n };\n\n processMessages(state.messages);\n\n if (isInitialState.current) {\n isInitialState.current = false;\n }\n }, [state, controller, onResult]);\n\n const abort = () => {\n humanInputRef.current.forEach(({ reject }) => {\n reject(new Error(\"Tool execution aborted\"));\n });\n humanInputRef.current.clear();\n setToolStatuses({});\n\n acRef.current.abort();\n acRef.current = new AbortController();\n };\n\n return {\n reset: () => {\n abort();\n isInitialState.current = true;\n },\n abort,\n resume: (toolCallId: string, payload: unknown) => {\n const handlers = humanInputRef.current.get(toolCallId);\n if (handlers) {\n humanInputRef.current.delete(toolCallId);\n setToolStatuses((prev) => {\n const next = { ...prev };\n delete next[toolCallId];\n return next;\n });\n handlers.resolve(payload);\n } else {\n throw new Error(\n `Tool call ${toolCallId} is not waiting for human input`,\n );\n }\n },\n };\n}\n"],"mappings":"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","names":["controller"]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport {\n createAssistantStreamController,\n type ToolCallStreamController,\n ToolResponse,\n unstable_toolResultStream,\n type Tool,\n} from \"assistant-stream\";\nimport type {\n AssistantTransportCommand,\n AssistantTransportState,\n} from \"./types\";\nimport {\n AssistantMetaTransformStream,\n type ReadonlyJSONValue,\n} from \"assistant-stream/utils\";\n\nconst isArgsTextComplete = (argsText: string) => {\n try {\n JSON.parse(argsText);\n return true;\n } catch {\n return false;\n }\n};\n\ntype UseToolInvocationsParams = {\n state: AssistantTransportState;\n getTools: () => Record<string, Tool> | undefined;\n onResult: (command: AssistantTransportCommand) => void;\n setToolStatuses: (\n updater:\n | Record<string, ToolExecutionStatus>\n | ((\n prev: Record<string, ToolExecutionStatus>,\n ) => Record<string, ToolExecutionStatus>),\n ) => void;\n};\n\nexport type ToolExecutionStatus =\n | { type: \"executing\" }\n | { type: \"interrupt\"; payload: { type: \"human\"; payload: unknown } };\n\nexport function useToolInvocations({\n state,\n getTools,\n onResult,\n setToolStatuses,\n}: UseToolInvocationsParams) {\n const lastToolStates = useRef<\n Record<\n string,\n {\n argsText: string;\n hasResult: boolean;\n argsComplete: boolean;\n controller: ToolCallStreamController;\n }\n >\n >({});\n\n const humanInputRef = useRef<\n Map<\n string,\n {\n resolve: (payload: unknown) => void;\n reject: (reason: unknown) => void;\n }\n >\n >(new Map());\n\n const acRef = useRef<AbortController>(new AbortController());\n const executingCountRef = useRef(0);\n const settledResolversRef = useRef<Array<() => void>>([]);\n\n const [controller] = useState(() => {\n const [stream, controller] = createAssistantStreamController();\n const transform = unstable_toolResultStream(\n getTools,\n () => acRef.current?.signal ?? new AbortController().signal,\n (toolCallId: string, payload: unknown) => {\n return new Promise<unknown>((resolve, reject) => {\n // Reject previous human input request if it exists\n const previous = humanInputRef.current.get(toolCallId);\n if (previous) {\n previous.reject(\n new Error(\"Human input request was superseded by a new request\"),\n );\n }\n\n humanInputRef.current.set(toolCallId, { resolve, reject });\n setToolStatuses((prev) => ({\n ...prev,\n [toolCallId]: {\n type: \"interrupt\",\n payload: { type: \"human\", payload },\n },\n }));\n });\n },\n {\n onExecutionStart: (toolCallId: string) => {\n executingCountRef.current++;\n setToolStatuses((prev) => ({\n ...prev,\n [toolCallId]: { type: \"executing\" },\n }));\n },\n onExecutionEnd: (toolCallId: string) => {\n executingCountRef.current--;\n setToolStatuses((prev) => {\n const next = { ...prev };\n delete next[toolCallId];\n return next;\n });\n // Resolve any waiting abort promises when all tools have settled\n if (executingCountRef.current === 0) {\n settledResolversRef.current.forEach((resolve) => resolve());\n settledResolversRef.current = [];\n }\n },\n },\n );\n stream\n .pipeThrough(transform)\n .pipeThrough(new AssistantMetaTransformStream())\n .pipeTo(\n new WritableStream({\n write(chunk) {\n if (chunk.type === \"result\") {\n // the tool call result was already set by the backend\n if (lastToolStates.current[chunk.meta.toolCallId]?.hasResult)\n return;\n\n onResult({\n type: \"add-tool-result\",\n toolCallId: chunk.meta.toolCallId,\n toolName: chunk.meta.toolName,\n result: chunk.result,\n isError: chunk.isError,\n ...(chunk.artifact && { artifact: chunk.artifact }),\n });\n }\n },\n }),\n );\n\n return controller;\n });\n\n const ignoredToolIds = useRef<Set<string>>(new Set());\n const isInitialState = useRef(true);\n\n useEffect(() => {\n const processMessages = (\n messages: readonly (typeof state.messages)[number][],\n ) => {\n messages.forEach((message) => {\n message.content.forEach((content) => {\n if (content.type === \"tool-call\") {\n if (isInitialState.current) {\n ignoredToolIds.current.add(content.toolCallId);\n } else {\n if (ignoredToolIds.current.has(content.toolCallId)) {\n return;\n }\n let lastState = lastToolStates.current[content.toolCallId];\n if (!lastState) {\n const toolCallController = controller.addToolCallPart({\n toolName: content.toolName,\n toolCallId: content.toolCallId,\n });\n lastState = {\n argsText: \"\",\n hasResult: false,\n argsComplete: false,\n controller: toolCallController,\n };\n lastToolStates.current[content.toolCallId] = lastState;\n }\n\n if (content.argsText !== lastState.argsText) {\n if (lastState.argsComplete) {\n if (process.env[\"NODE_ENV\"] !== \"production\") {\n console.warn(\n \"argsText updated after controller was closed:\",\n {\n previous: lastState.argsText,\n next: content.argsText,\n },\n );\n }\n } else {\n if (!content.argsText.startsWith(lastState.argsText)) {\n throw new Error(\n `Tool call argsText can only be appended, not updated: ${content.argsText} does not start with ${lastState.argsText}`,\n );\n }\n\n const argsTextDelta = content.argsText.slice(\n lastState.argsText.length,\n );\n lastState.controller.argsText.append(argsTextDelta);\n\n const shouldClose = isArgsTextComplete(content.argsText);\n if (shouldClose) {\n lastState.controller.argsText.close();\n }\n\n lastToolStates.current[content.toolCallId] = {\n argsText: content.argsText,\n hasResult: lastState.hasResult,\n argsComplete: shouldClose,\n controller: lastState.controller,\n };\n }\n }\n\n if (content.result !== undefined && !lastState.hasResult) {\n lastState.controller.setResponse(\n new ToolResponse({\n result: content.result as ReadonlyJSONValue,\n artifact: content.artifact as ReadonlyJSONValue | undefined,\n isError: content.isError,\n }),\n );\n lastState.controller.close();\n\n lastToolStates.current[content.toolCallId] = {\n hasResult: true,\n argsComplete: true,\n argsText: lastState.argsText,\n controller: lastState.controller,\n };\n }\n }\n\n // Recursively process nested messages\n if (content.messages) {\n processMessages(content.messages);\n }\n }\n });\n });\n };\n\n processMessages(state.messages);\n\n if (isInitialState.current) {\n isInitialState.current = false;\n }\n }, [state, controller, onResult]);\n\n const abort = (): Promise<void> => {\n humanInputRef.current.forEach(({ reject }) => {\n reject(new Error(\"Tool execution aborted\"));\n });\n humanInputRef.current.clear();\n\n acRef.current.abort();\n acRef.current = new AbortController();\n\n // Return a promise that resolves when all executing tools have settled\n if (executingCountRef.current === 0) {\n return Promise.resolve();\n }\n return new Promise<void>((resolve) => {\n settledResolversRef.current.push(resolve);\n });\n };\n\n return {\n reset: () => {\n void abort();\n isInitialState.current = true;\n },\n abort,\n resume: (toolCallId: string, payload: unknown) => {\n const handlers = humanInputRef.current.get(toolCallId);\n if (handlers) {\n humanInputRef.current.delete(toolCallId);\n setToolStatuses((prev) => ({\n ...prev,\n [toolCallId]: { type: \"executing\" },\n }));\n handlers.resolve(payload);\n } else {\n throw new Error(\n `Tool call ${toolCallId} is not waiting for human input`,\n );\n }\n },\n };\n}\n"],"mappings":"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","names":["controller"]}
|
|
@@ -16,6 +16,24 @@ export declare namespace ThreadPrimitiveViewport {
|
|
|
16
16
|
* - "top": New user messages anchor at the top of the viewport for a focused reading experience.
|
|
17
17
|
*/
|
|
18
18
|
turnAnchor?: "top" | "bottom" | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Whether to scroll to bottom when a new run starts.
|
|
21
|
+
*
|
|
22
|
+
* Defaults to true.
|
|
23
|
+
*/
|
|
24
|
+
scrollToBottomOnRunStart?: boolean | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Whether to scroll to bottom when thread history is first loaded.
|
|
27
|
+
*
|
|
28
|
+
* Defaults to true.
|
|
29
|
+
*/
|
|
30
|
+
scrollToBottomOnInitialize?: boolean | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Whether to scroll to bottom when switching to a different thread.
|
|
33
|
+
*
|
|
34
|
+
* Defaults to true.
|
|
35
|
+
*/
|
|
36
|
+
scrollToBottomOnThreadSwitch?: boolean | undefined;
|
|
19
37
|
};
|
|
20
38
|
}
|
|
21
39
|
/**
|
|
@@ -48,5 +66,23 @@ export declare const ThreadPrimitiveViewport: import("react").ForwardRefExoticCo
|
|
|
48
66
|
* - "top": New user messages anchor at the top of the viewport for a focused reading experience.
|
|
49
67
|
*/
|
|
50
68
|
turnAnchor?: "top" | "bottom" | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* Whether to scroll to bottom when a new run starts.
|
|
71
|
+
*
|
|
72
|
+
* Defaults to true.
|
|
73
|
+
*/
|
|
74
|
+
scrollToBottomOnRunStart?: boolean | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* Whether to scroll to bottom when thread history is first loaded.
|
|
77
|
+
*
|
|
78
|
+
* Defaults to true.
|
|
79
|
+
*/
|
|
80
|
+
scrollToBottomOnInitialize?: boolean | undefined;
|
|
81
|
+
/**
|
|
82
|
+
* Whether to scroll to bottom when switching to a different thread.
|
|
83
|
+
*
|
|
84
|
+
* Defaults to true.
|
|
85
|
+
*/
|
|
86
|
+
scrollToBottomOnThreadSwitch?: boolean | undefined;
|
|
51
87
|
} & import("react").RefAttributes<HTMLDivElement>>;
|
|
52
88
|
//# sourceMappingURL=ThreadViewport.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThreadViewport.d.ts","sourceRoot":"","sources":["../../../src/primitives/thread/ThreadViewport.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EACL,KAAK,YAAY,EAEjB,wBAAwB,EAEzB,MAAM,OAAO,CAAC;AAMf,yBAAiB,uBAAuB,CAAC;IACvC,KAAY,OAAO,GAAG,YAAY,CAAC,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC;IACzD,KAAY,KAAK,GAAG,wBAAwB,CAAC,OAAO,SAAS,CAAC,GAAG,CAAC,GAAG;QACnE;;;;;WAKG;QACH,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QAEjC;;;;WAIG;QACH,UAAU,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"ThreadViewport.d.ts","sourceRoot":"","sources":["../../../src/primitives/thread/ThreadViewport.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EACL,KAAK,YAAY,EAEjB,wBAAwB,EAEzB,MAAM,OAAO,CAAC;AAMf,yBAAiB,uBAAuB,CAAC;IACvC,KAAY,OAAO,GAAG,YAAY,CAAC,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC;IACzD,KAAY,KAAK,GAAG,wBAAwB,CAAC,OAAO,SAAS,CAAC,GAAG,CAAC,GAAG;QACnE;;;;;WAKG;QACH,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QAEjC;;;;WAIG;QACH,UAAU,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,SAAS,CAAC;QAE1C;;;;WAIG;QACH,wBAAwB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QAE/C;;;;WAIG;QACH,0BAA0B,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QAEjD;;;;WAIG;QACH,4BAA4B,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;KACpD,CAAC;CACH;AA2CD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,uBAAuB;;;IA7FhC;;;;;OAKG;iBACU,OAAO,GAAG,SAAS;IAEhC;;;;OAIG;iBACU,KAAK,GAAG,QAAQ,GAAG,SAAS;IAEzC;;;;OAIG;+BACwB,OAAO,GAAG,SAAS;IAE9C;;;;OAIG;iCAC0B,OAAO,GAAG,SAAS;IAEhD;;;;OAIG;mCAC4B,OAAO,GAAG,SAAS;kDAoEpD,CAAC"}
|
|
@@ -14,20 +14,29 @@ import { useThreadViewport } from "../../context/react/ThreadViewportContext.js"
|
|
|
14
14
|
import { jsx } from "react/jsx-runtime";
|
|
15
15
|
var useViewportSizeRef = () => {
|
|
16
16
|
const register = useThreadViewport((s) => s.registerViewport);
|
|
17
|
-
const getHeight = useCallback(
|
|
18
|
-
(el) => el.clientHeight - parseFloat(getComputedStyle(el).paddingTop),
|
|
19
|
-
[]
|
|
20
|
-
);
|
|
17
|
+
const getHeight = useCallback((el) => el.clientHeight, []);
|
|
21
18
|
return useSizeHandle(register, getHeight);
|
|
22
19
|
};
|
|
23
|
-
var ThreadPrimitiveViewportScrollable = forwardRef(
|
|
24
|
-
|
|
25
|
-
autoScroll
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
20
|
+
var ThreadPrimitiveViewportScrollable = forwardRef(
|
|
21
|
+
({
|
|
22
|
+
autoScroll,
|
|
23
|
+
scrollToBottomOnRunStart,
|
|
24
|
+
scrollToBottomOnInitialize,
|
|
25
|
+
scrollToBottomOnThreadSwitch,
|
|
26
|
+
children,
|
|
27
|
+
...rest
|
|
28
|
+
}, forwardedRef) => {
|
|
29
|
+
const autoScrollRef = useThreadViewportAutoScroll({
|
|
30
|
+
autoScroll,
|
|
31
|
+
scrollToBottomOnRunStart,
|
|
32
|
+
scrollToBottomOnInitialize,
|
|
33
|
+
scrollToBottomOnThreadSwitch
|
|
34
|
+
});
|
|
35
|
+
const viewportSizeRef = useViewportSizeRef();
|
|
36
|
+
const ref = useComposedRefs(forwardedRef, autoScrollRef, viewportSizeRef);
|
|
37
|
+
return /* @__PURE__ */ jsx(Primitive.div, { ...rest, ref, children });
|
|
38
|
+
}
|
|
39
|
+
);
|
|
31
40
|
ThreadPrimitiveViewportScrollable.displayName = "ThreadPrimitive.ViewportScrollable";
|
|
32
41
|
var ThreadPrimitiveViewport = forwardRef(({ turnAnchor, ...props }, ref) => {
|
|
33
42
|
return /* @__PURE__ */ jsx(ThreadPrimitiveViewportProvider, { options: { turnAnchor }, children: /* @__PURE__ */ jsx(ThreadPrimitiveViewportScrollable, { ...props, ref }) });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/primitives/thread/ThreadViewport.tsx"],"sourcesContent":["\"use client\";\n\nimport { useComposedRefs } from \"@radix-ui/react-compose-refs\";\nimport { Primitive } from \"@radix-ui/react-primitive\";\nimport {\n type ComponentRef,\n forwardRef,\n ComponentPropsWithoutRef,\n useCallback,\n} from \"react\";\nimport { useThreadViewportAutoScroll } from \"./useThreadViewportAutoScroll\";\nimport { ThreadPrimitiveViewportProvider } from \"../../context/providers/ThreadViewportProvider\";\nimport { useSizeHandle } from \"../../utils/hooks/useSizeHandle\";\nimport { useThreadViewport } from \"../../context/react/ThreadViewportContext\";\n\nexport namespace ThreadPrimitiveViewport {\n export type Element = ComponentRef<typeof Primitive.div>;\n export type Props = ComponentPropsWithoutRef<typeof Primitive.div> & {\n /**\n * Whether to automatically scroll to the bottom when new messages are added.\n * When enabled, the viewport will automatically scroll to show the latest content.\n *\n * Default false if `turnAnchor` is \"top\", otherwise defaults to true.\n */\n autoScroll?: boolean | undefined;\n\n /**\n * Controls scroll anchoring behavior for new messages.\n * - \"bottom\" (default): Messages anchor at the bottom, classic chat behavior.\n * - \"top\": New user messages anchor at the top of the viewport for a focused reading experience.\n */\n turnAnchor?: \"top\" | \"bottom\" | undefined;\n };\n}\n\nconst useViewportSizeRef = () => {\n const register = useThreadViewport((s) => s.registerViewport);\n const getHeight = useCallback(
|
|
1
|
+
{"version":3,"sources":["../../../src/primitives/thread/ThreadViewport.tsx"],"sourcesContent":["\"use client\";\n\nimport { useComposedRefs } from \"@radix-ui/react-compose-refs\";\nimport { Primitive } from \"@radix-ui/react-primitive\";\nimport {\n type ComponentRef,\n forwardRef,\n ComponentPropsWithoutRef,\n useCallback,\n} from \"react\";\nimport { useThreadViewportAutoScroll } from \"./useThreadViewportAutoScroll\";\nimport { ThreadPrimitiveViewportProvider } from \"../../context/providers/ThreadViewportProvider\";\nimport { useSizeHandle } from \"../../utils/hooks/useSizeHandle\";\nimport { useThreadViewport } from \"../../context/react/ThreadViewportContext\";\n\nexport namespace ThreadPrimitiveViewport {\n export type Element = ComponentRef<typeof Primitive.div>;\n export type Props = ComponentPropsWithoutRef<typeof Primitive.div> & {\n /**\n * Whether to automatically scroll to the bottom when new messages are added.\n * When enabled, the viewport will automatically scroll to show the latest content.\n *\n * Default false if `turnAnchor` is \"top\", otherwise defaults to true.\n */\n autoScroll?: boolean | undefined;\n\n /**\n * Controls scroll anchoring behavior for new messages.\n * - \"bottom\" (default): Messages anchor at the bottom, classic chat behavior.\n * - \"top\": New user messages anchor at the top of the viewport for a focused reading experience.\n */\n turnAnchor?: \"top\" | \"bottom\" | undefined;\n\n /**\n * Whether to scroll to bottom when a new run starts.\n *\n * Defaults to true.\n */\n scrollToBottomOnRunStart?: boolean | undefined;\n\n /**\n * Whether to scroll to bottom when thread history is first loaded.\n *\n * Defaults to true.\n */\n scrollToBottomOnInitialize?: boolean | undefined;\n\n /**\n * Whether to scroll to bottom when switching to a different thread.\n *\n * Defaults to true.\n */\n scrollToBottomOnThreadSwitch?: boolean | undefined;\n };\n}\n\nconst useViewportSizeRef = () => {\n const register = useThreadViewport((s) => s.registerViewport);\n const getHeight = useCallback((el: HTMLElement) => el.clientHeight, []);\n return useSizeHandle(register, getHeight);\n};\n\nconst ThreadPrimitiveViewportScrollable = forwardRef<\n ThreadPrimitiveViewport.Element,\n ThreadPrimitiveViewport.Props\n>(\n (\n {\n autoScroll,\n scrollToBottomOnRunStart,\n scrollToBottomOnInitialize,\n scrollToBottomOnThreadSwitch,\n children,\n ...rest\n },\n forwardedRef,\n ) => {\n const autoScrollRef = useThreadViewportAutoScroll<HTMLDivElement>({\n autoScroll,\n scrollToBottomOnRunStart,\n scrollToBottomOnInitialize,\n scrollToBottomOnThreadSwitch,\n });\n const viewportSizeRef = useViewportSizeRef();\n const ref = useComposedRefs(forwardedRef, autoScrollRef, viewportSizeRef);\n\n return (\n <Primitive.div {...rest} ref={ref}>\n {children}\n </Primitive.div>\n );\n },\n);\n\nThreadPrimitiveViewportScrollable.displayName =\n \"ThreadPrimitive.ViewportScrollable\";\n\n/**\n * A scrollable viewport container for thread messages.\n *\n * This component provides a scrollable area for displaying thread messages with\n * automatic scrolling capabilities. It manages the viewport state and provides\n * context for child components to access viewport-related functionality.\n *\n * @example\n * ```tsx\n * <ThreadPrimitive.Viewport turnAnchor=\"top\">\n * <ThreadPrimitive.Messages components={{ Message: MyMessage }} />\n * </ThreadPrimitive.Viewport>\n * ```\n */\nexport const ThreadPrimitiveViewport = forwardRef<\n ThreadPrimitiveViewport.Element,\n ThreadPrimitiveViewport.Props\n>(({ turnAnchor, ...props }, ref) => {\n return (\n <ThreadPrimitiveViewportProvider options={{ turnAnchor }}>\n <ThreadPrimitiveViewportScrollable {...props} ref={ref} />\n </ThreadPrimitiveViewportProvider>\n );\n});\n\nThreadPrimitiveViewport.displayName = \"ThreadPrimitive.Viewport\";\n"],"mappings":";;;AAEA,SAAS,uBAAuB;AAChC,SAAS,iBAAiB;AAC1B;AAAA,EAEE;AAAA,EAEA;AAAA,OACK;AACP,SAAS,mCAAmC;AAC5C,SAAS,uCAAuC;AAChD,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AA0E5B;AA/BN,IAAM,qBAAqB,MAAM;AAC/B,QAAM,WAAW,kBAAkB,CAAC,MAAM,EAAE,gBAAgB;AAC5D,QAAM,YAAY,YAAY,CAAC,OAAoB,GAAG,cAAc,CAAC,CAAC;AACtE,SAAO,cAAc,UAAU,SAAS;AAC1C;AAEA,IAAM,oCAAoC;AAAA,EAIxC,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,GACA,iBACG;AACH,UAAM,gBAAgB,4BAA4C;AAAA,MAChE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AACD,UAAM,kBAAkB,mBAAmB;AAC3C,UAAM,MAAM,gBAAgB,cAAc,eAAe,eAAe;AAExE,WACE,oBAAC,UAAU,KAAV,EAAe,GAAG,MAAM,KACtB,UACH;AAAA,EAEJ;AACF;AAEA,kCAAkC,cAChC;AAgBK,IAAM,0BAA0B,WAGrC,CAAC,EAAE,YAAY,GAAG,MAAM,GAAG,QAAQ;AACnC,SACE,oBAAC,mCAAgC,SAAS,EAAE,WAAW,GACrD,8BAAC,qCAAmC,GAAG,OAAO,KAAU,GAC1D;AAEJ,CAAC;AAED,wBAAwB,cAAc;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThreadViewportSlack.d.ts","sourceRoot":"","sources":["../../../src/primitives/thread/ThreadViewportSlack.tsx"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,EAAE,EACP,KAAK,SAAS,EAGf,MAAM,OAAO,CAAC;AA2Bf,MAAM,MAAM,wBAAwB,GAAG;IACrC,sEAAsE;IACtE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oDAAoD;IACpD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,4BAA4B,EAAE,EAAE,CAAC,wBAAwB,
|
|
1
|
+
{"version":3,"file":"ThreadViewportSlack.d.ts","sourceRoot":"","sources":["../../../src/primitives/thread/ThreadViewportSlack.tsx"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,EAAE,EACP,KAAK,SAAS,EAGf,MAAM,OAAO,CAAC;AA2Bf,MAAM,MAAM,wBAAwB,GAAG;IACrC,sEAAsE;IACtE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oDAAoD;IACpD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,4BAA4B,EAAE,EAAE,CAAC,wBAAwB,CAuDrE,CAAC"}
|
|
@@ -33,7 +33,10 @@ var ThreadPrimitiveViewportSlack = ({
|
|
|
33
33
|
fillClampThreshold = "10em",
|
|
34
34
|
fillClampOffset = "6em"
|
|
35
35
|
}) => {
|
|
36
|
-
const isLast = useAssistantState(
|
|
36
|
+
const isLast = useAssistantState(
|
|
37
|
+
// only add slack if the message is the last message and we already have at least 3 messages
|
|
38
|
+
({ message }) => message.isLast && message.index >= 2
|
|
39
|
+
);
|
|
37
40
|
const threadViewportStore = useThreadViewportStore({ optional: true });
|
|
38
41
|
const isNested = useContext(SlackNestingContext);
|
|
39
42
|
const callback = useCallback(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/primitives/thread/ThreadViewportSlack.tsx"],"sourcesContent":["\"use client\";\n\nimport { Slot } from \"@radix-ui/react-slot\";\nimport {\n createContext,\n type FC,\n type ReactNode,\n useCallback,\n useContext,\n} from \"react\";\nimport { useThreadViewportStore } from \"../../context/react/ThreadViewportContext\";\nimport { useAssistantState } from \"../../context\";\nimport { useManagedRef } from \"../../utils/hooks/useManagedRef\";\n\nconst SlackNestingContext = createContext(false);\n\nconst parseCssLength = (value: string, element: HTMLElement): number => {\n const match = value.match(/^([\\d.]+)(em|px|rem)$/);\n if (!match) return 0;\n\n const num = parseFloat(match[1]!);\n const unit = match[2];\n\n if (unit === \"px\") return num;\n if (unit === \"em\") {\n const fontSize = parseFloat(getComputedStyle(element).fontSize) || 16;\n return num * fontSize;\n }\n if (unit === \"rem\") {\n const rootFontSize =\n parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;\n return num * rootFontSize;\n }\n return 0;\n};\n\nexport type ThreadViewportSlackProps = {\n /** Threshold at which the user message height clamps to the offset */\n fillClampThreshold?: string;\n /** Offset used when clamping large user messages */\n fillClampOffset?: string;\n children: ReactNode;\n};\n\n/**\n * A slot component that provides minimum height to enable scroll anchoring.\n *\n * When using `turnAnchor=\"top\"`, this component ensures there is\n * enough scroll room below the anchor point (last user message) for it to scroll\n * to the top of the viewport. The min-height is applied only to the last\n * assistant message.\n *\n * This component is used internally by MessagePrimitive.Root.\n */\nexport const ThreadPrimitiveViewportSlack: FC<ThreadViewportSlackProps> = ({\n children,\n fillClampThreshold = \"10em\",\n fillClampOffset = \"6em\",\n}) => {\n const isLast = useAssistantState(({ message }) => message.isLast);\n const threadViewportStore = useThreadViewportStore({ optional: true });\n const isNested = useContext(SlackNestingContext);\n\n const callback = useCallback(\n (el: HTMLElement) => {\n if (!threadViewportStore || isNested) return;\n\n const updateMinHeight = () => {\n const state = threadViewportStore.getState();\n if (state.turnAnchor === \"top\" && isLast) {\n const { viewport, inset, userMessage } = state.height;\n const threshold = parseCssLength(fillClampThreshold, el);\n const offset = parseCssLength(fillClampOffset, el);\n const clampAdjustment =\n userMessage <= threshold ? userMessage : offset;\n\n const minHeight = Math.max(0, viewport - inset - clampAdjustment);\n el.style.minHeight = `${minHeight}px`;\n el.style.flexShrink = \"0\";\n el.style.transition = \"min-height 0s\";\n } else {\n el.style.minHeight = \"\";\n el.style.flexShrink = \"\";\n el.style.transition = \"\";\n }\n };\n\n updateMinHeight();\n return threadViewportStore.subscribe(updateMinHeight);\n },\n [\n threadViewportStore,\n isLast,\n isNested,\n fillClampThreshold,\n fillClampOffset,\n ],\n );\n\n const ref = useManagedRef<HTMLElement>(callback);\n\n return (\n <SlackNestingContext.Provider value={true}>\n <Slot ref={ref}>{children}</Slot>\n </SlackNestingContext.Provider>\n );\n};\n\nThreadPrimitiveViewportSlack.displayName = \"ThreadPrimitive.ViewportSlack\";\n"],"mappings":";;;AAEA,SAAS,YAAY;AACrB;AAAA,EACE;AAAA,EAGA;AAAA,EACA;AAAA,OACK;AACP,SAAS,8BAA8B;AACvC,SAAS,yBAAyB;AAClC,SAAS,qBAAqB;
|
|
1
|
+
{"version":3,"sources":["../../../src/primitives/thread/ThreadViewportSlack.tsx"],"sourcesContent":["\"use client\";\n\nimport { Slot } from \"@radix-ui/react-slot\";\nimport {\n createContext,\n type FC,\n type ReactNode,\n useCallback,\n useContext,\n} from \"react\";\nimport { useThreadViewportStore } from \"../../context/react/ThreadViewportContext\";\nimport { useAssistantState } from \"../../context\";\nimport { useManagedRef } from \"../../utils/hooks/useManagedRef\";\n\nconst SlackNestingContext = createContext(false);\n\nconst parseCssLength = (value: string, element: HTMLElement): number => {\n const match = value.match(/^([\\d.]+)(em|px|rem)$/);\n if (!match) return 0;\n\n const num = parseFloat(match[1]!);\n const unit = match[2];\n\n if (unit === \"px\") return num;\n if (unit === \"em\") {\n const fontSize = parseFloat(getComputedStyle(element).fontSize) || 16;\n return num * fontSize;\n }\n if (unit === \"rem\") {\n const rootFontSize =\n parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;\n return num * rootFontSize;\n }\n return 0;\n};\n\nexport type ThreadViewportSlackProps = {\n /** Threshold at which the user message height clamps to the offset */\n fillClampThreshold?: string;\n /** Offset used when clamping large user messages */\n fillClampOffset?: string;\n children: ReactNode;\n};\n\n/**\n * A slot component that provides minimum height to enable scroll anchoring.\n *\n * When using `turnAnchor=\"top\"`, this component ensures there is\n * enough scroll room below the anchor point (last user message) for it to scroll\n * to the top of the viewport. The min-height is applied only to the last\n * assistant message.\n *\n * This component is used internally by MessagePrimitive.Root.\n */\nexport const ThreadPrimitiveViewportSlack: FC<ThreadViewportSlackProps> = ({\n children,\n fillClampThreshold = \"10em\",\n fillClampOffset = \"6em\",\n}) => {\n const isLast = useAssistantState(\n // only add slack if the message is the last message and we already have at least 3 messages\n ({ message }) => message.isLast && message.index >= 2,\n );\n const threadViewportStore = useThreadViewportStore({ optional: true });\n const isNested = useContext(SlackNestingContext);\n\n const callback = useCallback(\n (el: HTMLElement) => {\n if (!threadViewportStore || isNested) return;\n\n const updateMinHeight = () => {\n const state = threadViewportStore.getState();\n if (state.turnAnchor === \"top\" && isLast) {\n const { viewport, inset, userMessage } = state.height;\n const threshold = parseCssLength(fillClampThreshold, el);\n const offset = parseCssLength(fillClampOffset, el);\n const clampAdjustment =\n userMessage <= threshold ? userMessage : offset;\n\n const minHeight = Math.max(0, viewport - inset - clampAdjustment);\n el.style.minHeight = `${minHeight}px`;\n el.style.flexShrink = \"0\";\n el.style.transition = \"min-height 0s\";\n } else {\n el.style.minHeight = \"\";\n el.style.flexShrink = \"\";\n el.style.transition = \"\";\n }\n };\n\n updateMinHeight();\n return threadViewportStore.subscribe(updateMinHeight);\n },\n [\n threadViewportStore,\n isLast,\n isNested,\n fillClampThreshold,\n fillClampOffset,\n ],\n );\n\n const ref = useManagedRef<HTMLElement>(callback);\n\n return (\n <SlackNestingContext.Provider value={true}>\n <Slot ref={ref}>{children}</Slot>\n </SlackNestingContext.Provider>\n );\n};\n\nThreadPrimitiveViewportSlack.displayName = \"ThreadPrimitive.ViewportSlack\";\n"],"mappings":";;;AAEA,SAAS,YAAY;AACrB;AAAA,EACE;AAAA,EAGA;AAAA,EACA;AAAA,OACK;AACP,SAAS,8BAA8B;AACvC,SAAS,yBAAyB;AAClC,SAAS,qBAAqB;AA8FxB;AA5FN,IAAM,sBAAsB,cAAc,KAAK;AAE/C,IAAM,iBAAiB,CAAC,OAAe,YAAiC;AACtE,QAAM,QAAQ,MAAM,MAAM,uBAAuB;AACjD,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,MAAM,WAAW,MAAM,CAAC,CAAE;AAChC,QAAM,OAAO,MAAM,CAAC;AAEpB,MAAI,SAAS,KAAM,QAAO;AAC1B,MAAI,SAAS,MAAM;AACjB,UAAM,WAAW,WAAW,iBAAiB,OAAO,EAAE,QAAQ,KAAK;AACnE,WAAO,MAAM;AAAA,EACf;AACA,MAAI,SAAS,OAAO;AAClB,UAAM,eACJ,WAAW,iBAAiB,SAAS,eAAe,EAAE,QAAQ,KAAK;AACrE,WAAO,MAAM;AAAA,EACf;AACA,SAAO;AACT;AAoBO,IAAM,+BAA6D,CAAC;AAAA,EACzE;AAAA,EACA,qBAAqB;AAAA,EACrB,kBAAkB;AACpB,MAAM;AACJ,QAAM,SAAS;AAAA;AAAA,IAEb,CAAC,EAAE,QAAQ,MAAM,QAAQ,UAAU,QAAQ,SAAS;AAAA,EACtD;AACA,QAAM,sBAAsB,uBAAuB,EAAE,UAAU,KAAK,CAAC;AACrE,QAAM,WAAW,WAAW,mBAAmB;AAE/C,QAAM,WAAW;AAAA,IACf,CAAC,OAAoB;AACnB,UAAI,CAAC,uBAAuB,SAAU;AAEtC,YAAM,kBAAkB,MAAM;AAC5B,cAAM,QAAQ,oBAAoB,SAAS;AAC3C,YAAI,MAAM,eAAe,SAAS,QAAQ;AACxC,gBAAM,EAAE,UAAU,OAAO,YAAY,IAAI,MAAM;AAC/C,gBAAM,YAAY,eAAe,oBAAoB,EAAE;AACvD,gBAAM,SAAS,eAAe,iBAAiB,EAAE;AACjD,gBAAM,kBACJ,eAAe,YAAY,cAAc;AAE3C,gBAAM,YAAY,KAAK,IAAI,GAAG,WAAW,QAAQ,eAAe;AAChE,aAAG,MAAM,YAAY,GAAG,SAAS;AACjC,aAAG,MAAM,aAAa;AACtB,aAAG,MAAM,aAAa;AAAA,QACxB,OAAO;AACL,aAAG,MAAM,YAAY;AACrB,aAAG,MAAM,aAAa;AACtB,aAAG,MAAM,aAAa;AAAA,QACxB;AAAA,MACF;AAEA,sBAAgB;AAChB,aAAO,oBAAoB,UAAU,eAAe;AAAA,IACtD;AAAA,IACA;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,MAAM,cAA2B,QAAQ;AAE/C,SACE,oBAAC,oBAAoB,UAApB,EAA6B,OAAO,MACnC,8BAAC,QAAK,KAAW,UAAS,GAC5B;AAEJ;AAEA,6BAA6B,cAAc;","names":[]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RefCallback } from "react";
|
|
1
|
+
import { type RefCallback } from "react";
|
|
2
2
|
export declare namespace useThreadViewportAutoScroll {
|
|
3
3
|
type Options = {
|
|
4
4
|
/**
|
|
@@ -8,7 +8,25 @@ export declare namespace useThreadViewportAutoScroll {
|
|
|
8
8
|
* Default false if `turnAnchor` is "top", otherwise defaults to true.
|
|
9
9
|
*/
|
|
10
10
|
autoScroll?: boolean | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Whether to scroll to bottom when a new run starts.
|
|
13
|
+
*
|
|
14
|
+
* Defaults to true.
|
|
15
|
+
*/
|
|
16
|
+
scrollToBottomOnRunStart?: boolean | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Whether to scroll to bottom when thread history is first loaded.
|
|
19
|
+
*
|
|
20
|
+
* Defaults to true.
|
|
21
|
+
*/
|
|
22
|
+
scrollToBottomOnInitialize?: boolean | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Whether to scroll to bottom when switching to a different thread.
|
|
25
|
+
*
|
|
26
|
+
* Defaults to true.
|
|
27
|
+
*/
|
|
28
|
+
scrollToBottomOnThreadSwitch?: boolean | undefined;
|
|
11
29
|
};
|
|
12
30
|
}
|
|
13
|
-
export declare const useThreadViewportAutoScroll: <TElement extends HTMLElement>({ autoScroll, }: useThreadViewportAutoScroll.Options) => RefCallback<TElement>;
|
|
31
|
+
export declare const useThreadViewportAutoScroll: <TElement extends HTMLElement>({ autoScroll, scrollToBottomOnRunStart, scrollToBottomOnInitialize, scrollToBottomOnThreadSwitch, }: useThreadViewportAutoScroll.Options) => RefCallback<TElement>;
|
|
14
32
|
//# sourceMappingURL=useThreadViewportAutoScroll.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useThreadViewportAutoScroll.d.ts","sourceRoot":"","sources":["../../../src/primitives/thread/useThreadViewportAutoScroll.tsx"],"names":[],"mappings":"AAGA,OAAO,
|
|
1
|
+
{"version":3,"file":"useThreadViewportAutoScroll.d.ts","sourceRoot":"","sources":["../../../src/primitives/thread/useThreadViewportAutoScroll.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,OAAO,CAAC;AAQ9D,yBAAiB,2BAA2B,CAAC;IAC3C,KAAY,OAAO,GAAG;QACpB;;;;;WAKG;QACH,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QAEjC;;;;WAIG;QACH,wBAAwB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QAE/C;;;;WAIG;QACH,0BAA0B,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QAEjD;;;;WAIG;QACH,4BAA4B,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;KACpD,CAAC;CACH;AAED,eAAO,MAAM,2BAA2B,GAAI,QAAQ,SAAS,WAAW,EAAE,qGAKvE,2BAA2B,CAAC,OAAO,KAAG,WAAW,CAAC,QAAQ,CAuG5D,CAAC"}
|
|
@@ -10,7 +10,10 @@ import { useManagedRef } from "../../utils/hooks/useManagedRef.js";
|
|
|
10
10
|
import { writableStore } from "../../context/ReadonlyStore.js";
|
|
11
11
|
import { useThreadViewportStore } from "../../context/react/ThreadViewportContext.js";
|
|
12
12
|
var useThreadViewportAutoScroll = ({
|
|
13
|
-
autoScroll
|
|
13
|
+
autoScroll,
|
|
14
|
+
scrollToBottomOnRunStart = true,
|
|
15
|
+
scrollToBottomOnInitialize = true,
|
|
16
|
+
scrollToBottomOnThreadSwitch = true
|
|
14
17
|
}) => {
|
|
15
18
|
const divRef = useRef(null);
|
|
16
19
|
const threadViewportStore = useThreadViewportStore();
|
|
@@ -35,7 +38,8 @@ var useThreadViewportAutoScroll = ({
|
|
|
35
38
|
if (newIsAtBottom) {
|
|
36
39
|
scrollingToBottomBehaviorRef.current = null;
|
|
37
40
|
}
|
|
38
|
-
|
|
41
|
+
const shouldUpdate = newIsAtBottom || scrollingToBottomBehaviorRef.current === null;
|
|
42
|
+
if (shouldUpdate && newIsAtBottom !== isAtBottom) {
|
|
39
43
|
writableStore(threadViewportStore).setState({
|
|
40
44
|
isAtBottom: newIsAtBottom
|
|
41
45
|
});
|
|
@@ -62,11 +66,26 @@ var useThreadViewportAutoScroll = ({
|
|
|
62
66
|
scrollToBottom(behavior);
|
|
63
67
|
});
|
|
64
68
|
useAssistantEvent("thread.run-start", () => {
|
|
69
|
+
if (!scrollToBottomOnRunStart) return;
|
|
65
70
|
scrollingToBottomBehaviorRef.current = "auto";
|
|
66
71
|
requestAnimationFrame(() => {
|
|
67
72
|
scrollToBottom("auto");
|
|
68
73
|
});
|
|
69
74
|
});
|
|
75
|
+
useAssistantEvent("thread.initialize", () => {
|
|
76
|
+
if (!scrollToBottomOnInitialize) return;
|
|
77
|
+
scrollingToBottomBehaviorRef.current = "instant";
|
|
78
|
+
requestAnimationFrame(() => {
|
|
79
|
+
scrollToBottom("instant");
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
useAssistantEvent("thread-list-item.switched-to", () => {
|
|
83
|
+
if (!scrollToBottomOnThreadSwitch) return;
|
|
84
|
+
scrollingToBottomBehaviorRef.current = "instant";
|
|
85
|
+
requestAnimationFrame(() => {
|
|
86
|
+
scrollToBottom("instant");
|
|
87
|
+
});
|
|
88
|
+
});
|
|
70
89
|
const autoScrollRef = useComposedRefs(resizeRef, scrollRef, divRef);
|
|
71
90
|
return autoScrollRef;
|
|
72
91
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/primitives/thread/useThreadViewportAutoScroll.tsx"],"sourcesContent":["\"use client\";\n\nimport { useComposedRefs } from \"@radix-ui/react-compose-refs\";\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/primitives/thread/useThreadViewportAutoScroll.tsx"],"sourcesContent":["\"use client\";\n\nimport { useComposedRefs } from \"@radix-ui/react-compose-refs\";\nimport { useCallback, useRef, type RefCallback } from \"react\";\nimport { useAssistantEvent } from \"../../context\";\nimport { useOnResizeContent } from \"../../utils/hooks/useOnResizeContent\";\nimport { useOnScrollToBottom } from \"../../utils/hooks/useOnScrollToBottom\";\nimport { useManagedRef } from \"../../utils/hooks/useManagedRef\";\nimport { writableStore } from \"../../context/ReadonlyStore\";\nimport { useThreadViewportStore } from \"../../context/react/ThreadViewportContext\";\n\nexport namespace useThreadViewportAutoScroll {\n export type Options = {\n /**\n * Whether to automatically scroll to the bottom when new messages are added.\n * When enabled, the viewport will automatically scroll to show the latest content.\n *\n * Default false if `turnAnchor` is \"top\", otherwise defaults to true.\n */\n autoScroll?: boolean | undefined;\n\n /**\n * Whether to scroll to bottom when a new run starts.\n *\n * Defaults to true.\n */\n scrollToBottomOnRunStart?: boolean | undefined;\n\n /**\n * Whether to scroll to bottom when thread history is first loaded.\n *\n * Defaults to true.\n */\n scrollToBottomOnInitialize?: boolean | undefined;\n\n /**\n * Whether to scroll to bottom when switching to a different thread.\n *\n * Defaults to true.\n */\n scrollToBottomOnThreadSwitch?: boolean | undefined;\n };\n}\n\nexport const useThreadViewportAutoScroll = <TElement extends HTMLElement>({\n autoScroll,\n scrollToBottomOnRunStart = true,\n scrollToBottomOnInitialize = true,\n scrollToBottomOnThreadSwitch = true,\n}: useThreadViewportAutoScroll.Options): RefCallback<TElement> => {\n const divRef = useRef<TElement>(null);\n\n const threadViewportStore = useThreadViewportStore();\n if (autoScroll === undefined) {\n autoScroll = threadViewportStore.getState().turnAnchor !== \"top\";\n }\n\n const lastScrollTop = useRef<number>(0);\n\n // bug: when ScrollToBottom's button changes its disabled state, the scroll stops\n // fix: delay the state change until the scroll is done\n // stores the scroll behavior to reuse during content resize, or null if not scrolling\n const scrollingToBottomBehaviorRef = useRef<ScrollBehavior | null>(null);\n\n const scrollToBottom = useCallback((behavior: ScrollBehavior) => {\n const div = divRef.current;\n if (!div) return;\n\n scrollingToBottomBehaviorRef.current = behavior;\n div.scrollTo({ top: div.scrollHeight, behavior });\n }, []);\n\n const handleScroll = () => {\n const div = divRef.current;\n if (!div) return;\n\n const isAtBottom = threadViewportStore.getState().isAtBottom;\n const newIsAtBottom =\n Math.abs(div.scrollHeight - div.scrollTop - div.clientHeight) < 1 ||\n div.scrollHeight <= div.clientHeight;\n\n if (!newIsAtBottom && lastScrollTop.current < div.scrollTop) {\n // ignore scroll down\n } else {\n if (newIsAtBottom) {\n scrollingToBottomBehaviorRef.current = null;\n }\n\n const shouldUpdate =\n newIsAtBottom || scrollingToBottomBehaviorRef.current === null;\n\n if (shouldUpdate && newIsAtBottom !== isAtBottom) {\n writableStore(threadViewportStore).setState({\n isAtBottom: newIsAtBottom,\n });\n }\n }\n\n lastScrollTop.current = div.scrollTop;\n };\n\n const resizeRef = useOnResizeContent(() => {\n const scrollBehavior = scrollingToBottomBehaviorRef.current;\n if (scrollBehavior) {\n scrollToBottom(scrollBehavior);\n } else if (autoScroll && threadViewportStore.getState().isAtBottom) {\n scrollToBottom(\"instant\");\n }\n\n handleScroll();\n });\n\n const scrollRef = useManagedRef<HTMLElement>((el) => {\n el.addEventListener(\"scroll\", handleScroll);\n return () => {\n el.removeEventListener(\"scroll\", handleScroll);\n };\n });\n\n useOnScrollToBottom(({ behavior }) => {\n scrollToBottom(behavior);\n });\n\n // autoscroll on run start\n useAssistantEvent(\"thread.run-start\", () => {\n if (!scrollToBottomOnRunStart) return;\n scrollingToBottomBehaviorRef.current = \"auto\";\n requestAnimationFrame(() => {\n scrollToBottom(\"auto\");\n });\n });\n\n // scroll to bottom instantly when thread history is first loaded\n useAssistantEvent(\"thread.initialize\", () => {\n if (!scrollToBottomOnInitialize) return;\n scrollingToBottomBehaviorRef.current = \"instant\";\n requestAnimationFrame(() => {\n scrollToBottom(\"instant\");\n });\n });\n\n // scroll to bottom instantly when switching threads\n useAssistantEvent(\"thread-list-item.switched-to\", () => {\n if (!scrollToBottomOnThreadSwitch) return;\n scrollingToBottomBehaviorRef.current = \"instant\";\n requestAnimationFrame(() => {\n scrollToBottom(\"instant\");\n });\n });\n\n const autoScrollRef = useComposedRefs<TElement>(resizeRef, scrollRef, divRef);\n return autoScrollRef as RefCallback<TElement>;\n};\n"],"mappings":";;;AAEA,SAAS,uBAAuB;AAChC,SAAS,aAAa,cAAgC;AACtD,SAAS,yBAAyB;AAClC,SAAS,0BAA0B;AACnC,SAAS,2BAA2B;AACpC,SAAS,qBAAqB;AAC9B,SAAS,qBAAqB;AAC9B,SAAS,8BAA8B;AAmChC,IAAM,8BAA8B,CAA+B;AAAA,EACxE;AAAA,EACA,2BAA2B;AAAA,EAC3B,6BAA6B;AAAA,EAC7B,+BAA+B;AACjC,MAAkE;AAChE,QAAM,SAAS,OAAiB,IAAI;AAEpC,QAAM,sBAAsB,uBAAuB;AACnD,MAAI,eAAe,QAAW;AAC5B,iBAAa,oBAAoB,SAAS,EAAE,eAAe;AAAA,EAC7D;AAEA,QAAM,gBAAgB,OAAe,CAAC;AAKtC,QAAM,+BAA+B,OAA8B,IAAI;AAEvE,QAAM,iBAAiB,YAAY,CAAC,aAA6B;AAC/D,UAAM,MAAM,OAAO;AACnB,QAAI,CAAC,IAAK;AAEV,iCAA6B,UAAU;AACvC,QAAI,SAAS,EAAE,KAAK,IAAI,cAAc,SAAS,CAAC;AAAA,EAClD,GAAG,CAAC,CAAC;AAEL,QAAM,eAAe,MAAM;AACzB,UAAM,MAAM,OAAO;AACnB,QAAI,CAAC,IAAK;AAEV,UAAM,aAAa,oBAAoB,SAAS,EAAE;AAClD,UAAM,gBACJ,KAAK,IAAI,IAAI,eAAe,IAAI,YAAY,IAAI,YAAY,IAAI,KAChE,IAAI,gBAAgB,IAAI;AAE1B,QAAI,CAAC,iBAAiB,cAAc,UAAU,IAAI,WAAW;AAAA,IAE7D,OAAO;AACL,UAAI,eAAe;AACjB,qCAA6B,UAAU;AAAA,MACzC;AAEA,YAAM,eACJ,iBAAiB,6BAA6B,YAAY;AAE5D,UAAI,gBAAgB,kBAAkB,YAAY;AAChD,sBAAc,mBAAmB,EAAE,SAAS;AAAA,UAC1C,YAAY;AAAA,QACd,CAAC;AAAA,MACH;AAAA,IACF;AAEA,kBAAc,UAAU,IAAI;AAAA,EAC9B;AAEA,QAAM,YAAY,mBAAmB,MAAM;AACzC,UAAM,iBAAiB,6BAA6B;AACpD,QAAI,gBAAgB;AAClB,qBAAe,cAAc;AAAA,IAC/B,WAAW,cAAc,oBAAoB,SAAS,EAAE,YAAY;AAClE,qBAAe,SAAS;AAAA,IAC1B;AAEA,iBAAa;AAAA,EACf,CAAC;AAED,QAAM,YAAY,cAA2B,CAAC,OAAO;AACnD,OAAG,iBAAiB,UAAU,YAAY;AAC1C,WAAO,MAAM;AACX,SAAG,oBAAoB,UAAU,YAAY;AAAA,IAC/C;AAAA,EACF,CAAC;AAED,sBAAoB,CAAC,EAAE,SAAS,MAAM;AACpC,mBAAe,QAAQ;AAAA,EACzB,CAAC;AAGD,oBAAkB,oBAAoB,MAAM;AAC1C,QAAI,CAAC,yBAA0B;AAC/B,iCAA6B,UAAU;AACvC,0BAAsB,MAAM;AAC1B,qBAAe,MAAM;AAAA,IACvB,CAAC;AAAA,EACH,CAAC;AAGD,oBAAkB,qBAAqB,MAAM;AAC3C,QAAI,CAAC,2BAA4B;AACjC,iCAA6B,UAAU;AACvC,0BAAsB,MAAM;AAC1B,qBAAe,SAAS;AAAA,IAC1B,CAAC;AAAA,EACH,CAAC;AAGD,oBAAkB,gCAAgC,MAAM;AACtD,QAAI,CAAC,6BAA8B;AACnC,iCAA6B,UAAU;AACvC,0BAAsB,MAAM;AAC1B,qBAAe,SAAS;AAAA,IAC1B,CAAC;AAAA,EACH,CAAC;AAED,QAAM,gBAAgB,gBAA0B,WAAW,WAAW,MAAM;AAC5E,SAAO;AACT;","names":[]}
|
package/package.json
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"conversational-ui",
|
|
29
29
|
"conversational-ai"
|
|
30
30
|
],
|
|
31
|
-
"version": "0.11.
|
|
31
|
+
"version": "0.11.49",
|
|
32
32
|
"license": "MIT",
|
|
33
33
|
"type": "module",
|
|
34
34
|
"exports": {
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"@radix-ui/react-use-callback-ref": "^1.1.1",
|
|
60
60
|
"@radix-ui/react-use-escape-keydown": "^1.1.1",
|
|
61
61
|
"@standard-schema/spec": "^1.0.0",
|
|
62
|
-
"assistant-stream": "^0.2.
|
|
62
|
+
"assistant-stream": "^0.2.44",
|
|
63
63
|
"nanoid": "5.1.6",
|
|
64
64
|
"react-textarea-autosize": "^8.5.9",
|
|
65
65
|
"zod": "^4.1.13",
|
|
@@ -39,8 +39,7 @@ type UseToolInvocationsParams = {
|
|
|
39
39
|
|
|
40
40
|
export type ToolExecutionStatus =
|
|
41
41
|
| { type: "executing" }
|
|
42
|
-
| { type: "interrupt"; payload: { type: "human"; payload: unknown } }
|
|
43
|
-
| { type: "cancelled"; reason: string };
|
|
42
|
+
| { type: "interrupt"; payload: { type: "human"; payload: unknown } };
|
|
44
43
|
|
|
45
44
|
export function useToolInvocations({
|
|
46
45
|
state,
|
|
@@ -71,6 +70,9 @@ export function useToolInvocations({
|
|
|
71
70
|
>(new Map());
|
|
72
71
|
|
|
73
72
|
const acRef = useRef<AbortController>(new AbortController());
|
|
73
|
+
const executingCountRef = useRef(0);
|
|
74
|
+
const settledResolversRef = useRef<Array<() => void>>([]);
|
|
75
|
+
|
|
74
76
|
const [controller] = useState(() => {
|
|
75
77
|
const [stream, controller] = createAssistantStreamController();
|
|
76
78
|
const transform = unstable_toolResultStream(
|
|
@@ -96,6 +98,28 @@ export function useToolInvocations({
|
|
|
96
98
|
}));
|
|
97
99
|
});
|
|
98
100
|
},
|
|
101
|
+
{
|
|
102
|
+
onExecutionStart: (toolCallId: string) => {
|
|
103
|
+
executingCountRef.current++;
|
|
104
|
+
setToolStatuses((prev) => ({
|
|
105
|
+
...prev,
|
|
106
|
+
[toolCallId]: { type: "executing" },
|
|
107
|
+
}));
|
|
108
|
+
},
|
|
109
|
+
onExecutionEnd: (toolCallId: string) => {
|
|
110
|
+
executingCountRef.current--;
|
|
111
|
+
setToolStatuses((prev) => {
|
|
112
|
+
const next = { ...prev };
|
|
113
|
+
delete next[toolCallId];
|
|
114
|
+
return next;
|
|
115
|
+
});
|
|
116
|
+
// Resolve any waiting abort promises when all tools have settled
|
|
117
|
+
if (executingCountRef.current === 0) {
|
|
118
|
+
settledResolversRef.current.forEach((resolve) => resolve());
|
|
119
|
+
settledResolversRef.current = [];
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
},
|
|
99
123
|
);
|
|
100
124
|
stream
|
|
101
125
|
.pipeThrough(transform)
|
|
@@ -116,13 +140,6 @@ export function useToolInvocations({
|
|
|
116
140
|
isError: chunk.isError,
|
|
117
141
|
...(chunk.artifact && { artifact: chunk.artifact }),
|
|
118
142
|
});
|
|
119
|
-
|
|
120
|
-
// Clear status when result is set
|
|
121
|
-
setToolStatuses((prev) => {
|
|
122
|
-
const next = { ...prev };
|
|
123
|
-
delete next[chunk.meta.toolCallId];
|
|
124
|
-
return next;
|
|
125
|
-
});
|
|
126
143
|
}
|
|
127
144
|
},
|
|
128
145
|
}),
|
|
@@ -234,20 +251,27 @@ export function useToolInvocations({
|
|
|
234
251
|
}
|
|
235
252
|
}, [state, controller, onResult]);
|
|
236
253
|
|
|
237
|
-
const abort = () => {
|
|
254
|
+
const abort = (): Promise<void> => {
|
|
238
255
|
humanInputRef.current.forEach(({ reject }) => {
|
|
239
256
|
reject(new Error("Tool execution aborted"));
|
|
240
257
|
});
|
|
241
258
|
humanInputRef.current.clear();
|
|
242
|
-
setToolStatuses({});
|
|
243
259
|
|
|
244
260
|
acRef.current.abort();
|
|
245
261
|
acRef.current = new AbortController();
|
|
262
|
+
|
|
263
|
+
// Return a promise that resolves when all executing tools have settled
|
|
264
|
+
if (executingCountRef.current === 0) {
|
|
265
|
+
return Promise.resolve();
|
|
266
|
+
}
|
|
267
|
+
return new Promise<void>((resolve) => {
|
|
268
|
+
settledResolversRef.current.push(resolve);
|
|
269
|
+
});
|
|
246
270
|
};
|
|
247
271
|
|
|
248
272
|
return {
|
|
249
273
|
reset: () => {
|
|
250
|
-
abort();
|
|
274
|
+
void abort();
|
|
251
275
|
isInitialState.current = true;
|
|
252
276
|
},
|
|
253
277
|
abort,
|
|
@@ -255,11 +279,10 @@ export function useToolInvocations({
|
|
|
255
279
|
const handlers = humanInputRef.current.get(toolCallId);
|
|
256
280
|
if (handlers) {
|
|
257
281
|
humanInputRef.current.delete(toolCallId);
|
|
258
|
-
setToolStatuses((prev) => {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
});
|
|
282
|
+
setToolStatuses((prev) => ({
|
|
283
|
+
...prev,
|
|
284
|
+
[toolCallId]: { type: "executing" },
|
|
285
|
+
}));
|
|
263
286
|
handlers.resolve(payload);
|
|
264
287
|
} else {
|
|
265
288
|
throw new Error(
|
|
@@ -30,36 +30,67 @@ export namespace ThreadPrimitiveViewport {
|
|
|
30
30
|
* - "top": New user messages anchor at the top of the viewport for a focused reading experience.
|
|
31
31
|
*/
|
|
32
32
|
turnAnchor?: "top" | "bottom" | undefined;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Whether to scroll to bottom when a new run starts.
|
|
36
|
+
*
|
|
37
|
+
* Defaults to true.
|
|
38
|
+
*/
|
|
39
|
+
scrollToBottomOnRunStart?: boolean | undefined;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Whether to scroll to bottom when thread history is first loaded.
|
|
43
|
+
*
|
|
44
|
+
* Defaults to true.
|
|
45
|
+
*/
|
|
46
|
+
scrollToBottomOnInitialize?: boolean | undefined;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Whether to scroll to bottom when switching to a different thread.
|
|
50
|
+
*
|
|
51
|
+
* Defaults to true.
|
|
52
|
+
*/
|
|
53
|
+
scrollToBottomOnThreadSwitch?: boolean | undefined;
|
|
33
54
|
};
|
|
34
55
|
}
|
|
35
56
|
|
|
36
57
|
const useViewportSizeRef = () => {
|
|
37
58
|
const register = useThreadViewport((s) => s.registerViewport);
|
|
38
|
-
const getHeight = useCallback(
|
|
39
|
-
(el: HTMLElement) =>
|
|
40
|
-
el.clientHeight - parseFloat(getComputedStyle(el).paddingTop),
|
|
41
|
-
[],
|
|
42
|
-
);
|
|
43
|
-
|
|
59
|
+
const getHeight = useCallback((el: HTMLElement) => el.clientHeight, []);
|
|
44
60
|
return useSizeHandle(register, getHeight);
|
|
45
61
|
};
|
|
46
62
|
|
|
47
63
|
const ThreadPrimitiveViewportScrollable = forwardRef<
|
|
48
64
|
ThreadPrimitiveViewport.Element,
|
|
49
65
|
ThreadPrimitiveViewport.Props
|
|
50
|
-
>(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
66
|
+
>(
|
|
67
|
+
(
|
|
68
|
+
{
|
|
69
|
+
autoScroll,
|
|
70
|
+
scrollToBottomOnRunStart,
|
|
71
|
+
scrollToBottomOnInitialize,
|
|
72
|
+
scrollToBottomOnThreadSwitch,
|
|
73
|
+
children,
|
|
74
|
+
...rest
|
|
75
|
+
},
|
|
76
|
+
forwardedRef,
|
|
77
|
+
) => {
|
|
78
|
+
const autoScrollRef = useThreadViewportAutoScroll<HTMLDivElement>({
|
|
79
|
+
autoScroll,
|
|
80
|
+
scrollToBottomOnRunStart,
|
|
81
|
+
scrollToBottomOnInitialize,
|
|
82
|
+
scrollToBottomOnThreadSwitch,
|
|
83
|
+
});
|
|
84
|
+
const viewportSizeRef = useViewportSizeRef();
|
|
85
|
+
const ref = useComposedRefs(forwardedRef, autoScrollRef, viewportSizeRef);
|
|
56
86
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
87
|
+
return (
|
|
88
|
+
<Primitive.div {...rest} ref={ref}>
|
|
89
|
+
{children}
|
|
90
|
+
</Primitive.div>
|
|
91
|
+
);
|
|
92
|
+
},
|
|
93
|
+
);
|
|
63
94
|
|
|
64
95
|
ThreadPrimitiveViewportScrollable.displayName =
|
|
65
96
|
"ThreadPrimitive.ViewportScrollable";
|
|
@@ -57,7 +57,10 @@ export const ThreadPrimitiveViewportSlack: FC<ThreadViewportSlackProps> = ({
|
|
|
57
57
|
fillClampThreshold = "10em",
|
|
58
58
|
fillClampOffset = "6em",
|
|
59
59
|
}) => {
|
|
60
|
-
const isLast = useAssistantState(
|
|
60
|
+
const isLast = useAssistantState(
|
|
61
|
+
// only add slack if the message is the last message and we already have at least 3 messages
|
|
62
|
+
({ message }) => message.isLast && message.index >= 2,
|
|
63
|
+
);
|
|
61
64
|
const threadViewportStore = useThreadViewportStore({ optional: true });
|
|
62
65
|
const isNested = useContext(SlackNestingContext);
|
|
63
66
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useComposedRefs } from "@radix-ui/react-compose-refs";
|
|
4
|
-
import {
|
|
4
|
+
import { useCallback, useRef, type RefCallback } from "react";
|
|
5
5
|
import { useAssistantEvent } from "../../context";
|
|
6
6
|
import { useOnResizeContent } from "../../utils/hooks/useOnResizeContent";
|
|
7
7
|
import { useOnScrollToBottom } from "../../utils/hooks/useOnScrollToBottom";
|
|
@@ -18,11 +18,35 @@ export namespace useThreadViewportAutoScroll {
|
|
|
18
18
|
* Default false if `turnAnchor` is "top", otherwise defaults to true.
|
|
19
19
|
*/
|
|
20
20
|
autoScroll?: boolean | undefined;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Whether to scroll to bottom when a new run starts.
|
|
24
|
+
*
|
|
25
|
+
* Defaults to true.
|
|
26
|
+
*/
|
|
27
|
+
scrollToBottomOnRunStart?: boolean | undefined;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Whether to scroll to bottom when thread history is first loaded.
|
|
31
|
+
*
|
|
32
|
+
* Defaults to true.
|
|
33
|
+
*/
|
|
34
|
+
scrollToBottomOnInitialize?: boolean | undefined;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Whether to scroll to bottom when switching to a different thread.
|
|
38
|
+
*
|
|
39
|
+
* Defaults to true.
|
|
40
|
+
*/
|
|
41
|
+
scrollToBottomOnThreadSwitch?: boolean | undefined;
|
|
21
42
|
};
|
|
22
43
|
}
|
|
23
44
|
|
|
24
45
|
export const useThreadViewportAutoScroll = <TElement extends HTMLElement>({
|
|
25
46
|
autoScroll,
|
|
47
|
+
scrollToBottomOnRunStart = true,
|
|
48
|
+
scrollToBottomOnInitialize = true,
|
|
49
|
+
scrollToBottomOnThreadSwitch = true,
|
|
26
50
|
}: useThreadViewportAutoScroll.Options): RefCallback<TElement> => {
|
|
27
51
|
const divRef = useRef<TElement>(null);
|
|
28
52
|
|
|
@@ -62,7 +86,10 @@ export const useThreadViewportAutoScroll = <TElement extends HTMLElement>({
|
|
|
62
86
|
scrollingToBottomBehaviorRef.current = null;
|
|
63
87
|
}
|
|
64
88
|
|
|
65
|
-
|
|
89
|
+
const shouldUpdate =
|
|
90
|
+
newIsAtBottom || scrollingToBottomBehaviorRef.current === null;
|
|
91
|
+
|
|
92
|
+
if (shouldUpdate && newIsAtBottom !== isAtBottom) {
|
|
66
93
|
writableStore(threadViewportStore).setState({
|
|
67
94
|
isAtBottom: newIsAtBottom,
|
|
68
95
|
});
|
|
@@ -96,12 +123,31 @@ export const useThreadViewportAutoScroll = <TElement extends HTMLElement>({
|
|
|
96
123
|
|
|
97
124
|
// autoscroll on run start
|
|
98
125
|
useAssistantEvent("thread.run-start", () => {
|
|
126
|
+
if (!scrollToBottomOnRunStart) return;
|
|
99
127
|
scrollingToBottomBehaviorRef.current = "auto";
|
|
100
128
|
requestAnimationFrame(() => {
|
|
101
129
|
scrollToBottom("auto");
|
|
102
130
|
});
|
|
103
131
|
});
|
|
104
132
|
|
|
133
|
+
// scroll to bottom instantly when thread history is first loaded
|
|
134
|
+
useAssistantEvent("thread.initialize", () => {
|
|
135
|
+
if (!scrollToBottomOnInitialize) return;
|
|
136
|
+
scrollingToBottomBehaviorRef.current = "instant";
|
|
137
|
+
requestAnimationFrame(() => {
|
|
138
|
+
scrollToBottom("instant");
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// scroll to bottom instantly when switching threads
|
|
143
|
+
useAssistantEvent("thread-list-item.switched-to", () => {
|
|
144
|
+
if (!scrollToBottomOnThreadSwitch) return;
|
|
145
|
+
scrollingToBottomBehaviorRef.current = "instant";
|
|
146
|
+
requestAnimationFrame(() => {
|
|
147
|
+
scrollToBottom("instant");
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
105
151
|
const autoScrollRef = useComposedRefs<TElement>(resizeRef, scrollRef, divRef);
|
|
106
152
|
return autoScrollRef as RefCallback<TElement>;
|
|
107
153
|
};
|