@fgv/ts-extras 5.1.0-55 → 5.1.0-57

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 (184) hide show
  1. package/CAPABILITIES.md +96 -0
  2. package/dist/packlets/ai-assist/cacheRequest.js +118 -0
  3. package/dist/packlets/ai-assist/cacheRequest.js.map +1 -0
  4. package/dist/packlets/ai-assist/chatRequestBuilders.js +72 -7
  5. package/dist/packlets/ai-assist/chatRequestBuilders.js.map +1 -1
  6. package/dist/packlets/ai-assist/completionClient.js +195 -58
  7. package/dist/packlets/ai-assist/completionClient.js.map +1 -1
  8. package/dist/packlets/ai-assist/converters.js +54 -0
  9. package/dist/packlets/ai-assist/converters.js.map +1 -1
  10. package/dist/packlets/ai-assist/embeddingClient.js +16 -1
  11. package/dist/packlets/ai-assist/embeddingClient.js.map +1 -1
  12. package/dist/packlets/ai-assist/imageGenerationClient.js +17 -2
  13. package/dist/packlets/ai-assist/imageGenerationClient.js.map +1 -1
  14. package/dist/packlets/ai-assist/index.js +3 -1
  15. package/dist/packlets/ai-assist/index.js.map +1 -1
  16. package/dist/packlets/ai-assist/listModelsClient.js +19 -1
  17. package/dist/packlets/ai-assist/listModelsClient.js.map +1 -1
  18. package/dist/packlets/ai-assist/model.js.map +1 -1
  19. package/dist/packlets/ai-assist/promptCacheAnthropicBreakpointHarness.js +200 -0
  20. package/dist/packlets/ai-assist/promptCacheAnthropicBreakpointHarness.js.map +1 -0
  21. package/dist/packlets/ai-assist/promptCacheObservabilityHarness.js +137 -0
  22. package/dist/packlets/ai-assist/promptCacheObservabilityHarness.js.map +1 -0
  23. package/dist/packlets/ai-assist/promptCacheRoutingAbHarness.js +156 -0
  24. package/dist/packlets/ai-assist/promptCacheRoutingAbHarness.js.map +1 -0
  25. package/dist/packlets/ai-assist/promptCacheUsageMath.js +124 -0
  26. package/dist/packlets/ai-assist/promptCacheUsageMath.js.map +1 -0
  27. package/dist/packlets/ai-assist/registry.js +9 -11
  28. package/dist/packlets/ai-assist/registry.js.map +1 -1
  29. package/dist/packlets/ai-assist/streamUsageCapability.js +134 -0
  30. package/dist/packlets/ai-assist/streamUsageCapability.js.map +1 -0
  31. package/dist/packlets/ai-assist/streamingAdapters/anthropic.js +32 -9
  32. package/dist/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -1
  33. package/dist/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js +81 -35
  34. package/dist/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js.map +1 -1
  35. package/dist/packlets/ai-assist/streamingAdapters/common.js +25 -1
  36. package/dist/packlets/ai-assist/streamingAdapters/common.js.map +1 -1
  37. package/dist/packlets/ai-assist/streamingAdapters/gemini.js +12 -5
  38. package/dist/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -1
  39. package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js +45 -11
  40. package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -1
  41. package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js +33 -22
  42. package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -1
  43. package/dist/packlets/ai-assist/streamingAdapters/proxy.js +62 -4
  44. package/dist/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -1
  45. package/dist/packlets/ai-assist/streamingClient.js +20 -5
  46. package/dist/packlets/ai-assist/streamingClient.js.map +1 -1
  47. package/dist/packlets/ai-assist/thinkingOptionsResolver.js +23 -5
  48. package/dist/packlets/ai-assist/thinkingOptionsResolver.js.map +1 -1
  49. package/dist/packlets/ai-assist/toolFormats.js +80 -0
  50. package/dist/packlets/ai-assist/toolFormats.js.map +1 -1
  51. package/dist/packlets/ai-assist/toolTypes.js +21 -0
  52. package/dist/packlets/ai-assist/toolTypes.js.map +1 -0
  53. package/dist/packlets/ai-assist/usageNormalization.js +178 -0
  54. package/dist/packlets/ai-assist/usageNormalization.js.map +1 -0
  55. package/dist/packlets/ai-assist/usageTypes.js +21 -0
  56. package/dist/packlets/ai-assist/usageTypes.js.map +1 -0
  57. package/dist/packlets/mustache/index.js.map +1 -1
  58. package/dist/packlets/mustache/interfaces.js.map +1 -1
  59. package/dist/packlets/mustache/mustacheTemplate.js +91 -0
  60. package/dist/packlets/mustache/mustacheTemplate.js.map +1 -1
  61. package/dist/ts-extras.d.ts +764 -48
  62. package/lib/packlets/ai-assist/cacheRequest.d.ts +100 -0
  63. package/lib/packlets/ai-assist/cacheRequest.d.ts.map +1 -0
  64. package/lib/packlets/ai-assist/cacheRequest.js +123 -0
  65. package/lib/packlets/ai-assist/cacheRequest.js.map +1 -0
  66. package/lib/packlets/ai-assist/chatRequestBuilders.d.ts +36 -6
  67. package/lib/packlets/ai-assist/chatRequestBuilders.d.ts.map +1 -1
  68. package/lib/packlets/ai-assist/chatRequestBuilders.js +75 -7
  69. package/lib/packlets/ai-assist/chatRequestBuilders.js.map +1 -1
  70. package/lib/packlets/ai-assist/completionClient.d.ts +19 -0
  71. package/lib/packlets/ai-assist/completionClient.d.ts.map +1 -1
  72. package/lib/packlets/ai-assist/completionClient.js +194 -57
  73. package/lib/packlets/ai-assist/completionClient.js.map +1 -1
  74. package/lib/packlets/ai-assist/converters.d.ts +46 -0
  75. package/lib/packlets/ai-assist/converters.d.ts.map +1 -1
  76. package/lib/packlets/ai-assist/converters.js +55 -1
  77. package/lib/packlets/ai-assist/converters.js.map +1 -1
  78. package/lib/packlets/ai-assist/embeddingClient.d.ts +9 -0
  79. package/lib/packlets/ai-assist/embeddingClient.d.ts.map +1 -1
  80. package/lib/packlets/ai-assist/embeddingClient.js +16 -1
  81. package/lib/packlets/ai-assist/embeddingClient.js.map +1 -1
  82. package/lib/packlets/ai-assist/imageGenerationClient.d.ts +9 -0
  83. package/lib/packlets/ai-assist/imageGenerationClient.d.ts.map +1 -1
  84. package/lib/packlets/ai-assist/imageGenerationClient.js +17 -2
  85. package/lib/packlets/ai-assist/imageGenerationClient.js.map +1 -1
  86. package/lib/packlets/ai-assist/index.d.ts +7 -2
  87. package/lib/packlets/ai-assist/index.d.ts.map +1 -1
  88. package/lib/packlets/ai-assist/index.js +11 -1
  89. package/lib/packlets/ai-assist/index.js.map +1 -1
  90. package/lib/packlets/ai-assist/listModelsClient.d.ts +12 -0
  91. package/lib/packlets/ai-assist/listModelsClient.d.ts.map +1 -1
  92. package/lib/packlets/ai-assist/listModelsClient.js +19 -1
  93. package/lib/packlets/ai-assist/listModelsClient.js.map +1 -1
  94. package/lib/packlets/ai-assist/model.d.ts +81 -295
  95. package/lib/packlets/ai-assist/model.d.ts.map +1 -1
  96. package/lib/packlets/ai-assist/model.js.map +1 -1
  97. package/lib/packlets/ai-assist/promptCacheAnthropicBreakpointHarness.d.ts +124 -0
  98. package/lib/packlets/ai-assist/promptCacheAnthropicBreakpointHarness.d.ts.map +1 -0
  99. package/lib/packlets/ai-assist/promptCacheAnthropicBreakpointHarness.js +208 -0
  100. package/lib/packlets/ai-assist/promptCacheAnthropicBreakpointHarness.js.map +1 -0
  101. package/lib/packlets/ai-assist/promptCacheObservabilityHarness.d.ts +77 -0
  102. package/lib/packlets/ai-assist/promptCacheObservabilityHarness.d.ts.map +1 -0
  103. package/lib/packlets/ai-assist/promptCacheObservabilityHarness.js +143 -0
  104. package/lib/packlets/ai-assist/promptCacheObservabilityHarness.js.map +1 -0
  105. package/lib/packlets/ai-assist/promptCacheRoutingAbHarness.d.ts +84 -0
  106. package/lib/packlets/ai-assist/promptCacheRoutingAbHarness.d.ts.map +1 -0
  107. package/lib/packlets/ai-assist/promptCacheRoutingAbHarness.js +163 -0
  108. package/lib/packlets/ai-assist/promptCacheRoutingAbHarness.js.map +1 -0
  109. package/lib/packlets/ai-assist/promptCacheUsageMath.d.ts +105 -0
  110. package/lib/packlets/ai-assist/promptCacheUsageMath.d.ts.map +1 -0
  111. package/lib/packlets/ai-assist/promptCacheUsageMath.js +130 -0
  112. package/lib/packlets/ai-assist/promptCacheUsageMath.js.map +1 -0
  113. package/lib/packlets/ai-assist/registry.d.ts.map +1 -1
  114. package/lib/packlets/ai-assist/registry.js +9 -11
  115. package/lib/packlets/ai-assist/registry.js.map +1 -1
  116. package/lib/packlets/ai-assist/streamUsageCapability.d.ts +129 -0
  117. package/lib/packlets/ai-assist/streamUsageCapability.d.ts.map +1 -0
  118. package/lib/packlets/ai-assist/streamUsageCapability.js +140 -0
  119. package/lib/packlets/ai-assist/streamUsageCapability.js.map +1 -0
  120. package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts +2 -1
  121. package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts.map +1 -1
  122. package/lib/packlets/ai-assist/streamingAdapters/anthropic.js +29 -6
  123. package/lib/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -1
  124. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.d.ts +58 -14
  125. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.d.ts.map +1 -1
  126. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js +81 -36
  127. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js.map +1 -1
  128. package/lib/packlets/ai-assist/streamingAdapters/common.d.ts +38 -3
  129. package/lib/packlets/ai-assist/streamingAdapters/common.d.ts.map +1 -1
  130. package/lib/packlets/ai-assist/streamingAdapters/common.js +25 -1
  131. package/lib/packlets/ai-assist/streamingAdapters/common.js.map +1 -1
  132. package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts.map +1 -1
  133. package/lib/packlets/ai-assist/streamingAdapters/gemini.js +12 -5
  134. package/lib/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -1
  135. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts +2 -1
  136. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts.map +1 -1
  137. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js +42 -8
  138. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -1
  139. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts +2 -1
  140. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts.map +1 -1
  141. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js +30 -19
  142. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -1
  143. package/lib/packlets/ai-assist/streamingAdapters/proxy.d.ts +22 -0
  144. package/lib/packlets/ai-assist/streamingAdapters/proxy.d.ts.map +1 -1
  145. package/lib/packlets/ai-assist/streamingAdapters/proxy.js +62 -4
  146. package/lib/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -1
  147. package/lib/packlets/ai-assist/streamingClient.d.ts +5 -0
  148. package/lib/packlets/ai-assist/streamingClient.d.ts.map +1 -1
  149. package/lib/packlets/ai-assist/streamingClient.js +20 -5
  150. package/lib/packlets/ai-assist/streamingClient.js.map +1 -1
  151. package/lib/packlets/ai-assist/thinkingOptionsResolver.d.ts +11 -3
  152. package/lib/packlets/ai-assist/thinkingOptionsResolver.d.ts.map +1 -1
  153. package/lib/packlets/ai-assist/thinkingOptionsResolver.js +23 -5
  154. package/lib/packlets/ai-assist/thinkingOptionsResolver.js.map +1 -1
  155. package/lib/packlets/ai-assist/toolFormats.d.ts +55 -1
  156. package/lib/packlets/ai-assist/toolFormats.d.ts.map +1 -1
  157. package/lib/packlets/ai-assist/toolFormats.js +82 -0
  158. package/lib/packlets/ai-assist/toolFormats.js.map +1 -1
  159. package/lib/packlets/ai-assist/toolTypes.d.ts +386 -0
  160. package/lib/packlets/ai-assist/toolTypes.d.ts.map +1 -0
  161. package/lib/packlets/ai-assist/toolTypes.js +22 -0
  162. package/lib/packlets/ai-assist/toolTypes.js.map +1 -0
  163. package/lib/packlets/ai-assist/usageNormalization.d.ts +58 -0
  164. package/lib/packlets/ai-assist/usageNormalization.d.ts.map +1 -0
  165. package/lib/packlets/ai-assist/usageNormalization.js +184 -0
  166. package/lib/packlets/ai-assist/usageNormalization.js.map +1 -0
  167. package/lib/packlets/ai-assist/usageTypes.d.ts +84 -0
  168. package/lib/packlets/ai-assist/usageTypes.d.ts.map +1 -0
  169. package/lib/packlets/ai-assist/usageTypes.js +22 -0
  170. package/lib/packlets/ai-assist/usageTypes.js.map +1 -0
  171. package/lib/packlets/mustache/index.d.ts +1 -1
  172. package/lib/packlets/mustache/index.d.ts.map +1 -1
  173. package/lib/packlets/mustache/index.js.map +1 -1
  174. package/lib/packlets/mustache/interfaces.d.ts +30 -0
  175. package/lib/packlets/mustache/interfaces.d.ts.map +1 -1
  176. package/lib/packlets/mustache/interfaces.js.map +1 -1
  177. package/lib/packlets/mustache/mustacheTemplate.d.ts +56 -1
  178. package/lib/packlets/mustache/mustacheTemplate.d.ts.map +1 -1
  179. package/lib/packlets/mustache/mustacheTemplate.js +91 -0
  180. package/lib/packlets/mustache/mustacheTemplate.js.map +1 -1
  181. package/package.json +16 -16
  182. package/perf/promptCacheAnthropicBreakpoint.js +135 -0
  183. package/perf/promptCacheObservability.js +107 -0
  184. package/perf/promptCacheRoutingAb.js +105 -0
