diff --git a/extensions/sglang/index.ts b/extensions/sglang/index.ts index 4c9102caebc..64143026592 100644 --- a/extensions/sglang/index.ts +++ b/extensions/sglang/index.ts @@ -1,13 +1,11 @@ import { buildSglangProvider, configureOpenAICompatibleSelfHostedProviderNonInteractive, + discoverOpenAICompatibleSelfHostedProvider, emptyPluginConfigSchema, - promptAndConfigureOpenAICompatibleSelfHostedProvider, + promptAndConfigureOpenAICompatibleSelfHostedProviderAuth, type OpenClawPluginApi, - type ProviderAuthContext, type ProviderAuthMethodNonInteractiveContext, - type ProviderAuthResult, - type ProviderDiscoveryContext, } from "openclaw/plugin-sdk/core"; const PROVIDER_ID = "sglang"; @@ -30,8 +28,8 @@ const sglangPlugin = { label: "SGLang", hint: "Fast self-hosted OpenAI-compatible server", kind: "custom", - run: async (ctx: ProviderAuthContext): Promise => { - const result = await promptAndConfigureOpenAICompatibleSelfHostedProvider({ + run: async (ctx) => + promptAndConfigureOpenAICompatibleSelfHostedProviderAuth({ cfg: ctx.config, prompter: ctx.prompter, providerId: PROVIDER_ID, @@ -39,18 +37,7 @@ const sglangPlugin = { defaultBaseUrl: DEFAULT_BASE_URL, defaultApiKeyEnvVar: "SGLANG_API_KEY", modelPlaceholder: "Qwen/Qwen3-8B", - }); - return { - profiles: [ - { - profileId: result.profileId, - credential: result.credential, - }, - ], - configPatch: result.config, - defaultModel: result.modelRef, - }; - }, + }), runNonInteractive: async (ctx: ProviderAuthMethodNonInteractiveContext) => configureOpenAICompatibleSelfHostedProviderNonInteractive({ ctx, @@ -64,21 +51,12 @@ const sglangPlugin = { ], discovery: { order: "late", - run: async (ctx: ProviderDiscoveryContext) => { - if (ctx.config.models?.providers?.sglang) { - return null; - } - const { apiKey, discoveryApiKey } = ctx.resolveProviderApiKey(PROVIDER_ID); - if (!apiKey) { - return null; - } - return { - provider: { - ...(await buildSglangProvider({ apiKey: discoveryApiKey })), - apiKey, - }, - }; - }, + run: async (ctx) => + discoverOpenAICompatibleSelfHostedProvider({ + ctx, + providerId: PROVIDER_ID, + buildProvider: buildSglangProvider, + }), }, wizard: { onboarding: { diff --git a/extensions/vllm/index.ts b/extensions/vllm/index.ts index fd0a5e18914..cb865de4dfd 100644 --- a/extensions/vllm/index.ts +++ b/extensions/vllm/index.ts @@ -1,13 +1,11 @@ import { buildVllmProvider, configureOpenAICompatibleSelfHostedProviderNonInteractive, + discoverOpenAICompatibleSelfHostedProvider, emptyPluginConfigSchema, - promptAndConfigureOpenAICompatibleSelfHostedProvider, + promptAndConfigureOpenAICompatibleSelfHostedProviderAuth, type OpenClawPluginApi, - type ProviderAuthContext, type ProviderAuthMethodNonInteractiveContext, - type ProviderAuthResult, - type ProviderDiscoveryContext, } from "openclaw/plugin-sdk/core"; const PROVIDER_ID = "vllm"; @@ -30,8 +28,8 @@ const vllmPlugin = { label: "vLLM", hint: "Local/self-hosted OpenAI-compatible server", kind: "custom", - run: async (ctx: ProviderAuthContext): Promise => { - const result = await promptAndConfigureOpenAICompatibleSelfHostedProvider({ + run: async (ctx) => + promptAndConfigureOpenAICompatibleSelfHostedProviderAuth({ cfg: ctx.config, prompter: ctx.prompter, providerId: PROVIDER_ID, @@ -39,18 +37,7 @@ const vllmPlugin = { defaultBaseUrl: DEFAULT_BASE_URL, defaultApiKeyEnvVar: "VLLM_API_KEY", modelPlaceholder: "meta-llama/Meta-Llama-3-8B-Instruct", - }); - return { - profiles: [ - { - profileId: result.profileId, - credential: result.credential, - }, - ], - configPatch: result.config, - defaultModel: result.modelRef, - }; - }, + }), runNonInteractive: async (ctx: ProviderAuthMethodNonInteractiveContext) => configureOpenAICompatibleSelfHostedProviderNonInteractive({ ctx, @@ -64,21 +51,12 @@ const vllmPlugin = { ], discovery: { order: "late", - run: async (ctx: ProviderDiscoveryContext) => { - if (ctx.config.models?.providers?.vllm) { - return null; - } - const { apiKey, discoveryApiKey } = ctx.resolveProviderApiKey(PROVIDER_ID); - if (!apiKey) { - return null; - } - return { - provider: { - ...(await buildVllmProvider({ apiKey: discoveryApiKey })), - apiKey, - }, - }; - }, + run: async (ctx) => + discoverOpenAICompatibleSelfHostedProvider({ + ctx, + providerId: PROVIDER_ID, + buildProvider: buildVllmProvider, + }), }, wizard: { onboarding: { diff --git a/src/commands/self-hosted-provider-setup.ts b/src/commands/self-hosted-provider-setup.ts index a92b7512d9a..c067d797f15 100644 --- a/src/commands/self-hosted-provider-setup.ts +++ b/src/commands/self-hosted-provider-setup.ts @@ -2,6 +2,7 @@ import { upsertAuthProfileWithLock } from "../agents/auth-profiles.js"; import type { ApiKeyCredential, AuthProfileCredential } from "../agents/auth-profiles/types.js"; import type { OpenClawConfig } from "../config/config.js"; import type { + ProviderDiscoveryContext, ProviderAuthResult, ProviderAuthMethodNonInteractiveContext, ProviderNonInteractiveApiKeyResult, @@ -181,6 +182,28 @@ export async function promptAndConfigureOpenAICompatibleSelfHostedProviderAuth( return buildSelfHostedProviderAuthResult(result); } +export async function discoverOpenAICompatibleSelfHostedProvider< + T extends Record, +>(params: { + ctx: ProviderDiscoveryContext; + providerId: string; + buildProvider: (params: { apiKey?: string }) => Promise; +}): Promise<{ provider: T & { apiKey: string } } | null> { + if (params.ctx.config.models?.providers?.[params.providerId]) { + return null; + } + const { apiKey, discoveryApiKey } = params.ctx.resolveProviderApiKey(params.providerId); + if (!apiKey) { + return null; + } + return { + provider: { + ...(await params.buildProvider({ apiKey: discoveryApiKey })), + apiKey, + }, + }; +} + function buildMissingNonInteractiveModelIdMessage(params: { authChoice: string; providerLabel: string; diff --git a/src/plugin-sdk/core.ts b/src/plugin-sdk/core.ts index 2a14be3b3ce..671071ebc6f 100644 --- a/src/plugin-sdk/core.ts +++ b/src/plugin-sdk/core.ts @@ -17,7 +17,9 @@ export { buildOauthProviderAuthResult } from "./provider-auth-result.js"; export { applyProviderDefaultModel, configureOpenAICompatibleSelfHostedProviderNonInteractive, + discoverOpenAICompatibleSelfHostedProvider, promptAndConfigureOpenAICompatibleSelfHostedProvider, + promptAndConfigureOpenAICompatibleSelfHostedProviderAuth, SELF_HOSTED_DEFAULT_CONTEXT_WINDOW, SELF_HOSTED_DEFAULT_COST, SELF_HOSTED_DEFAULT_MAX_TOKENS,