feat(muse): add Meta Muse provider with OAuth login and model catalog
This commit is contained in:
1 parent
2310ad4b14
commit
2880980709
15 files changed
+319
-14
No files matched your search
@@ -41,6 +41,9 @@ function applyAuth(headers, desc, credentials) {
|
||||
const HEADER_HOOKS = {
|
||||
// Stable device_id from OAuth connection (CLIProxyAPI KimiTokenStorage.DeviceID)
|
||||
kimiHeaders: (h, c) => Object.assign(h, buildKimiHeaders(c?.providerSpecificData?.deviceId)),
|
||||
// Muse: x-api-version only on subscription (minted key) requests — plain
|
||||
// Model API keys already work without it
|
||||
museHeaders: (h, c) => { if (c?.accessToken && !c?.apiKey) h["x-api-version"] = "1.0.0"; },
|
||||
clineHeaders: (h, c) => Object.assign(h, buildClineHeaders(c.apiKey || c.accessToken)),
|
||||
kilocodeOrg: (h, c) => { if (c.providerSpecificData?.orgId) h["X-Kilocode-OrganizationID"] = c.providerSpecificData.orgId; },
|
||||
};
|
||||
|
||||
@@ -156,6 +156,15 @@ export const MODEL_PRICING = {
|
||||
// === Grok ===
|
||||
"grok-code-fast-1": { input: 0.50, output: 2.00, cached: 0.25, reasoning: 3.00, cache_creation: 0.50 },
|
||||
|
||||
// === Muse (Meta Model API) ===
|
||||
// Rates from https://dev.meta.ai/docs/pricing-rate-limits (contributor tier:
|
||||
// cheaper, Meta may train on the data).
|
||||
"muse-spark-1.3": { input: 1.25, output: 4.25, cached: 0.15, reasoning: 4.25, cache_creation: 0 },
|
||||
"muse-spark-1.2": { input: 1.25, output: 4.25, cached: 0.15, reasoning: 4.25, cache_creation: 0 },
|
||||
"muse-spark-1.1": { input: 1.25, output: 4.25, cached: 0.15, reasoning: 4.25, cache_creation: 0 },
|
||||
"muse-spark-1.3-contributor": { input: 0.10, output: 0.20, cached: 0.002, reasoning: 0.20, cache_creation: 0 },
|
||||
"muse-spark-1.2-contributor": { input: 0.10, output: 0.20, cached: 0.002, reasoning: 0.20, cache_creation: 0 },
|
||||
|
||||
// === OpenRouter fallback ===
|
||||
"auto": { input: 2.00, output: 8.00, cached: 1.00, reasoning: 12.00, cache_creation: 2.00 },
|
||||
|
||||
|
||||
@@ -132,6 +132,7 @@ import p129 from "./agnes.js";
|
||||
import p130 from "./bai.js";
|
||||
import p131 from "./tinyfish.js";
|
||||
import p132 from "./v1m.js";
|
||||
import p133 from "./muse.js";
|
||||
export default [
|
||||
p0,
|
||||
p1,
|
||||
@@ -264,4 +265,5 @@ export default [
|
||||
p130,
|
||||
p131,
|
||||
p132,
|
||||
p133,
|
||||
];
|
||||
@@ -0,0 +1,67 @@
|
||||
// Muse (Meta Model API) — dual auth (same pattern as kimi):
|
||||
// oauth = Muse Code subscription (Meta account device code, mints an LLM|… key)
|
||||
// apikey = pay-as-you-go Model API key from dev.meta.ai
|
||||
// Transport is shared; oauth accounts get x-api-version via the museHeaders hook.
|
||||
// Meta issues no refresh token for the subscription flow → re-login on 401.
|
||||
export default {
|
||||
id: "muse",
|
||||
priority: 120,
|
||||
alias: "muse",
|
||||
aliases: [
|
||||
"muse-ai",
|
||||
"meta-model-api",
|
||||
"muse-code",
|
||||
"muse-subscription",
|
||||
],
|
||||
uiAlias: "muse",
|
||||
display: {
|
||||
name: "Muse (Meta Model API)",
|
||||
icon: "auto_awesome",
|
||||
color: "#0866FF",
|
||||
textIcon: "MU",
|
||||
website: "https://muse.ai",
|
||||
notice: {
|
||||
text: "Sign in with your Meta account (Muse Code subscription) or paste a Model API key from dev.meta.ai. Subscription keys are minted per account; Meta may train on contributor-tier data.",
|
||||
apiKeyUrl: "https://dev.meta.ai",
|
||||
signupUrl: "https://muse.ai",
|
||||
},
|
||||
},
|
||||
category: "oauth",
|
||||
authModes: ["oauth", "apikey"],
|
||||
hasOAuth: true,
|
||||
transport: {
|
||||
baseUrl: "https://api.meta.ai/v1/chat/completions",
|
||||
validateUrl: "https://api.meta.ai/v1/models",
|
||||
modelsUrl: "https://api.meta.ai/v1/models",
|
||||
auth: { combined: true, header: "Authorization", scheme: "bearer", hooks: ["museHeaders"] },
|
||||
},
|
||||
// Multi-endpoint: Meta accepts Chat Completions and Responses wire formats on
|
||||
// the same key (https://dev.meta.ai/docs/protocols). Muse Spark reasoning
|
||||
// (incl. encrypted_content replay) only round-trips on Responses, so models
|
||||
// pin targetFormat there.
|
||||
transports: [
|
||||
{
|
||||
format: "openai",
|
||||
baseUrl: "https://api.meta.ai/v1/chat/completions",
|
||||
auth: { combined: true, header: "Authorization", scheme: "bearer", hooks: ["museHeaders"] },
|
||||
},
|
||||
{
|
||||
format: "openai-responses",
|
||||
baseUrl: "https://api.meta.ai/v1/responses",
|
||||
auth: { combined: true, header: "Authorization", scheme: "bearer", hooks: ["museHeaders"] },
|
||||
},
|
||||
],
|
||||
models: [
|
||||
{ id: "muse-spark-1.3", name: "Muse Spark 1.3", targetFormat: "openai-responses", supportedFormats: ["openai-responses"] },
|
||||
{ id: "muse-spark-1.2", name: "Muse Spark 1.2", targetFormat: "openai-responses", supportedFormats: ["openai-responses"] },
|
||||
{ id: "muse-spark-1.1", name: "Muse Spark 1.1", targetFormat: "openai-responses", supportedFormats: ["openai-responses"] },
|
||||
{ id: "muse-spark-1.3-contributor", name: "Muse Spark 1.3 Contributor", targetFormat: "openai-responses", supportedFormats: ["openai-responses"] },
|
||||
{ id: "muse-spark-1.2-contributor", name: "Muse Spark 1.2 Contributor", targetFormat: "openai-responses", supportedFormats: ["openai-responses"] },
|
||||
],
|
||||
passthroughModels: true,
|
||||
oauth: {
|
||||
clientId: "1031625952748946",
|
||||
deviceCodeUrl: "https://auth.meta.com/oidc/device/authorization/",
|
||||
tokenUrl: "https://auth.meta.com/oidc/device/token/",
|
||||
},
|
||||
};
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 5.8 KiB |
@@ -265,6 +265,7 @@ export async function GET(request, { params }) {
|
||||
"qoder",
|
||||
"qoder-cn",
|
||||
"grok-cli",
|
||||
"muse",
|
||||
];
|
||||
let deviceData;
|
||||
if (noPkceDeviceProviders.includes(provider)) {
|
||||
@@ -546,12 +547,14 @@ export async function POST(request, { params }) {
|
||||
|
||||
// Still pending or error - don't create connection for pending states
|
||||
const isPending = result.pending || result.error === "authorization_pending" || result.error === "slow_down";
|
||||
|
||||
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: result.error,
|
||||
errorDescription: result.errorDescription,
|
||||
pending: isPending,
|
||||
// fatal: unrecoverable (e.g. post-exchange failure) — client must stop polling and show it
|
||||
...(result.fatal ? { fatal: true } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -169,6 +169,14 @@ function buildQoderModelsResolver(providerId) {
|
||||
|
||||
// Provider models endpoints configuration
|
||||
const PROVIDER_MODELS_CONFIG = {
|
||||
"muse": {
|
||||
url: "https://api.meta.ai/v1/models",
|
||||
method: "GET",
|
||||
headers: { "Content-Type": "application/json", "x-api-version": "1.0.0" },
|
||||
authHeader: "Authorization",
|
||||
authPrefix: "Bearer ",
|
||||
parseResponse: (data) => data.data || [],
|
||||
},
|
||||
claude: {
|
||||
url: "https://api.anthropic.com/v1/models",
|
||||
method: "GET",
|
||||
|
||||
@@ -137,6 +137,15 @@ const OAUTH_TEST_CONFIG = {
|
||||
402: "Connected, but Grok Build credits are exhausted (spending limit). Add credits or upgrade SuperGrok.",
|
||||
},
|
||||
},
|
||||
// Muse Code subscription — probe /v1/models with the minted LLM|… key
|
||||
"muse": {
|
||||
url: "https://api.meta.ai/v1/models",
|
||||
method: "GET",
|
||||
authHeader: "Authorization",
|
||||
authPrefix: "Bearer ",
|
||||
extraHeaders: { "x-api-version": "1.0.0" },
|
||||
refreshable: false,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -703,7 +712,8 @@ async function testApiKeyConnection(connection, effectiveProxy = null) {
|
||||
case "dahl":
|
||||
case "atria":
|
||||
case "agnes":
|
||||
case "bai": {
|
||||
case "bai":
|
||||
case "muse": {
|
||||
const cfg = PROVIDERS[connection.provider];
|
||||
const res = await fetchWithConnectionProxy(cfg.validateUrl, { headers: { Authorization: `Bearer ${connection.apiKey}` } }, effectiveProxy);
|
||||
return { valid: res.ok, error: res.ok ? null : "Invalid API key" };
|
||||
|
||||
@@ -127,6 +127,10 @@ export const KIMCHI_CONFIG = { ...PROVIDER_OAUTH["kimchi"] };
|
||||
// Endpoint: cli-chat-proxy.grok.com — same client_id as xai, different flow + scopes
|
||||
export const GROK_CLI_CONFIG = { ...PROVIDER_OAUTH["grok-cli"] };
|
||||
|
||||
// Muse — subscription device code flow to auth.meta.com, no refresh
|
||||
// (Meta rejects refresh_token grants; the minted Model API key never expires).
|
||||
export const MUSE_CONFIG = { ...PROVIDER_OAUTH["muse"] };
|
||||
|
||||
// Trae (ByteDance marscode) OAuth — authorization_code flow with local callback.
|
||||
// 1) POST GetLoginGuidance {loginTraceID} → {Result.LoginHost}
|
||||
// 2) Browser opens ${loginHost}/authorization?client_id=...&login_trace_id=...&auth_callback_url=${cb}
|
||||
|
||||
@@ -8,6 +8,7 @@ import claude from "./claude.js";
|
||||
import codex from "./codex.js";
|
||||
import xai from "./xai.js";
|
||||
import grokCli from "./grok-cli.js";
|
||||
import muse from "./muse.js";
|
||||
import geminiCli from "./gemini-cli.js";
|
||||
import antigravity from "./antigravity.js";
|
||||
import iflow from "./iflow.js";
|
||||
@@ -34,6 +35,7 @@ const PROVIDERS = {
|
||||
codex,
|
||||
xai,
|
||||
"grok-cli": grokCli,
|
||||
muse,
|
||||
"gemini-cli": geminiCli,
|
||||
antigravity,
|
||||
iflow,
|
||||
@@ -174,7 +176,15 @@ export async function pollForToken(providerName, deviceCode, codeVerifier, extra
|
||||
// Call postExchange to get additional data (copilotToken, userInfo, etc.)
|
||||
let extra = null;
|
||||
if (provider.postExchange) {
|
||||
extra = await provider.postExchange(result.data);
|
||||
try {
|
||||
extra = await provider.postExchange(result.data);
|
||||
} catch (err) {
|
||||
// The grant succeeded but post-login exchange failed (e.g. Muse key
|
||||
// mint). The device code is one-shot, so re-polling can never
|
||||
// recover — surface as fatal so the client stops and shows the error.
|
||||
console.warn(`[oauth] ${providerName} postExchange failed:`, err?.message || err);
|
||||
return { success: false, error: "exchange_failed", errorDescription: err.message, fatal: true };
|
||||
}
|
||||
}
|
||||
const tokens = provider.mapTokens(result.data, extra);
|
||||
// Kiro IDC/Builder-ID tokens lack profileArn; resolve it to avoid 403
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import { MUSE_CONFIG } from "../constants/oauth.js";
|
||||
|
||||
// Muse Code subscription — Meta account device code flow to auth.meta.com,
|
||||
// then mint the Model API key (LLM|…) the chat transport actually uses.
|
||||
const MUSE_KEY_URL = "https://api.meta.ai/muse-code/key";
|
||||
const API_VERSION = "1.0.0";
|
||||
|
||||
const muse = {
|
||||
config: MUSE_CONFIG,
|
||||
flowType: "device_code",
|
||||
requestDeviceCode: async (config) => {
|
||||
const response = await fetch(config.deviceCodeUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Accept: "application/json",
|
||||
"x-api-version": API_VERSION,
|
||||
},
|
||||
body: new URLSearchParams({ client_id: config.clientId }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text();
|
||||
throw new Error(`Muse Code device code request failed: ${error}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
},
|
||||
pollToken: async (config, deviceCode) => {
|
||||
const response = await fetch(config.tokenUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Accept: "application/json",
|
||||
"x-api-version": API_VERSION,
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
||||
device_code: deviceCode,
|
||||
client_id: config.clientId,
|
||||
}),
|
||||
});
|
||||
|
||||
let data;
|
||||
try {
|
||||
data = await response.json();
|
||||
} catch {
|
||||
const text = await response.text();
|
||||
data = { error: "invalid_response", error_description: text };
|
||||
}
|
||||
|
||||
const pending =
|
||||
data?.error === "authorization_pending" ||
|
||||
data?.error === "slow_down";
|
||||
return { ok: response.ok || pending, data };
|
||||
},
|
||||
postExchange: async (tokens) => {
|
||||
// Mint the subscription API key; onboard:true enrolls the account on first
|
||||
// login. The endpoint is aggressively rate-limited (429) and the device code
|
||||
// is one-shot, so retry transient failures here instead of failing the login.
|
||||
let response;
|
||||
for (let attempt = 0; ; attempt++) {
|
||||
response = await fetch(MUSE_KEY_URL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
Authorization: `Bearer ${tokens.access_token}`,
|
||||
"Content-Type": "application/json",
|
||||
"x-api-version": API_VERSION,
|
||||
},
|
||||
body: JSON.stringify({ onboard: true }),
|
||||
redirect: "error",
|
||||
});
|
||||
const transient = response.status === 429 || response.status >= 500;
|
||||
if (!transient || attempt >= 2) break;
|
||||
await new Promise((r) => setTimeout(r, 5000 * (attempt + 1)));
|
||||
}
|
||||
|
||||
const text = await response.text();
|
||||
if (!response.ok) {
|
||||
// Meta's error envelope (e.g. 403 code 4705002) carries the fix-it URL
|
||||
let msg = `${response.status} ${text.slice(0, 200)}`;
|
||||
try {
|
||||
const err = JSON.parse(text);
|
||||
if (err?.title || err?.detail) {
|
||||
msg = [err.title, err.detail].filter(Boolean).join(": ");
|
||||
if (err.action_url) msg += ` — ${err.action_url}`;
|
||||
}
|
||||
} catch { /* non-JSON error body */ }
|
||||
throw new Error(`Muse Code key mint failed: ${msg}`);
|
||||
}
|
||||
|
||||
let payload;
|
||||
try {
|
||||
payload = JSON.parse(text);
|
||||
} catch {
|
||||
throw new Error("Muse Code key mint returned invalid JSON");
|
||||
}
|
||||
|
||||
if (payload.is_subs_active === false) {
|
||||
throw new Error("Muse Code subscription is inactive — activate it on muse.ai first");
|
||||
}
|
||||
const actionUrl = payload.action_url || payload.require_payment_action_url;
|
||||
if (!payload.api_key && (payload.require_payment || actionUrl)) {
|
||||
throw new Error(`Muse Code subscription required${actionUrl ? `: ${actionUrl}` : ""}`);
|
||||
}
|
||||
if (!payload.api_key) {
|
||||
throw new Error("Muse Code key response is missing api_key");
|
||||
}
|
||||
|
||||
return { key: payload };
|
||||
},
|
||||
mapTokens: (tokens, extra) => {
|
||||
const payload = extra?.key || {};
|
||||
// Chat requests carry the minted Model API key, not the Meta account token.
|
||||
// No expiry/refresh from Meta — a dead key means re-login.
|
||||
return {
|
||||
accessToken: payload.api_key,
|
||||
refreshToken: null,
|
||||
expiresIn: null,
|
||||
email: payload.user_email?.trim().toLowerCase() || undefined,
|
||||
providerSpecificData: {
|
||||
authMethod: "device_code",
|
||||
// Kept so a future re-mint can run without another device login
|
||||
oauthAccessToken: tokens.access_token,
|
||||
subscriptionTier: payload.subs_tier_name || null,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default muse;
|
||||
@@ -175,7 +175,7 @@ export default function OAuthModal({ isOpen, provider, providerInfo, onSuccess,
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.error === "expired_token" || data.error === "access_denied") {
|
||||
if (data.error === "expired_token" || data.error === "access_denied" || data.fatal) {
|
||||
throw new Error(data.errorDescription || data.error);
|
||||
}
|
||||
|
||||
@@ -291,6 +291,7 @@ export default function OAuthModal({ isOpen, provider, providerInfo, onSuccess,
|
||||
"qoder",
|
||||
"qoder-cn",
|
||||
"grok-cli",
|
||||
"muse",
|
||||
];
|
||||
if (deviceCodeProviders.includes(provider)) {
|
||||
setIsDeviceCode(true);
|
||||
|
||||
@@ -116,7 +116,12 @@
|
||||
"devin": "devin",
|
||||
"devin-cli": "devin-cli",
|
||||
"morph": "morph",
|
||||
"morphllm": "morph"
|
||||
"morphllm": "morph",
|
||||
"muse": "muse",
|
||||
"muse-ai": "muse",
|
||||
"meta-model-api": "muse",
|
||||
"muse-code": "muse",
|
||||
"muse-subscription": "muse"
|
||||
},
|
||||
"idToAlias": {
|
||||
"agnes": "agnes",
|
||||
@@ -176,6 +181,7 @@
|
||||
"mistral": "mistral",
|
||||
"mmf": "mmf",
|
||||
"morph": "morph",
|
||||
"muse": "muse",
|
||||
"nanobanana": "nanobanana",
|
||||
"nebius": "nebius",
|
||||
"nvidia": "nvidia",
|
||||
@@ -211,6 +217,7 @@
|
||||
"modelKeys": [
|
||||
"af",
|
||||
"ag",
|
||||
"agnes",
|
||||
"alicode",
|
||||
"alicode-intl",
|
||||
"alims-intl",
|
||||
@@ -270,6 +277,7 @@
|
||||
"mistral",
|
||||
"mmf",
|
||||
"morph",
|
||||
"muse",
|
||||
"nanobanana",
|
||||
"nebius",
|
||||
"nvidia",
|
||||
@@ -302,6 +310,7 @@
|
||||
"together",
|
||||
"tokenharbor",
|
||||
"tokenrouter",
|
||||
"v1m",
|
||||
"venice",
|
||||
"vertex",
|
||||
"vertex-partner",
|
||||
|
||||
@@ -121,7 +121,9 @@
|
||||
"usage": {
|
||||
"oauthUrl": "https://api.anthropic.com/api/oauth/usage",
|
||||
"orgUrl": "https://api.anthropic.com/v1/organizations/{org_id}/usage",
|
||||
"settingsUrl": "https://api.anthropic.com/v1/settings"
|
||||
"settingsUrl": "https://api.anthropic.com/v1/settings",
|
||||
"profileUrl": "https://api.anthropic.com/api/oauth/profile",
|
||||
"resetUrl": "https://api.anthropic.com/api/organizations/{org_id}/reset_rate_limits"
|
||||
},
|
||||
"clientId": "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
|
||||
"tokenUrl": "https://api.anthropic.com/v1/oauth/token"
|
||||
@@ -199,10 +201,11 @@
|
||||
"baseUrl": "https://chatgpt.com/backend-api/codex/responses",
|
||||
"format": "openai-responses",
|
||||
"forceStream": true,
|
||||
"cliVersion": "0.154.0",
|
||||
"cliVersion": "0.155.0",
|
||||
"headers": {
|
||||
"originator": "codex_cli_rs",
|
||||
"User-Agent": "codex_cli_rs/0.154.0"
|
||||
"User-Agent": "codex_cli_rs/0.155.0",
|
||||
"version": "0.155.0"
|
||||
},
|
||||
"usage": {
|
||||
"url": "https://chatgpt.com/backend-api/wham/usage",
|
||||
@@ -1086,6 +1089,14 @@
|
||||
},
|
||||
"format": "openai"
|
||||
},
|
||||
"tokenharbor": {
|
||||
"baseUrl": "https://tokenharbor.ai/v1/chat/completions",
|
||||
"validateUrl": "https://tokenharbor.ai/v1/models",
|
||||
"retry": {
|
||||
"429": 2
|
||||
},
|
||||
"format": "openai"
|
||||
},
|
||||
"dahl": {
|
||||
"baseUrl": "https://inference.dahl.global/v1/chat/completions",
|
||||
"validateUrl": "https://inference.dahl.global/v1/models",
|
||||
@@ -1106,12 +1117,46 @@
|
||||
"validateUrl": "https://api.b.ai/v1/models",
|
||||
"format": "openai"
|
||||
},
|
||||
"tokenharbor": {
|
||||
"baseUrl": "https://tokenharbor.ai/v1/chat/completions",
|
||||
"validateUrl": "https://tokenharbor.ai/v1/models",
|
||||
"retry": {
|
||||
"429": 2
|
||||
"muse": {
|
||||
"baseUrl": "https://api.meta.ai/v1/chat/completions",
|
||||
"validateUrl": "https://api.meta.ai/v1/models",
|
||||
"modelsUrl": "https://api.meta.ai/v1/models",
|
||||
"auth": {
|
||||
"combined": true,
|
||||
"header": "Authorization",
|
||||
"scheme": "bearer",
|
||||
"hooks": [
|
||||
"museHeaders"
|
||||
]
|
||||
},
|
||||
"format": "openai"
|
||||
"format": "openai",
|
||||
"clientId": "1031625952748946",
|
||||
"tokenUrl": "https://auth.meta.com/oidc/device/token/",
|
||||
"transports": [
|
||||
{
|
||||
"format": "openai",
|
||||
"baseUrl": "https://api.meta.ai/v1/chat/completions",
|
||||
"auth": {
|
||||
"combined": true,
|
||||
"header": "Authorization",
|
||||
"scheme": "bearer",
|
||||
"hooks": [
|
||||
"museHeaders"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"format": "openai-responses",
|
||||
"baseUrl": "https://api.meta.ai/v1/responses",
|
||||
"auth": {
|
||||
"combined": true,
|
||||
"header": "Authorization",
|
||||
"scheme": "bearer",
|
||||
"hooks": [
|
||||
"museHeaders"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,8 @@ const ALIAS_TOKENS = [
|
||||
"af","airforce","api-airforce","llm7","llm-7","samba","sambanova","bm","bluesminds",
|
||||
"bzl","bazaarlink","kgw","kilo-gateway","hunyuan","tencent","qianfan","baidu","ernie",
|
||||
"dv","devin","devin-cli","morph","morphllm",
|
||||
"muse","muse-ai","meta-model-api",
|
||||
"muse-code","muse-subscription",
|
||||
];
|
||||
|
||||
// Sort idToAlias by key — runtime accesses by key, order is irrelevant (content-based)
|
||||
|
||||
Reference in new issue
Block a user