mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-18 00:02:45 +00:00
Better proxy handling.
This commit is contained in:
parent
ec50ede6c2
commit
b6b64dff86
6 changed files with 298 additions and 160 deletions
112
packages/web-ui/src/utils/proxy-utils.ts
Normal file
112
packages/web-ui/src/utils/proxy-utils.ts
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
import type { Api, Model } from "@mariozechner/pi-ai";
|
||||
|
||||
/**
|
||||
* Centralized proxy decision logic.
|
||||
*
|
||||
* Determines whether to use a CORS proxy for LLM API requests based on:
|
||||
* - Provider name
|
||||
* - API key pattern (for providers where it matters)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check if a provider/API key combination requires a CORS proxy.
|
||||
*
|
||||
* @param provider - Provider name (e.g., "anthropic", "openai", "zai")
|
||||
* @param apiKey - API key for the provider
|
||||
* @returns true if proxy is required, false otherwise
|
||||
*/
|
||||
export function shouldUseProxyForProvider(provider: string, apiKey: string): boolean {
|
||||
switch (provider.toLowerCase()) {
|
||||
case "zai":
|
||||
// Z-AI always requires proxy
|
||||
return true;
|
||||
|
||||
case "anthropic":
|
||||
// Anthropic OAuth tokens (sk-ant-oat-*) require proxy
|
||||
// Regular API keys (sk-ant-api-*) do NOT require proxy
|
||||
return apiKey.startsWith("sk-ant-oat");
|
||||
|
||||
// These providers work without proxy
|
||||
case "openai":
|
||||
case "google":
|
||||
case "groq":
|
||||
case "openrouter":
|
||||
case "cerebras":
|
||||
case "xai":
|
||||
case "ollama":
|
||||
case "lmstudio":
|
||||
return false;
|
||||
|
||||
// Unknown providers - assume no proxy needed
|
||||
// This allows new providers to work by default
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply CORS proxy to a model's baseUrl if needed.
|
||||
*
|
||||
* @param model - The model to potentially proxy
|
||||
* @param apiKey - API key for the provider
|
||||
* @param proxyUrl - CORS proxy URL (e.g., "https://proxy.mariozechner.at/proxy")
|
||||
* @returns Model with modified baseUrl if proxy is needed, otherwise original model
|
||||
*/
|
||||
export function applyProxyIfNeeded<T extends Api>(model: Model<T>, apiKey: string, proxyUrl?: string): Model<T> {
|
||||
// If no proxy URL configured, return original model
|
||||
if (!proxyUrl) {
|
||||
return model;
|
||||
}
|
||||
|
||||
// If model has no baseUrl, can't proxy it
|
||||
if (!model.baseUrl) {
|
||||
return model;
|
||||
}
|
||||
|
||||
// Check if this provider/key needs proxy
|
||||
if (!shouldUseProxyForProvider(model.provider, apiKey)) {
|
||||
return model;
|
||||
}
|
||||
|
||||
// Apply proxy to baseUrl
|
||||
return {
|
||||
...model,
|
||||
baseUrl: `${proxyUrl}/?url=${encodeURIComponent(model.baseUrl)}`,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an error is likely a CORS error.
|
||||
*
|
||||
* CORS errors in browsers typically manifest as:
|
||||
* - TypeError with message "Failed to fetch"
|
||||
* - NetworkError
|
||||
*
|
||||
* @param error - The error to check
|
||||
* @returns true if error is likely a CORS error
|
||||
*/
|
||||
export function isCorsError(error: unknown): boolean {
|
||||
if (!(error instanceof Error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for common CORS error patterns
|
||||
const message = error.message.toLowerCase();
|
||||
|
||||
// "Failed to fetch" is the standard CORS error in most browsers
|
||||
if (error.name === "TypeError" && message.includes("failed to fetch")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Some browsers report "NetworkError"
|
||||
if (error.name === "NetworkError") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// CORS-specific messages
|
||||
if (message.includes("cors") || message.includes("cross-origin")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue