@the-open-engine/zeroshot 6.15.0 → 6.17.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 (69) hide show
  1. package/README.md +10 -7
  2. package/cli/commands/providers.js +1 -0
  3. package/cli/index.js +45 -4
  4. package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -1
  5. package/lib/agent-cli-provider/adapters/codex.js +14 -2
  6. package/lib/agent-cli-provider/adapters/codex.js.map +1 -1
  7. package/lib/agent-cli-provider/adapters/common.d.ts +1 -0
  8. package/lib/agent-cli-provider/adapters/common.d.ts.map +1 -1
  9. package/lib/agent-cli-provider/adapters/common.js +14 -0
  10. package/lib/agent-cli-provider/adapters/common.js.map +1 -1
  11. package/lib/agent-cli-provider/adapters/index.d.ts.map +1 -1
  12. package/lib/agent-cli-provider/adapters/index.js +6 -1
  13. package/lib/agent-cli-provider/adapters/index.js.map +1 -1
  14. package/lib/agent-cli-provider/adapters/omp.d.ts +3 -0
  15. package/lib/agent-cli-provider/adapters/omp.d.ts.map +1 -0
  16. package/lib/agent-cli-provider/adapters/omp.js +356 -0
  17. package/lib/agent-cli-provider/adapters/omp.js.map +1 -0
  18. package/lib/agent-cli-provider/adapters/opencode.d.ts.map +1 -1
  19. package/lib/agent-cli-provider/adapters/opencode.js +52 -3
  20. package/lib/agent-cli-provider/adapters/opencode.js.map +1 -1
  21. package/lib/agent-cli-provider/contract-actions.d.ts.map +1 -1
  22. package/lib/agent-cli-provider/contract-actions.js +19 -8
  23. package/lib/agent-cli-provider/contract-actions.js.map +1 -1
  24. package/lib/agent-cli-provider/contract-fallback.d.ts.map +1 -1
  25. package/lib/agent-cli-provider/contract-fallback.js +9 -0
  26. package/lib/agent-cli-provider/contract-fallback.js.map +1 -1
  27. package/lib/agent-cli-provider/contract-options.d.ts.map +1 -1
  28. package/lib/agent-cli-provider/contract-options.js +7 -0
  29. package/lib/agent-cli-provider/contract-options.js.map +1 -1
  30. package/lib/agent-cli-provider/errors.d.ts +7 -0
  31. package/lib/agent-cli-provider/errors.d.ts.map +1 -1
  32. package/lib/agent-cli-provider/errors.js +14 -0
  33. package/lib/agent-cli-provider/errors.js.map +1 -1
  34. package/lib/agent-cli-provider/index.d.ts +1 -1
  35. package/lib/agent-cli-provider/index.d.ts.map +1 -1
  36. package/lib/agent-cli-provider/index.js.map +1 -1
  37. package/lib/agent-cli-provider/provider-registry.d.ts +66 -3
  38. package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
  39. package/lib/agent-cli-provider/provider-registry.js +54 -2
  40. package/lib/agent-cli-provider/provider-registry.js.map +1 -1
  41. package/lib/agent-cli-provider/single-agent-runtime.d.ts +14 -2
  42. package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -1
  43. package/lib/agent-cli-provider/single-agent-runtime.js +97 -22
  44. package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
  45. package/lib/agent-cli-provider/types.d.ts +30 -2
  46. package/lib/agent-cli-provider/types.d.ts.map +1 -1
  47. package/lib/agent-cli-provider/types.js.map +1 -1
  48. package/package.json +1 -1
  49. package/src/agent/agent-lifecycle.js +52 -1
  50. package/src/agent/agent-task-executor.js +8 -3
  51. package/src/agent-cli-provider/adapters/codex.ts +23 -2
  52. package/src/agent-cli-provider/adapters/common.ts +14 -0
  53. package/src/agent-cli-provider/adapters/index.ts +16 -2
  54. package/src/agent-cli-provider/adapters/omp.ts +448 -0
  55. package/src/agent-cli-provider/adapters/opencode.ts +60 -4
  56. package/src/agent-cli-provider/contract-actions.ts +20 -9
  57. package/src/agent-cli-provider/contract-fallback.ts +9 -0
  58. package/src/agent-cli-provider/contract-options.ts +7 -0
  59. package/src/agent-cli-provider/errors.ts +14 -0
  60. package/src/agent-cli-provider/index.ts +2 -0
  61. package/src/agent-cli-provider/provider-registry.ts +59 -2
  62. package/src/agent-cli-provider/single-agent-runtime.ts +144 -24
  63. package/src/agent-cli-provider/types.ts +32 -1
  64. package/src/claude-task-runner.js +11 -0
  65. package/src/orchestrator.js +8 -3
  66. package/src/providers/index.js +6 -0
  67. package/src/task-run-model-args.js +58 -40
  68. package/src/task-spawn-cleanup-ownership.js +3 -0
  69. package/src/task-startup-error.js +68 -0
