Files
ttstd-infinite-canvas/web/src/services/api/custom-channel-relay.ts

62 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { projectDesktopLocalChannelRuntime } from "@/lib/desktop-local-channel";
import { isSystemProxyBaseUrl, resolveBackendApiUrl, type AiConfig, type ChannelHeader } from "@/stores/use-config-store";
type RelayConfig = Pick<AiConfig, "baseUrl" | "apiKey" | "apiFormat"> & { allowLocalChannel?: boolean; headers?: ChannelHeader[] };
export type ChannelRequest = {
url: string;
headers: Record<string, string>;
credentials: RequestCredentials;
};
/** 自定义渠道统一经登录态后端中转,避免依赖第三方服务的浏览器 CORS。 */
export function channelRequest(config: RelayConfig, upstreamUrl: string, headers: HeadersInit = {}): ChannelRequest {
const runtimeConfig = projectDesktopLocalChannelRuntime(config);
const normalizedHeaders = new Headers(headers);
if (isSystemProxyBaseUrl(runtimeConfig.baseUrl)) {
return { url: upstreamUrl, headers: Object.fromEntries(normalizedHeaders.entries()), credentials: "include" };
}
const normalizedBaseUrl = requireHttpUrl(runtimeConfig.baseUrl, "当前模型渠道 Base URL");
const normalizedUpstreamUrl = requireHttpUrl(upstreamUrl, "当前模型请求地址");
normalizedHeaders.delete("X-Canvas-Upstream-Headers");
normalizedHeaders.delete("x-goog-api-key");
normalizedHeaders.set("Authorization", `Bearer ${runtimeConfig.apiKey}`);
normalizedHeaders.set("X-Canvas-Upstream-URL", normalizedUpstreamUrl);
normalizedHeaders.set("X-Canvas-Upstream-Format", runtimeConfig.apiFormat === "gemini" ? "gemini" : "openai");
if (runtimeConfig.allowLocalChannel === true) {
normalizedHeaders.set("X-Canvas-Allow-Local-Channel", "1");
normalizedHeaders.set("X-Canvas-Upstream-Base-URL", normalizedBaseUrl);
} else {
normalizedHeaders.delete("X-Canvas-Allow-Local-Channel");
normalizedHeaders.delete("X-Canvas-Upstream-Base-URL");
}
if (runtimeConfig.headers?.length) normalizedHeaders.set("X-Canvas-Upstream-Headers", encodeChannelHeaders(runtimeConfig.headers));
return {
url: resolveBackendApiUrl("/api/ai/custom"),
headers: Object.fromEntries(normalizedHeaders.entries()),
credentials: "include",
};
}
function requireHttpUrl(value: string, label: string) {
const normalized = value.trim();
let parsed: URL;
try {
parsed = new URL(normalized);
} catch {
throw new Error(`${label} 无效,请填写完整地址,例如:https://api.example.com/v1`);
}
if (!parsed.hostname || (parsed.protocol !== "http:" && parsed.protocol !== "https:")) {
throw new Error(`${label} 无效,请填写完整地址,例如:https://api.example.com/v1`);
}
return parsed.toString();
}
function encodeChannelHeaders(headers: ChannelHeader[]) {
const bytes = new TextEncoder().encode(JSON.stringify(headers));
let binary = "";
for (const byte of bytes) binary += String.fromCharCode(byte);
return btoa(binary);
}