mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 19:14:44 +00:00
fix(providers): harden model catalog response schemas
This commit is contained in:
@@ -646,6 +646,24 @@ describe("fetchCopilotModelCatalog", () => {
|
||||
).rejects.toThrow(/HTTP 401/);
|
||||
});
|
||||
|
||||
it("throws provider-owned errors for malformed successful /models payloads", async () => {
|
||||
for (const payload of [[], { data: {} }, { data: [null] }]) {
|
||||
const fetchImpl = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => payload,
|
||||
});
|
||||
|
||||
await expect(
|
||||
fetchCopilotModelCatalog({
|
||||
copilotApiToken: "tid=test",
|
||||
baseUrl: "https://api.githubcopilot.com",
|
||||
fetchImpl: fetchImpl as unknown as typeof fetch,
|
||||
}),
|
||||
).rejects.toThrow("Copilot /models: malformed JSON response");
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects empty token / baseUrl synchronously before fetching", async () => {
|
||||
const fetchImpl = vi.fn();
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import type {
|
||||
ProviderRuntimeModel,
|
||||
} from "openclaw/plugin-sdk/core";
|
||||
import { buildCopilotIdeHeaders, COPILOT_INTEGRATION_ID } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { readProviderJsonArrayFieldResponse } from "openclaw/plugin-sdk/provider-http";
|
||||
import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { normalizeModelCompat } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
@@ -191,6 +192,13 @@ function mapCopilotApiModelToDefinition(
|
||||
return definition;
|
||||
}
|
||||
|
||||
function asCopilotApiModelEntry(value: unknown): CopilotApiModelEntry {
|
||||
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
||||
throw new Error("Copilot /models: malformed JSON response");
|
||||
}
|
||||
return value as CopilotApiModelEntry;
|
||||
}
|
||||
|
||||
export type FetchCopilotModelCatalogParams = {
|
||||
/** Short-lived Copilot API token (from `resolveCopilotApiToken`). */
|
||||
copilotApiToken: string;
|
||||
@@ -242,11 +250,11 @@ export async function fetchCopilotModelCatalog(
|
||||
if (!res.ok) {
|
||||
throw new Error(`Copilot /models fetch failed: HTTP ${res.status}`);
|
||||
}
|
||||
const json = (await res.json()) as { data?: CopilotApiModelEntry[] };
|
||||
const data = Array.isArray(json?.data) ? json.data : [];
|
||||
const data = await readProviderJsonArrayFieldResponse(res, "Copilot /models", "data");
|
||||
const seen = new Set<string>();
|
||||
const out: ModelDefinitionConfig[] = [];
|
||||
for (const entry of data) {
|
||||
for (const rawEntry of data) {
|
||||
const entry = asCopilotApiModelEntry(rawEntry);
|
||||
const def = mapCopilotApiModelToDefinition(entry);
|
||||
if (!def) {
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user