Plugins: extract provider discovery
This commit is contained in:
parent
c03d3b33e3
commit
7b779f7b3f
@ -60,45 +60,6 @@ export function legacyModelKey(provider: string, model: string): string | null {
|
||||
return rawKey === canonicalKey ? null : rawKey;
|
||||
}
|
||||
|
||||
export function normalizeProviderId(provider: string): string {
|
||||
const normalized = provider.trim().toLowerCase();
|
||||
if (normalized === "z.ai" || normalized === "z-ai") {
|
||||
return "zai";
|
||||
}
|
||||
if (normalized === "opencode-zen") {
|
||||
return "opencode";
|
||||
}
|
||||
if (normalized === "opencode-go-auth") {
|
||||
return "opencode-go";
|
||||
}
|
||||
if (normalized === "qwen") {
|
||||
return "qwen-portal";
|
||||
}
|
||||
if (normalized === "kimi-code") {
|
||||
return "kimi-coding";
|
||||
}
|
||||
if (normalized === "bedrock" || normalized === "aws-bedrock") {
|
||||
return "amazon-bedrock";
|
||||
}
|
||||
// Backward compatibility for older provider naming.
|
||||
if (normalized === "bytedance" || normalized === "doubao") {
|
||||
return "volcengine";
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
/** Normalize provider ID for auth lookup. Coding-plan variants share auth with base. */
|
||||
export function normalizeProviderIdForAuth(provider: string): string {
|
||||
const normalized = normalizeProviderId(provider);
|
||||
if (normalized === "volcengine-plan") {
|
||||
return "volcengine";
|
||||
}
|
||||
if (normalized === "byteplus-plan") {
|
||||
return "byteplus";
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export function findNormalizedProviderValue<T>(
|
||||
entries: Record<string, T> | undefined,
|
||||
provider: string,
|
||||
|
||||
24
src/agents/provider-id.test.ts
Normal file
24
src/agents/provider-id.test.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { normalizeProviderId, normalizeProviderIdForAuth } from "./provider-id.js";
|
||||
|
||||
describe("normalizeProviderId", () => {
|
||||
it("applies provider aliases without pulling heavier model-selection dependencies", () => {
|
||||
expect(normalizeProviderId("Anthropic")).toBe("anthropic");
|
||||
expect(normalizeProviderId("Z.ai")).toBe("zai");
|
||||
expect(normalizeProviderId("z-ai")).toBe("zai");
|
||||
expect(normalizeProviderId("OpenCode-Zen")).toBe("opencode");
|
||||
expect(normalizeProviderId("qwen")).toBe("qwen-portal");
|
||||
expect(normalizeProviderId("kimi-code")).toBe("kimi-coding");
|
||||
expect(normalizeProviderId("bedrock")).toBe("amazon-bedrock");
|
||||
expect(normalizeProviderId("aws-bedrock")).toBe("amazon-bedrock");
|
||||
expect(normalizeProviderId("doubao")).toBe("volcengine");
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalizeProviderIdForAuth", () => {
|
||||
it("maps coding-plan variants back to their base auth providers", () => {
|
||||
expect(normalizeProviderIdForAuth("volcengine-plan")).toBe("volcengine");
|
||||
expect(normalizeProviderIdForAuth("byteplus-plan")).toBe("byteplus");
|
||||
expect(normalizeProviderIdForAuth("anthropic")).toBe("anthropic");
|
||||
});
|
||||
});
|
||||
38
src/agents/provider-id.ts
Normal file
38
src/agents/provider-id.ts
Normal file
@ -0,0 +1,38 @@
|
||||
export function normalizeProviderId(provider: string): string {
|
||||
const normalized = provider.trim().toLowerCase();
|
||||
if (normalized === "z.ai" || normalized === "z-ai") {
|
||||
return "zai";
|
||||
}
|
||||
if (normalized === "opencode-zen") {
|
||||
return "opencode";
|
||||
}
|
||||
if (normalized === "opencode-go-auth") {
|
||||
return "opencode-go";
|
||||
}
|
||||
if (normalized === "qwen") {
|
||||
return "qwen-portal";
|
||||
}
|
||||
if (normalized === "kimi-code") {
|
||||
return "kimi-coding";
|
||||
}
|
||||
if (normalized === "bedrock" || normalized === "aws-bedrock") {
|
||||
return "amazon-bedrock";
|
||||
}
|
||||
// Backward compatibility for older provider naming.
|
||||
if (normalized === "bytedance" || normalized === "doubao") {
|
||||
return "volcengine";
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
/** Normalize provider ID for auth lookup. Coding-plan variants share auth with base. */
|
||||
export function normalizeProviderIdForAuth(provider: string): string {
|
||||
const normalized = normalizeProviderId(provider);
|
||||
if (normalized === "volcengine-plan") {
|
||||
return "volcengine";
|
||||
}
|
||||
if (normalized === "byteplus-plan") {
|
||||
return "byteplus";
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
107
src/extension-host/provider-discovery.test.ts
Normal file
107
src/extension-host/provider-discovery.test.ts
Normal file
@ -0,0 +1,107 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { ModelProviderConfig } from "../config/types.js";
|
||||
import type { ProviderDiscoveryOrder, ProviderPlugin } from "../plugins/types.js";
|
||||
import {
|
||||
groupExtensionHostDiscoveryProvidersByOrder,
|
||||
normalizeExtensionHostDiscoveryResult,
|
||||
resolveExtensionHostDiscoveryProviders,
|
||||
} from "./provider-discovery.js";
|
||||
|
||||
function makeProvider(params: {
|
||||
id: string;
|
||||
label?: string;
|
||||
order?: ProviderDiscoveryOrder;
|
||||
discovery?: boolean;
|
||||
}): ProviderPlugin {
|
||||
return {
|
||||
id: params.id,
|
||||
label: params.label ?? params.id,
|
||||
auth: [],
|
||||
...(params.discovery === false
|
||||
? {}
|
||||
: {
|
||||
discovery: {
|
||||
...(params.order ? { order: params.order } : {}),
|
||||
run: async () => null,
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function makeModelProviderConfig(overrides?: Partial<ModelProviderConfig>): ModelProviderConfig {
|
||||
return {
|
||||
baseUrl: "http://127.0.0.1:8000/v1",
|
||||
models: [],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("resolveExtensionHostDiscoveryProviders", () => {
|
||||
it("keeps only providers with discovery handlers", () => {
|
||||
expect(
|
||||
resolveExtensionHostDiscoveryProviders([
|
||||
makeProvider({ id: "simple" }),
|
||||
makeProvider({ id: "hidden", discovery: false }),
|
||||
]).map((provider) => provider.id),
|
||||
).toEqual(["simple"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("groupExtensionHostDiscoveryProvidersByOrder", () => {
|
||||
it("groups providers by declared order and sorts labels within each group", () => {
|
||||
const grouped = groupExtensionHostDiscoveryProvidersByOrder([
|
||||
makeProvider({ id: "late-b", label: "Zulu" }),
|
||||
makeProvider({ id: "late-a", label: "Alpha" }),
|
||||
makeProvider({ id: "paired", label: "Paired", order: "paired" }),
|
||||
makeProvider({ id: "profile", label: "Profile", order: "profile" }),
|
||||
makeProvider({ id: "simple", label: "Simple", order: "simple" }),
|
||||
]);
|
||||
|
||||
expect(grouped.simple.map((provider) => provider.id)).toEqual(["simple"]);
|
||||
expect(grouped.profile.map((provider) => provider.id)).toEqual(["profile"]);
|
||||
expect(grouped.paired.map((provider) => provider.id)).toEqual(["paired"]);
|
||||
expect(grouped.late.map((provider) => provider.id)).toEqual(["late-a", "late-b"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalizeExtensionHostDiscoveryResult", () => {
|
||||
it("maps a single provider result to the provider id", () => {
|
||||
const provider = makeProvider({ id: "Ollama" });
|
||||
const normalized = normalizeExtensionHostDiscoveryResult({
|
||||
provider,
|
||||
result: {
|
||||
provider: makeModelProviderConfig({
|
||||
baseUrl: "http://127.0.0.1:11434",
|
||||
api: "ollama",
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
expect(normalized).toEqual({
|
||||
ollama: {
|
||||
baseUrl: "http://127.0.0.1:11434",
|
||||
api: "ollama",
|
||||
models: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("normalizes keys for multi-provider discovery results", () => {
|
||||
const normalized = normalizeExtensionHostDiscoveryResult({
|
||||
provider: makeProvider({ id: "ignored" }),
|
||||
result: {
|
||||
providers: {
|
||||
" VLLM ": makeModelProviderConfig(),
|
||||
"": makeModelProviderConfig({ baseUrl: "http://ignored" }),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(normalized).toEqual({
|
||||
vllm: {
|
||||
baseUrl: "http://127.0.0.1:8000/v1",
|
||||
models: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
61
src/extension-host/provider-discovery.ts
Normal file
61
src/extension-host/provider-discovery.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { normalizeProviderId } from "../agents/provider-id.js";
|
||||
import type { ModelProviderConfig } from "../config/types.js";
|
||||
import type { ProviderDiscoveryOrder, ProviderPlugin } from "../plugins/types.js";
|
||||
|
||||
const DISCOVERY_ORDER: readonly ProviderDiscoveryOrder[] = ["simple", "profile", "paired", "late"];
|
||||
|
||||
export function resolveExtensionHostDiscoveryProviders(
|
||||
providers: ProviderPlugin[],
|
||||
): ProviderPlugin[] {
|
||||
return providers.filter((provider) => provider.discovery);
|
||||
}
|
||||
|
||||
export function groupExtensionHostDiscoveryProvidersByOrder(
|
||||
providers: ProviderPlugin[],
|
||||
): Record<ProviderDiscoveryOrder, ProviderPlugin[]> {
|
||||
const grouped = {
|
||||
simple: [],
|
||||
profile: [],
|
||||
paired: [],
|
||||
late: [],
|
||||
} as Record<ProviderDiscoveryOrder, ProviderPlugin[]>;
|
||||
|
||||
for (const provider of providers) {
|
||||
const order = provider.discovery?.order ?? "late";
|
||||
grouped[order].push(provider);
|
||||
}
|
||||
|
||||
for (const order of DISCOVERY_ORDER) {
|
||||
grouped[order].sort((a, b) => a.label.localeCompare(b.label));
|
||||
}
|
||||
|
||||
return grouped;
|
||||
}
|
||||
|
||||
export function normalizeExtensionHostDiscoveryResult(params: {
|
||||
provider: ProviderPlugin;
|
||||
result:
|
||||
| { provider: ModelProviderConfig }
|
||||
| { providers: Record<string, ModelProviderConfig> }
|
||||
| null
|
||||
| undefined;
|
||||
}): Record<string, ModelProviderConfig> {
|
||||
const result = params.result;
|
||||
if (!result) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if ("provider" in result) {
|
||||
return { [normalizeProviderId(params.provider.id)]: result.provider };
|
||||
}
|
||||
|
||||
const normalized: Record<string, ModelProviderConfig> = {};
|
||||
for (const [key, value] of Object.entries(result.providers)) {
|
||||
const normalizedKey = normalizeProviderId(key);
|
||||
if (!normalizedKey || !value) {
|
||||
continue;
|
||||
}
|
||||
normalized[normalizedKey] = value;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
@ -1,6 +1,10 @@
|
||||
import { normalizeProviderId } from "../agents/model-selection.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { ModelProviderConfig } from "../config/types.js";
|
||||
import {
|
||||
groupExtensionHostDiscoveryProvidersByOrder,
|
||||
normalizeExtensionHostDiscoveryResult,
|
||||
resolveExtensionHostDiscoveryProviders,
|
||||
} from "../extension-host/provider-discovery.js";
|
||||
import { resolvePluginProviders } from "./providers.js";
|
||||
import type { ProviderDiscoveryOrder, ProviderPlugin } from "./types.js";
|
||||
|
||||
@ -51,24 +55,7 @@ export function normalizePluginDiscoveryResult(params: {
|
||||
| null
|
||||
| undefined;
|
||||
}): Record<string, ModelProviderConfig> {
|
||||
const result = params.result;
|
||||
if (!result) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if ("provider" in result) {
|
||||
return { [normalizeProviderId(params.provider.id)]: result.provider };
|
||||
}
|
||||
|
||||
const normalized: Record<string, ModelProviderConfig> = {};
|
||||
for (const [key, value] of Object.entries(result.providers)) {
|
||||
const normalizedKey = normalizeProviderId(key);
|
||||
if (!normalizedKey || !value) {
|
||||
continue;
|
||||
}
|
||||
normalized[normalizedKey] = value;
|
||||
}
|
||||
return normalized;
|
||||
return normalizeExtensionHostDiscoveryResult(params);
|
||||
}
|
||||
|
||||
export function runProviderCatalog(params: {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user