@@ -121,7 +121,7 @@ export class RunSubagentTool extends Disposable implements IToolImpl {
121121
122122 properties . model = {
123123 type : 'string' ,
124- description : 'Optional model for the subagent. Use a fast model for simple lookups and searches, or a reasoning model for complex analysis. Format: "Model Name (Vendor)" - vendor is usually "copilot". If not provided, uses the current model.' ,
124+ description : 'Optional model for the subagent. Format: "Model Name (Vendor)", vendor is usually "copilot". Only use to enforce a specific model.' ,
125125 } ;
126126
127127 const required : string [ ] = [ 'prompt' , 'description' ] ;
@@ -424,25 +424,77 @@ export class RunSubagentTool extends Disposable implements IToolImpl {
424424 }
425425
426426 /**
427- * Returns the list of available model qualified names suitable for agent mode.
427+ * Checks if a model exceeds the main model's cost tier based on multiplier.
428+ * @returns An object with `exceeds: true` and a reason string if blocked, or `exceeds: false` if allowed.
428429 */
429- private getAvailableModelNames ( ) : string [ ] {
430- return this . languageModelsService . getLanguageModelIds ( )
431- . map ( id => this . languageModelsService . lookupLanguageModel ( id ) )
432- . filter ( ( m ) : m is ILanguageModelChatMetadata =>
433- ! ! m
434- && ILanguageModelChatMetadata . suitableForAgentMode ( m )
435- && m . isUserSelectable !== false
436- && ! m . targetChatSessionType
437- )
438- . map ( m => ILanguageModelChatMetadata . asQualifiedName ( m ) ) ;
430+ private checkMultiplierConstraint ( modelId : string , mainModelId : string | undefined ) : { exceeds : false } | { exceeds : true ; reason : string } {
431+ if ( ! mainModelId || modelId === mainModelId ) {
432+ return { exceeds : false } ;
433+ }
434+
435+ const mainModelMetadata = this . languageModelsService . lookupLanguageModel ( mainModelId ) ;
436+ const modelMetadata = this . languageModelsService . lookupLanguageModel ( modelId ) ;
437+ const mainMultiplier = mainModelMetadata ?. multiplierNumeric ;
438+ const modelMultiplier = modelMetadata ?. multiplierNumeric ;
439+
440+ if ( mainMultiplier !== undefined && modelMultiplier !== undefined && modelMultiplier > mainMultiplier ) {
441+ return {
442+ exceeds : true ,
443+ reason : `exceeds the current model's cost tier (${ modelMultiplier } x vs ${ mainMultiplier } x)`
444+ } ;
445+ }
446+
447+ return { exceeds : false } ;
448+ }
449+
450+ /**
451+ * Returns information about available models for error messages.
452+ * Includes which models are unavailable due to multiplier restrictions.
453+ */
454+ private getAvailableModelsInfo ( mainModelId : string | undefined ) : string {
455+ const models = this . languageModelsService . getLanguageModelIds ( )
456+ . map ( id => ( { id, metadata : this . languageModelsService . lookupLanguageModel ( id ) } ) )
457+ . filter ( ( m ) : m is { id : string ; metadata : ILanguageModelChatMetadata } =>
458+ ! ! m . metadata
459+ && ILanguageModelChatMetadata . suitableForAgentMode ( m . metadata )
460+ && m . metadata . isUserSelectable !== false
461+ && ! m . metadata . targetChatSessionType
462+ ) ;
463+
464+ if ( models . length === 0 ) {
465+ return 'No models available.' ;
466+ }
467+
468+ const available : string [ ] = [ ] ;
469+ const unavailableDueToMultiplier : string [ ] = [ ] ;
470+
471+ for ( const { id, metadata } of models ) {
472+ const qualifiedName = ILanguageModelChatMetadata . asQualifiedName ( metadata ) ;
473+ const check = this . checkMultiplierConstraint ( id , mainModelId ) ;
474+
475+ if ( check . exceeds ) {
476+ unavailableDueToMultiplier . push ( qualifiedName ) ;
477+ } else {
478+ available . push ( qualifiedName ) ;
479+ }
480+ }
481+
482+ const parts : string [ ] = [ ] ;
483+ if ( available . length > 0 ) {
484+ parts . push ( `Available models: ${ available . join ( ', ' ) } ` ) ;
485+ }
486+ if ( unavailableDueToMultiplier . length > 0 ) {
487+ parts . push ( `Unavailable (exceeds current model's cost tier): ${ unavailableDueToMultiplier . join ( ', ' ) } ` ) ;
488+ }
489+
490+ return parts . join ( '. ' ) || 'No models available.' ;
439491 }
440492
441493 /**
442- * Resolves the model to be used by a subagent, applying multiplier-based
443- * fallback to avoid using a more expensive model than the main agent.
494+ * Resolves the model to be used by a subagent.
444495 * @param explicitModelQualifiedName Optional explicit model specified by the caller.
445- * If provided and not found, throws an error with the list of available models.
496+ * If provided and not found or not allowed, throws an error with available models.
497+ * @throws Error if the requested model is not found or exceeds the main model's cost tier.
446498 */
447499 private resolveSubagentModel ( subagent : ICustomAgent | undefined , mainModelId : string | undefined , explicitModelQualifiedName ?: string ) : { modeModelId : string | undefined ; resolvedModelName : string | undefined } {
448500 let modeModelId = mainModelId ;
@@ -456,9 +508,7 @@ export class RunSubagentTool extends Disposable implements IToolImpl {
456508 explicitModelResolved = true ;
457509 } else {
458510 // Model not found - throw error with available models
459- const available = this . getAvailableModelNames ( ) ;
460- const availableList = available . length > 0 ? `Available models: ${ available . join ( ', ' ) } ` : 'No models available.' ;
461- throw new Error ( `Requested model '${ explicitModelQualifiedName } ' not found. ${ availableList } ` ) ;
511+ throw new Error ( `Requested model '${ explicitModelQualifiedName } ' not found. ${ this . getAvailableModelsInfo ( mainModelId ) } ` ) ;
462512 }
463513 }
464514
@@ -476,16 +526,12 @@ export class RunSubagentTool extends Disposable implements IToolImpl {
476526 }
477527 }
478528
479- // If the resolved model has a larger multiplier than the main agent's model,
480- // fall back to the main agent's model to avoid using a more expensive model.
481- if ( modeModelId && modeModelId !== mainModelId ) {
482- const mainModelMetadata = mainModelId ? this . languageModelsService . lookupLanguageModel ( mainModelId ) : undefined ;
483- const subagentModelMetadata = this . languageModelsService . lookupLanguageModel ( modeModelId ) ;
484- const mainMultiplier = mainModelMetadata ?. multiplierNumeric ;
485- const subagentMultiplier = subagentModelMetadata ?. multiplierNumeric ;
486- if ( mainMultiplier !== undefined && subagentMultiplier !== undefined && subagentMultiplier > mainMultiplier ) {
487- this . logService . warn ( `[RunSubagentTool] Requested model '${ subagentModelMetadata ?. name } ' (multiplier: ${ subagentMultiplier } ) has a larger multiplier than the main agent model '${ mainModelMetadata ?. name } ' (multiplier: ${ mainMultiplier } ). Falling back to the main agent model.` ) ;
488- modeModelId = mainModelId ;
529+ // Check multiplier constraint - throw error if requested model exceeds main model's cost tier
530+ if ( modeModelId ) {
531+ const check = this . checkMultiplierConstraint ( modeModelId , mainModelId ) ;
532+ if ( check . exceeds ) {
533+ const modelMetadata = this . languageModelsService . lookupLanguageModel ( modeModelId ) ;
534+ throw new Error ( `Requested model '${ modelMetadata ?. name } ' ${ check . reason } . ${ this . getAvailableModelsInfo ( mainModelId ) } ` ) ;
489535 }
490536 }
491537
0 commit comments