@@ -18,7 +18,13 @@ import {
1818 OpenAICompatibleChatLanguageModel ,
1919 VERSION ,
2020} from '@codebuff/internal/openai-compatible/index'
21- import { streamText , APICallError , generateText , generateObject } from 'ai'
21+ import {
22+ streamText ,
23+ APICallError ,
24+ generateText ,
25+ generateObject ,
26+ NoSuchToolError ,
27+ } from 'ai'
2228
2329import { WEBSITE_URL } from '../constants'
2430import { NetworkError , PaymentRequiredError , ErrorCodes } from '../errors'
@@ -217,6 +223,115 @@ export async function* promptAiSdkStream(
217223 ...params ,
218224 agentProviderOptions : params . agentProviderOptions ,
219225 } ) ,
226+ // Transform agent tool calls (e.g. 'file-picker') into spawn_agents calls
227+ experimental_repairToolCall : async ( { toolCall, tools, error } ) => {
228+ // Only handle NoSuchToolError - when model tries to call a non-existent tool
229+ if ( ! NoSuchToolError . isInstance ( error ) ) {
230+ return null
231+ }
232+
233+ const { spawnableAgents = [ ] , localAgentTemplates = { } } = params
234+
235+ // Check if spawn_agents tool is available
236+ if ( ! ( 'spawn_agents' in tools ) ) {
237+ return null
238+ }
239+
240+ // Check if the tool name matches a spawnable agent or local agent template
241+ const toolName = toolCall . toolName
242+ const isSpawnableAgent = spawnableAgents . some ( ( agentId ) => {
243+ // Match by exact name or by the agent part of publisher/agent@version format
244+ const withoutVersion = agentId . split ( '@' ) [ 0 ]
245+ const parts = withoutVersion . split ( '/' )
246+ const agentName = parts [ parts . length - 1 ]
247+ return agentName === toolName || agentId === toolName
248+ } )
249+ const isLocalAgent = toolName in localAgentTemplates
250+
251+ if ( ! isSpawnableAgent && ! isLocalAgent ) {
252+ return null
253+ }
254+
255+ // Parse the input - it comes as a JSON string from the AI SDK
256+ // We also need to recursively parse any nested JSON strings
257+ const deepParseJson = ( value : unknown ) : unknown => {
258+ if ( typeof value === 'string' ) {
259+ try {
260+ const parsed = JSON . parse ( value )
261+ // Recursively parse the result in case it contains more JSON strings
262+ return deepParseJson ( parsed )
263+ } catch {
264+ // Not valid JSON, return as-is
265+ return value
266+ }
267+ }
268+ if ( Array . isArray ( value ) ) {
269+ return value . map ( deepParseJson )
270+ }
271+ if ( value !== null && typeof value === 'object' ) {
272+ const result : Record < string , unknown > = { }
273+ for ( const [ k , v ] of Object . entries ( value ) ) {
274+ result [ k ] = deepParseJson ( v )
275+ }
276+ return result
277+ }
278+ return value
279+ }
280+
281+ let input : Record < string , unknown > = { }
282+ try {
283+ const rawInput =
284+ typeof toolCall . input === 'string'
285+ ? JSON . parse ( toolCall . input )
286+ : ( toolCall . input as Record < string , unknown > )
287+ // Deep parse to handle any nested JSON strings
288+ input = deepParseJson ( rawInput ) as Record < string , unknown >
289+ } catch {
290+ // If parsing fails, use empty object
291+ }
292+
293+ // Extract prompt from input if it exists and is a string
294+ const prompt =
295+ typeof input . prompt === 'string' ? input . prompt : undefined
296+
297+ // All other input parameters become the params object
298+ // These should NOT be stringified - they should remain as objects
299+ const agentParams : Record < string , unknown > = { }
300+ for ( const [ key , value ] of Object . entries ( input ) ) {
301+ if ( key === 'prompt' && typeof value === 'string' ) {
302+ continue
303+ }
304+ agentParams [ key ] = value
305+ }
306+
307+ // Transform into spawn_agents call
308+ // The input must be a JSON string for the AI SDK, but the nested objects
309+ // should remain as proper objects (not stringified)
310+ const spawnAgentsInput = {
311+ agents : [
312+ {
313+ agent_type : toolName ,
314+ ...( prompt !== undefined && { prompt } ) ,
315+ ...( Object . keys ( agentParams ) . length > 0 && { params : agentParams } ) ,
316+ } ,
317+ ] ,
318+ }
319+
320+ logger . info (
321+ {
322+ originalToolName : toolName ,
323+ transformedInput : spawnAgentsInput ,
324+ } ,
325+ 'Transformed agent tool call to spawn_agents' ,
326+ )
327+
328+ // Return the repaired tool call - input must be a JSON string
329+ return {
330+ ...toolCall ,
331+ toolName : 'spawn_agents' ,
332+ input : JSON . stringify ( spawnAgentsInput ) ,
333+ }
334+ } ,
220335 } )
221336
222337 let content = ''
0 commit comments