mirror of
https://fastgit.cc/github.com/openclaw/openclaw
synced 2026-04-30 22:12:32 +08:00
perf: avoid registry loads in hot tests
This commit is contained in:
@@ -1,8 +1,12 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { resolveAuthProfileOrder } from "./auth-profiles/order.js";
|
||||
import type { AuthProfileStore } from "./auth-profiles/types.js";
|
||||
import { isProfileInCooldown } from "./auth-profiles/usage-state.js";
|
||||
|
||||
vi.mock("./provider-auth-aliases.js", () => ({
|
||||
resolveProviderIdForAuth: (provider: string) => provider.trim().toLowerCase(),
|
||||
}));
|
||||
|
||||
/**
|
||||
* Integration tests for cooldown auto-expiry through resolveAuthProfileOrder.
|
||||
* Verifies that profiles with expired cooldowns are treated as available and
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
ANTHROPIC_CFG,
|
||||
ANTHROPIC_STORE,
|
||||
@@ -6,6 +6,11 @@ import {
|
||||
import { resolveAuthProfileOrder } from "./auth-profiles/order.js";
|
||||
import type { AuthProfileStore } from "./auth-profiles/types.js";
|
||||
|
||||
vi.mock("./provider-auth-aliases.js", () => ({
|
||||
resolveProviderIdForAuth: (provider: string) =>
|
||||
provider.trim().toLowerCase() === "z.ai" ? "zai" : provider.trim().toLowerCase(),
|
||||
}));
|
||||
|
||||
function makeApiKeyStore(provider: string, profileIds: string[]): AuthProfileStore {
|
||||
return {
|
||||
version: 1,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { SecretRefSource } from "../config/types.secrets.js";
|
||||
import { loadPluginManifestRegistryForPluginRegistry } from "../plugins/plugin-registry.js";
|
||||
import { listOpenClawPluginManifestMetadata } from "../plugins/manifest-metadata-scan.js";
|
||||
import { listKnownProviderEnvApiKeyNames } from "./model-auth-env-vars.js";
|
||||
|
||||
export const MINIMAX_OAUTH_MARKER = "minimax-oauth";
|
||||
@@ -35,6 +35,13 @@ const LEGACY_ENV_API_KEY_MARKERS = [
|
||||
"MINIMAX_CODE_PLAN_KEY",
|
||||
];
|
||||
|
||||
function normalizeStringList(value: unknown): string[] {
|
||||
if (!Array.isArray(value)) {
|
||||
return [];
|
||||
}
|
||||
return value.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean);
|
||||
}
|
||||
|
||||
function listKnownEnvApiKeyMarkers(): Set<string> {
|
||||
knownEnvApiKeyMarkersCache ??= new Set([
|
||||
...listKnownProviderEnvApiKeyNames(),
|
||||
@@ -48,8 +55,10 @@ export function listKnownNonSecretApiKeyMarkers(): string[] {
|
||||
knownNonSecretApiKeyMarkersCache ??= [
|
||||
...new Set([
|
||||
...CORE_NON_SECRET_API_KEY_MARKERS,
|
||||
...loadPluginManifestRegistryForPluginRegistry({ includeDisabled: true }).plugins.flatMap(
|
||||
(plugin) => (plugin.origin === "bundled" ? (plugin.nonSecretAuthMarkers ?? []) : []),
|
||||
...listOpenClawPluginManifestMetadata().flatMap((plugin) =>
|
||||
plugin.origin === "bundled"
|
||||
? normalizeStringList(plugin.manifest.nonSecretAuthMarkers)
|
||||
: [],
|
||||
),
|
||||
]),
|
||||
];
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { loadPluginManifestRegistryForPluginRegistry } from "../plugins/plugin-registry.js";
|
||||
import { listOpenClawPluginManifestMetadata } from "../plugins/manifest-metadata-scan.js";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
} from "../shared/string-coerce.js";
|
||||
@@ -224,57 +225,123 @@ function isManifestProviderEndpointClass(value: string): value is ProviderEndpoi
|
||||
return MANIFEST_PROVIDER_ENDPOINT_CLASSES.has(value as ProviderEndpointClass);
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function normalizeStringList(value: unknown): string[] {
|
||||
if (!Array.isArray(value)) {
|
||||
return [];
|
||||
}
|
||||
return value
|
||||
.map((entry) => normalizeOptionalString(entry))
|
||||
.filter((entry): entry is string => entry !== undefined);
|
||||
}
|
||||
|
||||
function readManifestProviderEndpoints(
|
||||
manifest: Record<string, unknown>,
|
||||
): ManifestProviderEndpointCacheEntry[] {
|
||||
if (!Array.isArray(manifest.providerEndpoints)) {
|
||||
return [];
|
||||
}
|
||||
const entries: ManifestProviderEndpointCacheEntry[] = [];
|
||||
for (const rawEndpoint of manifest.providerEndpoints) {
|
||||
if (!isRecord(rawEndpoint)) {
|
||||
continue;
|
||||
}
|
||||
const endpointClassRaw = normalizeOptionalString(rawEndpoint.endpointClass);
|
||||
if (!endpointClassRaw || !isManifestProviderEndpointClass(endpointClassRaw)) {
|
||||
continue;
|
||||
}
|
||||
entries.push({
|
||||
endpointClass: endpointClassRaw,
|
||||
hosts: normalizeStringList(rawEndpoint.hosts).map((host) => host.toLowerCase()),
|
||||
hostSuffixes: normalizeStringList(rawEndpoint.hostSuffixes).map((host) => host.toLowerCase()),
|
||||
normalizedBaseUrls: normalizeStringList(rawEndpoint.baseUrls)
|
||||
.map((baseUrl) => normalizeComparableBaseUrl(baseUrl))
|
||||
.filter((baseUrl): baseUrl is string => baseUrl !== undefined),
|
||||
...(normalizeOptionalString(rawEndpoint.googleVertexRegion)
|
||||
? { googleVertexRegion: normalizeOptionalString(rawEndpoint.googleVertexRegion) }
|
||||
: {}),
|
||||
...(normalizeOptionalString(rawEndpoint.googleVertexRegionHostSuffix)
|
||||
? {
|
||||
googleVertexRegionHostSuffix: normalizeOptionalString(
|
||||
rawEndpoint.googleVertexRegionHostSuffix,
|
||||
),
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
function readManifestProviderRequests(
|
||||
manifest: Record<string, unknown>,
|
||||
): Array<[string, ManifestProviderRequestCacheEntry]> {
|
||||
const providerRequest = manifest.providerRequest;
|
||||
if (!isRecord(providerRequest) || !isRecord(providerRequest.providers)) {
|
||||
return [];
|
||||
}
|
||||
const entries: Array<[string, ManifestProviderRequestCacheEntry]> = [];
|
||||
for (const [providerRaw, requestRaw] of Object.entries(providerRequest.providers)) {
|
||||
if (!isRecord(requestRaw)) {
|
||||
continue;
|
||||
}
|
||||
const provider = normalizeLowercaseStringOrEmpty(providerRaw);
|
||||
if (!provider) {
|
||||
continue;
|
||||
}
|
||||
const compatibilityFamily =
|
||||
normalizeOptionalString(requestRaw.compatibilityFamily) === "moonshot"
|
||||
? "moonshot"
|
||||
: undefined;
|
||||
const supportsStreamingUsage = isRecord(requestRaw.openAICompletions)
|
||||
? requestRaw.openAICompletions.supportsStreamingUsage
|
||||
: undefined;
|
||||
entries.push([
|
||||
provider,
|
||||
{
|
||||
...(normalizeOptionalString(requestRaw.family)
|
||||
? { family: normalizeOptionalString(requestRaw.family) }
|
||||
: {}),
|
||||
...(compatibilityFamily ? { compatibilityFamily } : {}),
|
||||
...(typeof supportsStreamingUsage === "boolean"
|
||||
? { supportsOpenAICompletionsStreamingUsageCompat: supportsStreamingUsage }
|
||||
: {}),
|
||||
},
|
||||
]);
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
function collectManifestProviderEndpoints(): ManifestProviderEndpointCacheEntry[] {
|
||||
const entries: ManifestProviderEndpointCacheEntry[] = [];
|
||||
for (const { manifest } of listOpenClawPluginManifestMetadata()) {
|
||||
entries.push(...readManifestProviderEndpoints(manifest));
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
function collectManifestProviderRequests(): Map<string, ManifestProviderRequestCacheEntry> {
|
||||
const entries = new Map<string, ManifestProviderRequestCacheEntry>();
|
||||
for (const { manifest } of listOpenClawPluginManifestMetadata()) {
|
||||
for (const [provider, request] of readManifestProviderRequests(manifest)) {
|
||||
entries.set(provider, request);
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
function loadManifestProviderEndpointCache(): ManifestProviderEndpointCacheEntry[] {
|
||||
if (!manifestProviderEndpointCache) {
|
||||
const registry = loadPluginManifestRegistryForPluginRegistry({ includeDisabled: true });
|
||||
const entries: ManifestProviderEndpointCacheEntry[] = [];
|
||||
for (const plugin of registry.plugins) {
|
||||
for (const endpoint of plugin.providerEndpoints ?? []) {
|
||||
if (!isManifestProviderEndpointClass(endpoint.endpointClass)) {
|
||||
continue;
|
||||
}
|
||||
entries.push({
|
||||
endpointClass: endpoint.endpointClass,
|
||||
hosts: (endpoint.hosts ?? []).map((host) => host.toLowerCase()),
|
||||
hostSuffixes: (endpoint.hostSuffixes ?? []).map((host) => host.toLowerCase()),
|
||||
normalizedBaseUrls: (endpoint.baseUrls ?? [])
|
||||
.map((baseUrl) => normalizeComparableBaseUrl(baseUrl))
|
||||
.filter((baseUrl): baseUrl is string => baseUrl !== undefined),
|
||||
...(endpoint.googleVertexRegion
|
||||
? { googleVertexRegion: endpoint.googleVertexRegion }
|
||||
: {}),
|
||||
...(endpoint.googleVertexRegionHostSuffix
|
||||
? { googleVertexRegionHostSuffix: endpoint.googleVertexRegionHostSuffix }
|
||||
: {}),
|
||||
});
|
||||
}
|
||||
}
|
||||
manifestProviderEndpointCache = entries;
|
||||
manifestProviderEndpointCache = collectManifestProviderEndpoints();
|
||||
}
|
||||
return manifestProviderEndpointCache;
|
||||
}
|
||||
|
||||
function loadManifestProviderRequestCache(): Map<string, ManifestProviderRequestCacheEntry> {
|
||||
if (!manifestProviderRequestCache) {
|
||||
const registry = loadPluginManifestRegistryForPluginRegistry({ includeDisabled: true });
|
||||
const entries = new Map<string, ManifestProviderRequestCacheEntry>();
|
||||
for (const plugin of registry.plugins) {
|
||||
for (const [provider, request] of Object.entries(plugin.providerRequest?.providers ?? {})) {
|
||||
entries.set(provider, {
|
||||
...(request.family ? { family: request.family } : {}),
|
||||
...(request.compatibilityFamily
|
||||
? { compatibilityFamily: request.compatibilityFamily }
|
||||
: {}),
|
||||
...(request.openAICompletions?.supportsStreamingUsage !== undefined
|
||||
? {
|
||||
supportsOpenAICompletionsStreamingUsageCompat:
|
||||
request.openAICompletions.supportsStreamingUsage,
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
}
|
||||
}
|
||||
manifestProviderRequestCache = entries;
|
||||
manifestProviderRequestCache = collectManifestProviderRequests();
|
||||
}
|
||||
return manifestProviderRequestCache;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import path from "node:path";
|
||||
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import { withEnv } from "../test-utils/env.js";
|
||||
import { createFixtureSuite } from "../test-utils/fixture-suite.js";
|
||||
import { writeSkill } from "./skills.e2e-test-helpers.js";
|
||||
@@ -7,6 +7,10 @@ import { createSyntheticSourceInfo } from "./skills/skill-contract.js";
|
||||
import type { OpenClawSkillMetadata, SkillEntry } from "./skills/types.js";
|
||||
import { buildWorkspaceSkillsPrompt } from "./skills/workspace.js";
|
||||
|
||||
vi.mock("./skills/plugin-skills.js", () => ({
|
||||
resolvePluginSkillDirs: () => [],
|
||||
}));
|
||||
|
||||
const fixtureSuite = createFixtureSuite("openclaw-skills-prompt-suite-");
|
||||
|
||||
beforeAll(async () => {
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
// Keep provider-owned exports out of this subpath so plugin loaders can import it
|
||||
// without recursing through provider-specific facades.
|
||||
|
||||
import { resolveProviderRequestCapabilities } from "../agents/provider-attribution.js";
|
||||
import { findNormalizedProviderKey } from "../agents/provider-id.js";
|
||||
import type { ModelDefinitionConfig } from "../config/types.models.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { resolveProviderRequestCapabilities } from "./provider-http.js";
|
||||
import type { ModelProviderConfig } from "./provider-model-shared.js";
|
||||
|
||||
export type { ProviderCatalogContext, ProviderCatalogResult } from "../plugins/types.js";
|
||||
|
||||
188
src/plugins/manifest-metadata-scan.ts
Normal file
188
src/plugins/manifest-metadata-scan.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { parseJsonWithJson5Fallback } from "../utils/parse-json-compat.js";
|
||||
|
||||
type PluginManifestMetadataRecord = {
|
||||
pluginDir: string;
|
||||
manifest: Record<string, unknown>;
|
||||
origin?: string;
|
||||
};
|
||||
|
||||
type CandidateDir = {
|
||||
pluginDir: string;
|
||||
rank: number;
|
||||
order: number;
|
||||
origin?: string;
|
||||
};
|
||||
|
||||
const OPENCLAW_PACKAGE_ROOT = fileURLToPath(new URL("../..", import.meta.url));
|
||||
const PLUGIN_MANIFEST_FILENAME = "openclaw.plugin.json";
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function normalizeTrimmedString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
function resolveUserPath(value: string, env: NodeJS.ProcessEnv): string {
|
||||
if (value === "~" || value.startsWith("~/")) {
|
||||
const home = env.OPENCLAW_HOME ?? env.HOME ?? env.USERPROFILE ?? os.homedir();
|
||||
return path.join(home, value.slice(2));
|
||||
}
|
||||
return path.resolve(value);
|
||||
}
|
||||
|
||||
function resolveStateDir(env: NodeJS.ProcessEnv): string {
|
||||
const override = normalizeTrimmedString(env.OPENCLAW_STATE_DIR);
|
||||
if (override) {
|
||||
return resolveUserPath(override, env);
|
||||
}
|
||||
const home = env.OPENCLAW_HOME ?? env.HOME ?? env.USERPROFILE ?? os.homedir();
|
||||
return path.join(home, ".openclaw");
|
||||
}
|
||||
|
||||
function areBundledPluginsDisabled(env: NodeJS.ProcessEnv): boolean {
|
||||
const value = normalizeTrimmedString(env.OPENCLAW_DISABLE_BUNDLED_PLUGINS)?.toLowerCase();
|
||||
return value === "1" || value === "true";
|
||||
}
|
||||
|
||||
function hasManifestDir(root: string | undefined): root is string {
|
||||
return Boolean(root && fs.existsSync(root));
|
||||
}
|
||||
|
||||
function resolveBundledPluginRoot(env: NodeJS.ProcessEnv): string | undefined {
|
||||
if (areBundledPluginsDisabled(env)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const override = normalizeTrimmedString(env.OPENCLAW_BUNDLED_PLUGINS_DIR);
|
||||
if (override) {
|
||||
return resolveUserPath(override, env);
|
||||
}
|
||||
|
||||
const sourceRoot = path.join(OPENCLAW_PACKAGE_ROOT, "extensions");
|
||||
const runtimeRoot = path.join(OPENCLAW_PACKAGE_ROOT, "dist-runtime", "extensions");
|
||||
const distRoot = path.join(OPENCLAW_PACKAGE_ROOT, "dist", "extensions");
|
||||
return [sourceRoot, runtimeRoot, distRoot].find(hasManifestDir);
|
||||
}
|
||||
|
||||
function listChildPluginDirs(
|
||||
root: string | undefined,
|
||||
rank: number,
|
||||
startOrder: number,
|
||||
origin: string,
|
||||
): CandidateDir[] {
|
||||
if (!root || !fs.existsSync(root)) {
|
||||
return [];
|
||||
}
|
||||
const dirs: CandidateDir[] = [];
|
||||
let order = startOrder;
|
||||
try {
|
||||
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
||||
if (entry.isDirectory()) {
|
||||
dirs.push({ pluginDir: path.join(root, entry.name), rank, order: order++, origin });
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
return dirs;
|
||||
}
|
||||
|
||||
function readJsonObject(filePath: string): Record<string, unknown> | undefined {
|
||||
try {
|
||||
const parsed = parseJsonWithJson5Fallback(fs.readFileSync(filePath, "utf8"));
|
||||
return isRecord(parsed) ? parsed : undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function readManifestObject(pluginDir: string): Record<string, unknown> | undefined {
|
||||
return readJsonObject(path.join(pluginDir, PLUGIN_MANIFEST_FILENAME));
|
||||
}
|
||||
|
||||
function listPersistedIndexPluginDirs(env: NodeJS.ProcessEnv, startOrder: number): CandidateDir[] {
|
||||
const index = readJsonObject(path.join(resolveStateDir(env), "plugins", "installs.json"));
|
||||
if (!index || !Array.isArray(index.plugins)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const dirs: CandidateDir[] = [];
|
||||
let order = startOrder;
|
||||
for (const rawPlugin of index.plugins) {
|
||||
if (!isRecord(rawPlugin)) {
|
||||
continue;
|
||||
}
|
||||
const rootDir = normalizeTrimmedString(rawPlugin.rootDir);
|
||||
if (!rootDir) {
|
||||
continue;
|
||||
}
|
||||
dirs.push({
|
||||
pluginDir: resolveUserPath(rootDir, env),
|
||||
rank: rawPlugin.origin === "bundled" ? 2 : 1,
|
||||
order: order++,
|
||||
origin: normalizeTrimmedString(rawPlugin.origin),
|
||||
});
|
||||
}
|
||||
return dirs;
|
||||
}
|
||||
|
||||
function resolveComparablePath(filePath: string): string {
|
||||
try {
|
||||
return fs.realpathSync(filePath);
|
||||
} catch {
|
||||
return path.resolve(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
function uniqueCandidateDirs(candidates: CandidateDir[]): CandidateDir[] {
|
||||
const byPath = new Map<string, CandidateDir>();
|
||||
for (const candidate of candidates) {
|
||||
const key = resolveComparablePath(candidate.pluginDir);
|
||||
const existing = byPath.get(key);
|
||||
if (!existing || candidate.rank < existing.rank || candidate.order < existing.order) {
|
||||
byPath.set(key, candidate);
|
||||
}
|
||||
}
|
||||
return [...byPath.values()].toSorted(
|
||||
(left, right) => left.rank - right.rank || left.order - right.order,
|
||||
);
|
||||
}
|
||||
|
||||
export function listOpenClawPluginManifestMetadata(
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): PluginManifestMetadataRecord[] {
|
||||
const candidates: CandidateDir[] = [];
|
||||
let order = 0;
|
||||
candidates.push(...listPersistedIndexPluginDirs(env, order));
|
||||
order = candidates.length;
|
||||
candidates.push(...listChildPluginDirs(resolveBundledPluginRoot(env), 2, order, "bundled"));
|
||||
order = candidates.length;
|
||||
candidates.push(
|
||||
...listChildPluginDirs(path.join(resolveStateDir(env), "extensions"), 4, order, "global"),
|
||||
);
|
||||
|
||||
const byManifestId = new Map<string, CandidateDir>();
|
||||
const records: PluginManifestMetadataRecord[] = [];
|
||||
for (const candidate of uniqueCandidateDirs(candidates)) {
|
||||
const manifest = readManifestObject(candidate.pluginDir);
|
||||
if (!manifest) {
|
||||
continue;
|
||||
}
|
||||
const manifestId = normalizeTrimmedString(manifest.id);
|
||||
if (manifestId) {
|
||||
const existing = byManifestId.get(manifestId);
|
||||
if (existing && existing.rank <= candidate.rank) {
|
||||
continue;
|
||||
}
|
||||
byManifestId.set(manifestId, candidate);
|
||||
}
|
||||
records.push({ pluginDir: candidate.pluginDir, manifest, origin: candidate.origin });
|
||||
}
|
||||
return records;
|
||||
}
|
||||
@@ -1,11 +1,115 @@
|
||||
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
|
||||
import { listOpenClawPluginManifestMetadata } from "./manifest-metadata-scan.js";
|
||||
import type { PluginManifestModelIdNormalizationProvider } from "./manifest.js";
|
||||
import { loadPluginManifestRegistryForPluginRegistry } from "./plugin-registry.js";
|
||||
|
||||
let manifestModelIdNormalizationCache:
|
||||
| Map<string, PluginManifestModelIdNormalizationProvider>
|
||||
| undefined;
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function normalizeTrimmedString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
function normalizeStringList(value: unknown): string[] {
|
||||
if (!Array.isArray(value)) {
|
||||
return [];
|
||||
}
|
||||
return value
|
||||
.map((entry) => normalizeTrimmedString(entry))
|
||||
.filter((entry): entry is string => entry !== undefined);
|
||||
}
|
||||
|
||||
function normalizePrefixRules(
|
||||
value: unknown,
|
||||
): PluginManifestModelIdNormalizationProvider["prefixWhenBareAfterAliasStartsWith"] {
|
||||
if (!Array.isArray(value)) {
|
||||
return undefined;
|
||||
}
|
||||
const rules: NonNullable<
|
||||
PluginManifestModelIdNormalizationProvider["prefixWhenBareAfterAliasStartsWith"]
|
||||
> = [];
|
||||
for (const rawRule of value) {
|
||||
if (!isRecord(rawRule)) {
|
||||
continue;
|
||||
}
|
||||
const modelPrefix = normalizeTrimmedString(rawRule.modelPrefix);
|
||||
const prefix = normalizeTrimmedString(rawRule.prefix);
|
||||
if (modelPrefix && prefix) {
|
||||
rules.push({ modelPrefix, prefix });
|
||||
}
|
||||
}
|
||||
return rules.length > 0 ? rules : undefined;
|
||||
}
|
||||
|
||||
function normalizeModelIdNormalizationPolicy(
|
||||
value: unknown,
|
||||
): PluginManifestModelIdNormalizationProvider | undefined {
|
||||
if (!isRecord(value)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const aliases: Record<string, string> = {};
|
||||
if (isRecord(value.aliases)) {
|
||||
for (const [aliasRaw, canonicalRaw] of Object.entries(value.aliases)) {
|
||||
const alias = normalizeLowercaseStringOrEmpty(aliasRaw);
|
||||
const canonical = normalizeTrimmedString(canonicalRaw);
|
||||
if (alias && canonical) {
|
||||
aliases[alias] = canonical;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const stripPrefixes = normalizeStringList(value.stripPrefixes);
|
||||
const prefixWhenBare = normalizeTrimmedString(value.prefixWhenBare);
|
||||
const prefixWhenBareAfterAliasStartsWith = normalizePrefixRules(
|
||||
value.prefixWhenBareAfterAliasStartsWith,
|
||||
);
|
||||
const policy = {
|
||||
...(Object.keys(aliases).length > 0 ? { aliases } : {}),
|
||||
...(stripPrefixes.length > 0 ? { stripPrefixes } : {}),
|
||||
...(prefixWhenBare ? { prefixWhenBare } : {}),
|
||||
...(prefixWhenBareAfterAliasStartsWith ? { prefixWhenBareAfterAliasStartsWith } : {}),
|
||||
} satisfies PluginManifestModelIdNormalizationProvider;
|
||||
|
||||
return Object.keys(policy).length > 0 ? policy : undefined;
|
||||
}
|
||||
|
||||
function readManifestModelIdNormalizationPolicies(
|
||||
manifest: Record<string, unknown>,
|
||||
): Array<[string, PluginManifestModelIdNormalizationProvider]> {
|
||||
const modelIdNormalization = manifest.modelIdNormalization;
|
||||
if (!isRecord(modelIdNormalization) || !isRecord(modelIdNormalization.providers)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const entries: Array<[string, PluginManifestModelIdNormalizationProvider]> = [];
|
||||
for (const [providerRaw, rawPolicy] of Object.entries(modelIdNormalization.providers)) {
|
||||
const provider = normalizeLowercaseStringOrEmpty(providerRaw);
|
||||
const policy = normalizeModelIdNormalizationPolicy(rawPolicy);
|
||||
if (provider && policy) {
|
||||
entries.push([provider, policy]);
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
function collectManifestModelIdNormalizationPolicies(): Map<
|
||||
string,
|
||||
PluginManifestModelIdNormalizationProvider
|
||||
> {
|
||||
const policies = new Map<string, PluginManifestModelIdNormalizationProvider>();
|
||||
for (const { manifest } of listOpenClawPluginManifestMetadata()) {
|
||||
for (const [provider, policy] of readManifestModelIdNormalizationPolicies(manifest)) {
|
||||
policies.set(provider, policy);
|
||||
}
|
||||
}
|
||||
return policies;
|
||||
}
|
||||
|
||||
function loadManifestModelIdNormalizationPolicies(): Map<
|
||||
string,
|
||||
PluginManifestModelIdNormalizationProvider
|
||||
@@ -14,17 +118,18 @@ function loadManifestModelIdNormalizationPolicies(): Map<
|
||||
return manifestModelIdNormalizationCache;
|
||||
}
|
||||
|
||||
const policies = new Map<string, PluginManifestModelIdNormalizationProvider>();
|
||||
const registry = loadPluginManifestRegistryForPluginRegistry({ includeDisabled: true });
|
||||
for (const plugin of registry.plugins) {
|
||||
for (const [provider, policy] of Object.entries(plugin.modelIdNormalization?.providers ?? {})) {
|
||||
policies.set(provider, policy);
|
||||
}
|
||||
}
|
||||
const policies = collectManifestModelIdNormalizationPolicies();
|
||||
manifestModelIdNormalizationCache = policies;
|
||||
return policies;
|
||||
}
|
||||
|
||||
function resolveManifestModelIdNormalizationPolicy(
|
||||
provider: string,
|
||||
): PluginManifestModelIdNormalizationProvider | undefined {
|
||||
const providerId = normalizeLowercaseStringOrEmpty(provider);
|
||||
return loadManifestModelIdNormalizationPolicies().get(providerId);
|
||||
}
|
||||
|
||||
function hasProviderPrefix(modelId: string): boolean {
|
||||
return modelId.includes("/");
|
||||
}
|
||||
@@ -40,7 +145,7 @@ export function normalizeProviderModelIdWithManifest(params: {
|
||||
modelId: string;
|
||||
};
|
||||
}): string | undefined {
|
||||
const policy = loadManifestModelIdNormalizationPolicies().get(params.provider);
|
||||
const policy = resolveManifestModelIdNormalizationPolicy(params.provider);
|
||||
if (!policy) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user