From fa77f312f47f697959f8b2a498b7ae51d2946bab Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 09:36:37 +0000 Subject: [PATCH] Improve error handling for AI provider connections --- src/ai/http.ts | 15 ++++++++++++++- src/test/http_error.test.ts | 29 +++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 src/test/http_error.test.ts diff --git a/src/ai/http.ts b/src/ai/http.ts index b3010cf..fd401e6 100644 --- a/src/ai/http.ts +++ b/src/ai/http.ts @@ -31,7 +31,20 @@ export async function postChatCompletion( signal: controller.signal, }); } catch (e) { - throw new ProviderError(e instanceof Error ? e.message : 'Network error'); + if (e instanceof Error) { + if (e.name === 'AbortError') { + throw new ProviderError(`Request timed out after 30s connecting to ${url}`); + } + const cause = (e as any).cause; + if (cause?.code === 'ECONNREFUSED') { + throw new ProviderError(`Connection refused to ${url}. Is the server running?`); + } + if (cause?.code === 'ENOTFOUND') { + throw new ProviderError(`Address not found: ${url}. Check your settings.`); + } + throw new ProviderError(`Network error: ${e.message}`); + } + throw new ProviderError('Network error'); } if (!res.ok) { diff --git a/src/test/http_error.test.ts b/src/test/http_error.test.ts new file mode 100644 index 0000000..6a4d22c --- /dev/null +++ b/src/test/http_error.test.ts @@ -0,0 +1,29 @@ +import * as assert from 'assert'; +import { postChatCompletion } from '../ai/http'; +import { ProviderError } from '../ai/errors'; + +suite('HTTP Error Handling', () => { + test('ECONNREFUSED returns friendly message', async () => { + const url = 'http://localhost:11111/v1/chat/completions'; // Port likely closed + try { + await postChatCompletion(url, undefined, { model: 'test', messages: [] }); + assert.fail('Should have thrown'); + } catch (e) { + assert.ok(e instanceof ProviderError, `Expected ProviderError but got ${e}`); + assert.ok(e.message.includes('Connection refused'), `Expected 'Connection refused' in '${e.message}'`); + assert.ok(e.message.includes(url), `Expected URL in error message '${e.message}'`); + } + }); + + test('ENOTFOUND returns friendly message', async () => { + const url = 'http://nonexistent-domain-xyz.local/v1/chat/completions'; + try { + await postChatCompletion(url, undefined, { model: 'test', messages: [] }); + assert.fail('Should have thrown'); + } catch (e) { + assert.ok(e instanceof ProviderError, `Expected ProviderError but got ${e}`); + assert.ok(e.message.includes('Address not found'), `Expected 'Address not found' in '${e.message}'`); + assert.ok(e.message.includes(url), `Expected URL in error message '${e.message}'`); + } + }); +});