refactor: improve error response for non-available models (#298)

This commit is contained in:
Christian Schimetschka
2026-04-04 04:15:27 +02:00
committed by GitHub
parent 694c242865
commit 2031c67d46
2 changed files with 82 additions and 2 deletions

View File

@@ -0,0 +1,42 @@
import { describe, expect, test } from 'bun:test'
import { formatReachabilityFailureDetail } from './system-check.ts'
describe('formatReachabilityFailureDetail', () => {
test('returns generic failure detail for non-codex transport', () => {
const detail = formatReachabilityFailureDetail(
'https://api.openai.com/v1/models',
429,
'{"error":"rate_limit"}',
{
transport: 'chat_completions',
requestedModel: 'gpt-4o',
resolvedModel: 'gpt-4o',
},
)
expect(detail).toBe(
'Unexpected status 429 from https://api.openai.com/v1/models. Body: {"error":"rate_limit"}',
)
})
test('adds alias/entitlement hint for codex model support 400s', () => {
const detail = formatReachabilityFailureDetail(
'https://chatgpt.com/backend-api/codex/responses',
400,
'{"detail":"The \\"gpt-5.3-codex-spark\\" model is not supported when using Codex with a ChatGPT account."}',
{
transport: 'codex_responses',
requestedModel: 'codexspark',
resolvedModel: 'gpt-5.3-codex-spark',
},
)
expect(detail).toContain(
'model alias "codexspark" resolved to "gpt-5.3-codex-spark"',
)
expect(detail).toContain(
'Try "codexplan" or another entitled Codex model.',
)
})
})

View File

@@ -58,6 +58,31 @@ function parseOptions(argv: string[]): CliOptions {
return options
}
export function formatReachabilityFailureDetail(
endpoint: string,
status: number,
responseBody: string,
request: {
transport: string
requestedModel: string
resolvedModel: string
},
): string {
const compactBody = responseBody.trim().replace(/\s+/g, ' ').slice(0, 240)
const base = `Unexpected status ${status} from ${endpoint}.`
const bodySuffix = compactBody ? ` Body: ${compactBody}` : ''
if (request.transport !== 'codex_responses' || status !== 400) {
return `${base}${bodySuffix}`
}
if (!/not supported.*chatgpt account/i.test(responseBody)) {
return `${base}${bodySuffix}`
}
return `${base}${bodySuffix} Hint: model alias "${request.requestedModel}" resolved to "${request.resolvedModel}", which this ChatGPT account does not currently allow. Try "codexplan" or another entitled Codex model.`
}
function checkNodeVersion(): CheckResult {
const raw = process.versions.node
const major = Number(raw.split('.')[0] ?? '0')
@@ -284,6 +309,7 @@ async function checkBaseUrlReachability(): Promise<CheckResult> {
headers['chatgpt-account-id'] = credentials.accountId
}
headers['Content-Type'] = 'application/json'
headers.originator = 'openclaude'
method = 'POST'
body = JSON.stringify({
model: request.resolvedModel,
@@ -315,7 +341,17 @@ async function checkBaseUrlReachability(): Promise<CheckResult> {
return pass('Provider reachability', `Reached ${endpoint} (status ${response.status}).`)
}
return fail('Provider reachability', `Unexpected status ${response.status} from ${endpoint}.`)
const responseBody = await response.text().catch(() => '')
const detail = formatReachabilityFailureDetail(
endpoint,
response.status,
responseBody,
request,
)
return fail(
'Provider reachability',
detail,
)
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
return fail('Provider reachability', `Failed to reach ${endpoint}: ${message}`)
@@ -504,6 +540,8 @@ async function main(): Promise<void> {
}
}
await main()
if (import.meta.main) {
await main()
}
export {}