11import { Chat } from "@ai-sdk/vue" ;
2- import { computed , ComputedRef , watch } from "vue" ;
2+ import { computed , ComputedRef , ref , Ref , watch } from "vue" ;
33import { AvailableProviders , AvailableModels } from "@/config" ;
44import { tools , userRejectedToolCall } from "@/tools" ;
55import {
@@ -52,6 +52,8 @@ class AIShellChat {
5252 readonly pendingToolCallIds : ComputedRef < string [ ] > ;
5353 readonly askingPermission : ComputedRef < boolean > ;
5454
55+ /** Force the `status` if not `null` */
56+ private forceStatus = ref < ChatStatus | null > ( ) ;
5557 private chat : Chat < UIMessage > ;
5658 private emitter = mitt < {
5759 finish : void ;
@@ -71,7 +73,7 @@ class AIShellChat {
7173
7274 this . messages = computed ( ( ) => this . chat . messages ) ;
7375 this . error = computed ( ( ) => this . chat . error ) ;
74- this . status = computed ( ( ) => this . chat . status ) ;
76+ this . status = computed ( ( ) => this . forceStatus . value ?? this . chat . status ) ;
7577 this . pendingToolCallIds = computed ( ( ) =>
7678 this . pendingToolCalls . map ( ( t ) => t . toolCallId ) ,
7779 ) ;
@@ -80,11 +82,11 @@ class AIShellChat {
8082 ) ;
8183 }
8284
83- async send ( message : string | UIMessage [ 'parts' ] , options : SendOptions ) {
84- await this . chat . sendMessage (
85- typeof message === "string"
86- ? { text : message }
87- : { parts : message } ,
85+ /** Pass `undefined` to trigger the API without sending the message */
86+ async send ( message : string | undefined , options : SendOptions ) {
87+ await this . chat . sendMessage ( message
88+ ? { text : message }
89+ : undefined ,
8890 {
8991 body : {
9092 sendOptions : options ,
@@ -124,7 +126,7 @@ class AIShellChat {
124126 **/
125127 rejectPermission ( options ?:
126128 { toolCallId : string ; }
127- | { toolCallId : string ; userEditedCode : string ; sendOptions : SendOptions }
129+ | { toolCallId : string ; editedQuery : string ; sendOptions : SendOptions }
128130 ) {
129131 if ( ! options ) {
130132 this . pendingToolCalls . forEach ( ( t ) => {
@@ -138,16 +140,118 @@ class AIShellChat {
138140 throw new Error ( `Tool call with id ${ options . toolCallId } not found` ) ;
139141 }
140142
141- if ( 'userEditedCode ' in options ) {
143+ if ( 'editedQuery ' in options ) {
142144 const sendFollowupMessage = async ( ) => {
143145 this . emitter . off ( "finish" , sendFollowupMessage ) ;
144- this . send (
145- [ {
146- type : "data-userEditedCode" ,
147- data : { code : options . userEditedCode }
148- } ] ,
149- options . sendOptions
146+
147+ const assistantMessageId = this . chat . generateId ( ) ;
148+ const replacementToolCallId = this . chat . generateId ( ) ;
149+
150+ this . forceStatus . value = "streaming" ;
151+
152+ this . chat . messages = [
153+ ...this . chat . messages . slice ( 0 , - 1 ) ,
154+ {
155+ ...this . chat . lastMessage ! ,
156+ parts : [
157+ ...this . chat . lastMessage ! . parts ,
158+ {
159+ type : "data-userEditedToolCall" ,
160+ data : { replacementToolCallId } ,
161+ }
162+ ] ,
163+ } ,
164+ {
165+ id : this . chat . generateId ( ) ,
166+ role : "user" ,
167+ parts : [ {
168+ // We use data so it's not shown in the UI
169+ type : "data-editedQuery" ,
170+ data : {
171+ query : options . editedQuery ,
172+ targetToolCallId : options . toolCallId ,
173+ } ,
174+ } ] ,
175+ } ,
176+ {
177+ id : assistantMessageId ,
178+ role : "assistant" ,
179+ parts : [
180+ { type : "step-start" } ,
181+ {
182+ type : "data-toolReplacement" ,
183+ data : { targetToolCallId : options . toolCallId } ,
184+ } ,
185+ {
186+ type : "tool-run_query" ,
187+ state : "input-available" ,
188+ toolCallId : replacementToolCallId ,
189+ input : { query : options . editedQuery } ,
190+ } ,
191+ ] ,
192+ } ,
193+ ] ;
194+
195+ const runQueryOutput = await this . createRunQueryToolOutput (
196+ replacementToolCallId ,
197+ options . editedQuery
150198 ) ;
199+
200+ if ( runQueryOutput . state === "output-error" ) {
201+ this . chat . messages = [
202+ ...this . chat . messages . slice ( 0 , - 1 ) ,
203+ {
204+ id : assistantMessageId ,
205+ role : "assistant" ,
206+ parts : [
207+ { type : "step-start" } ,
208+ {
209+ type : "data-toolReplacement" ,
210+ data : { targetToolCallId : options . toolCallId } ,
211+ } ,
212+ {
213+ type : "tool-run_query" ,
214+ state : "output-error" ,
215+ toolCallId : replacementToolCallId ,
216+ input : { query : options . editedQuery } ,
217+ errorText : runQueryOutput . errorText ,
218+ } ,
219+ ] ,
220+ } ,
221+ ] ;
222+ } else {
223+ this . chat . messages = [
224+ ...this . chat . messages . slice ( 0 , - 1 ) ,
225+ {
226+ id : assistantMessageId ,
227+ role : "assistant" ,
228+ parts : [
229+ { type : "step-start" } ,
230+ {
231+ type : "data-toolReplacement" ,
232+ data : { targetToolCallId : options . toolCallId } ,
233+ } ,
234+ {
235+ type : "tool-run_query" ,
236+ state : "output-available" ,
237+ toolCallId : replacementToolCallId ,
238+ input : { query : options . editedQuery } ,
239+ // @ts -expect-error ts doesnt like this because the execute()
240+ // method for run_query (see tools/index.ts) is not defined.
241+ // It can be fixed:
242+ // 1. Upgrading to AI SDK v6
243+ // 2. Use the new API for user permission check
244+ // 3. define execute() method
245+ output : runQueryOutput . output ,
246+ } ,
247+ ] ,
248+ } ,
249+ ] ;
250+ }
251+
252+ this . forceStatus . value = null ;
253+
254+ this . send ( undefined , options . sendOptions ) ;
151255 } ;
152256 this . emitter . on ( "finish" , sendFollowupMessage ) ;
153257 }
@@ -264,11 +368,11 @@ class AIShellChat {
264368 modelId : sendOptions . modelId ,
265369 messages : convertToModelMessages < UIMessage > ( m . messages , {
266370 convertDataPart ( part ) {
267- if ( part . type === "data-userEditedCode " ) {
371+ if ( part . type === "data-editedQuery " ) {
268372 return {
269373 type : "text" ,
270374 text : "Please run the following code instead:\n```\n"
271- + part . data . code
375+ + part . data . query
272376 + "\n```" ,
273377 } ;
274378 }
0 commit comments