@sayknow-cli/ai 0.3.16 → 0.4.1

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.
Files changed (214) hide show
  1. package/package.json +24 -25
  2. package/src/auth-gateway/server.ts +164 -22
  3. package/src/auth-storage.ts +76 -43
  4. package/src/context-cap-policy.ts +59 -0
  5. package/src/index.ts +2 -0
  6. package/src/model-cache.ts +10 -0
  7. package/src/model-manager.ts +30 -17
  8. package/src/model-thinking.ts +14 -8
  9. package/src/providers/amazon-bedrock.ts +9 -1
  10. package/src/providers/anthropic-messages-server.ts +178 -17
  11. package/src/providers/anthropic.ts +199 -213
  12. package/src/providers/azure-openai-responses.ts +6 -1
  13. package/src/providers/google-gemini-cli.ts +26 -13
  14. package/src/providers/google-shared.ts +7 -1
  15. package/src/providers/ollama.ts +7 -1
  16. package/src/providers/openai-chat-server.ts +93 -5
  17. package/src/providers/openai-codex/response-handler.ts +11 -3
  18. package/src/providers/openai-codex-responses.ts +107 -20
  19. package/src/providers/openai-responses-server.ts +111 -36
  20. package/src/providers/openai-responses-shared.ts +71 -12
  21. package/src/providers/openai-responses.ts +26 -3
  22. package/src/providers/pi-native-client.ts +24 -12
  23. package/src/providers/pi-native-server.ts +275 -21
  24. package/src/types.ts +53 -4
  25. package/src/utils/discovery/codex.ts +3 -12
  26. package/src/utils/event-stream.ts +146 -58
  27. package/src/utils/fallback-transport.ts +269 -0
  28. package/src/utils/overflow.ts +72 -31
  29. package/src/utils/retry.ts +2 -2
  30. package/src/utils/validation.ts +6 -1
  31. package/src/utils.ts +120 -12
  32. package/dist/types/api-registry.d.ts +0 -30
  33. package/dist/types/auth-broker/client.d.ts +0 -67
  34. package/dist/types/auth-broker/index.d.ts +0 -5
  35. package/dist/types/auth-broker/refresher.d.ts +0 -25
  36. package/dist/types/auth-broker/remote-store.d.ts +0 -99
  37. package/dist/types/auth-broker/server.d.ts +0 -32
  38. package/dist/types/auth-broker/types.d.ts +0 -110
  39. package/dist/types/auth-broker/wire-schemas.d.ts +0 -443
  40. package/dist/types/auth-gateway/http.d.ts +0 -40
  41. package/dist/types/auth-gateway/index.d.ts +0 -3
  42. package/dist/types/auth-gateway/server.d.ts +0 -17
  43. package/dist/types/auth-gateway/types.d.ts +0 -115
  44. package/dist/types/auth-storage.d.ts +0 -695
  45. package/dist/types/cli.d.ts +0 -2
  46. package/dist/types/index.d.ts +0 -51
  47. package/dist/types/model-cache.d.ts +0 -17
  48. package/dist/types/model-manager.d.ts +0 -62
  49. package/dist/types/model-retirements.d.ts +0 -6
  50. package/dist/types/model-thinking.d.ts +0 -74
  51. package/dist/types/models.d.ts +0 -21
  52. package/dist/types/provider-details.d.ts +0 -24
  53. package/dist/types/provider-models/bundled-references.d.ts +0 -4
  54. package/dist/types/provider-models/descriptors.d.ts +0 -48
  55. package/dist/types/provider-models/google.d.ts +0 -20
  56. package/dist/types/provider-models/index.d.ts +0 -5
  57. package/dist/types/provider-models/ollama.d.ts +0 -7
  58. package/dist/types/provider-models/openai-compat.d.ts +0 -256
  59. package/dist/types/provider-models/special.d.ts +0 -19
  60. package/dist/types/providers/amazon-bedrock.d.ts +0 -60
  61. package/dist/types/providers/anthropic-messages-server-schema.d.ts +0 -450
  62. package/dist/types/providers/anthropic-messages-server.d.ts +0 -17
  63. package/dist/types/providers/anthropic.d.ts +0 -208
  64. package/dist/types/providers/aws-credential-config.d.ts +0 -19
  65. package/dist/types/providers/aws-credentials.d.ts +0 -43
  66. package/dist/types/providers/aws-eventstream.d.ts +0 -38
  67. package/dist/types/providers/aws-sigv4.d.ts +0 -55
  68. package/dist/types/providers/azure-openai-responses.d.ts +0 -15
  69. package/dist/types/providers/composer-discipline.d.ts +0 -26
  70. package/dist/types/providers/cursor/client-version.d.ts +0 -10
  71. package/dist/types/providers/cursor/gen/agent_pb.d.ts +0 -13022
  72. package/dist/types/providers/cursor.d.ts +0 -45
  73. package/dist/types/providers/error-message.d.ts +0 -27
  74. package/dist/types/providers/github-copilot-headers.d.ts +0 -40
  75. package/dist/types/providers/gitlab-duo.d.ts +0 -27
  76. package/dist/types/providers/google-auth.d.ts +0 -24
  77. package/dist/types/providers/google-gemini-cli.d.ts +0 -75
  78. package/dist/types/providers/google-gemini-headers.d.ts +0 -43
  79. package/dist/types/providers/google-shared.d.ts +0 -179
  80. package/dist/types/providers/google-types.d.ts +0 -138
  81. package/dist/types/providers/google-vertex.d.ts +0 -7
  82. package/dist/types/providers/google.d.ts +0 -4
  83. package/dist/types/providers/grammar.d.ts +0 -1
  84. package/dist/types/providers/kimi.d.ts +0 -27
  85. package/dist/types/providers/mock.d.ts +0 -177
  86. package/dist/types/providers/ollama.d.ts +0 -41
  87. package/dist/types/providers/openai-anthropic-shim.d.ts +0 -31
  88. package/dist/types/providers/openai-bounded-rate-limits.d.ts +0 -3
  89. package/dist/types/providers/openai-chat-server-schema.d.ts +0 -815
  90. package/dist/types/providers/openai-chat-server.d.ts +0 -16
  91. package/dist/types/providers/openai-codex/constants.d.ts +0 -26
  92. package/dist/types/providers/openai-codex/request-transformer.d.ts +0 -50
  93. package/dist/types/providers/openai-codex/response-handler.d.ts +0 -17
  94. package/dist/types/providers/openai-codex-responses.d.ts +0 -67
  95. package/dist/types/providers/openai-completions-compat.d.ts +0 -27
  96. package/dist/types/providers/openai-completions.d.ts +0 -33
  97. package/dist/types/providers/openai-request-transform.d.ts +0 -4
  98. package/dist/types/providers/openai-responses-server-schema.d.ts +0 -392
  99. package/dist/types/providers/openai-responses-server.d.ts +0 -17
  100. package/dist/types/providers/openai-responses-shared.d.ts +0 -104
  101. package/dist/types/providers/openai-responses.d.ts +0 -32
  102. package/dist/types/providers/pi-native-client.d.ts +0 -13
  103. package/dist/types/providers/pi-native-server.d.ts +0 -68
  104. package/dist/types/providers/register-builtins.d.ts +0 -31
  105. package/dist/types/providers/synthetic.d.ts +0 -26
  106. package/dist/types/providers/transform-messages.d.ts +0 -14
  107. package/dist/types/providers/vision-guard.d.ts +0 -8
  108. package/dist/types/rate-limit-utils.d.ts +0 -19
  109. package/dist/types/stream.d.ts +0 -43
  110. package/dist/types/types.d.ts +0 -831
  111. package/dist/types/usage/claude.d.ts +0 -3
  112. package/dist/types/usage/gemini.d.ts +0 -2
  113. package/dist/types/usage/github-copilot.d.ts +0 -7
  114. package/dist/types/usage/google-antigravity.d.ts +0 -2
  115. package/dist/types/usage/grok-cli.d.ts +0 -10
  116. package/dist/types/usage/kimi.d.ts +0 -2
  117. package/dist/types/usage/minimax-code.d.ts +0 -2
  118. package/dist/types/usage/openai-codex.d.ts +0 -3
  119. package/dist/types/usage/shared.d.ts +0 -1
  120. package/dist/types/usage/zai.d.ts +0 -2
  121. package/dist/types/usage.d.ts +0 -258
  122. package/dist/types/utils/abort.d.ts +0 -19
  123. package/dist/types/utils/anthropic-auth.d.ts +0 -31
  124. package/dist/types/utils/discovery/antigravity.d.ts +0 -67
  125. package/dist/types/utils/discovery/codex.d.ts +0 -38
  126. package/dist/types/utils/discovery/cursor.d.ts +0 -23
  127. package/dist/types/utils/discovery/gemini.d.ts +0 -25
  128. package/dist/types/utils/discovery/index.d.ts +0 -4
  129. package/dist/types/utils/discovery/openai-compatible.d.ts +0 -74
  130. package/dist/types/utils/event-stream.d.ts +0 -33
  131. package/dist/types/utils/fireworks-model-id.d.ts +0 -10
  132. package/dist/types/utils/foundry.d.ts +0 -1
  133. package/dist/types/utils/h2-fetch.d.ts +0 -22
  134. package/dist/types/utils/http-inspector.d.ts +0 -35
  135. package/dist/types/utils/idle-iterator.d.ts +0 -67
  136. package/dist/types/utils/json-parse.d.ts +0 -18
  137. package/dist/types/utils/oauth/alibaba-coding-plan.d.ts +0 -18
  138. package/dist/types/utils/oauth/anthropic.d.ts +0 -22
  139. package/dist/types/utils/oauth/api-key-login.d.ts +0 -35
  140. package/dist/types/utils/oauth/api-key-validation.d.ts +0 -27
  141. package/dist/types/utils/oauth/callback-server.d.ts +0 -60
  142. package/dist/types/utils/oauth/cerebras.d.ts +0 -1
  143. package/dist/types/utils/oauth/cloudflare-ai-gateway.d.ts +0 -18
  144. package/dist/types/utils/oauth/cursor.d.ts +0 -15
  145. package/dist/types/utils/oauth/deepinfra.d.ts +0 -1
  146. package/dist/types/utils/oauth/deepseek.d.ts +0 -10
  147. package/dist/types/utils/oauth/firepass.d.ts +0 -1
  148. package/dist/types/utils/oauth/fireworks.d.ts +0 -1
  149. package/dist/types/utils/oauth/fugu.d.ts +0 -1
  150. package/dist/types/utils/oauth/github-copilot.d.ts +0 -38
  151. package/dist/types/utils/oauth/gitlab-duo.d.ts +0 -3
  152. package/dist/types/utils/oauth/glm-zcode.d.ts +0 -71
  153. package/dist/types/utils/oauth/google-antigravity.d.ts +0 -11
  154. package/dist/types/utils/oauth/google-gemini-cli.d.ts +0 -10
  155. package/dist/types/utils/oauth/google-oauth-shared.d.ts +0 -28
  156. package/dist/types/utils/oauth/huggingface.d.ts +0 -19
  157. package/dist/types/utils/oauth/index.d.ts +0 -39
  158. package/dist/types/utils/oauth/kagi.d.ts +0 -17
  159. package/dist/types/utils/oauth/kilo.d.ts +0 -5
  160. package/dist/types/utils/oauth/kimi.d.ts +0 -21
  161. package/dist/types/utils/oauth/litellm.d.ts +0 -18
  162. package/dist/types/utils/oauth/lm-studio.d.ts +0 -17
  163. package/dist/types/utils/oauth/minimax-code.d.ts +0 -28
  164. package/dist/types/utils/oauth/moonshot.d.ts +0 -1
  165. package/dist/types/utils/oauth/nanogpt.d.ts +0 -1
  166. package/dist/types/utils/oauth/nvidia.d.ts +0 -18
  167. package/dist/types/utils/oauth/ollama-cloud.d.ts +0 -2
  168. package/dist/types/utils/oauth/ollama.d.ts +0 -18
  169. package/dist/types/utils/oauth/openai-codex.d.ts +0 -21
  170. package/dist/types/utils/oauth/opencode.d.ts +0 -18
  171. package/dist/types/utils/oauth/parallel.d.ts +0 -17
  172. package/dist/types/utils/oauth/perplexity.d.ts +0 -9
  173. package/dist/types/utils/oauth/pkce.d.ts +0 -8
  174. package/dist/types/utils/oauth/qianfan.d.ts +0 -17
  175. package/dist/types/utils/oauth/qwen-portal.d.ts +0 -19
  176. package/dist/types/utils/oauth/synthetic.d.ts +0 -1
  177. package/dist/types/utils/oauth/tavily.d.ts +0 -17
  178. package/dist/types/utils/oauth/together.d.ts +0 -1
  179. package/dist/types/utils/oauth/types.d.ts +0 -45
  180. package/dist/types/utils/oauth/venice.d.ts +0 -18
  181. package/dist/types/utils/oauth/vercel-ai-gateway.d.ts +0 -18
  182. package/dist/types/utils/oauth/vllm.d.ts +0 -16
  183. package/dist/types/utils/oauth/xai.d.ts +0 -30
  184. package/dist/types/utils/oauth/xiaomi.d.ts +0 -25
  185. package/dist/types/utils/oauth/zai.d.ts +0 -18
  186. package/dist/types/utils/oauth/zenmux.d.ts +0 -1
  187. package/dist/types/utils/overflow.d.ts +0 -64
  188. package/dist/types/utils/parse-bind.d.ts +0 -23
  189. package/dist/types/utils/provider-response.d.ts +0 -3
  190. package/dist/types/utils/retry-after.d.ts +0 -3
  191. package/dist/types/utils/retry-budget.d.ts +0 -1
  192. package/dist/types/utils/retry.d.ts +0 -26
  193. package/dist/types/utils/schema/adapt.d.ts +0 -24
  194. package/dist/types/utils/schema/compatibility.d.ts +0 -30
  195. package/dist/types/utils/schema/dereference.d.ts +0 -11
  196. package/dist/types/utils/schema/draft.d.ts +0 -10
  197. package/dist/types/utils/schema/equality.d.ts +0 -4
  198. package/dist/types/utils/schema/fields.d.ts +0 -49
  199. package/dist/types/utils/schema/index.d.ts +0 -14
  200. package/dist/types/utils/schema/json-schema-validator.d.ts +0 -12
  201. package/dist/types/utils/schema/meta-validator.d.ts +0 -2
  202. package/dist/types/utils/schema/normalize.d.ts +0 -93
  203. package/dist/types/utils/schema/root-combinator.d.ts +0 -12
  204. package/dist/types/utils/schema/spill.d.ts +0 -8
  205. package/dist/types/utils/schema/stamps.d.ts +0 -25
  206. package/dist/types/utils/schema/types.d.ts +0 -4
  207. package/dist/types/utils/schema/wire.d.ts +0 -54
  208. package/dist/types/utils/schema/zod-decontaminate.d.ts +0 -31
  209. package/dist/types/utils/sse-debug.d.ts +0 -10
  210. package/dist/types/utils/tool-call-healing.d.ts +0 -71
  211. package/dist/types/utils/tool-choice-capability.d.ts +0 -41
  212. package/dist/types/utils/tool-choice.d.ts +0 -50
  213. package/dist/types/utils/validation.d.ts +0 -17
  214. package/dist/types/utils.d.ts +0 -35
