mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 20:35:39 -06:00
fix(google): exclude versioned Gemini client header from memory identity (#104759)
* fix(google): exclude versioned Gemini client header from memory identity * test(google): focus memory identity coverage --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
// Google tests cover memory embedding adapter plugin behavior.
|
||||
import {
|
||||
sanitizeEmbeddingCacheHeaders,
|
||||
type MemoryEmbeddingProvider,
|
||||
} from "openclaw/plugin-sdk/memory-core-host-engine-embeddings";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
createGeminiEmbeddingProvider: vi.fn(),
|
||||
runGeminiEmbeddingBatches: vi.fn(async () => new Map([["0", [1, 0]]])),
|
||||
}));
|
||||
|
||||
vi.mock("./embedding-provider.js", () => ({
|
||||
DEFAULT_GEMINI_EMBEDDING_MODEL: "gemini-embedding-001",
|
||||
createGeminiEmbeddingProvider: mocks.createGeminiEmbeddingProvider,
|
||||
buildGeminiEmbeddingRequest: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./embedding-batch.js", () => ({
|
||||
runGeminiEmbeddingBatches: mocks.runGeminiEmbeddingBatches,
|
||||
}));
|
||||
|
||||
import { geminiMemoryEmbeddingProviderAdapter } from "./memory-embedding-adapter.js";
|
||||
|
||||
const provider: MemoryEmbeddingProvider = {
|
||||
id: "gemini",
|
||||
model: "gemini-embedding-2-preview",
|
||||
embedQuery: async () => [1, 0],
|
||||
embedBatch: async (texts) => texts.map(() => [1, 0]),
|
||||
};
|
||||
|
||||
const clientBase = {
|
||||
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
|
||||
model: "gemini-embedding-2-preview",
|
||||
modelPath: "models/gemini-embedding-2-preview",
|
||||
outputDimensionality: 768,
|
||||
};
|
||||
|
||||
async function createAdapterWithHeaders(headers: Record<string, string>) {
|
||||
mocks.createGeminiEmbeddingProvider.mockResolvedValueOnce({
|
||||
provider,
|
||||
client: { ...clientBase, headers },
|
||||
});
|
||||
return await geminiMemoryEmbeddingProviderAdapter.create({
|
||||
config: {} as never,
|
||||
provider: "gemini",
|
||||
model: "gemini-embedding-2-preview",
|
||||
fallback: "none",
|
||||
});
|
||||
}
|
||||
|
||||
describe("Gemini memory embedding adapter", () => {
|
||||
beforeEach(() => {
|
||||
mocks.createGeminiEmbeddingProvider.mockReset();
|
||||
mocks.runGeminiEmbeddingBatches.mockClear();
|
||||
});
|
||||
|
||||
it("keeps durable identity stable across generated client-version changes", async () => {
|
||||
const sharedHeaders = {
|
||||
"Content-Type": "application/json",
|
||||
"x-goog-api-key": "secret-key",
|
||||
Authorization: "Bearer token",
|
||||
"X-Custom-Region": "us-central1",
|
||||
};
|
||||
const older = await createAdapterWithHeaders({
|
||||
...sharedHeaders,
|
||||
"x-goog-api-client": "openclaw/2026.6.11",
|
||||
});
|
||||
const newer = await createAdapterWithHeaders({
|
||||
...sharedHeaders,
|
||||
"x-goog-api-client": "openclaw/2026.7.1-beta.5",
|
||||
});
|
||||
|
||||
expect(older.runtime?.cacheKeyData).toEqual(newer.runtime?.cacheKeyData);
|
||||
expect(older.runtime?.cacheKeyData?.headers).toEqual(
|
||||
sanitizeEmbeddingCacheHeaders(
|
||||
{
|
||||
"Content-Type": "application/json",
|
||||
"X-Custom-Region": "us-central1",
|
||||
},
|
||||
[],
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it("still invalidates identity when a semantic custom header changes", async () => {
|
||||
const first = await createAdapterWithHeaders({
|
||||
"x-goog-api-client": "openclaw/2026.7.1-beta.5",
|
||||
"x-custom-endpoint": "https://example.invalid/a",
|
||||
});
|
||||
const second = await createAdapterWithHeaders({
|
||||
"x-goog-api-client": "openclaw/2026.7.1-beta.5",
|
||||
"x-custom-endpoint": "https://example.invalid/b",
|
||||
});
|
||||
|
||||
expect(first.runtime?.cacheKeyData).not.toEqual(second.runtime?.cacheKeyData);
|
||||
});
|
||||
});
|
||||
@@ -45,9 +45,13 @@ export const geminiMemoryEmbeddingProviderAdapter: MemoryEmbeddingProviderAdapte
|
||||
baseUrl: client.baseUrl,
|
||||
model: client.model,
|
||||
outputDimensionality: client.outputDimensionality,
|
||||
// x-goog-api-client is generated partner attribution (openclaw/<version>).
|
||||
// Keep it on outbound requests, but exclude it from durable memory identity so
|
||||
// OpenClaw version bumps do not pause otherwise-compatible Gemini indexes.
|
||||
headers: sanitizeEmbeddingCacheHeaders(client.headers, [
|
||||
"authorization",
|
||||
"x-goog-api-key",
|
||||
"x-goog-api-client",
|
||||
]),
|
||||
},
|
||||
batchEmbed: async (batch) => {
|
||||
|
||||
Reference in New Issue
Block a user