agent-ecosystem/src/main/services/runtime/providerModelProbe.ts

119 lines
3.4 KiB
TypeScript

import type { CliProviderId, TeamProviderId } from '@shared/types';
const PROVIDER_MODEL_PROBE_TIMEOUT_MS = 60_000;
const PROVIDER_MODEL_PROBE_CODEX_TIMEOUT_MS = 60_000;
const PROVIDER_MODEL_PROBE_GEMINI_TIMEOUT_MS = 15_000;
const PROVIDER_MODEL_PROBE_PROMPT = 'Output only the single word PONG.';
type SupportedProviderId = CliProviderId | TeamProviderId;
function resolveProbeProviderId(providerId: SupportedProviderId | undefined): SupportedProviderId {
return providerId === 'codex' || providerId === 'gemini' ? providerId : 'anthropic';
}
export function getProviderModelProbePrompt(): string {
return PROVIDER_MODEL_PROBE_PROMPT;
}
export function getProviderModelProbeExpectedOutput(): string {
return 'PONG';
}
export function isProviderModelProbeSuccessOutput(output: string): boolean {
return new RegExp(`\\b${getProviderModelProbeExpectedOutput()}\\b`, 'i').test(output.trim());
}
export function classifyProviderModelProbeFailure(message: string): 'unavailable' | 'unknown' {
const lower = message.toLowerCase();
if (
lower.includes('model is not supported') ||
lower.includes('model not supported') ||
lower.includes('unsupported model') ||
lower.includes('model is not available') ||
lower.includes('model not available') ||
lower.includes('model unavailable') ||
lower.includes('model not found') ||
lower.includes('unknown model') ||
lower.includes('invalid model')
) {
return 'unavailable';
}
return 'unknown';
}
export function isProviderModelProbeTimeoutMessage(message: string): boolean {
const lower = message.toLowerCase();
return (
lower.includes('timeout running:') ||
lower.includes('timed out') ||
lower.includes('etimedout') ||
lower.includes('did not complete')
);
}
export function normalizeProviderModelProbeFailureReason(message: string): string {
const trimmed = message.trim();
if (!trimmed) {
return 'Model verification failed';
}
if (
/The '[^']+' model is not supported when using Codex with a ChatGPT account\./i.test(trimmed)
) {
return 'Not available on this Codex native runtime';
}
if (/The requested model is not available for your account\./i.test(trimmed)) {
return 'Not available for this account';
}
if (isProviderModelProbeTimeoutMessage(trimmed)) {
return 'Model verification timed out';
}
return trimmed;
}
export function buildProviderModelProbeArgs(modelId: string): string[] {
return [
'-p',
getProviderModelProbePrompt(),
'--output-format',
'text',
'--model',
modelId,
'--max-turns',
'1',
'--no-session-persistence',
];
}
export function getProviderModelProbeTimeoutMs(
providerId: SupportedProviderId | undefined
): number {
switch (resolveProbeProviderId(providerId)) {
case 'codex':
return PROVIDER_MODEL_PROBE_CODEX_TIMEOUT_MS;
case 'gemini':
return PROVIDER_MODEL_PROBE_GEMINI_TIMEOUT_MS;
case 'anthropic':
default:
return PROVIDER_MODEL_PROBE_TIMEOUT_MS;
}
}
export function getProviderPreflightModel(providerId: TeamProviderId | undefined): string {
switch (resolveProbeProviderId(providerId)) {
case 'codex':
return 'gpt-5.4-mini';
case 'gemini':
return 'gemini-2.5-flash-lite';
case 'anthropic':
default:
return 'haiku';
}
}
export function buildProviderPreflightPingArgs(providerId: TeamProviderId | undefined): string[] {
return buildProviderModelProbeArgs(getProviderPreflightModel(providerId));
}