feat(chat): add 'model' parameter to runSubagent tool for call-time model override#306841
feat(chat): add 'model' parameter to runSubagent tool for call-time model override#306841RockyWearsAHat wants to merge 2 commits intomicrosoft:mainfrom
Conversation
…odel override Add an optional `model` parameter to the `runSubagent` built-in tool that allows callers to specify which language model should be used for a subagent invocation, overriding the agent's default model from frontmatter. ## Changes ### runSubagentTool.ts - Add `model?: string` to `IRunSubagentToolInputParams` - Add `model` property to the tool's input schema in `getToolData()` - Extract `resolveModelHint()` helper that resolves a model hint string by trying qualified name lookup first, then direct model ID lookup - Extend `resolveSubagentModel()` with optional `callTimeModelHint` parameter that takes highest precedence over agent frontmatter - Move multiplier cap logic outside the `if (subagent)` block so it applies uniformly to both agent frontmatter and call-time overrides - Update `invoke()` no-subagent branch to read `modeModelId` from cache and handle call-time model override without a named agent - Update `prepareToolInvocation()` to pass `args.model` through ### runSubagentTool.test.ts - Add schema test: `model` property appears in tool data - Add 6 call-time model parameter tests: - Override via qualified name without a subagent - Override via direct model ID without a subagent - Call-time model takes precedence over agent frontmatter - Call-time model respects multiplier cap - Unknown model name falls back gracefully - Override with cheaper model on a named agent ## Design Resolution priority: 1. Call-time `model` parameter (highest precedence) 2. Agent's `model:` frontmatter qualified names 3. Parent model (default) After resolution, the existing multiplier-based cost cap still applies: a subagent cannot use a more expensive model than the parent agent regardless of how the model was specified. Fixes microsoft#306836
There was a problem hiding this comment.
Pull request overview
Adds an optional call-time model override to the built-in runSubagent tool so orchestrator agents can route individual subagent invocations to specific language models, while still enforcing the existing multiplier-based cost cap.
Changes:
- Extend
runSubagenttool input schema and params with optionalmodel?: string. - Implement model-hint resolution (qualified name → model id) and update model resolution priority (call-time override → agent frontmatter → parent).
- Add a dedicated test suite covering precedence, qualified-name/id resolution, cost-cap behavior, and fallback cases.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/vs/workbench/contrib/chat/common/tools/builtinTools/runSubagentTool.ts | Adds model param support, resolves model hints, and applies multiplier cap across all resolution paths. |
| src/vs/workbench/contrib/chat/test/common/tools/builtinTools/runSubagentTool.test.ts | Adds schema coverage and multiple call-time model override behavior tests. |
| if (modeModelId && modeModelId !== mainModelId) { | ||
| const mainModelMetadata = mainModelId ? this.languageModelsService.lookupLanguageModel(mainModelId) : undefined; | ||
| const resolvedModelMetadata = this.languageModelsService.lookupLanguageModel(modeModelId); | ||
| const mainMultiplier = mainModelMetadata?.multiplierNumeric; | ||
| const resolvedMultiplier = resolvedModelMetadata?.multiplierNumeric; | ||
| if (mainMultiplier !== undefined && resolvedMultiplier !== undefined && resolvedMultiplier > mainMultiplier) { | ||
| const source = callTimeModelHint ? 'Call-time' : subagent ? `Subagent '${subagent.name}'` : 'Unknown'; | ||
| this.logService.warn(`[RunSubagentTool] ${source} requested model '${resolvedModelMetadata?.name}' (multiplier: ${resolvedMultiplier}) which exceeds the main agent model '${mainModelMetadata?.name}' (multiplier: ${mainMultiplier}). Falling back to the main agent model.`); | ||
| modeModelId = mainModelId; |
There was a problem hiding this comment.
In resolveSubagentModel, the multiplier-cap warning uses source = callTimeModelHint ? 'Call-time' : .... If a call-time model hint is provided but doesn’t resolve (so the override is ignored), the subsequent multiplier-cap warning will still attribute the model request to “Call-time”, even though the selected model may have come from the subagent frontmatter. Consider tracking whether the call-time override actually applied (e.g., a boolean or enum resolutionSource) and use that for the warning message.
| suite('call-time model parameter', () => { | ||
| function createMetadata(name: string, multiplierNumeric?: number): ILanguageModelChatMetadata { | ||
| return { | ||
| extension: new ExtensionIdentifier('test.extension'), | ||
| name, |
There was a problem hiding this comment.
This suite redefines helper functions (createMetadata/createTool/createAgent) that already exist in the earlier "model fallback behavior" suite in the same file. Consider extracting these helpers once (e.g., to file scope or a shared helper) to avoid duplication and keep future changes (like new required metadata fields) from needing updates in multiple places.
Previous commit exposed the runSubagent `model` schema unconditionally, which conflicted with the feature toggle that controls custom-agent usage. This change moves the property description under the `customAgentInSubagent` gate so the schema only offers `model` when agents are available, and updates the schema test to enable that flag to keep coverage aligned.
|
Thanks for the PR, but this is already in progress in #298161, and we should drive that forward cc @digitarald |
Summary
Add an optional
modelparameter to therunSubagentbuilt-in tool, allowing callers to specify which language model a subagent invocation should use at call time, independent of the agent'smodel:frontmatter configuration.Motivation
Currently, subagent model assignment is static — determined by the agent definition's
model:frontmatter and resolved at agent load time. This works for fixed agent pipelines, but leaves orchestrator agents unable to route individual tasks to the most appropriate model based on task complexity.Use cases:
claude-haiku-4-5) and reserves the parent model for complex reasoning.list_language_models) enable the LLM to make informed model selection decisions.Design
Resolution Priority
The model resolution in
resolveSubagentModel()now follows this priority order:modelparameter (highest precedence) — from the tool invocationmodel:frontmatter — qualified names from.agent.mdModel Resolution
The new
resolveModelHint()helper resolves a model hint string by:lookupLanguageModelByQualifiedName()(e.g.,"Claude Sonnet 4.6 (Copilot)")lookupLanguageModel()(e.g.,"claude-sonnet-4-6")undefinedif neither resolves, with a warn-level logCost Safety
The existing multiplier-based cost cap continues to apply — the resolved model's
multiplierNumericmust not exceed the parent agent's multiplier. This cap was moved outside theif (subagent)block to apply uniformly to all resolution paths.Changes
runSubagentTool.ts(+65 lines)model?: stringtoIRunSubagentToolInputParamsmodelto the tool's input schema ingetToolData()resolveModelHint()helper for dual-lookup model resolutionresolveSubagentModel()with optionalcallTimeModelHintparameterinvoke()no-subagent branch to handle model override from cacheprepareToolInvocation()to passargs.modelthroughrunSubagentTool.test.ts(+273 lines)modelproperty present in tool datacall-time model parametersuite:model:frontmatterTesting
All 24 tests in the
RunSubagentTooltest suite pass, including the 7 new tests.Related