@assistant-ui/react 0.11.31 → 0.11.32
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/useToolInvocations.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.js +28 -15
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.js.map +1 -1
- package/package.json +1 -1
- package/src/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.ts +30 -16
|
@@ -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,CAAC;AAExE,wBAAgB,kBAAkB,CAAC,EACjC,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,eAAe,GAChB,EAAE,wBAAwB;;;
|
|
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;;;yBAyMF,MAAM,WAAW,OAAO;EAiBhD"}
|
|
@@ -96,28 +96,40 @@ function useToolInvocations({
|
|
|
96
96
|
lastState = {
|
|
97
97
|
argsText: "",
|
|
98
98
|
hasResult: false,
|
|
99
|
+
argsComplete: false,
|
|
99
100
|
controller: toolCallController
|
|
100
101
|
};
|
|
101
102
|
lastToolStates.current[content.toolCallId] = lastState;
|
|
102
103
|
}
|
|
103
104
|
if (content.argsText !== lastState.argsText) {
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
if (lastState.argsComplete) {
|
|
106
|
+
if (process.env["NODE_ENV"] !== "production") {
|
|
107
|
+
console.warn(
|
|
108
|
+
"argsText updated after controller was closed:",
|
|
109
|
+
{ previous: lastState.argsText, next: content.argsText }
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
if (!content.argsText.startsWith(lastState.argsText)) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`Tool call argsText can only be appended, not updated: ${content.argsText} does not start with ${lastState.argsText}`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
const argsTextDelta = content.argsText.slice(
|
|
119
|
+
lastState.argsText.length
|
|
107
120
|
);
|
|
121
|
+
lastState.controller.argsText.append(argsTextDelta);
|
|
122
|
+
const shouldClose = isArgsTextComplete(content.argsText);
|
|
123
|
+
if (shouldClose) {
|
|
124
|
+
lastState.controller.argsText.close();
|
|
125
|
+
}
|
|
126
|
+
lastToolStates.current[content.toolCallId] = {
|
|
127
|
+
argsText: content.argsText,
|
|
128
|
+
hasResult: lastState.hasResult,
|
|
129
|
+
argsComplete: shouldClose,
|
|
130
|
+
controller: lastState.controller
|
|
131
|
+
};
|
|
108
132
|
}
|
|
109
|
-
const argsTextDelta = content.argsText.slice(
|
|
110
|
-
lastState.argsText.length
|
|
111
|
-
);
|
|
112
|
-
lastState.controller.argsText.append(argsTextDelta);
|
|
113
|
-
if (isArgsTextComplete(content.argsText)) {
|
|
114
|
-
lastState.controller.argsText.close();
|
|
115
|
-
}
|
|
116
|
-
lastToolStates.current[content.toolCallId] = {
|
|
117
|
-
argsText: content.argsText,
|
|
118
|
-
hasResult: lastState.hasResult,
|
|
119
|
-
controller: lastState.controller
|
|
120
|
-
};
|
|
121
133
|
}
|
|
122
134
|
if (content.result !== void 0 && !lastState.hasResult) {
|
|
123
135
|
lastState.controller.setResponse(
|
|
@@ -130,6 +142,7 @@ function useToolInvocations({
|
|
|
130
142
|
lastState.controller.close();
|
|
131
143
|
lastToolStates.current[content.toolCallId] = {
|
|
132
144
|
hasResult: true,
|
|
145
|
+
argsComplete: true,
|
|
133
146
|
argsText: lastState.argsText,
|
|
134
147
|
controller: lastState.controller
|
|
135
148
|
};
|
|
@@ -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 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 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 isInititialState = 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 (isInititialState.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 controller: toolCallController,\n };\n lastToolStates.current[content.toolCallId] = lastState;\n }\n\n if (content.argsText !== lastState.argsText) {\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 if (isArgsTextComplete(content.argsText)) {\n lastState.controller.argsText.close();\n }\n\n lastToolStates.current[content.toolCallId] = {\n argsText: content.argsText,\n hasResult: lastState.hasResult,\n controller: lastState.controller,\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 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 (isInititialState.current) {\n isInititialState.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 isInititialState.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 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 [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 isInititialState = 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 (isInititialState.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 { previous: lastState.argsText, next: content.argsText },\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 (isInititialState.current) {\n isInititialState.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 isInititialState.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"]}
|
package/package.json
CHANGED
|
@@ -53,6 +53,7 @@ export function useToolInvocations({
|
|
|
53
53
|
{
|
|
54
54
|
argsText: string;
|
|
55
55
|
hasResult: boolean;
|
|
56
|
+
argsComplete: boolean;
|
|
56
57
|
controller: ToolCallStreamController;
|
|
57
58
|
}
|
|
58
59
|
>
|
|
@@ -154,32 +155,44 @@ export function useToolInvocations({
|
|
|
154
155
|
lastState = {
|
|
155
156
|
argsText: "",
|
|
156
157
|
hasResult: false,
|
|
158
|
+
argsComplete: false,
|
|
157
159
|
controller: toolCallController,
|
|
158
160
|
};
|
|
159
161
|
lastToolStates.current[content.toolCallId] = lastState;
|
|
160
162
|
}
|
|
161
163
|
|
|
162
164
|
if (content.argsText !== lastState.argsText) {
|
|
163
|
-
if (
|
|
164
|
-
|
|
165
|
-
|
|
165
|
+
if (lastState.argsComplete) {
|
|
166
|
+
if (process.env["NODE_ENV"] !== "production") {
|
|
167
|
+
console.warn(
|
|
168
|
+
"argsText updated after controller was closed:",
|
|
169
|
+
{ previous: lastState.argsText, next: content.argsText },
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
if (!content.argsText.startsWith(lastState.argsText)) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
`Tool call argsText can only be appended, not updated: ${content.argsText} does not start with ${lastState.argsText}`,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const argsTextDelta = content.argsText.slice(
|
|
180
|
+
lastState.argsText.length,
|
|
166
181
|
);
|
|
167
|
-
|
|
182
|
+
lastState.controller.argsText.append(argsTextDelta);
|
|
168
183
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
184
|
+
const shouldClose = isArgsTextComplete(content.argsText);
|
|
185
|
+
if (shouldClose) {
|
|
186
|
+
lastState.controller.argsText.close();
|
|
187
|
+
}
|
|
173
188
|
|
|
174
|
-
|
|
175
|
-
|
|
189
|
+
lastToolStates.current[content.toolCallId] = {
|
|
190
|
+
argsText: content.argsText,
|
|
191
|
+
hasResult: lastState.hasResult,
|
|
192
|
+
argsComplete: shouldClose,
|
|
193
|
+
controller: lastState.controller,
|
|
194
|
+
};
|
|
176
195
|
}
|
|
177
|
-
|
|
178
|
-
lastToolStates.current[content.toolCallId] = {
|
|
179
|
-
argsText: content.argsText,
|
|
180
|
-
hasResult: lastState.hasResult,
|
|
181
|
-
controller: lastState.controller,
|
|
182
|
-
};
|
|
183
196
|
}
|
|
184
197
|
|
|
185
198
|
if (content.result !== undefined && !lastState.hasResult) {
|
|
@@ -194,6 +207,7 @@ export function useToolInvocations({
|
|
|
194
207
|
|
|
195
208
|
lastToolStates.current[content.toolCallId] = {
|
|
196
209
|
hasResult: true,
|
|
210
|
+
argsComplete: true,
|
|
197
211
|
argsText: lastState.argsText,
|
|
198
212
|
controller: lastState.controller,
|
|
199
213
|
};
|