feat: add provider auth plugins
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
## 2026.1.15 (unreleased)
|
## 2026.1.15 (unreleased)
|
||||||
|
|
||||||
- Heartbeat: tighten prompt guidance + suppress duplicate alerts for 24h. (#980) — thanks @voidserf.
|
- Heartbeat: tighten prompt guidance + suppress duplicate alerts for 24h. (#980) — thanks @voidserf.
|
||||||
|
- Plugins: add provider auth registry + `clawdbot models auth login` for plugin-driven OAuth/API key flows.
|
||||||
- Fix: list model picker entries as provider/model pairs for explicit selection. (#970) — thanks @mcinteerj.
|
- Fix: list model picker entries as provider/model pairs for explicit selection. (#970) — thanks @mcinteerj.
|
||||||
- Daemon: fix profile-aware service label resolution (env-driven) and add coverage for launchd/systemd/schtasks. (#969) — thanks @bjesuiter.
|
- Daemon: fix profile-aware service label resolution (env-driven) and add coverage for launchd/systemd/schtasks. (#969) — thanks @bjesuiter.
|
||||||
- Daemon: share profile/state-dir resolution across service helpers and honor `CLAWDBOT_STATE_DIR` for Windows task scripts.
|
- Daemon: share profile/state-dir resolution across service helpers and honor `CLAWDBOT_STATE_DIR` for Windows task scripts.
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ import {
|
|||||||
githubCopilotLoginCommand,
|
githubCopilotLoginCommand,
|
||||||
modelsAliasesAddCommand,
|
modelsAliasesAddCommand,
|
||||||
modelsAliasesListCommand,
|
modelsAliasesListCommand,
|
||||||
modelsAliasesRemoveCommand,
|
modelsAliasesRemoveCommand,
|
||||||
modelsAuthAddCommand,
|
modelsAuthAddCommand,
|
||||||
modelsAuthOrderClearCommand,
|
modelsAuthLoginCommand,
|
||||||
modelsAuthOrderGetCommand,
|
modelsAuthOrderClearCommand,
|
||||||
modelsAuthOrderSetCommand,
|
modelsAuthOrderGetCommand,
|
||||||
modelsAuthPasteTokenCommand,
|
modelsAuthOrderSetCommand,
|
||||||
modelsAuthSetupTokenCommand,
|
modelsAuthPasteTokenCommand,
|
||||||
|
modelsAuthSetupTokenCommand,
|
||||||
modelsFallbacksAddCommand,
|
modelsFallbacksAddCommand,
|
||||||
modelsFallbacksClearCommand,
|
modelsFallbacksClearCommand,
|
||||||
modelsFallbacksListCommand,
|
modelsFallbacksListCommand,
|
||||||
@@ -309,6 +310,28 @@ export function registerModelsCli(program: Command) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
auth
|
||||||
|
.command("login")
|
||||||
|
.description("Run a provider plugin auth flow (OAuth/API key)")
|
||||||
|
.option("--provider <id>", "Provider id registered by a plugin")
|
||||||
|
.option("--method <id>", "Provider auth method id")
|
||||||
|
.option("--set-default", "Apply the provider's default model recommendation", false)
|
||||||
|
.action(async (opts) => {
|
||||||
|
try {
|
||||||
|
await modelsAuthLoginCommand(
|
||||||
|
{
|
||||||
|
provider: opts.provider as string | undefined,
|
||||||
|
method: opts.method as string | undefined,
|
||||||
|
setDefault: Boolean(opts.setDefault),
|
||||||
|
},
|
||||||
|
defaultRuntime,
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
defaultRuntime.error(String(err));
|
||||||
|
defaultRuntime.exit(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
auth
|
auth
|
||||||
.command("setup-token")
|
.command("setup-token")
|
||||||
.description("Run a provider CLI to create/sync a token (TTY required)")
|
.description("Run a provider CLI to create/sync a token (TTY required)")
|
||||||
|
|||||||
@@ -49,6 +49,9 @@ function formatPluginLine(plugin: PluginRecord, verbose = false): string {
|
|||||||
` origin: ${plugin.origin}`,
|
` origin: ${plugin.origin}`,
|
||||||
];
|
];
|
||||||
if (plugin.version) parts.push(` version: ${plugin.version}`);
|
if (plugin.version) parts.push(` version: ${plugin.version}`);
|
||||||
|
if (plugin.providerIds.length > 0) {
|
||||||
|
parts.push(` providers: ${plugin.providerIds.join(", ")}`);
|
||||||
|
}
|
||||||
if (plugin.error) parts.push(chalk.red(` error: ${plugin.error}`));
|
if (plugin.error) parts.push(chalk.red(` error: ${plugin.error}`));
|
||||||
return parts.join("\n");
|
return parts.join("\n");
|
||||||
}
|
}
|
||||||
@@ -138,6 +141,9 @@ export function registerPluginsCli(program: Command) {
|
|||||||
if (plugin.gatewayMethods.length > 0) {
|
if (plugin.gatewayMethods.length > 0) {
|
||||||
lines.push(`Gateway methods: ${plugin.gatewayMethods.join(", ")}`);
|
lines.push(`Gateway methods: ${plugin.gatewayMethods.join(", ")}`);
|
||||||
}
|
}
|
||||||
|
if (plugin.providerIds.length > 0) {
|
||||||
|
lines.push(`Providers: ${plugin.providerIds.join(", ")}`);
|
||||||
|
}
|
||||||
if (plugin.cliCommands.length > 0) {
|
if (plugin.cliCommands.length > 0) {
|
||||||
lines.push(`CLI commands: ${plugin.cliCommands.join(", ")}`);
|
lines.push(`CLI commands: ${plugin.cliCommands.join(", ")}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export {
|
|||||||
} from "./models/aliases.js";
|
} from "./models/aliases.js";
|
||||||
export {
|
export {
|
||||||
modelsAuthAddCommand,
|
modelsAuthAddCommand,
|
||||||
|
modelsAuthLoginCommand,
|
||||||
modelsAuthPasteTokenCommand,
|
modelsAuthPasteTokenCommand,
|
||||||
modelsAuthSetupTokenCommand,
|
modelsAuthSetupTokenCommand,
|
||||||
} from "./models/auth.js";
|
} from "./models/auth.js";
|
||||||
|
|||||||
@@ -8,12 +8,21 @@ import {
|
|||||||
upsertAuthProfile,
|
upsertAuthProfile,
|
||||||
} from "../../agents/auth-profiles.js";
|
} from "../../agents/auth-profiles.js";
|
||||||
import { normalizeProviderId } from "../../agents/model-selection.js";
|
import { normalizeProviderId } from "../../agents/model-selection.js";
|
||||||
|
import { resolveAgentDir, resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../../agents/agent-scope.js";
|
||||||
|
import { resolveDefaultAgentWorkspaceDir } from "../../agents/workspace.js";
|
||||||
import { parseDurationMs } from "../../cli/parse-duration.js";
|
import { parseDurationMs } from "../../cli/parse-duration.js";
|
||||||
import { CONFIG_PATH_CLAWDBOT } from "../../config/config.js";
|
import { CONFIG_PATH_CLAWDBOT, readConfigFileSnapshot } from "../../config/config.js";
|
||||||
import type { RuntimeEnv } from "../../runtime.js";
|
import type { RuntimeEnv } from "../../runtime.js";
|
||||||
import { stylePromptHint, stylePromptMessage } from "../../terminal/prompt-style.js";
|
import { stylePromptHint, stylePromptMessage } from "../../terminal/prompt-style.js";
|
||||||
import { applyAuthProfileConfig } from "../onboard-auth.js";
|
import { applyAuthProfileConfig } from "../onboard-auth.js";
|
||||||
|
import { isRemoteEnvironment } from "../antigravity-oauth.js";
|
||||||
|
import { openUrl } from "../onboard-helpers.js";
|
||||||
|
import { createVpsAwareOAuthHandlers } from "../oauth-flow.js";
|
||||||
import { updateConfig } from "./shared.js";
|
import { updateConfig } from "./shared.js";
|
||||||
|
import { resolvePluginProviders } from "../../plugins/providers.js";
|
||||||
|
import { createClackPrompter } from "../../wizard/clack-prompter.js";
|
||||||
|
import type { ProviderAuthMethod, ProviderAuthResult, ProviderPlugin } from "../../plugins/types.js";
|
||||||
|
import type { AuthProfileCredential } from "../../agents/auth-profiles/types.js";
|
||||||
|
|
||||||
const confirm = (params: Parameters<typeof clackConfirm>[0]) =>
|
const confirm = (params: Parameters<typeof clackConfirm>[0]) =>
|
||||||
clackConfirm({
|
clackConfirm({
|
||||||
@@ -215,3 +224,204 @@ export async function modelsAuthAddCommand(_opts: Record<string, never>, runtime
|
|||||||
|
|
||||||
await modelsAuthPasteTokenCommand({ provider: providerId, profileId, expiresIn }, runtime);
|
await modelsAuthPasteTokenCommand({ provider: providerId, profileId, expiresIn }, runtime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LoginOptions = {
|
||||||
|
provider?: string;
|
||||||
|
method?: string;
|
||||||
|
setDefault?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
function resolveProviderMatch(
|
||||||
|
providers: ProviderPlugin[],
|
||||||
|
rawProvider?: string,
|
||||||
|
): ProviderPlugin | null {
|
||||||
|
const raw = rawProvider?.trim();
|
||||||
|
if (!raw) return null;
|
||||||
|
const normalized = normalizeProviderId(raw);
|
||||||
|
return (
|
||||||
|
providers.find((provider) => normalizeProviderId(provider.id) === normalized) ??
|
||||||
|
providers.find(
|
||||||
|
(provider) =>
|
||||||
|
provider.aliases?.some((alias) => normalizeProviderId(alias) === normalized) ?? false,
|
||||||
|
) ??
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickAuthMethod(provider: ProviderPlugin, rawMethod?: string): ProviderAuthMethod | null {
|
||||||
|
const raw = rawMethod?.trim();
|
||||||
|
if (!raw) return null;
|
||||||
|
const normalized = raw.toLowerCase();
|
||||||
|
return (
|
||||||
|
provider.auth.find((method) => method.id.toLowerCase() === normalized) ??
|
||||||
|
provider.auth.find((method) => method.label.toLowerCase() === normalized) ??
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
||||||
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeConfigPatch<T>(base: T, patch: unknown): T {
|
||||||
|
if (!isPlainRecord(base) || !isPlainRecord(patch)) {
|
||||||
|
return patch as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
const next: Record<string, unknown> = { ...base };
|
||||||
|
for (const [key, value] of Object.entries(patch)) {
|
||||||
|
const existing = next[key];
|
||||||
|
if (isPlainRecord(existing) && isPlainRecord(value)) {
|
||||||
|
next[key] = mergeConfigPatch(existing, value);
|
||||||
|
} else {
|
||||||
|
next[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return next as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyDefaultModel(cfg: ClawdbotConfig, model: string): ClawdbotConfig {
|
||||||
|
const models = { ...cfg.agents?.defaults?.models };
|
||||||
|
models[model] = models[model] ?? {};
|
||||||
|
|
||||||
|
const existingModel = cfg.agents?.defaults?.model;
|
||||||
|
return {
|
||||||
|
...cfg,
|
||||||
|
agents: {
|
||||||
|
...cfg.agents,
|
||||||
|
defaults: {
|
||||||
|
...cfg.agents?.defaults,
|
||||||
|
models,
|
||||||
|
model: {
|
||||||
|
...(existingModel && typeof existingModel === "object" && "fallbacks" in existingModel
|
||||||
|
? { fallbacks: (existingModel as { fallbacks?: string[] }).fallbacks }
|
||||||
|
: undefined),
|
||||||
|
primary: model,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function credentialMode(credential: AuthProfileCredential): "api_key" | "oauth" | "token" {
|
||||||
|
if (credential.type === "api_key") return "api_key";
|
||||||
|
if (credential.type === "token") return "token";
|
||||||
|
return "oauth";
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function modelsAuthLoginCommand(opts: LoginOptions, runtime: RuntimeEnv) {
|
||||||
|
if (!process.stdin.isTTY) {
|
||||||
|
throw new Error("models auth login requires an interactive TTY.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const snapshot = await readConfigFileSnapshot();
|
||||||
|
if (!snapshot.valid) {
|
||||||
|
const issues = snapshot.issues.map((issue) => `- ${issue.path}: ${issue.message}`).join("\n");
|
||||||
|
throw new Error(`Invalid config at ${snapshot.path}\n${issues}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = snapshot.config;
|
||||||
|
const defaultAgentId = resolveDefaultAgentId(config);
|
||||||
|
const agentDir = resolveAgentDir(config, defaultAgentId);
|
||||||
|
const workspaceDir =
|
||||||
|
resolveAgentWorkspaceDir(config, defaultAgentId) ?? resolveDefaultAgentWorkspaceDir();
|
||||||
|
|
||||||
|
const providers = resolvePluginProviders({ config, workspaceDir });
|
||||||
|
if (providers.length === 0) {
|
||||||
|
throw new Error("No provider plugins found. Install one via `clawdbot plugins install`.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const prompter = createClackPrompter();
|
||||||
|
const selectedProvider =
|
||||||
|
resolveProviderMatch(providers, opts.provider) ??
|
||||||
|
(await prompter.select({
|
||||||
|
message: "Select a provider",
|
||||||
|
options: providers.map((provider) => ({
|
||||||
|
value: provider.id,
|
||||||
|
label: provider.label,
|
||||||
|
hint: provider.docsPath ? `Docs: ${provider.docsPath}` : undefined,
|
||||||
|
})),
|
||||||
|
}).then((id) => resolveProviderMatch(providers, String(id))));
|
||||||
|
|
||||||
|
if (!selectedProvider) {
|
||||||
|
throw new Error("Unknown provider. Use --provider <id> to pick a provider plugin.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const chosenMethod =
|
||||||
|
pickAuthMethod(selectedProvider, opts.method) ??
|
||||||
|
(selectedProvider.auth.length === 1
|
||||||
|
? selectedProvider.auth[0]
|
||||||
|
: await prompter.select({
|
||||||
|
message: `Auth method for ${selectedProvider.label}`,
|
||||||
|
options: selectedProvider.auth.map((method) => ({
|
||||||
|
value: method.id,
|
||||||
|
label: method.label,
|
||||||
|
hint: method.hint,
|
||||||
|
})),
|
||||||
|
}).then((id) =>
|
||||||
|
selectedProvider.auth.find((method) => method.id === String(id)),
|
||||||
|
));
|
||||||
|
|
||||||
|
if (!chosenMethod) {
|
||||||
|
throw new Error("Unknown auth method. Use --method <id> to select one.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const isRemote = isRemoteEnvironment();
|
||||||
|
const result: ProviderAuthResult = await chosenMethod.run({
|
||||||
|
config,
|
||||||
|
agentDir,
|
||||||
|
workspaceDir,
|
||||||
|
prompter,
|
||||||
|
runtime,
|
||||||
|
isRemote,
|
||||||
|
openUrl: async (url) => {
|
||||||
|
await openUrl(url);
|
||||||
|
},
|
||||||
|
oauth: {
|
||||||
|
createVpsAwareHandlers: (params) => createVpsAwareOAuthHandlers(params),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const profile of result.profiles) {
|
||||||
|
upsertAuthProfile({
|
||||||
|
profileId: profile.profileId,
|
||||||
|
credential: profile.credential,
|
||||||
|
agentDir,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateConfig((cfg) => {
|
||||||
|
let next = cfg;
|
||||||
|
if (result.configPatch) {
|
||||||
|
next = mergeConfigPatch(next, result.configPatch);
|
||||||
|
}
|
||||||
|
for (const profile of result.profiles) {
|
||||||
|
next = applyAuthProfileConfig(next, {
|
||||||
|
profileId: profile.profileId,
|
||||||
|
provider: profile.credential.provider,
|
||||||
|
mode: credentialMode(profile.credential),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (opts.setDefault && result.defaultModel) {
|
||||||
|
next = applyDefaultModel(next, result.defaultModel);
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
|
||||||
|
runtime.log(`Updated ${CONFIG_PATH_CLAWDBOT}`);
|
||||||
|
for (const profile of result.profiles) {
|
||||||
|
runtime.log(
|
||||||
|
`Auth profile: ${profile.profileId} (${profile.credential.provider}/${credentialMode(profile.credential)})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (result.defaultModel) {
|
||||||
|
runtime.log(
|
||||||
|
opts.setDefault
|
||||||
|
? `Default model set to ${result.defaultModel}`
|
||||||
|
: `Default model available: ${result.defaultModel} (use --set-default to apply)`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (result.notes && result.notes.length > 0) {
|
||||||
|
await prompter.note(result.notes.join("\n"), "Provider notes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -190,6 +190,7 @@ function createPluginRecord(params: {
|
|||||||
status: params.enabled ? "loaded" : "disabled",
|
status: params.enabled ? "loaded" : "disabled",
|
||||||
toolNames: [],
|
toolNames: [],
|
||||||
channelIds: [],
|
channelIds: [],
|
||||||
|
providerIds: [],
|
||||||
gatewayMethods: [],
|
gatewayMethods: [],
|
||||||
cliCommands: [],
|
cliCommands: [],
|
||||||
services: [],
|
services: [],
|
||||||
|
|||||||
23
src/plugins/providers.ts
Normal file
23
src/plugins/providers.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { createSubsystemLogger } from "../logging.js";
|
||||||
|
import { loadClawdbotPlugins } from "./loader.js";
|
||||||
|
import type { ProviderPlugin } from "./types.js";
|
||||||
|
|
||||||
|
const log = createSubsystemLogger("plugins");
|
||||||
|
|
||||||
|
export function resolvePluginProviders(params: {
|
||||||
|
config?: Parameters<typeof loadClawdbotPlugins>[0]["config"];
|
||||||
|
workspaceDir?: string;
|
||||||
|
}): ProviderPlugin[] {
|
||||||
|
const registry = loadClawdbotPlugins({
|
||||||
|
config: params.config,
|
||||||
|
workspaceDir: params.workspaceDir,
|
||||||
|
logger: {
|
||||||
|
info: (msg) => log.info(msg),
|
||||||
|
warn: (msg) => log.warn(msg),
|
||||||
|
error: (msg) => log.error(msg),
|
||||||
|
debug: (msg) => log.debug(msg),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return registry.providers.map((entry) => entry.provider);
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import type {
|
|||||||
ClawdbotPluginChannelRegistration,
|
ClawdbotPluginChannelRegistration,
|
||||||
ClawdbotPluginCliRegistrar,
|
ClawdbotPluginCliRegistrar,
|
||||||
ClawdbotPluginHttpHandler,
|
ClawdbotPluginHttpHandler,
|
||||||
|
ProviderPlugin,
|
||||||
ClawdbotPluginService,
|
ClawdbotPluginService,
|
||||||
ClawdbotPluginToolContext,
|
ClawdbotPluginToolContext,
|
||||||
ClawdbotPluginToolFactory,
|
ClawdbotPluginToolFactory,
|
||||||
@@ -47,6 +48,12 @@ export type PluginChannelRegistration = {
|
|||||||
source: string;
|
source: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type PluginProviderRegistration = {
|
||||||
|
pluginId: string;
|
||||||
|
provider: ProviderPlugin;
|
||||||
|
source: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type PluginServiceRegistration = {
|
export type PluginServiceRegistration = {
|
||||||
pluginId: string;
|
pluginId: string;
|
||||||
service: ClawdbotPluginService;
|
service: ClawdbotPluginService;
|
||||||
@@ -66,6 +73,7 @@ export type PluginRecord = {
|
|||||||
error?: string;
|
error?: string;
|
||||||
toolNames: string[];
|
toolNames: string[];
|
||||||
channelIds: string[];
|
channelIds: string[];
|
||||||
|
providerIds: string[];
|
||||||
gatewayMethods: string[];
|
gatewayMethods: string[];
|
||||||
cliCommands: string[];
|
cliCommands: string[];
|
||||||
services: string[];
|
services: string[];
|
||||||
@@ -78,6 +86,7 @@ export type PluginRegistry = {
|
|||||||
plugins: PluginRecord[];
|
plugins: PluginRecord[];
|
||||||
tools: PluginToolRegistration[];
|
tools: PluginToolRegistration[];
|
||||||
channels: PluginChannelRegistration[];
|
channels: PluginChannelRegistration[];
|
||||||
|
providers: PluginProviderRegistration[];
|
||||||
gatewayHandlers: GatewayRequestHandlers;
|
gatewayHandlers: GatewayRequestHandlers;
|
||||||
httpHandlers: PluginHttpRegistration[];
|
httpHandlers: PluginHttpRegistration[];
|
||||||
cliRegistrars: PluginCliRegistration[];
|
cliRegistrars: PluginCliRegistration[];
|
||||||
@@ -95,6 +104,7 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
|||||||
plugins: [],
|
plugins: [],
|
||||||
tools: [],
|
tools: [],
|
||||||
channels: [],
|
channels: [],
|
||||||
|
providers: [],
|
||||||
gatewayHandlers: {},
|
gatewayHandlers: {},
|
||||||
httpHandlers: [],
|
httpHandlers: [],
|
||||||
cliRegistrars: [],
|
cliRegistrars: [],
|
||||||
@@ -189,6 +199,35 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const registerProvider = (record: PluginRecord, provider: ProviderPlugin) => {
|
||||||
|
const id = typeof provider?.id === "string" ? provider.id.trim() : "";
|
||||||
|
if (!id) {
|
||||||
|
pushDiagnostic({
|
||||||
|
level: "error",
|
||||||
|
pluginId: record.id,
|
||||||
|
source: record.source,
|
||||||
|
message: "provider registration missing id",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const existing = registry.providers.find((entry) => entry.provider.id === id);
|
||||||
|
if (existing) {
|
||||||
|
pushDiagnostic({
|
||||||
|
level: "error",
|
||||||
|
pluginId: record.id,
|
||||||
|
source: record.source,
|
||||||
|
message: `provider already registered: ${id} (${existing.pluginId})`,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
record.providerIds.push(id);
|
||||||
|
registry.providers.push({
|
||||||
|
pluginId: record.id,
|
||||||
|
provider,
|
||||||
|
source: record.source,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const registerCli = (
|
const registerCli = (
|
||||||
record: PluginRecord,
|
record: PluginRecord,
|
||||||
registrar: ClawdbotPluginCliRegistrar,
|
registrar: ClawdbotPluginCliRegistrar,
|
||||||
@@ -241,6 +280,7 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
|||||||
registerTool: (tool, opts) => registerTool(record, tool, opts),
|
registerTool: (tool, opts) => registerTool(record, tool, opts),
|
||||||
registerHttpHandler: (handler) => registerHttpHandler(record, handler),
|
registerHttpHandler: (handler) => registerHttpHandler(record, handler),
|
||||||
registerChannel: (registration) => registerChannel(record, registration),
|
registerChannel: (registration) => registerChannel(record, registration),
|
||||||
|
registerProvider: (provider) => registerProvider(record, provider),
|
||||||
registerGatewayMethod: (method, handler) => registerGatewayMethod(record, method, handler),
|
registerGatewayMethod: (method, handler) => registerGatewayMethod(record, method, handler),
|
||||||
registerCli: (registrar, opts) => registerCli(record, registrar, opts),
|
registerCli: (registrar, opts) => registerCli(record, registrar, opts),
|
||||||
registerService: (service) => registerService(record, service),
|
registerService: (service) => registerService(record, service),
|
||||||
@@ -254,6 +294,7 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
|||||||
pushDiagnostic,
|
pushDiagnostic,
|
||||||
registerTool,
|
registerTool,
|
||||||
registerChannel,
|
registerChannel,
|
||||||
|
registerProvider,
|
||||||
registerGatewayMethod,
|
registerGatewayMethod,
|
||||||
registerCli,
|
registerCli,
|
||||||
registerService,
|
registerService,
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||||
import type { Command } from "commander";
|
import type { Command } from "commander";
|
||||||
|
|
||||||
|
import type { AuthProfileCredential, OAuthCredential } from "../agents/auth-profiles/types.js";
|
||||||
import type { AnyAgentTool } from "../agents/tools/common.js";
|
import type { AnyAgentTool } from "../agents/tools/common.js";
|
||||||
import type { ChannelDock } from "../channels/dock.js";
|
import type { ChannelDock } from "../channels/dock.js";
|
||||||
import type { ChannelPlugin } from "../channels/plugins/types.js";
|
import type { ChannelPlugin } from "../channels/plugins/types.js";
|
||||||
import type { ClawdbotConfig } from "../config/config.js";
|
import type { ClawdbotConfig } from "../config/config.js";
|
||||||
|
import type { ModelProviderConfig } from "../config/types.js";
|
||||||
|
import type { RuntimeEnv } from "../runtime.js";
|
||||||
|
import type { WizardPrompter } from "../wizard/prompts.js";
|
||||||
|
import type { createVpsAwareOAuthHandlers } from "../commands/oauth-flow.js";
|
||||||
import type { GatewayRequestHandler } from "../gateway/server-methods/types.js";
|
import type { GatewayRequestHandler } from "../gateway/server-methods/types.js";
|
||||||
|
|
||||||
export type PluginLogger = {
|
export type PluginLogger = {
|
||||||
@@ -54,6 +59,48 @@ export type ClawdbotPluginToolFactory = (
|
|||||||
ctx: ClawdbotPluginToolContext,
|
ctx: ClawdbotPluginToolContext,
|
||||||
) => AnyAgentTool | AnyAgentTool[] | null | undefined;
|
) => AnyAgentTool | AnyAgentTool[] | null | undefined;
|
||||||
|
|
||||||
|
export type ProviderAuthKind = "oauth" | "api_key" | "token" | "device_code" | "custom";
|
||||||
|
|
||||||
|
export type ProviderAuthResult = {
|
||||||
|
profiles: Array<{ profileId: string; credential: AuthProfileCredential }>;
|
||||||
|
configPatch?: Partial<ClawdbotConfig>;
|
||||||
|
defaultModel?: string;
|
||||||
|
notes?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProviderAuthContext = {
|
||||||
|
config: ClawdbotConfig;
|
||||||
|
agentDir?: string;
|
||||||
|
workspaceDir?: string;
|
||||||
|
prompter: WizardPrompter;
|
||||||
|
runtime: RuntimeEnv;
|
||||||
|
isRemote: boolean;
|
||||||
|
openUrl: (url: string) => Promise<void>;
|
||||||
|
oauth: {
|
||||||
|
createVpsAwareHandlers: typeof createVpsAwareOAuthHandlers;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProviderAuthMethod = {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
hint?: string;
|
||||||
|
kind: ProviderAuthKind;
|
||||||
|
run: (ctx: ProviderAuthContext) => Promise<ProviderAuthResult>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProviderPlugin = {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
docsPath?: string;
|
||||||
|
aliases?: string[];
|
||||||
|
envVars?: string[];
|
||||||
|
models?: ModelProviderConfig;
|
||||||
|
auth: ProviderAuthMethod[];
|
||||||
|
formatApiKey?: (cred: AuthProfileCredential) => string;
|
||||||
|
refreshOAuth?: (cred: OAuthCredential) => Promise<OAuthCredential>;
|
||||||
|
};
|
||||||
|
|
||||||
export type ClawdbotPluginGatewayMethod = {
|
export type ClawdbotPluginGatewayMethod = {
|
||||||
method: string;
|
method: string;
|
||||||
handler: GatewayRequestHandler;
|
handler: GatewayRequestHandler;
|
||||||
@@ -123,6 +170,7 @@ export type ClawdbotPluginApi = {
|
|||||||
registerGatewayMethod: (method: string, handler: GatewayRequestHandler) => void;
|
registerGatewayMethod: (method: string, handler: GatewayRequestHandler) => void;
|
||||||
registerCli: (registrar: ClawdbotPluginCliRegistrar, opts?: { commands?: string[] }) => void;
|
registerCli: (registrar: ClawdbotPluginCliRegistrar, opts?: { commands?: string[] }) => void;
|
||||||
registerService: (service: ClawdbotPluginService) => void;
|
registerService: (service: ClawdbotPluginService) => void;
|
||||||
|
registerProvider: (provider: ProviderPlugin) => void;
|
||||||
resolvePath: (input: string) => string;
|
resolvePath: (input: string) => string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user