@the-open-engine/zeroshot 6.7.2 → 6.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/lib/agent-cli-provider/adapters/common.js +1 -1
  2. package/lib/agent-cli-provider/adapters/common.js.map +1 -1
  3. package/lib/agent-cli-provider/adapters/opencode-models.d.ts +7 -0
  4. package/lib/agent-cli-provider/adapters/opencode-models.d.ts.map +1 -0
  5. package/lib/agent-cli-provider/adapters/opencode-models.js +87 -0
  6. package/lib/agent-cli-provider/adapters/opencode-models.js.map +1 -0
  7. package/lib/agent-cli-provider/adapters/opencode.d.ts.map +1 -1
  8. package/lib/agent-cli-provider/adapters/opencode.js +6 -58
  9. package/lib/agent-cli-provider/adapters/opencode.js.map +1 -1
  10. package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -1
  11. package/lib/agent-cli-provider/single-agent-runtime.js +27 -13
  12. package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
  13. package/lib/agent-cli-provider/types.d.ts +1 -0
  14. package/lib/agent-cli-provider/types.d.ts.map +1 -1
  15. package/lib/agent-cli-provider/types.js.map +1 -1
  16. package/package.json +1 -1
  17. package/scripts/assert-release-published.js +46 -15
  18. package/src/agent/agent-task-executor.js +18 -9
  19. package/src/agent-cli-provider/adapters/common.ts +1 -1
  20. package/src/agent-cli-provider/adapters/opencode-models.ts +100 -0
  21. package/src/agent-cli-provider/adapters/opencode.ts +8 -65
  22. package/src/agent-cli-provider/single-agent-runtime.ts +51 -33
  23. package/src/agent-cli-provider/types.ts +1 -0
  24. package/src/agent-wrapper.js +14 -2
  25. package/src/claude-task-runner.js +121 -37
  26. package/src/config-validator.js +2 -2
  27. package/src/providers/index.js +5 -0
  28. package/src/task-run-model-args.js +155 -0
  29. package/task-lib/runner.js +66 -26
@@ -23,6 +23,10 @@ const {
23
23
  prepareClaudeConfigDir,
24
24
  resolveRepoMcpConfigPath,
25
25
  } = require('../worktree-claude-config.js');
26
+ const {
27
+ appendTaskRunModelArgs,
28
+ wrapTaskRunWithIsolatedSettings,
29
+ } = require('../task-run-model-args.js');
26
30
  const { buildRawLogOnlyMetadata } = require('./context-replay-policy');
27
31
 
