mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 19:14:44 +00:00
fix(gateway): wrap malformed pricing catalog json
This commit is contained in:
@@ -54,6 +54,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Voice-call provider APIs: report malformed successful guarded JSON responses with provider-prefixed errors instead of leaking raw parser failures.
|
||||
- Realtime transcription: report malformed provider websocket JSON frames with owned parser errors instead of leaking raw `SyntaxError` objects.
|
||||
- Microsoft Foundry: report malformed Azure CLI token JSON with owned auth errors instead of leaking raw parser failures.
|
||||
- Gateway/model pricing: report malformed external pricing catalog JSON with source-owned errors instead of leaking raw parser failures.
|
||||
- Models config/auth: stop inferring provider env-var markers from broad `^[A-Z_][A-Z0-9_]*$` strings, and resolve config-backed provider `apiKey` values only through structured env SecretRefs (`secrets.providers[id]` / `secrets.defaults`), so unrelated env vars cannot accidentally become provider credentials. Thanks @sallyom.
|
||||
- Media fetch: skip allocating and buffering the response body for bodyless media responses (HEAD probes and 204-style empty bodies), avoiding wasted heap on streams that carry no payload. Thanks @shakkernerd.
|
||||
- CLI/onboarding: forward provider-specific auth flags (e.g. `--openai-api-key`) through the onboarding wizard so they reach provider auth methods via `ctx.opts`, letting `--openai-api-key "$OPENAI_API_KEY"` skip the redundant "use existing env var?" prompt in non-interactive harnesses. (#81669) Thanks @sjf.
|
||||
|
||||
@@ -452,6 +452,47 @@ describe("model-pricing-cache", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("records malformed remote pricing catalog JSON as source failures", 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 fetchImpl = withFetchPreconnect(async (input: RequestInfo | URL) => {
|
||||
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
||||
if (url.includes("openrouter.ai")) {
|
||||
return new Response("{not json", {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
return new Response(JSON.stringify({}), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
});
|
||||
|
||||
await refreshGatewayModelPricingCache({ config, fetchImpl });
|
||||
|
||||
const health = getGatewayModelPricingHealth();
|
||||
expect(health.state).toBe("degraded");
|
||||
expect(health.sources).toHaveLength(1);
|
||||
expect(health.sources[0]?.source).toBe("openrouter");
|
||||
expect(health.sources[0]?.state).toBe("degraded");
|
||||
expect(health.sources[0]?.detail).toContain("OpenRouter pricing response is malformed JSON");
|
||||
});
|
||||
|
||||
it("records and clears scheduled refresh rejections for health surfaces", async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
|
||||
@@ -261,7 +261,12 @@ async function readPricingJsonObject(
|
||||
if (buffer.byteLength > MAX_PRICING_CATALOG_BYTES) {
|
||||
throw new Error(`${source} pricing response too large: ${buffer.byteLength} bytes`);
|
||||
}
|
||||
const payload = JSON.parse(Buffer.from(buffer).toString("utf8")) as unknown;
|
||||
let payload: unknown;
|
||||
try {
|
||||
payload = JSON.parse(Buffer.from(buffer).toString("utf8")) as unknown;
|
||||
} catch {
|
||||
throw new Error(`${source} pricing response is malformed JSON`);
|
||||
}
|
||||
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
||||
throw new Error(`${source} pricing response is not a JSON object`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user