mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 04:15:48 -06:00
fix(agents): cancel model scan error bodies
This commit is contained in:
@@ -103,6 +103,16 @@ describe("scanOpenRouterModels", () => {
|
|||||||
expect(result?.createdAtMs).toBeNull();
|
expect(result?.createdAtMs).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("cancels catalog error response bodies", async () => {
|
||||||
|
const response = new Response("unavailable", { status: 503 });
|
||||||
|
const cancel = vi.spyOn(response.body!, "cancel").mockResolvedValue(undefined);
|
||||||
|
const fetchImpl = withFetchPreconnect(async () => response);
|
||||||
|
|
||||||
|
await expect(scanOpenRouterModels({ fetchImpl, probe: false })).rejects.toThrow(/HTTP 503/);
|
||||||
|
|
||||||
|
expect(cancel).toHaveBeenCalledOnce();
|
||||||
|
});
|
||||||
|
|
||||||
it("requires an API key when probing", async () => {
|
it("requires an API key when probing", async () => {
|
||||||
const fetchImpl = createFetchFixture({ data: [] });
|
const fetchImpl = createFetchFixture({ data: [] });
|
||||||
await withEnvAsync({ OPENROUTER_API_KEY: undefined }, async () => {
|
await withEnvAsync({ OPENROUTER_API_KEY: undefined }, async () => {
|
||||||
|
|||||||
+67
-59
@@ -188,73 +188,81 @@ async function fetchOpenRouterModels(
|
|||||||
fetchImpl: typeof fetch,
|
fetchImpl: typeof fetch,
|
||||||
timeoutMs: number,
|
timeoutMs: number,
|
||||||
): Promise<OpenRouterModelMeta[]> {
|
): Promise<OpenRouterModelMeta[]> {
|
||||||
const res = await withTimeout(timeoutMs, (signal) =>
|
let res: Response | undefined;
|
||||||
fetchImpl(OPENROUTER_MODELS_URL, {
|
try {
|
||||||
headers: { Accept: "application/json" },
|
res = await withTimeout(timeoutMs, (signal) =>
|
||||||
signal,
|
fetchImpl(OPENROUTER_MODELS_URL, {
|
||||||
}),
|
headers: { Accept: "application/json" },
|
||||||
);
|
signal,
|
||||||
if (!res.ok) {
|
}),
|
||||||
throw new Error(`OpenRouter /models failed: HTTP ${res.status}`);
|
);
|
||||||
}
|
if (!res.ok) {
|
||||||
const payload = (await res.json()) as { data?: unknown };
|
throw new Error(`OpenRouter /models failed: HTTP ${res.status}`);
|
||||||
const entries = Array.isArray(payload.data) ? payload.data : [];
|
}
|
||||||
|
const payload = (await res.json()) as { data?: unknown };
|
||||||
|
const entries = Array.isArray(payload.data) ? payload.data : [];
|
||||||
|
|
||||||
return entries
|
return entries
|
||||||
.map((entry) => {
|
.map((entry) => {
|
||||||
if (!entry || typeof entry !== "object") {
|
if (!entry || typeof entry !== "object") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const obj = entry as Record<string, unknown>;
|
const obj = entry as Record<string, unknown>;
|
||||||
const id = normalizeOptionalString(obj.id) ?? "";
|
const id = normalizeOptionalString(obj.id) ?? "";
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const name = typeof obj.name === "string" && obj.name.trim() ? obj.name.trim() : id;
|
const name = typeof obj.name === "string" && obj.name.trim() ? obj.name.trim() : id;
|
||||||
|
|
||||||
const contextLength =
|
const contextLength =
|
||||||
typeof obj.context_length === "number" && Number.isFinite(obj.context_length)
|
typeof obj.context_length === "number" && Number.isFinite(obj.context_length)
|
||||||
? obj.context_length
|
? obj.context_length
|
||||||
: null;
|
|
||||||
|
|
||||||
const maxCompletionTokens =
|
|
||||||
typeof obj.max_completion_tokens === "number" && Number.isFinite(obj.max_completion_tokens)
|
|
||||||
? obj.max_completion_tokens
|
|
||||||
: typeof obj.max_output_tokens === "number" && Number.isFinite(obj.max_output_tokens)
|
|
||||||
? obj.max_output_tokens
|
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const supportedParameters = Array.isArray(obj.supported_parameters)
|
const maxCompletionTokens =
|
||||||
? normalizeStringEntries(
|
typeof obj.max_completion_tokens === "number" &&
|
||||||
obj.supported_parameters.filter((value) => typeof value === "string"),
|
Number.isFinite(obj.max_completion_tokens)
|
||||||
)
|
? obj.max_completion_tokens
|
||||||
: [];
|
: typeof obj.max_output_tokens === "number" && Number.isFinite(obj.max_output_tokens)
|
||||||
|
? obj.max_output_tokens
|
||||||
|
: null;
|
||||||
|
|
||||||
const supportedParametersCount = supportedParameters.length;
|
const supportedParameters = Array.isArray(obj.supported_parameters)
|
||||||
const supportsToolsMeta = supportedParameters.includes("tools");
|
? normalizeStringEntries(
|
||||||
|
obj.supported_parameters.filter((value) => typeof value === "string"),
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
|
||||||
const modality =
|
const supportedParametersCount = supportedParameters.length;
|
||||||
typeof obj.modality === "string" && obj.modality.trim() ? obj.modality.trim() : null;
|
const supportsToolsMeta = supportedParameters.includes("tools");
|
||||||
|
|
||||||
const inferredParamB = inferParamBFromIdOrName(`${id} ${name}`);
|
const modality =
|
||||||
const createdAtMs = normalizeCreatedAtMs(obj.created_at);
|
typeof obj.modality === "string" && obj.modality.trim() ? obj.modality.trim() : null;
|
||||||
const pricing = parseOpenRouterPricing(obj.pricing);
|
|
||||||
|
|
||||||
return {
|
const inferredParamB = inferParamBFromIdOrName(`${id} ${name}`);
|
||||||
id,
|
const createdAtMs = normalizeCreatedAtMs(obj.created_at);
|
||||||
name,
|
const pricing = parseOpenRouterPricing(obj.pricing);
|
||||||
contextLength,
|
|
||||||
maxCompletionTokens,
|
return {
|
||||||
supportedParameters,
|
id,
|
||||||
supportedParametersCount,
|
name,
|
||||||
supportsToolsMeta,
|
contextLength,
|
||||||
modality,
|
maxCompletionTokens,
|
||||||
inferredParamB,
|
supportedParameters,
|
||||||
createdAtMs,
|
supportedParametersCount,
|
||||||
pricing,
|
supportsToolsMeta,
|
||||||
} satisfies OpenRouterModelMeta;
|
modality,
|
||||||
})
|
inferredParamB,
|
||||||
.filter((entry): entry is OpenRouterModelMeta => Boolean(entry));
|
createdAtMs,
|
||||||
|
pricing,
|
||||||
|
} satisfies OpenRouterModelMeta;
|
||||||
|
})
|
||||||
|
.filter((entry): entry is OpenRouterModelMeta => Boolean(entry));
|
||||||
|
} finally {
|
||||||
|
if (res && !res.bodyUsed) {
|
||||||
|
await res.body?.cancel().catch(() => undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function probeTool(
|
async function probeTool(
|
||||||
|
|||||||
Reference in New Issue
Block a user