Files
edgeever/apps/api/src/ai-runtime.ts
T
tianma-if f5e9bc1a44 feat(ai): call model APIs from the client instead of the worker
Keep Cloudflare requests short for Agent, tag suggestions, plugin generate, and Paw checks. The instance only prepares credentials and executes tools; CORS failures still fall back to the existing proxy.
2026-09-16 22:45:07 +08:00

67 lines
2.2 KiB
TypeScript

import { createAnthropic } from "@ai-sdk/anthropic";
import { createGoogle } from "@ai-sdk/google";
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
import { buildAiTagSuggestionRequest, parseAiTagSuggestionNames, type AiProvider } from "@edgeever/shared";
export { parseAiTagSuggestionNames } from "@edgeever/shared";
import { generateText, streamText } from "ai";
export const openaiCompatibleHeaders = (baseUrl: string) => {
try {
const host = new URL(baseUrl).hostname.toLowerCase();
if (host === "openrouter.ai" || host.endsWith(".openrouter.ai")) {
return { "HTTP-Referer": "https://edgeever.org", "X-Title": "EdgeEver" };
}
} catch {
// Invalid URLs are rejected by the provider factory.
}
};
export const createAiModel = (config: {
provider: AiProvider;
baseUrl: string;
apiKey: string;
modelId: string;
}) => {
switch (config.provider) {
case "anthropic":
return createAnthropic({ baseURL: config.baseUrl, apiKey: config.apiKey })(config.modelId);
case "google":
return createGoogle({ baseURL: config.baseUrl, apiKey: config.apiKey })(config.modelId);
default:
return createOpenAICompatible({
name: "edgeever-openai-compatible",
baseURL: config.baseUrl,
apiKey: config.apiKey,
includeUsage: true,
headers: openaiCompatibleHeaders(config.baseUrl),
})(config.modelId);
}
};
export const generateAiText = (...args: Parameters<typeof generateText>) => generateText(...args);
export const streamAiText = (...args: Parameters<typeof streamText>) => streamText(...args);
export const generateAiTagSuggestionNames = async (input: {
model: ReturnType<typeof createAiModel>;
instruction: string;
title: string;
contentMarkdown: string;
currentTags: string[];
existingTags: string[];
locale?: string;
abortSignal?: AbortSignal;
}) => {
const fields = buildAiTagSuggestionRequest(input);
const result = await generateText({
model: input.model,
system: fields.system,
prompt: fields.prompt,
maxOutputTokens: fields.maxOutputTokens,
temperature: fields.temperature,
abortSignal: input.abortSignal,
});
return parseAiTagSuggestionNames(result.text);
};