mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-04 01:11:37 +00:00
221 lines
7.9 KiB
TypeScript
221 lines
7.9 KiB
TypeScript
// Google tests cover provider catalog plugin behavior.
|
|
import {
|
|
clearLiveCatalogCacheForTests,
|
|
type LiveModelCatalogFetchGuard,
|
|
} from "openclaw/plugin-sdk/provider-catalog-live-runtime";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import {
|
|
buildGoogleLiveCatalogProvider,
|
|
buildGoogleStaticCatalogProvider,
|
|
buildGoogleVertexStaticCatalogProvider,
|
|
} from "./provider-catalog.js";
|
|
|
|
describe("google provider catalog", () => {
|
|
beforeEach(() => {
|
|
clearLiveCatalogCacheForTests();
|
|
});
|
|
|
|
it("registers current Gemini rows for the Google Vertex provider", () => {
|
|
const provider = buildGoogleVertexStaticCatalogProvider();
|
|
|
|
expect(provider.api).toBe("google-vertex");
|
|
expect(provider.baseUrl).toBe("https://{location}-aiplatform.googleapis.com");
|
|
expect(provider.models.map((model) => model.id)).toEqual(
|
|
expect.arrayContaining([
|
|
"gemini-2.5-pro",
|
|
"gemini-3.1-pro-preview",
|
|
"gemini-3.5-flash-lite",
|
|
"gemini-3.6-flash",
|
|
]),
|
|
);
|
|
expect(provider.models.find((model) => model.id === "gemini-3.6-flash")).toMatchObject({
|
|
contextWindow: 1_048_576,
|
|
maxTokens: 65_536,
|
|
reasoning: true,
|
|
});
|
|
});
|
|
|
|
it("keeps Google AI Studio and Vertex model ids aligned", () => {
|
|
expect(buildGoogleVertexStaticCatalogProvider().models.map((model) => model.id)).toEqual(
|
|
buildGoogleStaticCatalogProvider().models.map((model) => model.id),
|
|
);
|
|
});
|
|
|
|
it("builds the authenticated text catalog from Google models.list metadata", async () => {
|
|
const release = vi.fn(async () => undefined);
|
|
const fetchGuard: LiveModelCatalogFetchGuard = vi.fn(async ({ url }) => {
|
|
const isSecondPage = new URL(url).searchParams.get("pageToken") === "page-2";
|
|
return {
|
|
response: Response.json(
|
|
isSecondPage
|
|
? {
|
|
models: [
|
|
{
|
|
name: "models/gemini-3.5-flash-lite",
|
|
displayName: "Gemini 3.5 Flash-Lite",
|
|
inputTokenLimit: 1_048_576,
|
|
outputTokenLimit: 65_536,
|
|
supportedGenerationMethods: ["generateContent"],
|
|
thinking: true,
|
|
},
|
|
{
|
|
name: "models/gemma-3-4b-it",
|
|
displayName: "Gemma 3 4B",
|
|
inputTokenLimit: 131_072,
|
|
outputTokenLimit: 8_192,
|
|
supportedGenerationMethods: ["generateContent"],
|
|
},
|
|
],
|
|
}
|
|
: {
|
|
models: [
|
|
{
|
|
name: "models/gemini-3.6-flash",
|
|
displayName: "Gemini 3.6 Flash",
|
|
inputTokenLimit: 1_048_576,
|
|
outputTokenLimit: 65_536,
|
|
supportedGenerationMethods: ["generateContent", "countTokens"],
|
|
thinking: true,
|
|
},
|
|
{
|
|
name: "models/gemma-3-1b-it",
|
|
displayName: "Gemma 3 1B",
|
|
inputTokenLimit: 32_768,
|
|
outputTokenLimit: 8_192,
|
|
supportedGenerationMethods: ["generateContent"],
|
|
},
|
|
{
|
|
name: "models/gemini-3.1-flash-image",
|
|
displayName: "Nano Banana 2",
|
|
inputTokenLimit: 65_536,
|
|
outputTokenLimit: 32_768,
|
|
supportedGenerationMethods: ["generateContent"],
|
|
},
|
|
{
|
|
name: "models/gemini-embedding-2-preview",
|
|
displayName: "Gemini Embedding 2",
|
|
inputTokenLimit: 8_192,
|
|
outputTokenLimit: 8_192,
|
|
supportedGenerationMethods: ["embedContent"],
|
|
},
|
|
],
|
|
nextPageToken: "page-2",
|
|
},
|
|
),
|
|
finalUrl: url,
|
|
release,
|
|
};
|
|
});
|
|
|
|
const provider = await buildGoogleLiveCatalogProvider({
|
|
apiKey: "GEMINI_API_KEY",
|
|
discoveryApiKey: "resolved-google-key",
|
|
fetchGuard,
|
|
});
|
|
|
|
expect(provider.apiKey).toBe("GEMINI_API_KEY");
|
|
expect(provider.models).toEqual([
|
|
expect.objectContaining({
|
|
id: "gemini-3.5-flash-lite",
|
|
name: "Gemini 3.5 Flash-Lite",
|
|
reasoning: true,
|
|
contextWindow: 1_048_576,
|
|
maxTokens: 65_536,
|
|
input: ["text", "image"],
|
|
compat: { codeMode: "preferred" },
|
|
}),
|
|
expect.objectContaining({
|
|
id: "gemini-3.6-flash",
|
|
name: "Gemini 3.6 Flash",
|
|
reasoning: true,
|
|
contextWindow: 1_048_576,
|
|
maxTokens: 65_536,
|
|
input: ["text", "image"],
|
|
compat: { codeMode: "preferred" },
|
|
}),
|
|
expect.objectContaining({
|
|
id: "gemma-3-1b-it",
|
|
name: "Gemma 3 1B",
|
|
input: ["text"],
|
|
}),
|
|
expect.objectContaining({
|
|
id: "gemma-3-4b-it",
|
|
name: "Gemma 3 4B",
|
|
input: ["text", "image"],
|
|
}),
|
|
]);
|
|
expect(
|
|
provider.models
|
|
.filter((model) => model.id.startsWith("gemma-"))
|
|
.every((model) => model.compat === undefined),
|
|
).toBe(true);
|
|
const request = vi.mocked(fetchGuard).mock.calls[0]?.[0];
|
|
expect(request?.url).toBe(
|
|
"https://generativelanguage.googleapis.com/v1beta/models?pageSize=1000",
|
|
);
|
|
expect(new Headers(request?.init?.headers).get("x-goog-api-key")).toBe("resolved-google-key");
|
|
expect(vi.mocked(fetchGuard).mock.calls[1]?.[0].url).toBe(
|
|
"https://generativelanguage.googleapis.com/v1beta/models?pageSize=1000&pageToken=page-2",
|
|
);
|
|
expect(release).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it("propagates static compat to discovered id variants of the same weights", async () => {
|
|
const cases: ReadonlyArray<{ id: string; compat: { codeMode: string } | undefined }> = [
|
|
{ id: "gemini-3.5-flash", compat: { codeMode: "preferred" } },
|
|
{ id: "gemini-3.5-flash-preview-06-17", compat: { codeMode: "preferred" } },
|
|
{ id: "gemini-3.1-pro-preview-05-06", compat: { codeMode: "preferred" } },
|
|
{ id: "gemini-flash-latest", compat: { codeMode: "preferred" } },
|
|
{ id: "gemini-flash-lite-latest", compat: { codeMode: "preferred" } },
|
|
{ id: "gemini-pro-latest", compat: { codeMode: "preferred" } },
|
|
// Dated variant of unflagged weights resolves but carries no compat.
|
|
{ id: "gemini-2.5-flash-preview-05-20", compat: undefined },
|
|
// Unknown family member fails closed: no static entry, no compat.
|
|
{ id: "gemini-3.9-flash", compat: undefined },
|
|
];
|
|
const fetchGuard: LiveModelCatalogFetchGuard = vi.fn(async ({ url }) => ({
|
|
response: Response.json({
|
|
models: cases.map(({ id }) => ({
|
|
name: `models/${id}`,
|
|
displayName: id,
|
|
inputTokenLimit: 1_048_576,
|
|
outputTokenLimit: 65_536,
|
|
supportedGenerationMethods: ["generateContent"],
|
|
thinking: true,
|
|
})),
|
|
}),
|
|
finalUrl: url,
|
|
release: async () => undefined,
|
|
}));
|
|
|
|
const provider = await buildGoogleLiveCatalogProvider({
|
|
apiKey: "GEMINI_API_KEY",
|
|
fetchGuard,
|
|
});
|
|
|
|
for (const { id, compat } of cases) {
|
|
const model = provider.models.find((entry) => entry.id === id);
|
|
expect(model, id).toBeDefined();
|
|
expect(model?.compat, id).toEqual(compat);
|
|
}
|
|
});
|
|
|
|
it("falls back to bundled rows when live discovery is unusable", async () => {
|
|
const fetchGuard: LiveModelCatalogFetchGuard = vi.fn(async ({ url }) => ({
|
|
response: Response.json({ models: [{ name: "models/gemini-3.6-flash" }] }),
|
|
finalUrl: url,
|
|
release: async () => undefined,
|
|
}));
|
|
|
|
const provider = await buildGoogleLiveCatalogProvider({
|
|
apiKey: "GEMINI_API_KEY",
|
|
discoveryApiKey: "resolved-google-key",
|
|
fetchGuard,
|
|
});
|
|
|
|
expect(provider.models.map((model) => model.id)).toEqual(
|
|
buildGoogleStaticCatalogProvider().models.map((model) => model.id),
|
|
);
|
|
});
|
|
});
|