fix(runtime): pass provider args through codex probes

This commit is contained in:
777genius 2026-04-23 21:29:19 +03:00
parent 155a9f76ab
commit 0bde66b032
4 changed files with 165 additions and 23 deletions

View file

@ -44,7 +44,7 @@ interface ProviderModelAvailabilityCacheEntry {
providerId: CliProviderId;
signature: string;
snapshot: ProviderModelAvailabilitySnapshot;
envPromise: Promise<NodeJS.ProcessEnv>;
cliEnvPromise: Promise<{ env: NodeJS.ProcessEnv; providerArgs: string[] }>;
}
type ProviderAvailabilityUpdateHandler = (
@ -190,10 +190,13 @@ export class CliProviderModelAvailabilityService {
providerId: context.provider.providerId,
signature,
snapshot: createCheckingSnapshot(signature, visibleModels),
envPromise: buildProviderAwareCliEnv({
cliEnvPromise: buildProviderAwareCliEnv({
binaryPath: context.binaryPath,
providerId: context.provider.providerId,
}).then((result) => result.env),
}).then((result) => ({
env: result.env,
providerArgs: result.providerArgs ?? [],
})),
};
this.cache.set(signature, entry);
this.startProbes(context, entry);
@ -268,11 +271,15 @@ export class CliProviderModelAvailabilityService {
modelId: string
): Promise<Pick<CliProviderModelAvailability, 'status' | 'reason'>> {
try {
const env = await entry.envPromise;
const { stdout } = await execCli(context.binaryPath, buildProviderModelProbeArgs(modelId), {
timeout: getProviderModelProbeTimeoutMs(context.provider.providerId),
env,
});
const { env, providerArgs } = await entry.cliEnvPromise;
const { stdout } = await execCli(
context.binaryPath,
[...buildProviderModelProbeArgs(modelId), ...providerArgs],
{
timeout: getProviderModelProbeTimeoutMs(context.provider.providerId),
env,
}
);
const output = stdout.trim();
if (isProviderModelProbeSuccessOutput(output)) {
return {

View file

@ -3563,11 +3563,13 @@ export class TeamProvisioningService {
cwd: string;
providerId: TeamProviderId;
env: NodeJS.ProcessEnv;
providerArgs?: string[];
limitContext?: boolean;
}): Promise<RuntimeProviderLaunchFacts> {
const providerArgs = params.providerArgs ?? [];
const modelListPromise = execCli(
params.claudePath,
['model', 'list', '--json', '--provider', params.providerId],
['model', 'list', '--json', '--provider', params.providerId, ...providerArgs],
{
cwd: params.cwd,
env: params.env,
@ -3578,7 +3580,7 @@ export class TeamProvisioningService {
params.providerId === 'codex' || params.providerId === 'anthropic'
? execCli(
params.claudePath,
['runtime', 'status', '--json', '--provider', params.providerId],
['runtime', 'status', '--json', '--provider', params.providerId, ...providerArgs],
{
cwd: params.cwd,
env: params.env,
@ -7434,11 +7436,11 @@ export class TeamProvisioningService {
const isAuthFailure = this.isAuthFailureWarning(probeResult.warning, 'probe');
const isBlockingPreflightWarning =
authSource === 'configured_api_key_missing' ||
(((authSource === 'none' ||
((authSource === 'none' ||
authSource === 'codex_runtime' ||
authSource === 'gemini_runtime') &&
isAuthFailure) ||
isBinaryProbeWarning(probeResult.warning));
isBinaryProbeWarning(probeResult.warning);
if (authSource === 'configured_api_key_missing') {
blockingMessages.push(prefixedWarning);
} else if (
@ -7849,12 +7851,13 @@ export class TeamProvisioningService {
return { details, warnings, blockingMessages };
}
const { env } = await this.buildProvisioningEnv(providerId);
const { env, providerArgs = [] } = await this.buildProvisioningEnv(providerId);
const runtimeFacts = await this.readRuntimeProviderLaunchFacts({
claudePath,
cwd,
providerId,
env,
providerArgs,
limitContext,
});
const probeOutcomeByResolvedModelId = new Map<
@ -7912,6 +7915,7 @@ export class TeamProvisioningService {
cwd,
providerId,
env,
providerArgs,
limitContext
);
} catch {
@ -7966,7 +7970,7 @@ export class TeamProvisioningService {
try {
const result = await this.spawnProbe(
claudePath,
buildProviderModelProbeArgs(targetModelId),
[...buildProviderModelProbeArgs(targetModelId), ...providerArgs],
cwd,
env,
getProviderModelProbeTimeoutMs(providerId),
@ -8069,13 +8073,18 @@ export class TeamProvisioningService {
cwd: string,
providerId: TeamProviderId,
env: NodeJS.ProcessEnv,
providerArgs: string[] = [],
limitContext: boolean
): Promise<string | null> {
const { stdout } = await execCli(claudePath, ['model', 'list', '--json', '--provider', 'all'], {
cwd,
env,
timeout: 10_000,
});
const { stdout } = await execCli(
claudePath,
['model', 'list', '--json', '--provider', 'all', ...providerArgs],
{
cwd,
env,
timeout: 10_000,
}
);
const parsed = extractJsonObjectFromCli<ProviderModelListCommandResponse>(stdout);
const defaultModel = parsed.providers?.[providerId]?.defaultModel;
const normalizedDefaultModel =
@ -8145,6 +8154,7 @@ export class TeamProvisioningService {
params.cwd,
providerId,
envResolution.env,
envResolution.providerArgs,
params.limitContext === true
);
const normalized = resolvedDefaultModel?.trim();
@ -8240,7 +8250,12 @@ export class TeamProvisioningService {
const claudePath = await ClaudeBinaryResolver.resolve();
if (!claudePath) return null;
const { env, authSource, warning } = await this.buildProvisioningEnv(providerId);
const {
env,
authSource,
providerArgs = [],
warning,
} = await this.buildProvisioningEnv(providerId);
if (warning) {
return {
claudePath,
@ -8249,7 +8264,7 @@ export class TeamProvisioningService {
};
}
const probe = await this.probeClaudeRuntime(claudePath, cwd, env, providerId);
const probe = await this.probeClaudeRuntime(claudePath, cwd, env, providerId, providerArgs);
const result = {
claudePath,
authSource,
@ -18476,7 +18491,8 @@ export class TeamProvisioningService {
claudePath: string,
cwd: string,
env: NodeJS.ProcessEnv,
providerId: TeamProviderId | undefined = 'anthropic'
providerId: TeamProviderId | undefined = 'anthropic',
providerArgs: string[] = []
): Promise<{ warning?: string }> {
const resolvedProviderId = resolveTeamProviderId(providerId);
const cliCommandLabel = getConfiguredCliCommandLabel();
@ -18543,7 +18559,7 @@ export class TeamProvisioningService {
try {
pingProbe = await this.spawnProbe(
claudePath,
getPreflightPingArgs(providerId),
[...getPreflightPingArgs(providerId), ...providerArgs],
cwd,
env,
getPreflightTimeoutMs(providerId),

View file

@ -152,4 +152,29 @@ describe('CliProviderModelAvailabilityService', () => {
expect(execCliMock).toHaveBeenCalledTimes(3);
});
});
it('passes provider launch args into codex model probes', async () => {
buildProviderAwareCliEnvMock.mockResolvedValue({
env: { HOME: '/Users/tester' },
providerArgs: ['--settings', '{"codex":{"forced_login_method":"chatgpt"}}'],
connectionIssues: {},
});
execCliMock.mockResolvedValue({ stdout: 'PONG', stderr: '' });
const service = new CliProviderModelAvailabilityService();
service.getSnapshot(createContext(['gpt-5.4']));
await vi.waitFor(() => {
expect(execCliMock).toHaveBeenCalledWith(
'/usr/local/bin/claude',
expect.arrayContaining([
'--settings',
'{"codex":{"forced_login_method":"chatgpt"}}',
]),
expect.objectContaining({
env: { HOME: '/Users/tester' },
})
);
});
});
});

View file

@ -1025,6 +1025,47 @@ describe('TeamProvisioningService prepare/auth behavior', () => {
expect(result.warning).toContain('request id: req_123');
});
it('passes provider launch args into codex preflight ping probes', async () => {
const svc = new TeamProvisioningService();
const spawnProbe = vi
.spyOn(svc as any, 'spawnProbe')
.mockResolvedValueOnce({
stdout: 'orchestrator-cli 1.2.3',
stderr: '',
exitCode: 0,
})
.mockResolvedValueOnce({
stdout: 'PONG',
stderr: '',
exitCode: 0,
});
const result = await (svc as any).probeClaudeRuntime(
'/fake/claude',
tempRoot,
{
PATH: '/usr/bin',
SHELL: '/bin/zsh',
},
'codex',
['--settings', '{"codex":{"forced_login_method":"chatgpt"}}']
);
expect(result.warning).toBeUndefined();
expect(spawnProbe).toHaveBeenNthCalledWith(
2,
'/fake/claude',
expect.arrayContaining([
'--settings',
'{"codex":{"forced_login_method":"chatgpt"}}',
]),
tempRoot,
expect.any(Object),
60_000,
expect.any(Object)
);
});
it('continues selected model verification after transient preflight warnings', async () => {
const svc = new TeamProvisioningService();
vi.spyOn(svc as any, 'getCachedOrProbeResult').mockResolvedValue({
@ -1091,6 +1132,59 @@ describe('TeamProvisioningService prepare/auth behavior', () => {
);
});
it('passes provider launch args into selected codex model probes', async () => {
const svc = new TeamProvisioningService();
vi.spyOn(svc as any, 'buildProvisioningEnv').mockResolvedValue({
env: {
PATH: '/usr/bin',
SHELL: '/bin/zsh',
},
authSource: 'codex_runtime',
geminiRuntimeAuth: null,
providerArgs: ['--settings', '{"codex":{"forced_login_method":"chatgpt"}}'],
});
const readRuntimeProviderLaunchFacts = vi
.spyOn(svc as any, 'readRuntimeProviderLaunchFacts')
.mockResolvedValue({
defaultModel: null,
modelIds: new Set(['gpt-5.4']),
modelCatalog: null,
runtimeCapabilities: null,
providerStatus: null,
});
const spawnProbe = vi.spyOn(svc as any, 'spawnProbe').mockResolvedValue({
stdout: 'PONG',
stderr: '',
exitCode: 0,
});
const result = await (svc as any).verifySelectedProviderModels({
claudePath: '/fake/claude',
cwd: tempRoot,
providerId: 'codex',
modelIds: ['gpt-5.4'],
limitContext: false,
});
expect(result.details).toEqual(['Selected model gpt-5.4 verified for launch.']);
expect(readRuntimeProviderLaunchFacts).toHaveBeenCalledWith(
expect.objectContaining({
providerArgs: ['--settings', '{"codex":{"forced_login_method":"chatgpt"}}'],
})
);
expect(spawnProbe).toHaveBeenCalledWith(
'/fake/claude',
expect.arrayContaining([
'--settings',
'{"codex":{"forced_login_method":"chatgpt"}}',
]),
tempRoot,
expect.any(Object),
60_000,
expect.any(Object)
);
});
it('maps ANTHROPIC_AUTH_TOKEN into ANTHROPIC_API_KEY for headless preflight', async () => {
const svc = new TeamProvisioningService();
vi.mocked(resolveInteractiveShellEnv).mockResolvedValue({