fix(runtime): pass provider args through codex probes
This commit is contained in:
parent
155a9f76ab
commit
0bde66b032
4 changed files with 165 additions and 23 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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' },
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
Loading…
Reference in a new issue