refactor: split OpenWebUI into strategy classes by backend
Replace Conditional with Polymorphism from the Refactoring Guru catalog.
The OpenWebUIProvider previously contained two parallel implementations
(completeWithChat / completeWithOllama, streamWithChat / streamWithOllama,
plus branching in validateConnection) selected by a `useOllamaProxy`
boolean. The 987-line file is split into:
- openwebui-types.ts wire-format response types
- openwebui-http.ts shared HTTP client (auth, timeout)
- openwebui-strategies.ts OpenWebUIStrategy interface plus
OpenWebUIChatStrategy and
OpenWebUIOllamaStrategy implementations
- openwebui.ts thin provider that picks one strategy at
construction and delegates
OpenWebUIProvider and OpenWebUIConfig (including useOllamaProxy) stay
in their original locations, so consumer imports are unchanged.
Side effects:
- Error mapping now goes through the base mapProviderError / providerErrorMessages
hooks added in R2, removing handleOpenWebUIError and its duplicated status switch.
- providerInfo.version bumped to 2.0.0 to reflect the rewrite.
This commit is contained in:
59
src/providers/openwebui-http.ts
Normal file
59
src/providers/openwebui-http.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { AIProviderError, AIErrorType } from '../types/index.js';
|
||||
import { DEFAULT_TIMEOUT_MS } from '../constants.js';
|
||||
|
||||
export interface OpenWebUIHttpOptions {
|
||||
baseUrl: string;
|
||||
apiKey?: string;
|
||||
timeout?: number;
|
||||
dangerouslyAllowInsecureConnections?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Thin HTTP client shared by OpenWebUI strategies.
|
||||
* Handles auth header, request body serialization, and timeout-to-AIProviderError translation.
|
||||
*/
|
||||
export class OpenWebUIHttpClient {
|
||||
readonly baseUrl: string;
|
||||
private readonly apiKey: string | undefined;
|
||||
private readonly timeout: number;
|
||||
private readonly dangerouslyAllowInsecureConnections: boolean;
|
||||
|
||||
constructor(options: OpenWebUIHttpOptions) {
|
||||
this.baseUrl = options.baseUrl;
|
||||
this.apiKey = options.apiKey;
|
||||
this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
|
||||
this.dangerouslyAllowInsecureConnections = options.dangerouslyAllowInsecureConnections ?? true;
|
||||
}
|
||||
|
||||
async request(path: string, method: string, body?: unknown): Promise<Response> {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': 'simple-ai-provider/2.0.0'
|
||||
};
|
||||
|
||||
if (this.apiKey) {
|
||||
headers['Authorization'] = `Bearer ${this.apiKey}`;
|
||||
}
|
||||
|
||||
const requestOptions: RequestInit = {
|
||||
method,
|
||||
headers,
|
||||
body: body !== undefined ? JSON.stringify(body) : undefined,
|
||||
signal: AbortSignal.timeout(this.timeout)
|
||||
};
|
||||
|
||||
try {
|
||||
return await fetch(`${this.baseUrl}${path}`, requestOptions);
|
||||
} catch (error: any) {
|
||||
if (error.name === 'AbortError') {
|
||||
throw new AIProviderError(
|
||||
'Request timed out',
|
||||
AIErrorType.TIMEOUT,
|
||||
undefined,
|
||||
error
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user