mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 01:31:08 +00:00
test: speed up provider entry tests
This commit is contained in:
@@ -9,12 +9,20 @@ import {
|
||||
registerProviderPlugin,
|
||||
requireRegisteredProvider,
|
||||
} from "../../test/helpers/plugins/provider-registration.js";
|
||||
import googlePlugin from "./index.js";
|
||||
import { registerGoogleGeminiCliProvider } from "./gemini-cli-provider.js";
|
||||
import { registerGoogleProvider } from "./provider-registration.js";
|
||||
|
||||
const googleProviderPlugin = {
|
||||
register(api: Parameters<typeof registerGoogleProvider>[0]) {
|
||||
registerGoogleProvider(api);
|
||||
registerGoogleGeminiCliProvider(api);
|
||||
},
|
||||
};
|
||||
|
||||
describe("google provider plugin hooks", () => {
|
||||
it("owns replay policy and reasoning mode for the direct Gemini provider", async () => {
|
||||
const { providers } = await registerProviderPlugin({
|
||||
plugin: googlePlugin,
|
||||
plugin: googleProviderPlugin,
|
||||
id: "google",
|
||||
name: "Google Provider",
|
||||
});
|
||||
@@ -85,7 +93,7 @@ describe("google provider plugin hooks", () => {
|
||||
|
||||
it("owns Gemini CLI tool schema normalization", async () => {
|
||||
const { providers } = await registerProviderPlugin({
|
||||
plugin: googlePlugin,
|
||||
plugin: googleProviderPlugin,
|
||||
id: "google",
|
||||
name: "Google Provider",
|
||||
});
|
||||
@@ -132,7 +140,7 @@ describe("google provider plugin hooks", () => {
|
||||
|
||||
it("wires google-thinking stream hooks for direct and Gemini CLI providers", async () => {
|
||||
const { providers } = await registerProviderPlugin({
|
||||
plugin: googlePlugin,
|
||||
plugin: googleProviderPlugin,
|
||||
id: "google",
|
||||
name: "Google Provider",
|
||||
});
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
import type { ImageGenerationProvider } from "openclaw/plugin-sdk/image-generation";
|
||||
import type { MediaUnderstandingProvider } from "openclaw/plugin-sdk/media-understanding";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key";
|
||||
import { buildProviderReplayFamilyHooks } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { buildProviderStreamFamilyHooks } from "openclaw/plugin-sdk/provider-stream-family";
|
||||
import {
|
||||
GOOGLE_GEMINI_DEFAULT_MODEL,
|
||||
applyGoogleGeminiModelDefault,
|
||||
normalizeGoogleProviderConfig,
|
||||
normalizeGoogleModelId,
|
||||
resolveGoogleGenerativeAiTransport,
|
||||
} from "./api.js";
|
||||
import { buildGoogleGeminiCliBackend } from "./cli-backend.js";
|
||||
import { registerGoogleGeminiCliProvider } from "./gemini-cli-provider.js";
|
||||
import { buildGoogleMusicGenerationProvider } from "./music-generation-provider.js";
|
||||
import { isModernGoogleModel, resolveGoogleGeminiForwardCompatModel } from "./provider-models.js";
|
||||
import { registerGoogleProvider } from "./provider-registration.js";
|
||||
import { createGeminiWebSearchProvider } from "./src/gemini-web-search-provider.js";
|
||||
import { buildGoogleVideoGenerationProvider } from "./video-generation-provider.js";
|
||||
|
||||
@@ -28,13 +18,6 @@ type GoogleMediaUnderstandingProvider = MediaUnderstandingProvider & {
|
||||
describeVideo: NonNullable<MediaUnderstandingProvider["describeVideo"]>;
|
||||
};
|
||||
|
||||
const GOOGLE_GEMINI_PROVIDER_HOOKS = {
|
||||
...buildProviderReplayFamilyHooks({
|
||||
family: "google-gemini",
|
||||
}),
|
||||
...buildProviderStreamFamilyHooks("google-thinking"),
|
||||
};
|
||||
|
||||
async function loadGoogleImageGenerationProvider(): Promise<ImageGenerationProvider> {
|
||||
if (!googleImageGenerationProviderPromise) {
|
||||
googleImageGenerationProviderPromise = import("./image-generation-provider.js").then((mod) =>
|
||||
@@ -126,47 +109,7 @@ export default definePluginEntry({
|
||||
register(api) {
|
||||
api.registerCliBackend(buildGoogleGeminiCliBackend());
|
||||
registerGoogleGeminiCliProvider(api);
|
||||
api.registerProvider({
|
||||
id: "google",
|
||||
label: "Google AI Studio",
|
||||
docsPath: "/providers/models",
|
||||
hookAliases: ["google-antigravity", "google-vertex"],
|
||||
envVars: ["GEMINI_API_KEY", "GOOGLE_API_KEY"],
|
||||
auth: [
|
||||
createProviderApiKeyAuthMethod({
|
||||
providerId: "google",
|
||||
methodId: "api-key",
|
||||
label: "Google Gemini API key",
|
||||
hint: "AI Studio / Gemini API key",
|
||||
optionKey: "geminiApiKey",
|
||||
flagName: "--gemini-api-key",
|
||||
envVar: "GEMINI_API_KEY",
|
||||
promptMessage: "Enter Gemini API key",
|
||||
defaultModel: GOOGLE_GEMINI_DEFAULT_MODEL,
|
||||
expectedProviders: ["google"],
|
||||
applyConfig: (cfg) => applyGoogleGeminiModelDefault(cfg).next,
|
||||
wizard: {
|
||||
choiceId: "gemini-api-key",
|
||||
choiceLabel: "Google Gemini API key",
|
||||
groupId: "google",
|
||||
groupLabel: "Google",
|
||||
groupHint: "Gemini API key + OAuth",
|
||||
},
|
||||
}),
|
||||
],
|
||||
normalizeTransport: ({ api, baseUrl }) =>
|
||||
resolveGoogleGenerativeAiTransport({ api, baseUrl }),
|
||||
normalizeConfig: ({ provider, providerConfig }) =>
|
||||
normalizeGoogleProviderConfig(provider, providerConfig),
|
||||
normalizeModelId: ({ modelId }) => normalizeGoogleModelId(modelId),
|
||||
resolveDynamicModel: (ctx) =>
|
||||
resolveGoogleGeminiForwardCompatModel({
|
||||
providerId: ctx.provider,
|
||||
ctx,
|
||||
}),
|
||||
...GOOGLE_GEMINI_PROVIDER_HOOKS,
|
||||
isModernModelRef: ({ modelId }) => isModernGoogleModel(modelId),
|
||||
});
|
||||
registerGoogleProvider(api);
|
||||
api.registerImageGenerationProvider(createLazyGoogleImageGenerationProvider());
|
||||
api.registerMediaUnderstandingProvider(createLazyGoogleMediaUnderstandingProvider());
|
||||
api.registerMusicGenerationProvider(buildGoogleMusicGenerationProvider());
|
||||
|
||||
62
extensions/google/provider-registration.ts
Normal file
62
extensions/google/provider-registration.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key";
|
||||
import { buildProviderReplayFamilyHooks } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { buildProviderStreamFamilyHooks } from "openclaw/plugin-sdk/provider-stream-family";
|
||||
import {
|
||||
GOOGLE_GEMINI_DEFAULT_MODEL,
|
||||
applyGoogleGeminiModelDefault,
|
||||
normalizeGoogleProviderConfig,
|
||||
normalizeGoogleModelId,
|
||||
resolveGoogleGenerativeAiTransport,
|
||||
} from "./api.js";
|
||||
import { isModernGoogleModel, resolveGoogleGeminiForwardCompatModel } from "./provider-models.js";
|
||||
|
||||
const GOOGLE_GEMINI_PROVIDER_HOOKS = {
|
||||
...buildProviderReplayFamilyHooks({
|
||||
family: "google-gemini",
|
||||
}),
|
||||
...buildProviderStreamFamilyHooks("google-thinking"),
|
||||
};
|
||||
|
||||
export function registerGoogleProvider(api: OpenClawPluginApi) {
|
||||
api.registerProvider({
|
||||
id: "google",
|
||||
label: "Google AI Studio",
|
||||
docsPath: "/providers/models",
|
||||
hookAliases: ["google-antigravity", "google-vertex"],
|
||||
envVars: ["GEMINI_API_KEY", "GOOGLE_API_KEY"],
|
||||
auth: [
|
||||
createProviderApiKeyAuthMethod({
|
||||
providerId: "google",
|
||||
methodId: "api-key",
|
||||
label: "Google Gemini API key",
|
||||
hint: "AI Studio / Gemini API key",
|
||||
optionKey: "geminiApiKey",
|
||||
flagName: "--gemini-api-key",
|
||||
envVar: "GEMINI_API_KEY",
|
||||
promptMessage: "Enter Gemini API key",
|
||||
defaultModel: GOOGLE_GEMINI_DEFAULT_MODEL,
|
||||
expectedProviders: ["google"],
|
||||
applyConfig: (cfg) => applyGoogleGeminiModelDefault(cfg).next,
|
||||
wizard: {
|
||||
choiceId: "gemini-api-key",
|
||||
choiceLabel: "Google Gemini API key",
|
||||
groupId: "google",
|
||||
groupLabel: "Google",
|
||||
groupHint: "Gemini API key + OAuth",
|
||||
},
|
||||
}),
|
||||
],
|
||||
normalizeTransport: ({ api, baseUrl }) => resolveGoogleGenerativeAiTransport({ api, baseUrl }),
|
||||
normalizeConfig: ({ provider, providerConfig }) =>
|
||||
normalizeGoogleProviderConfig(provider, providerConfig),
|
||||
normalizeModelId: ({ modelId }) => normalizeGoogleModelId(modelId),
|
||||
resolveDynamicModel: (ctx) =>
|
||||
resolveGoogleGeminiForwardCompatModel({
|
||||
providerId: ctx.provider,
|
||||
ctx,
|
||||
}),
|
||||
...GOOGLE_GEMINI_PROVIDER_HOOKS,
|
||||
isModernModelRef: ({ modelId }) => isModernGoogleModel(modelId),
|
||||
});
|
||||
}
|
||||
@@ -5,12 +5,20 @@ import {
|
||||
registerProviderPlugin,
|
||||
requireRegisteredProvider,
|
||||
} from "../../test/helpers/plugins/provider-registration.js";
|
||||
import minimaxPlugin from "./index.js";
|
||||
import { registerMinimaxProviders } from "./provider-registration.js";
|
||||
import { createMiniMaxWebSearchProvider } from "./src/minimax-web-search-provider.js";
|
||||
|
||||
const minimaxProviderPlugin = {
|
||||
register(api: Parameters<typeof registerMinimaxProviders>[0]) {
|
||||
registerMinimaxProviders(api);
|
||||
api.registerWebSearchProvider(createMiniMaxWebSearchProvider());
|
||||
},
|
||||
};
|
||||
|
||||
describe("minimax provider hooks", () => {
|
||||
it("keeps native reasoning mode for MiniMax transports", async () => {
|
||||
const { providers } = await registerProviderPlugin({
|
||||
plugin: minimaxPlugin,
|
||||
plugin: minimaxProviderPlugin,
|
||||
id: "minimax",
|
||||
name: "MiniMax Provider",
|
||||
});
|
||||
@@ -38,7 +46,7 @@ describe("minimax provider hooks", () => {
|
||||
|
||||
it("owns replay policy for Anthropic and OpenAI-compatible MiniMax transports", async () => {
|
||||
const { providers } = await registerProviderPlugin({
|
||||
plugin: minimaxPlugin,
|
||||
plugin: minimaxProviderPlugin,
|
||||
id: "minimax",
|
||||
name: "MiniMax Provider",
|
||||
});
|
||||
@@ -75,7 +83,7 @@ describe("minimax provider hooks", () => {
|
||||
|
||||
it("owns fast-mode stream wrapping for MiniMax transports", async () => {
|
||||
const { providers } = await registerProviderPlugin({
|
||||
plugin: minimaxPlugin,
|
||||
plugin: minimaxProviderPlugin,
|
||||
id: "minimax",
|
||||
name: "MiniMax Provider",
|
||||
});
|
||||
@@ -133,7 +141,7 @@ describe("minimax provider hooks", () => {
|
||||
it("registers the bundled MiniMax web search provider", () => {
|
||||
const webSearchProviders: unknown[] = [];
|
||||
|
||||
minimaxPlugin.register({
|
||||
minimaxProviderPlugin.register({
|
||||
registerProvider() {},
|
||||
registerMediaUnderstandingProvider() {},
|
||||
registerImageGenerationProvider() {},
|
||||
@@ -155,7 +163,7 @@ describe("minimax provider hooks", () => {
|
||||
|
||||
it("prefers minimax-portal oauth when resolving MiniMax usage auth", async () => {
|
||||
const { providers } = await registerProviderPlugin({
|
||||
plugin: minimaxPlugin,
|
||||
plugin: minimaxProviderPlugin,
|
||||
id: "minimax",
|
||||
name: "MiniMax Provider",
|
||||
});
|
||||
|
||||
@@ -1,21 +1,4 @@
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import {
|
||||
definePluginEntry,
|
||||
type ProviderAuthContext,
|
||||
type ProviderAuthResult,
|
||||
type ProviderCatalogContext,
|
||||
} from "openclaw/plugin-sdk/plugin-entry";
|
||||
import {
|
||||
MINIMAX_OAUTH_MARKER,
|
||||
ensureAuthProfileStore,
|
||||
listProfilesForProvider,
|
||||
} from "openclaw/plugin-sdk/provider-auth";
|
||||
import { buildOauthProviderAuthResult } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key";
|
||||
import { buildProviderReplayFamilyHooks } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { buildProviderStreamFamilyHooks } from "openclaw/plugin-sdk/provider-stream-family";
|
||||
import { fetchMinimaxUsage } from "openclaw/plugin-sdk/provider-usage";
|
||||
import { isMiniMaxModernModelId, MINIMAX_DEFAULT_MODEL_ID } from "./api.js";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import {
|
||||
buildMinimaxImageGenerationProvider,
|
||||
buildMinimaxPortalImageGenerationProvider,
|
||||
@@ -25,295 +8,19 @@ import {
|
||||
minimaxPortalMediaUnderstandingProvider,
|
||||
} from "./media-understanding-provider.js";
|
||||
import { buildMinimaxMusicGenerationProvider } from "./music-generation-provider.js";
|
||||
import type { MiniMaxRegion } from "./oauth.js";
|
||||
import { applyMinimaxApiConfig, applyMinimaxApiConfigCn } from "./onboard.js";
|
||||
import { buildMinimaxPortalProvider, buildMinimaxProvider } from "./provider-catalog.js";
|
||||
import { registerMinimaxProviders } from "./provider-registration.js";
|
||||
import { buildMinimaxSpeechProvider } from "./speech-provider.js";
|
||||
import { createMiniMaxWebSearchProvider } from "./src/minimax-web-search-provider.js";
|
||||
import { buildMinimaxVideoGenerationProvider } from "./video-generation-provider.js";
|
||||
|
||||
const API_PROVIDER_ID = "minimax";
|
||||
const PORTAL_PROVIDER_ID = "minimax-portal";
|
||||
const PROVIDER_LABEL = "MiniMax";
|
||||
const DEFAULT_MODEL = MINIMAX_DEFAULT_MODEL_ID;
|
||||
const DEFAULT_BASE_URL_CN = "https://api.minimaxi.com/anthropic";
|
||||
const DEFAULT_BASE_URL_GLOBAL = "https://api.minimax.io/anthropic";
|
||||
const MINIMAX_USAGE_ENV_VAR_KEYS = [
|
||||
"MINIMAX_OAUTH_TOKEN",
|
||||
"MINIMAX_CODE_PLAN_KEY",
|
||||
"MINIMAX_CODING_API_KEY",
|
||||
"MINIMAX_API_KEY",
|
||||
] as const;
|
||||
const HYBRID_ANTHROPIC_OPENAI_REPLAY_HOOKS = buildProviderReplayFamilyHooks({
|
||||
family: "hybrid-anthropic-openai",
|
||||
anthropicModelDropThinkingBlocks: true,
|
||||
});
|
||||
const MINIMAX_FAST_MODE_STREAM_HOOKS = buildProviderStreamFamilyHooks("minimax-fast-mode");
|
||||
|
||||
function resolveMinimaxReasoningOutputMode(): "native" {
|
||||
// Keep MiniMax on native reasoning mode. Tagged enforcement previously
|
||||
// suppressed normal assistant replies on this Anthropic-compatible surface.
|
||||
return "native";
|
||||
}
|
||||
|
||||
function getDefaultBaseUrl(region: MiniMaxRegion): string {
|
||||
return region === "cn" ? DEFAULT_BASE_URL_CN : DEFAULT_BASE_URL_GLOBAL;
|
||||
}
|
||||
|
||||
function apiModelRef(modelId: string): string {
|
||||
return `${API_PROVIDER_ID}/${modelId}`;
|
||||
}
|
||||
|
||||
function portalModelRef(modelId: string): string {
|
||||
return `${PORTAL_PROVIDER_ID}/${modelId}`;
|
||||
}
|
||||
|
||||
function buildPortalProviderCatalog(params: { baseUrl: string; apiKey: string }) {
|
||||
return {
|
||||
...buildMinimaxPortalProvider(),
|
||||
baseUrl: params.baseUrl,
|
||||
apiKey: params.apiKey,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveApiCatalog(ctx: ProviderCatalogContext) {
|
||||
const apiKey = ctx.resolveProviderApiKey(API_PROVIDER_ID).apiKey;
|
||||
if (!apiKey) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
provider: {
|
||||
...buildMinimaxProvider(ctx.env),
|
||||
apiKey,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function resolvePortalCatalog(ctx: ProviderCatalogContext) {
|
||||
const explicitProvider = ctx.config.models?.providers?.[PORTAL_PROVIDER_ID];
|
||||
const envApiKey = ctx.resolveProviderApiKey(PORTAL_PROVIDER_ID).apiKey;
|
||||
const authStore = ensureAuthProfileStore(ctx.agentDir, {
|
||||
allowKeychainPrompt: false,
|
||||
});
|
||||
const hasProfiles = listProfilesForProvider(authStore, PORTAL_PROVIDER_ID).length > 0;
|
||||
const explicitApiKey =
|
||||
typeof explicitProvider?.apiKey === "string" ? explicitProvider.apiKey.trim() : undefined;
|
||||
const apiKey = envApiKey ?? explicitApiKey ?? (hasProfiles ? MINIMAX_OAUTH_MARKER : undefined);
|
||||
if (!apiKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const explicitBaseUrl =
|
||||
typeof explicitProvider?.baseUrl === "string" ? explicitProvider.baseUrl.trim() : undefined;
|
||||
|
||||
return {
|
||||
provider: buildPortalProviderCatalog({
|
||||
baseUrl: explicitBaseUrl || buildMinimaxPortalProvider(ctx.env).baseUrl,
|
||||
apiKey,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function createOAuthHandler(region: MiniMaxRegion) {
|
||||
const defaultBaseUrl = getDefaultBaseUrl(region);
|
||||
const regionLabel = region === "cn" ? "CN" : "Global";
|
||||
|
||||
return async (ctx: ProviderAuthContext): Promise<ProviderAuthResult> => {
|
||||
const progress = ctx.prompter.progress(`Starting MiniMax OAuth (${regionLabel})…`);
|
||||
try {
|
||||
const { loginMiniMaxPortalOAuth } = await import("./oauth.runtime.js");
|
||||
const result = await loginMiniMaxPortalOAuth({
|
||||
openUrl: ctx.openUrl,
|
||||
note: ctx.prompter.note,
|
||||
progress,
|
||||
region,
|
||||
});
|
||||
|
||||
progress.stop("MiniMax OAuth complete");
|
||||
|
||||
if (result.notification_message) {
|
||||
await ctx.prompter.note(result.notification_message, "MiniMax OAuth");
|
||||
}
|
||||
|
||||
const baseUrl = result.resourceUrl || defaultBaseUrl;
|
||||
|
||||
return buildOauthProviderAuthResult({
|
||||
providerId: PORTAL_PROVIDER_ID,
|
||||
defaultModel: portalModelRef(DEFAULT_MODEL),
|
||||
access: result.access,
|
||||
refresh: result.refresh,
|
||||
expires: result.expires,
|
||||
configPatch: {
|
||||
models: {
|
||||
providers: {
|
||||
[PORTAL_PROVIDER_ID]: {
|
||||
baseUrl,
|
||||
models: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
agents: {
|
||||
defaults: {
|
||||
models: {
|
||||
[portalModelRef("MiniMax-M2.7")]: { alias: "minimax-m2.7" },
|
||||
[portalModelRef("MiniMax-M2.7-highspeed")]: {
|
||||
alias: "minimax-m2.7-highspeed",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
notes: [
|
||||
"MiniMax OAuth tokens auto-refresh. Re-run login if refresh fails or access is revoked.",
|
||||
`Base URL defaults to ${defaultBaseUrl}. Override models.providers.${PORTAL_PROVIDER_ID}.baseUrl if needed.`,
|
||||
...(result.notification_message ? [result.notification_message] : []),
|
||||
],
|
||||
});
|
||||
} catch (err) {
|
||||
const errorMsg = formatErrorMessage(err);
|
||||
progress.stop(`MiniMax OAuth failed: ${errorMsg}`);
|
||||
await ctx.prompter.note(
|
||||
"If OAuth fails, verify your MiniMax account has portal access and try again.",
|
||||
"MiniMax OAuth",
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default definePluginEntry({
|
||||
id: API_PROVIDER_ID,
|
||||
id: "minimax",
|
||||
name: "MiniMax",
|
||||
description: "Bundled MiniMax API-key and OAuth provider plugin",
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: API_PROVIDER_ID,
|
||||
label: PROVIDER_LABEL,
|
||||
hookAliases: ["minimax-cn"],
|
||||
docsPath: "/providers/minimax",
|
||||
envVars: ["MINIMAX_API_KEY"],
|
||||
auth: [
|
||||
createProviderApiKeyAuthMethod({
|
||||
providerId: API_PROVIDER_ID,
|
||||
methodId: "api-global",
|
||||
label: "MiniMax API key (Global)",
|
||||
hint: "Global endpoint - api.minimax.io",
|
||||
optionKey: "minimaxApiKey",
|
||||
flagName: "--minimax-api-key",
|
||||
envVar: "MINIMAX_API_KEY",
|
||||
promptMessage:
|
||||
"Enter MiniMax API key (sk-api- or sk-cp-)\nhttps://platform.minimax.io/user-center/basic-information/interface-key",
|
||||
profileId: "minimax:global",
|
||||
allowProfile: false,
|
||||
defaultModel: apiModelRef(DEFAULT_MODEL),
|
||||
expectedProviders: ["minimax"],
|
||||
applyConfig: (cfg) => applyMinimaxApiConfig(cfg),
|
||||
wizard: {
|
||||
choiceId: "minimax-global-api",
|
||||
choiceLabel: "MiniMax API key (Global)",
|
||||
choiceHint: "Global endpoint - api.minimax.io",
|
||||
groupId: "minimax",
|
||||
groupLabel: "MiniMax",
|
||||
groupHint: "M2.7 (recommended)",
|
||||
},
|
||||
}),
|
||||
createProviderApiKeyAuthMethod({
|
||||
providerId: API_PROVIDER_ID,
|
||||
methodId: "api-cn",
|
||||
label: "MiniMax API key (CN)",
|
||||
hint: "CN endpoint - api.minimaxi.com",
|
||||
optionKey: "minimaxApiKey",
|
||||
flagName: "--minimax-api-key",
|
||||
envVar: "MINIMAX_API_KEY",
|
||||
promptMessage:
|
||||
"Enter MiniMax CN API key (sk-api- or sk-cp-)\nhttps://platform.minimaxi.com/user-center/basic-information/interface-key",
|
||||
profileId: "minimax:cn",
|
||||
allowProfile: false,
|
||||
defaultModel: apiModelRef(DEFAULT_MODEL),
|
||||
expectedProviders: ["minimax", "minimax-cn"],
|
||||
applyConfig: (cfg) => applyMinimaxApiConfigCn(cfg),
|
||||
wizard: {
|
||||
choiceId: "minimax-cn-api",
|
||||
choiceLabel: "MiniMax API key (CN)",
|
||||
choiceHint: "CN endpoint - api.minimaxi.com",
|
||||
groupId: "minimax",
|
||||
groupLabel: "MiniMax",
|
||||
groupHint: "M2.7 (recommended)",
|
||||
},
|
||||
}),
|
||||
],
|
||||
catalog: {
|
||||
order: "simple",
|
||||
run: async (ctx) => resolveApiCatalog(ctx),
|
||||
},
|
||||
resolveUsageAuth: async (ctx) => {
|
||||
const portalOauth = await ctx.resolveOAuthToken({ provider: PORTAL_PROVIDER_ID });
|
||||
if (portalOauth) {
|
||||
return portalOauth;
|
||||
}
|
||||
const apiKey = ctx.resolveApiKeyFromConfigAndStore({
|
||||
providerIds: [API_PROVIDER_ID, PORTAL_PROVIDER_ID],
|
||||
envDirect: MINIMAX_USAGE_ENV_VAR_KEYS.map((name) => ctx.env[name]),
|
||||
});
|
||||
return apiKey ? { token: apiKey } : null;
|
||||
},
|
||||
...HYBRID_ANTHROPIC_OPENAI_REPLAY_HOOKS,
|
||||
...MINIMAX_FAST_MODE_STREAM_HOOKS,
|
||||
resolveReasoningOutputMode: () => resolveMinimaxReasoningOutputMode(),
|
||||
isModernModelRef: ({ modelId }) => isMiniMaxModernModelId(modelId),
|
||||
fetchUsageSnapshot: async (ctx) =>
|
||||
await fetchMinimaxUsage(ctx.token, ctx.timeoutMs, ctx.fetchFn),
|
||||
});
|
||||
|
||||
registerMinimaxProviders(api);
|
||||
api.registerMediaUnderstandingProvider(minimaxMediaUnderstandingProvider);
|
||||
api.registerMediaUnderstandingProvider(minimaxPortalMediaUnderstandingProvider);
|
||||
|
||||
api.registerProvider({
|
||||
id: PORTAL_PROVIDER_ID,
|
||||
label: PROVIDER_LABEL,
|
||||
hookAliases: ["minimax-portal-cn"],
|
||||
docsPath: "/providers/minimax",
|
||||
envVars: ["MINIMAX_OAUTH_TOKEN", "MINIMAX_API_KEY"],
|
||||
catalog: {
|
||||
run: async (ctx) => resolvePortalCatalog(ctx),
|
||||
},
|
||||
auth: [
|
||||
{
|
||||
id: "oauth",
|
||||
label: "MiniMax OAuth (Global)",
|
||||
hint: "Global endpoint - api.minimax.io",
|
||||
kind: "device_code",
|
||||
wizard: {
|
||||
choiceId: "minimax-global-oauth",
|
||||
choiceLabel: "MiniMax OAuth (Global)",
|
||||
choiceHint: "Global endpoint - api.minimax.io",
|
||||
groupId: "minimax",
|
||||
groupLabel: "MiniMax",
|
||||
groupHint: "M2.7 (recommended)",
|
||||
},
|
||||
run: createOAuthHandler("global"),
|
||||
},
|
||||
{
|
||||
id: "oauth-cn",
|
||||
label: "MiniMax OAuth (CN)",
|
||||
hint: "CN endpoint - api.minimaxi.com",
|
||||
kind: "device_code",
|
||||
wizard: {
|
||||
choiceId: "minimax-cn-oauth",
|
||||
choiceLabel: "MiniMax OAuth (CN)",
|
||||
choiceHint: "CN endpoint - api.minimaxi.com",
|
||||
groupId: "minimax",
|
||||
groupLabel: "MiniMax",
|
||||
groupHint: "M2.7 (recommended)",
|
||||
},
|
||||
run: createOAuthHandler("cn"),
|
||||
},
|
||||
],
|
||||
...HYBRID_ANTHROPIC_OPENAI_REPLAY_HOOKS,
|
||||
...MINIMAX_FAST_MODE_STREAM_HOOKS,
|
||||
resolveReasoningOutputMode: () => resolveMinimaxReasoningOutputMode(),
|
||||
isModernModelRef: ({ modelId }) => isMiniMaxModernModelId(modelId),
|
||||
});
|
||||
api.registerImageGenerationProvider(buildMinimaxImageGenerationProvider());
|
||||
api.registerImageGenerationProvider(buildMinimaxPortalImageGenerationProvider());
|
||||
api.registerMusicGenerationProvider(buildMinimaxMusicGenerationProvider());
|
||||
|
||||
296
extensions/minimax/provider-registration.ts
Normal file
296
extensions/minimax/provider-registration.ts
Normal file
@@ -0,0 +1,296 @@
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import type {
|
||||
OpenClawPluginApi,
|
||||
ProviderAuthContext,
|
||||
ProviderAuthResult,
|
||||
ProviderCatalogContext,
|
||||
} from "openclaw/plugin-sdk/plugin-entry";
|
||||
import {
|
||||
MINIMAX_OAUTH_MARKER,
|
||||
ensureAuthProfileStore,
|
||||
listProfilesForProvider,
|
||||
} from "openclaw/plugin-sdk/provider-auth";
|
||||
import { buildOauthProviderAuthResult } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key";
|
||||
import { buildProviderReplayFamilyHooks } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { buildProviderStreamFamilyHooks } from "openclaw/plugin-sdk/provider-stream-family";
|
||||
import { fetchMinimaxUsage } from "openclaw/plugin-sdk/provider-usage";
|
||||
import { isMiniMaxModernModelId, MINIMAX_DEFAULT_MODEL_ID } from "./api.js";
|
||||
import type { MiniMaxRegion } from "./oauth.js";
|
||||
import { applyMinimaxApiConfig, applyMinimaxApiConfigCn } from "./onboard.js";
|
||||
import { buildMinimaxPortalProvider, buildMinimaxProvider } from "./provider-catalog.js";
|
||||
|
||||
const API_PROVIDER_ID = "minimax";
|
||||
const PORTAL_PROVIDER_ID = "minimax-portal";
|
||||
const PROVIDER_LABEL = "MiniMax";
|
||||
const DEFAULT_MODEL = MINIMAX_DEFAULT_MODEL_ID;
|
||||
const DEFAULT_BASE_URL_CN = "https://api.minimaxi.com/anthropic";
|
||||
const DEFAULT_BASE_URL_GLOBAL = "https://api.minimax.io/anthropic";
|
||||
const MINIMAX_USAGE_ENV_VAR_KEYS = [
|
||||
"MINIMAX_OAUTH_TOKEN",
|
||||
"MINIMAX_CODE_PLAN_KEY",
|
||||
"MINIMAX_CODING_API_KEY",
|
||||
"MINIMAX_API_KEY",
|
||||
] as const;
|
||||
const HYBRID_ANTHROPIC_OPENAI_REPLAY_HOOKS = buildProviderReplayFamilyHooks({
|
||||
family: "hybrid-anthropic-openai",
|
||||
anthropicModelDropThinkingBlocks: true,
|
||||
});
|
||||
const MINIMAX_FAST_MODE_STREAM_HOOKS = buildProviderStreamFamilyHooks("minimax-fast-mode");
|
||||
|
||||
function resolveMinimaxReasoningOutputMode(): "native" {
|
||||
return "native";
|
||||
}
|
||||
|
||||
function getDefaultBaseUrl(region: MiniMaxRegion): string {
|
||||
return region === "cn" ? DEFAULT_BASE_URL_CN : DEFAULT_BASE_URL_GLOBAL;
|
||||
}
|
||||
|
||||
function apiModelRef(modelId: string): string {
|
||||
return `${API_PROVIDER_ID}/${modelId}`;
|
||||
}
|
||||
|
||||
function portalModelRef(modelId: string): string {
|
||||
return `${PORTAL_PROVIDER_ID}/${modelId}`;
|
||||
}
|
||||
|
||||
function buildPortalProviderCatalog(params: { baseUrl: string; apiKey: string }) {
|
||||
return {
|
||||
...buildMinimaxPortalProvider(),
|
||||
baseUrl: params.baseUrl,
|
||||
apiKey: params.apiKey,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveApiCatalog(ctx: ProviderCatalogContext) {
|
||||
const apiKey = ctx.resolveProviderApiKey(API_PROVIDER_ID).apiKey;
|
||||
if (!apiKey) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
provider: {
|
||||
...buildMinimaxProvider(ctx.env),
|
||||
apiKey,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function resolvePortalCatalog(ctx: ProviderCatalogContext) {
|
||||
const explicitProvider = ctx.config.models?.providers?.[PORTAL_PROVIDER_ID];
|
||||
const envApiKey = ctx.resolveProviderApiKey(PORTAL_PROVIDER_ID).apiKey;
|
||||
const authStore = ensureAuthProfileStore(ctx.agentDir, {
|
||||
allowKeychainPrompt: false,
|
||||
});
|
||||
const hasProfiles = listProfilesForProvider(authStore, PORTAL_PROVIDER_ID).length > 0;
|
||||
const explicitApiKey =
|
||||
typeof explicitProvider?.apiKey === "string" ? explicitProvider.apiKey.trim() : undefined;
|
||||
const apiKey = envApiKey ?? explicitApiKey ?? (hasProfiles ? MINIMAX_OAUTH_MARKER : undefined);
|
||||
if (!apiKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const explicitBaseUrl =
|
||||
typeof explicitProvider?.baseUrl === "string" ? explicitProvider.baseUrl.trim() : undefined;
|
||||
|
||||
return {
|
||||
provider: buildPortalProviderCatalog({
|
||||
baseUrl: explicitBaseUrl || buildMinimaxPortalProvider(ctx.env).baseUrl,
|
||||
apiKey,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function createOAuthHandler(region: MiniMaxRegion) {
|
||||
const defaultBaseUrl = getDefaultBaseUrl(region);
|
||||
const regionLabel = region === "cn" ? "CN" : "Global";
|
||||
|
||||
return async (ctx: ProviderAuthContext): Promise<ProviderAuthResult> => {
|
||||
const progress = ctx.prompter.progress(`Starting MiniMax OAuth (${regionLabel})…`);
|
||||
try {
|
||||
const { loginMiniMaxPortalOAuth } = await import("./oauth.runtime.js");
|
||||
const result = await loginMiniMaxPortalOAuth({
|
||||
openUrl: ctx.openUrl,
|
||||
note: ctx.prompter.note,
|
||||
progress,
|
||||
region,
|
||||
});
|
||||
|
||||
progress.stop("MiniMax OAuth complete");
|
||||
|
||||
if (result.notification_message) {
|
||||
await ctx.prompter.note(result.notification_message, "MiniMax OAuth");
|
||||
}
|
||||
|
||||
const baseUrl = result.resourceUrl || defaultBaseUrl;
|
||||
|
||||
return buildOauthProviderAuthResult({
|
||||
providerId: PORTAL_PROVIDER_ID,
|
||||
defaultModel: portalModelRef(DEFAULT_MODEL),
|
||||
access: result.access,
|
||||
refresh: result.refresh,
|
||||
expires: result.expires,
|
||||
configPatch: {
|
||||
models: {
|
||||
providers: {
|
||||
[PORTAL_PROVIDER_ID]: {
|
||||
baseUrl,
|
||||
models: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
agents: {
|
||||
defaults: {
|
||||
models: {
|
||||
[portalModelRef("MiniMax-M2.7")]: { alias: "minimax-m2.7" },
|
||||
[portalModelRef("MiniMax-M2.7-highspeed")]: {
|
||||
alias: "minimax-m2.7-highspeed",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
notes: [
|
||||
"MiniMax OAuth tokens auto-refresh. Re-run login if refresh fails or access is revoked.",
|
||||
`Base URL defaults to ${defaultBaseUrl}. Override models.providers.${PORTAL_PROVIDER_ID}.baseUrl if needed.`,
|
||||
...(result.notification_message ? [result.notification_message] : []),
|
||||
],
|
||||
});
|
||||
} catch (err) {
|
||||
const errorMsg = formatErrorMessage(err);
|
||||
progress.stop(`MiniMax OAuth failed: ${errorMsg}`);
|
||||
await ctx.prompter.note(
|
||||
"If OAuth fails, verify your MiniMax account has portal access and try again.",
|
||||
"MiniMax OAuth",
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function registerMinimaxProviders(api: OpenClawPluginApi) {
|
||||
api.registerProvider({
|
||||
id: API_PROVIDER_ID,
|
||||
label: PROVIDER_LABEL,
|
||||
hookAliases: ["minimax-cn"],
|
||||
docsPath: "/providers/minimax",
|
||||
envVars: ["MINIMAX_API_KEY"],
|
||||
auth: [
|
||||
createProviderApiKeyAuthMethod({
|
||||
providerId: API_PROVIDER_ID,
|
||||
methodId: "api-global",
|
||||
label: "MiniMax API key (Global)",
|
||||
hint: "Global endpoint - api.minimax.io",
|
||||
optionKey: "minimaxApiKey",
|
||||
flagName: "--minimax-api-key",
|
||||
envVar: "MINIMAX_API_KEY",
|
||||
promptMessage:
|
||||
"Enter MiniMax API key (sk-api- or sk-cp-)\nhttps://platform.minimax.io/user-center/basic-information/interface-key",
|
||||
profileId: "minimax:global",
|
||||
allowProfile: false,
|
||||
defaultModel: apiModelRef(DEFAULT_MODEL),
|
||||
expectedProviders: ["minimax"],
|
||||
applyConfig: (cfg) => applyMinimaxApiConfig(cfg),
|
||||
wizard: {
|
||||
choiceId: "minimax-global-api",
|
||||
choiceLabel: "MiniMax API key (Global)",
|
||||
choiceHint: "Global endpoint - api.minimax.io",
|
||||
groupId: "minimax",
|
||||
groupLabel: "MiniMax",
|
||||
groupHint: "M2.7 (recommended)",
|
||||
},
|
||||
}),
|
||||
createProviderApiKeyAuthMethod({
|
||||
providerId: API_PROVIDER_ID,
|
||||
methodId: "api-cn",
|
||||
label: "MiniMax API key (CN)",
|
||||
hint: "CN endpoint - api.minimaxi.com",
|
||||
optionKey: "minimaxApiKey",
|
||||
flagName: "--minimax-api-key",
|
||||
envVar: "MINIMAX_API_KEY",
|
||||
promptMessage:
|
||||
"Enter MiniMax CN API key (sk-api- or sk-cp-)\nhttps://platform.minimaxi.com/user-center/basic-information/interface-key",
|
||||
profileId: "minimax:cn",
|
||||
allowProfile: false,
|
||||
defaultModel: apiModelRef(DEFAULT_MODEL),
|
||||
expectedProviders: ["minimax", "minimax-cn"],
|
||||
applyConfig: (cfg) => applyMinimaxApiConfigCn(cfg),
|
||||
wizard: {
|
||||
choiceId: "minimax-cn-api",
|
||||
choiceLabel: "MiniMax API key (CN)",
|
||||
choiceHint: "CN endpoint - api.minimaxi.com",
|
||||
groupId: "minimax",
|
||||
groupLabel: "MiniMax",
|
||||
groupHint: "M2.7 (recommended)",
|
||||
},
|
||||
}),
|
||||
],
|
||||
catalog: {
|
||||
order: "simple",
|
||||
run: async (ctx) => resolveApiCatalog(ctx),
|
||||
},
|
||||
resolveUsageAuth: async (ctx) => {
|
||||
const portalOauth = await ctx.resolveOAuthToken({ provider: PORTAL_PROVIDER_ID });
|
||||
if (portalOauth) {
|
||||
return portalOauth;
|
||||
}
|
||||
const apiKey = ctx.resolveApiKeyFromConfigAndStore({
|
||||
providerIds: [API_PROVIDER_ID, PORTAL_PROVIDER_ID],
|
||||
envDirect: MINIMAX_USAGE_ENV_VAR_KEYS.map((name) => ctx.env[name]),
|
||||
});
|
||||
return apiKey ? { token: apiKey } : null;
|
||||
},
|
||||
...HYBRID_ANTHROPIC_OPENAI_REPLAY_HOOKS,
|
||||
...MINIMAX_FAST_MODE_STREAM_HOOKS,
|
||||
resolveReasoningOutputMode: () => resolveMinimaxReasoningOutputMode(),
|
||||
isModernModelRef: ({ modelId }) => isMiniMaxModernModelId(modelId),
|
||||
fetchUsageSnapshot: async (ctx) =>
|
||||
await fetchMinimaxUsage(ctx.token, ctx.timeoutMs, ctx.fetchFn),
|
||||
});
|
||||
|
||||
api.registerProvider({
|
||||
id: PORTAL_PROVIDER_ID,
|
||||
label: PROVIDER_LABEL,
|
||||
hookAliases: ["minimax-portal-cn"],
|
||||
docsPath: "/providers/minimax",
|
||||
envVars: ["MINIMAX_OAUTH_TOKEN", "MINIMAX_API_KEY"],
|
||||
catalog: {
|
||||
run: async (ctx) => resolvePortalCatalog(ctx),
|
||||
},
|
||||
auth: [
|
||||
{
|
||||
id: "oauth",
|
||||
label: "MiniMax OAuth (Global)",
|
||||
hint: "Global endpoint - api.minimax.io",
|
||||
kind: "device_code",
|
||||
wizard: {
|
||||
choiceId: "minimax-global-oauth",
|
||||
choiceLabel: "MiniMax OAuth (Global)",
|
||||
choiceHint: "Global endpoint - api.minimax.io",
|
||||
groupId: "minimax",
|
||||
groupLabel: "MiniMax",
|
||||
groupHint: "M2.7 (recommended)",
|
||||
},
|
||||
run: createOAuthHandler("global"),
|
||||
},
|
||||
{
|
||||
id: "oauth-cn",
|
||||
label: "MiniMax OAuth (CN)",
|
||||
hint: "CN endpoint - api.minimaxi.com",
|
||||
kind: "device_code",
|
||||
wizard: {
|
||||
choiceId: "minimax-cn-oauth",
|
||||
choiceLabel: "MiniMax OAuth (CN)",
|
||||
choiceHint: "CN endpoint - api.minimaxi.com",
|
||||
groupId: "minimax",
|
||||
groupLabel: "MiniMax",
|
||||
groupHint: "M2.7 (recommended)",
|
||||
},
|
||||
run: createOAuthHandler("cn"),
|
||||
},
|
||||
],
|
||||
...HYBRID_ANTHROPIC_OPENAI_REPLAY_HOOKS,
|
||||
...MINIMAX_FAST_MODE_STREAM_HOOKS,
|
||||
resolveReasoningOutputMode: () => resolveMinimaxReasoningOutputMode(),
|
||||
isModernModelRef: ({ modelId }) => isMiniMaxModernModelId(modelId),
|
||||
});
|
||||
}
|
||||
@@ -2,11 +2,10 @@ import { NON_ENV_SECRETREF_MARKER } from "openclaw/plugin-sdk/provider-auth-runt
|
||||
import { createNonExitingRuntime } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { withEnv } from "openclaw/plugin-sdk/testing";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { capturePluginRegistration } from "../../src/plugins/captured-registration.js";
|
||||
import { createWizardPrompter } from "../../test/helpers/wizard-prompter.js";
|
||||
import xaiPlugin from "./index.js";
|
||||
import { resolveXaiCatalogEntry } from "./model-definitions.js";
|
||||
import { isModernXaiModel, resolveXaiForwardCompatModel } from "./provider-models.js";
|
||||
import { resolveFallbackXaiAuth } from "./src/tool-auth-shared.js";
|
||||
import { __testing, createXaiWebSearchProvider } from "./web-search.js";
|
||||
|
||||
const {
|
||||
@@ -188,84 +187,63 @@ describe("xai web search config resolution", () => {
|
||||
});
|
||||
|
||||
it("reuses the plugin web search api key for provider auth fallback", () => {
|
||||
const captured = capturePluginRegistration(xaiPlugin);
|
||||
const provider = captured.providers[0];
|
||||
expect(
|
||||
provider?.resolveSyntheticAuth?.({
|
||||
config: {
|
||||
plugins: {
|
||||
entries: {
|
||||
xai: {
|
||||
config: {
|
||||
webSearch: {
|
||||
apiKey: "xai-provider-fallback", // pragma: allowlist secret
|
||||
},
|
||||
resolveFallbackXaiAuth({
|
||||
plugins: {
|
||||
entries: {
|
||||
xai: {
|
||||
config: {
|
||||
webSearch: {
|
||||
apiKey: "xai-provider-fallback", // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
provider: "xai",
|
||||
providerConfig: undefined,
|
||||
}),
|
||||
} as never),
|
||||
).toEqual({
|
||||
apiKey: "xai-provider-fallback",
|
||||
source: "plugins.entries.xai.config.webSearch.apiKey",
|
||||
mode: "api-key",
|
||||
});
|
||||
});
|
||||
|
||||
it("reuses the legacy grok web search api key for provider auth fallback", () => {
|
||||
const captured = capturePluginRegistration(xaiPlugin);
|
||||
const provider = captured.providers[0];
|
||||
expect(
|
||||
provider?.resolveSyntheticAuth?.({
|
||||
config: {
|
||||
tools: {
|
||||
web: {
|
||||
search: {
|
||||
grok: {
|
||||
apiKey: "xai-legacy-fallback", // pragma: allowlist secret
|
||||
},
|
||||
resolveFallbackXaiAuth({
|
||||
tools: {
|
||||
web: {
|
||||
search: {
|
||||
grok: {
|
||||
apiKey: "xai-legacy-fallback", // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
provider: "xai",
|
||||
providerConfig: undefined,
|
||||
}),
|
||||
} as never),
|
||||
).toEqual({
|
||||
apiKey: "xai-legacy-fallback",
|
||||
source: "tools.web.search.grok.apiKey",
|
||||
mode: "api-key",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns a managed marker for SecretRef-backed plugin auth fallback", () => {
|
||||
const captured = capturePluginRegistration(xaiPlugin);
|
||||
const provider = captured.providers[0];
|
||||
expect(
|
||||
provider?.resolveSyntheticAuth?.({
|
||||
config: {
|
||||
plugins: {
|
||||
entries: {
|
||||
xai: {
|
||||
config: {
|
||||
webSearch: {
|
||||
apiKey: { source: "file", provider: "vault", id: "/xai/api-key" },
|
||||
},
|
||||
resolveFallbackXaiAuth({
|
||||
plugins: {
|
||||
entries: {
|
||||
xai: {
|
||||
config: {
|
||||
webSearch: {
|
||||
apiKey: { source: "file", provider: "vault", id: "/xai/api-key" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
provider: "xai",
|
||||
providerConfig: undefined,
|
||||
}),
|
||||
} as never),
|
||||
).toEqual({
|
||||
apiKey: NON_ENV_SECRETREF_MARKER,
|
||||
source: "plugins.entries.xai.config.webSearch.apiKey",
|
||||
mode: "api-key",
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user