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.
This commit is contained in:
can1357
2026-01-25 14:05:45 +01:00
parent b9dfcb2890
commit 7745206ebc
6 changed files with 60 additions and 25 deletions
+2 -1
View File
@@ -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
+12 -2
View File
@@ -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<string, unknown>): 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 };
+1 -1
View File
@@ -18,7 +18,7 @@ interface AgentFrontmatter {
name: string;
description: string;
spawns?: string;
model?: string;
model?: string | string[];
thinkingLevel?: string;
}
+35 -15
View File
@@ -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<SingleRes
const serializedSettings = options.settingsManager?.serialize();
const availableModels = options.modelRegistry?.getAvailable() ?? [];
// Resolve model pattern to provider/modelId string
const modelPattern = modelOverride ?? agent.model;
// Resolve model pattern list to provider/modelId string
const modelPatterns = normalizeModelPatterns(modelOverride ?? agent.model);
let resolvedModel: string | undefined;
if (modelPattern) {
// Handle omp/<role> or pi/<role> 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<string, string> | undefined;
const configured = roles?.[role] ?? roles?.[role.toLowerCase()];
if (configured) {
effectivePattern = configured;
if (modelPatterns.length > 0) {
const roles = serializedSettings?.modelRoles as Record<string, string> | 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(",");
+7 -3
View File
@@ -163,10 +163,14 @@ export class TaskTool implements AgentTool<typeof taskSchema, TaskToolDetails, T
const { agent: agentName, context, output: outputSchema, isolated } = params;
const isIsolated = isolated === true;
const isDefaultModelAlias = (value: string | undefined): boolean => {
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
+3 -3
View File
@@ -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<string, unknown[]>;
}
@@ -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. */