pi-cache-optimizer 2.6.17 → 2.6.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -6
- package/README.zh-CN.md +27 -6
- package/index.ts +73 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ Pi extension for improving provider-side KV / prompt cache hit rates. It keeps s
|
|
|
17
17
|
- [Commands](#commands)
|
|
18
18
|
- [Persistent opt-out](#persistent-opt-out)
|
|
19
19
|
- [OpenAI-compatible proxy setup](#openai-compatible-proxy-setup)
|
|
20
|
-
- [
|
|
20
|
+
- [Adaptive thinking models](#adaptive-thinking-models)
|
|
21
21
|
- [Auto-repair with `/cache-optimizer fix`](#auto-repair-with-cache-optimizer-fix)
|
|
22
22
|
- [Footer stats](#footer-stats)
|
|
23
23
|
- [For router / virtual-channel extension authors](#for-router--virtual-channel-extension-authors)
|
|
@@ -32,7 +32,7 @@ Pi extension for improving provider-side KV / prompt cache hit rates. It keeps s
|
|
|
32
32
|
- Requests long cache retention when Pi/provider compat supports it.
|
|
33
33
|
- Adds a session-id `prompt_cache_key` fallback for `openai-completions` / `openai-responses` payloads when no effective key exists.
|
|
34
34
|
- Warns once for third-party OpenAI-compatible proxies missing cache/session-affinity compat flags.
|
|
35
|
-
- Detects
|
|
35
|
+
- Detects adaptive-thinking compat for Claude (opus-4.6+, sonnet-4.6+ including Sonnet 5, fable-5+) and Kimi Coding K3 / `kimi-for-coding` custom channels.
|
|
36
36
|
- Shows restart-persistent provider/model footer stats for supported model families.
|
|
37
37
|
- Supports optional router-extension integration through versioned global protocols (`Symbol.for("pi.routing.registry.v1")` and `Symbol.for("pi.cache.hints.v1")`) without importing router packages.
|
|
38
38
|
|
|
@@ -109,9 +109,9 @@ Notes:
|
|
|
109
109
|
- For DeepSeek models, the Pi Mono guidance expects `compat.requiresReasoningContentOnAssistantMessages: true` and `compat.thinkingFormat: "deepseek"` alongside cache/session-affinity flags when the endpoint supports them.
|
|
110
110
|
- This extension's `doctor` and `compat` commands only advise; they do not modify `models.json`.
|
|
111
111
|
|
|
112
|
-
##
|
|
112
|
+
## Adaptive thinking models
|
|
113
113
|
|
|
114
|
-
Claude models from opus-4.6 / sonnet-4.6 (including Sonnet 5) / fable-5 onwards require `forceAdaptiveThinking: true` in compat. Without
|
|
114
|
+
Claude models from opus-4.6 / sonnet-4.6 (including Sonnet 5) / fable-5 onwards require `forceAdaptiveThinking: true` in compat. Kimi Coding K3 (`k3`) and `kimi-for-coding` also use adaptive thinking and need `allowEmptySignature: true` so replayed empty-signature thinking blocks remain valid. Without the required compat, Pi may send a legacy thinking payload or replay thinking incorrectly.
|
|
115
115
|
|
|
116
116
|
Pi's built-in catalog already sets this flag for official models. Custom channels in `models.json` that override these models must include the flag:
|
|
117
117
|
|
|
@@ -151,15 +151,36 @@ Or use model-level override:
|
|
|
151
151
|
}
|
|
152
152
|
```
|
|
153
153
|
|
|
154
|
+
For Kimi Coding K3 custom channels, use model-level compat when the provider contains mixed models:
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"providers": {
|
|
159
|
+
"your-kimi-coding-channel": {
|
|
160
|
+
"modelOverrides": {
|
|
161
|
+
"k3": {
|
|
162
|
+
"compat": {
|
|
163
|
+
"forceAdaptiveThinking": true,
|
|
164
|
+
"allowEmptySignature": true
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Pi 0.80.9+ already includes Kimi K3 in built-in Kimi Coding, Moonshot AI / China, OpenRouter, and Vercel AI Gateway catalogs. The Moonshot/OpenRouter variants use their OpenAI-compatible transport and continue through the normal Kimi footer/proxy path; the adaptive compat above applies only to `anthropic-messages` Kimi Coding channels.
|
|
174
|
+
|
|
154
175
|
`/cache-optimizer doctor` and `/cache-optimizer compat` detect missing flags and show copyable JSON.
|
|
155
176
|
|
|
156
177
|
## Auto-repair with `/cache-optimizer fix`
|
|
157
178
|
|
|
158
179
|
**v2.6.0+** adds a `fix` subcommand that can auto-repair safe compat issues:
|
|
159
180
|
|
|
160
|
-
-
|
|
181
|
+
- Adaptive thinking (`forceAdaptiveThinking: true`; Kimi Coding K3 / `kimi-for-coding` also `allowEmptySignature: true`)
|
|
161
182
|
- DeepSeek Pi Mono reasoning compat (`thinkingFormat: "deepseek"`, `requiresReasoningContentOnAssistantMessages: true`)
|
|
162
|
-
- OpenAI-compatible proxy session affinity (`sendSessionAffinityHeaders: true` for `openai-completions
|
|
183
|
+
- OpenAI-compatible proxy session affinity (`sendSessionAffinityHeaders: true` for `openai-completions`). Pi 0.80.7+ controls `openai-responses` header shape with `sessionAffinityFormat` and auto-detects its default; this extension no longer writes the removed `sendSessionIdHeader` field.
|
|
163
184
|
|
|
164
185
|
**Scope:** only the currently active model. Other channels require switching models and running `fix` again.
|
|
165
186
|
|
package/README.zh-CN.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
- [命令](#命令)
|
|
18
18
|
- [持久 Opt-out](#持久-opt-out)
|
|
19
19
|
- [OpenAI-compatible 代理配置](#openai-compatible-代理配置)
|
|
20
|
-
- [
|
|
20
|
+
- [Adaptive thinking 模型](#adaptive-thinking-模型)
|
|
21
21
|
- [使用 `/cache-optimizer fix` 自动修复](#使用-cache-optimizer-fix-自动修复)
|
|
22
22
|
- [Footer 统计](#footer-统计)
|
|
23
23
|
- [Router / Virtual-channel 扩展作者指南](#router--virtual-channel-扩展作者指南)
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
- 在 Pi / provider compat 支持时请求长缓存保留。
|
|
33
33
|
- 对 `openai-completions` / `openai-responses` 请求,在没有有效 key 时使用 Pi session id 补 `prompt_cache_key`。
|
|
34
34
|
- 对缺少缓存 / session-affinity compat 的第三方 OpenAI-compatible 代理给出一次性提醒。
|
|
35
|
-
- 检测
|
|
35
|
+
- 检测 Claude(opus-4.6+、sonnet-4.6+ 含 Sonnet 5、fable-5+)以及 Kimi Coding K3 / `kimi-for-coding` 自定义渠道的 adaptive-thinking compat。
|
|
36
36
|
- 为支持的模型家族显示可跨 Pi 进程 / 终端重启延续的 provider/model footer 缓存统计。
|
|
37
37
|
- 通过版本化全局协议(`Symbol.for("pi.routing.registry.v1")` 与 `Symbol.for("pi.cache.hints.v1")`)支持可选的 router extension 集成,而不导入任何 router 包。
|
|
38
38
|
|
|
@@ -109,9 +109,9 @@ LiteLLM / OneAPI / NewAPI / 类 OpenRouter 渠道等第三方 `openai-completion
|
|
|
109
109
|
- 对 DeepSeek 模型,Pi Mono 指南期望在支持时同时设置 `compat.requiresReasoningContentOnAssistantMessages: true` 和 `compat.thinkingFormat: "deepseek"`,再配合缓存 / session-affinity 相关 compat。
|
|
110
110
|
- 本扩展的 `doctor` 和 `compat` 命令只给建议,不会修改 `models.json`。
|
|
111
111
|
|
|
112
|
-
##
|
|
112
|
+
## Adaptive thinking 模型
|
|
113
113
|
|
|
114
|
-
Claude 从 opus-4.6 / sonnet-4.6(含 Sonnet 5)/ fable-5 开始需要在 compat 中设置 `forceAdaptiveThinking: true
|
|
114
|
+
Claude 从 opus-4.6 / sonnet-4.6(含 Sonnet 5)/ fable-5 开始需要在 compat 中设置 `forceAdaptiveThinking: true`。Kimi Coding K3(`k3`)和 `kimi-for-coding` 也使用 adaptive thinking,并需要 `allowEmptySignature: true`,以正确重放空 signature 的 thinking block。缺少这些 compat 时,Pi 可能发送旧版 thinking payload 或错误重放 thinking。
|
|
115
115
|
|
|
116
116
|
Pi 内置 catalog 已为官方模型设置此 flag。`models.json` 中覆盖这些模型的自定义渠道必须包含该 flag:
|
|
117
117
|
|
|
@@ -151,15 +151,36 @@ Pi 内置 catalog 已为官方模型设置此 flag。`models.json` 中覆盖这
|
|
|
151
151
|
}
|
|
152
152
|
```
|
|
153
153
|
|
|
154
|
+
Kimi Coding K3 自定义渠道如果包含混合模型,建议使用模型级 compat:
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"providers": {
|
|
159
|
+
"your-kimi-coding-channel": {
|
|
160
|
+
"modelOverrides": {
|
|
161
|
+
"k3": {
|
|
162
|
+
"compat": {
|
|
163
|
+
"forceAdaptiveThinking": true,
|
|
164
|
+
"allowEmptySignature": true
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Pi 0.80.9+ 已在内置 Kimi Coding、Moonshot AI / 中国区、OpenRouter 和 Vercel AI Gateway catalog 中加入 Kimi K3。Moonshot/OpenRouter 变体使用 OpenAI-compatible transport,继续走普通 Kimi footer / proxy 路径;上面的 adaptive compat 只适用于 `anthropic-messages` Kimi Coding 渠道。
|
|
174
|
+
|
|
154
175
|
`/cache-optimizer doctor` 和 `/cache-optimizer compat` 会检测缺失的 flag 并显示可复制的 JSON。
|
|
155
176
|
|
|
156
177
|
## 使用 `/cache-optimizer fix` 自动修复
|
|
157
178
|
|
|
158
179
|
**v2.6.0+** 新增 `fix` 子命令,可自动修复安全的 compat 问题:
|
|
159
180
|
|
|
160
|
-
-
|
|
181
|
+
- Adaptive thinking(`forceAdaptiveThinking: true`;Kimi Coding K3 / `kimi-for-coding` 还包括 `allowEmptySignature: true`)
|
|
161
182
|
- DeepSeek Pi Mono reasoning compat(`thinkingFormat: "deepseek"`、`requiresReasoningContentOnAssistantMessages: true`)
|
|
162
|
-
- OpenAI-compatible proxy session affinity(`openai-completions`
|
|
183
|
+
- OpenAI-compatible proxy session affinity(`openai-completions` 使用 `sendSessionAffinityHeaders: true`)。Pi 0.80.7+ 使用 `sessionAffinityFormat` 控制 `openai-responses` header 形式并自动检测默认值;本扩展不再写入已移除的 `sendSessionIdHeader`。
|
|
163
184
|
|
|
164
185
|
**范围:** 仅当前 active model。其他渠道需切换模型后再次运行 `fix`。
|
|
165
186
|
|
package/index.ts
CHANGED
|
@@ -142,12 +142,13 @@ const ORION_MODEL_PATTERN = /(^|[\/\s:_-])orion($|[\-_.:\/\s])/i;
|
|
|
142
142
|
|
|
143
143
|
type CacheCompat = {
|
|
144
144
|
sendSessionAffinityHeaders?: boolean;
|
|
145
|
-
|
|
145
|
+
sessionAffinityFormat?: "openai" | "openai-nosession" | "openrouter";
|
|
146
146
|
supportsLongCacheRetention?: boolean;
|
|
147
147
|
thinkingFormat?: string;
|
|
148
148
|
requiresReasoningContentOnAssistantMessages?: boolean;
|
|
149
149
|
cacheControlFormat?: string;
|
|
150
150
|
forceAdaptiveThinking?: boolean;
|
|
151
|
+
allowEmptySignature?: boolean;
|
|
151
152
|
};
|
|
152
153
|
|
|
153
154
|
type CacheStats = {
|
|
@@ -1159,8 +1160,38 @@ function isAdaptiveGenerationModel(model: PiModel | undefined): boolean {
|
|
|
1159
1160
|
return tokens.some((t) => ADAPTIVE_OPUS_PATTERN.test(t) || ADAPTIVE_SONNET_PATTERN.test(t) || ADAPTIVE_FABLE_PATTERN.test(t));
|
|
1160
1161
|
}
|
|
1161
1162
|
|
|
1163
|
+
function isKimiCodingAdaptiveModel(model: PiModel | undefined): boolean {
|
|
1164
|
+
if (!model) return false;
|
|
1165
|
+
const provider = lower(model.provider);
|
|
1166
|
+
const baseUrl = lower(model.baseUrl);
|
|
1167
|
+
const isKimiCodingChannel = provider.includes("kimi-coding") || baseUrl.includes("api.kimi.com/coding");
|
|
1168
|
+
if (!isKimiCodingChannel) return false;
|
|
1169
|
+
|
|
1170
|
+
const tokens = getModelIdNameTokenValues(model);
|
|
1171
|
+
return tokens.some((token) =>
|
|
1172
|
+
token === "k3"
|
|
1173
|
+
|| token.includes("kimi-k3")
|
|
1174
|
+
|| token.includes("kimi k3")
|
|
1175
|
+
|| token.includes("kimi-for-coding")
|
|
1176
|
+
|| token.includes("kimi for coding")
|
|
1177
|
+
);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
function isKimiCodingEmptySignatureModel(model: PiModel | undefined): boolean {
|
|
1181
|
+
if (!isKimiCodingAdaptiveModel(model)) return false;
|
|
1182
|
+
return getModelIdNameTokenValues(model).some((token) =>
|
|
1183
|
+
token === "k3"
|
|
1184
|
+
|| token === "kimi-k3"
|
|
1185
|
+
|| token.startsWith("kimi-k3-")
|
|
1186
|
+
|| token === "kimi k3"
|
|
1187
|
+
|| token === "kimi-for-coding"
|
|
1188
|
+
|| token === "kimi for coding"
|
|
1189
|
+
);
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1162
1192
|
function isAdaptiveThinkingCompatApplicable(model: PiModel): boolean {
|
|
1163
|
-
return lower(model.api) === "anthropic-messages"
|
|
1193
|
+
return lower(model.api) === "anthropic-messages"
|
|
1194
|
+
&& (isAdaptiveGenerationModel(model) || isKimiCodingAdaptiveModel(model));
|
|
1164
1195
|
}
|
|
1165
1196
|
|
|
1166
1197
|
function describeMissingAdaptiveThinkingCompat(model: PiModel): string[] {
|
|
@@ -1169,6 +1200,9 @@ function describeMissingAdaptiveThinkingCompat(model: PiModel): string[] {
|
|
|
1169
1200
|
if (compat.forceAdaptiveThinking !== true) {
|
|
1170
1201
|
missing.push("forceAdaptiveThinking");
|
|
1171
1202
|
}
|
|
1203
|
+
if (isKimiCodingEmptySignatureModel(model) && compat.allowEmptySignature !== true) {
|
|
1204
|
+
missing.push("allowEmptySignature");
|
|
1205
|
+
}
|
|
1172
1206
|
return missing;
|
|
1173
1207
|
}
|
|
1174
1208
|
|
|
@@ -1177,6 +1211,9 @@ function buildAdaptiveThinkingCompatSuggestion(missing: string[]): Record<string
|
|
|
1177
1211
|
if (missing.includes("forceAdaptiveThinking")) {
|
|
1178
1212
|
suggestion.forceAdaptiveThinking = true;
|
|
1179
1213
|
}
|
|
1214
|
+
if (missing.includes("allowEmptySignature")) {
|
|
1215
|
+
suggestion.allowEmptySignature = true;
|
|
1216
|
+
}
|
|
1180
1217
|
return suggestion;
|
|
1181
1218
|
}
|
|
1182
1219
|
|
|
@@ -1189,6 +1226,9 @@ function appendAdaptiveThinkingCompatAdviceLines(lines: string[], missing: strin
|
|
|
1189
1226
|
lines.push("- forceAdaptiveThinking: true tells Pi to use adaptive thinking format");
|
|
1190
1227
|
lines.push(" (thinking: {type: 'adaptive'}) instead of legacy budget tokens format.");
|
|
1191
1228
|
lines.push(" Without this flag, Pi sends legacy thinking which adaptive-only upstreams reject.");
|
|
1229
|
+
if (missing.includes("allowEmptySignature")) {
|
|
1230
|
+
lines.push("- allowEmptySignature: true preserves Kimi Coding K3 thinking blocks whose replay signature is empty.");
|
|
1231
|
+
}
|
|
1192
1232
|
appendCredentialSafeProviderGuidance(lines, placement, suggestion);
|
|
1193
1233
|
}
|
|
1194
1234
|
|
|
@@ -1198,8 +1238,8 @@ function buildAdaptiveThinkingCompatWarningText(key: string, missing: string[]):
|
|
|
1198
1238
|
const modelId = slashIdx > 0 ? key.slice(slashIdx + 1) : undefined;
|
|
1199
1239
|
const modelsJsonPath = getModelsJsonDisplayPath();
|
|
1200
1240
|
const lines: string[] = [
|
|
1201
|
-
`💡 pi-cache-optimizer: ${key} is an adaptive-generation
|
|
1202
|
-
`Without
|
|
1241
|
+
`💡 pi-cache-optimizer: ${key} is an adaptive-generation model but merged compat lacks ${missing.join(" and ")}.`,
|
|
1242
|
+
`Without the required compat, Pi may send legacy thinking or replay thinking blocks incorrectly.`,
|
|
1203
1243
|
`Edit ${modelsJsonPath} -> providers["${providerLabel}"] -> compat (at the same level as baseUrl/api/apiKey/models).`,
|
|
1204
1244
|
"",
|
|
1205
1245
|
];
|
|
@@ -2177,11 +2217,7 @@ function describeMissingDeepSeekCompat(model: PiModel): string[] {
|
|
|
2177
2217
|
if (compat.supportsLongCacheRetention !== true) {
|
|
2178
2218
|
missing.push("supportsLongCacheRetention");
|
|
2179
2219
|
}
|
|
2180
|
-
if (model.api
|
|
2181
|
-
if (compat.sendSessionIdHeader !== true) {
|
|
2182
|
-
missing.push("sendSessionIdHeader");
|
|
2183
|
-
}
|
|
2184
|
-
} else if (compat.sendSessionAffinityHeaders !== true) {
|
|
2220
|
+
if (model.api !== "openai-responses" && compat.sendSessionAffinityHeaders !== true) {
|
|
2185
2221
|
missing.push("sendSessionAffinityHeaders");
|
|
2186
2222
|
}
|
|
2187
2223
|
if (compat.requiresReasoningContentOnAssistantMessages !== true) {
|
|
@@ -2214,9 +2250,6 @@ function buildDeepSeekCompatSuggestion(missing: string[]): Record<string, unknow
|
|
|
2214
2250
|
if (missing.includes("supportsLongCacheRetention")) {
|
|
2215
2251
|
suggestion.supportsLongCacheRetention = true;
|
|
2216
2252
|
}
|
|
2217
|
-
if (missing.includes("sendSessionIdHeader")) {
|
|
2218
|
-
suggestion.sendSessionIdHeader = true;
|
|
2219
|
-
}
|
|
2220
2253
|
if (missing.includes("sendSessionAffinityHeaders")) {
|
|
2221
2254
|
suggestion.sendSessionAffinityHeaders = true;
|
|
2222
2255
|
}
|
|
@@ -2246,9 +2279,6 @@ function appendDeepSeekCompatAdviceLines(lines: string[], missing: string[], pla
|
|
|
2246
2279
|
if (missing.includes("sendSessionAffinityHeaders")) {
|
|
2247
2280
|
lines.push("- sendSessionAffinityHeaders: recommended for OpenAI-compatible DeepSeek proxies when supported; it helps keep one Pi session on the same upstream/backend.");
|
|
2248
2281
|
}
|
|
2249
|
-
if (missing.includes("sendSessionIdHeader")) {
|
|
2250
|
-
lines.push("- sendSessionIdHeader: recommended for OpenAI Responses-compatible DeepSeek proxies when supported.");
|
|
2251
|
-
}
|
|
2252
2282
|
if (missing.includes("supportsLongCacheRetention")) {
|
|
2253
2283
|
lines.push("- supportsLongCacheRetention: enable for DeepSeek-compatible endpoints that support long cache retention.");
|
|
2254
2284
|
}
|
|
@@ -3256,15 +3286,14 @@ function notifyCacheCompatIfNeeded(
|
|
|
3256
3286
|
if (!model) return;
|
|
3257
3287
|
|
|
3258
3288
|
// Native anthropic-messages adaptive thinking compat check.
|
|
3259
|
-
//
|
|
3260
|
-
//
|
|
3261
|
-
if (
|
|
3262
|
-
const
|
|
3263
|
-
if (
|
|
3289
|
+
// Adapter warningText only fires for OpenAI-compatible APIs, so native
|
|
3290
|
+
// Anthropic and Kimi Coding adaptive models need a separate check.
|
|
3291
|
+
if (isAdaptiveThinkingCompatApplicable(model)) {
|
|
3292
|
+
const missing = describeMissingAdaptiveThinkingCompat(model);
|
|
3293
|
+
if (missing.length > 0) {
|
|
3264
3294
|
const key = `adaptive-thinking:${modelKey(model)}`;
|
|
3265
3295
|
if (!warnedModels.has(key)) {
|
|
3266
3296
|
warnedModels.add(key);
|
|
3267
|
-
const missing = describeMissingAdaptiveThinkingCompat(model);
|
|
3268
3297
|
ctx.ui.notify(buildAdaptiveThinkingCompatWarningText(modelKey(model), missing), "warning");
|
|
3269
3298
|
}
|
|
3270
3299
|
}
|
|
@@ -3930,16 +3959,16 @@ function isPromptCacheRetention400Applicable(model: PiModel): boolean {
|
|
|
3930
3959
|
/**
|
|
3931
3960
|
* Whether the 403 sendSessionAffinityHeaders diagnostic applies to a model.
|
|
3932
3961
|
*
|
|
3933
|
-
* Pi's openai-completions
|
|
3934
|
-
*
|
|
3935
|
-
*
|
|
3936
|
-
*
|
|
3937
|
-
*
|
|
3938
|
-
*
|
|
3939
|
-
*
|
|
3962
|
+
* Pi's openai-completions adapter sends custom HTTP headers (session_id,
|
|
3963
|
+
* x-client-request-id, x-session-affinity) when `sendSessionAffinityHeaders`
|
|
3964
|
+
* is enabled. Some third-party proxies / CDNs / WAFs block these headers and
|
|
3965
|
+
* return HTTP 403 "Your request was blocked". Pi 0.80.7+ no longer uses this
|
|
3966
|
+
* flag for openai-responses; Responses header shape is controlled by
|
|
3967
|
+
* `sessionAffinityFormat`. This guard therefore applies only to
|
|
3968
|
+
* openai-completions, where doctor/fix can safely advise disabling the flag.
|
|
3940
3969
|
*/
|
|
3941
3970
|
function isSessionAffinity403Applicable(model: PiModel): boolean {
|
|
3942
|
-
if (!
|
|
3971
|
+
if (!isOpenAICompatibleProxyApi(model.api)) return false;
|
|
3943
3972
|
return getCompat(model).sendSessionAffinityHeaders === true;
|
|
3944
3973
|
}
|
|
3945
3974
|
|
|
@@ -5290,7 +5319,6 @@ function deepEqualIgnoringKeys(a: unknown, b: unknown, extraKeys: string[]): boo
|
|
|
5290
5319
|
*/
|
|
5291
5320
|
const PROVIDER_LEVEL_SAFE_COMPAT_KEYS = new Set<string>([
|
|
5292
5321
|
"sendSessionAffinityHeaders",
|
|
5293
|
-
"sendSessionIdHeader",
|
|
5294
5322
|
"supportsLongCacheRetention",
|
|
5295
5323
|
]);
|
|
5296
5324
|
|
|
@@ -5304,9 +5332,10 @@ function syntheticModelForId(providerLabel: string, id: string): PiModel {
|
|
|
5304
5332
|
* Strategy (auto-detect, prefer provider level when safe):
|
|
5305
5333
|
* - Channel-capability keys (session affinity / long retention) are always
|
|
5306
5334
|
* provider-safe.
|
|
5307
|
-
* - Model-behavior keys (forceAdaptiveThinking,
|
|
5308
|
-
* provider-safe ONLY when every sibling model in
|
|
5309
|
-
* the same detection (all adaptive-generation /
|
|
5335
|
+
* - Model-behavior keys (forceAdaptiveThinking, allowEmptySignature,
|
|
5336
|
+
* thinkingFormat, ...) are provider-safe ONLY when every sibling model in
|
|
5337
|
+
* the provider also matches the same detection (all adaptive-generation /
|
|
5338
|
+
* Kimi Coding adaptive / DeepSeek-like).
|
|
5310
5339
|
* - Single-model providers: provider level is equivalent — prefer it.
|
|
5311
5340
|
* - Any unsafe key → fall back to model level (single write, smallest blast radius).
|
|
5312
5341
|
*/
|
|
@@ -5329,10 +5358,18 @@ function decideFixPlacement(
|
|
|
5329
5358
|
if (PROVIDER_LEVEL_SAFE_COMPAT_KEYS.has(key)) continue;
|
|
5330
5359
|
|
|
5331
5360
|
if (key === "forceAdaptiveThinking") {
|
|
5332
|
-
const allAdaptive = siblings.every((id) =>
|
|
5361
|
+
const allAdaptive = siblings.every((id) => {
|
|
5362
|
+
const sibling = syntheticModelForId(providerLabel, id);
|
|
5363
|
+
return isAdaptiveGenerationModel(sibling) || isKimiCodingAdaptiveModel(sibling);
|
|
5364
|
+
});
|
|
5333
5365
|
if (!allAdaptive) unsafeKeys.push(key);
|
|
5334
5366
|
continue;
|
|
5335
5367
|
}
|
|
5368
|
+
if (key === "allowEmptySignature") {
|
|
5369
|
+
const allKimiCodingAdaptive = siblings.every((id) => isKimiCodingAdaptiveModel(syntheticModelForId(providerLabel, id)));
|
|
5370
|
+
if (!allKimiCodingAdaptive) unsafeKeys.push(key);
|
|
5371
|
+
continue;
|
|
5372
|
+
}
|
|
5336
5373
|
if (key === "thinkingFormat" || key === "requiresReasoningContentOnAssistantMessages") {
|
|
5337
5374
|
const allDeepSeek = siblings.every((id) => isDeepSeekLikeModel(syntheticModelForId(providerLabel, id)));
|
|
5338
5375
|
if (!allDeepSeek) unsafeKeys.push(key);
|
|
@@ -5935,6 +5972,8 @@ export const __internals_for_tests = {
|
|
|
5935
5972
|
buildFixSuggestion,
|
|
5936
5973
|
// Adaptive thinking compat helpers
|
|
5937
5974
|
isAdaptiveGenerationModel,
|
|
5975
|
+
isKimiCodingAdaptiveModel,
|
|
5976
|
+
isKimiCodingEmptySignatureModel,
|
|
5938
5977
|
isAdaptiveThinkingCompatApplicable,
|
|
5939
5978
|
describeMissingAdaptiveThinkingCompat,
|
|
5940
5979
|
buildAdaptiveThinkingCompatSuggestion,
|
package/package.json
CHANGED