diff --git a/src/shared/agent-session-option-agent-args.ts b/src/shared/agent-session-option-agent-args.ts index d357ea57b73..eed4f1ec3ed 100644 --- a/src/shared/agent-session-option-agent-args.ts +++ b/src/shared/agent-session-option-agent-args.ts @@ -1,35 +1,24 @@ +import { findOptionOccurrence } from './command-option-occurrence' + export function agentArgOptionTokens(tokens: readonly string[]): readonly string[] { const terminator = tokens.indexOf('--') return terminator === -1 ? tokens : tokens.slice(0, terminator) } +/** Removes each occurrence before `--`, or only those whose value `matchesValue` accepts. */ export function removeAgentArgOption( tokens: readonly string[], - aliases: readonly string[] + aliases: readonly string[], + matchesValue: (value: string | undefined) => boolean = () => true ): string[] { - const result: string[] = [] - for (let index = 0; index < tokens.length; index += 1) { - const token = tokens[index] - if (token === '--') { - result.push(...tokens.slice(index)) - break - } - const exact = aliases.includes(token) - const matched = aliases.some( - (alias) => - token.startsWith(`${alias}=`) || - (alias.startsWith('-') && - !alias.startsWith('--') && - token.startsWith(alias) && - token.length > alias.length) - ) - if (!exact && !matched) { - result.push(token) - continue - } - if (exact && tokens[index + 1] && !tokens[index + 1].startsWith('-')) { - index += 1 - } + const kept: string[] = [] + let rest = tokens + let found = findOptionOccurrence(rest, aliases, true) + while (found) { + const end = found.index + found.consumed + kept.push(...rest.slice(0, matchesValue(found.value) ? found.index : end)) + rest = rest.slice(end) + found = findOptionOccurrence(rest, aliases, true) } - return result + return [...kept, ...rest] } diff --git a/src/shared/agent-session-option-catalog-antigravity.ts b/src/shared/agent-session-option-catalog-antigravity.ts index e6427a1070b..a2078c963d8 100644 --- a/src/shared/agent-session-option-catalog-antigravity.ts +++ b/src/shared/agent-session-option-catalog-antigravity.ts @@ -1,4 +1,3 @@ -import { hasFlag } from './agent-cli-flag-detection' import { removeAgentArgOption } from './agent-session-option-agent-args' import type { AgentSessionOptionCatalog, CatalogOption } from './agent-session-option-catalog-types' @@ -17,7 +16,6 @@ const ANTIGRAVITY_EFFORT: CatalogOption = { }, apply: { launchArgs: (value) => ['--effort', String(value)], - agentArgsOverride: (tokens) => hasFlag(tokens, ['--effort']), removeAgentArgs: (tokens) => removeAgentArgOption(tokens, ['--effort']), midSession: { kind: 'command', build: (value) => `/effort ${String(value)}` } } @@ -29,7 +27,6 @@ export const ANTIGRAVITY_SESSION_OPTION_CATALOG: AgentSessionOptionCatalog = { models: [], modelApply: { launchArgs: (value) => ['--model', String(value)], - agentArgsOverride: (tokens) => hasFlag(tokens, ['--model']), removeAgentArgs: (tokens) => removeAgentArgOption(tokens, ['--model']), midSession: { kind: 'agent-picker', command: '/model' } }, diff --git a/src/shared/agent-session-option-catalog-claude-codex.ts b/src/shared/agent-session-option-catalog-claude-codex.ts index b7e9d0e6bed..9fb27d3a97f 100644 --- a/src/shared/agent-session-option-catalog-claude-codex.ts +++ b/src/shared/agent-session-option-catalog-claude-codex.ts @@ -3,55 +3,24 @@ import type { CatalogModel, CatalogOption } from './agent-session-option-catalog-types' -import { agentArgOptionTokens, removeAgentArgOption } from './agent-session-option-agent-args' +import { removeAgentArgOption } from './agent-session-option-agent-args' import { CLAUDE_MODEL_LIST_ARGS, CLAUDE_MODEL_LIST_STDIN, parseClaudeModelList } from './claude-model-list-probe' -import { hasFlag } from './agent-cli-flag-detection' -function hasCodexEffortOverride(tokens: readonly string[]): boolean { - if (hasFlag(tokens, ['--reasoning-effort'])) { - return true - } - const optionTokens = agentArgOptionTokens(tokens) - return optionTokens.some((token, index) => { - const previous = optionTokens[index - 1] - return ( - (token.startsWith('model_reasoning_effort=') && - (previous === '-c' || previous === '--config')) || - token.startsWith('-cmodel_reasoning_effort=') || - token.startsWith('-c=model_reasoning_effort=') || - token.startsWith('--config=model_reasoning_effort=') - ) - }) -} - -function removeCodexEffortOverride(tokens: readonly string[]): string[] { - const withoutFlag = removeAgentArgOption(tokens, ['--reasoning-effort']) - const result: string[] = [] - for (let index = 0; index < withoutFlag.length; index += 1) { - const token = withoutFlag[index] - if (token === '--') { - result.push(...withoutFlag.slice(index)) - break - } - const next = withoutFlag[index + 1] - if ((token === '-c' || token === '--config') && next?.startsWith('model_reasoning_effort=')) { - index += 1 - continue - } - if ( - token.startsWith('-cmodel_reasoning_effort=') || - token.startsWith('-c=model_reasoning_effort=') || - token.startsWith('--config=model_reasoning_effort=') - ) { - continue - } - result.push(token) - } - return result +// Why: Codex also reads any setting from a `-c key=value` config override. +function removeCodexAgentArgs( + tokens: readonly string[], + flags: readonly string[], + configKey: string +): string[] { + return removeAgentArgOption( + removeAgentArgOption(tokens, flags), + ['-c', '--config'], + (value) => value?.startsWith(`${configKey}=`) === true + ) } const STANDARD_EFFORT_CHOICES = [ @@ -84,7 +53,6 @@ function claudeEffortWithChoices(choices: typeof EXTENDED_EFFORT_CHOICES): Catal }, apply: { launchArgs: (value) => ['--effort', String(value)], - agentArgsOverride: (tokens) => hasFlag(tokens, ['--effort']), removeAgentArgs: (tokens) => removeAgentArgOption(tokens, ['--effort']), midSession: { kind: 'command', build: (value) => `/effort ${String(value)}` } } @@ -162,7 +130,6 @@ export const CLAUDE_SESSION_OPTION_CATALOG: AgentSessionOptionCatalog = { ], modelApply: { launchArgs: (value) => ['--model', String(value)], - agentArgsOverride: (tokens) => hasFlag(tokens, ['--model']), removeAgentArgs: (tokens) => removeAgentArgOption(tokens, ['--model']), midSession: { kind: 'command', @@ -204,8 +171,8 @@ function codexEffort(ceiling: 'xhigh' | 'max' | 'ultra'): CatalogOption { }, apply: { launchArgs: (value) => ['-c', `model_reasoning_effort=${String(value)}`], - agentArgsOverride: hasCodexEffortOverride, - removeAgentArgs: removeCodexEffortOverride, + removeAgentArgs: (tokens) => + removeCodexAgentArgs(tokens, ['--reasoning-effort'], 'model_reasoning_effort'), midSession: { kind: 'agent-picker', command: '/model', delivery: 'type' } } } @@ -228,8 +195,7 @@ export const CODEX_SESSION_OPTION_CATALOG: AgentSessionOptionCatalog = { ], modelApply: { launchArgs: (value) => ['-m', String(value)], - agentArgsOverride: (tokens) => hasFlag(tokens, ['-m', '--model']), - removeAgentArgs: (tokens) => removeAgentArgOption(tokens, ['-m', '--model']), + removeAgentArgs: (tokens) => removeCodexAgentArgs(tokens, ['-m', '--model'], 'model'), // Codex classifies multi-character writes as pasted prose; type the bare // command and let its own picker apply the account-supported model. midSession: { kind: 'agent-picker', command: '/model', delivery: 'type' } diff --git a/src/shared/agent-session-option-catalog-codebuddy.ts b/src/shared/agent-session-option-catalog-codebuddy.ts index d0a8afa04c5..9c149919472 100644 --- a/src/shared/agent-session-option-catalog-codebuddy.ts +++ b/src/shared/agent-session-option-catalog-codebuddy.ts @@ -1,4 +1,3 @@ -import { hasFlag } from './agent-cli-flag-detection' import { removeAgentArgOption } from './agent-session-option-agent-args' import type { AgentSessionOptionCatalog, CatalogOption } from './agent-session-option-catalog-types' @@ -20,7 +19,6 @@ const CODEBUDDY_EFFORT: CatalogOption = { }, apply: { launchArgs: (value) => ['--effort', String(value)], - agentArgsOverride: (tokens) => hasFlag(tokens, ['--effort']), removeAgentArgs: (tokens) => removeAgentArgOption(tokens, ['--effort']) } } @@ -37,7 +35,6 @@ export const CODEBUDDY_SESSION_OPTION_CATALOG: AgentSessionOptionCatalog = { ].map((model) => ({ ...model, options: [CODEBUDDY_EFFORT] })), modelApply: { launchArgs: (value) => ['--model', String(value)], - agentArgsOverride: (tokens) => hasFlag(tokens, ['--model']), removeAgentArgs: (tokens) => removeAgentArgOption(tokens, ['--model']) }, unknownModelOptions: [CODEBUDDY_EFFORT] diff --git a/src/shared/agent-session-option-catalog-gemini-cursor.ts b/src/shared/agent-session-option-catalog-gemini-cursor.ts index d6dfe08627e..510394d2bd5 100644 --- a/src/shared/agent-session-option-catalog-gemini-cursor.ts +++ b/src/shared/agent-session-option-catalog-gemini-cursor.ts @@ -1,4 +1,3 @@ -import { hasFlag } from './agent-cli-flag-detection' import type { AgentSessionOptionCatalog, CatalogModel, @@ -6,7 +5,8 @@ import type { } from './agent-session-option-catalog-types' import { removeAgentArgOption } from './agent-session-option-agent-args' -const hasModelFlag = (tokens: readonly string[]): boolean => hasFlag(tokens, ['-m', '--model']) +const removeModelFlag = (tokens: readonly string[]): string[] => + removeAgentArgOption(tokens, ['-m', '--model']) export const GEMINI_SESSION_OPTION_CATALOG: AgentSessionOptionCatalog = { models: [ @@ -17,7 +17,7 @@ export const GEMINI_SESSION_OPTION_CATALOG: AgentSessionOptionCatalog = { ], modelApply: { launchArgs: (value) => ['-m', String(value)], - agentArgsOverride: hasModelFlag, + removeAgentArgs: removeModelFlag, midSession: { kind: 'agent-picker', command: '/model' } } } @@ -86,8 +86,7 @@ export const CURSOR_SESSION_OPTION_CATALOG: AgentSessionOptionCatalog = { ], modelApply: { launchArgs: (value) => ['--model', String(value)], - agentArgsOverride: hasModelFlag, - removeAgentArgs: (tokens) => removeAgentArgOption(tokens, ['-m', '--model']), + removeAgentArgs: removeModelFlag, midSession: { kind: 'command', build: (value) => `/model ${String(value)}` } }, composeModelValue: (modelId, values) => { diff --git a/src/shared/agent-session-option-catalog-grok.test.ts b/src/shared/agent-session-option-catalog-grok.test.ts index 0295cb46ad7..7a54621c7c6 100644 --- a/src/shared/agent-session-option-catalog-grok.test.ts +++ b/src/shared/agent-session-option-catalog-grok.test.ts @@ -179,32 +179,37 @@ describe('grok launch args', () => { }) }) -describe('grok agentArgsOverride', () => { - const modelOverride = GROK_SESSION_OPTION_CATALOG.modelApply.agentArgsOverride! - const effortOverride = grokEffortOption().apply.agentArgsOverride! +describe('grok agent-arg overrides', () => { + const removeModel = GROK_SESSION_OPTION_CATALOG.modelApply.removeAgentArgs! + const removeEffort = grokEffortOption().apply.removeAgentArgs! - it('detects a user-supplied model flag in every spelling', () => { + it('strips a user-supplied model flag in every spelling', () => { for (const tokens of [ ['-m', 'grok-build'], ['-mgrok-build'], ['--model', 'grok-build'], ['--model=grok-build'] ]) { - expect(modelOverride(tokens)).toBe(true) + expect(removeModel(tokens)).toEqual([]) } }) it('does not fire on a different flag or a positional that contains -m', () => { - expect(modelOverride(['--model-context', '8000'])).toBe(false) - expect(modelOverride(['summarize-my-diff'])).toBe(false) - expect(modelOverride(['--reasoning-effort', 'low'])).toBe(false) - expect(modelOverride([])).toBe(false) + for (const tokens of [ + ['--model-context', '8000'], + ['summarize-my-diff'], + ['--reasoning-effort', 'low'], + [] + ]) { + expect(removeModel(tokens)).toEqual(tokens) + } }) - it('detects both effort spellings', () => { - expect(effortOverride(['--effort', 'low'])).toBe(true) - expect(effortOverride(['--reasoning-effort=low'])).toBe(true) - expect(effortOverride(['--effortless'])).toBe(false) + it('strips both effort spellings', () => { + expect(removeEffort(['--effort', 'low', '--reasoning-effort=high', '--keep'])).toEqual([ + '--keep' + ]) + expect(removeEffort(['--effortless'])).toEqual(['--effortless']) }) it('drops only the overridden key from the launch record', () => { diff --git a/src/shared/agent-session-option-catalog-grok.ts b/src/shared/agent-session-option-catalog-grok.ts index 4f14fdbeade..3577ae036a0 100644 --- a/src/shared/agent-session-option-catalog-grok.ts +++ b/src/shared/agent-session-option-catalog-grok.ts @@ -1,9 +1,9 @@ -import { hasFlag } from './agent-cli-flag-detection' import type { AgentSessionOptionCatalog, CatalogModel, CatalogOption } from './agent-session-option-catalog-types' +import { removeAgentArgOption } from './agent-session-option-agent-args' import { parseGrokModelList } from './grok-model-list-probe' // The offered slice of grok's canonical ladder, low to high. Its `none` tier is @@ -35,7 +35,8 @@ function grokEffort(ceiling: 'high' | 'xhigh'): CatalogOption { }, apply: { launchArgs: (value) => ['--reasoning-effort', String(value)], - agentArgsOverride: (tokens) => hasFlag(tokens, ['--effort', '--reasoning-effort']), + // Why: grok reads both spellings. + removeAgentArgs: (tokens) => removeAgentArgOption(tokens, ['--effort', '--reasoning-effort']), midSession: { kind: 'command', build: (value) => `/effort ${String(value)}` } } } @@ -67,7 +68,7 @@ export const GROK_SESSION_OPTION_CATALOG: AgentSessionOptionCatalog = { ], modelApply: { launchArgs: (value) => ['-m', String(value)], - agentArgsOverride: (tokens) => hasFlag(tokens, ['-m', '--model']), + removeAgentArgs: (tokens) => removeAgentArgOption(tokens, ['-m', '--model']), // Why: `agent-picker` would replace the whole model list with "Choose in // agent picker…" and never persist a model, so `-m` would never be emitted. midSession: { kind: 'command', build: (value) => `/model ${String(value)}` } diff --git a/src/shared/agent-session-option-catalog-muse.ts b/src/shared/agent-session-option-catalog-muse.ts index 6b5df60fe4d..91163ca7267 100644 --- a/src/shared/agent-session-option-catalog-muse.ts +++ b/src/shared/agent-session-option-catalog-muse.ts @@ -1,4 +1,3 @@ -import { hasFlag } from './agent-cli-flag-detection' import { removeAgentArgOption } from './agent-session-option-agent-args' import type { AgentSessionOptionCatalog, CatalogOption } from './agent-session-option-catalog-types' @@ -22,7 +21,6 @@ const MUSE_EFFORT: CatalogOption = { }, apply: { launchArgs: (value) => ['--reasoning-effort', String(value)], - agentArgsOverride: (tokens) => hasFlag(tokens, ['--reasoning-effort']), removeAgentArgs: (tokens) => removeAgentArgOption(tokens, ['--reasoning-effort']) } } @@ -34,7 +32,6 @@ export const MUSE_SESSION_OPTION_CATALOG: AgentSessionOptionCatalog = { models: [], modelApply: { launchArgs: (value) => ['--model', String(value)], - agentArgsOverride: (tokens) => hasFlag(tokens, ['--model']), removeAgentArgs: (tokens) => removeAgentArgOption(tokens, ['--model']) }, unknownModelOptions: [MUSE_EFFORT] diff --git a/src/shared/agent-session-option-catalog-omp.test.ts b/src/shared/agent-session-option-catalog-omp.test.ts index fdac98154c6..b5bd44e32f3 100644 --- a/src/shared/agent-session-option-catalog-omp.test.ts +++ b/src/shared/agent-session-option-catalog-omp.test.ts @@ -116,13 +116,16 @@ describe('omp session option catalog', () => { }) it('yields to a user --model in the launch args, in either spelling', () => { - const override = OMP_SESSION_OPTION_CATALOG.modelApply.agentArgsOverride! - expect(override(['--model', 'opus'])).toBe(true) - expect(override(['--model=openai/gpt-5.5'])).toBe(true) - expect(override(['--no-extensions'])).toBe(false) + const remove = OMP_SESSION_OPTION_CATALOG.modelApply.removeAgentArgs! + expect(remove(['--model', 'opus', '--model=openai/gpt-5.5', '--no-extensions'])).toEqual([ + '--no-extensions' + ]) // `--models` scopes Ctrl+P cycling; it does not pick a model. omp has no `-m`. - expect(override(['--models=anthropic/*'])).toBe(false) - expect(override(['-m', 'opus'])).toBe(false) + expect(remove(['--models=anthropic/*', '-m', 'opus'])).toEqual([ + '--models=anthropic/*', + '-m', + 'opus' + ]) }) it('switches mid-session with /orca-model , which omp resolves exactly', () => { diff --git a/src/shared/agent-session-option-catalog-omp.ts b/src/shared/agent-session-option-catalog-omp.ts index 33b870b3d7d..312b3f6306a 100644 --- a/src/shared/agent-session-option-catalog-omp.ts +++ b/src/shared/agent-session-option-catalog-omp.ts @@ -1,4 +1,4 @@ -import { hasFlag } from './agent-cli-flag-detection' +import { removeAgentArgOption } from './agent-session-option-agent-args' import type { AgentSessionOptionCatalog, CatalogModel } from './agent-session-option-catalog-types' import { parseOmpModelList } from './omp-model-list-probe' @@ -18,7 +18,7 @@ export const OMP_SESSION_OPTION_CATALOG: AgentSessionOptionCatalog = { models: [], modelApply: { launchArgs: (value) => ['--model', String(value)], - agentArgsOverride: (tokens) => hasFlag(tokens, ['--model']), + removeAgentArgs: (tokens) => removeAgentArgOption(tokens, ['--model']), // OMP only opens its TUI picker for /model; our extension applies the exact selector. midSession: { kind: 'command', build: (value) => `/orca-model ${String(value)}` } }, diff --git a/src/shared/agent-session-option-catalog-removers.test.ts b/src/shared/agent-session-option-catalog-removers.test.ts new file mode 100644 index 00000000000..64262213d4e --- /dev/null +++ b/src/shared/agent-session-option-catalog-removers.test.ts @@ -0,0 +1,61 @@ +import { describe, expect, it } from 'vitest' +import { getAgentSessionOptionCatalog } from './agent-session-option-catalog' +import { removeAgentArgOption } from './agent-session-option-agent-args' +import { + removeOverriddenAgentSessionArgs, + resolveAgentSessionOptionLaunch +} from './agent-session-option-launch' + +const removeCodexModel = getAgentSessionOptionCatalog('codex')!.modelApply.removeAgentArgs! + +describe('catalog removers', () => { + it.each([ + [['-c', 'model=o3']], + [['--config', 'model=o3']], + [['-c=model=o3']], + [['-cmodel=o3']], + [['--config=model=o3']] + ])('strips Codex model config %j and yields the picked model to it', (tokens) => { + expect(removeCodexModel([...tokens, '--search'])).toEqual(['--search']) + expect( + resolveAgentSessionOptionLaunch('codex', { model: 'gpt-5.5' }, tokens).appliedValues + ).toEqual({}) + }) + + it('keeps other Codex config, including effort, when stripping the model', () => { + const tokens = [ + '-c', + 'model_reasoning_effort=high', + '-cmodel_reasoning_effort=low', + '-csandbox=x' + ] + expect(removeCodexModel(tokens)).toEqual(tokens) + }) + + it('leaves everything after a terminator', () => { + const tokens = ['--', '-c', 'model=o3'] + expect(removeCodexModel(tokens)).toEqual(tokens) + }) + + it('lets a chat pick replace a configured gemini model on server launches', () => { + expect( + removeOverriddenAgentSessionArgs('gemini', { model: 'gemini-2.5-pro' }, [ + '-m', + 'a', + '--model=b', + '-mc', + '--yolo' + ]) + ).toEqual(['--yolo']) + }) +}) + +describe('removeAgentArgOption', () => { + it('consumes an empty value', () => { + expect(removeAgentArgOption(['--model', '', '--yolo'], ['--model'])).toEqual(['--yolo']) + }) + + it('keeps a following flag', () => { + expect(removeAgentArgOption(['--model', '--yolo'], ['--model'])).toEqual(['--yolo']) + }) +}) diff --git a/src/shared/agent-session-option-catalog-types.ts b/src/shared/agent-session-option-catalog-types.ts index 74e488e5549..fe15d5080e0 100644 --- a/src/shared/agent-session-option-catalog-types.ts +++ b/src/shared/agent-session-option-catalog-types.ts @@ -21,10 +21,8 @@ export type CatalogMidSessionApply = export type CatalogOptionApply = { launchArgs?: (value: SessionOptionValue) => string[] - /** Why: later free-form args win, so the launch record must discard any - * picker value that those args may have replaced. */ - agentArgsOverride?: (tokens: readonly string[]) => boolean - /** Removes conflicting defaults before a more specific launch choice is inserted. */ + /** Strips the free-form args that set this option. Why: later free-form args win, so + * anything this strips also tells the launch record to discard the picker value. */ removeAgentArgs?: (tokens: readonly string[]) => string[] composedIntoModel?: true midSession?: CatalogMidSessionApply @@ -75,7 +73,7 @@ export type AgentSessionOptionCatalog = { * * Known gap: "no model flag" is unverified. A user `-m` in `agentArgs` launches that * model while the picker, which never reads launch args, still names the CLI default. - * A real fix means threading `modelApply.agentArgsOverride` through to the surface. */ + * A real fix means threading `modelApply.removeAgentArgs` through to the surface. */ defaultModelIsCliDefault?: true listModels?: { command: string diff --git a/src/shared/agent-session-option-launch.ts b/src/shared/agent-session-option-launch.ts index 53df967a838..e9c5b90181d 100644 --- a/src/shared/agent-session-option-launch.ts +++ b/src/shared/agent-session-option-launch.ts @@ -1,5 +1,6 @@ import type { AgentType } from './agent-status-types' import { findCatalogModel, getAgentSessionOptionCatalog } from './agent-session-option-catalog' +import type { CatalogOptionApply } from './agent-session-option-catalog-types' import type { SessionOptionValue } from './native-chat-session-options' export type ResolvedSessionOptionLaunch = { @@ -7,6 +8,11 @@ export type ResolvedSessionOptionLaunch = { appliedValues: Record } +function isOverriddenByAgentArgs(apply: CatalogOptionApply, tokens: readonly string[]): boolean { + const kept = apply.removeAgentArgs?.(tokens) + return kept !== undefined && kept.length < tokens.length +} + export function removeOverriddenAgentSessionArgs( agent: AgentType, values: Record | null | undefined, @@ -63,7 +69,7 @@ export function resolveAgentSessionOptionLaunch( const composedModelId = catalog.composeModelValue ? catalog.composeModelValue(modelId, modelValues) : modelId - const modelOverridden = catalog.modelApply.agentArgsOverride?.(trailingAgentArgs) === true + const modelOverridden = isOverriddenByAgentArgs(catalog.modelApply, trailingAgentArgs) if (catalog.modelApply.launchArgs) { args.push(...catalog.modelApply.launchArgs(composedModelId)) @@ -86,7 +92,7 @@ export function resolveAgentSessionOptionLaunch( continue } args.push(...option.apply.launchArgs(value)) - if (!modelOverridden && !option.apply.agentArgsOverride?.(trailingAgentArgs)) { + if (!modelOverridden && !isOverriddenByAgentArgs(option.apply, trailingAgentArgs)) { appliedValues[option.id] = value } } diff --git a/src/shared/command-option-occurrence.ts b/src/shared/command-option-occurrence.ts index 6dee668829b..203e7252723 100644 --- a/src/shared/command-option-occurrence.ts +++ b/src/shared/command-option-occurrence.ts @@ -11,7 +11,7 @@ function matchesOption(token: string, aliases: readonly string[]): boolean { } export function findOptionOccurrence( - tokens: string[], + tokens: readonly string[], aliases: readonly string[], stopAtTerminator: boolean ): { index: number; consumed: number; value?: string } | null {