28
32
  function runCommandWithTimeout(command, args, options = {}, callback = null) {
@@ -722,14 +726,10 @@ function resolveOutputFormatConfig(agent) {
722
726
 
723
727
  function buildTaskRunArgs({ agent, providerName, modelSpec, runOutputFormat }) {
724
728
  const args = ['task', 'run', '--output-format', runOutputFormat, '--provider', providerName];
725
-
726
- if (modelSpec?.model) {
727
- args.push('--model', modelSpec.model);
728
- }
729
-
730
- if (modelSpec?.reasoningEffort) {
731
- args.push('--reasoning-effort', modelSpec.reasoningEffort);
732
- }
729
+ const modelSpecSource = agent._resolveModelSpecSource
730
+ ? agent._resolveModelSpecSource()
731
+ : 'direct';
732
+ appendTaskRunModelArgs(args, modelSpec, modelSpecSource);
733
733
 
734
734
  // Add verification mode flag if configured
735
735
  if (agent.config.verificationMode) {
@@ -1509,11 +1509,14 @@ async function spawnClaudeTaskIsolated(agent, context) {
1509
1509
  const { manager, clusterId } = agent.isolation;
1510
1510
  const providerName = agent._resolveProvider ? agent._resolveProvider() : 'claude';
1511
1511
  const modelSpec = resolveAgentModelSpec(agent);
1512
+ const modelSpecSource = agent._resolveModelSpecSource
1513
+ ? agent._resolveModelSpecSource()
1514
+ : 'direct';
1512
1515
 
1513
1516
  agent._log(`📦 Agent ${agent.id}: Running task in isolated container using zeroshot task run...`);
1514
1517
 
1515
1518
  const { desiredOutputFormat, runOutputFormat } = resolveOutputFormatConfig(agent);
1516
- const command = [
1519
+ let command = [
1517
1520
  'zeroshot',
1518
1521
  ...buildTaskRunArgs({
1519
1522
  agent,
@@ -1531,6 +1534,12 @@ async function spawnClaudeTaskIsolated(agent, context) {
1531
1534
  });
1532
1535
 
1533
1536
  command.push(finalContext);
1537
+ command = wrapTaskRunWithIsolatedSettings(command, {
1538
+ providerName,
1539
+ settings: loadSettings(),
1540
+ modelSpecSource,
1541
+ modelSpec,
1542
+ });
1534
1543
 
1535
1544
  // STEP 1: Spawn task and extract task ID (same as non-isolated mode)
1536
1545
  // Timeout for spawn phase - if CLI hangs during init (e.g., opencode 429 bug), kill it
@@ -99,7 +99,7 @@ export function validateModelIdFromCatalog(
99
99
  modelId: string | null | undefined
100
100
  ): string | null | undefined {
101
101
  if (!modelId) return modelId;
102
- if (catalog[modelId] !== undefined) return modelId;
102
+ if (Object.prototype.hasOwnProperty.call(catalog, modelId)) return modelId;
103
103
  const validModels = Object.keys(catalog).join(', ');
104
104
  throw new InvalidProviderModelError(
105
105
  `Invalid model "${modelId}" for provider "${provider}". Valid models: ${validModels}.`
@@ -0,0 +1,100 @@
1
+ import { unknownToMessage } from '../json';
2
+ import {
3
+ InvalidProviderModelError,
4
+ type LevelModelSpec,
5
+ type LevelOverrides,
6
+ type ModelCatalogEntry,
7
+ type ModelLevel,
8
+ type ResolvedModelSpec,
9
+ } from '../types';
10
+ import { validateModelIdFromCatalog } from './common';
11
+
12
+ export const MODEL_CATALOG: Readonly<Record<string, ModelCatalogEntry>> = {
13
+ 'opencode/big-pickle': { rank: 1 },
14
+ 'opencode/glm-4.7-free': { rank: 1 },
15
+ 'opencode/gpt-5-nano': { rank: 1 },
16
+ 'opencode/grok-code': { rank: 1 },
17
+ 'opencode/minimax-m2.1-free': { rank: 1 },
18
+ 'google/gemini-1.5-flash': { rank: 1 },
19
+ 'google/gemini-1.5-flash-8b': { rank: 1 },
20
+ 'google/gemini-1.5-pro': { rank: 1 },
21
+ 'google/gemini-2.0-flash': { rank: 1 },
22
+ 'google/gemini-2.0-flash-lite': { rank: 1 },
23
+ 'google/gemini-2.5-flash': { rank: 1 },
24
+ 'google/gemini-2.5-flash-image': { rank: 1 },
25
+ 'google/gemini-2.5-flash-image-preview': { rank: 1 },
26
+ 'google/gemini-2.5-flash-lite': { rank: 1 },
27
+ 'google/gemini-2.5-flash-lite-preview-06-17': { rank: 1 },
28
+ 'google/gemini-2.5-flash-lite-preview-09-2025': { rank: 1 },
29
+ 'google/gemini-2.5-flash-preview-04-17': { rank: 1 },
30
+ 'google/gemini-2.5-flash-preview-05-20': { rank: 1 },
31
+ 'google/gemini-2.5-flash-preview-09-2025': { rank: 1 },
32
+ 'google/gemini-2.5-flash-preview-tts': { rank: 1 },
33
+ 'google/gemini-2.5-pro': { rank: 1 },
34
+ 'google/gemini-2.5-pro-preview-05-06': { rank: 1 },
35
+ 'google/gemini-2.5-pro-preview-06-05': { rank: 1 },
36
+ 'google/gemini-2.5-pro-preview-tts': { rank: 1 },
37
+ 'google/gemini-3-flash-preview': { rank: 1 },
38
+ 'google/gemini-3-pro-preview': { rank: 1 },
39
+ 'google/gemini-embedding-001': { rank: 1 },
40
+ 'google/gemini-flash-latest': { rank: 1 },
41
+ 'google/gemini-flash-lite-latest': { rank: 1 },
42
+ 'google/gemini-live-2.5-flash': { rank: 1 },
43
+ 'google/gemini-live-2.5-flash-preview-native-audio': { rank: 1 },
44
+ 'openai/gpt-5.1-codex-max': { rank: 1 },
45
+ 'openai/gpt-5.1-codex-mini': { rank: 1 },
46
+ 'openai/gpt-5.2': { rank: 1 },
47
+ 'openai/gpt-5.2-codex': { rank: 1 },
48
+ };
49
+
50
+ export const LEVEL_MAPPING: Readonly<Record<ModelLevel, LevelModelSpec>> = {
51
+ level1: { rank: 1, model: null, reasoningEffort: 'low' },
52
+ level2: { rank: 2, model: null, reasoningEffort: 'medium' },
53
+ level3: { rank: 3, model: null, reasoningEffort: 'high' },
54
+ };
55
+
56
+ function containsControlCharacter(value: string): boolean {
57
+ return Array.from(value).some((character) => {
58
+ const codePoint = character.codePointAt(0);
59
+ return codePoint !== undefined && (codePoint <= 0x1f || codePoint === 0x7f);
60
+ });
61
+ }
62
+
63
+ export function resolveModelSpec(level: ModelLevel, overrides?: LevelOverrides): ResolvedModelSpec {
64
+ const base = LEVEL_MAPPING[level] ?? LEVEL_MAPPING.level2;
65
+ const override = overrides?.[level];
66
+ const selectedModel = override?.model ?? base.model;
67
+ return {
68
+ level,
69
+ model:
70
+ override?.model === undefined || override.model === null
71
+ ? (validateModelId(selectedModel) ?? null)
72
+ : (validateConfiguredModelId(selectedModel) ?? null),
73
+ reasoningEffort: override?.reasoningEffort ?? base.reasoningEffort,
74
+ };
75
+ }
76
+
77
+ export function validateConfiguredModelId(
78
+ modelId: string | null | undefined
79
+ ): string | null | undefined {
80
+ if (modelId && Object.prototype.hasOwnProperty.call(MODEL_CATALOG, modelId)) return modelId;
81
+ if (typeof modelId !== 'string') return validateModelId(modelId);
82
+ const segments = modelId.split('/');
83
+ if (
84
+ segments.length >= 2 &&
85
+ segments.every(Boolean) &&
86
+ !/\s/u.test(modelId) &&
87
+ !containsControlCharacter(modelId)
88
+ ) {
89
+ return modelId;
90
+ }
91
+ throw new InvalidProviderModelError(
92
+ `Invalid configured model "${unknownToMessage(
93
+ modelId
94
+ )}" for provider "opencode". Expected "provider/model" with no whitespace, control characters, or empty path segments.`
95
+ );
96
+ }
97
+
98
+ export function validateModelId(modelId: string | null | undefined): string | null | undefined {
99
+ return validateModelIdFromCatalog('opencode', MODEL_CATALOG, modelId);
100
+ }
@@ -11,14 +11,9 @@ import {
11
11
  type BuildProviderCommandOptions,
12
12
  type CommandSpec,
13
13
  type ErrorClassification,
14
- type LevelModelSpec,
15
- type LevelOverrides,
16
- type ModelCatalogEntry,
17
- type ModelLevel,
18
14
  type OpencodeCliFeatures,
19
15
  type OutputEvent,
20
16
  type ProviderAdapter,
21
- type ResolvedModelSpec,
22
17
  type WarningMetadata,
23
18
  } from '../types';
24
19
  import {
@@ -26,55 +21,16 @@ import {
26
21
  commandSpec,
27
22
  createParserState,
28
23
  optionFeatures,
29
- resolveModelSpecWithConfig,
30
24
  unsupportedSessionControlWarnings,
31
- validateModelIdFromCatalog,
32
25
  warning,
33
26
  } from './common';
34
-
35
- const MODEL_CATALOG: Readonly<Record<string, ModelCatalogEntry>> = {
36
- 'opencode/big-pickle': { rank: 1 },
37
- 'opencode/glm-4.7-free': { rank: 1 },
38
- 'opencode/gpt-5-nano': { rank: 1 },
39
- 'opencode/grok-code': { rank: 1 },
40
- 'opencode/minimax-m2.1-free': { rank: 1 },
41
- 'google/gemini-1.5-flash': { rank: 1 },
42
- 'google/gemini-1.5-flash-8b': { rank: 1 },
43
- 'google/gemini-1.5-pro': { rank: 1 },
44
- 'google/gemini-2.0-flash': { rank: 1 },
45
- 'google/gemini-2.0-flash-lite': { rank: 1 },
46
- 'google/gemini-2.5-flash': { rank: 1 },
47
- 'google/gemini-2.5-flash-image': { rank: 1 },
48
- 'google/gemini-2.5-flash-image-preview': { rank: 1 },
49
- 'google/gemini-2.5-flash-lite': { rank: 1 },
50
- 'google/gemini-2.5-flash-lite-preview-06-17': { rank: 1 },
51
- 'google/gemini-2.5-flash-lite-preview-09-2025': { rank: 1 },
52
- 'google/gemini-2.5-flash-preview-04-17': { rank: 1 },
53
- 'google/gemini-2.5-flash-preview-05-20': { rank: 1 },
54
- 'google/gemini-2.5-flash-preview-09-2025': { rank: 1 },
55
- 'google/gemini-2.5-flash-preview-tts': { rank: 1 },
56
- 'google/gemini-2.5-pro': { rank: 1 },
57
- 'google/gemini-2.5-pro-preview-05-06': { rank: 1 },
58
- 'google/gemini-2.5-pro-preview-06-05': { rank: 1 },
59
- 'google/gemini-2.5-pro-preview-tts': { rank: 1 },
60
- 'google/gemini-3-flash-preview': { rank: 1 },
61
- 'google/gemini-3-pro-preview': { rank: 1 },
62
- 'google/gemini-embedding-001': { rank: 1 },
63
- 'google/gemini-flash-latest': { rank: 1 },
64
- 'google/gemini-flash-lite-latest': { rank: 1 },
65
- 'google/gemini-live-2.5-flash': { rank: 1 },
66
- 'google/gemini-live-2.5-flash-preview-native-audio': { rank: 1 },
67
- 'openai/gpt-5.1-codex-max': { rank: 1 },
68
- 'openai/gpt-5.1-codex-mini': { rank: 1 },
69
- 'openai/gpt-5.2': { rank: 1 },
70
- 'openai/gpt-5.2-codex': { rank: 1 },
71
- };
72
-
73
- const LEVEL_MAPPING: Readonly<Record<ModelLevel, LevelModelSpec>> = {
74
- level1: { rank: 1, model: null, reasoningEffort: 'low' },
75
- level2: { rank: 2, model: null, reasoningEffort: 'medium' },
76
- level3: { rank: 3, model: null, reasoningEffort: 'high' },
77
- };
27
+ import {
28
+ LEVEL_MAPPING,
29
+ MODEL_CATALOG,
30
+ resolveModelSpec,
31
+ validateConfiguredModelId,
32
+ validateModelId,
33
+ } from './opencode-models';
78
34
 
79
35
  function detectCliFeatures(helpText?: string | null): OpencodeCliFeatures {
80
36
  const help = helpText ?? '';
@@ -244,20 +200,6 @@ function parseEvent(line: string): OutputEvent | null {
244
200
  return null;
245
201
  }
246
202
 
247
- function resolveModelSpec(level: ModelLevel, overrides?: LevelOverrides): ResolvedModelSpec {
248
- return resolveModelSpecWithConfig({
249
- mapping: LEVEL_MAPPING,
250
- defaultLevel: 'level2',
251
- level,
252
- overrides,
253
- validateModelId,
254
- });
255
- }
256
-
257
- function validateModelId(modelId: string | null | undefined): string | null | undefined {
258
- return validateModelIdFromCatalog('opencode', MODEL_CATALOG, modelId);
259
- }
260
-
261
203
  function classifyError(error: unknown): ErrorClassification {
262
204
  return classifyBaseProviderError(error, [], []);
263
205
  }
@@ -285,5 +227,6 @@ export const opencodeAdapter: ProviderAdapter = {
285
227
  createParserState: () => createParserState('opencode'),
286
228
  resolveModelSpec,
287
229
  validateModelId,
230
+ validateConfiguredModelId,
288
231
  classifyError,
289
232
  };
@@ -34,6 +34,11 @@ interface RuntimeProviderSettings {
34
34
  readonly gateway?: GatewayBuildOptions;
35
35
  }
36
36
 
37
+ interface RuntimeCommandContext {
38
+ readonly cliFeatures: CliFeatureOverrides;
39
+ readonly authEnv: Readonly<Record<string, string>>;
40
+ }
41
+
37
42
  export interface SingleAgentProviderCommandInput {
38
43
  readonly provider?: string | null;
39
44
  readonly context: string;
@@ -62,6 +67,7 @@ type MutableModelSpec = {
62
67
 
63
68
  const MODEL_LEVELS: readonly ModelLevel[] = ['level1', 'level2', 'level3'];
64
69
  const REASONING_EFFORTS: readonly ReasoningEffort[] = ['low', 'medium', 'high', 'xhigh', 'max'];
70
+ const LEGACY_ISOLATED_PROVIDER_SETTINGS_ENV = 'ZEROSHOT_ISOLATED_PROVIDER_SETTINGS_JSON';
65
71
  const settingsModule: unknown = require('../../lib/settings');
66
72
  const providerDetectionModule: unknown = require('../../lib/provider-detection');
67
73
  const claudeAuthModule: unknown = require('../../lib/settings/claude-auth');
@@ -76,6 +82,7 @@ const resolveClaudeAuthFn = moduleFunction(claudeAuthModule, 'resolveClaudeAuth'
76
82
  export function prepareSingleAgentProviderCommand(
77
83
  input: SingleAgentProviderCommandInput
78
84
  ): PreparedSingleAgentProviderCommand {
85
+ rejectCallerSuppliedModelProvenance(input);
79
86
  const baseOptions = input.options ?? {};
80
87
  const settings = loadRuntimeSettings();
81
88
  const adapter = adapterForRuntimeInput(input.provider, settings);
@@ -86,7 +93,10 @@ export function prepareSingleAgentProviderCommand(
86
93
  );
87
94
  const cliFeatures = resolveRuntimeCliFeatures(adapter.id, baseOptions.cliFeatures);
88
95
  const authEnv = baseOptions.authEnv ?? resolveRuntimeAuthEnv(adapter.id, settings);
89
- const options = buildRuntimeOptions(baseOptions, adapter, providerSettings, cliFeatures, authEnv);
96
+ const options = buildRuntimeOptions(baseOptions, adapter, providerSettings, {
97
+ cliFeatures,
98
+ authEnv,
99
+ });
90
100
  return {
91
101
  adapter,
92
102
  options,
@@ -128,8 +138,7 @@ function mergeAcpFailClosedCliFeatures(
128
138
  ...detected,
129
139
  ...overrides,
130
140
  supportsAcpStdio: detected.supportsAcpStdio && overrides.supportsAcpStdio !== false,
131
- supportsPromptImages:
132
- detected.supportsPromptImages && overrides.supportsPromptImages !== false,
141
+ supportsPromptImages: detected.supportsPromptImages && overrides.supportsPromptImages !== false,
133
142
  supportsLoadSession: detected.supportsLoadSession && overrides.supportsLoadSession !== false,
134
143
  supportsSessionCancel:
135
144
  detected.supportsSessionCancel && overrides.supportsSessionCancel !== false,
@@ -162,7 +171,9 @@ export function probeRuntimeProviderCli(provider: string): RuntimeProviderProbe
162
171
  }
163
172
 
164
173
  const helpText = stringResult(getHelpOutputFn(helpCommand.command, helpCommand.args)).trim();
165
- const versionText = stringResult(getVersionOutputFn(helpCommand.command, helpCommand.args)).trim();
174
+ const versionText = stringResult(
175
+ getVersionOutputFn(helpCommand.command, helpCommand.args)
176
+ ).trim();
166
177
  const availabilityProbe = getProviderRegistryEntry(adapter.id).availabilityProbe ?? 'command';
167
178
 
168
179
  return {
@@ -177,8 +188,7 @@ function buildRuntimeOptions(
177
188
  baseOptions: BuildProviderCommandOptions,
178
189
  adapter: ProviderAdapter,
179
190
  providerSettings: RuntimeProviderSettings,
180
- cliFeatures: CliFeatureOverrides,
181
- authEnv: Readonly<Record<string, string>>
191
+ runtime: RuntimeCommandContext
182
192
  ): BuildProviderCommandOptions {
183
193
  const modelSpec = resolveRuntimeModelSpec(adapter, baseOptions.modelSpec, providerSettings);
184
194
  const gateway = resolveRuntimeGatewayOptions(
@@ -191,16 +201,16 @@ function buildRuntimeOptions(
191
201
  ...baseOptions,
192
202
  modelSpec,
193
203
  ...(gateway === undefined ? {} : { gateway }),
194
- cliFeatures,
204
+ cliFeatures: runtime.cliFeatures,
195
205
  };
196
206
  if (baseOptions.jsonSchema && !supportsProviderCapability(adapter.id, 'jsonSchema')) {
197
- if (!shouldIncludeAuthEnv(baseOptions, authEnv)) {
207
+ if (!shouldIncludeAuthEnv(baseOptions, runtime.authEnv)) {
198
208
  return { ...resolved, strictSchema: false };
199
209
  }
200
- return { ...resolved, authEnv, strictSchema: false };
210
+ return { ...resolved, authEnv: runtime.authEnv, strictSchema: false };
201
211
  }
202
- if (!shouldIncludeAuthEnv(baseOptions, authEnv)) return resolved;
203
- return { ...resolved, authEnv };
212
+ if (!shouldIncludeAuthEnv(baseOptions, runtime.authEnv)) return resolved;
213
+ return { ...resolved, authEnv: runtime.authEnv };
204
214
  }
205
215
 
206
216
  function resolveRuntimeGatewayOptions(
@@ -218,23 +228,19 @@ function resolveRuntimeGatewayOptions(
218
228
  ? settingsGateway.headers
219
229
  : { ...(settingsGateway.headers ?? {}), ...requestGateway.headers };
220
230
  const mergedGateway: GatewayBuildOptions = {
221
- ...(requestGateway.baseUrl ?? settingsGateway.baseUrl
231
+ ...((requestGateway.baseUrl ?? settingsGateway.baseUrl)
222
232
  ? { baseUrl: requestGateway.baseUrl ?? settingsGateway.baseUrl }
223
233
  : {}),
224
- ...(requestGateway.apiKey ?? settingsGateway.apiKey
234
+ ...((requestGateway.apiKey ?? settingsGateway.apiKey)
225
235
  ? { apiKey: requestGateway.apiKey ?? settingsGateway.apiKey }
226
236
  : {}),
227
237
  ...(mergedHeaders === undefined ? {} : { headers: mergedHeaders }),
228
238
  model: requestGateway.model ?? modelSpec.model ?? settingsGateway.model ?? null,
229
- ...(requestGateway.toolPolicy ?? settingsGateway.toolPolicy
239
+ ...((requestGateway.toolPolicy ?? settingsGateway.toolPolicy)
230
240
  ? { toolPolicy: requestGateway.toolPolicy ?? settingsGateway.toolPolicy }
231
241
  : {}),
232
242
  };
233
- return resolveGatewayConfiguration(
234
- mergedGateway,
235
- 'options.gateway',
236
- cwd
237
- );
243
+ return resolveGatewayConfiguration(mergedGateway, 'options.gateway', cwd);
238
244
  }
239
245
 
240
246
  function shouldIncludeAuthEnv(
@@ -286,7 +292,8 @@ function adapterForRuntimeInput(
286
292
  provider: string | null | undefined,
287
293
  settings: Record<string, unknown>
288
294
  ): ProviderAdapter {
289
- const configured = provider ?? optionalString(settings.defaultProvider, 'settings.defaultProvider');
295
+ const configured =
296
+ provider ?? optionalString(settings.defaultProvider, 'settings.defaultProvider');
290
297
  return getProviderAdapter(configured ?? 'claude');
291
298
  }
292
299
 
@@ -309,16 +316,14 @@ function runtimeProviderSettings(
309
316
  );
310
317
  const gateway =
311
318
  provider === 'gateway'
312
- ? normalizeGatewayBuildOptions(
313
- providerSettings,
314
- 'settings.providerSettings.gateway',
315
- cwd
316
- )
319
+ ? normalizeGatewayBuildOptions(providerSettings, 'settings.providerSettings.gateway', cwd)
317
320
  : undefined;
318
321
  if (defaultLevel === undefined) {
319
322
  return gateway === undefined ? { levelOverrides } : { levelOverrides, gateway };
320
323
  }
321
- return gateway === undefined ? { defaultLevel, levelOverrides } : { defaultLevel, levelOverrides, gateway };
324
+ return gateway === undefined
325
+ ? { defaultLevel, levelOverrides }
326
+ : { defaultLevel, levelOverrides, gateway };
322
327
  }
323
328
 
324
329
  function runtimeHelpCommand(provider: ProviderId): CommandParts {
@@ -333,7 +338,11 @@ function probeGatewayProvider(adapter: ProviderAdapter): RuntimeProviderProbe {
333
338
  try {
334
339
  const settings = loadRuntimeSettings();
335
340
  const providerSettings = runtimeProviderSettings(settings, 'gateway', process.cwd());
336
- resolveGatewayConfiguration(providerSettings.gateway, 'settings.providerSettings.gateway', process.cwd());
341
+ resolveGatewayConfiguration(
342
+ providerSettings.gateway,
343
+ 'settings.providerSettings.gateway',
344
+ process.cwd()
345
+ );
337
346
  return {
338
347
  available: true,
339
348
  helpText: 'Bundled gateway runner',
@@ -351,8 +360,20 @@ function probeGatewayProvider(adapter: ProviderAdapter): RuntimeProviderProbe {
351
360
  }
352
361
 
353
362
  function loadRuntimeSettings(): Record<string, unknown> {
354
- const settings = loadSettingsFn();
355
- return requiredRecord(settings, 'loadSettings');
363
+ if (Object.prototype.hasOwnProperty.call(process.env, LEGACY_ISOLATED_PROVIDER_SETTINGS_ENV)) {
364
+ throw new Error(
365
+ `${LEGACY_ISOLATED_PROVIDER_SETTINGS_ENV} is not a trusted settings channel; use the settings file.`
366
+ );
367
+ }
368
+ return requiredRecord(loadSettingsFn(), 'loadSettings');
369
+ }
370
+
371
+ function rejectCallerSuppliedModelProvenance(input: SingleAgentProviderCommandInput): void {
372
+ if (Object.prototype.hasOwnProperty.call(input, 'modelSpecSource')) {
373
+ throw new Error(
374
+ 'modelSpecSource is not accepted at the child provider boundary; use modelLevel and effective provider settings.'
375
+ );
376
+ }
356
377
  }
357
378
 
358
379
  function moduleFunction(moduleValue: unknown, field: string): UnknownFunction {
@@ -456,10 +477,7 @@ function requiredRecord(value: unknown, field: string): Record<string, unknown>
456
477
  throw new Error(`${field} must be an object.`);
457
478
  }
458
479
 
459
- function stringRecordFromUnknown(
460
- value: unknown,
461
- field: string
462
- ): Readonly<Record<string, string>> {
480
+ function stringRecordFromUnknown(value: unknown, field: string): Readonly<Record<string, string>> {
463
481
  if (value === undefined || value === null) return {};
464
482
  const record = requiredRecord(value, field);
465
483
  const result: Record<string, string> = {};
@@ -367,6 +367,7 @@ export interface ProviderAdapter {
367
367
  createParserState(): ProviderParserState;
368
368
  resolveModelSpec(level: ModelLevel, overrides?: LevelOverrides): ResolvedModelSpec;
369
369
  validateModelId(modelId: string | null | undefined): string | null | undefined;
370
+ validateConfiguredModelId?(modelId: string | null | undefined): string | null | undefined;
370
371
  classifyError(error: unknown): ErrorClassification;
371
372
  }
372
373
 
@@ -103,10 +103,11 @@ class AgentWrapper {
103
103
  const taskRunner = options.taskRunner;
104
104
  this.mockSpawnFn = (args, { context }) => {
105
105
  const spec = this._resolveModelSpec();
106
+ const source = this._resolveModelSpecSource();
106
107
  return taskRunner.run(context, {
107
108
  agentId: this.id,
108
- model: this._selectModel(),
109
- modelSpec: spec,
109
+ ...(source === 'direct' ? { model: spec.model } : { modelLevel: spec.level }),
110
+ ...(spec.reasoningEffort ? { reasoningEffort: spec.reasoningEffort } : {}),
110
111
  provider: this._resolveProvider(),
111
112
  cwd: this.config.cwd || process.cwd(),
112
113
  worktreePath: this.worktree?.path || null,
@@ -222,6 +223,17 @@ class AgentWrapper {
222
223
  return applyReasoningOverride({ ...spec, level }, this.config.reasoningEffort);
223
224
  }
224
225
 
226
+ _resolveModelSpecSource() {
227
+ if (this.modelConfig.type === 'rules') {
228
+ for (const rule of this.modelConfig.rules) {
229
+ if (this._matchesIterationRange(rule.iterations)) {
230
+ return rule.model ? 'direct' : 'provider-level';
231
+ }
232
+ }
233
+ }
234
+ return this.modelConfig.model ? 'direct' : 'provider-level';
235
+ }
236
+
225
237
  /**
226
238
  * Publish a message to the message bus, always including sender_model
227
239
  * @private