fix(openai): preserve Azure embedding index across key rotation (#129516)

This commit is contained in:
Peter Steinberger
2026-08-25 11:48:25 -07:00
committed by GitHub
parent 4f18aeabf3
commit 858d95fa4d
2 changed files with 85 additions and 59 deletions
@@ -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({
@@ -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;
}