refactor(ai-client): extract shared createLanguageModel helper

De-duplicate the provider switch logic that was identical in chat.ts
and vision.ts into a shared model.ts module.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
yuanzonghao
2026-06-07 11:52:20 +08:00
parent 57bc6556ab
commit f4aca0b59c
3 changed files with 30 additions and 50 deletions
+3 -24
View File
@@ -1,10 +1,7 @@
import { generateText } from "ai"; import { generateText } from "ai";
import type { LanguageModelUsage, ModelMessage } from "ai"; import type { LanguageModelUsage, ModelMessage } from "ai";
import { createAnthropic } from "@ai-sdk/anthropic"; import type { ProviderConfig } from "@infiplot/types";
import { createGoogleGenerativeAI } from "@ai-sdk/google"; import { createLanguageModel, resolveProtocol } from "./model";
import { createOpenAI } from "@ai-sdk/openai";
import type { ProviderConfig, ProviderProtocol } from "@infiplot/types";
import { normalizeBaseUrl } from "./normalizeUrl";
export type ChatMessage = { export type ChatMessage = {
role: "system" | "user" | "assistant"; role: "system" | "user" | "assistant";
@@ -31,24 +28,6 @@ function summarizeSdkUsage(
return `[cache] ${tag} input=${input} completion=${output} (provider didn't report cache stats)`; return `[cache] ${tag} input=${input} completion=${output} (provider didn't report cache stats)`;
} }
function resolveTextProtocol(config: ProviderConfig): ProviderProtocol {
return config.provider ?? "openai_compatible";
}
function createLanguageModel(config: ProviderConfig, protocol: ProviderProtocol) {
const baseURL = normalizeBaseUrl(config.baseUrl, protocol);
switch (protocol) {
case "anthropic":
return createAnthropic({ apiKey: config.apiKey, baseURL })(config.model);
case "google":
return createGoogleGenerativeAI({ apiKey: config.apiKey, baseURL })(config.model);
case "openai_compatible":
case "openai":
default:
return createOpenAI({ apiKey: config.apiKey, baseURL }).chat(config.model);
}
}
export async function chat( export async function chat(
config: ProviderConfig, config: ProviderConfig,
messages: ChatMessage[], messages: ChatMessage[],
@@ -57,7 +36,7 @@ export async function chat(
tag?: string; tag?: string;
}, },
): Promise<string> { ): Promise<string> {
const protocol = resolveTextProtocol(config); const protocol = resolveProtocol(config);
const model = createLanguageModel(config, protocol); const model = createLanguageModel(config, protocol);
const system = messages.find((m) => m.role === "system")?.content; const system = messages.find((m) => m.role === "system")?.content;
+23
View File
@@ -0,0 +1,23 @@
import { createAnthropic } from "@ai-sdk/anthropic";
import { createGoogleGenerativeAI } from "@ai-sdk/google";
import { createOpenAI } from "@ai-sdk/openai";
import type { ProviderConfig, ProviderProtocol } from "@infiplot/types";
import { normalizeBaseUrl } from "./normalizeUrl";
export function resolveProtocol(config: ProviderConfig): ProviderProtocol {
return config.provider ?? "openai_compatible";
}
export function createLanguageModel(config: ProviderConfig, protocol: ProviderProtocol) {
const baseURL = normalizeBaseUrl(config.baseUrl, protocol);
switch (protocol) {
case "anthropic":
return createAnthropic({ apiKey: config.apiKey, baseURL })(config.model);
case "google":
return createGoogleGenerativeAI({ apiKey: config.apiKey, baseURL })(config.model);
case "openai_compatible":
case "openai":
default:
return createOpenAI({ apiKey: config.apiKey, baseURL }).chat(config.model);
}
}
+4 -26
View File
@@ -1,10 +1,7 @@
import { generateText } from "ai"; import { generateText } from "ai";
import type { ModelMessage } from "ai"; import type { ModelMessage } from "ai";
import { createAnthropic } from "@ai-sdk/anthropic"; import type { ProviderConfig } from "@infiplot/types";
import { createGoogleGenerativeAI } from "@ai-sdk/google"; import { createLanguageModel, resolveProtocol } from "./model";
import { createOpenAI } from "@ai-sdk/openai";
import type { ProviderConfig, ProviderProtocol } from "@infiplot/types";
import { normalizeBaseUrl } from "./normalizeUrl";
const VISION_TIMEOUT_MS = 60_000; const VISION_TIMEOUT_MS = 60_000;
@@ -20,32 +17,13 @@ export async function interpretClick(
); );
} }
function resolveVisionProtocol(config: ProviderConfig): ProviderProtocol {
return config.provider ?? "openai_compatible";
}
export async function analyzeImageDataUrl( export async function analyzeImageDataUrl(
config: ProviderConfig, config: ProviderConfig,
imageDataUrl: string, imageDataUrl: string,
prompt: string, prompt: string,
): Promise<string> { ): Promise<string> {
const protocol = resolveVisionProtocol(config); const protocol = resolveProtocol(config);
const baseURL = normalizeBaseUrl(config.baseUrl, protocol); const model = createLanguageModel(config, protocol);
let model;
switch (protocol) {
case "anthropic":
model = createAnthropic({ apiKey: config.apiKey, baseURL })(config.model);
break;
case "google":
model = createGoogleGenerativeAI({ apiKey: config.apiKey, baseURL })(config.model);
break;
case "openai_compatible":
case "openai":
default:
model = createOpenAI({ apiKey: config.apiKey, baseURL }).chat(config.model);
break;
}
const messages: ModelMessage[] = [ const messages: ModelMessage[] = [
{ {