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.
60 lines
1.7 KiB
TypeScript
60 lines
1.7 KiB
TypeScript
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;
|
|
}
|
|
}
|
|
}
|