From 858d95fa4dcfcf36403695c68f3789f439772473 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 25 Aug 2026 11:48:25 -0700 Subject: [PATCH] fix(openai): preserve Azure embedding index across key rotation (#129516) --- .../openai/memory-embedding-adapter.test.ts | 142 +++++++++++------- extensions/openai/memory-embedding-adapter.ts | 2 +- 2 files changed, 85 insertions(+), 59 deletions(-) diff --git a/extensions/openai/memory-embedding-adapter.test.ts b/extensions/openai/memory-embedding-adapter.test.ts index 72b90b7562c3..812f48a16d41 100644 --- a/extensions/openai/memory-embedding-adapter.test.ts +++ b/extensions/openai/memory-embedding-adapter.test.ts @@ -105,68 +105,94 @@ describe("OpenAI memory embedding adapter", () => { expect(JSON.stringify(current.cacheKeyData)).not.toContain("fixture-secret"); }); - it("keeps rotated proxy credentials out of tenant-specific embedding cache identity", async () => { - const createForTenant = async ( - tenant: string, - proxyApiKey: string, - proxyHeaderName = "X-Api-Key", - ) => { - const client = await resolveRemoteEmbeddingBearerClient({ - provider: "bailian-embedding", - defaultBaseUrl: "https://embeddings.example/v1", - options: { - config: { models: {} } as never, - model: "text-embedding-v3", - remote: { - apiKey: "fixture-secret", - headers: { - [proxyHeaderName]: proxyApiKey, - "X-Deployment": tenant, - "X-Tenant": tenant, - version: "tenant-api-v2", - "User-Agent": "tenant-client/2", + it.each([ + { + name: "proxy", + providerId: "bailian-embedding", + baseUrl: "https://embeddings.example/v1", + headerName: "X-Api-Key", + rotatedHeaderName: "x-aPI-kEY", + }, + { + name: "Azure OpenAI", + providerId: "openai", + baseUrl: "https://qa-resource.openai.azure.com/openai/v1", + headerName: "api-key", + rotatedHeaderName: "Api-Key", + }, + ])( + "keeps rotated $name credentials out of tenant-specific embedding cache identity", + async ({ providerId, baseUrl, headerName, rotatedHeaderName }) => { + const createForTenant = async ( + tenant: string, + proxyApiKey: string, + proxyHeaderName = headerName, + ) => { + const client = await resolveRemoteEmbeddingBearerClient({ + provider: providerId, + defaultBaseUrl: baseUrl, + options: { + config: { models: {} } as never, + model: "text-embedding-v3", + remote: { + baseUrl, + apiKey: "fixture-secret", + headers: { + [proxyHeaderName]: proxyApiKey, + "X-Deployment": tenant, + "X-Tenant": tenant, + version: "tenant-api-v2", + "User-Agent": "tenant-client/2", + }, }, }, - }, - }); - expect(client.headers).toMatchObject({ - Authorization: "Bearer fixture-secret", - [proxyHeaderName]: proxyApiKey, - "X-Deployment": tenant, - }); - mocks.createOpenAiEmbeddingProvider.mockResolvedValueOnce({ - provider, - client: { ...client, model: "text-embedding-v3" }, - }); - return await openAiMemoryEmbeddingProviderAdapter.create({ - config: {} as never, - provider: "bailian-embedding", - model: "text-embedding-v3", - fallback: "none", - }); - }; + }); + expect(client.headers).toMatchObject({ + Authorization: "Bearer fixture-secret", + [proxyHeaderName]: proxyApiKey, + "X-Deployment": tenant, + }); + mocks.createOpenAiEmbeddingProvider.mockResolvedValueOnce({ + provider, + client: { ...client, model: "text-embedding-v3" }, + }); + return await openAiMemoryEmbeddingProviderAdapter.create({ + config: {} as never, + provider: providerId, + model: "text-embedding-v3", + fallback: "none", + }); + }; - const first = await createForTenant("tenant-a", "proxy-key-before-rotation"); - const rotated = await createForTenant("tenant-a", "proxy-key-after-rotation", "x-aPI-kEY"); - const second = await createForTenant("tenant-b", "proxy-key-after-rotation"); - const headers = first.runtime?.cacheKeyData?.headers; + const first = await createForTenant("tenant-a", "proxy-key-before-rotation"); + const rotated = await createForTenant( + "tenant-a", + "proxy-key-after-rotation", + rotatedHeaderName, + ); + const second = await createForTenant("tenant-b", "proxy-key-after-rotation"); + const headers = first.runtime?.cacheKeyData?.headers; - expect(headers).toEqual( - expect.arrayContaining([ - ["X-Deployment", "tenant-a"], - ["X-Tenant", "tenant-a"], - ["version", "tenant-api-v2"], - ["User-Agent", "tenant-client/2"], - ]), - ); - expect(first.runtime?.cacheKeyData).toEqual(rotated.runtime?.cacheKeyData); - expect(hashText(JSON.stringify(first.runtime?.cacheKeyData))).toBe( - hashText(JSON.stringify(rotated.runtime?.cacheKeyData)), - ); - expect(first.runtime?.cacheKeyData).not.toEqual(second.runtime?.cacheKeyData); - expect(JSON.stringify(first.runtime?.cacheKeyData)).not.toContain("fixture-secret"); - expect(JSON.stringify(first.runtime?.cacheKeyData)).not.toContain("proxy-key-before-rotation"); - }); + expect(headers).toEqual( + expect.arrayContaining([ + ["X-Deployment", "tenant-a"], + ["X-Tenant", "tenant-a"], + ["version", "tenant-api-v2"], + ["User-Agent", "tenant-client/2"], + ]), + ); + expect(first.runtime?.cacheKeyData).toEqual(rotated.runtime?.cacheKeyData); + expect(hashText(JSON.stringify(first.runtime?.cacheKeyData))).toBe( + hashText(JSON.stringify(rotated.runtime?.cacheKeyData)), + ); + expect(first.runtime?.cacheKeyData).not.toEqual(second.runtime?.cacheKeyData); + expect(JSON.stringify(first.runtime?.cacheKeyData)).not.toContain("fixture-secret"); + expect(JSON.stringify(first.runtime?.cacheKeyData)).not.toContain( + "proxy-key-before-rotation", + ); + expect(first.runtime?.cacheKeyData).toMatchObject({ provider: providerId, baseUrl }); + }, + ); it("sends document input_type in OpenAI batch embedding requests", async () => { const result = await openAiMemoryEmbeddingProviderAdapter.create({ diff --git a/extensions/openai/memory-embedding-adapter.ts b/extensions/openai/memory-embedding-adapter.ts index def27ed7a6c7..f015a7d2bd3e 100644 --- a/extensions/openai/memory-embedding-adapter.ts +++ b/extensions/openai/memory-embedding-adapter.ts @@ -12,7 +12,7 @@ import { } from "./embedding-provider.js"; function resolveEmbeddingCacheExcludedHeaders(providerId: string, baseUrl: string): string[] { - const excludedHeaders = ["authorization", "x-api-key"]; + const excludedHeaders = ["authorization", "x-api-key", "api-key"]; if (providerId !== "openai") { return excludedHeaders; }