|
15 | 15 | * layer logs them with `reason`. |
16 | 16 | */ |
17 | 17 |
|
18 | | -import type { ProviderId } from '@open-codesign/shared'; |
| 18 | +import { looksLikeGatewayMissingMessagesApi } from '@open-codesign/providers'; |
| 19 | +import type { ProviderId, WireApi } from '@open-codesign/shared'; |
19 | 20 | import { CodesignError, ERROR_CODES } from '@open-codesign/shared'; |
20 | 21 |
|
21 | 22 | export const PROVIDER_KEY_HELP_URL: Partial<Record<ProviderId, string>> = { |
@@ -98,9 +99,29 @@ export function rewriteUpstreamMessage( |
98 | 99 | * 4xx errors are rewritten — everything else is rethrown unchanged so the |
99 | 100 | * retry/network layer keeps its own taxonomy. |
100 | 101 | */ |
101 | | -export function remapProviderError(err: unknown, provider: string | undefined): unknown { |
| 102 | +export function remapProviderError( |
| 103 | + err: unknown, |
| 104 | + provider: string | undefined, |
| 105 | + wire?: WireApi | undefined, |
| 106 | +): unknown { |
102 | 107 | if (!(err instanceof Error)) return err; |
103 | 108 | if (err instanceof CodesignError && err.code === ERROR_CODES.PROVIDER_ABORTED) return err; |
| 109 | + // Third-party Anthropic relays often reply to POST /v1/messages with 5xx + |
| 110 | + // "not implemented" while their /v1/models endpoint works. Catch that shape |
| 111 | + // before any other classification so the UI can suggest switching wire |
| 112 | + // instead of the misleading default "check your API key" message. Guard on |
| 113 | + // wire === 'anthropic' because the actionable hint ("switch wire to |
| 114 | + // openai-chat") only makes sense for Anthropic-compatible endpoints — a 501 |
| 115 | + // from an OpenAI/Google wire is just a generic upstream error. |
| 116 | + if ( |
| 117 | + wire === 'anthropic' && |
| 118 | + !(err instanceof CodesignError && err.code === ERROR_CODES.PROVIDER_GATEWAY_INCOMPATIBLE) && |
| 119 | + looksLikeGatewayMissingMessagesApi(err) |
| 120 | + ) { |
| 121 | + return new CodesignError(err.message, ERROR_CODES.PROVIDER_GATEWAY_INCOMPATIBLE, { |
| 122 | + cause: err, |
| 123 | + }); |
| 124 | + } |
104 | 125 | const status = statusFromError(err); |
105 | 126 | if (status === undefined || status < 400 || status >= 500) return err; |
106 | 127 | const { message, rewritten } = rewriteUpstreamMessage(err.message, provider, status); |
|
0 commit comments