refactor: add non-interactive provider plugin setup

This commit is contained in:
Peter Steinberger
2026-03-13 01:08:29 +00:00
parent bcbf429d6b
commit 87ad1ce9b1
13 changed files with 490 additions and 63 deletions

View File

@@ -4,8 +4,10 @@ import {
ensureOllamaModelPulled,
OLLAMA_DEFAULT_BASE_URL,
promptAndConfigureOllama,
configureOllamaNonInteractive,
type OpenClawPluginApi,
type ProviderAuthContext,
type ProviderAuthMethodNonInteractiveContext,
type ProviderAuthResult,
type ProviderDiscoveryContext,
} from "openclaw/plugin-sdk/core";
@@ -50,6 +52,12 @@ const ollamaPlugin = {
defaultModel: `ollama/${result.defaultModelId}`,
};
},
runNonInteractive: async (ctx: ProviderAuthMethodNonInteractiveContext) =>
configureOllamaNonInteractive({
nextConfig: ctx.config,
opts: ctx.opts,
runtime: ctx.runtime,
}),
},
],
discovery: {

View File

@@ -1,5 +1,5 @@
{
"name": "@openclaw/ollama-provider",
"name": "@openclaw/ollama",
"version": "2026.3.11",
"private": true,
"description": "OpenClaw Ollama provider plugin",

View File

@@ -1,9 +1,11 @@
import {
buildSglangProvider,
configureOpenAICompatibleSelfHostedProviderNonInteractive,
emptyPluginConfigSchema,
promptAndConfigureOpenAICompatibleSelfHostedProvider,
type OpenClawPluginApi,
type ProviderAuthContext,
type ProviderAuthMethodNonInteractiveContext,
type ProviderAuthResult,
type ProviderDiscoveryContext,
} from "openclaw/plugin-sdk/core";
@@ -49,6 +51,15 @@ const sglangPlugin = {
defaultModel: result.modelRef,
};
},
runNonInteractive: async (ctx: ProviderAuthMethodNonInteractiveContext) =>
configureOpenAICompatibleSelfHostedProviderNonInteractive({
ctx,
providerId: PROVIDER_ID,
providerLabel: "SGLang",
defaultBaseUrl: DEFAULT_BASE_URL,
defaultApiKeyEnvVar: "SGLANG_API_KEY",
modelPlaceholder: "Qwen/Qwen3-8B",
}),
},
],
discovery: {

View File

@@ -1,5 +1,5 @@
{
"name": "@openclaw/sglang-provider",
"name": "@openclaw/sglang",
"version": "2026.3.11",
"private": true,
"description": "OpenClaw SGLang provider plugin",

View File

@@ -1,9 +1,11 @@
import {
buildVllmProvider,
configureOpenAICompatibleSelfHostedProviderNonInteractive,
emptyPluginConfigSchema,
promptAndConfigureOpenAICompatibleSelfHostedProvider,
type OpenClawPluginApi,
type ProviderAuthContext,
type ProviderAuthMethodNonInteractiveContext,
type ProviderAuthResult,
type ProviderDiscoveryContext,
} from "openclaw/plugin-sdk/core";
@@ -49,6 +51,15 @@ const vllmPlugin = {
defaultModel: result.modelRef,
};
},
runNonInteractive: async (ctx: ProviderAuthMethodNonInteractiveContext) =>
configureOpenAICompatibleSelfHostedProviderNonInteractive({
ctx,
providerId: PROVIDER_ID,
providerLabel: "vLLM",
defaultBaseUrl: DEFAULT_BASE_URL,
defaultApiKeyEnvVar: "VLLM_API_KEY",
modelPlaceholder: "meta-llama/Meta-Llama-3-8B-Instruct",
}),
},
],
discovery: {

View File

@@ -1,5 +1,5 @@
{
"name": "@openclaw/vllm-provider",
"name": "@openclaw/vllm",
"version": "2026.3.11",
"private": true,
"description": "OpenClaw vLLM provider plugin",

View File

@@ -17,7 +17,7 @@ type OnboardEnv = {
runtime: NonInteractiveRuntime;
};
const ensureWorkspaceAndSessionsMock = vi.fn(async (..._args: unknown[]) => {});
const ensureWorkspaceAndSessionsMock = vi.hoisted(() => vi.fn(async (..._args: unknown[]) => {}));
vi.mock("./onboard-helpers.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("./onboard-helpers.js")>();
@@ -474,14 +474,63 @@ describe("onboard (non-interactive): provider auth", () => {
});
});
it("rejects vLLM auth choice in non-interactive mode", async () => {
await withOnboardEnv("openclaw-onboard-vllm-non-interactive-", async ({ runtime }) => {
await expect(
runNonInteractiveOnboardingWithDefaults(runtime, {
authChoice: "vllm",
skipSkills: true,
}),
).rejects.toThrow('Auth choice "vllm" requires interactive mode.');
it("configures vLLM via the provider plugin in non-interactive mode", async () => {
await withOnboardEnv("openclaw-onboard-vllm-non-interactive-", async (env) => {
const cfg = await runOnboardingAndReadConfig(env, {
authChoice: "vllm",
customBaseUrl: "http://127.0.0.1:8100/v1",
customApiKey: "vllm-test-key", // pragma: allowlist secret
customModelId: "Qwen/Qwen3-8B",
});
expect(cfg.auth?.profiles?.["vllm:default"]?.provider).toBe("vllm");
expect(cfg.auth?.profiles?.["vllm:default"]?.mode).toBe("api_key");
expect(cfg.models?.providers?.vllm).toEqual({
baseUrl: "http://127.0.0.1:8100/v1",
api: "openai-completions",
apiKey: "VLLM_API_KEY",
models: [
expect.objectContaining({
id: "Qwen/Qwen3-8B",
}),
],
});
expect(cfg.agents?.defaults?.model?.primary).toBe("vllm/Qwen/Qwen3-8B");
await expectApiKeyProfile({
profileId: "vllm:default",
provider: "vllm",
key: "vllm-test-key",
});
});
});
it("configures SGLang via the provider plugin in non-interactive mode", async () => {
await withOnboardEnv("openclaw-onboard-sglang-non-interactive-", async (env) => {
const cfg = await runOnboardingAndReadConfig(env, {
authChoice: "sglang",
customBaseUrl: "http://127.0.0.1:31000/v1",
customApiKey: "sglang-test-key", // pragma: allowlist secret
customModelId: "Qwen/Qwen3-32B",
});
expect(cfg.auth?.profiles?.["sglang:default"]?.provider).toBe("sglang");
expect(cfg.auth?.profiles?.["sglang:default"]?.mode).toBe("api_key");
expect(cfg.models?.providers?.sglang).toEqual({
baseUrl: "http://127.0.0.1:31000/v1",
api: "openai-completions",
apiKey: "SGLANG_API_KEY",
models: [
expect.objectContaining({
id: "Qwen/Qwen3-32B",
}),
],
});
expect(cfg.agents?.defaults?.model?.primary).toBe("sglang/Qwen/Qwen3-32B");
await expectApiKeyProfile({
profileId: "sglang:default",
provider: "sglang",
key: "sglang-test-key",
});
});
});

View File

@@ -0,0 +1,121 @@
import { resolveDefaultAgentId, resolveAgentWorkspaceDir } from "../../../agents/agent-scope.js";
import type { ApiKeyCredential } from "../../../agents/auth-profiles/types.js";
import { resolveDefaultAgentWorkspaceDir } from "../../../agents/workspace.js";
import type { OpenClawConfig } from "../../../config/config.js";
import { enablePluginInConfig } from "../../../plugins/enable.js";
import {
PROVIDER_PLUGIN_CHOICE_PREFIX,
resolveProviderPluginChoice,
} from "../../../plugins/provider-wizard.js";
import { resolvePluginProviders } from "../../../plugins/providers.js";
import type {
ProviderNonInteractiveApiKeyCredentialParams,
ProviderResolveNonInteractiveApiKeyParams,
} from "../../../plugins/types.js";
import type { RuntimeEnv } from "../../../runtime.js";
import { resolvePreferredProviderForAuthChoice } from "../../auth-choice.preferred-provider.js";
import type { OnboardOptions } from "../../onboard-types.js";
function buildIsolatedProviderResolutionConfig(
cfg: OpenClawConfig,
providerId: string | undefined,
): OpenClawConfig {
if (!providerId) {
return cfg;
}
const allow = new Set(cfg.plugins?.allow ?? []);
allow.add(providerId);
return {
...cfg,
plugins: {
...cfg.plugins,
allow: Array.from(allow),
entries: {
...cfg.plugins?.entries,
[providerId]: {
...cfg.plugins?.entries?.[providerId],
enabled: true,
},
},
},
};
}
export async function applyNonInteractivePluginProviderChoice(params: {
nextConfig: OpenClawConfig;
authChoice: string;
opts: OnboardOptions;
runtime: RuntimeEnv;
baseConfig: OpenClawConfig;
resolveApiKey: (input: ProviderResolveNonInteractiveApiKeyParams) => Promise<{
key: string;
source: "profile" | "env" | "flag";
envVarName?: string;
} | null>;
toApiKeyCredential: (
input: ProviderNonInteractiveApiKeyCredentialParams,
) => ApiKeyCredential | null;
}): Promise<OpenClawConfig | null | undefined> {
const agentId = resolveDefaultAgentId(params.nextConfig);
const workspaceDir =
resolveAgentWorkspaceDir(params.nextConfig, agentId) ?? resolveDefaultAgentWorkspaceDir();
const prefixedProviderId = params.authChoice.startsWith(PROVIDER_PLUGIN_CHOICE_PREFIX)
? params.authChoice.slice(PROVIDER_PLUGIN_CHOICE_PREFIX.length).split(":", 1)[0]?.trim()
: undefined;
const preferredProviderId =
prefixedProviderId ||
resolvePreferredProviderForAuthChoice({
choice: params.authChoice,
config: params.nextConfig,
workspaceDir,
});
const resolutionConfig = buildIsolatedProviderResolutionConfig(
params.nextConfig,
preferredProviderId,
);
const providerChoice = resolveProviderPluginChoice({
providers: resolvePluginProviders({
config: resolutionConfig,
workspaceDir,
}),
choice: params.authChoice,
});
if (!providerChoice) {
return undefined;
}
const enableResult = enablePluginInConfig(
params.nextConfig,
providerChoice.provider.pluginId ?? providerChoice.provider.id,
);
if (!enableResult.enabled) {
params.runtime.error(
`${providerChoice.provider.label} plugin is disabled (${enableResult.reason ?? "blocked"}).`,
);
params.runtime.exit(1);
return null;
}
const method = providerChoice.method;
if (!method.runNonInteractive) {
params.runtime.error(
[
`Auth choice "${params.authChoice}" requires interactive mode.`,
`The ${providerChoice.provider.label} provider plugin does not implement non-interactive setup.`,
].join("\n"),
);
params.runtime.exit(1);
return null;
}
return method.runNonInteractive({
authChoice: params.authChoice,
config: enableResult.config,
baseConfig: params.baseConfig,
opts: params.opts,
runtime: params.runtime,
workspaceDir,
resolveApiKey: params.resolveApiKey,
toApiKeyCredential: params.toApiKeyCredential,
});
}

View File

@@ -1,4 +1,5 @@
import { upsertAuthProfile } from "../../../agents/auth-profiles.js";
import type { ApiKeyCredential } from "../../../agents/auth-profiles/types.js";
import { normalizeProviderId } from "../../../agents/model-selection.js";
import { parseDurationMs } from "../../../cli/parse-duration.js";
import type { OpenClawConfig } from "../../../config/config.js";
@@ -8,7 +9,6 @@ import { resolveDefaultSecretProviderAlias } from "../../../secrets/ref-contract
import { normalizeSecretInput } from "../../../utils/normalize-secret-input.js";
import { normalizeSecretInputModeInput } from "../../auth-choice.apply-helpers.js";
import { buildTokenProfileId, validateAnthropicSetupToken } from "../../auth-token.js";
import { configureOllamaNonInteractive } from "../../ollama-setup.js";
import {
applyAuthProfileConfig,
applyCloudflareAiGatewayConfig,
@@ -29,6 +29,7 @@ import type { AuthChoice, OnboardOptions } from "../../onboard-types.js";
import { detectZaiEndpoint } from "../../zai-endpoint-detect.js";
import { resolveNonInteractiveApiKey } from "../api-keys.js";
import { applySimpleNonInteractiveApiKeyChoice } from "./auth-choice.api-key-providers.js";
import { applyNonInteractivePluginProviderChoice } from "./auth-choice.plugin-providers.js";
type ResolvedNonInteractiveApiKey = NonNullable<
Awaited<ReturnType<typeof resolveNonInteractiveApiKey>>
@@ -83,6 +84,46 @@ export async function applyNonInteractiveAuthChoice(params: {
...input,
secretInputMode: requestedSecretInputMode,
});
const toApiKeyCredential = (params: {
provider: string;
resolved: ResolvedNonInteractiveApiKey;
email?: string;
metadata?: Record<string, string>;
}): ApiKeyCredential | null => {
const storeSecretRef = requestedSecretInputMode === "ref" && params.resolved.source === "env"; // pragma: allowlist secret
if (storeSecretRef) {
if (!params.resolved.envVarName) {
runtime.error(
[
`--secret-input-mode ref requires an explicit environment variable for provider "${params.provider}".`,
"Set the provider API key env var and retry, or use --secret-input-mode plaintext.",
].join("\n"),
);
runtime.exit(1);
return null;
}
return {
type: "api_key",
provider: params.provider,
keyRef: {
source: "env",
provider: resolveDefaultSecretProviderAlias(baseConfig, "env", {
preferFirstProviderForSource: true,
}),
id: params.resolved.envVarName,
},
...(params.email ? { email: params.email } : {}),
...(params.metadata ? { metadata: params.metadata } : {}),
};
}
return {
type: "api_key",
provider: params.provider,
key: params.resolved.key,
...(params.email ? { email: params.email } : {}),
...(params.metadata ? { metadata: params.metadata } : {}),
};
};
const maybeSetResolvedApiKey = async (
resolved: ResolvedNonInteractiveApiKey,
setter: (value: SecretInput) => Promise<void> | void,
@@ -120,19 +161,22 @@ export async function applyNonInteractiveAuthChoice(params: {
return null;
}
if (authChoice === "vllm") {
runtime.error(
[
'Auth choice "vllm" requires interactive mode.',
"Use interactive onboard/configure to enter base URL, API key, and model ID.",
].join("\n"),
);
runtime.exit(1);
return null;
}
if (authChoice === "ollama") {
return configureOllamaNonInteractive({ nextConfig, opts, runtime });
const pluginProviderChoice = await applyNonInteractivePluginProviderChoice({
nextConfig,
authChoice,
opts,
runtime,
baseConfig,
resolveApiKey: (input) =>
resolveApiKey({
...input,
cfg: baseConfig,
runtime,
}),
toApiKeyCredential,
});
if (pluginProviderChoice !== undefined) {
return pluginProviderChoice;
}
if (authChoice === "token") {

View File

@@ -1,6 +1,12 @@
import type { AuthProfileCredential } from "../agents/auth-profiles/types.js";
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 {
ProviderAuthMethodNonInteractiveContext,
ProviderNonInteractiveApiKeyResult,
} from "../plugins/types.js";
import type { WizardPrompter } from "../wizard/prompts.js";
import { applyAuthProfileConfig } from "./onboard-auth.js";
export const SELF_HOSTED_DEFAULT_CONTEXT_WINDOW = 128000;
export const SELF_HOSTED_DEFAULT_MAX_TOKENS = 8192;
@@ -33,6 +39,52 @@ export function applyProviderDefaultModel(cfg: OpenClawConfig, modelRef: string)
};
}
function buildOpenAICompatibleSelfHostedProviderConfig(params: {
cfg: OpenClawConfig;
providerId: string;
baseUrl: string;
providerApiKey: string;
modelId: string;
input?: Array<"text" | "image">;
reasoning?: boolean;
contextWindow?: number;
maxTokens?: number;
}): { config: OpenClawConfig; modelId: string; modelRef: string; profileId: string } {
const modelRef = `${params.providerId}/${params.modelId}`;
const profileId = `${params.providerId}:default`;
return {
config: {
...params.cfg,
models: {
...params.cfg.models,
mode: params.cfg.models?.mode ?? "merge",
providers: {
...params.cfg.models?.providers,
[params.providerId]: {
baseUrl: params.baseUrl,
api: "openai-completions",
apiKey: params.providerApiKey,
models: [
{
id: params.modelId,
name: params.modelId,
reasoning: params.reasoning ?? false,
input: params.input ?? ["text"],
cost: SELF_HOSTED_DEFAULT_COST,
contextWindow: params.contextWindow ?? SELF_HOSTED_DEFAULT_CONTEXT_WINDOW,
maxTokens: params.maxTokens ?? SELF_HOSTED_DEFAULT_MAX_TOKENS,
},
],
},
},
},
},
modelId: params.modelId,
modelRef,
profileId,
};
}
export async function promptAndConfigureOpenAICompatibleSelfHostedProvider(params: {
cfg: OpenClawConfig;
prompter: WizardPrompter;
@@ -74,46 +126,125 @@ export async function promptAndConfigureOpenAICompatibleSelfHostedProvider(param
.replace(/\/+$/, "");
const apiKey = String(apiKeyRaw ?? "").trim();
const modelId = String(modelIdRaw ?? "").trim();
const modelRef = `${params.providerId}/${modelId}`;
const profileId = `${params.providerId}:default`;
const credential: AuthProfileCredential = {
type: "api_key",
provider: params.providerId,
key: apiKey,
};
const nextConfig: OpenClawConfig = {
...params.cfg,
models: {
...params.cfg.models,
mode: params.cfg.models?.mode ?? "merge",
providers: {
...params.cfg.models?.providers,
[params.providerId]: {
baseUrl,
api: "openai-completions",
apiKey: params.defaultApiKeyEnvVar,
models: [
{
id: modelId,
name: modelId,
reasoning: params.reasoning ?? false,
input: params.input ?? ["text"],
cost: SELF_HOSTED_DEFAULT_COST,
contextWindow: params.contextWindow ?? SELF_HOSTED_DEFAULT_CONTEXT_WINDOW,
maxTokens: params.maxTokens ?? SELF_HOSTED_DEFAULT_MAX_TOKENS,
},
],
},
},
},
};
const configured = buildOpenAICompatibleSelfHostedProviderConfig({
cfg: params.cfg,
providerId: params.providerId,
baseUrl,
providerApiKey: params.defaultApiKeyEnvVar,
modelId,
input: params.input,
reasoning: params.reasoning,
contextWindow: params.contextWindow,
maxTokens: params.maxTokens,
});
return {
config: nextConfig,
config: configured.config,
credential,
modelId,
modelRef,
profileId,
modelId: configured.modelId,
modelRef: configured.modelRef,
profileId: configured.profileId,
};
}
function buildMissingNonInteractiveModelIdMessage(params: {
authChoice: string;
providerLabel: string;
modelPlaceholder: string;
}): string {
return [
`Missing --custom-model-id for --auth-choice ${params.authChoice}.`,
`Pass the ${params.providerLabel} model id to use, for example ${params.modelPlaceholder}.`,
].join("\n");
}
function buildSelfHostedProviderCredential(params: {
ctx: ProviderAuthMethodNonInteractiveContext;
providerId: string;
resolved: ProviderNonInteractiveApiKeyResult;
}): ApiKeyCredential | null {
return params.ctx.toApiKeyCredential({
provider: params.providerId,
resolved: params.resolved,
});
}
export async function configureOpenAICompatibleSelfHostedProviderNonInteractive(params: {
ctx: ProviderAuthMethodNonInteractiveContext;
providerId: string;
providerLabel: string;
defaultBaseUrl: string;
defaultApiKeyEnvVar: string;
modelPlaceholder: string;
input?: Array<"text" | "image">;
reasoning?: boolean;
contextWindow?: number;
maxTokens?: number;
}): Promise<OpenClawConfig | null> {
const baseUrl = (params.ctx.opts.customBaseUrl?.trim() || params.defaultBaseUrl).replace(
/\/+$/,
"",
);
const modelId = params.ctx.opts.customModelId?.trim();
if (!modelId) {
params.ctx.runtime.error(
buildMissingNonInteractiveModelIdMessage({
authChoice: params.ctx.authChoice,
providerLabel: params.providerLabel,
modelPlaceholder: params.modelPlaceholder,
}),
);
params.ctx.runtime.exit(1);
return null;
}
const resolved = await params.ctx.resolveApiKey({
provider: params.providerId,
flagValue: params.ctx.opts.customApiKey,
flagName: "--custom-api-key",
envVar: params.defaultApiKeyEnvVar,
envVarName: params.defaultApiKeyEnvVar,
});
if (!resolved) {
return null;
}
const credential = buildSelfHostedProviderCredential({
ctx: params.ctx,
providerId: params.providerId,
resolved,
});
if (!credential) {
return null;
}
const configured = buildOpenAICompatibleSelfHostedProviderConfig({
cfg: params.ctx.config,
providerId: params.providerId,
baseUrl,
providerApiKey: params.defaultApiKeyEnvVar,
modelId,
input: params.input,
reasoning: params.reasoning,
contextWindow: params.contextWindow,
maxTokens: params.maxTokens,
});
await upsertAuthProfileWithLock({
profileId: configured.profileId,
credential,
agentDir: params.ctx.agentDir,
});
const withProfile = applyAuthProfileConfig(configured.config, {
profileId: configured.profileId,
provider: params.providerId,
mode: "api_key",
});
params.ctx.runtime.log(`Default ${params.providerLabel} model: ${modelId}`);
return applyProviderDefaultModel(withProfile, configured.modelRef);
}

View File

@@ -4,6 +4,7 @@ export type {
ProviderDiscoveryContext,
OpenClawPluginService,
ProviderAuthContext,
ProviderAuthMethodNonInteractiveContext,
ProviderAuthResult,
} from "../plugins/types.js";
export type { ChannelPlugin } from "../channels/plugins/types.plugin.js";
@@ -15,6 +16,7 @@ export { emptyPluginConfigSchema } from "../plugins/config-schema.js";
export { buildOauthProviderAuthResult } from "./provider-auth-result.js";
export {
applyProviderDefaultModel,
configureOpenAICompatibleSelfHostedProviderNonInteractive,
promptAndConfigureOpenAICompatibleSelfHostedProvider,
SELF_HOSTED_DEFAULT_CONTEXT_WINDOW,
SELF_HOSTED_DEFAULT_COST,

View File

@@ -18,5 +18,8 @@ export function resolvePluginProviders(params: {
logger: createPluginLoaderLogger(log),
});
return registry.providers.map((entry) => entry.provider);
return registry.providers.map((entry) => ({
...entry.provider,
pluginId: entry.pluginId,
}));
}

View File

@@ -1,12 +1,17 @@
import type { IncomingMessage, ServerResponse } from "node:http";
import type { AgentMessage } from "@mariozechner/pi-agent-core";
import type { Command } from "commander";
import type { AuthProfileCredential, OAuthCredential } from "../agents/auth-profiles/types.js";
import type {
ApiKeyCredential,
AuthProfileCredential,
OAuthCredential,
} from "../agents/auth-profiles/types.js";
import type { AnyAgentTool } from "../agents/tools/common.js";
import type { ReplyPayload } from "../auto-reply/types.js";
import type { ChannelDock } from "../channels/dock.js";
import type { ChannelId, ChannelPlugin } from "../channels/plugins/types.js";
import type { createVpsAwareOAuthHandlers } from "../commands/oauth-flow.js";
import type { OnboardOptions } from "../commands/onboard-types.js";
import type { OpenClawConfig } from "../config/config.js";
import type { ModelProviderConfig } from "../config/types.js";
import type { GatewayRequestHandler } from "../gateway/server-methods/types.js";
@@ -111,12 +116,54 @@ export type ProviderAuthContext = {
};
};
export type ProviderNonInteractiveApiKeyResult = {
key: string;
source: "profile" | "env" | "flag";
envVarName?: string;
};
export type ProviderResolveNonInteractiveApiKeyParams = {
provider: string;
flagValue?: string;
flagName: `--${string}`;
envVar: string;
envVarName?: string;
allowProfile?: boolean;
required?: boolean;
};
export type ProviderNonInteractiveApiKeyCredentialParams = {
provider: string;
resolved: ProviderNonInteractiveApiKeyResult;
email?: string;
metadata?: Record<string, string>;
};
export type ProviderAuthMethodNonInteractiveContext = {
authChoice: string;
config: OpenClawConfig;
baseConfig: OpenClawConfig;
opts: OnboardOptions;
runtime: RuntimeEnv;
agentDir?: string;
workspaceDir?: string;
resolveApiKey: (
params: ProviderResolveNonInteractiveApiKeyParams,
) => Promise<ProviderNonInteractiveApiKeyResult | null>;
toApiKeyCredential: (
params: ProviderNonInteractiveApiKeyCredentialParams,
) => ApiKeyCredential | null;
};
export type ProviderAuthMethod = {
id: string;
label: string;
hint?: string;
kind: ProviderAuthKind;
run: (ctx: ProviderAuthContext) => Promise<ProviderAuthResult>;
runNonInteractive?: (
ctx: ProviderAuthMethodNonInteractiveContext,
) => Promise<OpenClawConfig | null>;
};
export type ProviderDiscoveryOrder = "simple" | "profile" | "paired" | "late";
@@ -174,11 +221,11 @@ export type ProviderModelSelectedContext = {
export type ProviderPlugin = {
id: string;
pluginId?: string;
label: string;
docsPath?: string;
aliases?: string[];
envVars?: string[];
models?: ModelProviderConfig;
auth: ProviderAuthMethod[];
discovery?: ProviderPluginDiscovery;
wizard?: ProviderPluginWizard;