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 () => {
|
it("loads openrouter pricing and maps provider aliases, wrappers, and anthropic dotted ids", async () => {
|
||||||
const config = {
|
const config = {
|
||||||
agents: {
|
agents: {
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import type { ModelCatalogCost } from "@openclaw/model-catalog-core/model-catalog-types";
|
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 { DEFAULT_PROVIDER } from "../agents/defaults.js";
|
||||||
import {
|
import {
|
||||||
buildModelAliasIndex,
|
buildModelAliasIndex,
|
||||||
@@ -26,7 +30,6 @@ import {
|
|||||||
} from "../plugins/plugin-metadata-snapshot.js";
|
} from "../plugins/plugin-metadata-snapshot.js";
|
||||||
import type { PluginMetadataRegistryView } from "../plugins/plugin-metadata-snapshot.types.js";
|
import type { PluginMetadataRegistryView } from "../plugins/plugin-metadata-snapshot.types.js";
|
||||||
import type { PluginRegistrySnapshot } from "../plugins/plugin-registry.js";
|
import type { PluginRegistrySnapshot } from "../plugins/plugin-registry.js";
|
||||||
import { normalizeOptionalString, resolvePrimaryStringValue } from "../../packages/normalization-core/src/string-coerce.js";
|
|
||||||
import {
|
import {
|
||||||
clearGatewayModelPricingCacheState,
|
clearGatewayModelPricingCacheState,
|
||||||
clearGatewayModelPricingFailures,
|
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: {
|
function filterActiveManifestRegistry(params: {
|
||||||
registry: PluginManifestRegistry;
|
registry: PluginManifestRegistry;
|
||||||
index: PluginRegistrySnapshot;
|
index: PluginRegistrySnapshot;
|
||||||
@@ -504,8 +546,8 @@ function loadManifestPricingContext(
|
|||||||
} {
|
} {
|
||||||
const policies = new Map<string, ExternalPricingPolicy>();
|
const policies = new Map<string, ExternalPricingPolicy>();
|
||||||
for (const plugin of registry.plugins) {
|
for (const plugin of registry.plugins) {
|
||||||
for (const [provider, rawPolicy] of Object.entries(plugin.modelPricing?.providers ?? {})) {
|
for (const { provider, policy: rawPolicy } of listManifestModelPricingPolicies(plugin)) {
|
||||||
const policy = normalizeExternalPricingPolicy(rawPolicy, normalizationOptions);
|
const policy = normalizeManifestModelPricingPolicy(rawPolicy, normalizationOptions);
|
||||||
if (policy) {
|
if (policy) {
|
||||||
policies.set(provider, policy);
|
policies.set(provider, policy);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user