mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-25 08:02:04 +00:00
refactor: extract arcee provider cleanup seams
This commit is contained in:
@@ -1,7 +1,11 @@
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key";
|
||||
import { readConfiguredProviderCatalogEntries } from "openclaw/plugin-sdk/provider-catalog-shared";
|
||||
import {
|
||||
readConfiguredProviderCatalogEntries,
|
||||
type ProviderCatalogContext,
|
||||
} from "openclaw/plugin-sdk/provider-catalog-shared";
|
||||
import { buildProviderReplayFamilyHooks } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/provider-onboard";
|
||||
import {
|
||||
applyArceeConfig,
|
||||
applyArceeOpenRouterConfig,
|
||||
@@ -19,6 +23,88 @@ const PROVIDER_ID = "arcee";
|
||||
const OPENAI_COMPATIBLE_REPLAY_HOOKS = buildProviderReplayFamilyHooks({
|
||||
family: "openai-compatible",
|
||||
});
|
||||
const ARCEE_WIZARD_GROUP = {
|
||||
groupId: "arcee",
|
||||
groupLabel: "Arcee AI",
|
||||
groupHint: "Direct API or OpenRouter",
|
||||
} as const;
|
||||
|
||||
function buildArceeAuthMethods() {
|
||||
return [
|
||||
createProviderApiKeyAuthMethod({
|
||||
providerId: PROVIDER_ID,
|
||||
methodId: "arcee-platform",
|
||||
label: "Arcee AI API key",
|
||||
hint: "Direct access to Arcee platform",
|
||||
optionKey: "arceeaiApiKey",
|
||||
flagName: "--arceeai-api-key",
|
||||
envVar: "ARCEEAI_API_KEY",
|
||||
promptMessage: "Enter Arcee AI API key",
|
||||
defaultModel: ARCEE_DEFAULT_MODEL_REF,
|
||||
expectedProviders: [PROVIDER_ID],
|
||||
applyConfig: (cfg) => applyArceeConfig(cfg),
|
||||
wizard: {
|
||||
choiceId: "arceeai-api-key",
|
||||
choiceLabel: "Arcee AI API key",
|
||||
choiceHint: "Direct (chat.arcee.ai)",
|
||||
...ARCEE_WIZARD_GROUP,
|
||||
},
|
||||
}),
|
||||
createProviderApiKeyAuthMethod({
|
||||
providerId: PROVIDER_ID,
|
||||
methodId: "openrouter",
|
||||
label: "OpenRouter API key",
|
||||
hint: "Access Arcee models via OpenRouter",
|
||||
optionKey: "openrouterApiKey",
|
||||
flagName: "--openrouter-api-key",
|
||||
envVar: "OPENROUTER_API_KEY",
|
||||
promptMessage: "Enter OpenRouter API key",
|
||||
profileId: "openrouter:default",
|
||||
defaultModel: ARCEE_OPENROUTER_DEFAULT_MODEL_REF,
|
||||
expectedProviders: [PROVIDER_ID, "openrouter"],
|
||||
applyConfig: (cfg) => applyArceeOpenRouterConfig(cfg),
|
||||
wizard: {
|
||||
choiceId: "arceeai-openrouter",
|
||||
choiceLabel: "OpenRouter API key",
|
||||
choiceHint: "Via OpenRouter (openrouter.ai)",
|
||||
...ARCEE_WIZARD_GROUP,
|
||||
},
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
function readConfiguredArceeCatalogEntries(config: OpenClawConfig | undefined) {
|
||||
return readConfiguredProviderCatalogEntries({
|
||||
config,
|
||||
providerId: PROVIDER_ID,
|
||||
});
|
||||
}
|
||||
|
||||
async function resolveArceeCatalog(ctx: ProviderCatalogContext) {
|
||||
const directKey = ctx.resolveProviderApiKey(PROVIDER_ID).apiKey;
|
||||
if (directKey) {
|
||||
return { provider: { ...buildArceeProvider(), apiKey: directKey } };
|
||||
}
|
||||
|
||||
const openRouterKey = ctx.resolveProviderApiKey("openrouter").apiKey;
|
||||
if (openRouterKey) {
|
||||
return { provider: { ...buildArceeOpenRouterProvider(), apiKey: openRouterKey } };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function normalizeArceeResolvedModel<T extends { baseUrl?: string; id: string }>(
|
||||
model: T,
|
||||
): T | undefined {
|
||||
if (!isArceeOpenRouterBaseUrl(model.baseUrl)) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
...model,
|
||||
id: toArceeOpenRouterModelId(model.id),
|
||||
};
|
||||
}
|
||||
|
||||
export default definePluginEntry({
|
||||
id: PROVIDER_ID,
|
||||
@@ -30,76 +116,12 @@ export default definePluginEntry({
|
||||
label: "Arcee AI",
|
||||
docsPath: "/providers/arcee",
|
||||
envVars: ["ARCEEAI_API_KEY", "OPENROUTER_API_KEY"],
|
||||
auth: [
|
||||
createProviderApiKeyAuthMethod({
|
||||
providerId: PROVIDER_ID,
|
||||
methodId: "arcee-platform",
|
||||
label: "Arcee AI API key",
|
||||
hint: "Direct access to Arcee platform",
|
||||
optionKey: "arceeaiApiKey",
|
||||
flagName: "--arceeai-api-key",
|
||||
envVar: "ARCEEAI_API_KEY",
|
||||
promptMessage: "Enter Arcee AI API key",
|
||||
defaultModel: ARCEE_DEFAULT_MODEL_REF,
|
||||
expectedProviders: [PROVIDER_ID],
|
||||
applyConfig: (cfg) => applyArceeConfig(cfg),
|
||||
wizard: {
|
||||
choiceId: "arceeai-api-key",
|
||||
choiceLabel: "Arcee AI API key",
|
||||
choiceHint: "Direct (chat.arcee.ai)",
|
||||
groupId: "arcee",
|
||||
groupLabel: "Arcee AI",
|
||||
groupHint: "Direct API or OpenRouter",
|
||||
},
|
||||
}),
|
||||
createProviderApiKeyAuthMethod({
|
||||
providerId: PROVIDER_ID,
|
||||
methodId: "openrouter",
|
||||
label: "OpenRouter API key",
|
||||
hint: "Access Arcee models via OpenRouter",
|
||||
optionKey: "openrouterApiKey",
|
||||
flagName: "--openrouter-api-key",
|
||||
envVar: "OPENROUTER_API_KEY",
|
||||
promptMessage: "Enter OpenRouter API key",
|
||||
profileId: "openrouter:default",
|
||||
defaultModel: ARCEE_OPENROUTER_DEFAULT_MODEL_REF,
|
||||
expectedProviders: [PROVIDER_ID, "openrouter"],
|
||||
applyConfig: (cfg) => applyArceeOpenRouterConfig(cfg),
|
||||
wizard: {
|
||||
choiceId: "arceeai-openrouter",
|
||||
choiceLabel: "OpenRouter API key",
|
||||
choiceHint: "Via OpenRouter (openrouter.ai)",
|
||||
groupId: "arcee",
|
||||
groupLabel: "Arcee AI",
|
||||
groupHint: "Direct API or OpenRouter",
|
||||
},
|
||||
}),
|
||||
],
|
||||
auth: buildArceeAuthMethods(),
|
||||
catalog: {
|
||||
run: async (ctx) => {
|
||||
const directKey = ctx.resolveProviderApiKey(PROVIDER_ID).apiKey;
|
||||
if (directKey) {
|
||||
return { provider: { ...buildArceeProvider(), apiKey: directKey } };
|
||||
}
|
||||
const orKey = ctx.resolveProviderApiKey("openrouter").apiKey;
|
||||
if (orKey) {
|
||||
return { provider: { ...buildArceeOpenRouterProvider(), apiKey: orKey } };
|
||||
}
|
||||
return null;
|
||||
},
|
||||
run: resolveArceeCatalog,
|
||||
},
|
||||
augmentModelCatalog: ({ config }) =>
|
||||
readConfiguredProviderCatalogEntries({
|
||||
config,
|
||||
providerId: PROVIDER_ID,
|
||||
}),
|
||||
normalizeResolvedModel: ({ model }) =>
|
||||
isArceeOpenRouterBaseUrl(model.baseUrl)
|
||||
? {
|
||||
...model,
|
||||
id: toArceeOpenRouterModelId(model.id),
|
||||
}
|
||||
: undefined,
|
||||
augmentModelCatalog: ({ config }) => readConfiguredArceeCatalogEntries(config),
|
||||
normalizeResolvedModel: ({ model }) => normalizeArceeResolvedModel(model),
|
||||
...OPENAI_COMPATIBLE_REPLAY_HOOKS,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -2,8 +2,12 @@ import {
|
||||
createModelCatalogPresetAppliers,
|
||||
type OpenClawConfig,
|
||||
} from "openclaw/plugin-sdk/provider-onboard";
|
||||
import { buildArceeModelDefinition, ARCEE_BASE_URL, ARCEE_MODEL_CATALOG } from "./api.js";
|
||||
import { OPENROUTER_BASE_URL, toArceeOpenRouterModelId } from "./provider-catalog.js";
|
||||
import { ARCEE_BASE_URL } from "./api.js";
|
||||
import {
|
||||
buildArceeCatalogModels,
|
||||
buildArceeOpenRouterCatalogModels,
|
||||
OPENROUTER_BASE_URL,
|
||||
} from "./provider-catalog.js";
|
||||
|
||||
export const ARCEE_DEFAULT_MODEL_REF = "arcee/trinity-large-thinking";
|
||||
export const ARCEE_OPENROUTER_DEFAULT_MODEL_REF = "arcee/trinity-large-thinking";
|
||||
@@ -14,7 +18,7 @@ const arceePresetAppliers = createModelCatalogPresetAppliers({
|
||||
providerId: "arcee",
|
||||
api: "openai-completions",
|
||||
baseUrl: ARCEE_BASE_URL,
|
||||
catalogModels: ARCEE_MODEL_CATALOG.map(buildArceeModelDefinition),
|
||||
catalogModels: buildArceeCatalogModels(),
|
||||
aliases: [{ modelRef: ARCEE_DEFAULT_MODEL_REF, alias: "Arcee AI" }],
|
||||
}),
|
||||
});
|
||||
@@ -25,10 +29,7 @@ const arceeOpenRouterPresetAppliers = createModelCatalogPresetAppliers({
|
||||
providerId: "arcee",
|
||||
api: "openai-completions",
|
||||
baseUrl: OPENROUTER_BASE_URL,
|
||||
catalogModels: ARCEE_MODEL_CATALOG.map((model) => ({
|
||||
...buildArceeModelDefinition(model),
|
||||
id: toArceeOpenRouterModelId(model.id),
|
||||
})),
|
||||
catalogModels: buildArceeOpenRouterCatalogModels(),
|
||||
aliases: [{ modelRef: ARCEE_OPENROUTER_DEFAULT_MODEL_REF, alias: "Arcee AI (OpenRouter)" }],
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -21,11 +21,22 @@ export function toArceeOpenRouterModelId(modelId: string): string {
|
||||
return `arcee/${normalized}`;
|
||||
}
|
||||
|
||||
export function buildArceeCatalogModels(): NonNullable<ModelProviderConfig["models"]> {
|
||||
return ARCEE_MODEL_CATALOG.map(buildArceeModelDefinition);
|
||||
}
|
||||
|
||||
export function buildArceeOpenRouterCatalogModels(): NonNullable<ModelProviderConfig["models"]> {
|
||||
return buildArceeCatalogModels().map((model) => ({
|
||||
...model,
|
||||
id: toArceeOpenRouterModelId(model.id),
|
||||
}));
|
||||
}
|
||||
|
||||
export function buildArceeProvider(): ModelProviderConfig {
|
||||
return {
|
||||
baseUrl: ARCEE_BASE_URL,
|
||||
api: "openai-completions",
|
||||
models: ARCEE_MODEL_CATALOG.map(buildArceeModelDefinition),
|
||||
models: buildArceeCatalogModels(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -33,9 +44,6 @@ export function buildArceeOpenRouterProvider(): ModelProviderConfig {
|
||||
return {
|
||||
baseUrl: OPENROUTER_BASE_URL,
|
||||
api: "openai-completions",
|
||||
models: ARCEE_MODEL_CATALOG.map((model) => ({
|
||||
...buildArceeModelDefinition(model),
|
||||
id: toArceeOpenRouterModelId(model.id),
|
||||
})),
|
||||
models: buildArceeOpenRouterCatalogModels(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ export type BuiltInAuthChoice =
|
||||
"oauth" | "setup-token" | "token" | "apiKey" | "custom-api-key" | "skip";
|
||||
export type AuthChoice = BuiltInAuthChoice | (string & {});
|
||||
|
||||
export type BuiltInAuthChoiceGroupId = "custom";
|
||||
export type AuthChoiceGroupId = BuiltInAuthChoiceGroupId | (string & {});
|
||||
/** Auth choice groups are plugin-owned ids plus the core `custom` bucket. */
|
||||
export type AuthChoiceGroupId = "custom" | (string & {});
|
||||
export type GatewayAuthChoice = "token" | "password";
|
||||
export type ResetScope = "config" | "config+creds+sessions" | "full";
|
||||
export type GatewayBind = "loopback" | "lan" | "auto" | "custom" | "tailnet";
|
||||
|
||||
@@ -15,7 +15,7 @@ describe("config io shell env expected keys", () => {
|
||||
]);
|
||||
|
||||
vi.resetModules();
|
||||
const { resolveShellEnvExpectedKeys } = await import("./io.js");
|
||||
const { resolveShellEnvExpectedKeys } = await import("./shell-env-expected-keys.js");
|
||||
|
||||
expect(resolveShellEnvExpectedKeys({} as NodeJS.ProcessEnv)).toEqual(
|
||||
expect.arrayContaining([
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
shouldEnableShellEnvFallback,
|
||||
} from "../infra/shell-env.js";
|
||||
import { listPluginDoctorLegacyConfigRules } from "../plugins/doctor-contract-registry.js";
|
||||
import { listKnownProviderAuthEnvVarNames } from "../secrets/provider-env-vars.js";
|
||||
import { sanitizeTerminalText } from "../terminal/safe-text.js";
|
||||
import { VERSION } from "../version.js";
|
||||
import { DuplicateAgentDirError, findDuplicateAgentDirs } from "./agent-dirs.js";
|
||||
@@ -51,6 +50,7 @@ import {
|
||||
setRuntimeConfigSnapshot as setRuntimeConfigSnapshotState,
|
||||
setRuntimeConfigSnapshotRefreshHandler as setRuntimeConfigSnapshotRefreshHandlerState,
|
||||
} from "./runtime-snapshot.js";
|
||||
import { resolveShellEnvExpectedKeys } from "./shell-env-expected-keys.js";
|
||||
import type { OpenClawConfig, ConfigFileSnapshot, LegacyConfigIssue } from "./types.js";
|
||||
import {
|
||||
validateConfigObjectRawWithPlugins,
|
||||
@@ -70,21 +70,7 @@ export {
|
||||
// Re-export for backwards compatibility
|
||||
export { CircularIncludeError, ConfigIncludeError } from "./includes.js";
|
||||
export { MissingEnvVarError } from "./env-substitution.js";
|
||||
|
||||
const CORE_SHELL_ENV_EXPECTED_KEYS = [
|
||||
"TELEGRAM_BOT_TOKEN",
|
||||
"DISCORD_BOT_TOKEN",
|
||||
"SLACK_BOT_TOKEN",
|
||||
"SLACK_APP_TOKEN",
|
||||
"OPENCLAW_GATEWAY_TOKEN",
|
||||
"OPENCLAW_GATEWAY_PASSWORD",
|
||||
];
|
||||
|
||||
export function resolveShellEnvExpectedKeys(env: NodeJS.ProcessEnv): string[] {
|
||||
return [
|
||||
...new Set([...listKnownProviderAuthEnvVarNames({ env }), ...CORE_SHELL_ENV_EXPECTED_KEYS]),
|
||||
];
|
||||
}
|
||||
export { resolveShellEnvExpectedKeys } from "./shell-env-expected-keys.js";
|
||||
|
||||
const OPEN_DM_POLICY_ALLOW_FROM_RE =
|
||||
/^(?<policyPath>[a-z0-9_.-]+)\s*=\s*"open"\s+requires\s+(?<allowPath>[a-z0-9_.-]+)(?:\s+\(or\s+[a-z0-9_.-]+\))?\s+to include "\*"$/i;
|
||||
|
||||
16
src/config/shell-env-expected-keys.ts
Normal file
16
src/config/shell-env-expected-keys.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { listKnownProviderAuthEnvVarNames } from "../secrets/provider-env-vars.js";
|
||||
|
||||
const CORE_SHELL_ENV_EXPECTED_KEYS = [
|
||||
"TELEGRAM_BOT_TOKEN",
|
||||
"DISCORD_BOT_TOKEN",
|
||||
"SLACK_BOT_TOKEN",
|
||||
"SLACK_APP_TOKEN",
|
||||
"OPENCLAW_GATEWAY_TOKEN",
|
||||
"OPENCLAW_GATEWAY_PASSWORD",
|
||||
];
|
||||
|
||||
export function resolveShellEnvExpectedKeys(env: NodeJS.ProcessEnv): string[] {
|
||||
return [
|
||||
...new Set([...listKnownProviderAuthEnvVarNames({ env }), ...CORE_SHELL_ENV_EXPECTED_KEYS]),
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user