mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:40:44 +00:00
test: trim duplicate hotspot coverage
This commit is contained in:
@@ -47,11 +47,6 @@ function installFetchMock(fetchMock: typeof globalThis.fetch) {
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
}
|
||||
|
||||
function readFirstFetchRequest(fetchMock: { mock: { calls: unknown[][] } }) {
|
||||
const [url, init] = fetchMock.mock.calls[0] ?? [];
|
||||
return { url, init: init as RequestInit | undefined };
|
||||
}
|
||||
|
||||
function parseFetchBody(fetchMock: { mock: { calls: unknown[][] } }, callIndex = 0) {
|
||||
const init = fetchMock.mock.calls[callIndex]?.[1] as RequestInit | undefined;
|
||||
return JSON.parse((init?.body as string) ?? "{}") as Record<string, unknown>;
|
||||
@@ -67,6 +62,7 @@ let createGeminiEmbeddingProvider: typeof import("./embeddings-gemini.js").creat
|
||||
let DEFAULT_GEMINI_EMBEDDING_MODEL: typeof import("./embeddings-gemini.js").DEFAULT_GEMINI_EMBEDDING_MODEL;
|
||||
let GEMINI_EMBEDDING_2_MODELS: typeof import("./embeddings-gemini.js").GEMINI_EMBEDDING_2_MODELS;
|
||||
let isGeminiEmbedding2Model: typeof import("./embeddings-gemini.js").isGeminiEmbedding2Model;
|
||||
let normalizeGeminiModel: typeof import("./embeddings-gemini.js").normalizeGeminiModel;
|
||||
let resolveGeminiOutputDimensionality: typeof import("./embeddings-gemini.js").resolveGeminiOutputDimensionality;
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -78,6 +74,7 @@ beforeAll(async () => {
|
||||
DEFAULT_GEMINI_EMBEDDING_MODEL,
|
||||
GEMINI_EMBEDDING_2_MODELS,
|
||||
isGeminiEmbedding2Model,
|
||||
normalizeGeminiModel,
|
||||
resolveGeminiOutputDimensionality,
|
||||
} = await import("./embeddings-gemini.js"));
|
||||
});
|
||||
@@ -224,346 +221,73 @@ describe("resolveGeminiOutputDimensionality", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ---------- Provider: gemini-embedding-001 (backward compat) ----------
|
||||
// ---------- Provider behavior smoke coverage ----------
|
||||
|
||||
describe("gemini-embedding-001 provider (backward compat)", () => {
|
||||
it("does NOT include outputDimensionality in embedQuery", async () => {
|
||||
const fetchMock = createGeminiFetchMock();
|
||||
const provider = await createProviderWithFetch(fetchMock, {
|
||||
describe("gemini embedding provider", () => {
|
||||
it("handles legacy and v2 request/response behavior", async () => {
|
||||
const legacyFetch = createGeminiBatchFetchMock(2);
|
||||
const legacyProvider = await createProviderWithFetch(legacyFetch, {
|
||||
model: "gemini-embedding-001",
|
||||
});
|
||||
|
||||
await provider.embedQuery("test query");
|
||||
await legacyProvider.embedQuery("test query");
|
||||
await legacyProvider.embedBatch(["text1", "text2"]);
|
||||
|
||||
const body = parseFetchBody(fetchMock);
|
||||
expect(body).not.toHaveProperty("outputDimensionality");
|
||||
expect(body.taskType).toBe("RETRIEVAL_QUERY");
|
||||
expect(body.content).toEqual({ parts: [{ text: "test query" }] });
|
||||
});
|
||||
|
||||
it("does NOT include outputDimensionality in embedBatch", async () => {
|
||||
const fetchMock = createGeminiBatchFetchMock(2);
|
||||
const provider = await createProviderWithFetch(fetchMock, {
|
||||
model: "gemini-embedding-001",
|
||||
expect(parseFetchBody(legacyFetch, 0)).toMatchObject({
|
||||
taskType: "RETRIEVAL_QUERY",
|
||||
content: { parts: [{ text: "test query" }] },
|
||||
});
|
||||
expect(parseFetchBody(legacyFetch, 0)).not.toHaveProperty("outputDimensionality");
|
||||
expect(parseFetchBody(legacyFetch, 1)).not.toHaveProperty("outputDimensionality");
|
||||
|
||||
await provider.embedBatch(["text1", "text2"]);
|
||||
|
||||
const body = parseFetchBody(fetchMock);
|
||||
expect(body).not.toHaveProperty("outputDimensionality");
|
||||
});
|
||||
});
|
||||
|
||||
// ---------- Provider: gemini-embedding-2-preview ----------
|
||||
|
||||
describe("gemini-embedding-2-preview provider", () => {
|
||||
it("includes outputDimensionality in embedQuery request", async () => {
|
||||
const fetchMock = createGeminiFetchMock();
|
||||
const provider = await createProviderWithFetch(fetchMock, {
|
||||
model: "gemini-embedding-2-preview",
|
||||
});
|
||||
|
||||
await provider.embedQuery("test query");
|
||||
|
||||
const body = parseFetchBody(fetchMock);
|
||||
expect(body.outputDimensionality).toBe(3072);
|
||||
expect(body.taskType).toBe("RETRIEVAL_QUERY");
|
||||
expect(body.content).toEqual({ parts: [{ text: "test query" }] });
|
||||
});
|
||||
|
||||
it("normalizes embedQuery response vectors", async () => {
|
||||
const fetchMock = createGeminiFetchMock([3, 4]);
|
||||
const provider = await createProviderWithFetch(fetchMock, {
|
||||
model: "gemini-embedding-2-preview",
|
||||
});
|
||||
|
||||
const embedding = await provider.embedQuery("test query");
|
||||
|
||||
expectNormalizedThreeFourVector(embedding);
|
||||
});
|
||||
|
||||
it("includes outputDimensionality in embedBatch request", async () => {
|
||||
const fetchMock = createGeminiBatchFetchMock(2);
|
||||
const provider = await createProviderWithFetch(fetchMock, {
|
||||
model: "gemini-embedding-2-preview",
|
||||
});
|
||||
|
||||
await provider.embedBatch(["text1", "text2"]);
|
||||
|
||||
const body = parseFetchBody(fetchMock);
|
||||
expect(body.requests).toEqual([
|
||||
{
|
||||
model: "models/gemini-embedding-2-preview",
|
||||
content: { parts: [{ text: "text1" }] },
|
||||
taskType: "RETRIEVAL_DOCUMENT",
|
||||
outputDimensionality: 3072,
|
||||
},
|
||||
{
|
||||
model: "models/gemini-embedding-2-preview",
|
||||
content: { parts: [{ text: "text2" }] },
|
||||
taskType: "RETRIEVAL_DOCUMENT",
|
||||
outputDimensionality: 3072,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("normalizes embedBatch response vectors", async () => {
|
||||
const fetchMock = createGeminiBatchFetchMock(2, [3, 4]);
|
||||
const provider = await createProviderWithFetch(fetchMock, {
|
||||
model: "gemini-embedding-2-preview",
|
||||
});
|
||||
|
||||
const embeddings = await provider.embedBatch(["text1", "text2"]);
|
||||
|
||||
expect(embeddings).toHaveLength(2);
|
||||
for (const embedding of embeddings) {
|
||||
expectNormalizedThreeFourVector(embedding);
|
||||
}
|
||||
});
|
||||
|
||||
it("respects custom outputDimensionality", async () => {
|
||||
const fetchMock = createGeminiFetchMock();
|
||||
const provider = await createProviderWithFetch(fetchMock, {
|
||||
const v2QueryFetch = createGeminiFetchMock([3, 4]);
|
||||
const v2QueryProvider = await createProviderWithFetch(v2QueryFetch, {
|
||||
model: "gemini-embedding-2-preview",
|
||||
outputDimensionality: 768,
|
||||
});
|
||||
|
||||
await provider.embedQuery("test");
|
||||
|
||||
const body = parseFetchBody(fetchMock);
|
||||
expect(body.outputDimensionality).toBe(768);
|
||||
});
|
||||
|
||||
it("sanitizes and normalizes embedQuery responses", async () => {
|
||||
const fetchMock = createGeminiFetchMock([3, 4, Number.NaN]);
|
||||
const provider = await createProviderWithFetch(fetchMock, {
|
||||
model: "gemini-embedding-2-preview",
|
||||
});
|
||||
|
||||
await expect(provider.embedQuery("test")).resolves.toEqual([0.6, 0.8, 0]);
|
||||
});
|
||||
|
||||
it("uses custom outputDimensionality for each embedBatch request", async () => {
|
||||
const fetchMock = createGeminiBatchFetchMock(2);
|
||||
const provider = await createProviderWithFetch(fetchMock, {
|
||||
model: "gemini-embedding-2-preview",
|
||||
outputDimensionality: 768,
|
||||
});
|
||||
|
||||
await provider.embedBatch(["text1", "text2"]);
|
||||
|
||||
const body = parseFetchBody(fetchMock);
|
||||
expect(body.requests).toEqual([
|
||||
expect.objectContaining({ outputDimensionality: 768 }),
|
||||
expect.objectContaining({ outputDimensionality: 768 }),
|
||||
]);
|
||||
});
|
||||
|
||||
it("sanitizes and normalizes structured batch responses", async () => {
|
||||
const fetchMock = createGeminiBatchFetchMock(1, [0, Number.POSITIVE_INFINITY, 5]);
|
||||
const provider = await createProviderWithFetch(fetchMock, {
|
||||
model: "gemini-embedding-2-preview",
|
||||
});
|
||||
|
||||
await expect(
|
||||
provider.embedBatchInputs?.([
|
||||
{
|
||||
text: "Image file: diagram.png",
|
||||
parts: [
|
||||
{ type: "text", text: "Image file: diagram.png" },
|
||||
{ type: "inline-data", mimeType: "image/png", data: "img" },
|
||||
],
|
||||
},
|
||||
]),
|
||||
).resolves.toEqual([[0, 0, 1]]);
|
||||
});
|
||||
|
||||
it("supports multimodal embedBatchInputs requests", async () => {
|
||||
const fetchMock = createGeminiBatchFetchMock(2);
|
||||
const provider = await createProviderWithFetch(fetchMock, {
|
||||
model: "gemini-embedding-2-preview",
|
||||
});
|
||||
|
||||
expect(provider.embedBatchInputs).toBeDefined();
|
||||
await provider.embedBatchInputs?.([
|
||||
{
|
||||
text: "Image file: diagram.png",
|
||||
parts: [
|
||||
{ type: "text", text: "Image file: diagram.png" },
|
||||
{ type: "inline-data", mimeType: "image/png", data: "img" },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Audio file: note.wav",
|
||||
parts: [
|
||||
{ type: "text", text: "Audio file: note.wav" },
|
||||
{ type: "inline-data", mimeType: "audio/wav", data: "aud" },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const body = parseFetchBody(fetchMock);
|
||||
expect(body.requests).toEqual([
|
||||
{
|
||||
model: "models/gemini-embedding-2-preview",
|
||||
content: {
|
||||
parts: [
|
||||
{ text: "Image file: diagram.png" },
|
||||
{ inlineData: { mimeType: "image/png", data: "img" } },
|
||||
],
|
||||
},
|
||||
taskType: "RETRIEVAL_DOCUMENT",
|
||||
outputDimensionality: 3072,
|
||||
},
|
||||
{
|
||||
model: "models/gemini-embedding-2-preview",
|
||||
content: {
|
||||
parts: [
|
||||
{ text: "Audio file: note.wav" },
|
||||
{ inlineData: { mimeType: "audio/wav", data: "aud" } },
|
||||
],
|
||||
},
|
||||
taskType: "RETRIEVAL_DOCUMENT",
|
||||
outputDimensionality: 3072,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("throws for invalid outputDimensionality", async () => {
|
||||
mockResolvedProviderKey();
|
||||
|
||||
await expect(
|
||||
createGeminiEmbeddingProvider({
|
||||
config: {} as never,
|
||||
provider: "gemini",
|
||||
model: "gemini-embedding-2-preview",
|
||||
fallback: "none",
|
||||
outputDimensionality: 512,
|
||||
}),
|
||||
).rejects.toThrow(/Invalid outputDimensionality 512/);
|
||||
});
|
||||
|
||||
it("sanitizes non-finite values before normalization", async () => {
|
||||
const fetchMock = createGeminiFetchMock([
|
||||
1,
|
||||
Number.NaN,
|
||||
Number.POSITIVE_INFINITY,
|
||||
Number.NEGATIVE_INFINITY,
|
||||
]);
|
||||
const provider = await createProviderWithFetch(fetchMock, {
|
||||
model: "gemini-embedding-2-preview",
|
||||
});
|
||||
|
||||
const embedding = await provider.embedQuery("test");
|
||||
|
||||
expect(embedding).toEqual([1, 0, 0, 0]);
|
||||
});
|
||||
|
||||
it("uses correct endpoint URL", async () => {
|
||||
const fetchMock = createGeminiFetchMock();
|
||||
const provider = await createProviderWithFetch(fetchMock, {
|
||||
model: "gemini-embedding-2-preview",
|
||||
});
|
||||
|
||||
await provider.embedQuery("test");
|
||||
|
||||
const { url } = readFirstFetchRequest(fetchMock);
|
||||
expect(url).toBe(
|
||||
"https://generativelanguage.googleapis.com/v1beta/models/gemini-embedding-2-preview:embedContent",
|
||||
);
|
||||
});
|
||||
|
||||
it("allows taskType override via options", async () => {
|
||||
const fetchMock = createGeminiFetchMock();
|
||||
const provider = await createProviderWithFetch(fetchMock, {
|
||||
model: "gemini-embedding-2-preview",
|
||||
taskType: "SEMANTIC_SIMILARITY",
|
||||
});
|
||||
expectNormalizedThreeFourVector(await v2QueryProvider.embedQuery("test query"));
|
||||
|
||||
await provider.embedQuery("test");
|
||||
const v2BatchFetch = createGeminiBatchFetchMock(2, [3, 4]);
|
||||
const v2BatchProvider = await createProviderWithFetch(v2BatchFetch, {
|
||||
model: "gemini-embedding-2-preview",
|
||||
outputDimensionality: 768,
|
||||
taskType: "SEMANTIC_SIMILARITY",
|
||||
});
|
||||
const batch = await v2BatchProvider.embedBatch(["text1", "text2"]);
|
||||
expect(batch).toHaveLength(2);
|
||||
for (const embedding of batch) {
|
||||
expectNormalizedThreeFourVector(embedding);
|
||||
}
|
||||
|
||||
const body = parseFetchBody(fetchMock);
|
||||
expect(body.taskType).toBe("SEMANTIC_SIMILARITY");
|
||||
expect(parseFetchBody(v2QueryFetch)).toMatchObject({
|
||||
outputDimensionality: 768,
|
||||
taskType: "SEMANTIC_SIMILARITY",
|
||||
});
|
||||
expect(parseFetchBody(v2BatchFetch).requests).toEqual([
|
||||
expect.objectContaining({ outputDimensionality: 768 }),
|
||||
expect.objectContaining({ outputDimensionality: 768 }),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------- Model normalization ----------
|
||||
|
||||
describe("gemini model normalization", () => {
|
||||
it("handles models/ prefix for v2 model", async () => {
|
||||
const fetchMock = createGeminiFetchMock();
|
||||
installFetchMock(fetchMock as unknown as typeof globalThis.fetch);
|
||||
mockPublicPinnedHostname();
|
||||
mockResolvedProviderKey();
|
||||
|
||||
const { provider } = await createGeminiEmbeddingProvider({
|
||||
config: {} as never,
|
||||
provider: "gemini",
|
||||
model: "models/gemini-embedding-2-preview",
|
||||
fallback: "none",
|
||||
});
|
||||
|
||||
await provider.embedQuery("test");
|
||||
|
||||
const body = parseFetchBody(fetchMock);
|
||||
expect(body.outputDimensionality).toBe(3072);
|
||||
it("normalizes known model prefixes and default model", () => {
|
||||
expect(normalizeGeminiModel("models/gemini-embedding-2-preview")).toBe(
|
||||
"gemini-embedding-2-preview",
|
||||
);
|
||||
expect(normalizeGeminiModel("gemini/gemini-embedding-2-preview")).toBe(
|
||||
"gemini-embedding-2-preview",
|
||||
);
|
||||
expect(normalizeGeminiModel("google/gemini-embedding-2-preview")).toBe(
|
||||
"gemini-embedding-2-preview",
|
||||
);
|
||||
expect(normalizeGeminiModel("")).toBe(DEFAULT_GEMINI_EMBEDDING_MODEL);
|
||||
});
|
||||
|
||||
it("handles gemini/ prefix for v2 model", async () => {
|
||||
const fetchMock = createGeminiFetchMock();
|
||||
installFetchMock(fetchMock as unknown as typeof globalThis.fetch);
|
||||
mockPublicPinnedHostname();
|
||||
mockResolvedProviderKey();
|
||||
|
||||
const { provider } = await createGeminiEmbeddingProvider({
|
||||
config: {} as never,
|
||||
provider: "gemini",
|
||||
model: "gemini/gemini-embedding-2-preview",
|
||||
fallback: "none",
|
||||
});
|
||||
|
||||
await provider.embedQuery("test");
|
||||
|
||||
const body = parseFetchBody(fetchMock);
|
||||
expect(body.outputDimensionality).toBe(3072);
|
||||
});
|
||||
|
||||
it("handles google/ prefix for v2 model", async () => {
|
||||
const fetchMock = createGeminiFetchMock();
|
||||
installFetchMock(fetchMock as unknown as typeof globalThis.fetch);
|
||||
mockPublicPinnedHostname();
|
||||
mockResolvedProviderKey();
|
||||
|
||||
const { provider } = await createGeminiEmbeddingProvider({
|
||||
config: {} as never,
|
||||
provider: "gemini",
|
||||
model: "google/gemini-embedding-2-preview",
|
||||
fallback: "none",
|
||||
});
|
||||
|
||||
await provider.embedQuery("test");
|
||||
|
||||
const body = parseFetchBody(fetchMock);
|
||||
expect(body.outputDimensionality).toBe(3072);
|
||||
});
|
||||
|
||||
it("defaults to gemini-embedding-001 when model is empty", async () => {
|
||||
const fetchMock = createGeminiFetchMock();
|
||||
installFetchMock(fetchMock as unknown as typeof globalThis.fetch);
|
||||
mockResolvedProviderKey();
|
||||
|
||||
const { provider, client } = await createGeminiEmbeddingProvider({
|
||||
config: {} as never,
|
||||
provider: "gemini",
|
||||
model: "",
|
||||
fallback: "none",
|
||||
});
|
||||
|
||||
expect(client.model).toBe(DEFAULT_GEMINI_EMBEDDING_MODEL);
|
||||
expect(provider.model).toBe(DEFAULT_GEMINI_EMBEDDING_MODEL);
|
||||
});
|
||||
|
||||
it("returns empty array for blank query text", async () => {
|
||||
it("returns empty arrays without fetching for blank query and empty batch", async () => {
|
||||
mockResolvedProviderKey();
|
||||
|
||||
const { provider } = await createGeminiEmbeddingProvider({
|
||||
@@ -573,21 +297,7 @@ describe("gemini model normalization", () => {
|
||||
fallback: "none",
|
||||
});
|
||||
|
||||
const result = await provider.embedQuery(" ");
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("returns empty array for empty batch", async () => {
|
||||
mockResolvedProviderKey();
|
||||
|
||||
const { provider } = await createGeminiEmbeddingProvider({
|
||||
config: {} as never,
|
||||
provider: "gemini",
|
||||
model: "gemini-embedding-2-preview",
|
||||
fallback: "none",
|
||||
});
|
||||
|
||||
const result = await provider.embedBatch([]);
|
||||
expect(result).toEqual([]);
|
||||
await expect(provider.embedQuery(" ")).resolves.toEqual([]);
|
||||
await expect(provider.embedBatch([])).resolves.toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -651,7 +651,7 @@ describe("local embedding ensureContext concurrency", () => {
|
||||
it("loads the model only once when embedBatch is called concurrently", async () => {
|
||||
const { provider, getLlamaSpy, loadModelSpy, createContextSpy } =
|
||||
await setupLocalProviderWithMockedInit({
|
||||
initializationDelayMs: 50,
|
||||
initializationDelayMs: 5,
|
||||
});
|
||||
|
||||
const results = await Promise.all([
|
||||
@@ -692,7 +692,7 @@ describe("local embedding ensureContext concurrency", () => {
|
||||
it("shares initialization when embedQuery and embedBatch start concurrently", async () => {
|
||||
const { provider, getLlamaSpy, loadModelSpy, createContextSpy } =
|
||||
await setupLocalProviderWithMockedInit({
|
||||
initializationDelayMs: 50,
|
||||
initializationDelayMs: 5,
|
||||
});
|
||||
|
||||
const [queryA, batch, queryB] = await Promise.all([
|
||||
|
||||
@@ -26,6 +26,7 @@ let createGeminiEmbeddingProvider: typeof import("./embeddings-gemini.js").creat
|
||||
let DEFAULT_GEMINI_EMBEDDING_MODEL: typeof import("./embeddings-gemini.js").DEFAULT_GEMINI_EMBEDDING_MODEL;
|
||||
let GEMINI_EMBEDDING_2_MODELS: typeof import("./embeddings-gemini.js").GEMINI_EMBEDDING_2_MODELS;
|
||||
let isGeminiEmbedding2Model: typeof import("./embeddings-gemini.js").isGeminiEmbedding2Model;
|
||||
let normalizeGeminiModel: typeof import("./embeddings-gemini.js").normalizeGeminiModel;
|
||||
let resolveGeminiOutputDimensionality: typeof import("./embeddings-gemini.js").resolveGeminiOutputDimensionality;
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -37,6 +38,7 @@ beforeAll(async () => {
|
||||
DEFAULT_GEMINI_EMBEDDING_MODEL,
|
||||
GEMINI_EMBEDDING_2_MODELS,
|
||||
isGeminiEmbedding2Model,
|
||||
normalizeGeminiModel,
|
||||
resolveGeminiOutputDimensionality,
|
||||
} = await import("./embeddings-gemini.js"));
|
||||
});
|
||||
@@ -171,74 +173,47 @@ describe("resolveGeminiOutputDimensionality", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ---------- Provider: gemini-embedding-001 (backward compat) ----------
|
||||
// ---------- Provider behavior ----------
|
||||
|
||||
describe("gemini-embedding-001 provider (backward compat)", () => {
|
||||
it("does NOT include outputDimensionality in embedQuery", async () => {
|
||||
const fetchMock = createGeminiFetchMock();
|
||||
const provider = await createProviderWithFetch(fetchMock, {
|
||||
describe("gemini embedding provider", () => {
|
||||
it("handles legacy and v2 request/response behavior", async () => {
|
||||
const legacyFetch = createGeminiBatchFetchMock(2);
|
||||
const legacyProvider = await createProviderWithFetch(legacyFetch, {
|
||||
model: "gemini-embedding-001",
|
||||
});
|
||||
|
||||
await provider.embedQuery("test query");
|
||||
await legacyProvider.embedQuery("test query");
|
||||
await legacyProvider.embedBatch(["text1", "text2"]);
|
||||
|
||||
const body = parseFetchBody(fetchMock);
|
||||
expect(body).not.toHaveProperty("outputDimensionality");
|
||||
expect(body.taskType).toBe("RETRIEVAL_QUERY");
|
||||
expect(body.content).toEqual({ parts: [{ text: "test query" }] });
|
||||
});
|
||||
|
||||
it("does NOT include outputDimensionality in embedBatch", async () => {
|
||||
const fetchMock = createGeminiBatchFetchMock(2);
|
||||
const provider = await createProviderWithFetch(fetchMock, {
|
||||
model: "gemini-embedding-001",
|
||||
expect(parseFetchBody(legacyFetch, 0)).toMatchObject({
|
||||
taskType: "RETRIEVAL_QUERY",
|
||||
content: { parts: [{ text: "test query" }] },
|
||||
});
|
||||
expect(parseFetchBody(legacyFetch, 0)).not.toHaveProperty("outputDimensionality");
|
||||
expect(parseFetchBody(legacyFetch, 1)).not.toHaveProperty("outputDimensionality");
|
||||
|
||||
await provider.embedBatch(["text1", "text2"]);
|
||||
|
||||
const body = parseFetchBody(fetchMock);
|
||||
expect(body).not.toHaveProperty("outputDimensionality");
|
||||
});
|
||||
});
|
||||
|
||||
// ---------- Provider: gemini-embedding-2-preview ----------
|
||||
|
||||
describe("gemini-embedding-2-preview provider", () => {
|
||||
it("includes outputDimensionality in embedQuery request", async () => {
|
||||
const fetchMock = createGeminiFetchMock();
|
||||
const provider = await createProviderWithFetch(fetchMock, {
|
||||
const v2QueryFetch = createGeminiFetchMock([3, 4]);
|
||||
const v2QueryProvider = await createProviderWithFetch(v2QueryFetch, {
|
||||
model: "gemini-embedding-2-preview",
|
||||
});
|
||||
expectNormalizedThreeFourVector(await v2QueryProvider.embedQuery("test query"));
|
||||
|
||||
await provider.embedQuery("test query");
|
||||
|
||||
const body = parseFetchBody(fetchMock);
|
||||
expect(body.outputDimensionality).toBe(3072);
|
||||
expect(body.taskType).toBe("RETRIEVAL_QUERY");
|
||||
expect(body.content).toEqual({ parts: [{ text: "test query" }] });
|
||||
});
|
||||
|
||||
it("normalizes embedQuery response vectors", async () => {
|
||||
const fetchMock = createGeminiFetchMock([3, 4]);
|
||||
const provider = await createProviderWithFetch(fetchMock, {
|
||||
const v2BatchFetch = createGeminiBatchFetchMock(2, [3, 4]);
|
||||
const v2BatchProvider = await createProviderWithFetch(v2BatchFetch, {
|
||||
model: "gemini-embedding-2-preview",
|
||||
});
|
||||
const batch = await v2BatchProvider.embedBatch(["text1", "text2"]);
|
||||
expect(batch).toHaveLength(2);
|
||||
for (const embedding of batch) {
|
||||
expectNormalizedThreeFourVector(embedding);
|
||||
}
|
||||
|
||||
const embedding = await provider.embedQuery("test query");
|
||||
|
||||
expectNormalizedThreeFourVector(embedding);
|
||||
});
|
||||
|
||||
it("includes outputDimensionality in embedBatch request", async () => {
|
||||
const fetchMock = createGeminiBatchFetchMock(2);
|
||||
const provider = await createProviderWithFetch(fetchMock, {
|
||||
model: "gemini-embedding-2-preview",
|
||||
expect(parseFetchBody(v2QueryFetch)).toMatchObject({
|
||||
outputDimensionality: 3072,
|
||||
taskType: "RETRIEVAL_QUERY",
|
||||
content: { parts: [{ text: "test query" }] },
|
||||
});
|
||||
|
||||
await provider.embedBatch(["text1", "text2"]);
|
||||
|
||||
const body = parseFetchBody(fetchMock);
|
||||
expect(body.requests).toEqual([
|
||||
expect(parseFetchBody(v2BatchFetch).requests).toEqual([
|
||||
{
|
||||
model: "models/gemini-embedding-2-preview",
|
||||
content: { parts: [{ text: "text1" }] },
|
||||
@@ -254,84 +229,15 @@ describe("gemini-embedding-2-preview provider", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("normalizes embedBatch response vectors", async () => {
|
||||
const fetchMock = createGeminiBatchFetchMock(2, [3, 4]);
|
||||
const provider = await createProviderWithFetch(fetchMock, {
|
||||
model: "gemini-embedding-2-preview",
|
||||
});
|
||||
|
||||
const embeddings = await provider.embedBatch(["text1", "text2"]);
|
||||
|
||||
expect(embeddings).toHaveLength(2);
|
||||
for (const embedding of embeddings) {
|
||||
expectNormalizedThreeFourVector(embedding);
|
||||
}
|
||||
});
|
||||
|
||||
it("respects custom outputDimensionality", async () => {
|
||||
const fetchMock = createGeminiFetchMock();
|
||||
it("supports custom dimensions, task type, multimodal inputs, and endpoint URL", async () => {
|
||||
const fetchMock = createGeminiBatchFetchMock(2);
|
||||
const provider = await createProviderWithFetch(fetchMock, {
|
||||
model: "gemini-embedding-2-preview",
|
||||
outputDimensionality: 768,
|
||||
taskType: "SEMANTIC_SIMILARITY",
|
||||
});
|
||||
|
||||
await provider.embedQuery("test");
|
||||
|
||||
const body = parseFetchBody(fetchMock);
|
||||
expect(body.outputDimensionality).toBe(768);
|
||||
});
|
||||
|
||||
it("sanitizes and normalizes embedQuery responses", async () => {
|
||||
const fetchMock = createGeminiFetchMock([3, 4, Number.NaN]);
|
||||
const provider = await createProviderWithFetch(fetchMock, {
|
||||
model: "gemini-embedding-2-preview",
|
||||
});
|
||||
|
||||
await expect(provider.embedQuery("test")).resolves.toEqual([0.6, 0.8, 0]);
|
||||
});
|
||||
|
||||
it("uses custom outputDimensionality for each embedBatch request", async () => {
|
||||
const fetchMock = createGeminiBatchFetchMock(2);
|
||||
const provider = await createProviderWithFetch(fetchMock, {
|
||||
model: "gemini-embedding-2-preview",
|
||||
outputDimensionality: 768,
|
||||
});
|
||||
|
||||
await provider.embedBatch(["text1", "text2"]);
|
||||
|
||||
const body = parseFetchBody(fetchMock);
|
||||
expect(body.requests).toEqual([
|
||||
expect.objectContaining({ outputDimensionality: 768 }),
|
||||
expect.objectContaining({ outputDimensionality: 768 }),
|
||||
]);
|
||||
});
|
||||
|
||||
it("sanitizes and normalizes structured batch responses", async () => {
|
||||
const fetchMock = createGeminiBatchFetchMock(1, [0, Number.POSITIVE_INFINITY, 5]);
|
||||
const provider = await createProviderWithFetch(fetchMock, {
|
||||
model: "gemini-embedding-2-preview",
|
||||
});
|
||||
|
||||
await expect(
|
||||
provider.embedBatchInputs?.([
|
||||
{
|
||||
text: "Image file: diagram.png",
|
||||
parts: [
|
||||
{ type: "text", text: "Image file: diagram.png" },
|
||||
{ type: "inline-data", mimeType: "image/png", data: "img" },
|
||||
],
|
||||
},
|
||||
]),
|
||||
).resolves.toEqual([[0, 0, 1]]);
|
||||
});
|
||||
|
||||
it("supports multimodal embedBatchInputs requests", async () => {
|
||||
const fetchMock = createGeminiBatchFetchMock(2);
|
||||
const provider = await createProviderWithFetch(fetchMock, {
|
||||
model: "gemini-embedding-2-preview",
|
||||
});
|
||||
|
||||
expect(provider.embedBatchInputs).toBeDefined();
|
||||
await provider.embedBatchInputs?.([
|
||||
{
|
||||
text: "Image file: diagram.png",
|
||||
@@ -349,8 +255,15 @@ describe("gemini-embedding-2-preview provider", () => {
|
||||
},
|
||||
]);
|
||||
|
||||
const body = parseFetchBody(fetchMock);
|
||||
expect(body.requests).toEqual([
|
||||
const { url } = readFirstFetchRequest(fetchMock);
|
||||
expect(url).toBe(
|
||||
"https://generativelanguage.googleapis.com/v1beta/models/gemini-embedding-2-preview:embedContent",
|
||||
);
|
||||
expect(parseFetchBody(fetchMock, 0)).toMatchObject({
|
||||
outputDimensionality: 768,
|
||||
taskType: "SEMANTIC_SIMILARITY",
|
||||
});
|
||||
expect(parseFetchBody(fetchMock, 1).requests).toEqual([
|
||||
{
|
||||
model: "models/gemini-embedding-2-preview",
|
||||
content: {
|
||||
@@ -359,8 +272,8 @@ describe("gemini-embedding-2-preview provider", () => {
|
||||
{ inlineData: { mimeType: "image/png", data: "img" } },
|
||||
],
|
||||
},
|
||||
taskType: "RETRIEVAL_DOCUMENT",
|
||||
outputDimensionality: 3072,
|
||||
taskType: "SEMANTIC_SIMILARITY",
|
||||
outputDimensionality: 768,
|
||||
},
|
||||
{
|
||||
model: "models/gemini-embedding-2-preview",
|
||||
@@ -370,147 +283,54 @@ describe("gemini-embedding-2-preview provider", () => {
|
||||
{ inlineData: { mimeType: "audio/wav", data: "aud" } },
|
||||
],
|
||||
},
|
||||
taskType: "RETRIEVAL_DOCUMENT",
|
||||
outputDimensionality: 3072,
|
||||
taskType: "SEMANTIC_SIMILARITY",
|
||||
outputDimensionality: 768,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("throws for invalid outputDimensionality", async () => {
|
||||
mockResolvedProviderKey(authModule.resolveApiKeyForProvider);
|
||||
it("sanitizes non-finite query and structured batch responses", async () => {
|
||||
const queryFetch = createGeminiFetchMock([3, 4, Number.NaN]);
|
||||
const queryProvider = await createProviderWithFetch(queryFetch, {
|
||||
model: "gemini-embedding-2-preview",
|
||||
});
|
||||
await expect(queryProvider.embedQuery("test")).resolves.toEqual([0.6, 0.8, 0]);
|
||||
|
||||
const batchFetch = createGeminiBatchFetchMock(1, [0, Number.POSITIVE_INFINITY, 5]);
|
||||
const batchProvider = await createProviderWithFetch(batchFetch, {
|
||||
model: "gemini-embedding-2-preview",
|
||||
});
|
||||
await expect(
|
||||
createGeminiEmbeddingProvider({
|
||||
config: {} as never,
|
||||
provider: "gemini",
|
||||
model: "gemini-embedding-2-preview",
|
||||
fallback: "none",
|
||||
outputDimensionality: 512,
|
||||
}),
|
||||
).rejects.toThrow(/Invalid outputDimensionality 512/);
|
||||
});
|
||||
|
||||
it("sanitizes non-finite values before normalization", async () => {
|
||||
const fetchMock = createGeminiFetchMock([
|
||||
1,
|
||||
Number.NaN,
|
||||
Number.POSITIVE_INFINITY,
|
||||
Number.NEGATIVE_INFINITY,
|
||||
]);
|
||||
const provider = await createProviderWithFetch(fetchMock, {
|
||||
model: "gemini-embedding-2-preview",
|
||||
});
|
||||
|
||||
const embedding = await provider.embedQuery("test");
|
||||
|
||||
expect(embedding).toEqual([1, 0, 0, 0]);
|
||||
});
|
||||
|
||||
it("uses correct endpoint URL", async () => {
|
||||
const fetchMock = createGeminiFetchMock();
|
||||
const provider = await createProviderWithFetch(fetchMock, {
|
||||
model: "gemini-embedding-2-preview",
|
||||
});
|
||||
|
||||
await provider.embedQuery("test");
|
||||
|
||||
const { url } = readFirstFetchRequest(fetchMock);
|
||||
expect(url).toBe(
|
||||
"https://generativelanguage.googleapis.com/v1beta/models/gemini-embedding-2-preview:embedContent",
|
||||
);
|
||||
});
|
||||
|
||||
it("allows taskType override via options", async () => {
|
||||
const fetchMock = createGeminiFetchMock();
|
||||
const provider = await createProviderWithFetch(fetchMock, {
|
||||
model: "gemini-embedding-2-preview",
|
||||
taskType: "SEMANTIC_SIMILARITY",
|
||||
});
|
||||
|
||||
await provider.embedQuery("test");
|
||||
|
||||
const body = parseFetchBody(fetchMock);
|
||||
expect(body.taskType).toBe("SEMANTIC_SIMILARITY");
|
||||
batchProvider.embedBatchInputs?.([
|
||||
{
|
||||
text: "Image file: diagram.png",
|
||||
parts: [
|
||||
{ type: "text", text: "Image file: diagram.png" },
|
||||
{ type: "inline-data", mimeType: "image/png", data: "img" },
|
||||
],
|
||||
},
|
||||
]),
|
||||
).resolves.toEqual([[0, 0, 1]]);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------- Model normalization ----------
|
||||
|
||||
describe("gemini model normalization", () => {
|
||||
it("handles models/ prefix for v2 model", async () => {
|
||||
const fetchMock = createGeminiFetchMock();
|
||||
installFetchMock(fetchMock as unknown as typeof globalThis.fetch);
|
||||
mockPublicPinnedHostname();
|
||||
mockResolvedProviderKey(authModule.resolveApiKeyForProvider);
|
||||
|
||||
const { provider } = await createGeminiEmbeddingProvider({
|
||||
config: {} as never,
|
||||
provider: "gemini",
|
||||
model: "models/gemini-embedding-2-preview",
|
||||
fallback: "none",
|
||||
});
|
||||
|
||||
await provider.embedQuery("test");
|
||||
|
||||
const body = parseFetchBody(fetchMock);
|
||||
expect(body.outputDimensionality).toBe(3072);
|
||||
it("normalizes known model prefixes and default model", () => {
|
||||
expect(normalizeGeminiModel("models/gemini-embedding-2-preview")).toBe(
|
||||
"gemini-embedding-2-preview",
|
||||
);
|
||||
expect(normalizeGeminiModel("gemini/gemini-embedding-2-preview")).toBe(
|
||||
"gemini-embedding-2-preview",
|
||||
);
|
||||
expect(normalizeGeminiModel("google/gemini-embedding-2-preview")).toBe(
|
||||
"gemini-embedding-2-preview",
|
||||
);
|
||||
expect(normalizeGeminiModel("")).toBe(DEFAULT_GEMINI_EMBEDDING_MODEL);
|
||||
});
|
||||
|
||||
it("handles gemini/ prefix for v2 model", async () => {
|
||||
const fetchMock = createGeminiFetchMock();
|
||||
installFetchMock(fetchMock as unknown as typeof globalThis.fetch);
|
||||
mockPublicPinnedHostname();
|
||||
mockResolvedProviderKey(authModule.resolveApiKeyForProvider);
|
||||
|
||||
const { provider } = await createGeminiEmbeddingProvider({
|
||||
config: {} as never,
|
||||
provider: "gemini",
|
||||
model: "gemini/gemini-embedding-2-preview",
|
||||
fallback: "none",
|
||||
});
|
||||
|
||||
await provider.embedQuery("test");
|
||||
|
||||
const body = parseFetchBody(fetchMock);
|
||||
expect(body.outputDimensionality).toBe(3072);
|
||||
});
|
||||
|
||||
it("handles google/ prefix for v2 model", async () => {
|
||||
const fetchMock = createGeminiFetchMock();
|
||||
installFetchMock(fetchMock as unknown as typeof globalThis.fetch);
|
||||
mockPublicPinnedHostname();
|
||||
mockResolvedProviderKey(authModule.resolveApiKeyForProvider);
|
||||
|
||||
const { provider } = await createGeminiEmbeddingProvider({
|
||||
config: {} as never,
|
||||
provider: "gemini",
|
||||
model: "google/gemini-embedding-2-preview",
|
||||
fallback: "none",
|
||||
});
|
||||
|
||||
await provider.embedQuery("test");
|
||||
|
||||
const body = parseFetchBody(fetchMock);
|
||||
expect(body.outputDimensionality).toBe(3072);
|
||||
});
|
||||
|
||||
it("defaults to gemini-embedding-001 when model is empty", async () => {
|
||||
const fetchMock = createGeminiFetchMock();
|
||||
installFetchMock(fetchMock as unknown as typeof globalThis.fetch);
|
||||
mockResolvedProviderKey(authModule.resolveApiKeyForProvider);
|
||||
|
||||
const { provider, client } = await createGeminiEmbeddingProvider({
|
||||
config: {} as never,
|
||||
provider: "gemini",
|
||||
model: "",
|
||||
fallback: "none",
|
||||
});
|
||||
|
||||
expect(client.model).toBe(DEFAULT_GEMINI_EMBEDDING_MODEL);
|
||||
expect(provider.model).toBe(DEFAULT_GEMINI_EMBEDDING_MODEL);
|
||||
});
|
||||
|
||||
it("returns empty array for blank query text", async () => {
|
||||
it("returns empty arrays without fetching for blank query and empty batch", async () => {
|
||||
mockResolvedProviderKey(authModule.resolveApiKeyForProvider);
|
||||
|
||||
const { provider } = await createGeminiEmbeddingProvider({
|
||||
@@ -520,21 +340,7 @@ describe("gemini model normalization", () => {
|
||||
fallback: "none",
|
||||
});
|
||||
|
||||
const result = await provider.embedQuery(" ");
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("returns empty array for empty batch", async () => {
|
||||
mockResolvedProviderKey(authModule.resolveApiKeyForProvider);
|
||||
|
||||
const { provider } = await createGeminiEmbeddingProvider({
|
||||
config: {} as never,
|
||||
provider: "gemini",
|
||||
model: "gemini-embedding-2-preview",
|
||||
fallback: "none",
|
||||
});
|
||||
|
||||
const result = await provider.embedBatch([]);
|
||||
expect(result).toEqual([]);
|
||||
await expect(provider.embedQuery(" ")).resolves.toEqual([]);
|
||||
await expect(provider.embedBatch([])).resolves.toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -660,7 +660,7 @@ describe("local embedding ensureContext concurrency", () => {
|
||||
it("loads the model only once when embedBatch is called concurrently", async () => {
|
||||
const { provider, getLlamaSpy, loadModelSpy, createContextSpy } =
|
||||
await setupLocalProviderWithMockedInit({
|
||||
initializationDelayMs: 50,
|
||||
initializationDelayMs: 5,
|
||||
});
|
||||
|
||||
const results = await Promise.all([
|
||||
@@ -701,7 +701,7 @@ describe("local embedding ensureContext concurrency", () => {
|
||||
it("shares initialization when embedQuery and embedBatch start concurrently", async () => {
|
||||
const { provider, getLlamaSpy, loadModelSpy, createContextSpy } =
|
||||
await setupLocalProviderWithMockedInit({
|
||||
initializationDelayMs: 50,
|
||||
initializationDelayMs: 5,
|
||||
});
|
||||
|
||||
const [queryA, batch, queryB] = await Promise.all([
|
||||
|
||||
@@ -247,27 +247,35 @@ describe("parseSlashCommand", () => {
|
||||
it("caps remote command payload size and long metadata before it reaches UI state", async () => {
|
||||
const longName = "x".repeat(260);
|
||||
const longDescription = "d".repeat(2_500);
|
||||
const request = async () => ({
|
||||
commands: Array.from({ length: 520 }, (_, index) => ({
|
||||
name: `plugin-${index}`,
|
||||
textAliases: Array.from(
|
||||
{ length: 25 },
|
||||
(_, aliasIndex) => `/plugin-${index}-${aliasIndex}`,
|
||||
),
|
||||
const oversizedCommand = {
|
||||
name: "plugin-0",
|
||||
textAliases: Array.from({ length: 25 }, (_, aliasIndex) => `/plugin-0-${aliasIndex}`),
|
||||
description: longDescription,
|
||||
source: "plugin" as const,
|
||||
scope: "both" as const,
|
||||
acceptsArgs: true,
|
||||
args: Array.from({ length: 25 }, (_, argIndex) => ({
|
||||
name: `${longName}-${argIndex}`,
|
||||
description: longDescription,
|
||||
source: "plugin" as const,
|
||||
scope: "both" as const,
|
||||
acceptsArgs: true,
|
||||
args: Array.from({ length: 25 }, (_, argIndex) => ({
|
||||
name: `${longName}-${argIndex}`,
|
||||
description: longDescription,
|
||||
type: "string" as const,
|
||||
choices: Array.from({ length: 55 }, (_, choiceIndex) => ({
|
||||
value: `${longName}-${choiceIndex}`,
|
||||
label: `${longName}-${choiceIndex}`,
|
||||
})),
|
||||
type: "string" as const,
|
||||
choices: Array.from({ length: 55 }, (_, choiceIndex) => ({
|
||||
value: `${longName}-${choiceIndex}`,
|
||||
label: `${longName}-${choiceIndex}`,
|
||||
})),
|
||||
})),
|
||||
};
|
||||
const request = async () => ({
|
||||
commands: [
|
||||
oversizedCommand,
|
||||
...Array.from({ length: 519 }, (_, index) => ({
|
||||
name: `plugin-${index + 1}`,
|
||||
textAliases: [`/plugin-${index + 1}`],
|
||||
description: "Plugin command.",
|
||||
source: "plugin" as const,
|
||||
scope: "both" as const,
|
||||
acceptsArgs: false,
|
||||
})),
|
||||
],
|
||||
});
|
||||
|
||||
await refreshSlashCommands({
|
||||
|
||||
Reference in New Issue
Block a user