feat(api): classify openai-compatible provider failures (#708)

* feat(api): classify openai-compatible provider failures

* Update src/services/api/providerConfig.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/services/api/errors.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* feat(api): harden openai-compatible diagnostics and env fallback

* Update src/services/api/openaiShim.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/services/api/openaiShim.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/services/api/errors.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/services/api/errors.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix openaiShim duplicate requests and diagnostics

* remove unused url from http failure classifier

* dedupe env diagnostic warnings

* Remove hardcoded URLs from OpenAI error tests

Removed hardcoded URLs from network failure classification tests.

* Update providerConfig.envDiagnostics.test.ts

* fix(openai-shim): return successful responses and restore localhost classifier tests

* Update src/services/api/openaiShim.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/services/api/openaiShim.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/services/api/openaiShim.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
nehan
2026-04-17 14:01:40 +04:00
committed by GitHub
parent eed77e6579
commit 80a00acc2c
9 changed files with 1117 additions and 18 deletions

View File

@@ -2775,3 +2775,84 @@ test('streaming: strips leaked reasoning preamble when split across multiple con
expect(textDeltas).toEqual(['Hey! How can I help you today?'])
})
test('classifies localhost transport failures with actionable category marker', async () => {
process.env.OPENAI_BASE_URL = 'http://localhost:11434/v1'
const transportError = Object.assign(new TypeError('fetch failed'), {
code: 'ECONNREFUSED',
})
globalThis.fetch = (async () => {
throw transportError
}) as FetchType
const client = createOpenAIShimClient({}) as OpenAIShimClient
await expect(
client.beta.messages.create({
model: 'qwen2.5-coder:7b',
messages: [{ role: 'user', content: 'hello' }],
max_tokens: 64,
stream: false,
}),
).rejects.toThrow('openai_category=connection_refused')
await expect(
client.beta.messages.create({
model: 'qwen2.5-coder:7b',
messages: [{ role: 'user', content: 'hello' }],
max_tokens: 64,
stream: false,
}),
).rejects.toThrow('local server is running')
})
test('propagates AbortError without wrapping it as transport failure', async () => {
process.env.OPENAI_BASE_URL = 'http://localhost:11434/v1'
const abortError = new DOMException('The operation was aborted.', 'AbortError')
globalThis.fetch = (async () => {
throw abortError
}) as FetchType
const controller = new AbortController()
controller.abort()
const client = createOpenAIShimClient({}) as OpenAIShimClient
await expect(
client.beta.messages.create(
{
model: 'qwen2.5-coder:7b',
messages: [{ role: 'user', content: 'hello' }],
max_tokens: 64,
stream: false,
},
{ signal: controller.signal },
),
).rejects.toBe(abortError)
})
test('classifies chat-completions endpoint 404 failures with endpoint_not_found marker', async () => {
process.env.OPENAI_BASE_URL = 'http://localhost:11434'
globalThis.fetch = (async () =>
new Response('Not Found', {
status: 404,
headers: {
'Content-Type': 'text/plain',
},
})) as FetchType
const client = createOpenAIShimClient({}) as OpenAIShimClient
await expect(
client.beta.messages.create({
model: 'qwen2.5-coder:7b',
messages: [{ role: 'user', content: 'hello' }],
max_tokens: 64,
stream: false,
}),
).rejects.toThrow('openai_category=endpoint_not_found')
})