mirror of
https://github.com/harivansh-afk/clanker-agent.git
synced 2026-04-18 08:02:52 +00:00
move pi-mono into companion-cloud as apps/companion-os
- Copy all pi-mono source into apps/companion-os/ - Update Dockerfile to COPY pre-built binary instead of downloading from GitHub Releases - Update deploy-staging.yml to build pi from source (bun compile) before Docker build - Add apps/companion-os/** to path triggers - No more cross-repo dispatch needed Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
0250f72976
579 changed files with 206942 additions and 0 deletions
150
packages/web-ui/src/utils/proxy-utils.ts
Normal file
150
packages/web-ui/src/utils/proxy-utils.ts
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
import type {
|
||||
Api,
|
||||
Context,
|
||||
Model,
|
||||
SimpleStreamOptions,
|
||||
} from "@mariozechner/pi-ai";
|
||||
import { streamSimple } 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a streamFn that applies CORS proxy when needed.
|
||||
* Reads proxy settings from storage on each call.
|
||||
*
|
||||
* @param getProxyUrl - Async function to get current proxy URL (or undefined if disabled)
|
||||
* @returns A streamFn compatible with Agent's streamFn option
|
||||
*/
|
||||
export function createStreamFn(getProxyUrl: () => Promise<string | undefined>) {
|
||||
return async (
|
||||
model: Model<any>,
|
||||
context: Context,
|
||||
options?: SimpleStreamOptions,
|
||||
) => {
|
||||
const apiKey = options?.apiKey;
|
||||
const proxyUrl = await getProxyUrl();
|
||||
|
||||
if (!apiKey || !proxyUrl) {
|
||||
return streamSimple(model, context, options);
|
||||
}
|
||||
|
||||
const proxiedModel = applyProxyIfNeeded(model, apiKey, proxyUrl);
|
||||
return streamSimple(proxiedModel, context, options);
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue