From 2031c67d46eee0e5dcec59917eedb945639e1300 Mon Sep 17 00:00:00 2001 From: Christian Schimetschka Date: Sat, 4 Apr 2026 04:15:27 +0200 Subject: [PATCH] refactor: improve error response for non-available models (#298) --- scripts/system-check.test.ts | 42 ++++++++++++++++++++++++++++++++++++ scripts/system-check.ts | 42 ++++++++++++++++++++++++++++++++++-- 2 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 scripts/system-check.test.ts diff --git a/scripts/system-check.test.ts b/scripts/system-check.test.ts new file mode 100644 index 00000000..6937414c --- /dev/null +++ b/scripts/system-check.test.ts @@ -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.', + ) + }) +}) diff --git a/scripts/system-check.ts b/scripts/system-check.ts index 6b4526f0..1261e485 100644 --- a/scripts/system-check.ts +++ b/scripts/system-check.ts @@ -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 { 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 { 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 { } } -await main() +if (import.meta.main) { + await main() +} export {}