1
- import { generateText , streamText , Provider , type LanguageModelUsage , type ToolSet } from 'ai' ;
1
+ import { generateText , streamText , Provider , type LanguageModelUsage , type ToolSet , tool } from 'ai' ;
2
2
import { Message , MessageRole } from '../../types/chat' ;
3
3
import { AiServiceProvider , CompletionOptions } from '../core/ai-service-provider' ;
4
4
import { SettingsService } from '../settings-service' ;
@@ -7,7 +7,8 @@ import { v4 as uuidv4 } from 'uuid';
7
7
import { MessageHelper } from '../message-helper' ;
8
8
import { AIServiceCapability , mapModelCapabilities } from '../../types/capabilities' ;
9
9
import { ModelSettings } from '../../types/settings' ;
10
- import { LanguageModelV1 , ToolChoice } from 'ai' ;
10
+ import { LanguageModelV1 } from 'ai' ;
11
+ import { z } from 'zod' ;
11
12
12
13
// Define an interface for tool results to fix the 'never' type errors
13
14
interface ToolResult {
@@ -159,23 +160,64 @@ export class CommonProviderHelper implements AiServiceProvider {
159
160
modelInstance : LanguageModelV1 ,
160
161
messages : Message [ ] ,
161
162
options : CompletionOptions ,
162
- streamController : StreamControlHandler ,
163
- tools : ToolSet | undefined = undefined ,
164
- toolChoice : ToolChoice < ToolSet > | undefined = undefined
163
+ streamController : StreamControlHandler
165
164
) : Promise < Message > {
166
165
try {
167
166
const formattedMessages = await MessageHelper . MessagesContentToOpenAIFormat ( messages ) ;
168
167
169
168
console . log ( 'formattedMessages: ' , formattedMessages ) ;
170
169
170
+ // Build ToolSet & ToolChoice for getChatCompletionByModel API
171
+ const rawTools = options . tools ;
172
+
173
+ // Convert raw tools to AI SDK format
174
+ const formattedTools : ToolSet = { } ;
175
+
176
+ if ( rawTools && typeof rawTools === 'object' ) {
177
+ for ( const [ toolName , toolConfig ] of Object . entries ( rawTools ) ) {
178
+ if ( toolConfig && typeof toolConfig === 'object' ) {
179
+ // Special case for image generation
180
+ if ( toolName === 'generate_image' ) {
181
+ formattedTools [ toolName ] = tool ( {
182
+ description : 'Generate an image from a text prompt' ,
183
+ parameters : z . object ( {
184
+ prompt : z . string ( ) . describe ( 'The text prompt to generate an image from' ) ,
185
+ size : z . string ( ) . optional ( ) . describe ( 'The size of the image to generate' ) ,
186
+ style : z . enum ( [ 'vivid' , 'natural' ] ) . optional ( ) . describe ( 'The style of the image to generate' )
187
+ } ) ,
188
+ execute : async ( args ) => {
189
+ // Execute is handled later in the tool call handler
190
+ return ( toolConfig as ToolWithExecute ) . execute ( args ) ;
191
+ }
192
+ } ) ;
193
+ } else {
194
+ // For other tools, try to extract description and parameters
195
+ const toolWithExecute = toolConfig as ToolWithExecute ;
196
+ const description = ( toolConfig as { description ?: string } ) . description || `Execute ${ toolName } tool` ;
197
+
198
+ // Create a fallback schema if not provided
199
+ const parameters = z . object ( { } ) . catchall ( z . unknown ( ) ) ;
200
+
201
+ formattedTools [ toolName ] = tool ( {
202
+ description,
203
+ parameters,
204
+ execute : async ( args ) => {
205
+ if ( typeof toolWithExecute . execute === 'function' ) {
206
+ return toolWithExecute . execute ( args ) ;
207
+ }
208
+ throw new Error ( `Tool ${ toolName } does not have an execute function` ) ;
209
+ }
210
+ } ) ;
211
+ }
212
+ }
213
+ }
214
+ }
215
+
171
216
let fullText = '' ;
172
217
173
218
if ( options . stream ) {
174
219
console . log ( `Streaming ${ options . provider } /${ options . model } response` ) ;
175
220
176
- // Prepare tools for AI SDK format if they exist
177
- const toolsForStream = tools || options . tools as unknown as ToolSet ;
178
-
179
221
const result = streamText ( {
180
222
model : modelInstance ,
181
223
abortSignal : streamController . getAbortSignal ( ) ,
@@ -185,8 +227,7 @@ export class CommonProviderHelper implements AiServiceProvider {
185
227
topP : options . top_p ,
186
228
frequencyPenalty : options . frequency_penalty ,
187
229
presencePenalty : options . presence_penalty ,
188
- tools : toolsForStream ,
189
- toolChoice : toolChoice ,
230
+ tools : Object . keys ( formattedTools ) . length > 0 ? formattedTools : undefined ,
190
231
toolCallStreaming : true ,
191
232
onFinish : ( result : { usage : LanguageModelUsage } ) => {
192
233
console . log ( 'OpenAI streaming chat completion finished' ) ;
@@ -266,9 +307,9 @@ export class CommonProviderHelper implements AiServiceProvider {
266
307
error instanceof Error ? error : new Error ( 'Unknown error in image generation' )
267
308
) ;
268
309
}
269
- } else if ( options . tools ) {
310
+ } else if ( rawTools ) {
270
311
// Use a safer way to check for and execute tools
271
- const toolsMap = options . tools as Record < string , unknown > ;
312
+ const toolsMap = rawTools as Record < string , unknown > ;
272
313
const tool = toolsMap [ toolName ] as ToolWithExecute | undefined ;
273
314
274
315
if ( tool && typeof tool . execute === 'function' ) {
@@ -300,9 +341,6 @@ export class CommonProviderHelper implements AiServiceProvider {
300
341
else {
301
342
console . log ( `Generating ${ options . provider } /${ options . model } response` ) ;
302
343
303
- // Prepare tools for AI SDK format if they exist
304
- const toolsForGenerate = tools || options . tools as unknown as ToolSet ;
305
-
306
344
const { text, usage, toolResults } = await generateText ( {
307
345
model : modelInstance ,
308
346
messages : formattedMessages ,
@@ -311,8 +349,7 @@ export class CommonProviderHelper implements AiServiceProvider {
311
349
topP : options . top_p ,
312
350
frequencyPenalty : options . frequency_penalty ,
313
351
presencePenalty : options . presence_penalty ,
314
- tools : toolsForGenerate ,
315
- toolChoice : toolChoice ,
352
+ tools : Object . keys ( formattedTools ) . length > 0 ? formattedTools : undefined ,
316
353
maxSteps : 3 , // Allow multiple steps for tool calls
317
354
} ) ;
318
355
0 commit comments