From 7745206ebc7307902bf3ac552ec0d96d7776cd14 Mon Sep 17 00:00:00 2001 From: can1357 Date: Sun, 25 Jan 2026 14:05:45 +0100 Subject: [PATCH] feat(coding-agent/task): added support for comma-separated model lists - Model field now accepts string arrays for fallback model prioritization. - Extracted model list parsing into dedicated parseModelList helper function. - Extended default model alias detection to validate all entries in comma-separated lists. --- packages/coding-agent/CHANGELOG.md | 3 +- .../coding-agent/src/discovery/helpers.ts | 14 +++++- packages/coding-agent/src/task/agents.ts | 2 +- packages/coding-agent/src/task/executor.ts | 50 +++++++++++++------ packages/coding-agent/src/task/index.ts | 10 ++-- packages/coding-agent/src/task/types.ts | 6 +-- 6 files changed, 60 insertions(+), 25 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 4fc2fdf5d..bcaa592cc 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -6,6 +6,7 @@ - Added extension API to set working/loading messages during streaming - Added task worker propagation of context files, skills, and prompt templates - Added subagent option to skip Python preflight checks when Python tooling is unused +- Model field now accepts string arrays for fallback model prioritization ### Changed - Merged patch application warnings into edit tool diagnostics output @@ -15,7 +16,7 @@ ### Fixed - Rewrote empty or corrupted session files to restore valid headers - Improved patch applicator ambiguity errors with match previews and overlap detection - +- Fixed Task tool agent model resolution to honor comma-separated model lists ## [8.3.0] - 2026-01-25 ### Changed diff --git a/packages/coding-agent/src/discovery/helpers.ts b/packages/coding-agent/src/discovery/helpers.ts index fc81680b2..ada59df22 100644 --- a/packages/coding-agent/src/discovery/helpers.ts +++ b/packages/coding-agent/src/discovery/helpers.ts @@ -156,13 +156,23 @@ export function parseArrayOrCSV(value: unknown): string[] | undefined { return undefined; } +/** + * Parse model field into a prioritized list. + */ +export function parseModelList(value: unknown): string[] | undefined { + const parsed = parseArrayOrCSV(value); + if (!parsed) return undefined; + const normalized = parsed.map(entry => entry.trim()).filter(Boolean); + return normalized.length > 0 ? normalized : undefined; +} + /** Parsed agent fields from frontmatter (excludes source/filePath/systemPrompt) */ export interface ParsedAgentFields { name: string; description: string; tools?: string[]; spawns?: string[] | "*"; - model?: string; + model?: string[]; output?: unknown; thinkingLevel?: ThinkingLevel; } @@ -202,7 +212,7 @@ export function parseAgentFields(frontmatter: Record): ParsedAg } const output = frontmatter.output !== undefined ? frontmatter.output : undefined; - const model = typeof frontmatter.model === "string" ? frontmatter.model : undefined; + const model = parseModelList(frontmatter.model); const thinkingLevel = parseThinkingLevel(frontmatter); return { name, description, tools, spawns, model, output, thinkingLevel }; diff --git a/packages/coding-agent/src/task/agents.ts b/packages/coding-agent/src/task/agents.ts index f0e44dd05..d928d79ae 100644 --- a/packages/coding-agent/src/task/agents.ts +++ b/packages/coding-agent/src/task/agents.ts @@ -18,7 +18,7 @@ interface AgentFrontmatter { name: string; description: string; spawns?: string; - model?: string; + model?: string | string[]; thinkingLevel?: string; } diff --git a/packages/coding-agent/src/task/executor.ts b/packages/coding-agent/src/task/executor.ts index f4ec793d6..ad10af781 100644 --- a/packages/coding-agent/src/task/executor.ts +++ b/packages/coding-agent/src/task/executor.ts @@ -40,6 +40,19 @@ import type { SubagentWorkerResponse, } from "./worker-protocol"; +const DEFAULT_MODEL_ALIASES = new Set(["default", "pi/default", "omp/default"]); + +function normalizeModelPatterns(value: string | string[] | undefined): string[] { + if (!value) return []; + if (Array.isArray(value)) { + return value.map(entry => entry.trim()).filter(Boolean); + } + return value + .split(",") + .map(entry => entry.trim()) + .filter(Boolean); +} + /** Options for worker execution */ export interface ExecutorOptions { cwd: string; @@ -50,7 +63,7 @@ export interface ExecutorOptions { index: number; id: string; context?: string; - modelOverride?: string; + modelOverride?: string | string[]; thinkingLevel?: ThinkingLevel; outputSchema?: unknown; enableLsp?: boolean; @@ -283,23 +296,30 @@ export async function runSubprocess(options: ExecutorOptions): Promise or pi/ aliases (e.g., "omp/slow", "pi/fast") - let effectivePattern = modelPattern; - const lower = modelPattern.toLowerCase(); - if (lower.startsWith("omp/") || lower.startsWith("pi/")) { - const role = lower.startsWith("omp/") ? modelPattern.slice(4) : modelPattern.slice(3); - const roles = serializedSettings?.modelRoles as Record | undefined; - const configured = roles?.[role] ?? roles?.[role.toLowerCase()]; - if (configured) { - effectivePattern = configured; + if (modelPatterns.length > 0) { + const roles = serializedSettings?.modelRoles as Record | undefined; + for (const pattern of modelPatterns) { + const normalized = pattern.trim().toLowerCase(); + if (!normalized || DEFAULT_MODEL_ALIASES.has(normalized)) { + continue; + } + let effectivePattern = pattern; + if (normalized.startsWith("omp/") || normalized.startsWith("pi/")) { + const role = normalized.startsWith("omp/") ? pattern.slice(4) : pattern.slice(3); + const configured = roles?.[role] ?? roles?.[role.toLowerCase()]; + if (configured) { + effectivePattern = configured; + } + } + const { model } = parseModelPattern(effectivePattern, availableModels); + if (model) { + resolvedModel = formatModelString(model); + break; } } - const { model } = parseModelPattern(effectivePattern, availableModels); - resolvedModel = model ? formatModelString(model) : undefined; } const sessionFile = subtaskSessionFile ?? null; const spawnsEnv = agent.spawns === undefined ? "" : agent.spawns === "*" ? "*" : agent.spawns.join(","); diff --git a/packages/coding-agent/src/task/index.ts b/packages/coding-agent/src/task/index.ts index 4d8a0f95c..47e33a6d8 100644 --- a/packages/coding-agent/src/task/index.ts +++ b/packages/coding-agent/src/task/index.ts @@ -163,10 +163,14 @@ export class TaskTool implements AgentTool { + const isDefaultModelAlias = (value: string | string[] | undefined): boolean => { if (!value) return true; - const normalized = value.trim().toLowerCase(); - return normalized === "default" || normalized === "pi/default" || normalized === "omp/default"; + const values = Array.isArray(value) ? value : [value]; + if (values.length === 0) return true; + return values.every(entry => { + const normalized = entry.trim().toLowerCase(); + return normalized === "default" || normalized === "pi/default" || normalized === "omp/default"; + }); }; // Validate agent exists diff --git a/packages/coding-agent/src/task/types.ts b/packages/coding-agent/src/task/types.ts index b54de8269..a1cf6a14f 100644 --- a/packages/coding-agent/src/task/types.ts +++ b/packages/coding-agent/src/task/types.ts @@ -97,7 +97,7 @@ export interface AgentDefinition { systemPrompt: string; tools?: string[]; spawns?: string[] | "*"; - model?: string; + model?: string[]; thinkingLevel?: ThinkingLevel; output?: unknown; source: AgentSource; @@ -122,7 +122,7 @@ export interface AgentProgress { toolCount: number; tokens: number; durationMs: number; - modelOverride?: string; + modelOverride?: string | string[]; /** Data extracted by registered subprocess tool handlers (keyed by tool name) */ extractedToolData?: Record; } @@ -142,7 +142,7 @@ export interface SingleResult { truncated: boolean; durationMs: number; tokens: number; - modelOverride?: string; + modelOverride?: string | string[]; error?: string; aborted?: boolean; /** Aggregated usage from the subprocess, accumulated incrementally from message_end events. */