fix: remove provider hardcoding and fix arcee openrouter

This commit is contained in:
Peter Steinberger
2026-04-06 18:19:09 +01:00
parent 95106be59b
commit 177be0f237
15 changed files with 375 additions and 293 deletions

View File

@@ -29,6 +29,44 @@ describe("arcee provider plugin", () => {
expect(orChoice?.method.id).toBe("openrouter");
});
it("stores the OpenRouter onboarding path under the OpenRouter auth profile", async () => {
const provider = await registerSingleProviderPlugin(arceePlugin);
const openRouterMethod = provider.auth?.find((method) => method.id === "openrouter");
if (!openRouterMethod?.runNonInteractive) {
throw new Error("expected OpenRouter non-interactive auth");
}
const config = await openRouterMethod.runNonInteractive({
config: {},
opts: {},
env: {},
runtime: {
error: () => {},
exit: () => {},
log: () => {},
},
resolveApiKey: async () => ({
key: "sk-or-test",
source: "profile",
}),
toApiKeyCredential: () => null,
} as never);
expect(config?.auth?.profiles?.["openrouter:default"]).toMatchObject({
provider: "openrouter",
mode: "api_key",
});
expect(config?.models?.providers?.arcee).toMatchObject({
baseUrl: "https://openrouter.ai/api/v1",
api: "openai-completions",
});
expect(config?.models?.providers?.arcee?.models?.map((model) => model.id)).toEqual([
"arcee/trinity-mini",
"arcee/trinity-large-preview",
"arcee/trinity-large-thinking",
]);
});
it("builds the direct Arcee AI model catalog", async () => {
const provider = await registerSingleProviderPlugin(arceePlugin);
expect(provider.catalog).toBeDefined();
@@ -81,9 +119,41 @@ describe("arcee provider plugin", () => {
expect(catalog.provider.baseUrl).toBe("https://openrouter.ai/api/v1");
expect(catalog.provider.models?.map((model) => model.id)).toEqual([
"trinity-mini",
"trinity-large-preview",
"trinity-large-thinking",
"arcee/trinity-mini",
"arcee/trinity-large-preview",
"arcee/trinity-large-thinking",
]);
});
it("normalizes Arcee OpenRouter models to vendor-prefixed runtime ids", async () => {
const provider = await registerSingleProviderPlugin(arceePlugin);
expect(
provider.normalizeResolvedModel?.({
modelId: "arcee/trinity-large-thinking",
model: {
provider: "arcee",
id: "trinity-large-thinking",
name: "Trinity Large Thinking",
api: "openai-completions",
baseUrl: "https://openrouter.ai/api/v1",
},
} as never),
).toMatchObject({
id: "arcee/trinity-large-thinking",
});
expect(
provider.normalizeResolvedModel?.({
modelId: "arcee/trinity-large-thinking",
model: {
provider: "arcee",
id: "trinity-large-thinking",
name: "Trinity Large Thinking",
api: "openai-completions",
baseUrl: "https://api.arcee.ai/api/v1",
},
} as never),
).toBeUndefined();
});
});

View File

@@ -1,5 +1,6 @@
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 { buildProviderReplayFamilyHooks } from "openclaw/plugin-sdk/provider-model-shared";
import {
applyArceeConfig,
@@ -7,7 +8,12 @@ import {
ARCEE_DEFAULT_MODEL_REF,
ARCEE_OPENROUTER_DEFAULT_MODEL_REF,
} from "./onboard.js";
import { buildArceeProvider, buildArceeOpenRouterProvider } from "./provider-catalog.js";
import {
buildArceeProvider,
buildArceeOpenRouterProvider,
isArceeOpenRouterBaseUrl,
toArceeOpenRouterModelId,
} from "./provider-catalog.js";
const PROVIDER_ID = "arcee";
const OPENAI_COMPATIBLE_REPLAY_HOOKS = buildProviderReplayFamilyHooks({
@@ -55,6 +61,7 @@ export default definePluginEntry({
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),
@@ -81,6 +88,18 @@ export default definePluginEntry({
return null;
},
},
augmentModelCatalog: ({ config }) =>
readConfiguredProviderCatalogEntries({
config,
providerId: PROVIDER_ID,
}),
normalizeResolvedModel: ({ model }) =>
isArceeOpenRouterBaseUrl(model.baseUrl)
? {
...model,
id: toArceeOpenRouterModelId(model.id),
}
: undefined,
...OPENAI_COMPATIBLE_REPLAY_HOOKS,
});
},

View File

@@ -3,8 +3,7 @@ import {
type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard";
import { buildArceeModelDefinition, ARCEE_BASE_URL, ARCEE_MODEL_CATALOG } from "./api.js";
const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
import { OPENROUTER_BASE_URL, toArceeOpenRouterModelId } 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";
@@ -26,7 +25,10 @@ const arceeOpenRouterPresetAppliers = createModelCatalogPresetAppliers({
providerId: "arcee",
api: "openai-completions",
baseUrl: OPENROUTER_BASE_URL,
catalogModels: ARCEE_MODEL_CATALOG.map(buildArceeModelDefinition),
catalogModels: ARCEE_MODEL_CATALOG.map((model) => ({
...buildArceeModelDefinition(model),
id: toArceeOpenRouterModelId(model.id),
})),
aliases: [{ modelRef: ARCEE_OPENROUTER_DEFAULT_MODEL_REF, alias: "Arcee AI (OpenRouter)" }],
}),
});

View File

@@ -1,7 +1,25 @@
import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-shared";
import { buildArceeModelDefinition, ARCEE_BASE_URL, ARCEE_MODEL_CATALOG } from "./api.js";
const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
export const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
function normalizeBaseUrl(baseUrl: string | undefined): string {
return String(baseUrl ?? "")
.trim()
.replace(/\/+$/, "");
}
export function isArceeOpenRouterBaseUrl(baseUrl: string | undefined): boolean {
return normalizeBaseUrl(baseUrl) === OPENROUTER_BASE_URL;
}
export function toArceeOpenRouterModelId(modelId: string): string {
const normalized = modelId.trim();
if (!normalized || normalized.startsWith("arcee/")) {
return normalized;
}
return `arcee/${normalized}`;
}
export function buildArceeProvider(): ModelProviderConfig {
return {
@@ -15,6 +33,9 @@ export function buildArceeOpenRouterProvider(): ModelProviderConfig {
return {
baseUrl: OPENROUTER_BASE_URL,
api: "openai-completions",
models: ARCEE_MODEL_CATALOG.map(buildArceeModelDefinition),
models: ARCEE_MODEL_CATALOG.map((model) => ({
...buildArceeModelDefinition(model),
id: toArceeOpenRouterModelId(model.id),
})),
};
}