@@ -82,6 +82,8 @@ declare namespace AiAssist {
82
82
  AiServerToolType,
83
83
  AiServerToolConfig,
84
84
  AiToolConfig,
85
+ AiToolConflictPolicy,
86
+ IAiToolConflictReport,
85
87
  IAiWebSearchToolConfig,
86
88
  IAiClientToolConfig,
87
89
  IAiToolAnnotations,
@@ -155,7 +157,6 @@ declare namespace AiAssist {
155
157
  isAdaptiveThinkingModel,
156
158
  usesMaxCompletionTokensField,
157
159
  toDataUrl,
158
- AiThinkingMode,
159
160
  IThinkingConfig,
160
161
  IThinkingProviderConfig,
161
162
  IAnthropicThinkingOptions,
@@ -215,6 +216,16 @@ declare namespace AiAssist {
215
216
  modelSpecKey,
216
217
  modelSpec,
217
218
  resolveEffectiveTools,
219
+ resolveToolConflicts,
220
+ defaultToolConflictPolicy,
221
+ IAiResolvedToolConflicts,
222
+ AiCacheReportingLevel,
223
+ IAiCompletionUsage,
224
+ supportsCacheUsageReporting,
225
+ supportsPromptCacheBreakpoints,
226
+ supportsPromptCacheRouting,
227
+ supportsStreamUsageOption,
228
+ IAiPromptCacheRoutingSupport,
218
229
  ANTHROPIC_STRUCTURED_OUTPUT_TOOL_NAME,
219
230
  AiStructuredOutputFormat,
220
231
  IAiStructuredOutputCapability,
@@ -236,7 +247,10 @@ declare namespace AiAssist {
236
247
  IGenerateJsonCompletionResult,
237
248
  JsonPromptHint,
238
249
  anthropicEffortToBudgetTokens,
239
- IResolvedThinkingConfig
250
+ IResolvedThinkingConfig,
251
+ IAiCacheRequest,
252
+ validateAiCacheRequest,
253
+ validateCacheBreakpoints
240
254
  }
241
255
  }
242
256
  export { AiAssist }
@@ -253,6 +267,27 @@ declare const aiAssistProviderConfig: Converter<IAiAssistProviderConfig>;
253
267
  */
254
268
  declare const aiAssistSettings: Converter<IAiAssistSettings>;
255
269
 
270
+ /**
271
+ * What a provider's wire shape is able to report about prompt-cache usage,
272
+ * given that a usage block was present on the response at all.
273
+ *
274
+ * @remarks
275
+ * `'reads'` — cache reads are reported; cache writes cannot be (either the API
276
+ * has no write concept, or the field structurally does not exist on this route).
277
+ * `'reads-and-writes'` — both reads and writes are reported.
278
+ *
279
+ * There is deliberately no `'none'` member: "no usage was reported at all" is
280
+ * already expressed by {@link AiAssist.IAiCompletionResponse.usage} itself
281
+ * being absent, and a third value here would be a second, reachable-only-via-
282
+ * bug way to say the same thing — check `usage === undefined`, not a `reports`
283
+ * value, to detect that case.
284
+ *
285
+ * See {@link AiAssist.IAiCompletionUsage.reports} for why this is required
286
+ * rather than optional.
287
+ * @public
288
+ */
289
+ declare type AiCacheReportingLevel = 'reads' | 'reads-and-writes';
290
+
256
291
  /**
257
292
  * Converter for {@link AiAssist.IAiClientToolConfig}. Validates the wrapper shape: `type`,
258
293
  * `name`, `description`, the presence of a usable `parametersSchema`, and optional
@@ -417,12 +452,6 @@ declare const aiServerToolType: Converter<AiServerToolType>;
417
452
  */
418
453
  declare type AiStructuredOutputFormat = 'openai-json-schema' | 'openai-responses-format' | 'gemini-response-schema' | 'anthropic-tool-forced';
419
454
 
420
- /**
421
- * Thinking/reasoning mode support for a provider.
422
- * @public
423
- */
424
- declare type AiThinkingMode = 'optional' | 'required' | 'unsupported';
425
-
426
455
  /**
427
456
  * Converter for {@link AiAssist.IAiToolAnnotations} — the five optional host-advisory hints.
428
457
  * @public
@@ -436,6 +465,28 @@ declare const aiToolAnnotations: Converter<IAiToolAnnotations>;
436
465
  */
437
466
  declare type AiToolConfig = AiServerToolConfig | IAiClientToolConfig;
438
467
 
468
+ /**
469
+ * What to do when a request would combine a server tool with client (function)
470
+ * tools on a provider that declares the two mutually exclusive (see
471
+ * `IAiProviderDescriptor.serverToolsExclusiveWithClientTools`).
472
+ *
473
+ * @remarks
474
+ * - `'drop-server-tools'` (the default) removes the offending server tools and
475
+ * keeps every client tool. Client tools are what the host registered
476
+ * explicitly for this turn; a built-in like grounding is a per-provider
477
+ * nicety, and dropping it degrades the turn rather than losing it.
478
+ * - `'prefer-server-tools'` is the mirror image: keep the server tools and drop
479
+ * every client tool. For hosts whose turn is *about* the grounded answer.
480
+ * - `'fail'` refuses the request up front with a message naming the conflict —
481
+ * the behavior before this policy existed. For hosts that would rather
482
+ * choose themselves than have either half silently disappear.
483
+ *
484
+ * Whichever is chosen, what actually happened is reported, never inferred: see
485
+ * {@link AiAssist.IAiToolConflictReport}.
486
+ * @public
487
+ */
488
+ declare type AiToolConflictPolicy = 'drop-server-tools' | 'prefer-server-tools' | 'fail';
489
+
439
490
  /**
440
491
  * Converter for {@link IAiToolEnablement}.
441
492
  * @public
@@ -593,8 +644,11 @@ declare const ANTHROPIC_STRUCTURED_OUTPUT_TOOL_NAME: string;
593
644
  *
594
645
  * Policy: low = 2048, medium = 8192, high = 24000, max = 32000. The lower three
595
646
  * align with the Anthropic-published minimum-meaningful budget, a mid-range
596
- * default, and a "deep thinking" allotment respectively. `max` targets Opus 4.6's
597
- * deepest budget and stays within typical model limits.
647
+ * default, and a "deep thinking" allotment respectively. `max` is the deepest
648
+ * allotment and stays within typical model limits.
649
+ *
650
+ * Only reached for models outside the Claude 5 family — the adaptive-thinking path
651
+ * sends no budget at all.
598
652
  *
599
653
  * @public
600
654
  */
@@ -758,6 +812,11 @@ declare function callProviderCompletion(params: IProviderCompletionParams): Prom
758
812
  * `Result.fail` before fetch is invoked. Callers should route through
759
813
  * {@link AiAssist.callProxiedCompletionStream} or surface the failure to the user.
760
814
  *
815
+ * This guard is applied **here only**. `executeClientToolTurn` is also a
816
+ * streaming entry point and does not pre-flight the flag, so a browser
817
+ * client-tool turn against a `streamingCorsRestricted` provider fails at the
818
+ * fetch with a CORS error rather than with this legible refusal.
819
+ *
761
820
  * Connection-time failures (auth, network, non-2xx) surface as the outer
762
821
  * `Result.fail`. Once iteration begins, errors mid-stream surface as a
763
822
  * terminal error event ({@link AiAssist.IAiStreamError}) followed by the iterable
@@ -814,6 +873,19 @@ declare function callProviderListModels(params: IProviderListModelsParams): Prom
814
873
  * CORS, and API key forwarding. The request body serializes the unified
815
874
  * {@link AiAssist.IChatRequest} shape (`system?` + `messages`). Enforces the same
816
875
  * non-empty / trailing-user-turn and image-input invariants as the direct path.
876
+ *
877
+ * @remarks
878
+ * Two parameters are handled before the body is composed, rather than forwarded.
879
+ * `tier` is resolved here and sent as a concrete `modelOverride`, because both
880
+ * halves of the resolution live on the descriptor the caller already holds — so a
881
+ * proxy needs no `tier` vocabulary, where a `tier` body field it did not understand
882
+ * would be ignored and silently serve a frontier request from the base model.
883
+ * `endpoint` is **refused**: it names where the prompt must go, the proxy is what
884
+ * makes that call, and the field has never been sent to one — so honoring it is
885
+ * unverifiable and ignoring it would reach the provider's default upstream instead
886
+ * of the host the caller pinned. Use `callProviderCompletion`, or point the
887
+ * proxy itself at the intended upstream.
888
+ *
817
889
  * @param proxyUrl - Base URL of the proxy server
818
890
  * @param params - Same parameters as {@link callProviderCompletion}
819
891
  * @public
@@ -831,9 +903,23 @@ declare function callProxiedCompletion(proxyUrl: string, params: IProviderComple
831
903
  * - Response: `Content-Type: text/event-stream`; body is the unified
832
904
  * {@link AiAssist.IAiStreamEvent} JSON-serialized one event per SSE `data:` line
833
905
  * (no `event:` line needed since the type discriminator is in the JSON).
906
+ * **Only `text-delta`, `tool-event`, `done` and `error` are carried** — an event
907
+ * of any other type, including the three `client-tool-*` variants, is discarded
908
+ * without error. Client-tool turns are not proxiable; `executeClientToolTurn`
909
+ * has no proxied entry point.
834
910
  * - Error response (when the proxy can't even start): JSON `{error: string}`
835
911
  * with a non-2xx status, surfaced as `proxy: ${error}`.
836
912
  *
913
+ * Two request parameters are handled before the body is composed, identically to
914
+ * `callProxiedCompletion`: `tier` is resolved here and sent as a concrete
915
+ * `modelOverride`, so a proxy needs no `tier` vocabulary; `endpoint` is refused,
916
+ * because a proxy cannot confirm it honored it and reaching the provider's default
917
+ * upstream instead would send the request somewhere the caller excluded. `cache`,
918
+ * by contrast, is forwarded as a plain `body.cache` field exactly as
919
+ * `callProxiedCompletion` forwards it — a proxy that ignores it degrades to the
920
+ * request it would have sent anyway, never a misdirected one, so there is no
921
+ * `endpoint`-style reason to refuse it.
922
+ *
837
923
  * The proxy server is responsible for opening the upstream SSE connection,
838
924
  * translating provider-native events to the unified vocabulary, and
839
925
  * forwarding events as they arrive (no buffering). The library does not
@@ -850,6 +936,15 @@ declare function callProxiedCompletionStream(proxyUrl: string, params: IProvider
850
936
  * handles descriptor lookup, model/capability resolution, and provider dispatch.
851
937
  * Error body `{ error: string }` is surfaced as `proxy: ${error}`.
852
938
  *
939
+ * @remarks
940
+ * `endpoint` is **refused** on this path rather than forwarded or dropped, on the
941
+ * same terms as `callProxiedCompletion`. It matters more here than
942
+ * anywhere else: pointing `endpoint` at `http://localhost:11434/v1` is the
943
+ * documented way to reach a local Ollama, so dropping it does not degrade the
944
+ * answer — it sends the text to OpenAI's public API instead of the machine the
945
+ * caller named. Use `callProviderEmbedding`, or point the proxy
946
+ * itself at the intended upstream.
947
+ *
853
948
  * @param proxyUrl - Base URL of the proxy server (e.g. `http://localhost:3001`).
854
949
  * @param params - Same parameters as {@link AiAssist.callProviderEmbedding}.
855
950
  * @returns The embedding result, or a failure.
@@ -865,6 +960,15 @@ declare function callProxiedEmbedding(proxyUrl: string, params: IProviderEmbeddi
865
960
  * lookup, model resolution, provider dispatch, and response normalization
866
961
  * (including repackaging `referenceImages` for the upstream wire format).
867
962
  * Error body `{error: string}` is surfaced as `proxy: ${error}`.
963
+ *
964
+ * @remarks
965
+ * `endpoint` is **refused** on this path rather than forwarded or dropped, on the
966
+ * same terms as `callProxiedCompletion`: a proxy cannot confirm it honored
967
+ * it, the field has never been sent to one, and reaching the provider's default
968
+ * upstream instead would send the prompt somewhere the caller excluded. Use
969
+ * `callProviderImageGeneration`, or point the proxy itself at the intended
970
+ * upstream.
971
+ *
868
972
  * @param proxyUrl - Base URL of the proxy server
869
973
  * @param params - Same parameters as {@link callProviderImageGeneration}
870
974
  * @public
@@ -876,6 +980,18 @@ declare function callProxiedImageGeneration(proxyUrl: string, params: IProviderI
876
980
  * `POST ${proxyUrl}/api/ai/list-models`. Capability config is not forwarded;
877
981
  * `capabilities` is serialized as a string array. Error body `{error: string}`
878
982
  * is surfaced as `proxy: ${error}`.
983
+ *
984
+ * @remarks
985
+ * `capabilityConfig` and `endpoint` are both unusable here, but they are not the
986
+ * same case and are not treated the same way. `capabilityConfig` overrides how a
987
+ * model id is classified, and the proxy returns models already classified — so a
988
+ * caller-supplied override could only ever have arrived too late. Ignoring it
989
+ * yields the default classification: a degraded answer, disclosed above, and
990
+ * that is where it stays. `endpoint` names *which server answers at all*, so
991
+ * ignoring it would list the provider's public catalog in place of the host the
992
+ * caller pinned; it is **refused**, on the same terms as
993
+ * `callProxiedCompletion`.
994
+ *
879
995
  * @public
880
996
  */
881
997
  declare function callProxiedListModels(proxyUrl: string, params: IProviderListModelsParams): Promise<Result<ReadonlyArray<IAiModelInfo>>>;
@@ -1273,6 +1389,19 @@ declare const DEFAULT_SECRET_ITERATIONS: number;
1273
1389
  */
1274
1390
  declare const DEFAULT_TIMEOUT_MS: number;
1275
1391
 
1392
+ /**
1393
+ * The default {@link AiAssist.AiToolConflictPolicy} applied when a caller passes none.
1394
+ *
1395
+ * @remarks
1396
+ * Keeping the client tools is the right default because they are what the host
1397
+ * registered explicitly for *this* turn, while a built-in server tool is a
1398
+ * per-provider nicety the same host would get on some providers and not others.
1399
+ * Dropping the nicety degrades a turn; dropping the client tools changes what the
1400
+ * turn can do.
1401
+ * @public
1402
+ */
1403
+ declare const defaultToolConflictPolicy: AiToolConflictPolicy;
1404
+
1276
1405
  /**
1277
1406
  * Derives an asymmetric keypair *deterministically* from a fixed secret seed
1278
1407
  * using `globalThis.crypto.subtle`, shared by the Node and browser
@@ -1533,6 +1662,13 @@ declare const encryptionAlgorithm: Converter<EncryptionAlgorithm>;
1533
1662
  * a forced `tool_choice`. Only `tool_choice: 'auto'` (the default, i.e.
1534
1663
  * omitted) is compatible with extended thinking.
1535
1664
  *
1665
+ * **Provider tool exclusions:** when the descriptor declares a server tool
1666
+ * mutually exclusive with client tools (today only Gemini, for `web_search`),
1667
+ * `toolConflictPolicy` decides — by default the server tool is dropped and the
1668
+ * turn proceeds. The turn result's `toolConflicts` always says what happened, so
1669
+ * a host can tell the person that e.g. web search was unavailable this turn.
1670
+ * Pass `'fail'` to refuse the request up front instead.
1671
+ *
1536
1672
  * @param params - Turn parameters
1537
1673
  * @returns `{ events, nextTurn }` — stream iterable + completion promise
1538
1674
  * @public
@@ -2485,6 +2621,41 @@ declare interface IAiAssistSettings {
2485
2621
  readonly proxyAllProviders?: boolean;
2486
2622
  }
2487
2623
 
2624
+ /**
2625
+ * A request-level prompt-caching plan.
2626
+ *
2627
+ * @remarks
2628
+ * Offsets rather than content blocks — see design.md §6.1 for why: `AiPrompt`/`IChatRequest`
2629
+ * carry `system` as a plain string, offsets are usable without `@fgv/ts-prompt-assist` (a
2630
+ * caller can say "the first 4,000 characters of my system prompt are stable" directly), and
2631
+ * they are already the currency `IPromptSection.start`/`.chars` report.
2632
+ * @public
2633
+ */
2634
+ declare interface IAiCacheRequest {
2635
+ /**
2636
+ * Strictly ascending UTF-16 offsets into `AiPrompt.system` (equivalently
2637
+ * `IProviderCompletionParams.system`), each marking the end of a cacheable prefix. Each must
2638
+ * be an integer `> 0` and `< system.length`. Validated where the offsets are actually used
2639
+ * (against the request's own `system.length`, and optionally against a cap) — never clamped;
2640
+ * an invalid plan fails the request rather than silently caching less than declared. Sent where
2641
+ * the provider has a breakpoint mechanism (Anthropic `cache_control`, OpenAI
2642
+ * `prompt_cache_breakpoint`). Ignored by adapters that have no such concept (Gemini).
2643
+ */
2644
+ readonly systemBreakpoints?: ReadonlyArray<number>;
2645
+ /**
2646
+ * Opaque cache-routing key, sent where the provider has one and by that provider's transport:
2647
+ * `prompt_cache_key` as a body field on OpenAI (both routes) and on xAI Responses, and an
2648
+ * `x-grok-conv-id` header on xAI Chat Completions. See `supportsPromptCacheRouting`.
2649
+ *
2650
+ * @remarks
2651
+ * On xAI this is not a nicety. Its prompt cache is per-server and evictable, so two requests
2652
+ * sharing a prefix can still miss if they are routed to different servers; the key pins them
2653
+ * together. Providers with no routing concept ignore it and receive a request byte-identical
2654
+ * to one built without a `cache` at all.
2655
+ */
2656
+ readonly cacheKey?: string;
2657
+ }
2658
+
2488
2659
  /**
2489
2660
  * A client-defined tool: configuration + execution callback pair.
2490
2661
  *
@@ -2552,7 +2723,7 @@ declare interface IAiClientToolConfig<TParams = unknown> {
2552
2723
  /**
2553
2724
  * Optional host-advisory behavior annotations (read-only / destructive /
2554
2725
  * idempotent / open-world hints + display title). Consumed by the host's
2555
- * tool loop; never serialized to the model. See {@link IAiToolAnnotations}.
2726
+ * tool loop; never serialized to the model. See {@link AiAssist.IAiToolAnnotations}.
2556
2727
  */
2557
2728
  readonly annotations?: IAiToolAnnotations;
2558
2729
  }
@@ -2622,6 +2793,61 @@ declare interface IAiClientToolTurnResult {
2622
2793
  readonly truncated: boolean;
2623
2794
  /** The full concatenated text from all `text-delta` events in this turn. */
2624
2795
  readonly fullText: string;
2796
+ /**
2797
+ * What the provider tool-conflict resolution did to this turn's tools.
2798
+ * Always present; empty arrays mean nothing conflicted.
2799
+ *
2800
+ * @remarks
2801
+ * Read this to tell the person (or the model, on a later turn) that a
2802
+ * capability they expected was unavailable — e.g. that web search was dropped
2803
+ * because the pinned model cannot ground and call functions in one request.
2804
+ */
2805
+ readonly toolConflicts: IAiToolConflictReport;
2806
+ /**
2807
+ * Token usage for this turn's provider call, when the provider reports it.
2808
+ *
2809
+ * @remarks
2810
+ * The same `IAiCompletionUsage` the plain completion paths carry — normalized
2811
+ * cache reads/writes, output and total input tokens, and a `reports` level
2812
+ * saying which of those the provider is able to tell you. Sourced from the
2813
+ * terminal `done` event of the underlying stream, so it is exactly what a
2814
+ * caller watching `events` would see.
2815
+ *
2816
+ * Reading it here spares you *inspecting* the event stream, not *driving* it:
2817
+ * `nextTurn` only resolves once `events` has been iterated to completion, the
2818
+ * same as every other field on this interface.
2819
+ *
2820
+ * **One call, one usage.** `executeClientToolTurn` makes a single provider
2821
+ * call per invocation; the multi-round loop is driven by the caller
2822
+ * re-invoking with `continuationMessages`. So each result's `usage` already
2823
+ * *is* one round's usage, and a host accumulating a per-turn total sums the
2824
+ * results of its own loop. There is deliberately no `perRound` array here —
2825
+ * it could only ever have one entry.
2826
+ *
2827
+ * **Optional on purpose, unlike `toolConflicts`.** The library always knows
2828
+ * what it did to your tools, so a silent `toolConflicts` would be a defect
2829
+ * and it is required. It cannot always know what a turn cost, so a required
2830
+ * `usage` could only be satisfied by inventing one — the exact failure
2831
+ * {@link AiAssist.IAiCompletionUsage.reports} exists to prevent.
2832
+ *
2833
+ * Absent means **no normalized usage is being exposed**, which covers two
2834
+ * cases, the same as on {@link AiAssist.IAiCompletionResponse.usage} and
2835
+ * {@link AiAssist.IAiStreamDone.usage}:
2836
+ *
2837
+ * - the provider reported nothing; or
2838
+ * - the provider reported something the library declines to normalize,
2839
+ * because this descriptor is outside
2840
+ * {@link AiAssist.supportsCacheUsageReporting}. Groq, Mistral, Ollama and
2841
+ * self-hosted `openai-compat` share the OpenAI-format adapter but have
2842
+ * unverified usage shapes, so a raw `usage` block from one of them is
2843
+ * dropped rather than stamped with a cache-reporting level it may not
2844
+ * have earned.
2845
+ *
2846
+ * Either way it is never a fabricated zero. If you need to tell the two
2847
+ * apart, the descriptor answers it: the second case is a property of the
2848
+ * provider you picked, not of the turn.
2849
+ */
2850
+ readonly usage?: IAiCompletionUsage;
2625
2851
  }
2626
2852
 
2627
2853
  /**
@@ -2651,6 +2877,61 @@ declare interface IAiCompletionResponse {
2651
2877
  * split.
2652
2878
  */
2653
2879
  readonly structuredOutput: StructuredOutputEnforcement;
2880
+ /** Token usage, when the provider reports it. */
2881
+ readonly usage?: IAiCompletionUsage;
2882
+ }
2883
+
2884
+ /**
2885
+ * Token accounting for a completion, normalized across providers.
2886
+ *
2887
+ * @remarks
2888
+ * Every field but `reports` is best-effort: a provider's wire shape reports
2889
+ * what it reports, and an absent field here means the corresponding wire
2890
+ * field was itself absent — never a guessed zero. See `reports` for the one
2891
+ * exception this rule has to be explicit about.
2892
+ * @public
2893
+ */
2894
+ declare interface IAiCompletionUsage {
2895
+ /**
2896
+ * What this response's wire shape *can* report about caching.
2897
+ *
2898
+ * @remarks
2899
+ * **Required, not optional.** An optional field would make an absent
2900
+ * `cacheWriteTokens` three-ways ambiguous — no write happened / this API
2901
+ * cannot report writes / a build predating the field — and disambiguating
2902
+ * exactly that is what this field exists for. Under `'reads-and-writes'`, an
2903
+ * absent `cacheWriteTokens` genuinely means zero tokens were written this
2904
+ * request — the actionable signal that a breakpoint did not take. Under
2905
+ * `'reads'` it means the API cannot say, and inferring "zero writes" from it
2906
+ * would be a fabricated fact. Same remedy as
2907
+ * `IAiCompletionResponse.structuredOutput`.
2908
+ */
2909
+ readonly reports: AiCacheReportingLevel;
2910
+ /** Input tokens **not** served from cache. Normalized — see `CAPABILITIES.md` for the per-provider table. */
2911
+ readonly uncachedInputTokens?: number;
2912
+ /** Input tokens served from cache. */
2913
+ readonly cachedInputTokens?: number;
2914
+ /**
2915
+ * Input tokens written to cache.
2916
+ *
2917
+ * @remarks
2918
+ * Two absence cases, disambiguated by `reports` — never by a `0` this field
2919
+ * itself carries:
2920
+ * - **`reports: 'reads-and-writes'` and this field absent** — the wire
2921
+ * genuinely reported zero tokens written. Treat absence as `0`, not as
2922
+ * unknown; this is the actionable "the breakpoint did not take" signal.
2923
+ * - **`reports: 'reads'` and this field absent** — the API cannot report
2924
+ * writes on this route at all (structurally unfillable, e.g. OpenAI/xAI
2925
+ * Chat Completions). Never defaulted to `0` here — that would fabricate a
2926
+ * fact the wire never stated.
2927
+ */
2928
+ readonly cacheWriteTokens?: number;
2929
+ /** Generated output tokens. */
2930
+ readonly outputTokens?: number;
2931
+ /** Total input = uncached + cached, when both are known. */
2932
+ readonly totalInputTokens?: number;
2933
+ /** The provider's own usage block, unnormalized, for anything this shape drops. */
2934
+ readonly raw?: JsonObject;
2654
2935
  }
2655
2936
 
2656
2937
  /**
@@ -2964,6 +3245,23 @@ declare interface IAiModelInfo {
2964
3245
  readonly displayName?: string;
2965
3246
  }
2966
3247
 
3248
+ /**
3249
+ * How a provider carries an {@link AiAssist.IAiCacheRequest.cacheKey} on the wire.
3250
+ *
3251
+ * @remarks
3252
+ * Returned by {@link AiAssist.supportsPromptCacheRouting}. The Responses API takes the key as a
3253
+ * `prompt_cache_key` body field on every provider that supports it, so only the Chat Completions
3254
+ * transport varies and only that is described here.
3255
+ * @public
3256
+ */
3257
+ declare interface IAiPromptCacheRoutingSupport {
3258
+ /**
3259
+ * Header name to carry the key on Chat Completions, or `undefined` when the provider takes it
3260
+ * as a `prompt_cache_key` body field there (OpenAI). xAI uses `x-grok-conv-id`.
3261
+ */
3262
+ readonly chatCompletionsHeader: string | undefined;
3263
+ }
3264
+
2967
3265
  /**
2968
3266
  * Describes a single AI provider — single source of truth for all metadata.
2969
3267
  * @public
@@ -2998,6 +3296,29 @@ declare interface IAiProviderDescriptor {
2998
3296
  readonly aliases?: IModelAliasMap;
2999
3297
  /** Which server-side tools this provider supports (empty = none). */
3000
3298
  readonly supportedTools: ReadonlyArray<AiServerToolType>;
3299
+ /**
3300
+ * Server tools this provider's API refuses to accept in the *same request* as
3301
+ * client (function) tools. Absent or empty means the provider imposes no such
3302
+ * constraint and any supported server tool may be mixed with client tools.
3303
+ *
3304
+ * @remarks
3305
+ * This is a narrower statement than {@link AiAssist.IAiProviderDescriptor.supportedTools}:
3306
+ * a tool listed here is genuinely supported, just not *together* with function
3307
+ * calling. Only Gemini declares anything today — its `generateContent` API
3308
+ * HTTP-400s (`INVALID_ARGUMENT`) when built-in grounding (`web_search` →
3309
+ * `google_search`) and `function_declarations` appear in one request.
3310
+ *
3311
+ * Declaring the rule here rather than enforcing it at the call site is the
3312
+ * point: {@link AiAssist.resolveToolConflicts} applies it generically, so a host
3313
+ * never writes `if (descriptor.apiFormat === 'gemini')` in its tool assembly,
3314
+ * and the day a provider lifts its limit only this line changes.
3315
+ *
3316
+ * Optional because most providers have nothing to say. The library's own
3317
+ * registry descriptors are the ones that reach the wire and are pinned by test;
3318
+ * a hand-built descriptor that omits this simply gets the unconstrained
3319
+ * behavior, which is the correct reading of "declares no constraint".
3320
+ */
3321
+ readonly serverToolsExclusiveWithClientTools?: ReadonlyArray<AiServerToolType>;
3001
3322
  /** Whether this provider's API enforces CORS restrictions that prevent direct browser calls. */
3002
3323
  readonly corsRestricted: boolean;
3003
3324
  /**
@@ -3017,13 +3338,6 @@ declare interface IAiProviderDescriptor {
3017
3338
  * `prompt.attachments` are rejected up front.
3018
3339
  */
3019
3340
  readonly acceptsImageInput: boolean;
3020
- /**
3021
- * Whether this provider supports thinking/reasoning mode.
3022
- * - 'optional': thinking can be enabled but is not required
3023
- * - 'required': thinking is always active (e.g. o-series models)
3024
- * - 'unsupported': thinking is not supported
3025
- */
3026
- readonly thinkingMode: AiThinkingMode;
3027
3341
  /**
3028
3342
  * Image-generation capabilities, scoped to model id prefixes. Empty or
3029
3343
  * undefined means the provider does not support image generation.
@@ -3117,6 +3431,20 @@ declare interface IAiProviderDescriptor {
3117
3431
  readonly adaptiveThinkingModelPrefixes?: ReadonlyArray<string>;
3118
3432
  }
3119
3433
 
3434
+ /**
3435
+ * The outcome of {@link AiAssist.resolveToolConflicts}: the tools that survived,
3436
+ * plus the report of what did not.
3437
+ * @public
3438
+ */
3439
+ declare interface IAiResolvedToolConflicts {
3440
+ /** Server tools to send, after the policy was applied. */
3441
+ readonly serverTools: ReadonlyArray<AiServerToolConfig>;
3442
+ /** Client tools to send, after the policy was applied. */
3443
+ readonly clientTools: ReadonlyArray<IAiClientTool>;
3444
+ /** What the resolution did. Always populated; empty arrays mean nothing conflicted. */
3445
+ readonly report: IAiToolConflictReport;
3446
+ }
3447
+
3120
3448
  /**
3121
3449
  * Terminal success event for a streaming completion. Carries the aggregated
3122
3450
  * full text and truncation status for callers that want both the progressive
@@ -3138,6 +3466,8 @@ declare interface IAiStreamDone {
3138
3466
  * reports truncation without a reason).
3139
3467
  */
3140
3468
  readonly incompleteReason?: string;
3469
+ /** Token usage, when the provider reports it on this stream. */
3470
+ readonly usage?: IAiCompletionUsage;
3141
3471
  }
3142
3472
 
3143
3473
  /**
@@ -3303,6 +3633,27 @@ declare interface IAiToolAnnotations {
3303
3633
  readonly openWorldHint?: boolean;
3304
3634
  }
3305
3635
 
3636
+ /**
3637
+ * What the conflict resolution did to a turn's tools.
3638
+ *
3639
+ * @remarks
3640
+ * Required on {@link AiAssist.IAiClientToolTurnResult}, not optional, for the same reason
3641
+ * `structuredOutput` is required on a completion response: an absent report is
3642
+ * three-ways ambiguous — nothing conflicted / this build predates the feature /
3643
+ * the resolution ran and said nothing — and disambiguating exactly that is what
3644
+ * it is for. A turn where nothing conflicted reports the policy that was in
3645
+ * force and two empty arrays.
3646
+ * @public
3647
+ */
3648
+ declare interface IAiToolConflictReport {
3649
+ /** The policy that was in force for this turn (the default when none was passed). */
3650
+ readonly policy: AiToolConflictPolicy;
3651
+ /** Server tool types removed from the request. Empty when nothing was dropped. */
3652
+ readonly droppedServerTools: ReadonlyArray<AiServerToolType>;
3653
+ /** Names of client tools removed from the request. Empty when nothing was dropped. */
3654
+ readonly droppedClientTools: ReadonlyArray<string>;
3655
+ }
3656
+
3306
3657
  /**
3307
3658
  * Declares a tool as enabled/disabled in provider settings.
3308
3659
  * Tools are disabled by default — consuming apps must opt in explicitly.
@@ -3323,11 +3674,23 @@ declare interface IAiToolEnablement {
3323
3674
  */
3324
3675
  declare interface IAiWebSearchToolConfig {
3325
3676
  readonly type: 'web_search';
3326
- /** Optional: restrict search to these domains. */
3677
+ /**
3678
+ * Optional: restrict search to these domains.
3679
+ * Reaches Anthropic and the OpenAI/xAI Responses API; ignored on Gemini,
3680
+ * whose `google_search` tool takes no configuration at all.
3681
+ */
3327
3682
  readonly allowedDomains?: ReadonlyArray<string>;
3328
- /** Optional: exclude these domains from search. */
3683
+ /**
3684
+ * Optional: exclude these domains from search.
3685
+ * Same reach as {@link AiAssist.IAiWebSearchToolConfig.allowedDomains}:
3686
+ * Anthropic and OpenAI/xAI, ignored on Gemini.
3687
+ */
3329
3688
  readonly blockedDomains?: ReadonlyArray<string>;
3330
- /** Optional: max number of searches per request. */
3689
+ /**
3690
+ * Optional: max number of searches per request.
3691
+ * **Anthropic only** — emitted as `max_uses` by the Anthropic formatter and
3692
+ * read nowhere else, so it is silently ignored on OpenAI/xAI and Gemini.
3693
+ */
3331
3694
  readonly maxUses?: number;
3332
3695
  /**
3333
3696
  * Optional: enable image understanding during web search.
@@ -3343,11 +3706,25 @@ declare interface IAiWebSearchToolConfig {
3343
3706
  */
3344
3707
  declare interface IAnthropicThinkingConfig {
3345
3708
  /**
3346
- * Anthropic effort level. The emit-site converts to `thinking.budget_tokens`
3347
- * (the integer budget the Anthropic API requires). Mapping policy: low = 2048,
3348
- * medium = 8192, high = 24000, max = 32000.
3349
- * - 'low' | 'medium' | 'high': all thinking-capable models
3350
- * - 'max': Opus 4.6 only
3709
+ * Anthropic effort level, emitted as one of **two** wire shapes chosen by
3710
+ * {@link AiAssist.isAdaptiveThinkingModel}:
3711
+ *
3712
+ * - **Claude 5 family** (`claude-sonnet-5`, `claude-opus-5`, `claude-fable-5`
3713
+ * — which includes the `base` and `advanced` tiers, so this is the default
3714
+ * path): an adaptive `thinking` block plus an `output_config` block
3715
+ * carrying `effort`. The effort string goes on the wire verbatim and
3716
+ * **no token budget is sent**.
3717
+ * - **Older thinking-capable models**: an enabled `thinking` block carrying
3718
+ * `budget_tokens`, converted by `anthropicEffortToBudgetTokens` — low =
3719
+ * 2048, medium = 8192, high = 24000, max = 32000.
3720
+ *
3721
+ * So the budget mapping is not a description of what a default Anthropic
3722
+ * call sends; size a budget from it only when you have pinned a pre-Claude-5
3723
+ * model.
3724
+ *
3725
+ * `'max'` is accepted on every thinking-capable model here — the library
3726
+ * applies no model restriction of its own, and whether a given model accepts
3727
+ * it is the provider's to enforce.
3351
3728
  */
3352
3729
  readonly effort?: 'low' | 'medium' | 'high' | 'max';
3353
3730
  }
@@ -4311,6 +4688,30 @@ declare interface IExecuteClientToolTurnParams extends IChatRequest {
4311
4688
  * `DEFAULT_ANTHROPIC_MAX_TOKENS`.
4312
4689
  */
4313
4690
  readonly maxTokens?: number;
4691
+ /**
4692
+ * What to do when this provider declares one of the requested server tools
4693
+ * mutually exclusive with client tools (see
4694
+ * {@link AiAssist.IAiProviderDescriptor.serverToolsExclusiveWithClientTools}). Defaults to
4695
+ * `'drop-server-tools'` — the client tools this host registered for the turn are
4696
+ * kept and the server tool is dropped.
4697
+ *
4698
+ * @remarks
4699
+ * Whatever the policy, the turn result's `toolConflicts` says what actually
4700
+ * happened, so a degraded turn is never a silent one. Pass `'fail'` for the
4701
+ * pre-policy behavior: a `Result.fail` up front, before any wire call.
4702
+ */
4703
+ readonly toolConflictPolicy?: AiToolConflictPolicy;
4704
+ /**
4705
+ * Prompt-caching plan for this request. Omitted, nothing cache-related is sent — the request
4706
+ * body is byte-identical to a build predating this feature. See {@link AiAssist.IAiCacheRequest}.
4707
+ *
4708
+ * @remarks
4709
+ * `executeClientToolTurn` represents a single round of a multi-round tool loop; a caller
4710
+ * driving several rounds passes this on **each** call, and it is validated against — and
4711
+ * applied to — that round's own `system` each time, never cached across rounds. Gated per
4712
+ * provider exactly as `IProviderCompletionParams.cache` is on the non-streaming path.
4713
+ */
4714
+ readonly cache?: IAiCacheRequest;
4314
4715
  }
4315
4716
 
4316
4717
  /**
@@ -4320,14 +4721,37 @@ declare interface IExecuteClientToolTurnParams extends IChatRequest {
4320
4721
  declare interface IExecuteClientToolTurnResult {
4321
4722
  /**
4322
4723
  * The unified-event iterable. Callers iterate this to drive the streaming UI.
4323
- * The iterable forwards `text-delta`, `tool-event`, `client-tool-call-start`,
4324
- * `client-tool-call-done`, and `client-tool-result` events through.
4724
+ *
4725
+ * @remarks
4726
+ * Forwards every event the underlying adapter emits: `text-delta`,
4727
+ * `tool-event`, `done` and `error` come through from the provider stream, and
4728
+ * `client-tool-call-start`, `client-tool-call-done` and `client-tool-result`
4729
+ * are the tool-dispatch events this layer adds.
4730
+ *
4731
+ * The `done` event is the terminal one and carries
4732
+ * {@link AiAssist.IAiStreamDone.usage} when the provider reports it — this
4733
+ * iterable is not a text-only view. A caller that only wants the token counts
4734
+ * can read them off {@link AiAssist.IAiClientToolTurnResult.usage} instead of
4735
+ * matching on events — but it must still iterate this to completion, because
4736
+ * that is what drives `nextTurn` to resolve at all.
4737
+ *
4738
+ * (An earlier version of this comment listed only the five non-terminal
4739
+ * events. It was describing the tool-dispatch subset, but it read as the
4740
+ * complete set, and at least one consumer reasonably concluded from it that
4741
+ * usage never crossed the package boundary on this path. It always did.)
4325
4742
  */
4326
4743
  readonly events: AsyncIterable<IAiStreamEvent>;
4327
4744
  /**
4328
4745
  * Resolves when the stream terminates. On success, carries the
4329
4746
  * {@link AiAssist.IAiClientToolTurnResult} with the optional continuation for the
4330
4747
  * next round. On failure, carries the error message.
4748
+ *
4749
+ * @remarks
4750
+ * **You must drive `events` for this to resolve.** It is settled from inside
4751
+ * the generator backing `events`, and an async generator does not start until
4752
+ * something iterates it — so `await nextTurn` on its own waits forever, and
4753
+ * the response body is never drained. Iterate `events` to completion (ignore
4754
+ * the events if you do not want them) and then await this.
4331
4755
  */
4332
4756
  readonly nextTurn: Promise<Result<IAiClientToolTurnResult>>;
4333
4757
  }
@@ -4420,8 +4844,9 @@ declare interface IGeminiThinkingConfig {
4420
4844
  /**
4421
4845
  * Whether to include thought summaries in the response.
4422
4846
  * @remarks
4423
- * INERT in phase B. Adapters never send `includeThoughts: true`.
4424
- * Wired up by the followup stream `ai-assist-thinking-events`.
4847
+ * Reserved for forward compatibility and **currently ignored by every
4848
+ * adapter** — nothing reads this field, so setting it has no effect on the
4849
+ * request. Surfacing thinking output as stream events is not yet built.
4425
4850
  */
4426
4851
  readonly includeThoughts?: boolean;
4427
4852
  }
@@ -5361,12 +5786,23 @@ declare interface IProviderCompletionParams extends IChatRequest {
5361
5786
  * `IAiCompletionResponse.structuredOutput`.
5362
5787
  */
5363
5788
  readonly structuredOutput?: StructuredOutputRequest;
5789
+ /**
5790
+ * Prompt-caching plan for this request. Omitted, nothing cache-related is sent — the request
5791
+ * body is byte-identical to a build predating this feature. See {@link AiAssist.IAiCacheRequest}.
5792
+ */
5793
+ readonly cache?: IAiCacheRequest;
5364
5794
  }
5365
5795
 
5366
5796
  /**
5367
- * Parameters for a streaming completion request. Structurally identical to
5368
- * the non-streaming `IProviderCompletionParams`; kept as its own interface
5369
- * so callers can be explicit about which path they're invoking.
5797
+ * Parameters for a streaming completion request. The non-streaming
5798
+ * `IProviderCompletionParams` shape **minus `structuredOutput`**; kept as its
5799
+ * own interface so callers can be explicit about which path they're invoking.
5800
+ *
5801
+ * @remarks
5802
+ * The omission is deliberate, not an oversight. Native structured output is a
5803
+ * non-streaming feature on every provider the package supports, so the field
5804
+ * would have nowhere to go here. A caller that needs it must use
5805
+ * `callProviderCompletion`.
5370
5806
  *
5371
5807
  * @remarks
5372
5808
  * Carries the unified {@link AiAssist.IChatRequest} shape (`system?` + ordered
@@ -5422,6 +5858,13 @@ declare interface IProviderCompletionStreamParams extends IChatRequest {
5422
5858
  * Messages API requires the field, so it falls back to `DEFAULT_ANTHROPIC_MAX_TOKENS`.
5423
5859
  */
5424
5860
  readonly maxTokens?: number;
5861
+ /**
5862
+ * Prompt-caching plan for this request. Omitted, nothing cache-related is sent — the request
5863
+ * body is byte-identical to a build predating this feature. See {@link AiAssist.IAiCacheRequest}.
5864
+ * Validated against this request's own `system` at the point it is applied, and gated per
5865
+ * provider exactly as `IProviderCompletionParams.cache` is on the non-streaming path.
5866
+ */
5867
+ readonly cache?: IAiCacheRequest;
5425
5868
  }
5426
5869
 
5427
5870
  /**
@@ -5530,6 +5973,38 @@ declare interface IRemoveSecretResult {
5530
5973
  readonly warning?: string;
5531
5974
  }
5532
5975
 
5976
+ /**
5977
+ * One contiguous run of the rendered output, attributed to what produced it.
5978
+ * @public
5979
+ */
5980
+ declare interface IRenderedSegment {
5981
+ /** `'literal'` for template text; `'substitution'` for an interpolated value. */
5982
+ readonly kind: 'literal' | 'substitution';
5983
+ /** Variable name, when `kind` is `'substitution'`. */
5984
+ readonly name?: string;
5985
+ /** Offset into `IRenderedTemplate.text`, in UTF-16 code units. */
5986
+ readonly start: number;
5987
+ /** Length in UTF-16 code units. May be `0` for a variable that rendered empty. */
5988
+ readonly length: number;
5989
+ /** `false` for a triple-stash / `{{&}}` interpolation, which bypasses escaping. */
5990
+ readonly escaped: boolean;
5991
+ }
5992
+
5993
+ /**
5994
+ * A render plus the map of which part of the output came from where.
5995
+ * @public
5996
+ */
5997
+ declare interface IRenderedTemplate {
5998
+ /** The rendered output — identical to what `MustacheTemplate.render` returns. */
5999
+ readonly text: string;
6000
+ /**
6001
+ * Document-ordered, contiguous and gapless: every code unit of `text` belongs to exactly one
6002
+ * segment, so concatenating them reproduces `text` exactly. That identity is verified before
6003
+ * this is returned.
6004
+ */
6005
+ readonly segments: ReadonlyArray<IRenderedSegment>;
6006
+ }
6007
+
5533
6008
  /**
5534
6009
  * Policy guard over the outbound request.
5535
6010
  *
@@ -5628,7 +6103,12 @@ declare interface IResolvedImageOptions {
5628
6103
  * @public
5629
6104
  */
5630
6105
  declare interface IResolvedThinkingConfig {
5631
- /** Anthropic: effort level; emit-site converts to `thinking.budget_tokens` via `anthropicEffortToBudgetTokens`. */
6106
+ /**
6107
+ * Anthropic: effort level. The emit-site picks one of two wire shapes — on the
6108
+ * Claude 5 family it sends the effort string verbatim in `output_config` with no
6109
+ * budget; on older models it converts via `anthropicEffortToBudgetTokens`. See
6110
+ * {@link AiAssist.IAnthropicThinkingConfig.effort}.
6111
+ */
5632
6112
  readonly anthropicEffort?: IAnthropicThinkingConfig['effort'];
5633
6113
  /** OpenAI Chat: reasoning_effort value; OpenAI Responses: reasoning.effort */
5634
6114
  readonly openAiEffort?: IOpenAiThinkingConfig['effort'];
@@ -6072,11 +6552,27 @@ declare function isValidMultibaseSpkiPublicKey(value: unknown): value is Multiba
6072
6552
  declare interface IThinkingConfig {
6073
6553
  /**
6074
6554
  * Cross-provider effort level. Common-subset mapping:
6555
+ * - 'none': Anthropic — no thinking param emitted | OpenAI effort:none | Gemini thinkingBudget:0 | xAI reasoning_effort:none
6075
6556
  * - 'low': Anthropic effort:low | OpenAI effort:low | Gemini thinkingBudget:1024 | xAI reasoning_effort:low
6076
- * - 'medium': effort:medium | effort:medium | thinkingBudget:4096 | reasoning_effort:medium
6077
- * - 'high': effort:high | effort:high | thinkingBudget:8192 | reasoning_effort:high
6557
+ * - 'medium': Anthropic effort:medium | OpenAI effort:medium | Gemini thinkingBudget:4096 | xAI reasoning_effort:medium
6558
+ * - 'high': Anthropic effort:high | OpenAI effort:high | Gemini thinkingBudget:8192 | xAI reasoning_effort:high
6559
+ *
6560
+ * @remarks
6561
+ * `'none'` is the one cross-provider spelling for "thinking off". Anthropic has no
6562
+ * off value in its own effort vocabulary — off there means omitting the `thinking`
6563
+ * wire param entirely — so `'none'` maps to that omission rather than to a value.
6564
+ * Whichever provider is in play, `'none'` also re-enables `temperature`: see
6565
+ * `checkTemperatureConflict` in `thinkingOptionsResolver.ts`.
6566
+ *
6567
+ * The mapping is not model-aware (same posture as every other entry in this table —
6568
+ * see `ModelSpecKey`'s remarks for why thinking availability isn't gated at the call
6569
+ * path). For Gemini specifically, `thinkingBudget: 0` is documented as valid only on
6570
+ * Flash and Flash-Lite and erroring on Pro (see `IGeminiThinkingConfig.thinkingBudget`);
6571
+ * `'none'` on a Pro-family model inherits that same caveat, exactly as an explicit
6572
+ * `providers: [{ provider: 'google', config: { thinkingBudget: 0 } }]` block already did
6573
+ * before this field existed.
6078
6574
  */
6079
- readonly effort?: 'low' | 'medium' | 'high';
6575
+ readonly effort?: 'none' | 'low' | 'medium' | 'high';
6080
6576
  /**
6081
6577
  * Optional per-provider precision blocks. Blocks for providers that don't
6082
6578
  * match the resolved model's provider are silently skipped.
@@ -7225,17 +7721,15 @@ declare const modelSpec: Converter<ModelSpec>;
7225
7721
  * `'advanced'` / `'frontier'`) and sets the tools / thinking request params
7226
7722
  * independently. Thinking composes with any tier without a tier-level capability
7227
7723
  * check — but that is a statement about the tier axis, not a claim that every
7228
- * provider supports thinking: several descriptors declare
7229
- * `thinkingMode: 'unsupported'` (e.g. `copy-paste`, `groq`, `mistral`, `ollama`,
7230
- * `openai-compat`).
7231
- *
7232
- * Thinking availability is declared **per provider**, on the descriptor's
7233
- * `thinkingMode`; the descriptor does not encode per-model thinking availability at
7234
- * all, so a provider that declares support may still have individual models its own
7235
- * API rejects thinking on. (`adaptiveThinkingModelPrefixes` is per-model but selects
7236
- * a wire *shape*, not availability.) What the tier axis guarantees is therefore
7237
- * narrow and exact: a tier selects a model within one provider and never changes the
7238
- * provider, so it never changes `thinkingMode`.
7724
+ * provider supports thinking, or that every model of a thinking-capable provider
7725
+ * does: the descriptor itself declares no thinking-availability field at all.
7726
+ * `AiModelCapability`'s `'thinking'` entry (per-model, RegExp-matched on model id
7727
+ * in `DEFAULT_MODEL_CAPABILITY_CONFIG`) is a listing/filtering signal only — the
7728
+ * call path does not gate a thinking request on it, so an unlisted model is not
7729
+ * rejected, merely absent from a capability-filtered menu. (`adaptiveThinkingModelPrefixes`
7730
+ * is per-model but selects a wire *shape*, not availability.) What the tier axis
7731
+ * guarantees is therefore narrow and exact: a tier selects a model within one
7732
+ * provider and never changes the provider.
7239
7733
  *
7240
7734
  * Do not add a `'tools'` or `'thinking'` key here, and do not hand-roll a
7241
7735
  * `resolveModel` + `resolveModelAlias` walk to emulate one — call
@@ -7317,6 +7811,8 @@ declare namespace Mustache {
7317
7811
  IContextValidationResult,
7318
7812
  IMissingVariableDetail,
7319
7813
  IMustacheTemplateOptions,
7814
+ IRenderedSegment,
7815
+ IRenderedTemplate,
7320
7816
  IVariableRef,
7321
7817
  MustacheEscapeStrategy,
7322
7818
  MustacheTokenType,
@@ -7405,6 +7901,61 @@ declare class MustacheTemplate {
7405
7901
  * @returns Success with the rendered string, or Failure if rendering fails
7406
7902
  */
7407
7903
  render(context: unknown): Result<string>;
7904
+ /**
7905
+ * Renders the template and reports which part of the output came from where.
7906
+ *
7907
+ * @remarks
7908
+ * The rendered `text` is identical to `render`'s. What is added is
7909
+ * `segments`: a document-ordered, contiguous, gapless attribution of every code unit to either
7910
+ * template literal text or a named interpolation. It answers "what is in this output, in what
7911
+ * order, and how much of it is each part" — a prompt's composition, a generated document's
7912
+ * section sizes, a diff-free view of where a template's bulk actually goes.
7913
+ *
7914
+ * **Offsets are computed during the render, never recovered afterwards.** Recovering them by
7915
+ * searching the output for each substituted value is the obvious shortcut and it is unsound: a
7916
+ * value can occur more than once, can render empty, can be a substring of the surrounding
7917
+ * literal text, and can be altered by escaping between input and output. That failure is silent
7918
+ * and produces plausible offsets, which is the worst property a diagnostic can have.
7919
+ *
7920
+ * **Sections, inverted sections and partials are refused, not approximated.** Their rendered
7921
+ * output is not a linear image of the token order — a section may repeat its body, or omit it —
7922
+ * so a segment list walked from the template would mis-describe the result. Failing names the
7923
+ * construct. Comments and set-delimiter tags emit nothing and are skipped; because each
7924
+ * interpolation is rendered from its already-parsed token rather than from a re-parsed slice of
7925
+ * the template, a set-delimiter tag has no effect on what this produces.
7926
+ *
7927
+ * **Every context value is evaluated exactly once, as `render()` evaluates it.** A lambda in the
7928
+ * context is therefore invoked the same number of times either way, and a context whose lambda
7929
+ * answers differently per call is rendered rather than rejected — the returned `segments` always
7930
+ * describe the returned `text`, whatever that text was. (The concatenation identity is asserted
7931
+ * across every shape in this package's test suite, not re-checked at runtime: re-checking means
7932
+ * a second full render, which is precisely what would double those invocations.)
7933
+ *
7934
+ * @param context - The context object for template rendering.
7935
+ * @returns Success with the rendered text and its segment map, or Failure if the
7936
+ * template contains a construct this cannot describe
7937
+ */
7938
+ renderWithSegments(context: unknown): Result<IRenderedTemplate>;
7939
+ /**
7940
+ * Renders one interpolation token against a prepared lookup context.
7941
+ *
7942
+ * @remarks
7943
+ * Delegates to the same `Writer` primitives `render()` reaches through `renderTokens`, so the
7944
+ * value semantics — dotted paths, lambdas, `null` and `undefined` rendering empty, non-string
7945
+ * coercion — are mustache.js's rather than a reimplementation of them.
7946
+ *
7947
+ * The token is passed as a freshly built `[type, name]` array rather than the parsed span. Both
7948
+ * primitives read only the name, and `@types/mustache` declares the parameter as `string[]`
7949
+ * while a parsed span carries its source offsets as numbers — so handing over the span would
7950
+ * need a cast to work around the typings. Building the pair the primitives actually read needs
7951
+ * none.
7952
+ *
7953
+ * @param type - `'name'` for an escaped interpolation, `'&'` for an unescaped one.
7954
+ * @param name - The interpolation's name (may be a dotted path).
7955
+ * @param lookup - The context to resolve the name against.
7956
+ * @returns The rendered value; empty when the name resolves to `null` or `undefined`.
7957
+ */
7958
+ private _renderInterpolation;
7408
7959
  /**
7409
7960
  * Validates the context and renders the template if validation passes.
7410
7961
  * @param context - The context object to validate and render with
@@ -8083,6 +8634,34 @@ declare function resolveProviderModel(descriptor: IAiProviderDescriptor, modelOv
8083
8634
  */
8084
8635
  declare function resolveStructuredOutputCapability(descriptor: IAiProviderDescriptor, modelId: string): IAiStructuredOutputCapability | undefined;
8085
8636
 
8637
+ /**
8638
+ * Applies a provider's declared server-tool/client-tool exclusions to one request's
8639
+ * tools.
8640
+ *
8641
+ * @remarks
8642
+ * The generic half of a rule that used to be a provider-identity branch. The
8643
+ * constraint is declared on the descriptor
8644
+ * (`IAiProviderDescriptor.serverToolsExclusiveWithClientTools`); this reads it
8645
+ * and applies `policy`, so no consumer needs to know *which* provider has the limit
8646
+ * or *what* the limit is. A descriptor that declares nothing, or a request with no
8647
+ * client tools, or one with no server tools, passes through untouched with an empty
8648
+ * report — the exclusion is about the *combination*, and either half alone is legal.
8649
+ *
8650
+ * Sibling to {@link AiAssist.resolveEffectiveTools} rather than an extension of it:
8651
+ * that function filters server tools against what the provider supports at all and
8652
+ * is used by every completion path, most of which have no client tools to weigh.
8653
+ *
8654
+ * @param descriptor - The provider descriptor carrying the declared exclusions.
8655
+ * @param serverTools - The server tools the caller wants to send, if any.
8656
+ * @param clientTools - The client tools the caller wants to send.
8657
+ * @param policy - What to do about a conflict. Defaults to
8658
+ * {@link AiAssist.defaultToolConflictPolicy}.
8659
+ * @returns `Success` with the surviving tools and a report, or `Failure` naming the
8660
+ * conflict when `policy` is `'fail'`.
8661
+ * @public
8662
+ */
8663
+ declare function resolveToolConflicts(descriptor: IAiProviderDescriptor, serverTools: ReadonlyArray<AiServerToolConfig> | undefined, clientTools: ReadonlyArray<IAiClientTool>, policy?: AiToolConflictPolicy): Result<IAiResolvedToolConflicts>;
8664
+
8086
8665
  /**
8087
8666
  * Statuses whose `Retry-After` header is honored.
8088
8667
  *
@@ -8426,6 +9005,39 @@ declare type StructuredOutputRequest = ISchemaStructuredOutputRequest | IJsonObj
8426
9005
  */
8427
9006
  declare const SUPPORTED_SCHEMES: ReadonlyArray<string>;
8428
9007
 
9008
+ /**
9009
+ * Whether a provider has confirmed prompt-cache-relevant `usage` reporting on
9010
+ * the shared Chat Completions / Responses completion paths (streaming and
9011
+ * non-streaming alike).
9012
+ *
9013
+ * @remarks
9014
+ * The completion paths it gates — `callOpenAiCompletion`,
9015
+ * `callOpenAiResponsesCompletion`, the Responses streaming adapter, and
9016
+ * `executeClientToolTurn`'s direct Responses call — are shared by every
9017
+ * `apiFormat: 'openai'` descriptor — xAI Grok, Groq, Mistral, Ollama, and self-hosted
9018
+ * `openai-compat` all route through the same adapter as OpenAI. Only OpenAI
9019
+ * and xAI Grok are confirmed to report cache-relevant `usage` fields; an
9020
+ * ordinary `usage` block from one of the other descriptors (e.g. Groq's plain
9021
+ * `prompt_tokens` / `completion_tokens`, with no cache sub-object at all)
9022
+ * carries no cache information. Normalizing it anyway would stamp
9023
+ * `reports: 'reads'` on a provider that has no prompt-caching concept —
9024
+ * a false cache-reporting signal, not an absent one. So this gate is checked
9025
+ * before every normalize call it governs; when it's `false`, `usage` stays
9026
+ * absent entirely rather than being guessed from whatever shape the wire
9027
+ * happens to send.
9028
+ *
9029
+ * **Streaming Chat Completions is gated by a different predicate.** That path
9030
+ * consults {@link AiAssist.supportsStreamUsageOption} instead, because the
9031
+ * request has to opt in via `stream_options: { include_usage: true }` before
9032
+ * there is any usage to normalize — and that opt-in is `'openai'`-only, since
9033
+ * the other descriptors' tolerance for the unrecognized field is unverified.
9034
+ * The practical consequence is that xAI Grok, which *is* confirmed here, still
9035
+ * gets no `usage` on streaming Chat Completions. If you are tracing a missing
9036
+ * xAI streaming figure, that predicate is the one to read, not this one.
9037
+ * @public
9038
+ */
9039
+ declare function supportsCacheUsageReporting(descriptor: IAiProviderDescriptor): boolean;
9040
+
8429
9041
  /**
8430
9042
  * Whether a provider declares any embedding capability at all.
8431
9043
  *
@@ -8446,6 +9058,77 @@ declare function supportsEmbedding(descriptor: IAiProviderDescriptor): boolean;
8446
9058
  */
8447
9059
  declare function supportsImageGeneration(descriptor: IAiProviderDescriptor): boolean;
8448
9060
 
9061
+ /**
9062
+ * Whether a provider has confirmed tolerance for the request-shape change
9063
+ * {@link AiAssist.IAiCacheRequest.systemBreakpoints} produces on the shared Chat Completions /
9064
+ * Responses paths: splitting `system` into content parts each carrying a
9065
+ * `prompt_cache_breakpoint` field.
9066
+ *
9067
+ * This governs **breakpoints only**. The routing key is a separate capability with a different
9068
+ * support set — see {@link AiAssist.supportsPromptCacheRouting}. An earlier version of this
9069
+ * predicate gated both, which withheld the routing key from xAI, the one provider that most
9070
+ * needs it.
9071
+ *
9072
+ * @remarks
9073
+ * Same sharing problem as {@link AiAssist.supportsStreamUsageOption}, on the write side instead of the
9074
+ * read side: `callOpenAiCompletion` and `callOpenAiResponsesCompletion` are shared by every
9075
+ * `apiFormat: 'openai'` descriptor (xAI Grok, Groq, Mistral, Ollama, self-hosted
9076
+ * `openai-compat`), but only OpenAI's own API is confirmed to accept a `system`/first-item
9077
+ * `content` restructured into an array of parts each carrying an unrecognized
9078
+ * `prompt_cache_breakpoint` field. A
9079
+ * schema-strict server on one of the other descriptors could 400 on it — the identical
9080
+ * failure mode `supportsStreamUsageOption` was written to avoid for `stream_options`. So `cache`
9081
+ * is gated to `descriptor.id === 'openai'` at the dispatch site before it ever reaches these
9082
+ * builders; every other descriptor gets the same request body it would have gotten had the
9083
+ * caller passed no `cache` at all.
9084
+ * @public
9085
+ */
9086
+ declare function supportsPromptCacheBreakpoints(descriptor: IAiProviderDescriptor): boolean;
9087
+
9088
+ /**
9089
+ * Whether a provider accepts an opaque prompt-cache **routing** key, and by what transport.
9090
+ *
9091
+ * @remarks
9092
+ * Distinct from {@link AiAssist.supportsPromptCacheBreakpoints}, and deliberately so. A breakpoint
9093
+ * restructures the request body and needs the server to tolerate an unrecognized field inside
9094
+ * `content`; a routing key is a single opaque string whose only job is to make repeated requests
9095
+ * with a shared prefix land on the same cache-holding server.
9096
+ *
9097
+ * The two were originally gated together, which had a perverse effect: xAI does byte-for-byte
9098
+ * prefix matching from the start of the `messages` array, and its cache is **per-server and
9099
+ * evictable**, so without a routing key a caller can miss on an identical prefix simply by being
9100
+ * routed elsewhere. Measured on `grok-4.3`: two requests sharing an 8,684-token system prefix but
9101
+ * differing in their final user turn returned 2.2% cached, while a byte-identical pair returned
9102
+ * 99.5%. Withholding the routing key from xAI withheld it from the provider whose caching depends
9103
+ * on it most.
9104
+ *
9105
+ * The transport differs by route, which is why this returns a descriptor rather than a boolean:
9106
+ *
9107
+ * | provider | Chat Completions | Responses |
9108
+ * |---|---|---|
9109
+ * | `openai` | `prompt_cache_key` body field | `prompt_cache_key` body field |
9110
+ * | `xai-grok` | `x-grok-conv-id` **header** | `prompt_cache_key` body field |
9111
+ *
9112
+ * Every other `apiFormat: 'openai'` descriptor (Groq, Mistral, Ollama, self-hosted
9113
+ * `openai-compat`) returns `undefined` and gets the request it would have gotten had the caller
9114
+ * passed no `cache` at all — the same unconfirmed-tolerance reasoning as
9115
+ * {@link AiAssist.supportsStreamUsageOption}.
9116
+ * @public
9117
+ */
9118
+ declare function supportsPromptCacheRouting(descriptor: IAiProviderDescriptor): IAiPromptCacheRoutingSupport | undefined;
9119
+
9120
+ /**
9121
+ * Whether a provider's streaming Chat Completions request should carry
9122
+ * `stream_options: { include_usage: true }`. A request-shape gate, not a
9123
+ * capability guess: the shared adapter also carries providers (xAI, Groq,
9124
+ * Mistral, Ollama, self-hosted `openai-compat`) not verified to tolerate an
9125
+ * unrecognized field, so this is `true` only for `'openai'`; every other
9126
+ * provider simply never gets a streaming `usage` block — a safe degradation,
9127
+ * since `IAiStreamDone.usage` is optional everywhere.
9128
+ * @public
9129
+ */
9130
+ declare function supportsStreamUsageOption(descriptor: IAiProviderDescriptor): boolean;
9131
+
8449
9132
  /**
8450
9133
  * Whether `descriptor` declares any structured-output capability at all.
8451
9134
  * @public
@@ -8523,6 +9206,39 @@ declare const uint8ArrayFromBase64: Converter<Uint8Array>;
8523
9206
  */
8524
9207
  declare function usesMaxCompletionTokensField(descriptor: IAiProviderDescriptor): boolean;
8525
9208
 
9209
+ /**
9210
+ * Validates an {@link IAiCacheRequest}'s `systemBreakpoints` against the `system` string they
9211
+ * index into. Thin wrapper over {@link validateCacheBreakpoints} for a caller that holds the
9212
+ * actual string (every `ts-extras` adapter) rather than just its length.
9213
+ * @public
9214
+ */
9215
+ declare function validateAiCacheRequest(system: string, cache: IAiCacheRequest, maxBreakpointWrites?: number): Result<IAiCacheRequest>;
9216
+
9217
+ /**
9218
+ * Validates an {@link IAiCacheRequest}'s `systemBreakpoints` against the length of the `system`
9219
+ * string they index into, and optionally against a per-model write cap.
9220
+ *
9221
+ * @remarks
9222
+ * **Fails loudly, never clamps** (design.md §6.1): a non-ascending, out-of-range, or over-cap
9223
+ * offset returns `fail()`. A silently-clamped breakpoint is precisely the silent, non-caching
9224
+ * failure this stream exists to remove. `systemLength` and `maxBreakpointWrites` are themselves
9225
+ * validated as non-negative integers — checked before `systemBreakpoints` even, so a malformed
9226
+ * value (`NaN`, a negative number, a fraction) fails loudly rather than silently passing every
9227
+ * offset check it should have rejected (`offset >= NaN` is always `false`) or, for the cap,
9228
+ * passing unnoticed on an empty offset list. `maxBreakpointWrites` is left to the caller to supply —
9229
+ * there is no model-keyed cap table in this package yet (design.md §7's `IAiCacheCapability` is
9230
+ * out of this slice's scope), and design.md §5.2 establishes that a caller producing breakpoints
9231
+ * from the three-level `PromptCacheStability` vocabulary never emits more than two, so an
9232
+ * omitted cap is not a soundness gap for that caller — only a direct caller supplying more than a
9233
+ * provider's cap would ever need one enforced here.
9234
+ *
9235
+ * Takes a length rather than {@link validateAiCacheRequest}'s `system` string because a caller
9236
+ * building a plan from an `@fgv/ts-prompt-assist` `IPromptComposition` (`totalChars`) has the
9237
+ * length without holding the resolved body text itself.
9238
+ * @public
9239
+ */
9240
+ declare function validateCacheBreakpoints(systemLength: number, cache: IAiCacheRequest, maxBreakpointWrites?: number): Result<IAiCacheRequest>;
9241
+
8526
9242
  /**
8527
9243
  * Validates the resolved options against per-model registry constraints.
8528
9244
  *