fix(ai): disable OpenCode long cache retention for rejecting routes
closes #5702
This commit is contained in:
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed OpenCode/OpenCode Go completion models that reject `prompt_cache_retention` to omit long-retention cache fields when `cacheRetention` is `long` ([#5702](https://github.com/earendil-works/pi/issues/5702)).
|
||||||
|
|
||||||
## [0.79.3] - 2026-06-13
|
## [0.79.3] - 2026-06-13
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -187,6 +187,15 @@ const OPENAI_RESPONSES_NONE_REASONING_MODELS = new Set([
|
|||||||
"gpt-5.5",
|
"gpt-5.5",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const OPENCODE_OPENAI_COMPLETIONS_LONG_CACHE_RETENTION_UNSUPPORTED_MODELS = new Set([
|
||||||
|
"opencode:deepseek-v4-flash",
|
||||||
|
"opencode:deepseek-v4-pro",
|
||||||
|
"opencode:kimi-k2.5",
|
||||||
|
"opencode:kimi-k2.6",
|
||||||
|
"opencode:minimax-m2.7",
|
||||||
|
"opencode-go:kimi-k2.6",
|
||||||
|
]);
|
||||||
|
|
||||||
function mergeThinkingLevelMap(model: Model<any>, map: NonNullable<Model<any>["thinkingLevelMap"]>): void {
|
function mergeThinkingLevelMap(model: Model<any>, map: NonNullable<Model<any>["thinkingLevelMap"]>): void {
|
||||||
model.thinkingLevelMap = { ...model.thinkingLevelMap, ...map };
|
model.thinkingLevelMap = { ...model.thinkingLevelMap, ...map };
|
||||||
}
|
}
|
||||||
@@ -1070,6 +1079,13 @@ async function loadModelsDevData(): Promise<Model<any>[]> {
|
|||||||
|
|
||||||
if (api === "openai-completions") {
|
if (api === "openai-completions") {
|
||||||
compat = { ...(compat ?? {}), maxTokensField: "max_tokens" };
|
compat = { ...(compat ?? {}), maxTokensField: "max_tokens" };
|
||||||
|
if (
|
||||||
|
OPENCODE_OPENAI_COMPLETIONS_LONG_CACHE_RETENTION_UNSUPPORTED_MODELS.has(
|
||||||
|
`${variant.provider}:${modelId}`,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
compat = { ...compat, supportsLongCacheRetention: false };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
models.push({
|
models.push({
|
||||||
|
|||||||
@@ -7889,7 +7889,7 @@ export const MODELS = {
|
|||||||
api: "openai-completions",
|
api: "openai-completions",
|
||||||
provider: "opencode",
|
provider: "opencode",
|
||||||
baseUrl: "https://opencode.ai/zen/v1",
|
baseUrl: "https://opencode.ai/zen/v1",
|
||||||
compat: {"maxTokensField":"max_tokens","requiresReasoningContentOnAssistantMessages":true,"thinkingFormat":"deepseek"},
|
compat: {"maxTokensField":"max_tokens","supportsLongCacheRetention":false,"requiresReasoningContentOnAssistantMessages":true,"thinkingFormat":"deepseek"},
|
||||||
reasoning: true,
|
reasoning: true,
|
||||||
thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":"max"},
|
thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":"max"},
|
||||||
input: ["text"],
|
input: ["text"],
|
||||||
@@ -7927,7 +7927,7 @@ export const MODELS = {
|
|||||||
api: "openai-completions",
|
api: "openai-completions",
|
||||||
provider: "opencode",
|
provider: "opencode",
|
||||||
baseUrl: "https://opencode.ai/zen/v1",
|
baseUrl: "https://opencode.ai/zen/v1",
|
||||||
compat: {"maxTokensField":"max_tokens","requiresReasoningContentOnAssistantMessages":true,"thinkingFormat":"deepseek"},
|
compat: {"maxTokensField":"max_tokens","supportsLongCacheRetention":false,"requiresReasoningContentOnAssistantMessages":true,"thinkingFormat":"deepseek"},
|
||||||
reasoning: true,
|
reasoning: true,
|
||||||
thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":"max"},
|
thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":"max"},
|
||||||
input: ["text"],
|
input: ["text"],
|
||||||
@@ -8343,7 +8343,7 @@ export const MODELS = {
|
|||||||
api: "openai-completions",
|
api: "openai-completions",
|
||||||
provider: "opencode",
|
provider: "opencode",
|
||||||
baseUrl: "https://opencode.ai/zen/v1",
|
baseUrl: "https://opencode.ai/zen/v1",
|
||||||
compat: {"maxTokensField":"max_tokens"},
|
compat: {"maxTokensField":"max_tokens","supportsLongCacheRetention":false},
|
||||||
reasoning: true,
|
reasoning: true,
|
||||||
input: ["text", "image"],
|
input: ["text", "image"],
|
||||||
cost: {
|
cost: {
|
||||||
@@ -8361,7 +8361,7 @@ export const MODELS = {
|
|||||||
api: "openai-completions",
|
api: "openai-completions",
|
||||||
provider: "opencode",
|
provider: "opencode",
|
||||||
baseUrl: "https://opencode.ai/zen/v1",
|
baseUrl: "https://opencode.ai/zen/v1",
|
||||||
compat: {"thinkingFormat":"deepseek","supportsReasoningEffort":false,"maxTokensField":"max_tokens"},
|
compat: {"thinkingFormat":"deepseek","supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsLongCacheRetention":false},
|
||||||
reasoning: true,
|
reasoning: true,
|
||||||
input: ["text", "image"],
|
input: ["text", "image"],
|
||||||
cost: {
|
cost: {
|
||||||
@@ -8415,7 +8415,7 @@ export const MODELS = {
|
|||||||
api: "openai-completions",
|
api: "openai-completions",
|
||||||
provider: "opencode",
|
provider: "opencode",
|
||||||
baseUrl: "https://opencode.ai/zen/v1",
|
baseUrl: "https://opencode.ai/zen/v1",
|
||||||
compat: {"maxTokensField":"max_tokens"},
|
compat: {"maxTokensField":"max_tokens","supportsLongCacheRetention":false},
|
||||||
reasoning: true,
|
reasoning: true,
|
||||||
input: ["text"],
|
input: ["text"],
|
||||||
cost: {
|
cost: {
|
||||||
@@ -8579,7 +8579,7 @@ export const MODELS = {
|
|||||||
api: "openai-completions",
|
api: "openai-completions",
|
||||||
provider: "opencode-go",
|
provider: "opencode-go",
|
||||||
baseUrl: "https://opencode.ai/zen/go/v1",
|
baseUrl: "https://opencode.ai/zen/go/v1",
|
||||||
compat: {"thinkingFormat":"deepseek","supportsReasoningEffort":false,"maxTokensField":"max_tokens"},
|
compat: {"thinkingFormat":"deepseek","supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsLongCacheRetention":false},
|
||||||
reasoning: true,
|
reasoning: true,
|
||||||
thinkingLevelMap: {"minimal":null,"low":null,"medium":null},
|
thinkingLevelMap: {"minimal":null,"low":null,"medium":null},
|
||||||
input: ["text", "image"],
|
input: ["text", "image"],
|
||||||
@@ -10473,13 +10473,13 @@ export const MODELS = {
|
|||||||
reasoning: true,
|
reasoning: true,
|
||||||
input: ["text", "image"],
|
input: ["text", "image"],
|
||||||
cost: {
|
cost: {
|
||||||
input: 0.95,
|
input: 0.75,
|
||||||
output: 4,
|
output: 3.5,
|
||||||
cacheRead: 0.19,
|
cacheRead: 0.16,
|
||||||
cacheWrite: 0,
|
cacheWrite: 0,
|
||||||
},
|
},
|
||||||
contextWindow: 262144,
|
contextWindow: 262144,
|
||||||
maxTokens: 4096,
|
maxTokens: 262144,
|
||||||
} satisfies Model<"openai-completions">,
|
} satisfies Model<"openai-completions">,
|
||||||
"nex-agi/nex-n2-pro:free": {
|
"nex-agi/nex-n2-pro:free": {
|
||||||
id: "nex-agi/nex-n2-pro:free",
|
id: "nex-agi/nex-n2-pro:free",
|
||||||
@@ -16823,6 +16823,24 @@ export const MODELS = {
|
|||||||
contextWindow: 200000,
|
contextWindow: 200000,
|
||||||
maxTokens: 131072,
|
maxTokens: 131072,
|
||||||
} satisfies Model<"openai-completions">,
|
} satisfies Model<"openai-completions">,
|
||||||
|
"glm-5.2": {
|
||||||
|
id: "glm-5.2",
|
||||||
|
name: "GLM-5.2",
|
||||||
|
api: "openai-completions",
|
||||||
|
provider: "zai",
|
||||||
|
baseUrl: "https://api.z.ai/api/coding/paas/v4",
|
||||||
|
compat: {"supportsDeveloperRole":false,"thinkingFormat":"zai","zaiToolStream":true},
|
||||||
|
reasoning: true,
|
||||||
|
input: ["text"],
|
||||||
|
cost: {
|
||||||
|
input: 0,
|
||||||
|
output: 0,
|
||||||
|
cacheRead: 0,
|
||||||
|
cacheWrite: 0,
|
||||||
|
},
|
||||||
|
contextWindow: 1000000,
|
||||||
|
maxTokens: 131072,
|
||||||
|
} satisfies Model<"openai-completions">,
|
||||||
"glm-5v-turbo": {
|
"glm-5v-turbo": {
|
||||||
id: "glm-5v-turbo",
|
id: "glm-5v-turbo",
|
||||||
name: "GLM-5V-Turbo",
|
name: "GLM-5V-Turbo",
|
||||||
@@ -16915,6 +16933,24 @@ export const MODELS = {
|
|||||||
contextWindow: 200000,
|
contextWindow: 200000,
|
||||||
maxTokens: 131072,
|
maxTokens: 131072,
|
||||||
} satisfies Model<"openai-completions">,
|
} satisfies Model<"openai-completions">,
|
||||||
|
"glm-5.2": {
|
||||||
|
id: "glm-5.2",
|
||||||
|
name: "GLM-5.2",
|
||||||
|
api: "openai-completions",
|
||||||
|
provider: "zai-coding-cn",
|
||||||
|
baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
|
||||||
|
compat: {"supportsDeveloperRole":false,"thinkingFormat":"zai","zaiToolStream":true},
|
||||||
|
reasoning: true,
|
||||||
|
input: ["text"],
|
||||||
|
cost: {
|
||||||
|
input: 0,
|
||||||
|
output: 0,
|
||||||
|
cacheRead: 0,
|
||||||
|
cacheWrite: 0,
|
||||||
|
},
|
||||||
|
contextWindow: 1000000,
|
||||||
|
maxTokens: 131072,
|
||||||
|
} satisfies Model<"openai-completions">,
|
||||||
"glm-5v-turbo": {
|
"glm-5v-turbo": {
|
||||||
id: "glm-5v-turbo",
|
id: "glm-5v-turbo",
|
||||||
name: "GLM-5V-Turbo",
|
name: "GLM-5V-Turbo",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
import { MODELS } from "../src/models.generated.ts";
|
||||||
import { getModel } from "../src/models.ts";
|
import { getModel } from "../src/models.ts";
|
||||||
import { streamAnthropic } from "../src/providers/anthropic.ts";
|
import { streamAnthropic } from "../src/providers/anthropic.ts";
|
||||||
import { streamOpenAICompletions } from "../src/providers/openai-completions.ts";
|
import { streamOpenAICompletions } from "../src/providers/openai-completions.ts";
|
||||||
@@ -13,6 +14,11 @@ class PayloadCaptured extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface OpenAICompletionsCachePayload {
|
||||||
|
prompt_cache_key?: string;
|
||||||
|
prompt_cache_retention?: string;
|
||||||
|
}
|
||||||
|
|
||||||
function stopAfterPayload<TPayload>(capture: (payload: TPayload) => void): (payload: unknown) => never {
|
function stopAfterPayload<TPayload>(capture: (payload: TPayload) => void): (payload: unknown) => never {
|
||||||
return (payload: unknown): never => {
|
return (payload: unknown): never => {
|
||||||
capture(payload as TPayload);
|
capture(payload as TPayload);
|
||||||
@@ -455,5 +461,39 @@ describe("Cache Retention (PI_CACHE_RETENTION)", () => {
|
|||||||
expect(capturedPayload.prompt_cache_key).toBeUndefined();
|
expect(capturedPayload.prompt_cache_key).toBeUndefined();
|
||||||
expect(capturedPayload.prompt_cache_retention).toBeUndefined();
|
expect(capturedPayload.prompt_cache_retention).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
MODELS.opencode["deepseek-v4-flash"],
|
||||||
|
MODELS.opencode["deepseek-v4-pro"],
|
||||||
|
MODELS.opencode["kimi-k2.5"],
|
||||||
|
MODELS.opencode["kimi-k2.6"],
|
||||||
|
MODELS.opencode["minimax-m2.7"],
|
||||||
|
MODELS["opencode-go"]["kimi-k2.6"],
|
||||||
|
] as const)("should omit long cache retention for $provider/$id", async (metadata) => {
|
||||||
|
const model = metadata as Model<"openai-completions">;
|
||||||
|
let capturedPayload: OpenAICompletionsCachePayload | undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const s = streamOpenAICompletions(model, context, {
|
||||||
|
apiKey: "fake-key",
|
||||||
|
cacheRetention: "long",
|
||||||
|
sessionId: "session-opencode-long-cache-unsupported",
|
||||||
|
onPayload: stopAfterPayload<OpenAICompletionsCachePayload>((payload) => {
|
||||||
|
capturedPayload = payload;
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
for await (const event of s) {
|
||||||
|
if (event.type === "error") break;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Expected to fail
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(model.compat?.supportsLongCacheRetention).toBe(false);
|
||||||
|
expect(capturedPayload).toBeDefined();
|
||||||
|
expect(capturedPayload?.prompt_cache_key).toBeUndefined();
|
||||||
|
expect(capturedPayload?.prompt_cache_retention).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed inherited OpenCode/OpenCode Go completion model metadata to omit long-retention cache fields for routes that reject `prompt_cache_retention` ([#5702](https://github.com/earendil-works/pi/issues/5702)).
|
||||||
- Fixed custom provider config so plain uppercase API key and header values remain literals instead of being treated as legacy environment references; use explicit `$ENV_VAR` syntax for environment variables ([#5661](https://github.com/earendil-works/pi/issues/5661)).
|
- Fixed custom provider config so plain uppercase API key and header values remain literals instead of being treated as legacy environment references; use explicit `$ENV_VAR` syntax for environment variables ([#5661](https://github.com/earendil-works/pi/issues/5661)).
|
||||||
|
|
||||||
## [0.79.3] - 2026-06-13
|
## [0.79.3] - 2026-06-13
|
||||||
|
|||||||
Reference in New Issue
Block a user