@@ -66,6 +66,16 @@ function getDb(dbPath?: string): Database {
66
66
  return db;
67
67
  }
68
68
 
69
+ /** Close the shared cache only when it owns the exact requested database path. */
70
+ export function closeModelCache(dbPath?: string): boolean {
71
+ const resolvedPath = dbPath ?? getModelDbPath();
72
+ if (!sharedDb || sharedDbPath !== resolvedPath) return false;
73
+ sharedDb.close();
74
+ sharedDb = null;
75
+ sharedDbPath = null;
76
+ return true;
77
+ }
78
+
69
79
  function migrateCacheSchema(db: Database): void {
70
80
  const columns = db.prepare("PRAGMA table_info(model_cache)").all() as TableInfoRow[];
71
81
  if (!columns.some(column => column.name === "static_fingerprint")) {
@@ -1,3 +1,4 @@
1
+ import { applyFinalCodexGpt56ContextCap } from "./context-cap-policy";
1
2
  import { readModelCache, writeModelCache } from "./model-cache";
2
3
  import { isRetiredModel, isRetiredModelKey } from "./model-retirements";
3
4
  import { applyGeneratedModelPolicies, enrichModelThinking } from "./model-thinking";
@@ -40,6 +41,8 @@ export interface ModelManagerOptions<TApi extends Api = Api, TModelsDevPayload =
40
41
  modelsDev?: ModelsDevFallback<TApi, TModelsDevPayload>;
41
42
  /** Clock override for deterministic tests. */
42
43
  now?: () => number;
44
+ /** Optional guard that must permit cache publication. Default: writes are permitted. */
45
+ canPublishCache?: () => boolean;
43
46
  }
44
47
 
45
48
  /**
@@ -98,7 +101,7 @@ function passModelList<TApi extends Api>(value: unknown): Model<TApi>[] {
98
101
  out.push(enrichModelThinking(item as Model<TApi>));
99
102
  }
100
103
  applyGeneratedModelPolicies(out as Model<Api>[]);
101
- return out;
104
+ return applyFinalCodexGpt56ContextCap(out);
102
105
  }
103
106
 
104
107
  /**
@@ -147,7 +150,9 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
147
150
  return { models: cachedModels, stale: false };
148
151
  }
149
152
  const repairedModels = mergeDynamicModels(staticModels, cachedModels);
150
- writeModelCache(options.providerId, now(), repairedModels, true, staticFingerprint, dbPath);
153
+ if (options.canPublishCache?.() ?? true) {
154
+ writeModelCache(options.providerId, now(), repairedModels, true, staticFingerprint, dbPath);
155
+ }
151
156
  return { models: repairedModels, stale: false };
152
157
  }
153
158
 
@@ -161,27 +166,35 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
161
166
  const cacheModels = dynamicFetchSucceeded ? [] : normalizeModelList<TApi>(cache?.models ?? []);
162
167
  const dynamicModels = fetchedDynamicModels ?? [];
163
168
  const mergedWithCache = mergeDynamicModels(mergeModelSources(staticModels, modelsDevModels), cacheModels);
164
- const models = mergeDynamicModels(mergedWithCache, dynamicModels);
169
+ const models = applyFinalCodexGpt56ContextCap(mergeDynamicModels(mergedWithCache, dynamicModels));
165
170
  const dynamicAuthoritative = !hasDynamicFetcher || dynamicFetchSucceeded || shouldUseFreshCacheAsAuthoritative;
166
171
  if (shouldFetchFromNetwork) {
167
172
  if (dynamicFetchSucceeded) {
168
- const snapshotModels = mergeDynamicModels(mergeModelSources(staticModels, modelsDevModels), dynamicModels);
169
- writeModelCache(options.providerId, now(), snapshotModels, true, staticFingerprint, dbPath);
173
+ const snapshotModels = applyFinalCodexGpt56ContextCap(
174
+ mergeDynamicModels(mergeModelSources(staticModels, modelsDevModels), dynamicModels),
175
+ );
176
+ if (options.canPublishCache?.() ?? true) {
177
+ writeModelCache(options.providerId, now(), snapshotModels, true, staticFingerprint, dbPath);
178
+ }
170
179
  } else {
171
180
  // Dynamic fetch failed — update cache with a non-authoritative snapshot so
172
181
  // stale state remains visible while retry backoff still applies.
173
182
  const latestCache = readModelCache<TApi>(options.providerId, ttlMs, now, dbPath);
174
- writeModelCache(
175
- options.providerId,
176
- now(),
177
- mergeDynamicModels(
178
- mergeModelSources(staticModels, modelsDevModels),
179
- normalizeModelList<TApi>(latestCache?.models ?? cache?.models ?? []),
180
- ),
181
- false,
182
- staticFingerprint,
183
- dbPath,
184
- );
183
+ if (options.canPublishCache?.() ?? true) {
184
+ writeModelCache(
185
+ options.providerId,
186
+ now(),
187
+ applyFinalCodexGpt56ContextCap(
188
+ mergeDynamicModels(
189
+ mergeModelSources(staticModels, modelsDevModels),
190
+ normalizeModelList<TApi>(latestCache?.models ?? cache?.models ?? []),
191
+ ),
192
+ ),
193
+ false,
194
+ staticFingerprint,
195
+ dbPath,
196
+ );
197
+ }
185
198
  }
186
199
  }
187
200
  return {
@@ -393,7 +406,7 @@ function normalizeModelList<TApi extends Api>(value: unknown): Model<TApi>[] {
393
406
  models.push(enrichModelThinking(item as Model<TApi>));
394
407
  }
395
408
  }
396
- return models;
409
+ return applyFinalCodexGpt56ContextCap(models);
397
410
  }
398
411
 
399
412
  function isModelLike(value: unknown): value is Model<Api> {
@@ -1,3 +1,4 @@
1
+ import { CODEX_GPT_5_6_CONTEXT_CAP, isCodexGpt56Tier, isCodexProductTransport } from "./context-cap-policy";
1
2
  import { resolveOpenAICompat } from "./providers/openai-completions-compat";
2
3
  import type { Api, Model as ApiModel, ThinkingConfig } from "./types";
3
4
  import { isClaudeForcedToolChoiceIncapableModelId } from "./utils/tool-choice-capability";
@@ -49,6 +50,7 @@ const GEMINI_3_FLASH_EFFORTS: readonly Effort[] = [Effort.Minimal, Effort.Low, E
49
50
  const GPT_5_2_PLUS_EFFORTS: readonly Effort[] = [Effort.Low, Effort.Medium, Effort.High, Effort.XHigh];
50
51
  const GPT_5_6_PLUS_EFFORTS: readonly Effort[] = [Effort.Low, Effort.Medium, Effort.High, Effort.XHigh, Effort.Max];
51
52
  const GPT_5_5_DEFAULT_EFFORT = Effort.XHigh;
53
+ const KIMI_K3_EFFORTS: readonly Effort[] = [Effort.Low, Effort.High, Effort.Max];
52
54
 
53
55
  const GPT_5_1_CODEX_MINI_EFFORTS: readonly Effort[] = [Effort.Medium, Effort.High];
54
56
  const CLOUDFLARE_AI_GATEWAY_BASE_URL = "https://gateway.ai.cloudflare.com/v1/<account>/<gateway>/anthropic";
@@ -477,15 +479,13 @@ function applyGpt55ContextWindow(model: ApiModel<Api>, parsedModel: OpenAIModel)
477
479
  }
478
480
  return false;
479
481
  }
480
- const GPT_5_6_TIER_VARIANTS: ReadonlySet<OpenAIVariant> = new Set(["base", "sol", "terra", "luna"]);
481
-
482
- function applyGpt56ContextWindow(model: ApiModel<Api>, parsedModel: OpenAIModel): boolean {
483
- if (!semverGte(parsedModel.version, "5.6") || !GPT_5_6_TIER_VARIANTS.has(parsedModel.variant)) {
482
+ function applyGpt56ContextWindow(model: ApiModel<Api>): boolean {
483
+ if (!isCodexGpt56Tier(model) || !isCodexProductTransport(model)) {
484
484
  return false;
485
485
  }
486
- // GPT-5.6 tiers enforce a ~373K usable prompt budget on both transports
487
- // (matches the openai-codex live catalog), despite the 1M+ marketing window.
488
- model.contextWindow = 373_000;
486
+ // Codex product metadata is bounded by the currently enforced prompt cap.
487
+ // Smaller observed limits remain authoritative; first-party OpenAI is untouched.
488
+ model.contextWindow = Math.min(model.contextWindow, CODEX_GPT_5_6_CONTEXT_CAP.ceiling);
489
489
  return true;
490
490
  }
491
491
 
@@ -493,7 +493,7 @@ function applyOpenAICatalogPolicy(model: ApiModel<Api>, parsedModel: OpenAIModel
493
493
  if (applyGpt55ContextWindow(model, parsedModel)) {
494
494
  return;
495
495
  }
496
- if (applyGpt56ContextWindow(model, parsedModel)) {
496
+ if (applyGpt56ContextWindow(model)) {
497
497
  return;
498
498
  }
499
499
  // OpenAI code backend models: 400K figure includes output budget; input window is 272K.
@@ -517,6 +517,9 @@ function applyOpenAICatalogPolicy(model: ApiModel<Api>, parsedModel: OpenAIModel
517
517
  }
518
518
 
519
519
  function inferDefaultEffort<TApi extends Api>(model: ApiModel<TApi>, parsedModel: ParsedModel): Effort | undefined {
520
+ if (model.provider === "kimi-code" && model.id === "k3") {
521
+ return Effort.High;
522
+ }
520
523
  if (
521
524
  parsedModel.family === "openai" &&
522
525
  model.provider === "openai-codex" &&
@@ -592,6 +595,9 @@ function expandEffortRange(thinking: ThinkingConfig): readonly Effort[] {
592
595
  }
593
596
 
594
597
  function inferSupportedEfforts<TApi extends Api>(parsedModel: ParsedModel, model: ApiModel<TApi>): readonly Effort[] {
598
+ if (model.provider === "kimi-code" && model.id === "k3") {
599
+ return KIMI_K3_EFFORTS;
600
+ }
595
601
  switch (parsedModel.family) {
596
602
  case "openai":
597
603
  return inferOpenAISupportedEfforts(parsedModel);
@@ -30,6 +30,7 @@ import type {
30
30
  } from "../types";
31
31
  import { normalizeToolCallId, resolveCacheRetention, sanitizeJsonStrings } from "../utils";
32
32
  import { AssistantMessageEventStream } from "../utils/event-stream";
33
+ import { transportFailureFacts } from "../utils/fallback-transport";
33
34
  import { appendRawHttpRequestDumpFor400, type RawHttpRequestDump, withHttpStatus } from "../utils/http-inspector";
34
35
  import { parseStreamingJson } from "../utils/json-parse";
35
36
  import { resolveRetryBudget } from "../utils/retry-budget";
@@ -317,7 +318,12 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream"> = (
317
318
  new Error(`Bedrock HTTP ${response.status}: ${errBody.slice(0, 1000)}`),
318
319
  response.status,
319
320
  );
320
- if (firstTokenTime === undefined && !fallbackRan && isForcedToolChoiceUnsupportedError(error, true)) {
321
+ if (
322
+ firstTokenTime === undefined &&
323
+ !fallbackRan &&
324
+ !options.fallbackManaged &&
325
+ isForcedToolChoiceUnsupportedError(error, true)
326
+ ) {
321
327
  response = await retryWithoutForcedToolChoice(error.message);
322
328
  } else {
323
329
  throw error;
@@ -349,6 +355,7 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream"> = (
349
355
  firstTokenTime === undefined &&
350
356
  sentForcedToolChoice &&
351
357
  !fallbackRan &&
358
+ !options.fallbackManaged &&
352
359
  isForcedToolChoiceUnsupportedError(error, true)
353
360
  ) {
354
361
  response = await retryWithoutForcedToolChoice(error.message);
@@ -432,6 +439,7 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream"> = (
432
439
  }
433
440
  output.stopReason = options.signal?.aborted ? "aborted" : "error";
434
441
  output.errorStatus = extractHttpStatusFromError(error);
442
+ output.transportFailure = transportFailureFacts(error);
435
443
  const baseMessage = error instanceof Error ? error.message : JSON.stringify(error);
436
444
  // Enrich error with thinking block diagnostics for signature-related failures
437
445
  let diagnostics = "";
@@ -417,6 +417,62 @@ function mapStopReasonOut(reason: StopReason): "end_turn" | "max_tokens" | "tool
417
417
  }
418
418
  }
419
419
 
420
+ /**
421
+ * True when `signature` is one of OUR serialized OpenAI Responses reasoning-item
422
+ * envelopes (produced by the Responses/Codex decoders as
423
+ * `JSON.stringify(reasoningItem)` with `type: "reasoning"`). Such an envelope is
424
+ * NOT a valid Anthropic thinking signature and may embed raw chain-of-thought in
425
+ * `content[]`, so it must never be forwarded on Anthropic egress. Genuine opaque
426
+ * Anthropic signatures and any non-envelope string return false (fail safe).
427
+ */
428
+ function isSerializedResponsesReasoningItem(signature: string): boolean {
429
+ try {
430
+ const parsed: unknown = JSON.parse(signature);
431
+ return (
432
+ typeof parsed === "object" &&
433
+ parsed !== null &&
434
+ !Array.isArray(parsed) &&
435
+ (parsed as { type?: unknown }).type === "reasoning"
436
+ );
437
+ } catch {
438
+ return false;
439
+ }
440
+ }
441
+
442
+ /**
443
+ * A thinking-block signature safe to emit on Anthropic egress: cross-protocol
444
+ * serialized Responses reasoning envelopes are omitted (they can carry raw CoT);
445
+ * genuine opaque Anthropic signatures round-trip unchanged.
446
+ */
447
+ function safeThinkingSignature(signature: string | undefined): string | undefined {
448
+ if (!signature) return undefined;
449
+ if (isSerializedResponsesReasoningItem(signature)) return undefined;
450
+ return signature;
451
+ }
452
+
453
+ function isResponsesFamilyApi(api: AssistantMessage["api"]): boolean {
454
+ return api === "openai-responses" || api === "openai-codex-responses";
455
+ }
456
+
457
+ function safeThinkingText(content: ThinkingContent, api: AssistantMessage["api"]): string | undefined {
458
+ if (isResponsesFamilyApi(api) && content.provenance === undefined) return undefined;
459
+ if (content.provenance === "raw") return undefined;
460
+ if (content.provenance === "mixed") return content.summaryText;
461
+ if (content.provenance === "summary") return content.summaryText ?? content.thinking;
462
+ return content.thinking;
463
+ }
464
+
465
+ function hasRawOrMixedThinking(partial: AssistantMessage, contentIndex: number): boolean {
466
+ const content = partial.content[contentIndex];
467
+ return content?.type === "thinking" && (content.provenance === "raw" || content.provenance === "mixed");
468
+ }
469
+
470
+ /** Responses-family reasoning is untrusted until output_item.done assigns provenance. */
471
+ function hasUnfinalizedResponsesThinking(partial: AssistantMessage, contentIndex: number): boolean {
472
+ const content = partial.content[contentIndex];
473
+ return content?.type !== "thinking" || (isResponsesFamilyApi(partial.api) && content.provenance === undefined);
474
+ }
475
+
420
476
  function encodeContentBlocks(message: AssistantMessage): Record<string, unknown>[] {
421
477
  const blocks: Record<string, unknown>[] = [];
422
478
  for (const c of message.content) {
@@ -425,8 +481,11 @@ function encodeContentBlocks(message: AssistantMessage): Record<string, unknown>
425
481
  blocks.push({ type: "text", text: c.text });
426
482
  break;
427
483
  case "thinking": {
428
- const b: Record<string, unknown> = { type: "thinking", thinking: c.thinking };
429
- if (c.thinkingSignature) b.signature = c.thinkingSignature;
484
+ const thinking = safeThinkingText(c, message.api);
485
+ if (thinking === undefined) break;
486
+ const b: Record<string, unknown> = { type: "thinking", thinking };
487
+ const sig = safeThinkingSignature(c.thinkingSignature);
488
+ if (sig) b.signature = sig;
430
489
  blocks.push(b);
431
490
  break;
432
491
  }
@@ -495,6 +554,12 @@ export function encodeStream(
495
554
  const messageId = newMessageId();
496
555
  let started = false;
497
556
  const open = new Map<number, OpenBlock>();
557
+ // contentIndexes that already streamed a reasoning summary delta, so a
558
+ // final-only reasoning_summary_end does not duplicate streamed summary text.
559
+ const summaryDeltaSeen = new Set<number>();
560
+ // Responses assigns reasoning provenance only at output_item.done. Keep its
561
+ // pre-classification bytes out of this public compatibility stream.
562
+ const pendingThinkingDeltas = new Map<number, string[]>();
498
563
 
499
564
  const ensureStart = (partial: AssistantMessage) => {
500
565
  if (started) return;
@@ -524,6 +589,30 @@ export function encodeStream(
524
589
  open.delete(index);
525
590
  };
526
591
 
592
+ const openThinking = (partial: AssistantMessage, index: number) => {
593
+ if (open.has(index)) return;
594
+ ensureStart(partial);
595
+ open.set(index, { index, kind: "thinking" });
596
+ controller.enqueue(
597
+ sseFrame("content_block_start", {
598
+ type: "content_block_start",
599
+ index,
600
+ content_block: { type: "thinking", thinking: "" },
601
+ }),
602
+ );
603
+ };
604
+
605
+ const writeThinkingDelta = (index: number, thinking: string) => {
606
+ if (thinking.length === 0) return;
607
+ controller.enqueue(
608
+ sseFrame("content_block_delta", {
609
+ type: "content_block_delta",
610
+ index,
611
+ delta: { type: "thinking_delta", thinking },
612
+ }),
613
+ );
614
+ };
615
+
527
616
  try {
528
617
  for await (const ev of events) {
529
618
  switch (ev.type) {
@@ -555,18 +644,73 @@ export function encodeStream(
555
644
  closeBlock(ev.contentIndex);
556
645
  break;
557
646
  case "thinking_start": {
558
- ensureStart(ev.partial);
559
- open.set(ev.contentIndex, { index: ev.contentIndex, kind: "thinking" });
560
- controller.enqueue(
561
- sseFrame("content_block_start", {
562
- type: "content_block_start",
563
- index: ev.contentIndex,
564
- content_block: { type: "thinking", thinking: "" },
565
- }),
566
- );
647
+ if (hasRawOrMixedThinking(ev.partial, ev.contentIndex)) break;
648
+ if (hasUnfinalizedResponsesThinking(ev.partial, ev.contentIndex)) {
649
+ pendingThinkingDeltas.set(ev.contentIndex, []);
650
+ break;
651
+ }
652
+ openThinking(ev.partial, ev.contentIndex);
653
+ break;
654
+ }
655
+ case "thinking_delta": {
656
+ if (hasRawOrMixedThinking(ev.partial, ev.contentIndex)) break;
657
+ if (hasUnfinalizedResponsesThinking(ev.partial, ev.contentIndex)) {
658
+ const deltas = pendingThinkingDeltas.get(ev.contentIndex) ?? [];
659
+ deltas.push(ev.delta);
660
+ pendingThinkingDeltas.set(ev.contentIndex, deltas);
661
+ break;
662
+ }
663
+ writeThinkingDelta(ev.contentIndex, ev.delta);
664
+ break;
665
+ }
666
+ case "thinking_end": {
667
+ const unfinalized = hasUnfinalizedResponsesThinking(ev.partial, ev.contentIndex);
668
+ const rawOrMixed = hasRawOrMixedThinking(ev.partial, ev.contentIndex);
669
+ const pending = pendingThinkingDeltas.get(ev.contentIndex);
670
+ pendingThinkingDeltas.delete(ev.contentIndex);
671
+ if (rawOrMixed || unfinalized) {
672
+ closeBlock(ev.contentIndex);
673
+ break;
674
+ }
675
+ if (pending) {
676
+ openThinking(ev.partial, ev.contentIndex);
677
+ for (const delta of pending) writeThinkingDelta(ev.contentIndex, delta);
678
+ }
679
+ const c = ev.partial.content[ev.contentIndex];
680
+ const sig = c?.type === "thinking" ? safeThinkingSignature(c.thinkingSignature) : undefined;
681
+ if (sig) {
682
+ controller.enqueue(
683
+ sseFrame("content_block_delta", {
684
+ type: "content_block_delta",
685
+ index: ev.contentIndex,
686
+ delta: { type: "signature_delta", signature: sig },
687
+ }),
688
+ );
689
+ }
690
+ closeBlock(ev.contentIndex);
691
+ break;
692
+ }
693
+ case "reasoning_summary_start": {
694
+ // Provider-displayable summary reasoning surfaces as this format's
695
+ // native thinking channel. Open the thinking block if a thinking_start
696
+ // did not already (summary-only streams emit no thinking_start).
697
+ if (!open.has(ev.contentIndex)) {
698
+ ensureStart(ev.partial);
699
+ open.set(ev.contentIndex, { index: ev.contentIndex, kind: "thinking" });
700
+ controller.enqueue(
701
+ sseFrame("content_block_start", {
702
+ type: "content_block_start",
703
+ index: ev.contentIndex,
704
+ content_block: { type: "thinking", thinking: "" },
705
+ }),
706
+ );
707
+ }
567
708
  break;
568
709
  }
569
- case "thinking_delta":
710
+ case "reasoning_summary_delta":
711
+ // Only a non-whitespace delta counts as a delivered summary; a bare
712
+ // separator ("\n\n") must not suppress a later final-only end content.
713
+ if (ev.delta.trim().length > 0) summaryDeltaSeen.add(ev.contentIndex);
570
714
  controller.enqueue(
571
715
  sseFrame("content_block_delta", {
572
716
  type: "content_block_delta",
@@ -575,18 +719,31 @@ export function encodeStream(
575
719
  }),
576
720
  );
577
721
  break;
578
- case "thinking_end": {
579
- const c = ev.partial.content[ev.contentIndex];
580
- if (c?.type === "thinking" && c.thinkingSignature) {
722
+ case "reasoning_summary_end": {
723
+ // Final-only summary: text arrives only on the end event with no prior
724
+ // deltas. Ensure the thinking block is open, then surface the content as
725
+ // a thinking_delta (skip when deltas already streamed to avoid dup). The
726
+ // thinking block is closed by the subsequent thinking_end.
727
+ if (ev.content.length > 0 && !summaryDeltaSeen.has(ev.contentIndex)) {
728
+ if (!open.has(ev.contentIndex)) {
729
+ ensureStart(ev.partial);
730
+ open.set(ev.contentIndex, { index: ev.contentIndex, kind: "thinking" });
731
+ controller.enqueue(
732
+ sseFrame("content_block_start", {
733
+ type: "content_block_start",
734
+ index: ev.contentIndex,
735
+ content_block: { type: "thinking", thinking: "" },
736
+ }),
737
+ );
738
+ }
581
739
  controller.enqueue(
582
740
  sseFrame("content_block_delta", {
583
741
  type: "content_block_delta",
584
742
  index: ev.contentIndex,
585
- delta: { type: "signature_delta", signature: c.thinkingSignature },
743
+ delta: { type: "thinking_delta", thinking: ev.content },
586
744
  }),
587
745
  );
588
746
  }
589
- closeBlock(ev.contentIndex);
590
747
  break;
591
748
  }
592
749
  case "toolcall_start": {
@@ -621,6 +778,7 @@ export function encodeStream(
621
778
  break;
622
779
  case "done": {
623
780
  for (const idx of [...open.keys()]) closeBlock(idx);
781
+ pendingThinkingDeltas.clear();
624
782
  controller.enqueue(
625
783
  sseFrame("message_delta", {
626
784
  type: "message_delta",
@@ -636,6 +794,7 @@ export function encodeStream(
636
794
  }
637
795
  case "error": {
638
796
  const msg = ev.error.errorMessage ?? "stream error";
797
+ pendingThinkingDeltas.clear();
639
798
  controller.enqueue(
640
799
  sseFrame("error", { type: "error", error: { type: "api_error", message: msg } }),
641
800
  );
@@ -645,10 +804,12 @@ export function encodeStream(
645
804
  }
646
805
  }
647
806
  // stream ended without explicit done; close gracefully
807
+ pendingThinkingDeltas.clear();
648
808
  for (const idx of [...open.keys()]) closeBlock(idx);
649
809
  controller.enqueue(sseFrame("message_stop", { type: "message_stop" }));
650
810
  controller.close();
651
811
  } catch (err) {
812
+ pendingThinkingDeltas.clear();
652
813
  controller.enqueue(
653
814
  sseFrame("error", {
654
815
  type: "error",