pi-cache-optimizer 2.6.16 → 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 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
- - [Anthropic adaptive thinking models](#anthropic-adaptive-thinking-models)
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 Anthropic adaptive thinking models (opus-4.6+, sonnet-4.6+, fable-5+) missing `forceAdaptiveThinking: true` compat.
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
- ## Anthropic adaptive thinking models
112
+ ## Adaptive thinking models
113
113
 
114
- Claude models from opus-4.6 / sonnet-4.6 / fable-5 onwards require `forceAdaptiveThinking: true` in compat. Without it, Pi sends the legacy thinking format and Anthropic rejects the request.
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
 
@@ -126,7 +126,7 @@ Pi's built-in catalog already sets this flag for official models. Custom channel
126
126
  "forceAdaptiveThinking": true
127
127
  },
128
128
  "models": [
129
- { "id": "claude-opus-4-8", "name": "Claude Opus 4.8" }
129
+ { "id": "claude-sonnet-5", "name": "Claude Sonnet 5" }
130
130
  ]
131
131
  }
132
132
  }
@@ -140,7 +140,7 @@ Or use model-level override:
140
140
  "providers": {
141
141
  "your-claude-channel": {
142
142
  "modelOverrides": {
143
- "claude-opus-4-8": {
143
+ "claude-sonnet-5": {
144
144
  "compat": {
145
145
  "forceAdaptiveThinking": true
146
146
  }
@@ -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
- - Anthropic adaptive thinking (`forceAdaptiveThinking: true`)
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`, `sendSessionIdHeader: true` for `openai-responses`)
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
- - [Anthropic adaptive thinking 模型](#anthropic-adaptive-thinking-模型)
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
- - 检测 Anthropic adaptive thinking 模型(opus-4.6+、sonnet-4.6+、fable-5+)是否缺少 `forceAdaptiveThinking: true` compat。
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
- ## Anthropic adaptive thinking 模型
112
+ ## Adaptive thinking 模型
113
113
 
114
- Claude 从 opus-4.6 / sonnet-4.6 / fable-5 开始需要在 compat 中设置 `forceAdaptiveThinking: true`。缺少此 flag 时,Pi 会发送旧版 thinking 格式,Anthropic 会拒绝请求。
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
 
@@ -126,7 +126,7 @@ Pi 内置 catalog 已为官方模型设置此 flag。`models.json` 中覆盖这
126
126
  "forceAdaptiveThinking": true
127
127
  },
128
128
  "models": [
129
- { "id": "claude-opus-4-8", "name": "Claude Opus 4.8" }
129
+ { "id": "claude-sonnet-5", "name": "Claude Sonnet 5" }
130
130
  ]
131
131
  }
132
132
  }
@@ -140,7 +140,7 @@ Pi 内置 catalog 已为官方模型设置此 flag。`models.json` 中覆盖这
140
140
  "providers": {
141
141
  "your-claude-channel": {
142
142
  "modelOverrides": {
143
- "claude-opus-4-8": {
143
+ "claude-sonnet-5": {
144
144
  "compat": {
145
145
  "forceAdaptiveThinking": true
146
146
  }
@@ -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
- - Anthropic adaptive thinking(`forceAdaptiveThinking: true`)
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` 用 `sendSessionAffinityHeaders: true`,`openai-responses` 用 `sendSessionIdHeader: true`)
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
- sendSessionIdHeader?: boolean;
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 = {
@@ -1143,14 +1144,14 @@ function isGeminiLikeAssistantMessage(message: unknown, model: PiModel | undefin
1143
1144
  *
1144
1145
  * Adaptive-generation models (from pi-ai built-in catalog) include:
1145
1146
  * claude-opus-4-6, claude-opus-4-7, claude-opus-4-8 (also dotted 4.6/4.7/4.8)
1146
- * claude-sonnet-4-6
1147
+ * claude-sonnet-4-6, claude-sonnet-5
1147
1148
  * claude-fable-5
1148
1149
  *
1149
1150
  * We match broadly: opus >= 4-6, sonnet >= 4-6, fable >= 5.
1150
1151
  * Ids may carry date-stamp or size suffixes like "[1M]".
1151
1152
  */
1152
- const ADAPTIVE_OPUS_PATTERN = /(^|[\/\s:_-])(opus-4[.-][6-9]|opus-4-[1-9][0-9])($|[-_.:\/\s\[])/i;
1153
- const ADAPTIVE_SONNET_PATTERN = /(^|[\/\s:_-])(sonnet-4[.-][6-9]|sonnet-4-[1-9][0-9])($|[-_.:\/\s\[])/i;
1153
+ const ADAPTIVE_OPUS_PATTERN = /(^|[\/\s:_-])(opus-4[.-][6-9]|opus-4-[1-9][0-9]|opus-([5-9]|[1-9][0-9]))($|[-_.:\/\s\[])/i;
1154
+ const ADAPTIVE_SONNET_PATTERN = /(^|[\/\s:_-])(sonnet-4[.-][6-9]|sonnet-4-[1-9][0-9]|sonnet-([5-9]|[1-9][0-9]))($|[-_.:\/\s\[])/i;
1154
1155
  const ADAPTIVE_FABLE_PATTERN = /(^|[\/\s:_-])fable-([5-9]|[1-9][0-9])($|[-_.:\/\s\[])/i;
1155
1156
 
1156
1157
  function isAdaptiveGenerationModel(model: PiModel | undefined): boolean {
@@ -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" && isAdaptiveGenerationModel(model);
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 Claude model but merged compat lacks ${missing.join(" and ")}.`,
1202
- `Without this flag, Pi sends legacy thinking format that may be rejected by the upstream.`,
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 === "openai-responses") {
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
- // The Claude adapter's warningText only fires for OpenAI-compatible APIs,
3260
- // so native anthropic-messages models need a separate check.
3261
- if (lower(model.api) === "anthropic-messages" && isAdaptiveGenerationModel(model)) {
3262
- const compat = getCompat(model);
3263
- if (compat.forceAdaptiveThinking !== true) {
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 / openai-responses adapters send three custom HTTP
3934
- * headers (session_id, x-client-request-id, x-session-affinity) when
3935
- * `sendSessionAffinityHeaders` is enabled. Some third-party proxies / CDNs /
3936
- * WAFs block these custom headers and return HTTP 403 "Your request was
3937
- * blocked". This guard returns true only for OpenAI-compatible APIs whose
3938
- * merged compat currently enables session-affinity headers, so the 403
3939
- * monitoring and doctor/fix paths can advise disabling the flag.
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 (!isOpenAICompatibleApi(model.api)) return false;
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, thinkingFormat, ...) are
5308
- * provider-safe ONLY when every sibling model in the provider also matches
5309
- * the same detection (all adaptive-generation / all DeepSeek-like).
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) => isAdaptiveGenerationModel(syntheticModelForId(providerLabel, 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cache-optimizer",
3
- "version": "2.6.16",
3
+ "version": "2.6.18",
4
4
  "description": "Improve Pi prompt/KV cache hit rates with stable prompts, OpenAI-compatible cache keys, proxy compat warnings, and footer cache stats.",
5
5
  "keywords": [
6
6
  "pi-package",