@@ -23,6 +23,10 @@ import type { ProcessRunner } from './process-runner';
23
23
 
24
24
  function runBuildCommand(request: RequestData): ContractEnvelope {
25
25
  const { adapter, commandSpec, options } = buildCommandSpec(request);
26
+ const webSearch = {
27
+ requested: options.webSearch === true,
28
+ effective: options.webSearch === true && options.cliFeatures?.supportsWebSearch === true,
29
+ };
26
30
  return successEnvelope({
27
31
  command: request.command ?? 'build-command',
28
32
  adapter,
@@ -31,11 +35,13 @@ function runBuildCommand(request: RequestData): ContractEnvelope {
31
35
  evidence: {
32
36
  outputFormat: options.outputFormat ?? null,
33
37
  schemaMode: schemaMode(options),
38
+ configuration: { webSearch },
34
39
  },
35
40
  result: {
36
41
  commandSpec,
37
42
  outputFormat: options.outputFormat ?? null,
38
43
  schemaMode: schemaMode(options),
44
+ configuration: { webSearch },
39
45
  },
40
46
  });
41
47
  }
@@ -43,11 +49,15 @@ function runBuildCommand(request: RequestData): ContractEnvelope {
43
49
  function runProbe(request: RequestData): ContractEnvelope {
44
50
  const adapter = adapterForProvider(request.provider);
45
51
  const helpText = typeof request.raw.helpText === 'string' ? request.raw.helpText : null;
46
- const runtimeProbe = helpText === null ? probeRuntimeProviderCli(adapter.id) : null;
47
- const capabilities =
48
- runtimeProbe === null
49
- ? adapter.detectCliFeatures(helpText)
50
- : runtimeProbe.capabilities;
52
+ const versionText = typeof request.raw.versionText === 'string' ? request.raw.versionText : '';
53
+ const runtimeProbe =
54
+ helpText === null
55
+ ? probeRuntimeProviderCli(adapter.id)
56
+ : probeRuntimeProviderCli(adapter.id, {
57
+ available: true,
58
+ helpText,
59
+ versionText,
60
+ });
51
61
  return successEnvelope({
52
62
  command: request.command ?? 'probe',
53
63
  adapter,
@@ -60,10 +70,11 @@ function runProbe(request: RequestData): ContractEnvelope {
60
70
  },
61
71
  contractVersion: providerExecutableSchemaVersion,
62
72
  adapterVersion: adapter.adapterVersion,
63
- available: runtimeProbe?.available ?? true,
64
- helpText: runtimeProbe?.helpText ?? helpText,
65
- versionText: runtimeProbe?.versionText ?? null,
66
- capabilities,
73
+ available: runtimeProbe.available,
74
+ helpText: runtimeProbe.helpText,
75
+ versionText: runtimeProbe.versionText,
76
+ capabilities: runtimeProbe.capabilities,
77
+ configuration: runtimeProbe.configuration,
67
78
  credentials: adapter.credentialEnvKeys.map((key) => ({
68
79
  key,
69
80
  present: Boolean(request.env[key] ?? process.env[key]),
@@ -6,6 +6,7 @@ import {
6
6
  type ContractErrorObject,
7
7
  type ContractEnvelope,
8
8
  } from './contract-envelope';
9
+ import { UnsupportedProviderCapabilityError } from './errors';
9
10
  import { getString, isRecord, parseJson, unknownToMessage } from './json';
10
11
  import { mergeRedactions, redactString } from './redaction';
11
12
  import type { ProviderAdapter } from './types';
@@ -77,6 +78,14 @@ function errorObject(requestError: ContractRequestError): ContractErrorObject {
77
78
 
78
79
  export function requestErrorFromUnknown(error: unknown): ContractRequestError {
79
80
  if (error instanceof ContractRequestError) return error;
81
+ if (error instanceof UnsupportedProviderCapabilityError) {
82
+ return contractError({
83
+ code: error.code,
84
+ message: error.message,
85
+ exitCode: 4,
86
+ field: 'options.webSearch',
87
+ });
88
+ }
80
89
  return contractError({
81
90
  code: 'internal-error',
82
91
  message: unknownToMessage(error),
@@ -38,12 +38,18 @@ const CLI_FEATURE_FIELDS = [
38
38
  'supportsNoPromptTemplates',
39
39
  'supportsNoContextFiles',
40
40
  'supportsNoApprove',
41
+ 'supportsModeJson',
42
+ 'supportsPrint',
43
+ 'supportsThinking',
44
+ 'supportsNoRules',
45
+ 'supportsNoTitle',
41
46
  'supportsJsonOutput',
42
47
  'supportsAllowAll',
43
48
  'supportsNoAskUser',
44
49
  'supportsAddDir',
45
50
  'supportsMcpConfig',
46
51
  'supportsResume',
52
+ 'supportsWebSearch',
47
53
  'supportsBundledRunner',
48
54
  'supportsAcpStdio',
49
55
  'supportsPromptImages',
@@ -210,6 +216,7 @@ function normalizeBuildOptions(value: Record<string, unknown>): BuildProviderCom
210
216
  'continueSession',
211
217
  optionalBooleanValue(value.continueSession, 'options.continueSession')
212
218
  );
219
+ addDefined(result, 'webSearch', optionalBooleanValue(value.webSearch, 'options.webSearch'));
213
220
  addDefined(
214
221
  result,
215
222
  'claudeSettingsFile',
@@ -1,6 +1,20 @@
1
1
  import { getNumber, getRecord, getString, isRecord, unknownToMessage } from './json';
2
2
  import type { ErrorClassification } from './types';
3
3
 
4
+ export class UnsupportedProviderCapabilityError extends Error {
5
+ readonly code = 'unsupported-capability';
6
+ readonly permanent = true;
7
+ readonly provider: string;
8
+ readonly capability: string;
9
+
10
+ constructor(provider: string, capability: string, message: string) {
11
+ super(message);
12
+ this.name = 'UnsupportedProviderCapabilityError';
13
+ this.provider = provider;
14
+ this.capability = capability;
15
+ }
16
+ }
17
+
4
18
  const BASE_RETRYABLE_PATTERNS: readonly RegExp[] = [
5
19
  /rate.?limit/i,
6
20
  /\b429\b/i,
@@ -78,6 +78,7 @@ export type {
78
78
  ModelCatalogEntry,
79
79
  ModelLevel,
80
80
  ModelSpec,
81
+ OmpCliFeatures,
81
82
  OpencodeCliFeatures,
82
83
  OutputEvent,
83
84
  OutputFormat,
@@ -102,6 +103,7 @@ export type {
102
103
  ToolCallEvent,
103
104
  ToolResultEvent,
104
105
  WarningMetadata,
106
+ WebSearchAttestation,
105
107
  } from './types';
106
108
 
107
109
  import type { AgentCliProviderHelperMetadata } from './types';
@@ -5,6 +5,7 @@ import { copilotAdapter } from './adapters/copilot';
5
5
  import { gatewayAdapter, gatewaySettingsDefaults, validateGatewaySettings } from './adapters/gateway';
6
6
  import { geminiAdapter } from './adapters/gemini';
7
7
  import { opencodeAdapter } from './adapters/opencode';
8
+ import { ompAdapter } from './adapters/omp';
8
9
  import { piAdapter } from './adapters/pi';
9
10
  import { resolveClaudeCommand } from './claude-command';
10
11
  import type { ModelLevel, ProviderAdapter } from './types';
@@ -20,6 +21,7 @@ export interface ProviderCapabilities {
20
21
  readonly thinkingMode: ProviderCapabilityState;
21
22
  readonly reasoningEffort: ProviderCapabilityState;
22
23
  readonly sessionResume: ProviderCapabilityState;
24
+ readonly webSearch: ProviderCapabilityState;
23
25
  }
24
26
 
25
27
  interface FixedProviderCommandSpec {
@@ -102,6 +104,7 @@ const STANDARD_CAPABILITIES: Readonly<
102
104
  | 'streamJson'
103
105
  | 'thinkingMode'
104
106
  | 'sessionResume'
107
+ | 'webSearch'
105
108
  >
106
109
  > = {
107
110
  dockerIsolation: true,
@@ -110,6 +113,7 @@ const STANDARD_CAPABILITIES: Readonly<
110
113
  streamJson: true,
111
114
  thinkingMode: true,
112
115
  sessionResume: false,
116
+ webSearch: false,
113
117
  };
114
118
 
115
119
  const CLAUDE_DOCKER_ENV_PASSTHROUGH = [
@@ -203,12 +207,15 @@ export const providerRegistry = [
203
207
  authInstructions: 'codex login',
204
208
  credentialPaths: ['~/.config/codex', '~/.codex'],
205
209
  credentialEnvKeys: codexAdapter.credentialEnvKeys,
206
- settingsFields: [],
210
+ settingsFields: ['webSearch'],
211
+ settingsDefaults: { webSearch: false },
212
+ settingsValidator: (settings): string | null => validateWebSearchSettings('codex', settings),
207
213
  capabilities: {
208
214
  ...STANDARD_CAPABILITIES,
209
215
  jsonSchema: true,
210
216
  reasoningEffort: true,
211
217
  sessionResume: true,
218
+ webSearch: true,
212
219
  },
213
220
  docs: {
214
221
  label: 'Codex',
@@ -326,11 +333,15 @@ export const providerRegistry = [
326
333
  authInstructions: 'opencode auth login',
327
334
  credentialPaths: ['~/.local/share/opencode'],
328
335
  credentialEnvKeys: opencodeAdapter.credentialEnvKeys,
329
- settingsFields: [],
336
+ settingsFields: ['webSearch'],
337
+ settingsDefaults: { webSearch: false },
338
+ settingsValidator: (settings): string | null => validateWebSearchSettings('opencode', settings),
330
339
  capabilities: {
331
340
  ...STANDARD_CAPABILITIES,
332
341
  jsonSchema: 'experimental',
333
342
  reasoningEffort: true,
343
+ sessionResume: true,
344
+ webSearch: true,
334
345
  },
335
346
  docs: {
336
347
  label: 'Opencode',
@@ -390,6 +401,44 @@ export const providerRegistry = [
390
401
  },
391
402
  adapter: piAdapter,
392
403
  },
404
+ {
405
+ id: 'omp',
406
+ aliases: [],
407
+ displayName: 'OMP',
408
+ binary: 'omp',
409
+ command: { kind: 'fixed', command: 'omp', args: [] },
410
+ invoke: SPAWN_INVOKE,
411
+ installInstructions: 'npm install -g --ignore-scripts @oh-my-pi/pi-coding-agent',
412
+ authInstructions: 'omp\n/login',
413
+ credentialPaths: ['~/.omp'],
414
+ credentialEnvKeys: ompAdapter.credentialEnvKeys,
415
+ settingsFields: [],
416
+ availabilityProbe: 'help-or-version',
417
+ capabilities: {
418
+ ...STANDARD_CAPABILITIES,
419
+ mcpServers: false,
420
+ jsonSchema: false,
421
+ reasoningEffort: true,
422
+ },
423
+ docs: {
424
+ label: 'OMP',
425
+ setupHeading: 'OMP Setup',
426
+ },
427
+ docker: {
428
+ mount: {
429
+ host: '~/.omp',
430
+ container: '$HOME/.omp',
431
+ readonly: true,
432
+ },
433
+ envPassthrough: [],
434
+ },
435
+ defaultLevels: {
436
+ min: ompAdapter.defaultMinLevel,
437
+ default: ompAdapter.defaultLevel,
438
+ max: ompAdapter.defaultMaxLevel,
439
+ },
440
+ adapter: ompAdapter,
441
+ },
393
442
  {
394
443
  id: 'kiro',
395
444
  aliases: [],
@@ -472,6 +521,14 @@ export const providerRegistry = [
472
521
  },
473
522
  ] as const satisfies readonly ProviderRegistryEntry[];
474
523
 
524
+ function validateWebSearchSettings(
525
+ provider: 'codex' | 'opencode',
526
+ settings: Record<string, unknown>
527
+ ): string | null {
528
+ if (settings.webSearch === undefined || typeof settings.webSearch === 'boolean') return null;
529
+ return `providerSettings.${provider}.webSearch must be a boolean`;
530
+ }
531
+
475
532
  type RegistryProviderId = (typeof providerRegistry)[number]['id'];
476
533
  type RegistryProviderAlias = (typeof providerRegistry)[number]['aliases'][number];
477
534
 
@@ -1,4 +1,5 @@
1
1
  import { getProviderAdapter } from './adapters';
2
+ import { UnsupportedProviderCapabilityError } from './errors';
2
3
  import { normalizeGatewayBuildOptions, resolveGatewayConfiguration } from './gateway-tools';
3
4
  import { isRecord } from './json';
4
5
  import {
@@ -19,6 +20,7 @@ import type {
19
20
  ProviderId,
20
21
  ResolvedGatewayBuildOptions,
21
22
  ReasoningEffort,
23
+ WebSearchAttestation,
22
24
  } from './types';
23
25
 
24
26
  type UnknownFunction = (...args: readonly unknown[]) => unknown;
@@ -32,6 +34,7 @@ interface RuntimeProviderSettings {
32
34
  readonly defaultLevel?: ModelLevel;
33
35
  readonly levelOverrides: LevelOverrides;
34
36
  readonly gateway?: GatewayBuildOptions;
37
+ readonly webSearch?: boolean;
35
38
  }
36
39
 
37
40
  interface RuntimeCommandContext {
@@ -50,6 +53,9 @@ export interface PreparedSingleAgentProviderCommand {
50
53
  readonly commandSpec: CommandSpec;
51
54
  readonly options: BuildProviderCommandOptions;
52
55
  readonly cliFeatures: CliFeatureOverrides;
56
+ readonly configuration: {
57
+ readonly webSearch: WebSearchAttestation;
58
+ };
53
59
  }
54
60
 
55
61
  export interface RuntimeProviderProbe {
@@ -57,6 +63,15 @@ export interface RuntimeProviderProbe {
57
63
  readonly helpText: string;
58
64
  readonly versionText: string;
59
65
  readonly capabilities: ProviderCliFeatures;
66
+ readonly configuration: {
67
+ readonly webSearch: WebSearchAttestation;
68
+ };
69
+ }
70
+
71
+ interface RuntimeProbeEvidence {
72
+ readonly available?: boolean;
73
+ readonly helpText: string;
74
+ readonly versionText: string;
60
75
  }
61
76
 
62
77
  type MutableModelSpec = {
@@ -91,7 +106,13 @@ export function prepareSingleAgentProviderCommand(
91
106
  adapter.id,
92
107
  baseOptions.cwd ?? process.cwd()
93
108
  );
94
- const cliFeatures = resolveRuntimeCliFeatures(adapter.id, baseOptions.cliFeatures);
109
+ const requestedWebSearch = baseOptions.webSearch ?? providerSettings.webSearch;
110
+ assertWebSearchDeclared(adapter.id, requestedWebSearch);
111
+ const cliFeatures = resolveRuntimeCliFeatures(
112
+ adapter.id,
113
+ baseOptions.cliFeatures,
114
+ requestedWebSearch === true
115
+ );
95
116
  const authEnv = baseOptions.authEnv ?? resolveRuntimeAuthEnv(adapter.id, settings);
96
117
  const options = buildRuntimeOptions(baseOptions, adapter, providerSettings, {
97
118
  cliFeatures,
@@ -101,6 +122,9 @@ export function prepareSingleAgentProviderCommand(
101
122
  adapter,
102
123
  options,
103
124
  cliFeatures,
125
+ configuration: {
126
+ webSearch: webSearchAttestation(options),
127
+ },
104
128
  commandSpec: adapter.buildCommand(input.context, options),
105
129
  };
106
130
  }
@@ -111,22 +135,35 @@ export function detectRuntimeProviderCliFeatures(provider: string): ProviderCliF
111
135
 
112
136
  function resolveRuntimeCliFeatures(
113
137
  provider: ProviderId,
114
- overrides: CliFeatureOverrides | undefined
138
+ overrides: CliFeatureOverrides | undefined,
139
+ webSearchRequested: boolean
115
140
  ): CliFeatureOverrides {
116
141
  if (provider === 'gateway') {
142
+ const detected = detectRuntimeProviderCliFeatures(provider);
117
143
  return {
118
- ...detectRuntimeProviderCliFeatures(provider),
144
+ ...detected,
119
145
  ...overrides,
120
146
  supportsBundledRunner: true,
147
+ supportsWebSearch: false,
121
148
  };
122
149
  }
123
- if (getProviderRegistryEntry(provider).invoke.lane !== 'acp-stdio') {
124
- return overrides ?? detectRuntimeProviderCliFeatures(provider);
150
+ if (getProviderRegistryEntry(provider).invoke.lane === 'acp-stdio') {
151
+ const detected = detectRuntimeProviderCliFeatures(provider);
152
+ if (overrides === undefined) return detected;
153
+ return mergeAcpFailClosedCliFeatures(detected, overrides);
125
154
  }
126
-
155
+ if (overrides === undefined) return detectRuntimeProviderCliFeatures(provider);
156
+ if (!webSearchRequested) return overrides;
127
157
  const detected = detectRuntimeProviderCliFeatures(provider);
128
- if (overrides === undefined) return detected;
129
- return mergeAcpFailClosedCliFeatures(detected, overrides);
158
+ return {
159
+ ...overrides,
160
+ supportsResume:
161
+ 'supportsResume' in detected &&
162
+ detected.supportsResume === true &&
163
+ overrides.supportsResume !== false,
164
+ supportsWebSearch:
165
+ detected.supportsWebSearch === true && overrides.supportsWebSearch !== false,
166
+ };
130
167
  }
131
168
 
132
169
  function mergeAcpFailClosedCliFeatures(
@@ -154,33 +191,61 @@ function mergeAcpFailClosedCliFeatures(
154
191
  };
155
192
  }
156
193
 
157
- export function probeRuntimeProviderCli(provider: string): RuntimeProviderProbe {
194
+ export function probeRuntimeProviderCli(
195
+ provider: string,
196
+ evidence?: RuntimeProbeEvidence
197
+ ): RuntimeProviderProbe {
158
198
  const adapter = getProviderAdapter(provider);
159
199
  if (adapter.id === 'gateway') {
160
200
  return probeGatewayProvider(adapter);
161
201
  }
202
+ const requested = getProviderRegistryEntry(adapter.id).settingsFields.includes('webSearch')
203
+ ? runtimeProviderSettings(loadRuntimeSettings(), adapter.id, process.cwd()).webSearch === true
204
+ : false;
162
205
  const helpCommand = runtimeHelpCommand(adapter.id);
163
- const commandAvailable = booleanResult(commandExistsFn(helpCommand.command));
206
+ const commandAvailable =
207
+ evidence === undefined
208
+ ? booleanResult(commandExistsFn(helpCommand.command))
209
+ : evidence.available !== false;
164
210
  if (!commandAvailable) {
211
+ const capabilities = attestedCliFeatures(adapter, '', '');
165
212
  return {
166
213
  available: false,
167
214
  helpText: '',
168
215
  versionText: '',
169
- capabilities: adapter.detectCliFeatures(''),
216
+ capabilities,
217
+ configuration: {
218
+ webSearch: webSearchAttestation({ webSearch: requested, cliFeatures: capabilities }),
219
+ },
170
220
  };
171
221
  }
172
222
 
173
- const helpText = stringResult(getHelpOutputFn(helpCommand.command, helpCommand.args)).trim();
174
- const versionText = stringResult(
175
- getVersionOutputFn(helpCommand.command, helpCommand.args)
176
- ).trim();
223
+ const helpText =
224
+ evidence?.helpText ??
225
+ stringResult(getHelpOutputFn(helpCommand.command, helpCommand.args)).trim();
226
+ const versionText =
227
+ evidence?.versionText ??
228
+ stringResult(
229
+ getVersionOutputFn(
230
+ helpCommand.command,
231
+ getProviderRegistryEntry(adapter.id).capabilities.webSearch === true
232
+ ? []
233
+ : helpCommand.args
234
+ )
235
+ ).trim();
177
236
  const availabilityProbe = getProviderRegistryEntry(adapter.id).availabilityProbe ?? 'command';
237
+ const capabilities = attestedCliFeatures(adapter, helpText, versionText);
178
238
 
179
239
  return {
180
- available: availabilityProbe === 'help-or-version' ? Boolean(helpText || versionText) : true,
240
+ available:
241
+ evidence?.available ??
242
+ (availabilityProbe === 'help-or-version' ? Boolean(helpText || versionText) : true),
181
243
  helpText,
182
244
  versionText,
183
- capabilities: adapter.detectCliFeatures(helpText),
245
+ capabilities,
246
+ configuration: {
247
+ webSearch: webSearchAttestation({ webSearch: requested, cliFeatures: capabilities }),
248
+ },
184
249
  };
185
250
  }
186
251
 
@@ -197,10 +262,13 @@ function buildRuntimeOptions(
197
262
  providerSettings,
198
263
  modelSpec
199
264
  );
265
+ const webSearch = baseOptions.webSearch ?? providerSettings.webSearch;
266
+ assertWebSearchDeclared(adapter.id, webSearch);
200
267
  const resolved = {
201
268
  ...baseOptions,
202
269
  modelSpec,
203
270
  ...(gateway === undefined ? {} : { gateway }),
271
+ ...(webSearch === undefined ? {} : { webSearch }),
204
272
  cliFeatures: runtime.cliFeatures,
205
273
  };
206
274
  if (baseOptions.jsonSchema && !supportsProviderCapability(adapter.id, 'jsonSchema')) {
@@ -320,16 +388,20 @@ function runtimeProviderSettings(
320
388
  providerSettings.levelOverrides,
321
389
  `settings.providerSettings.${provider}.levelOverrides`
322
390
  );
391
+ const webSearch = optionalBoolean(
392
+ providerSettings.webSearch,
393
+ `settings.providerSettings.${provider}.webSearch`
394
+ );
323
395
  const gateway =
324
396
  provider === 'gateway'
325
397
  ? normalizeGatewayBuildOptions(providerSettings, 'settings.providerSettings.gateway', cwd)
326
398
  : undefined;
327
- if (defaultLevel === undefined) {
328
- return gateway === undefined ? { levelOverrides } : { levelOverrides, gateway };
329
- }
330
- return gateway === undefined
331
- ? { defaultLevel, levelOverrides }
332
- : { defaultLevel, levelOverrides, gateway };
399
+ const base = {
400
+ levelOverrides,
401
+ ...(gateway === undefined ? {} : { gateway }),
402
+ ...(webSearch === undefined ? {} : { webSearch }),
403
+ };
404
+ return defaultLevel === undefined ? base : { ...base, defaultLevel };
333
405
  }
334
406
 
335
407
  function runtimeHelpCommand(provider: ProviderId): CommandParts {
@@ -340,7 +412,7 @@ function runtimeHelpCommand(provider: ProviderId): CommandParts {
340
412
  }
341
413
 
342
414
  function probeGatewayProvider(adapter: ProviderAdapter): RuntimeProviderProbe {
343
- const capabilities = adapter.detectCliFeatures('');
415
+ const capabilities = attestedCliFeatures(adapter, '', '');
344
416
  try {
345
417
  const settings = loadRuntimeSettings();
346
418
  const providerSettings = runtimeProviderSettings(settings, 'gateway', process.cwd());
@@ -354,6 +426,9 @@ function probeGatewayProvider(adapter: ProviderAdapter): RuntimeProviderProbe {
354
426
  helpText: 'Bundled gateway runner',
355
427
  versionText: process.version,
356
428
  capabilities,
429
+ configuration: {
430
+ webSearch: { requested: false, effective: false },
431
+ },
357
432
  };
358
433
  } catch {
359
434
  return {
@@ -361,10 +436,49 @@ function probeGatewayProvider(adapter: ProviderAdapter): RuntimeProviderProbe {
361
436
  helpText: 'Bundled gateway runner',
362
437
  versionText: process.version,
363
438
  capabilities,
439
+ configuration: {
440
+ webSearch: { requested: false, effective: false },
441
+ },
364
442
  };
365
443
  }
366
444
  }
367
445
 
446
+ function attestedCliFeatures(
447
+ adapter: ProviderAdapter,
448
+ helpText: string,
449
+ versionText: string
450
+ ): ProviderCliFeatures {
451
+ const detected = adapter.detectCliFeatures(helpText, versionText);
452
+ return {
453
+ ...detected,
454
+ supportsWebSearch:
455
+ getProviderRegistryEntry(adapter.id).capabilities.webSearch === true &&
456
+ detected.supportsWebSearch === true,
457
+ };
458
+ }
459
+
460
+ function webSearchAttestation(options: {
461
+ readonly webSearch?: boolean;
462
+ readonly cliFeatures?: CliFeatureOverrides;
463
+ }): WebSearchAttestation {
464
+ const requested = options.webSearch === true;
465
+ return {
466
+ requested,
467
+ effective: requested && options.cliFeatures?.supportsWebSearch === true,
468
+ };
469
+ }
470
+
471
+ function assertWebSearchDeclared(provider: ProviderId, requested: boolean | undefined): void {
472
+ if (requested === undefined || getProviderRegistryEntry(provider).capabilities.webSearch === true) {
473
+ return;
474
+ }
475
+ throw new UnsupportedProviderCapabilityError(
476
+ provider,
477
+ 'webSearch',
478
+ `Provider ${provider} does not expose provider-controlled native web search; remove options.webSearch.`
479
+ );
480
+ }
481
+
368
482
  function loadRuntimeSettings(): Record<string, unknown> {
369
483
  if (Object.prototype.hasOwnProperty.call(process.env, LEGACY_ISOLATED_PROVIDER_SETTINGS_ENV)) {
370
484
  throw new Error(
@@ -439,6 +553,12 @@ function addReasoningEffort(result: MutableModelSpec, value: unknown, field: str
439
553
  if (effort !== undefined) result.reasoningEffort = effort;
440
554
  }
441
555
 
556
+ function optionalBoolean(value: unknown, field: string): boolean | undefined {
557
+ if (value === undefined) return undefined;
558
+ if (typeof value === 'boolean') return value;
559
+ throw new Error(`${field} must be a boolean.`);
560
+ }
561
+
442
562
  function optionalModelLevel(value: unknown, field: string): ModelLevel | undefined {
443
563
  if (value === undefined) return undefined;
444
564
  if (value === 'level1' || value === 'level2' || value === 'level3') return value;
@@ -89,6 +89,7 @@ export interface ResolvedGatewayBuildOptions {
89
89
  export interface BaseCliFeatures {
90
90
  readonly provider?: ProviderId;
91
91
  readonly unknown?: boolean;
92
+ readonly supportsWebSearch?: boolean;
92
93
  }
93
94
 
94
95
  export interface ClaudeCliFeatures extends BaseCliFeatures {
@@ -116,6 +117,7 @@ export interface CodexCliFeatures extends BaseCliFeatures {
116
117
  readonly supportsModel: boolean;
117
118
  readonly supportsSkipGitRepoCheck: boolean;
118
119
  readonly supportsResume: boolean;
120
+ readonly supportsWebSearch: boolean;
119
121
  }
120
122
 
121
123
  export interface GeminiCliFeatures extends BaseCliFeatures {
@@ -134,6 +136,8 @@ export interface OpencodeCliFeatures extends BaseCliFeatures {
134
136
  readonly supportsDir: boolean;
135
137
  readonly supportsCwd: boolean;
136
138
  readonly supportsAutoApprove: false;
139
+ readonly supportsResume: boolean;
140
+ readonly supportsWebSearch: boolean;
137
141
  }
138
142
 
139
143
  export interface PiCliFeatures extends BaseCliFeatures {
@@ -148,6 +152,20 @@ export interface PiCliFeatures extends BaseCliFeatures {
148
152
  readonly supportsNoApprove: boolean;
149
153
  }
150
154
 
155
+ export interface OmpCliFeatures extends BaseCliFeatures {
156
+ readonly provider: 'omp';
157
+ readonly supportsModeJson: boolean;
158
+ readonly supportsPrint: boolean;
159
+ readonly supportsCwd: boolean;
160
+ readonly supportsAutoApprove: boolean;
161
+ readonly supportsModel: boolean;
162
+ readonly supportsThinking: boolean;
163
+ readonly supportsNoExtensions: boolean;
164
+ readonly supportsNoSkills: boolean;
165
+ readonly supportsNoRules: boolean;
166
+ readonly supportsNoTitle: boolean;
167
+ }
168
+
151
169
  export interface CopilotCliFeatures extends BaseCliFeatures {
152
170
  readonly provider: 'copilot';
153
171
  readonly supportsJsonOutput: boolean;
@@ -184,6 +202,7 @@ export type ProviderCliFeatures =
184
202
  | GeminiCliFeatures
185
203
  | OpencodeCliFeatures
186
204
  | PiCliFeatures
205
+ | OmpCliFeatures
187
206
  | CopilotCliFeatures
188
207
  | GatewayCliFeatures
189
208
  | AcpCliFeatures;
@@ -212,12 +231,18 @@ export interface CliFeatureOverrides {
212
231
  readonly supportsNoPromptTemplates?: boolean;
213
232
  readonly supportsNoContextFiles?: boolean;
214
233
  readonly supportsNoApprove?: boolean;
234
+ readonly supportsModeJson?: boolean;
235
+ readonly supportsPrint?: boolean;
236
+ readonly supportsThinking?: boolean;
237
+ readonly supportsNoRules?: boolean;
238
+ readonly supportsNoTitle?: boolean;
215
239
  readonly supportsJsonOutput?: boolean;
216
240
  readonly supportsAllowAll?: boolean;
217
241
  readonly supportsNoAskUser?: boolean;
218
242
  readonly supportsAddDir?: boolean;
219
243
  readonly supportsMcpConfig?: boolean;
220
244
  readonly supportsResume?: boolean;
245
+ readonly supportsWebSearch?: boolean;
221
246
  readonly supportsBundledRunner?: boolean;
222
247
  readonly supportsAcpStdio?: boolean;
223
248
  readonly supportsPromptImages?: boolean;
@@ -252,6 +277,11 @@ export interface RedactionMetadata {
252
277
  readonly source?: string;
253
278
  }
254
279
 
280
+ export interface WebSearchAttestation {
281
+ readonly requested: boolean;
282
+ readonly effective: boolean;
283
+ }
284
+
255
285
  export interface CommandSpec {
256
286
  readonly binary: string;
257
287
  readonly args: readonly string[];
@@ -272,6 +302,7 @@ export interface BuildProviderCommandOptions {
272
302
  readonly autoApprove?: boolean;
273
303
  readonly resumeSessionId?: string;
274
304
  readonly continueSession?: boolean;
305
+ readonly webSearch?: boolean;
275
306
  readonly claudeSettingsFile?: string;
276
307
  readonly cliFeatures?: CliFeatureOverrides;
277
308
  readonly authEnv?: Readonly<Record<string, string>>;
@@ -373,7 +404,7 @@ export interface ProviderAdapter {
373
404
  readonly defaultLevel: ModelLevel;
374
405
  readonly defaultMaxLevel: ModelLevel;
375
406
  readonly defaultMinLevel: ModelLevel;
376
- detectCliFeatures(helpText?: string | null): ProviderCliFeatures;
407
+ detectCliFeatures(helpText?: string | null, versionText?: string | null): ProviderCliFeatures;
377
408
  buildCommand(context: string, options?: BuildProviderCommandOptions): CommandSpec;
378
409
  extractSessionId?(line: string): string | null;
379
410
  parseEvent(line: string, state: ProviderParserState): ProviderParseResult;