mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix(gateway): guard model pricing metadata
This commit is contained in:
@@ -649,6 +649,50 @@ describe("model-pricing-cache", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("skips malformed manifest model-pricing metadata while preserving healthy policies", async () => {
|
||||
const config = {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "custom/gpt-remote" },
|
||||
},
|
||||
},
|
||||
models: {
|
||||
providers: {
|
||||
custom: {
|
||||
baseUrl: "https://models.example/v1",
|
||||
api: "openai-completions",
|
||||
models: [{ id: "gpt-remote" }],
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
const poisoned = createManifestRecord({ id: "poisoned-pricing" });
|
||||
Object.defineProperty(poisoned, "modelPricing", {
|
||||
get() {
|
||||
throw new Error("model pricing metadata exploded");
|
||||
},
|
||||
});
|
||||
const healthy = createManifestRecord({
|
||||
id: "healthy-pricing",
|
||||
modelPricing: {
|
||||
providers: {
|
||||
custom: { external: false },
|
||||
},
|
||||
},
|
||||
});
|
||||
const fetchImpl = vi.fn<typeof fetch>();
|
||||
|
||||
await expect(
|
||||
refreshGatewayModelPricingCache({
|
||||
config,
|
||||
fetchImpl,
|
||||
manifestRegistry: { diagnostics: [], plugins: [poisoned, healthy] },
|
||||
}),
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
expect(fetchImpl).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("loads openrouter pricing and maps provider aliases, wrappers, and anthropic dotted ids", async () => {
|
||||
const config = {
|
||||
agents: {
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import type { ModelCatalogCost } from "@openclaw/model-catalog-core/model-catalog-types";
|
||||
import {
|
||||
normalizeOptionalString,
|
||||
resolvePrimaryStringValue,
|
||||
} from "../../packages/normalization-core/src/string-coerce.js";
|
||||
import { DEFAULT_PROVIDER } from "../agents/defaults.js";
|
||||
import {
|
||||
buildModelAliasIndex,
|
||||
@@ -26,7 +30,6 @@ import {
|
||||
} from "../plugins/plugin-metadata-snapshot.js";
|
||||
import type { PluginMetadataRegistryView } from "../plugins/plugin-metadata-snapshot.types.js";
|
||||
import type { PluginRegistrySnapshot } from "../plugins/plugin-registry.js";
|
||||
import { normalizeOptionalString, resolvePrimaryStringValue } from "../../packages/normalization-core/src/string-coerce.js";
|
||||
import {
|
||||
clearGatewayModelPricingCacheState,
|
||||
clearGatewayModelPricingFailures,
|
||||
@@ -433,6 +436,45 @@ function normalizeExternalPricingPolicy(
|
||||
};
|
||||
}
|
||||
|
||||
function listManifestModelPricingPolicies(
|
||||
plugin: PluginManifestRecord,
|
||||
): Array<{ provider: string; policy: PluginManifestModelPricingProvider }> {
|
||||
let providers: Record<string, PluginManifestModelPricingProvider> | undefined;
|
||||
try {
|
||||
providers = plugin.modelPricing?.providers;
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
if (!providers || typeof providers !== "object") {
|
||||
return [];
|
||||
}
|
||||
let providerIds: string[];
|
||||
try {
|
||||
providerIds = Object.keys(providers);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
return providerIds.flatMap((provider) => {
|
||||
try {
|
||||
const policy = providers[provider];
|
||||
return policy ? [{ provider, policy }] : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeManifestModelPricingPolicy(
|
||||
value: PluginManifestModelPricingProvider,
|
||||
options: PricingModelNormalizationOptions,
|
||||
): ExternalPricingPolicy | undefined {
|
||||
try {
|
||||
return normalizeExternalPricingPolicy(value, options);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function filterActiveManifestRegistry(params: {
|
||||
registry: PluginManifestRegistry;
|
||||
index: PluginRegistrySnapshot;
|
||||
@@ -504,8 +546,8 @@ function loadManifestPricingContext(
|
||||
} {
|
||||
const policies = new Map<string, ExternalPricingPolicy>();
|
||||
for (const plugin of registry.plugins) {
|
||||
for (const [provider, rawPolicy] of Object.entries(plugin.modelPricing?.providers ?? {})) {
|
||||
const policy = normalizeExternalPricingPolicy(rawPolicy, normalizationOptions);
|
||||
for (const { provider, policy: rawPolicy } of listManifestModelPricingPolicies(plugin)) {
|
||||
const policy = normalizeManifestModelPricingPolicy(rawPolicy, normalizationOptions);
|
||||
if (policy) {
|
||||
policies.set(provider, policy);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user