- 新增 StepFun Plan 套餐预设 - 供应商编辑页 API Key 下方添加获取 Key 链接(支持推广链接) - 修复链接无法打开浏览器的问题(Rust open_external_url) - 无推广链接时回退打开官网 - 必填字段(供应商名称/API Key/请求地址)添加红色 * 标注 - 用户自定义预设方案文档
577 lines
18 KiB
TypeScript
577 lines
18 KiB
TypeScript
// Provider presets: one-click form filling for well-known providers.
|
|
// Preset data structure inspired by cc-switch (MIT, © Jason Young)
|
|
// https://github.com/farion1231/cc-switch
|
|
|
|
import type { Model, Provider, ProviderType } from "../types";
|
|
import { getModelRef, capabilitiesFromRef } from "../lib/models-dev";
|
|
import { getDefaultMaxContextSize } from "../lib/model-defaults";
|
|
|
|
/** Usage query kinds supported by the Rust usage layer (v1 union). */
|
|
export type UsageKind =
|
|
| "balance:deepseek"
|
|
| "balance:siliconflow"
|
|
| "balance:openrouter"
|
|
| "balance:stepfun"
|
|
| "balance:novita"
|
|
| "plan:kimi_coding"
|
|
| "plan:zhipu"
|
|
| "plan:minimax";
|
|
|
|
/** Billing model for a preset. Drives the tab filter in PresetPickerModal. */
|
|
export type BillingMode = "subscription" | "pay_as_you_go";
|
|
|
|
/** Runtime set used to catch TS/Rust enum drift (see dev-assert below). */
|
|
export const SUPPORTED_USAGE_KINDS: ReadonlySet<string> = new Set<UsageKind>([
|
|
"balance:deepseek",
|
|
"balance:siliconflow",
|
|
"balance:openrouter",
|
|
"balance:stepfun",
|
|
"balance:novita",
|
|
"plan:kimi_coding",
|
|
"plan:zhipu",
|
|
"plan:minimax",
|
|
]);
|
|
|
|
export type PresetCategory =
|
|
| "official"
|
|
| "cn_official"
|
|
| "third_party"
|
|
| "aggregator"
|
|
| "custom";
|
|
|
|
export interface ProviderPreset {
|
|
/** Unique key, also used as the default Provider name, e.g. "deepseek". */
|
|
id: string;
|
|
/** Fallback display name (English). */
|
|
name: string;
|
|
/** i18n key; when present the UI prefers t(nameKey). */
|
|
nameKey?: string;
|
|
/** Official website / console URL. */
|
|
websiteUrl?: string;
|
|
/** Where to create / fetch an API key; shown as a link under the key input. */
|
|
apiKeyUrl?: string;
|
|
/** Category: sorting & badge. */
|
|
category: PresetCategory;
|
|
/** Written directly to provider.provider_type. */
|
|
providerType: ProviderType;
|
|
/** Preset base_url; `null` means no default (e.g. Anthropic). */
|
|
baseUrl: string | null;
|
|
/** Icon key from src/icons/extracted (falls back to initials when absent). */
|
|
icon?: string;
|
|
iconColor?: string;
|
|
/**
|
|
* Pre-filled model mappings. The first entry becomes the default model.
|
|
* `model` holds the real model id; the alias is NOT hand-written here —
|
|
* presetToProviderAndModels() forces `${providerName}/${modelId}` so that
|
|
* handleDuplicateProvider's `alias.slice(name.length)` keeps working.
|
|
*/
|
|
models: Array<{
|
|
/** Real model id sent to the provider API. */
|
|
model: string;
|
|
/** Optional display name override; defaults to models.dev derivation. */
|
|
displayName?: string;
|
|
/** Optional context override; defaults to the priority chain below. */
|
|
maxContextSize?: number;
|
|
/** Defaults to ["thinking"]. */
|
|
capabilities?: string[];
|
|
}>;
|
|
/**
|
|
* Billing model — drives the 套餐/按量 tab in PresetPickerModal.
|
|
* - "subscription": coding-plan / token-plan style subscription (quota-based)
|
|
* - "pay_as_you_go": per-token API billing
|
|
*/
|
|
billingMode: BillingMode;
|
|
/**
|
|
* Billing/usage query kinds; omitted means "do not query".
|
|
* Persisted to SQLite settings by the Rust side on save, never to
|
|
* config.toml.
|
|
*/
|
|
usageKinds?: ReadonlyArray<UsageKind>;
|
|
}
|
|
|
|
export const providerPresets: ProviderPreset[] = [
|
|
{
|
|
id: "anthropic",
|
|
name: "Anthropic",
|
|
nameKey: "presetNameAnthropic",
|
|
websiteUrl: "https://www.anthropic.com",
|
|
category: "official",
|
|
providerType: "anthropic",
|
|
// No default base_url; Kimi Code automatically targets api.anthropic.com.
|
|
baseUrl: null,
|
|
icon: "anthropic",
|
|
models: [
|
|
{ model: "claude-opus-4-5" },
|
|
{ model: "claude-sonnet-4-5" },
|
|
{ model: "claude-haiku-4-5" },
|
|
],
|
|
billingMode: "pay_as_you_go",
|
|
},
|
|
{
|
|
id: "kimi-coding",
|
|
name: "Kimi For Coding",
|
|
nameKey: "presetNameKimiCoding",
|
|
websiteUrl: "https://www.kimi.com/code",
|
|
apiKeyUrl: "https://kimi-bot.com/activities/zh-cn/invite/share?scenario=invite&from=share_poster&invitation_code=6UJX7J",
|
|
category: "official",
|
|
providerType: "kimi",
|
|
baseUrl: "https://api.kimi.com/coding/v1",
|
|
icon: "kimi",
|
|
models: [
|
|
// kimi-for-coding is not in the models.dev snapshot; context comes
|
|
// from the regex fallback in model-defaults (262144).
|
|
{ model: "kimi-for-coding", displayName: "Kimi For Coding" },
|
|
{ model: "kimi-k2.7-code" },
|
|
],
|
|
billingMode: "subscription",
|
|
usageKinds: ["plan:kimi_coding"],
|
|
},
|
|
{
|
|
id: "moonshot",
|
|
name: "Moonshot AI",
|
|
nameKey: "presetNameMoonshot",
|
|
websiteUrl: "https://platform.moonshot.ai",
|
|
category: "cn_official",
|
|
providerType: "kimi",
|
|
baseUrl: "https://api.moonshot.ai/v1",
|
|
icon: "kimi",
|
|
models: [
|
|
{ model: "kimi-k2.7-code" },
|
|
{ model: "kimi-k2-thinking-turbo" },
|
|
{ model: "kimi-k2.6" },
|
|
],
|
|
billingMode: "pay_as_you_go",
|
|
},
|
|
{
|
|
id: "deepseek",
|
|
name: "DeepSeek",
|
|
nameKey: "presetNameDeepseek",
|
|
websiteUrl: "https://platform.deepseek.com",
|
|
category: "cn_official",
|
|
providerType: "openai",
|
|
baseUrl: "https://api.deepseek.com/v1",
|
|
icon: "deepseek",
|
|
models: [{ model: "deepseek-chat" }, { model: "deepseek-reasoner" }],
|
|
billingMode: "pay_as_you_go",
|
|
usageKinds: ["balance:deepseek"],
|
|
},
|
|
{
|
|
id: "zhipu-api",
|
|
name: "Zhipu GLM (API)",
|
|
nameKey: "presetNameZhipuApi",
|
|
websiteUrl: "https://open.bigmodel.cn",
|
|
category: "cn_official",
|
|
providerType: "openai",
|
|
baseUrl: "https://open.bigmodel.cn/api/paas/v4",
|
|
icon: "zhipu",
|
|
models: [
|
|
{ model: "glm-4.7" },
|
|
{ model: "glm-4.6" },
|
|
{ model: "glm-4.5-air" },
|
|
],
|
|
billingMode: "pay_as_you_go",
|
|
},
|
|
{
|
|
id: "zhipu-coding",
|
|
name: "Zhipu GLM Coding Plan",
|
|
nameKey: "presetNameZhipuCoding",
|
|
websiteUrl: "https://open.bigmodel.cn",
|
|
category: "cn_official",
|
|
providerType: "openai",
|
|
// Coding Plan endpoint is separate from the pay-as-you-go PaaS API.
|
|
baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
|
|
icon: "zhipu",
|
|
models: [
|
|
{ model: "glm-4.7" },
|
|
{ model: "glm-4.6" },
|
|
{ model: "glm-4.5-air" },
|
|
],
|
|
billingMode: "subscription",
|
|
usageKinds: ["plan:zhipu"],
|
|
},
|
|
{
|
|
id: "zai-api",
|
|
name: "z.ai (API)",
|
|
nameKey: "presetNameZaiApi",
|
|
websiteUrl: "https://z.ai",
|
|
category: "third_party",
|
|
providerType: "openai",
|
|
baseUrl: "https://api.z.ai/api/paas/v4",
|
|
icon: "zhipu",
|
|
models: [{ model: "glm-4.7" }, { model: "glm-4.6" }],
|
|
billingMode: "pay_as_you_go",
|
|
},
|
|
{
|
|
id: "zai-coding",
|
|
name: "z.ai Coding Plan",
|
|
nameKey: "presetNameZaiCoding",
|
|
websiteUrl: "https://z.ai",
|
|
category: "third_party",
|
|
providerType: "openai",
|
|
// Coding Plan endpoint (mirror of Zhipu GLM Coding Plan). If the path
|
|
// differs on z.ai, fall back to a known mirror.
|
|
baseUrl: "https://api.z.ai/api/coding/paas/v4",
|
|
icon: "zhipu",
|
|
models: [{ model: "glm-4.7" }, { model: "glm-4.6" }],
|
|
billingMode: "subscription",
|
|
usageKinds: ["plan:zhipu"],
|
|
},
|
|
{
|
|
id: "bailian",
|
|
name: "Alibaba Bailian",
|
|
nameKey: "presetNameBailian",
|
|
websiteUrl: "https://bailian.console.aliyun.com",
|
|
category: "cn_official",
|
|
providerType: "openai",
|
|
baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
icon: "bailian",
|
|
models: [
|
|
{ model: "qwen3-max" },
|
|
{ model: "qwen3-coder-plus" },
|
|
{ model: "qwen3.5-plus" },
|
|
],
|
|
billingMode: "pay_as_you_go",
|
|
},
|
|
{
|
|
id: "minimax",
|
|
name: "MiniMax",
|
|
nameKey: "presetNameMinimax",
|
|
websiteUrl: "https://platform.minimaxi.com",
|
|
category: "cn_official",
|
|
providerType: "openai",
|
|
baseUrl: "https://api.minimaxi.com/v1",
|
|
icon: "minimax",
|
|
models: [{ model: "MiniMax-M3" }, { model: "MiniMax-M2.7" }],
|
|
billingMode: "pay_as_you_go",
|
|
usageKinds: ["plan:minimax"],
|
|
},
|
|
{
|
|
id: "minimax-token-plan",
|
|
name: "MiniMax Token Plan",
|
|
nameKey: "presetNameMinimaxTokenPlan",
|
|
websiteUrl: "https://platform.minimaxi.com",
|
|
apiKeyUrl: "https://platform.minimaxi.com/subscribe/token-plan?code=GmmZA629b5&source=link",
|
|
category: "cn_official",
|
|
providerType: "openai",
|
|
// Token Plan is sold against the same platform endpoint as the
|
|
// pay-as-you-go API — only the API key differs. The Rust detect
|
|
// routes api.minimaxi.com to plan:minimax, so this preset picks up
|
|
// the same query path; the `subscription` tag helps the user pick
|
|
// the right entry based on which key they hold.
|
|
baseUrl: "https://api.minimaxi.com/v1",
|
|
icon: "minimax",
|
|
models: [{ model: "MiniMax-M3" }, { model: "MiniMax-M2.7" }],
|
|
billingMode: "subscription",
|
|
usageKinds: ["plan:minimax"],
|
|
},
|
|
{
|
|
id: "stepfun",
|
|
name: "StepFun",
|
|
nameKey: "presetNameStepfun",
|
|
websiteUrl: "https://platform.stepfun.com",
|
|
category: "cn_official",
|
|
providerType: "openai",
|
|
baseUrl: "https://api.stepfun.com/v1",
|
|
icon: "stepfun",
|
|
models: [{ model: "step-3.7-flash" }, { model: "step-3.5-flash" }],
|
|
billingMode: "pay_as_you_go",
|
|
usageKinds: ["balance:stepfun"],
|
|
},
|
|
{
|
|
id: "stepfun-plan",
|
|
name: "StepFun Plan",
|
|
nameKey: "presetNameStepfunPlan",
|
|
websiteUrl: "https://platform.stepfun.com/step-plan",
|
|
apiKeyUrl: "https://platform.stepfun.com/?invite_code=HSIGHTPS",
|
|
category: "cn_official",
|
|
providerType: "openai",
|
|
// Step Plan endpoint — same host, different path prefix than the
|
|
// pay-as-you-go API (/v1). Serves both OpenAI and Anthropic protocols.
|
|
baseUrl: "https://api.stepfun.com/step_plan/v1",
|
|
icon: "stepfun",
|
|
models: [{ model: "step-3.7-flash" }, { model: "step-3.5-flash" }],
|
|
billingMode: "subscription",
|
|
},
|
|
{
|
|
id: "siliconflow",
|
|
name: "SiliconFlow",
|
|
nameKey: "presetNameSiliconflow",
|
|
websiteUrl: "https://siliconflow.cn",
|
|
category: "cn_official",
|
|
providerType: "openai",
|
|
baseUrl: "https://api.siliconflow.cn/v1",
|
|
icon: "siliconflow",
|
|
// The extracted icon has no metadata defaultColor; set it explicitly.
|
|
iconColor: "#6E29F6",
|
|
models: [
|
|
{ model: "Qwen/Qwen3-235B-A22B" },
|
|
{ model: "moonshotai/Kimi-K2.5" },
|
|
// Not covered by the snapshot; fill context/displayName explicitly.
|
|
{
|
|
model: "deepseek-ai/DeepSeek-V3.2",
|
|
displayName: "DeepSeek V3.2",
|
|
maxContextSize: 163840,
|
|
},
|
|
],
|
|
usageKinds: ["balance:siliconflow"],
|
|
billingMode: "pay_as_you_go",
|
|
},
|
|
{
|
|
id: "novita",
|
|
name: "Novita AI",
|
|
nameKey: "presetNameNovita",
|
|
websiteUrl: "https://novita.ai",
|
|
category: "third_party",
|
|
providerType: "openai",
|
|
// Note: /v3, not /v1.
|
|
baseUrl: "https://api.novita.ai/v3",
|
|
icon: "novita",
|
|
models: [
|
|
// Not covered by the snapshot; fill context/displayName explicitly.
|
|
{
|
|
model: "deepseek/deepseek-v3.2",
|
|
displayName: "DeepSeek V3.2",
|
|
maxContextSize: 163840,
|
|
},
|
|
{ model: "moonshotai/kimi-k2-thinking" },
|
|
{ model: "zai-org/glm-4.6" },
|
|
],
|
|
usageKinds: ["balance:novita"],
|
|
billingMode: "pay_as_you_go",
|
|
},
|
|
{
|
|
id: "openrouter",
|
|
name: "OpenRouter",
|
|
nameKey: "presetNameOpenrouter",
|
|
websiteUrl: "https://openrouter.ai",
|
|
category: "aggregator",
|
|
providerType: "openai",
|
|
baseUrl: "https://openrouter.ai/api/v1",
|
|
icon: "openrouter",
|
|
models: [
|
|
// OpenRouter uses dotted versions which the snapshot does not index;
|
|
// fill context/displayName explicitly for this one.
|
|
{
|
|
model: "anthropic/claude-opus-4.5",
|
|
displayName: "Claude Opus 4.5",
|
|
maxContextSize: 200000,
|
|
},
|
|
{ model: "openai/gpt-5.2" },
|
|
{ model: "google/gemini-3-pro-preview" },
|
|
],
|
|
usageKinds: ["balance:openrouter"],
|
|
billingMode: "pay_as_you_go",
|
|
},
|
|
{
|
|
id: "openai",
|
|
name: "OpenAI",
|
|
nameKey: "presetNameOpenai",
|
|
websiteUrl: "https://platform.openai.com",
|
|
category: "official",
|
|
providerType: "openai",
|
|
baseUrl: "https://api.openai.com/v1",
|
|
icon: "openai",
|
|
models: [
|
|
{ model: "gpt-5.2" },
|
|
{ model: "gpt-5.1-codex-max" },
|
|
{ model: "gpt-5-mini" },
|
|
],
|
|
billingMode: "pay_as_you_go",
|
|
},
|
|
{
|
|
id: "google-genai",
|
|
name: "Google AI Studio",
|
|
nameKey: "presetNameGoogleGenai",
|
|
websiteUrl: "https://aistudio.google.com",
|
|
category: "official",
|
|
providerType: "google-genai",
|
|
baseUrl: "https://generativelanguage.googleapis.com",
|
|
icon: "google",
|
|
models: [
|
|
{ model: "gemini-3-pro-preview" },
|
|
{ model: "gemini-3-flash-preview" },
|
|
{ model: "gemini-2.5-flash" },
|
|
],
|
|
billingMode: "pay_as_you_go",
|
|
},
|
|
{
|
|
id: "volcengine",
|
|
name: "Volcengine Ark",
|
|
nameKey: "presetNameVolcengine",
|
|
websiteUrl: "https://www.volcengine.com/product/ark",
|
|
category: "cn_official",
|
|
providerType: "openai",
|
|
baseUrl: "https://ark.cn-beijing.volces.com/api/v3",
|
|
icon: "huoshan",
|
|
// v1: inference only — plan queries need separate AK/SK (P2).
|
|
models: [
|
|
{
|
|
model: "doubao-seed-1-6-250615",
|
|
displayName: "Doubao Seed 1.6",
|
|
},
|
|
{ model: "kimi-k2-250905", displayName: "Kimi K2" },
|
|
],
|
|
billingMode: "pay_as_you_go",
|
|
},
|
|
{
|
|
id: "opencode-go",
|
|
name: "OpenCode Go",
|
|
nameKey: "presetNameOpencodeGo",
|
|
// Referral link — supports the OpenCode project
|
|
websiteUrl: "https://opencode.ai/go?ref=DFCNADQCEM",
|
|
apiKeyUrl: "https://opencode.ai/go?ref=DFCNADQCEM",
|
|
category: "third_party",
|
|
providerType: "openai",
|
|
baseUrl: "https://opencode.ai/zen/go/v1",
|
|
icon: "opencode",
|
|
models: [
|
|
{ model: "grok-4.5", displayName: "Grok 4.5" },
|
|
{ model: "glm-5.2", displayName: "GLM-5.2" },
|
|
{ model: "glm-5.1", displayName: "GLM-5.1" },
|
|
{ model: "kimi-k3", displayName: "Kimi K3" },
|
|
{ model: "kimi-k2.7-code", displayName: "Kimi K2.7 Code" },
|
|
{ model: "kimi-k2.6", displayName: "Kimi K2.6" },
|
|
{ model: "deepseek-v4-pro", displayName: "DeepSeek V4 Pro" },
|
|
{ model: "deepseek-v4-flash", displayName: "DeepSeek V4 Flash" },
|
|
],
|
|
billingMode: "subscription",
|
|
},
|
|
{
|
|
id: "opencode-zen",
|
|
name: "OpenCode Zen",
|
|
nameKey: "presetNameOpencodeZen",
|
|
websiteUrl: "https://opencode.ai/zen/",
|
|
apiKeyUrl: "https://opencode.ai/zen/",
|
|
category: "third_party",
|
|
providerType: "openai",
|
|
baseUrl: "https://opencode.ai/zen/v1",
|
|
icon: "opencode",
|
|
models: [
|
|
{ model: "claude-opus-5", displayName: "Claude Opus 5" },
|
|
{ model: "claude-sonnet-5", displayName: "Claude Sonnet 5" },
|
|
{ model: "gpt-5.5", displayName: "GPT 5.5" },
|
|
{ model: "gemini-3.6-flash", displayName: "Gemini 3.6 Flash" },
|
|
{ model: "grok-4.5", displayName: "Grok 4.5" },
|
|
{ model: "deepseek-v4-pro", displayName: "DeepSeek V4 Pro" },
|
|
],
|
|
billingMode: "pay_as_you_go",
|
|
},
|
|
];
|
|
|
|
export interface PresetConversionResult {
|
|
provider: Provider;
|
|
models: Model[];
|
|
/** Alias to write into config.default_model ("" when no models). */
|
|
defaultModel: string;
|
|
usageKinds: ReadonlyArray<string> | undefined;
|
|
}
|
|
|
|
/**
|
|
* Convert a preset into concrete Provider + Model entries.
|
|
*
|
|
* - Provider name collisions get a "-2" / "-3" suffix.
|
|
* - Model aliases are forced to `${name}/${modelId}` and deduped one by one
|
|
* against existing aliases (also with "-2" / "-3" suffixes).
|
|
* - max_context_size priority: preset override → models.dev snapshot →
|
|
* regex rules → DEFAULT_MAX_CONTEXT_SIZE (the last two are both handled
|
|
* inside getDefaultMaxContextSize).
|
|
*/
|
|
export function presetToProviderAndModels(
|
|
preset: ProviderPreset,
|
|
options: {
|
|
existingProviderNames: Set<string>;
|
|
existingModelAliases: Set<string>;
|
|
},
|
|
): PresetConversionResult {
|
|
let name = preset.id;
|
|
let n = 2;
|
|
while (options.existingProviderNames.has(name)) {
|
|
name = `${preset.id}-${n}`;
|
|
n++;
|
|
}
|
|
|
|
const models: Model[] = preset.models.map((m) => {
|
|
let alias = `${name}/${m.model}`;
|
|
let k = 2;
|
|
while (options.existingModelAliases.has(alias)) {
|
|
alias = `${name}/${m.model}-${k}`;
|
|
k++;
|
|
}
|
|
|
|
const ref = getModelRef(m.model);
|
|
const refCaps = ref ? capabilitiesFromRef(ref) : [];
|
|
const maxContextSize =
|
|
m.maxContextSize ?? ref?.context ?? getDefaultMaxContextSize(m.model);
|
|
|
|
return {
|
|
alias,
|
|
provider: name,
|
|
model: m.model,
|
|
max_context_size: maxContextSize,
|
|
display_name: m.displayName ?? ref?.name ?? alias,
|
|
capabilities: m.capabilities ?? (refCaps.length > 0 ? refCaps : ["thinking"]),
|
|
supports_1m: maxContextSize >= 1_000_000,
|
|
raw_other: {},
|
|
};
|
|
});
|
|
|
|
const provider: Provider = {
|
|
name,
|
|
provider_type: preset.providerType,
|
|
base_url: preset.baseUrl,
|
|
api_key: null,
|
|
env: {},
|
|
note: null,
|
|
official_url: preset.websiteUrl ?? null,
|
|
managed: false,
|
|
enabled: true,
|
|
icon: preset.icon ?? null,
|
|
icon_color: preset.iconColor ?? null,
|
|
usageKinds: preset.usageKinds ? [...preset.usageKinds] : undefined,
|
|
raw_other: {},
|
|
};
|
|
|
|
return {
|
|
provider,
|
|
models,
|
|
defaultModel: models[0]?.alias ?? "",
|
|
usageKinds: preset.usageKinds,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Best-effort reverse lookup: which preset (if any) a Provider was created
|
|
* from. Matches the provider name with any "-2"/"-3" dedup suffix stripped,
|
|
* falling back to an exact official_url match. Used to surface preset-only
|
|
* metadata (apiKeyUrl / referralUrl) in the edit form without persisting
|
|
* extra fields on Provider.
|
|
*/
|
|
export function findPresetForProvider(provider: Provider): ProviderPreset | undefined {
|
|
const base = provider.name.replace(/-\d+$/, "");
|
|
return (
|
|
providerPresets.find((p) => p.id === base) ??
|
|
providerPresets.find((p) => p.websiteUrl != null && p.websiteUrl === provider.official_url)
|
|
);
|
|
}
|
|
|
|
// Dev-only cross-end drift check: assert that every usage kind referenced by
|
|
// any preset is one the Rust `UsageKind` enum knows how to handle. The
|
|
// runtime filter in `query_provider_usage` silently drops unknown kinds,
|
|
// so without this guard a typo or schema drift would only surface as a
|
|
// missing usage footer with no error message. Gated by Vite's DEV flag so
|
|
// it costs nothing in production bundles.
|
|
if (import.meta.env.DEV) {
|
|
for (const preset of providerPresets) {
|
|
if (!preset.usageKinds) continue;
|
|
for (const kind of preset.usageKinds) {
|
|
if (!SUPPORTED_USAGE_KINDS.has(kind)) {
|
|
// eslint-disable-next-line no-console
|
|
console.error(
|
|
`[providerPresets] preset "${preset.id}" references unknown usage kind "${kind}". ` +
|
|
`Add it to src-tauri/src/services/mod.rs UsageKind + SUPPORTED_USAGE_KINDS, or remove it from the preset.`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|