fix(ai): support long cache retention compat

closes #3543
This commit is contained in:
Mario Zechner
2026-04-23 23:43:34 +02:00
parent 1312346199
commit 4cd4cfd98e
11 changed files with 331 additions and 29 deletions

View File

@@ -1,7 +1,7 @@
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { getModel } from "../src/models.js";
import { stream } from "../src/stream.js";
import type { Context } from "../src/types.js";
import type { Context, Model } from "../src/types.js";
describe("Cache Retention (PI_CACHE_RETENTION)", () => {
const originalEnv = process.env.PI_CACHE_RETENTION;
@@ -70,7 +70,7 @@ describe("Cache Retention (PI_CACHE_RETENTION)", () => {
expect(capturedPayload.system[0].cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
});
it("should not add ttl when baseUrl is not api.anthropic.com", async () => {
it("should add ttl for non-api.anthropic.com baseUrl by default", async () => {
process.env.PI_CACHE_RETENTION = "long";
// Create a model with a different baseUrl (simulating a proxy)
@@ -106,11 +106,39 @@ describe("Cache Retention (PI_CACHE_RETENTION)", () => {
// Expected to fail
}
// The payload should have been captured before the error
if (capturedPayload) {
// System prompt should have cache_control WITHOUT ttl (proxy URL)
expect(capturedPayload.system[0].cache_control).toEqual({ type: "ephemeral" });
expect(capturedPayload).not.toBeNull();
expect(capturedPayload.system[0].cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
});
it("should omit ttl when supportsLongCacheRetention is false", async () => {
const baseModel = getModel("anthropic", "claude-haiku-4-5");
const proxyModel = {
...baseModel,
baseUrl: "https://my-proxy.example.com/v1",
compat: { supportsLongCacheRetention: false },
};
let capturedPayload: any = null;
const { streamAnthropic } = await import("../src/providers/anthropic.js");
try {
const s = streamAnthropic(proxyModel, context, {
apiKey: "fake-key",
cacheRetention: "long",
onPayload: (payload) => {
capturedPayload = payload;
},
});
for await (const event of s) {
if (event.type === "error") break;
}
} catch {
// Expected to fail
}
expect(capturedPayload).not.toBeNull();
expect(capturedPayload.system[0].cache_control).toEqual({ type: "ephemeral" });
});
it("should omit cache_control when cacheRetention is none", async () => {
@@ -240,7 +268,7 @@ describe("Cache Retention (PI_CACHE_RETENTION)", () => {
},
);
it("should not set prompt_cache_retention when baseUrl is not api.openai.com", async () => {
it("should set prompt_cache_retention for non-api.openai.com baseUrl by default", async () => {
process.env.PI_CACHE_RETENTION = "long";
// Create a model with a different baseUrl (simulating a proxy)
@@ -270,10 +298,38 @@ describe("Cache Retention (PI_CACHE_RETENTION)", () => {
// Expected to fail
}
// The payload should have been captured before the error
if (capturedPayload) {
expect(capturedPayload.prompt_cache_retention).toBeUndefined();
expect(capturedPayload).not.toBeNull();
expect(capturedPayload.prompt_cache_retention).toBe("24h");
});
it("should omit prompt_cache_retention when supportsLongCacheRetention is false", async () => {
const model = {
...getModel("openai", "gpt-4o-mini"),
compat: { supportsLongCacheRetention: false },
};
let capturedPayload: any = null;
const { streamOpenAIResponses } = await import("../src/providers/openai-responses.js");
try {
const s = streamOpenAIResponses(model, context, {
apiKey: "fake-key",
cacheRetention: "long",
sessionId: "session-compat-false",
onPayload: (payload) => {
capturedPayload = payload;
},
});
for await (const event of s) {
if (event.type === "error") break;
}
} catch {
// Expected to fail
}
expect(capturedPayload).not.toBeNull();
expect(capturedPayload.prompt_cache_retention).toBeUndefined();
});
it("should omit prompt_cache_key when cacheRetention is none", async () => {
@@ -332,4 +388,74 @@ describe("Cache Retention (PI_CACHE_RETENTION)", () => {
expect(capturedPayload.prompt_cache_retention).toBe("24h");
});
});
describe("OpenAI Completions Provider", () => {
function createCompletionsModel(compat?: Model<"openai-completions">["compat"]): Model<"openai-completions"> {
return {
id: "test-model",
name: "Test Model",
api: "openai-completions",
provider: "test-openai-completions",
baseUrl: "https://my-proxy.example.com/v1",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 128000,
maxTokens: 4096,
compat,
};
}
it("should set prompt_cache_retention for non-api.openai.com baseUrl by default", async () => {
let capturedPayload: any = null;
const { streamOpenAICompletions } = await import("../src/providers/openai-completions.js");
try {
const s = streamOpenAICompletions(createCompletionsModel(), context, {
apiKey: "fake-key",
cacheRetention: "long",
sessionId: "session-completions",
onPayload: (payload) => {
capturedPayload = payload;
},
});
for await (const event of s) {
if (event.type === "error") break;
}
} catch {
// Expected to fail
}
expect(capturedPayload).not.toBeNull();
expect(capturedPayload.prompt_cache_key).toBe("session-completions");
expect(capturedPayload.prompt_cache_retention).toBe("24h");
});
it("should omit prompt_cache_retention when supportsLongCacheRetention is false", async () => {
let capturedPayload: any = null;
const { streamOpenAICompletions } = await import("../src/providers/openai-completions.js");
try {
const s = streamOpenAICompletions(createCompletionsModel({ supportsLongCacheRetention: false }), context, {
apiKey: "fake-key",
cacheRetention: "long",
sessionId: "session-completions-false",
onPayload: (payload) => {
capturedPayload = payload;
},
});
for await (const event of s) {
if (event.type === "error") break;
}
} catch {
// Expected to fail
}
expect(capturedPayload).not.toBeNull();
expect(capturedPayload.prompt_cache_key).toBeUndefined();
expect(capturedPayload.prompt_cache_retention).toBeUndefined();
});
});
});