From 3d06f71adeb1c0e78903b69030ebfc7812d11fa3 Mon Sep 17 00:00:00 2001 From: Christopher Date: Sat, 28 Mar 2026 06:12:44 +0000 Subject: [PATCH 1/4] feat(pipeline): support executor subagents for non-CLI targets (#797) Add subagent_mode_allowed field to target definitions so non-CLI providers (openai, claude, copilot, etc.) can be executed via executor subagents in subagent mode. CLI providers always use CLI invocation. Non-CLI providers default to agent mode unless subagent_mode_allowed: false is set in targets.yaml. - Add subagent_mode_allowed to BASE_TARGET_SCHEMA and ResolvedTarget - Extract ResolvedTargetBase interface to reduce duplication in union type - Update pipeline input to write invoke.json kind based on provider + flag - Include subagent_mode_allowed in manifest.json target info Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/cli/src/commands/pipeline/input.ts | 29 +- .../core/src/evaluation/providers/targets.ts | 276 +++++------------- 2 files changed, 89 insertions(+), 216 deletions(-) diff --git a/apps/cli/src/commands/pipeline/input.ts b/apps/cli/src/commands/pipeline/input.ts index 745947e76..832ef182c 100644 --- a/apps/cli/src/commands/pipeline/input.ts +++ b/apps/cli/src/commands/pipeline/input.ts @@ -65,10 +65,13 @@ export const evalInputCommand = command({ process.exit(1); } - // Try to resolve target for CLI invocation info + // Try to resolve target for CLI invocation info. + // Non-CLI providers default to agent mode (executor subagents) unless + // subagent_mode_allowed: false is set in targets.yaml. let targetInfo: { kind: 'cli'; command: string; cwd: string; timeoutMs: number } | null = null; let targetName = 'agent'; let targetKind = 'agent'; + let subagentModeAllowed = true; try { const selection = await selectTarget({ @@ -83,16 +86,21 @@ export const evalInputCommand = command({ }); targetName = selection.targetName; + const resolved = selection.resolvedTarget; + subagentModeAllowed = resolved.subagentModeAllowed !== false; - if (selection.resolvedTarget.kind === 'cli') { + if (resolved.kind === 'cli') { targetKind = 'cli'; - const config = selection.resolvedTarget.config; + subagentModeAllowed = false; + const config = resolved.config; targetInfo = { kind: 'cli', command: config.command, cwd: config.cwd ?? evalDir, timeoutMs: config.timeoutMs ?? 30000, }; + } else { + targetKind = resolved.kind; } } catch { // No targets file found — subagent-as-target mode @@ -122,7 +130,8 @@ export const evalInputCommand = command({ metadata: test.metadata ?? {}, }); - // invoke.json + // invoke.json — CLI targets get command info; non-CLI targets get agent + // mode unless subagent_mode_allowed: false forces CLI-based evaluation. if (targetInfo) { await writeJson(join(testDir, 'invoke.json'), { kind: 'cli', @@ -131,11 +140,20 @@ export const evalInputCommand = command({ timeout_ms: targetInfo.timeoutMs, env: {}, }); - } else { + } else if (subagentModeAllowed) { await writeJson(join(testDir, 'invoke.json'), { kind: 'agent', instructions: 'Execute this task in the current workspace. The agent IS the target.', }); + } else { + // Non-CLI provider with subagent_mode_allowed: false — use agentv eval + // CLI runner instead of executor subagents. + await writeJson(join(testDir, 'invoke.json'), { + kind: targetKind, + subagent_mode_allowed: false, + instructions: + 'This target has subagent_mode_allowed: false. Use `agentv eval` CLI to run this target.', + }); } // criteria.md @@ -165,6 +183,7 @@ export const evalInputCommand = command({ target: { name: targetName, kind: targetKind, + subagent_mode_allowed: subagentModeAllowed, }, test_ids: testIds, }); diff --git a/packages/core/src/evaluation/providers/targets.ts b/packages/core/src/evaluation/providers/targets.ts index 659f3e67f..603236472 100644 --- a/packages/core/src/evaluation/providers/targets.ts +++ b/packages/core/src/evaluation/providers/targets.ts @@ -570,151 +570,45 @@ export type CliHealthcheck = Readonly; // Note: CliResolvedConfig is a type alias derived from CliNormalizedConfig (see above), // which itself is inferred from CliTargetConfigSchema for type safety and single source of truth. +/** Base fields shared by all resolved targets. */ +interface ResolvedTargetBase { + readonly name: string; + readonly graderTarget?: string; + readonly workers?: number; + readonly providerBatching?: boolean; + /** + * Whether this target can be executed via executor subagents in subagent mode. + * Defaults to `true` for all non-CLI providers. Set `false` in targets.yaml + * to force CLI invocation even in subagent mode. + */ + readonly subagentModeAllowed?: boolean; +} + export type ResolvedTarget = - | { - readonly kind: 'openai'; - readonly name: string; - readonly graderTarget?: string; - readonly workers?: number; - readonly providerBatching?: boolean; - readonly config: OpenAIResolvedConfig; - } - | { - readonly kind: 'openrouter'; - readonly name: string; - readonly graderTarget?: string; - readonly workers?: number; - readonly providerBatching?: boolean; - readonly config: OpenRouterResolvedConfig; - } - | { - readonly kind: 'azure'; - readonly name: string; - readonly graderTarget?: string; - readonly workers?: number; - readonly providerBatching?: boolean; - readonly config: AzureResolvedConfig; - } - | { - readonly kind: 'anthropic'; - readonly name: string; - readonly graderTarget?: string; - readonly workers?: number; - readonly providerBatching?: boolean; - readonly config: AnthropicResolvedConfig; - } - | { - readonly kind: 'gemini'; - readonly name: string; - readonly graderTarget?: string; - readonly workers?: number; - readonly providerBatching?: boolean; - readonly config: GeminiResolvedConfig; - } - | { - readonly kind: 'codex'; - readonly name: string; - readonly graderTarget?: string; - readonly workers?: number; - readonly providerBatching?: boolean; - readonly config: CodexResolvedConfig; - } - | { - readonly kind: 'copilot-sdk'; - readonly name: string; - readonly graderTarget?: string; - readonly workers?: number; - readonly providerBatching?: boolean; - readonly config: CopilotSdkResolvedConfig; - } - | { - readonly kind: 'copilot-cli'; - readonly name: string; - readonly graderTarget?: string; - readonly workers?: number; - readonly providerBatching?: boolean; - readonly config: CopilotCliResolvedConfig; - } - | { - readonly kind: 'copilot-log'; - readonly name: string; - readonly graderTarget?: string; - readonly workers?: number; - readonly providerBatching?: boolean; - readonly config: CopilotLogResolvedConfig; - } - | { + | (ResolvedTargetBase & { readonly kind: 'openai'; readonly config: OpenAIResolvedConfig }) + | (ResolvedTargetBase & { readonly kind: 'openrouter'; readonly config: OpenRouterResolvedConfig }) + | (ResolvedTargetBase & { readonly kind: 'azure'; readonly config: AzureResolvedConfig }) + | (ResolvedTargetBase & { readonly kind: 'anthropic'; readonly config: AnthropicResolvedConfig }) + | (ResolvedTargetBase & { readonly kind: 'gemini'; readonly config: GeminiResolvedConfig }) + | (ResolvedTargetBase & { readonly kind: 'codex'; readonly config: CodexResolvedConfig }) + | (ResolvedTargetBase & { readonly kind: 'copilot-sdk'; readonly config: CopilotSdkResolvedConfig }) + | (ResolvedTargetBase & { readonly kind: 'copilot-cli'; readonly config: CopilotCliResolvedConfig }) + | (ResolvedTargetBase & { readonly kind: 'copilot-log'; readonly config: CopilotLogResolvedConfig }) + | (ResolvedTargetBase & { readonly kind: 'pi-coding-agent'; - readonly name: string; - readonly graderTarget?: string; - readonly workers?: number; - readonly providerBatching?: boolean; readonly config: PiCodingAgentResolvedConfig; - } - | { - readonly kind: 'pi-cli'; - readonly name: string; - readonly graderTarget?: string; - readonly workers?: number; - readonly providerBatching?: boolean; - readonly config: PiCliResolvedConfig; - } - | { - readonly kind: 'claude'; - readonly name: string; - readonly graderTarget?: string; - readonly workers?: number; - readonly providerBatching?: boolean; - readonly config: ClaudeResolvedConfig; - } - | { - readonly kind: 'claude-cli'; - readonly name: string; - readonly graderTarget?: string; - readonly workers?: number; - readonly providerBatching?: boolean; - readonly config: ClaudeResolvedConfig; - } - | { - readonly kind: 'claude-sdk'; - readonly name: string; - readonly graderTarget?: string; - readonly workers?: number; - readonly providerBatching?: boolean; - readonly config: ClaudeResolvedConfig; - } - | { - readonly kind: 'mock'; - readonly name: string; - readonly graderTarget?: string; - readonly workers?: number; - readonly providerBatching?: boolean; - readonly config: MockResolvedConfig; - } - | { + }) + | (ResolvedTargetBase & { readonly kind: 'pi-cli'; readonly config: PiCliResolvedConfig }) + | (ResolvedTargetBase & { readonly kind: 'claude'; readonly config: ClaudeResolvedConfig }) + | (ResolvedTargetBase & { readonly kind: 'claude-cli'; readonly config: ClaudeResolvedConfig }) + | (ResolvedTargetBase & { readonly kind: 'claude-sdk'; readonly config: ClaudeResolvedConfig }) + | (ResolvedTargetBase & { readonly kind: 'mock'; readonly config: MockResolvedConfig }) + | (ResolvedTargetBase & { readonly kind: 'vscode' | 'vscode-insiders'; - readonly name: string; - readonly graderTarget?: string; - readonly workers?: number; - readonly providerBatching?: boolean; readonly config: VSCodeResolvedConfig; - } - | { - readonly kind: 'agentv'; - readonly name: string; - readonly graderTarget?: string; - readonly workers?: number; - readonly providerBatching?: boolean; - readonly config: AgentVResolvedConfig; - } - | { - readonly kind: 'cli'; - readonly name: string; - readonly graderTarget?: string; - readonly workers?: number; - readonly providerBatching?: boolean; - readonly config: CliResolvedConfig; - }; + }) + | (ResolvedTargetBase & { readonly kind: 'agentv'; readonly config: AgentVResolvedConfig }) + | (ResolvedTargetBase & { readonly kind: 'cli'; readonly config: CliResolvedConfig }); const BASE_TARGET_SCHEMA = z .object({ @@ -725,6 +619,7 @@ const BASE_TARGET_SCHEMA = z workers: z.number().int().min(1).optional(), workspace_template: z.string().optional(), workspaceTemplate: z.string().optional(), + subagent_mode_allowed: z.boolean().optional(), }) .passthrough(); @@ -807,43 +702,43 @@ export function resolveTargetDefinition( const providerBatching = resolveOptionalBoolean( parsed.provider_batching ?? parsed.providerBatching, ); + const subagentModeAllowed = resolveOptionalBoolean( + parsed.subagent_mode_allowed ?? parsed.subagentModeAllowed, + ); + + // Shared base fields for all resolved targets + const base = { + name: parsed.name, + graderTarget: parsed.grader_target ?? parsed.judge_target, + workers: parsed.workers, + providerBatching, + subagentModeAllowed, + } as const; switch (provider) { case 'openai': return { kind: 'openai', - name: parsed.name, - graderTarget: parsed.grader_target ?? parsed.judge_target, - workers: parsed.workers, - providerBatching, + ...base, config: resolveOpenAIConfig(parsed, env), }; case 'openrouter': return { kind: 'openrouter', - name: parsed.name, - graderTarget: parsed.grader_target ?? parsed.judge_target, - workers: parsed.workers, - providerBatching, + ...base, config: resolveOpenRouterConfig(parsed, env), }; case 'azure': case 'azure-openai': return { kind: 'azure', - name: parsed.name, - graderTarget: parsed.grader_target ?? parsed.judge_target, - workers: parsed.workers, - providerBatching, + ...base, config: resolveAzureConfig(parsed, env), }; case 'anthropic': return { kind: 'anthropic', - name: parsed.name, - graderTarget: parsed.grader_target ?? parsed.judge_target, - workers: parsed.workers, - providerBatching, + ...base, config: resolveAnthropicConfig(parsed, env), }; case 'gemini': @@ -851,68 +746,47 @@ export function resolveTargetDefinition( case 'google-gemini': return { kind: 'gemini', - name: parsed.name, - graderTarget: parsed.grader_target ?? parsed.judge_target, - workers: parsed.workers, - providerBatching, + ...base, config: resolveGeminiConfig(parsed, env), }; case 'codex': case 'codex-cli': return { kind: 'codex', - name: parsed.name, - graderTarget: parsed.grader_target ?? parsed.judge_target, - workers: parsed.workers, - providerBatching, + ...base, config: resolveCodexConfig(parsed, env, evalFilePath), }; case 'copilot-sdk': case 'copilot_sdk': return { kind: 'copilot-sdk', - name: parsed.name, - graderTarget: parsed.grader_target ?? parsed.judge_target, - workers: parsed.workers, - providerBatching, + ...base, config: resolveCopilotSdkConfig(parsed, env, evalFilePath), }; case 'copilot': case 'copilot-cli': return { kind: 'copilot-cli', - name: parsed.name, - graderTarget: parsed.grader_target ?? parsed.judge_target, - workers: parsed.workers, - providerBatching, + ...base, config: resolveCopilotCliConfig(parsed, env, evalFilePath), }; case 'copilot-log': return { kind: 'copilot-log', - name: parsed.name, - graderTarget: parsed.grader_target ?? parsed.judge_target, - workers: parsed.workers, - providerBatching, + ...base, config: resolveCopilotLogConfig(parsed, env), }; case 'pi': case 'pi-coding-agent': return { kind: 'pi-coding-agent', - name: parsed.name, - graderTarget: parsed.grader_target ?? parsed.judge_target, - workers: parsed.workers, - providerBatching, + ...base, config: resolvePiCodingAgentConfig(parsed, env, evalFilePath), }; case 'pi-cli': return { kind: 'pi-cli', - name: parsed.name, - graderTarget: parsed.grader_target ?? parsed.judge_target, - workers: parsed.workers, - providerBatching, + ...base, config: resolvePiCliConfig(parsed, env, evalFilePath), }; case 'claude': @@ -920,38 +794,26 @@ export function resolveTargetDefinition( case 'claude-cli': return { kind: 'claude-cli', - name: parsed.name, - graderTarget: parsed.grader_target ?? parsed.judge_target, - workers: parsed.workers, - providerBatching, + ...base, config: resolveClaudeConfig(parsed, env, evalFilePath), }; case 'claude-sdk': return { kind: 'claude-sdk', - name: parsed.name, - graderTarget: parsed.grader_target ?? parsed.judge_target, - workers: parsed.workers, - providerBatching, + ...base, config: resolveClaudeConfig(parsed, env, evalFilePath), }; case 'mock': return { kind: 'mock', - name: parsed.name, - graderTarget: parsed.grader_target ?? parsed.judge_target, - workers: parsed.workers, - providerBatching, + ...base, config: resolveMockConfig(parsed), }; case 'vscode': case 'vscode-insiders': return { kind: provider as 'vscode' | 'vscode-insiders', - name: parsed.name, - graderTarget: parsed.grader_target ?? parsed.judge_target, - workers: parsed.workers, - providerBatching, + ...base, config: resolveVSCodeConfig(parsed, env, provider === 'vscode-insiders', evalFilePath), }; case 'agentv': { @@ -964,20 +826,15 @@ export function resolveTargetDefinition( const temperature = typeof parsed.temperature === 'number' ? parsed.temperature : 0; return { kind: 'agentv', - name: parsed.name, - graderTarget: parsed.grader_target ?? parsed.judge_target, + ...base, workers: typeof parsed.workers === 'number' ? parsed.workers : undefined, - providerBatching, config: { model, temperature }, }; } case 'cli': return { kind: 'cli', - name: parsed.name, - graderTarget: parsed.grader_target ?? parsed.judge_target, - workers: parsed.workers, - providerBatching, + ...base, config: resolveCliConfig(parsed, env, evalFilePath), }; default: @@ -989,10 +846,7 @@ export function resolveTargetDefinition( // CliProvider with the discovered script path. return { kind: 'cli', - name: parsed.name, - graderTarget: parsed.grader_target ?? parsed.judge_target, - workers: parsed.workers, - providerBatching, + ...base, config: resolveDiscoveredProviderConfig(parsed, provider, env, evalFilePath), }; } From dc1556d6357592d3620486532ef72a5515ac0fc5 Mon Sep 17 00:00:00 2001 From: Christopher Date: Sat, 28 Mar 2026 06:13:51 +0000 Subject: [PATCH 2/4] style: fix Biome formatting for ResolvedTarget union type Co-Authored-By: Claude Opus 4.6 (1M context) --- .../core/src/evaluation/providers/targets.ts | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/core/src/evaluation/providers/targets.ts b/packages/core/src/evaluation/providers/targets.ts index 603236472..0f9a4a679 100644 --- a/packages/core/src/evaluation/providers/targets.ts +++ b/packages/core/src/evaluation/providers/targets.ts @@ -586,14 +586,26 @@ interface ResolvedTargetBase { export type ResolvedTarget = | (ResolvedTargetBase & { readonly kind: 'openai'; readonly config: OpenAIResolvedConfig }) - | (ResolvedTargetBase & { readonly kind: 'openrouter'; readonly config: OpenRouterResolvedConfig }) + | (ResolvedTargetBase & { + readonly kind: 'openrouter'; + readonly config: OpenRouterResolvedConfig; + }) | (ResolvedTargetBase & { readonly kind: 'azure'; readonly config: AzureResolvedConfig }) | (ResolvedTargetBase & { readonly kind: 'anthropic'; readonly config: AnthropicResolvedConfig }) | (ResolvedTargetBase & { readonly kind: 'gemini'; readonly config: GeminiResolvedConfig }) | (ResolvedTargetBase & { readonly kind: 'codex'; readonly config: CodexResolvedConfig }) - | (ResolvedTargetBase & { readonly kind: 'copilot-sdk'; readonly config: CopilotSdkResolvedConfig }) - | (ResolvedTargetBase & { readonly kind: 'copilot-cli'; readonly config: CopilotCliResolvedConfig }) - | (ResolvedTargetBase & { readonly kind: 'copilot-log'; readonly config: CopilotLogResolvedConfig }) + | (ResolvedTargetBase & { + readonly kind: 'copilot-sdk'; + readonly config: CopilotSdkResolvedConfig; + }) + | (ResolvedTargetBase & { + readonly kind: 'copilot-cli'; + readonly config: CopilotCliResolvedConfig; + }) + | (ResolvedTargetBase & { + readonly kind: 'copilot-log'; + readonly config: CopilotLogResolvedConfig; + }) | (ResolvedTargetBase & { readonly kind: 'pi-coding-agent'; readonly config: PiCodingAgentResolvedConfig; From 56cef845d90cff612d346d2ecc8651698438665c Mon Sep 17 00:00:00 2001 From: Christopher Date: Sat, 28 Mar 2026 06:27:02 +0000 Subject: [PATCH 3/4] fix: add subagent_mode_allowed to targets validator COMMON_SETTINGS Without this, the validator warns about unknown property and the field gets ignored during target resolution. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../core/src/evaluation/validation/targets-validator.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/core/src/evaluation/validation/targets-validator.ts b/packages/core/src/evaluation/validation/targets-validator.ts index fd909f9e9..6d21176c5 100644 --- a/packages/core/src/evaluation/validation/targets-validator.ts +++ b/packages/core/src/evaluation/validation/targets-validator.ts @@ -15,7 +15,12 @@ function isObject(value: unknown): value is JsonObject { } // Known settings properties for each provider type -const COMMON_SETTINGS = new Set(['provider_batching', 'providerBatching']); +const COMMON_SETTINGS = new Set([ + 'provider_batching', + 'providerBatching', + 'subagent_mode_allowed', + 'subagentModeAllowed', +]); const RETRY_SETTINGS = new Set([ 'max_retries', From 2cb501a8e410ed0216ec7a2ee47107f9815a88fc Mon Sep 17 00:00:00 2001 From: Christopher Date: Sat, 28 Mar 2026 06:33:39 +0000 Subject: [PATCH 4/4] refactor: derive validator COMMON_SETTINGS from shared source of truth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract COMMON_TARGET_SETTINGS array from targets.ts and import it in targets-validator.ts. Adding a new cross-provider field to the schema now automatically makes it valid in the validator — no separate update. Also add security comment on resolveOptionalString explaining why literal values are rejected by default (prevents plaintext secrets in targets.yaml). Co-Authored-By: Claude Opus 4.6 (1M context) --- .../core/src/evaluation/providers/index.ts | 4 ++-- .../core/src/evaluation/providers/targets.ts | 20 +++++++++++++++++++ .../validation/targets-validator.ts | 12 ++++------- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/packages/core/src/evaluation/providers/index.ts b/packages/core/src/evaluation/providers/index.ts index bf5350705..cd6658396 100644 --- a/packages/core/src/evaluation/providers/index.ts +++ b/packages/core/src/evaluation/providers/index.ts @@ -19,7 +19,7 @@ import { PiCliProvider } from './pi-cli.js'; import { PiCodingAgentProvider } from './pi-coding-agent.js'; import { ProviderRegistry } from './provider-registry.js'; import type { ResolvedTarget } from './targets.js'; -import { resolveTargetDefinition } from './targets.js'; +import { COMMON_TARGET_SETTINGS, resolveTargetDefinition } from './targets.js'; import type { EnvLookup, Provider, TargetDefinition } from './types.js'; import { VSCodeProvider } from './vscode-provider.js'; @@ -56,7 +56,7 @@ export type { VSCodeResolvedConfig, } from './targets.js'; -export { resolveTargetDefinition }; +export { COMMON_TARGET_SETTINGS, resolveTargetDefinition }; export { readTargetDefinitions, listTargetNames } from './targets-file.js'; export { ensureVSCodeSubagents, diff --git a/packages/core/src/evaluation/providers/targets.ts b/packages/core/src/evaluation/providers/targets.ts index 0f9a4a679..57bb41a30 100644 --- a/packages/core/src/evaluation/providers/targets.ts +++ b/packages/core/src/evaluation/providers/targets.ts @@ -622,6 +622,18 @@ export type ResolvedTarget = | (ResolvedTargetBase & { readonly kind: 'agentv'; readonly config: AgentVResolvedConfig }) | (ResolvedTargetBase & { readonly kind: 'cli'; readonly config: CliResolvedConfig }); +/** + * Optional settings accepted on ALL target definitions regardless of provider. + * Exported so the targets validator can reuse the same list — adding a field + * here automatically makes it valid in targets.yaml without a separate update. + */ +export const COMMON_TARGET_SETTINGS = [ + 'provider_batching', + 'providerBatching', + 'subagent_mode_allowed', + 'subagentModeAllowed', +] as const; + const BASE_TARGET_SCHEMA = z .object({ name: z.string().min(1, 'target name is required'), @@ -1890,6 +1902,14 @@ function resolveCopilotLogConfig( }; } +/** + * Resolve a string value from targets.yaml, supporting `${{ VARIABLE }}` env var syntax. + * + * Security: By default (`allowLiteral: false`), values MUST use the `${{ VARIABLE_NAME }}` + * syntax to reference environment variables. Literal strings are rejected to prevent + * secrets (API keys, tokens) from being committed in plaintext to targets.yaml. + * Only non-sensitive fields like `cwd` or `model` use `allowLiteral: true`. + */ function resolveOptionalString( source: unknown, env: EnvLookup, diff --git a/packages/core/src/evaluation/validation/targets-validator.ts b/packages/core/src/evaluation/validation/targets-validator.ts index 6d21176c5..9f9fce9f8 100644 --- a/packages/core/src/evaluation/validation/targets-validator.ts +++ b/packages/core/src/evaluation/validation/targets-validator.ts @@ -2,7 +2,7 @@ import { readFile } from 'node:fs/promises'; import path from 'node:path'; import { parse } from 'yaml'; -import { CLI_PLACEHOLDERS } from '../providers/targets.js'; +import { CLI_PLACEHOLDERS, COMMON_TARGET_SETTINGS } from '../providers/targets.js'; import { KNOWN_PROVIDERS, PROVIDER_ALIASES } from '../providers/types.js'; import type { ValidationError, ValidationResult } from './types.js'; @@ -14,13 +14,9 @@ function isObject(value: unknown): value is JsonObject { return typeof value === 'object' && value !== null && !Array.isArray(value); } -// Known settings properties for each provider type -const COMMON_SETTINGS = new Set([ - 'provider_batching', - 'providerBatching', - 'subagent_mode_allowed', - 'subagentModeAllowed', -]); +// Cross-provider settings derived from the schema source of truth in targets.ts. +// Adding a field to COMMON_TARGET_SETTINGS automatically makes it valid here. +const COMMON_SETTINGS = new Set(COMMON_TARGET_SETTINGS); const RETRY_SETTINGS = new Set([ 'max_retries',