@@ -214,6 +214,7 @@ function createExtensionAPI(
214214 runtime : ExtensionRuntime ,
215215 cwd : string ,
216216 eventBus : EventBus ,
217+ onRegistrationError ?: ( error : string ) => void ,
217218) : ExtensionAPI {
218219 const api = {
219220 // Registration methods - write to extension
@@ -226,8 +227,13 @@ function createExtensionAPI(
226227
227228 registerTool ( tool : ToolDefinition ) : void {
228229 runtime . assertActive ( ) ;
229- extension . tools . set ( tool . name , {
230- definition : tool ,
230+ const snapshot = snapshotExtensionToolDefinition ( tool ) ;
231+ if ( "error" in snapshot ) {
232+ onRegistrationError ?.( snapshot . error ) ;
233+ return ;
234+ }
235+ extension . tools . set ( snapshot . definition . name , {
236+ definition : snapshot . definition ,
231237 sourceInfo : extension . sourceInfo ,
232238 } ) ;
233239 runtime . refreshTools ( ) ;
@@ -365,6 +371,75 @@ function createExtensionAPI(
365371 return api ;
366372}
367373
374+ function snapshotExtensionToolDefinition (
375+ definition : ToolDefinition ,
376+ ) : { definition : ToolDefinition } | { error : string } {
377+ let toolName : string | undefined ;
378+ try {
379+ if ( ! definition || typeof definition !== "object" ) {
380+ return { error : "skipped extension tool registration: tool definition is not an object" } ;
381+ }
382+ const name = Reflect . get ( definition , "name" ) ;
383+ toolName = typeof name === "string" ? name : undefined ;
384+ const execute = Reflect . get ( definition , "execute" ) ;
385+ if ( ! toolName ) {
386+ return { error : "skipped extension tool registration: tool name is missing or invalid" } ;
387+ }
388+ if ( typeof execute !== "function" ) {
389+ return {
390+ error : `skipped extension tool registration for "${ toolName } ": execute is missing or invalid` ,
391+ } ;
392+ }
393+ const promptGuidelines = Reflect . get ( definition , "promptGuidelines" ) ;
394+ const prepareArguments = Reflect . get ( definition , "prepareArguments" ) ;
395+ const renderCall = Reflect . get ( definition , "renderCall" ) ;
396+ const renderResult = Reflect . get ( definition , "renderResult" ) ;
397+ const executeWithReceiver = ( ( ...args : Parameters < ToolDefinition [ "execute" ] > ) =>
398+ Reflect . apply ( execute , definition , args ) ) as ToolDefinition [ "execute" ] ;
399+ return {
400+ definition : {
401+ name : toolName ,
402+ label : Reflect . get ( definition , "label" ) ,
403+ description : Reflect . get ( definition , "description" ) ,
404+ promptSnippet : Reflect . get ( definition , "promptSnippet" ) ,
405+ promptGuidelines : Array . isArray ( promptGuidelines )
406+ ? [ ...promptGuidelines ]
407+ : promptGuidelines ,
408+ parameters : Reflect . get ( definition , "parameters" ) ,
409+ renderShell : Reflect . get ( definition , "renderShell" ) ,
410+ prepareArguments :
411+ typeof prepareArguments === "function"
412+ ? ( ( ( ...args : Parameters < NonNullable < ToolDefinition [ "prepareArguments" ] > > ) =>
413+ Reflect . apply (
414+ prepareArguments ,
415+ definition ,
416+ args ,
417+ ) ) as ToolDefinition [ "prepareArguments" ] )
418+ : prepareArguments ,
419+ executionMode : Reflect . get ( definition , "executionMode" ) ,
420+ execute : executeWithReceiver ,
421+ renderCall :
422+ typeof renderCall === "function"
423+ ? ( ( ( ...args : Parameters < NonNullable < ToolDefinition [ "renderCall" ] > > ) =>
424+ Reflect . apply ( renderCall , definition , args ) ) as ToolDefinition [ "renderCall" ] )
425+ : renderCall ,
426+ renderResult :
427+ typeof renderResult === "function"
428+ ? ( ( ( ...args : Parameters < NonNullable < ToolDefinition [ "renderResult" ] > > ) =>
429+ Reflect . apply ( renderResult , definition , args ) ) as ToolDefinition [ "renderResult" ] )
430+ : renderResult ,
431+ } ,
432+ } ;
433+ } catch ( error ) {
434+ const message = error instanceof Error ? error . message : String ( error ) ;
435+ return {
436+ error : toolName
437+ ? `skipped extension tool registration for "${ toolName } ": ${ message } `
438+ : `skipped extension tool registration: ${ message } ` ,
439+ } ;
440+ }
441+ }
442+
368443function resolveExtensionFactory ( module : unknown ) : ExtensionFactory | undefined {
369444 const candidate =
370445 typeof module === "object" && module !== null && "default" in module
@@ -487,26 +562,34 @@ async function loadExtension(
487562 cwd : string ,
488563 eventBus : EventBus ,
489564 runtime : ExtensionRuntime ,
490- ) : Promise < { extension : Extension | null ; error : string | null } > {
565+ ) : Promise < { extension : Extension | null ; error : string | null ; registrationErrors : string [ ] } > {
491566 const resolvedPath = resolvePath ( extensionPath , cwd ) ;
567+ const registrationErrors : string [ ] = [ ] ;
492568
493569 try {
494570 const factory = await loadExtensionModule ( resolvedPath ) ;
495571 if ( ! factory ) {
496572 return {
497573 extension : null ,
498574 error : `Extension does not export a valid factory function: ${ extensionPath } ` ,
575+ registrationErrors,
499576 } ;
500577 }
501578
502579 const extension = createExtension ( extensionPath , resolvedPath ) ;
503- const api = createExtensionAPI ( extension , runtime , cwd , eventBus ) ;
580+ const api = createExtensionAPI ( extension , runtime , cwd , eventBus , ( error ) => {
581+ registrationErrors . push ( error ) ;
582+ } ) ;
504583 await factory ( api ) ;
505584
506- return { extension, error : null } ;
585+ return { extension, error : null , registrationErrors } ;
507586 } catch ( err ) {
508587 const message = err instanceof Error ? err . message : String ( err ) ;
509- return { extension : null , error : `Failed to load extension: ${ message } ` } ;
588+ return {
589+ extension : null ,
590+ error : `Failed to load extension: ${ message } ` ,
591+ registrationErrors,
592+ } ;
510593 }
511594}
512595
@@ -519,9 +602,12 @@ export async function loadExtensionFromFactory(
519602 eventBus : EventBus ,
520603 runtime : ExtensionRuntime ,
521604 extensionPath = "<inline>" ,
605+ registrationErrors ?: string [ ] ,
522606) : Promise < Extension > {
523607 const extension = createExtension ( extensionPath , extensionPath ) ;
524- const api = createExtensionAPI ( extension , runtime , cwd , eventBus ) ;
608+ const api = createExtensionAPI ( extension , runtime , cwd , eventBus , ( error ) => {
609+ registrationErrors ?. push ( error ) ;
610+ } ) ;
525611 await factory ( api ) ;
526612 return extension ;
527613}
@@ -540,16 +626,27 @@ export async function loadExtensions(
540626 const runtime = createExtensionRuntime ( ) ;
541627
542628 for ( const extPath of paths ) {
543- const { extension, error } = await loadExtension ( extPath , cwd , resolvedEventBus , runtime ) ;
629+ const { extension, error, registrationErrors } = await loadExtension (
630+ extPath ,
631+ cwd ,
632+ resolvedEventBus ,
633+ runtime ,
634+ ) ;
544635
545636 if ( error ) {
546637 errors . push ( { path : extPath , error } ) ;
638+ for ( const registrationError of registrationErrors ) {
639+ errors . push ( { path : extPath , error : registrationError } ) ;
640+ }
547641 continue ;
548642 }
549643
550644 if ( extension ) {
551645 extensions . push ( extension ) ;
552646 }
647+ for ( const registrationError of registrationErrors ) {
648+ errors . push ( { path : extPath , error : registrationError } ) ;
649+ }
553650 }
554651
555652 return {
0 commit comments