@fgv/ts-extras 5.1.0-26 → 5.1.0-28

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 (149) hide show
  1. package/dist/index.browser.js +2 -2
  2. package/dist/index.browser.js.map +1 -1
  3. package/dist/packlets/ai-assist/apiClient.js +300 -213
  4. package/dist/packlets/ai-assist/apiClient.js.map +1 -1
  5. package/dist/packlets/ai-assist/chatRequestBuilders.js +6 -0
  6. package/dist/packlets/ai-assist/chatRequestBuilders.js.map +1 -1
  7. package/dist/packlets/ai-assist/imageOptionsResolver.js +212 -0
  8. package/dist/packlets/ai-assist/imageOptionsResolver.js.map +1 -0
  9. package/dist/packlets/ai-assist/index.js +1 -0
  10. package/dist/packlets/ai-assist/index.js.map +1 -1
  11. package/dist/packlets/ai-assist/model.js +1 -1
  12. package/dist/packlets/ai-assist/model.js.map +1 -1
  13. package/dist/packlets/ai-assist/registry.js +120 -22
  14. package/dist/packlets/ai-assist/registry.js.map +1 -1
  15. package/dist/packlets/ai-assist/sseParser.js +1 -0
  16. package/dist/packlets/ai-assist/sseParser.js.map +1 -1
  17. package/dist/packlets/ai-assist/streamingAdapters/anthropic.js +17 -12
  18. package/dist/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -1
  19. package/dist/packlets/ai-assist/streamingAdapters/common.js +2 -0
  20. package/dist/packlets/ai-assist/streamingAdapters/common.js.map +1 -1
  21. package/dist/packlets/ai-assist/streamingAdapters/gemini.js +17 -5
  22. package/dist/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -1
  23. package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js +19 -4
  24. package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -1
  25. package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js +20 -5
  26. package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -1
  27. package/dist/packlets/ai-assist/streamingAdapters/proxy.js +9 -3
  28. package/dist/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -1
  29. package/dist/packlets/ai-assist/streamingClient.js +28 -6
  30. package/dist/packlets/ai-assist/streamingClient.js.map +1 -1
  31. package/dist/packlets/ai-assist/thinkingOptionsResolver.js +265 -0
  32. package/dist/packlets/ai-assist/thinkingOptionsResolver.js.map +1 -0
  33. package/dist/packlets/conversion/converters.js +1 -0
  34. package/dist/packlets/conversion/converters.js.map +1 -1
  35. package/dist/packlets/crypto-utils/converters.js +24 -4
  36. package/dist/packlets/crypto-utils/converters.js.map +1 -1
  37. package/dist/packlets/crypto-utils/hpkeProvider.js +333 -0
  38. package/dist/packlets/crypto-utils/hpkeProvider.js.map +1 -0
  39. package/dist/packlets/crypto-utils/index.browser.js +3 -0
  40. package/dist/packlets/crypto-utils/index.browser.js.map +1 -1
  41. package/dist/packlets/crypto-utils/index.js +2 -0
  42. package/dist/packlets/crypto-utils/index.js.map +1 -1
  43. package/dist/packlets/crypto-utils/keystore/converters.js +2 -2
  44. package/dist/packlets/crypto-utils/keystore/converters.js.map +1 -1
  45. package/dist/packlets/crypto-utils/keystore/keyStore.js +108 -2
  46. package/dist/packlets/crypto-utils/keystore/keyStore.js.map +1 -1
  47. package/dist/packlets/crypto-utils/keystore/model.js.map +1 -1
  48. package/dist/packlets/crypto-utils/model.js +21 -0
  49. package/dist/packlets/crypto-utils/model.js.map +1 -1
  50. package/dist/packlets/crypto-utils/nodeCryptoProvider.js +74 -0
  51. package/dist/packlets/crypto-utils/nodeCryptoProvider.js.map +1 -1
  52. package/dist/ts-extras.d.ts +1029 -137
  53. package/dist/tsdoc-metadata.json +1 -1
  54. package/lib/index.browser.d.ts +2 -2
  55. package/lib/index.browser.d.ts.map +1 -1
  56. package/lib/index.browser.js +4 -3
  57. package/lib/index.browser.js.map +1 -1
  58. package/lib/packlets/ai-assist/apiClient.d.ts +29 -85
  59. package/lib/packlets/ai-assist/apiClient.d.ts.map +1 -1
  60. package/lib/packlets/ai-assist/apiClient.js +300 -213
  61. package/lib/packlets/ai-assist/apiClient.js.map +1 -1
  62. package/lib/packlets/ai-assist/chatRequestBuilders.d.ts.map +1 -1
  63. package/lib/packlets/ai-assist/chatRequestBuilders.js +6 -0
  64. package/lib/packlets/ai-assist/chatRequestBuilders.js.map +1 -1
  65. package/lib/packlets/ai-assist/imageOptionsResolver.d.ts +74 -0
  66. package/lib/packlets/ai-assist/imageOptionsResolver.d.ts.map +1 -0
  67. package/lib/packlets/ai-assist/imageOptionsResolver.js +216 -0
  68. package/lib/packlets/ai-assist/imageOptionsResolver.js.map +1 -0
  69. package/lib/packlets/ai-assist/index.d.ts +2 -1
  70. package/lib/packlets/ai-assist/index.d.ts.map +1 -1
  71. package/lib/packlets/ai-assist/index.js +4 -1
  72. package/lib/packlets/ai-assist/index.js.map +1 -1
  73. package/lib/packlets/ai-assist/model.d.ts +410 -35
  74. package/lib/packlets/ai-assist/model.d.ts.map +1 -1
  75. package/lib/packlets/ai-assist/model.js +1 -1
  76. package/lib/packlets/ai-assist/model.js.map +1 -1
  77. package/lib/packlets/ai-assist/registry.d.ts.map +1 -1
  78. package/lib/packlets/ai-assist/registry.js +120 -22
  79. package/lib/packlets/ai-assist/registry.js.map +1 -1
  80. package/lib/packlets/ai-assist/sseParser.d.ts.map +1 -1
  81. package/lib/packlets/ai-assist/sseParser.js +1 -0
  82. package/lib/packlets/ai-assist/sseParser.js.map +1 -1
  83. package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts +2 -1
  84. package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts.map +1 -1
  85. package/lib/packlets/ai-assist/streamingAdapters/anthropic.js +17 -12
  86. package/lib/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -1
  87. package/lib/packlets/ai-assist/streamingAdapters/common.d.ts +5 -1
  88. package/lib/packlets/ai-assist/streamingAdapters/common.d.ts.map +1 -1
  89. package/lib/packlets/ai-assist/streamingAdapters/common.js +2 -0
  90. package/lib/packlets/ai-assist/streamingAdapters/common.js.map +1 -1
  91. package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts +2 -1
  92. package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts.map +1 -1
  93. package/lib/packlets/ai-assist/streamingAdapters/gemini.js +17 -5
  94. package/lib/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -1
  95. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts +2 -1
  96. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts.map +1 -1
  97. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js +19 -4
  98. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -1
  99. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts +2 -1
  100. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts.map +1 -1
  101. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js +20 -5
  102. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -1
  103. package/lib/packlets/ai-assist/streamingAdapters/proxy.d.ts.map +1 -1
  104. package/lib/packlets/ai-assist/streamingAdapters/proxy.js +9 -3
  105. package/lib/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -1
  106. package/lib/packlets/ai-assist/streamingClient.d.ts.map +1 -1
  107. package/lib/packlets/ai-assist/streamingClient.js +28 -6
  108. package/lib/packlets/ai-assist/streamingClient.js.map +1 -1
  109. package/lib/packlets/ai-assist/thinkingOptionsResolver.d.ts +71 -0
  110. package/lib/packlets/ai-assist/thinkingOptionsResolver.d.ts.map +1 -0
  111. package/lib/packlets/ai-assist/thinkingOptionsResolver.js +270 -0
  112. package/lib/packlets/ai-assist/thinkingOptionsResolver.js.map +1 -0
  113. package/lib/packlets/conversion/converters.d.ts.map +1 -1
  114. package/lib/packlets/conversion/converters.js +1 -0
  115. package/lib/packlets/conversion/converters.js.map +1 -1
  116. package/lib/packlets/crypto-utils/converters.d.ts +12 -1
  117. package/lib/packlets/crypto-utils/converters.d.ts.map +1 -1
  118. package/lib/packlets/crypto-utils/converters.js +25 -5
  119. package/lib/packlets/crypto-utils/converters.js.map +1 -1
  120. package/lib/packlets/crypto-utils/hpkeProvider.d.ts +142 -0
  121. package/lib/packlets/crypto-utils/hpkeProvider.d.ts.map +1 -0
  122. package/lib/packlets/crypto-utils/hpkeProvider.js +337 -0
  123. package/lib/packlets/crypto-utils/hpkeProvider.js.map +1 -0
  124. package/lib/packlets/crypto-utils/index.browser.d.ts +1 -0
  125. package/lib/packlets/crypto-utils/index.browser.d.ts.map +1 -1
  126. package/lib/packlets/crypto-utils/index.browser.js +5 -1
  127. package/lib/packlets/crypto-utils/index.browser.js.map +1 -1
  128. package/lib/packlets/crypto-utils/index.d.ts +1 -0
  129. package/lib/packlets/crypto-utils/index.d.ts.map +1 -1
  130. package/lib/packlets/crypto-utils/index.js +4 -1
  131. package/lib/packlets/crypto-utils/index.js.map +1 -1
  132. package/lib/packlets/crypto-utils/keystore/converters.js +1 -1
  133. package/lib/packlets/crypto-utils/keystore/converters.js.map +1 -1
  134. package/lib/packlets/crypto-utils/keystore/keyStore.d.ts +32 -2
  135. package/lib/packlets/crypto-utils/keystore/keyStore.d.ts.map +1 -1
  136. package/lib/packlets/crypto-utils/keystore/keyStore.js +116 -10
  137. package/lib/packlets/crypto-utils/keystore/keyStore.js.map +1 -1
  138. package/lib/packlets/crypto-utils/keystore/model.d.ts +21 -3
  139. package/lib/packlets/crypto-utils/keystore/model.d.ts.map +1 -1
  140. package/lib/packlets/crypto-utils/keystore/model.js.map +1 -1
  141. package/lib/packlets/crypto-utils/model.d.ts +165 -9
  142. package/lib/packlets/crypto-utils/model.d.ts.map +1 -1
  143. package/lib/packlets/crypto-utils/model.js +22 -1
  144. package/lib/packlets/crypto-utils/model.js.map +1 -1
  145. package/lib/packlets/crypto-utils/nodeCryptoProvider.d.ts +39 -0
  146. package/lib/packlets/crypto-utils/nodeCryptoProvider.d.ts.map +1 -1
  147. package/lib/packlets/crypto-utils/nodeCryptoProvider.js +74 -0
  148. package/lib/packlets/crypto-utils/nodeCryptoProvider.js.map +1 -1
  149. package/package.json +13 -13
@@ -1 +1 @@
1
- {"version":3,"file":"model.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/model.ts"],"names":[],"mappings":";AAAA,kCAAkC;AAClC,EAAE;AACF,+EAA+E;AAC/E,gFAAgF;AAChF,+EAA+E;AAC/E,4EAA4E;AAC5E,wEAAwE;AACxE,2DAA2D;AAC3D,EAAE;AACF,iFAAiF;AACjF,kDAAkD;AAClD,EAAE;AACF,6EAA6E;AAC7E,2EAA2E;AAC3E,8EAA8E;AAC9E,yEAAyE;AACzE,gFAAgF;AAChF,gFAAgF;AAChF,YAAY;;;AAoCZ,8BAEC;AAqMD,oCAsBC;AAnOD;;;;;GAKG;AACH,SAAgB,SAAS,CAAC,KAAmB;IAC3C,OAAO,QAAQ,KAAK,CAAC,QAAQ,WAAW,KAAK,CAAC,MAAM,EAAE,CAAC;AACzD,CAAC;AAuBD,+EAA+E;AAC/E,WAAW;AACX,+EAA+E;AAE/E;;;;GAIG;AACH,MAAa,QAAQ;IAYnB,YAAmB,IAAY,EAAE,MAAc,EAAE,WAA+C;QAC9F,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,WAAW,GAAG,WAAW,aAAX,WAAW,cAAX,WAAW,GAAI,EAAE,CAAC;IACvC,CAAC;IAED;;;;OAIG;IACH,IAAW,QAAQ;QACjB,MAAM,QAAQ,GACZ,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC;YACzB,CAAC,CAAC,QAAQ,IAAI,CAAC,WAAW,CAAC,MAAM,qDAAqD;YACtF,CAAC,CAAC,EAAE,CAAC;QACT,OAAO,GAAG,IAAI,CAAC,IAAI,GAAG,QAAQ,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;IACrD,CAAC;CACF;AA9BD,4BA8BC;AA6ED;;;GAGG;AACU,QAAA,gBAAgB,GAAgC,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAExF;;;GAGG;AACU,QAAA,mBAAmB,GAAiB,MAAM,CAAC;AAiCxD;;;;;;;;;;;;;;GAcG;AACH,SAAgB,YAAY,CAAC,IAAe,EAAE,OAAgB;IAC5D,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,sCAAsC;IACtC,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,IAAI,IAAI,EAAE,CAAC;QAC7C,OAAO,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IACrC,CAAC;IAED,sBAAsB;IACtB,IAAI,2BAAmB,IAAI,IAAI,EAAE,CAAC;QAChC,OAAO,YAAY,CAAC,IAAI,CAAC,2BAAmB,CAAC,CAAC,CAAC;IACjD,CAAC;IAED,yCAAyC;IACzC,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACrC,6FAA6F;IAC7F,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC;AAC7B,CAAC;AA2ZD;;;GAGG;AACU,QAAA,iBAAiB,GAAsB;IAClD,SAAS,EAAE,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC;CACxC,CAAC","sourcesContent":["// Copyright (c) 2026 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n/**\n * Core types for AI assist: prompt class, provider descriptors, settings, and chat messages.\n * @packageDocumentation\n */\n\nimport { type Result } from '@fgv/ts-utils';\n\n// ============================================================================\n// Image Data\n// ============================================================================\n\n/**\n * Universal image representation used for both image input (vision prompts)\n * and image output (generation responses).\n *\n * @remarks\n * The base64 string is raw — no `data:` URL prefix. Use {@link AiAssist.toDataUrl} to\n * format it for browser-display contexts.\n *\n * @public\n */\nexport interface IAiImageData {\n /** MIME type, e.g. `'image/png'`, `'image/jpeg'`, `'image/webp'`. */\n readonly mimeType: string;\n /** Base64-encoded image bytes (no `data:` prefix). */\n readonly base64: string;\n}\n\n/**\n * Formats an {@link IAiImageData} as a `data:` URL suitable for browser display.\n * @param image - The image to format\n * @returns A `data:<mime>;base64,<data>` URL string\n * @public\n */\nexport function toDataUrl(image: IAiImageData): string {\n return `data:${image.mimeType};base64,${image.base64}`;\n}\n\n/**\n * Image attachment for a vision (image-input) prompt.\n *\n * @remarks\n * Extends {@link IAiImageData} with an OpenAI-specific `detail` hint that is\n * silently ignored by Anthropic, Gemini, and other providers.\n *\n * @public\n */\nexport interface IAiImageAttachment extends IAiImageData {\n /**\n * OpenAI vision detail hint:\n * - `'low'`: faster, cheaper, lower fidelity\n * - `'high'`: slower, more expensive, higher fidelity\n * - `'auto'` (default): provider chooses\n *\n * Ignored by providers other than OpenAI.\n */\n readonly detail?: 'low' | 'high' | 'auto';\n}\n\n// ============================================================================\n// AiPrompt\n// ============================================================================\n\n/**\n * A structured AI prompt with system/user split for direct API calls,\n * and a lazily-constructed combined version for copy/paste workflows.\n * @public\n */\nexport class AiPrompt {\n /** System instructions: schema documentation, format rules, general guidance. */\n public readonly system: string;\n /** User request: the specific entity generation request. */\n public readonly user: string;\n /**\n * Optional image attachments. When present, vision-capable providers will\n * include them in the user message; non-vision providers will reject the\n * call up front (see {@link AiAssist.IAiProviderDescriptor.acceptsImageInput}).\n */\n public readonly attachments: ReadonlyArray<IAiImageAttachment>;\n\n public constructor(user: string, system: string, attachments?: ReadonlyArray<IAiImageAttachment>) {\n this.system = system;\n this.user = user;\n this.attachments = attachments ?? [];\n }\n\n /**\n * Combined single-string version (user + system joined) for copy/paste.\n * When attachments are present, includes a sentinel noting they aren't\n * part of the copied text.\n */\n public get combined(): string {\n const sentinel =\n this.attachments.length > 0\n ? `\\n\\n[${this.attachments.length} image attachment(s) — not included in copied text]`\n : '';\n return `${this.user}${sentinel}\\n\\n${this.system}`;\n }\n}\n\n// ============================================================================\n// Chat Message\n// ============================================================================\n\n/**\n * A single chat message in OpenAI format.\n * @public\n */\nexport interface IChatMessage {\n /** Message role */\n readonly role: 'system' | 'user' | 'assistant';\n /** Message content */\n readonly content: string;\n}\n\n// ============================================================================\n// Server-Side Tools\n// ============================================================================\n\n/**\n * Built-in server-side tool types supported across providers.\n * @public\n */\nexport type AiServerToolType = 'web_search';\n\n/**\n * Configuration specific to web search tools.\n * @public\n */\nexport interface IAiWebSearchToolConfig {\n readonly type: 'web_search';\n /** Optional: restrict search to these domains. */\n readonly allowedDomains?: ReadonlyArray<string>;\n /** Optional: exclude these domains from search. */\n readonly blockedDomains?: ReadonlyArray<string>;\n /** Optional: max number of searches per request. */\n readonly maxUses?: number;\n /**\n * Optional: enable image understanding during web search.\n * When true, the model can view and analyze images found during search.\n * Currently supported by xAI only; ignored by other providers.\n */\n readonly enableImageUnderstanding?: boolean;\n}\n\n/**\n * Union of all server-side tool configurations. Discriminated on `type`.\n * @public\n */\nexport type AiServerToolConfig = IAiWebSearchToolConfig;\n\n/**\n * Declares a tool as enabled/disabled in provider settings.\n * Tools are disabled by default — consuming apps must opt in explicitly.\n * @public\n */\nexport interface IAiToolEnablement {\n /** Which tool type. */\n readonly type: AiServerToolType;\n /** Whether this tool is enabled by default for this provider. */\n readonly enabled: boolean;\n /** Optional tool-specific configuration. */\n readonly config?: AiServerToolConfig;\n}\n\n// ============================================================================\n// Model Specification\n// ============================================================================\n\n/**\n * Known context keys for model specification maps.\n * @public\n */\nexport type ModelSpecKey = 'base' | 'tools' | 'image';\n\n/**\n * All valid {@link ModelSpecKey} values.\n * @public\n */\nexport const allModelSpecKeys: ReadonlyArray<ModelSpecKey> = ['base', 'tools', 'image'];\n\n/**\n * Default context key used as fallback when resolving a {@link ModelSpec}.\n * @public\n */\nexport const MODEL_SPEC_BASE_KEY: ModelSpecKey = 'base';\n\n/**\n * A model specification: either a simple model string or a record mapping\n * context keys to nested model specs.\n *\n * @remarks\n * A bare string is equivalent to `{ base: string }`. This keeps the simple\n * case simple while allowing context-aware model selection (e.g. different\n * models for tool-augmented vs. base completions).\n *\n * @example\n * ```typescript\n * // Simple — same model for all contexts:\n * const simple: ModelSpec = 'grok-4-1-fast';\n *\n * // Context-aware — reasoning model when tools are active:\n * const split: ModelSpec = { base: 'grok-4-1-fast', tools: 'grok-4-1-fast-reasoning' };\n *\n * // Future nested — per-tool model selection:\n * const nested: ModelSpec = { base: 'grok-fast', tools: { base: 'grok-r', image: 'grok-v' } };\n * ```\n * @public\n */\nexport interface IModelSpecMap {\n readonly [key: string]: ModelSpec;\n}\n\n/**\n * @public\n */\nexport type ModelSpec = string | IModelSpecMap;\n\n/**\n * Resolves a {@link ModelSpec} to a concrete model string given an optional context key.\n *\n * @remarks\n * Resolution rules:\n * 1. If the spec is a string, return it directly (context is irrelevant).\n * 2. If the spec is an object and the context key exists, recurse into that branch.\n * 3. Otherwise, fall back to the {@link MODEL_SPEC_BASE_KEY | 'base'} key.\n * 4. If neither context nor `'base'` exists, use the first available value.\n *\n * @param spec - The model specification to resolve\n * @param context - Optional context key (e.g. `'tools'`)\n * @returns The resolved model string\n * @public\n */\nexport function resolveModel(spec: ModelSpec, context?: string): string {\n if (typeof spec === 'string') {\n return spec;\n }\n\n // Try the requested context key first\n if (context !== undefined && context in spec) {\n return resolveModel(spec[context]);\n }\n\n // Fall back to 'base'\n if (MODEL_SPEC_BASE_KEY in spec) {\n return resolveModel(spec[MODEL_SPEC_BASE_KEY]);\n }\n\n // Last resort: first value in the record\n const first = Object.values(spec)[0];\n /* c8 ignore next 3 - defensive: only reachable with empty object (prevented by converter) */\n if (first === undefined) {\n return '';\n }\n return resolveModel(first);\n}\n\n// ============================================================================\n// Provider Descriptor\n// ============================================================================\n\n/**\n * All known AI provider identifiers.\n * @public\n */\nexport type AiProviderId =\n | 'copy-paste'\n | 'xai-grok'\n | 'openai'\n | 'openai-compat'\n | 'anthropic'\n | 'google-gemini'\n | 'groq'\n | 'mistral'\n | 'ollama';\n\n/**\n * API format categories for provider routing.\n * @public\n */\nexport type AiApiFormat = 'openai' | 'anthropic' | 'gemini';\n\n/**\n * API format categories for image-generation provider routing.\n *\n * @remarks\n * - `'openai-images'` — OpenAI Images API. Routes to `/images/generations`\n * (text-only) or `/images/edits` (when reference images are present).\n * - `'xai-images'` — xAI Images API. Same wire shape as OpenAI but text-only;\n * no reference-image support on grok-2-image.\n * - `'gemini-imagen'` — Google Imagen `:predict` endpoint. Text-only.\n * - `'gemini-image-out'` — Google Gemini chat-style `:generateContent`\n * endpoint that returns image parts (Gemini 2.5 Flash Image / \"Nano\n * Banana\"). Accepts reference images.\n *\n * @public\n */\nexport type AiImageApiFormat = 'openai-images' | 'gemini-imagen' | 'xai-images' | 'gemini-image-out';\n\n// ============================================================================\n// Completion Response\n// ============================================================================\n\n/**\n * Result of an AI provider completion call.\n * @public\n */\nexport interface IAiCompletionResponse {\n /** The generated text content */\n readonly content: string;\n /** Whether the response was truncated due to token limits */\n readonly truncated: boolean;\n}\n\n// ============================================================================\n// Streaming Events\n// ============================================================================\n\n/**\n * A text-content delta arriving during a streaming completion.\n * @public\n */\nexport interface IAiStreamTextDelta {\n readonly type: 'text-delta';\n /** The newly arrived text fragment. */\n readonly delta: string;\n}\n\n/**\n * A server-side tool progress event arriving during a streaming completion.\n * Surfaced for providers that emit explicit tool-progress markers (OpenAI\n * Responses API, Anthropic). Gemini's grounding doesn't emit these.\n * @public\n */\nexport interface IAiStreamToolEvent {\n readonly type: 'tool-event';\n /** Which server-side tool this event describes. */\n readonly toolType: AiServerToolType;\n /** Tool lifecycle phase. */\n readonly phase: 'started' | 'completed';\n /**\n * Optional provider-specific detail. For web_search this is typically the\n * search query when available; format varies by provider.\n */\n readonly detail?: string;\n}\n\n/**\n * Terminal success event for a streaming completion. Carries the aggregated\n * full text and truncation status for callers that want both the progressive\n * UI and the complete result.\n * @public\n */\nexport interface IAiStreamDone {\n readonly type: 'done';\n /** Whether the response was truncated due to token limits. */\n readonly truncated: boolean;\n /** The full concatenated text from all `text-delta` events. */\n readonly fullText: string;\n}\n\n/**\n * Terminal failure event for a streaming completion. After this event no\n * further events are emitted.\n *\n * @remarks\n * Connection-time failures (auth, network, pre-flight CORS rejection) are\n * surfaced via the outer `Result.fail` returned by\n * `callProviderCompletionStream` rather than as an `error` event, so callers\n * can distinguish \"didn't start\" from \"started but errored mid-stream.\"\n *\n * @public\n */\nexport interface IAiStreamError {\n readonly type: 'error';\n readonly message: string;\n}\n\n/**\n * Discriminated union of events emitted by a streaming completion.\n * @public\n */\nexport type IAiStreamEvent = IAiStreamTextDelta | IAiStreamToolEvent | IAiStreamDone | IAiStreamError;\n\n/**\n * Describes a single AI provider — single source of truth for all metadata.\n * @public\n */\nexport interface IAiProviderDescriptor {\n /** Provider identifier (e.g. 'xai-grok', 'anthropic') */\n readonly id: AiProviderId;\n /** Human-readable label (e.g. \"xAI Grok\") */\n readonly label: string;\n /** Button label for action buttons (e.g. \"AI Assist | Grok\") */\n readonly buttonLabel: string;\n /** Whether this provider requires an API key secret */\n readonly needsSecret: boolean;\n /** Which API adapter format to use */\n readonly apiFormat: AiApiFormat;\n /** Base URL for the API (e.g. 'https://api.x.ai/v1') */\n readonly baseUrl: string;\n /** Default model specification — string or context-aware map. */\n readonly defaultModel: ModelSpec;\n /** Which server-side tools this provider supports (empty = none). */\n readonly supportedTools: ReadonlyArray<AiServerToolType>;\n /** Whether this provider's API enforces CORS restrictions that prevent direct browser calls. */\n readonly corsRestricted: boolean;\n /**\n * Whether this provider's streaming completion endpoint requires a proxy\n * for direct browser calls. Some providers gate streaming separately from\n * non-streaming (rare), so this is tracked independently from\n * {@link IAiProviderDescriptor.corsRestricted}.\n *\n * @remarks\n * When `true`, `callProviderCompletionStream` rejects up front unless the\n * call is being routed through a proxy.\n */\n readonly streamingCorsRestricted: boolean;\n /**\n * Whether this provider's chat completions API accepts image input\n * (i.e. supports vision prompts). When false, calls with\n * `prompt.attachments` are rejected up front.\n */\n readonly acceptsImageInput: boolean;\n /**\n * Image-generation capabilities, scoped to model id prefixes. Empty or\n * undefined means the provider does not support image generation.\n *\n * @remarks\n * The dispatcher matches the resolved model id against each rule's\n * `modelPrefix` and selects the longest match (see\n * {@link AiAssist.resolveImageCapability}). An empty `modelPrefix` is the\n * catch-all and matches every model id.\n *\n * Multiple entries support providers that host more than one image-API\n * surface under one baseUrl. Google Gemini is the canonical case: the\n * `imagen-*` family is predict-only via `:predict`, while\n * `gemini-2.5-flash-image` uses chat-style `:generateContent` and accepts\n * reference images. Listing both lets callers pick the right model and the\n * dispatcher routes accordingly.\n *\n * Image-model selection reuses the existing `image` {@link ModelSpecKey}.\n * Providers that declare `imageGeneration` should declare a model in\n * `defaultModel.image`, e.g. `{ base: 'gpt-4o', image: 'dall-e-3' }`.\n */\n readonly imageGeneration?: ReadonlyArray<IAiImageModelCapability>;\n}\n\n/**\n * Image-generation capability for a model family within a provider. Used as\n * an entry in {@link IAiProviderDescriptor.imageGeneration}.\n *\n * @public\n */\nexport interface IAiImageModelCapability {\n /**\n * Prefix matched against the resolved image model id. The empty string is\n * the catch-all and matches every model. When multiple rules' prefixes\n * match a model id, the longest prefix wins; ties are broken by\n * first-encountered.\n */\n readonly modelPrefix: string;\n /** API format used to dispatch requests for matching models. */\n readonly format: AiImageApiFormat;\n /**\n * Whether matching models accept reference images via\n * {@link AiAssist.IAiImageGenerationParams.referenceImages}. When false or\n * undefined, calls that include reference images are rejected up front.\n *\n * @remarks\n * Per-model constraints beyond ref support (e.g. dall-e-3 ignores edits)\n * are not validated here and surface as provider 400s, consistent with the\n * existing image-generation policy.\n */\n readonly acceptsImageReferenceInput?: boolean;\n}\n\n// ============================================================================\n// Image Generation\n// ============================================================================\n\n/**\n * Options for image generation requests.\n *\n * @remarks\n * Provider compatibility is documented per field. The library does not\n * pre-validate against per-model constraints (e.g. `dall-e-3` rejects\n * `count > 1`); provider 400 errors surface through the failure path.\n *\n * @public\n */\nexport interface IAiImageGenerationOptions {\n /**\n * Image dimensions. Used by openai-format providers (mapped to the\n * provider's `size` field). Ignored by Imagen — use\n * {@link IAiImageGenerationOptions.imagen} `aspectRatio` instead.\n *\n * Note: each model has its own accepted set; `dall-e-3` only accepts the\n * values listed here.\n */\n readonly size?: '1024x1024' | '1024x1792' | '1792x1024' | 'auto';\n /**\n * Number of images to generate. Default 1.\n *\n * Note: `dall-e-3` rejects `count > 1`.\n */\n readonly count?: number;\n /** Generation quality hint where supported. */\n readonly quality?: 'standard' | 'high';\n /** Random seed for reproducibility, where supported. */\n readonly seed?: number;\n /**\n * Imagen-specific options. Ignored by other providers.\n */\n readonly imagen?: {\n readonly negativePrompt?: string;\n readonly aspectRatio?: '1:1' | '3:4' | '4:3' | '9:16' | '16:9';\n };\n}\n\n/**\n * Parameters for an image-generation request.\n * @public\n */\nexport interface IAiImageGenerationParams {\n /** The text prompt describing the desired image. */\n readonly prompt: string;\n /** Optional generation options. */\n readonly options?: IAiImageGenerationOptions;\n /**\n * Optional reference images. When present, the provider will use them as\n * visual context (e.g. to preserve a character's appearance across multiple\n * generations). The dispatcher resolves the\n * {@link AiAssist.IAiImageModelCapability} for the requested model and\n * rejects the call up front if `acceptsImageReferenceInput` is not set on\n * the matching capability. An empty array is treated identically to\n * `undefined`.\n */\n readonly referenceImages?: ReadonlyArray<IAiImageAttachment>;\n}\n\n/**\n * A single generated image.\n * @public\n */\nexport interface IAiGeneratedImage extends IAiImageData {\n /**\n * The prompt as rewritten by the provider, if any. OpenAI's image models\n * commonly rewrite prompts; other providers do not.\n */\n readonly revisedPrompt?: string;\n}\n\n// ============================================================================\n// Model Catalog (listModels)\n// ============================================================================\n\n/**\n * Capability vocabulary used to describe what a model can do. Used as both\n * a filter and as a tag in {@link AiAssist.IAiModelInfo.capabilities}.\n *\n * @remarks\n * Adding a new capability is cheap; adding the *first* one after consumers\n * already exist forces churn. The initial vocabulary is intentionally broad\n * even though only `image-generation` is fully exercised today.\n *\n * @public\n */\nexport type AiModelCapability = 'chat' | 'tools' | 'vision' | 'image-generation';\n\n/**\n * Information about a single model returned by a provider's list endpoint,\n * with capabilities already resolved (native + config rules).\n * @public\n */\nexport interface IAiModelInfo {\n /** Provider-native model identifier. */\n readonly id: string;\n /** Resolved capability set — union of native declarations and config rules. */\n readonly capabilities: ReadonlySet<AiModelCapability>;\n /** Friendly name for display, when known. */\n readonly displayName?: string;\n}\n\n/**\n * One rule in an {@link IAiModelCapabilityConfig}. Multiple rules can match\n * a single model — their capability arrays are unioned.\n * @public\n */\nexport interface IAiModelCapabilityRule {\n /** RegExp tested against the model id (using `.test`). */\n readonly idPattern: RegExp;\n /** Capabilities this rule attributes to matching models. */\n readonly capabilities: ReadonlyArray<AiModelCapability>;\n /**\n * Friendly display-name override for matching models. The function form\n * lets one rule format many ids (e.g. `(id) => id.toUpperCase()`).\n * If multiple matching rules supply `displayName`, the first match wins.\n */\n readonly displayName?: string | ((id: string) => string);\n}\n\n/**\n * Configuration that maps model id patterns to capabilities. Used to\n * augment (or, where the provider supplies no capability info, fully\n * derive) the capability set for each listed model.\n * @public\n */\nexport interface IAiModelCapabilityConfig {\n /** Per-provider rules. Tried before {@link AiAssist.IAiModelCapabilityConfig.global}. */\n readonly perProvider?: { readonly [P in AiProviderId]?: ReadonlyArray<IAiModelCapabilityRule> };\n /** Cross-provider fallback rules. */\n readonly global?: ReadonlyArray<IAiModelCapabilityRule>;\n}\n\n/**\n * Result of an image-generation call.\n * @public\n */\nexport interface IAiImageGenerationResponse {\n /** The generated images, in provider-returned order. */\n readonly images: ReadonlyArray<IAiGeneratedImage>;\n}\n\n// ============================================================================\n// Settings\n// ============================================================================\n\n/**\n * Configuration for a single AI assist provider.\n * @public\n */\nexport interface IAiAssistProviderConfig {\n /** Which provider this configures */\n readonly provider: AiProviderId;\n /** For API-based providers: the keystore secret name holding the API key */\n readonly secretName?: string;\n /** Optional model override — string or context-aware map. */\n readonly model?: ModelSpec;\n /** Tool enablement/configuration. Tools are disabled unless explicitly enabled. */\n readonly tools?: ReadonlyArray<IAiToolEnablement>;\n /**\n * Optional caller-supplied endpoint URL (http/https). Overrides\n * `descriptor.baseUrl` for this provider. Used to point a provider at a\n * self-hosted server (Ollama, LM Studio, llama.cpp's openai-server) or a\n * local proxy. Validation lives in `@fgv/ts-extras` — query strings,\n * fragments, and userinfo are rejected.\n */\n readonly endpoint?: string;\n}\n\n/**\n * AI assist settings — which providers are enabled and their configuration.\n * @public\n */\nexport interface IAiAssistSettings {\n /** Enabled providers and their configuration. */\n readonly providers: ReadonlyArray<IAiAssistProviderConfig>;\n /** Which enabled provider is the default for the main button. Falls back to first in list. */\n readonly defaultProvider?: AiProviderId;\n /** Optional proxy URL for routing API requests through a backend server (e.g. `http://localhost:3002`). */\n readonly proxyUrl?: string;\n /** When true, route all providers through the proxy. When false (default), only CORS-restricted providers use the proxy. */\n readonly proxyAllProviders?: boolean;\n}\n\n/**\n * Default AI assist settings (copy-paste only).\n * @public\n */\nexport const DEFAULT_AI_ASSIST: IAiAssistSettings = {\n providers: [{ provider: 'copy-paste' }]\n};\n\n// ============================================================================\n// Keystore Interface\n// ============================================================================\n\n/**\n * Minimal keystore interface for AI assist API key resolution.\n * Satisfied structurally by the concrete `KeyStore` class from `@fgv/ts-extras`.\n * @public\n */\nexport interface IAiAssistKeyStore {\n /** Whether the keystore is currently unlocked */\n readonly isUnlocked: boolean;\n /** Check if a named secret exists */\n hasSecret(name: string): Result<boolean>;\n /** Get an API key by secret name */\n getApiKey(name: string): Result<string>;\n}\n"]}
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/model.ts"],"names":[],"mappings":";AAAA,kCAAkC;AAClC,EAAE;AACF,+EAA+E;AAC/E,gFAAgF;AAChF,+EAA+E;AAC/E,4EAA4E;AAC5E,wEAAwE;AACxE,2DAA2D;AAC3D,EAAE;AACF,iFAAiF;AACjF,kDAAkD;AAClD,EAAE;AACF,6EAA6E;AAC7E,2EAA2E;AAC3E,8EAA8E;AAC9E,yEAAyE;AACzE,gFAAgF;AAChF,gFAAgF;AAChF,YAAY;;;AAqCZ,8BAEC;AAqMD,oCAsBC;AAnOD;;;;;GAKG;AACH,SAAgB,SAAS,CAAC,KAAmB;IAC3C,OAAO,QAAQ,KAAK,CAAC,QAAQ,WAAW,KAAK,CAAC,MAAM,EAAE,CAAC;AACzD,CAAC;AAuBD,+EAA+E;AAC/E,WAAW;AACX,+EAA+E;AAE/E;;;;GAIG;AACH,MAAa,QAAQ;IAYnB,YAAmB,IAAY,EAAE,MAAc,EAAE,WAA+C;QAC9F,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,WAAW,GAAG,WAAW,aAAX,WAAW,cAAX,WAAW,GAAI,EAAE,CAAC;IACvC,CAAC;IAED;;;;OAIG;IACH,IAAW,QAAQ;QACjB,MAAM,QAAQ,GACZ,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC;YACzB,CAAC,CAAC,QAAQ,IAAI,CAAC,WAAW,CAAC,MAAM,qDAAqD;YACtF,CAAC,CAAC,EAAE,CAAC;QACT,OAAO,GAAG,IAAI,CAAC,IAAI,GAAG,QAAQ,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;IACrD,CAAC;CACF;AA9BD,4BA8BC;AA6ED;;;GAGG;AACU,QAAA,gBAAgB,GAAgC,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;AAEpG;;;GAGG;AACU,QAAA,mBAAmB,GAAiB,MAAM,CAAC;AAiCxD;;;;;;;;;;;;;;GAcG;AACH,SAAgB,YAAY,CAAC,IAAe,EAAE,OAAgB;IAC5D,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,sCAAsC;IACtC,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,IAAI,IAAI,EAAE,CAAC;QAC7C,OAAO,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IACrC,CAAC;IAED,sBAAsB;IACtB,IAAI,2BAAmB,IAAI,IAAI,EAAE,CAAC;QAChC,OAAO,YAAY,CAAC,IAAI,CAAC,2BAAmB,CAAC,CAAC,CAAC;IACjD,CAAC;IAED,yCAAyC;IACzC,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACrC,6FAA6F;IAC7F,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC;AAC7B,CAAC;AAs2BD;;;GAGG;AACU,QAAA,iBAAiB,GAAsB;IAClD,SAAS,EAAE,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC;CACxC,CAAC","sourcesContent":["// Copyright (c) 2026 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n/**\n * Core types for AI assist: prompt class, provider descriptors, settings, and chat messages.\n * @packageDocumentation\n */\n\nimport { type Result } from '@fgv/ts-utils';\nimport { type JsonObject } from '@fgv/ts-json-base';\n\n// ============================================================================\n// Image Data\n// ============================================================================\n\n/**\n * Universal image representation used for both image input (vision prompts)\n * and image output (generation responses).\n *\n * @remarks\n * The base64 string is raw — no `data:` URL prefix. Use {@link AiAssist.toDataUrl} to\n * format it for browser-display contexts.\n *\n * @public\n */\nexport interface IAiImageData {\n /** MIME type, e.g. `'image/png'`, `'image/jpeg'`, `'image/webp'`. */\n readonly mimeType: string;\n /** Base64-encoded image bytes (no `data:` prefix). */\n readonly base64: string;\n}\n\n/**\n * Formats an {@link IAiImageData} as a `data:` URL suitable for browser display.\n * @param image - The image to format\n * @returns A `data:<mime>;base64,<data>` URL string\n * @public\n */\nexport function toDataUrl(image: IAiImageData): string {\n return `data:${image.mimeType};base64,${image.base64}`;\n}\n\n/**\n * Image attachment for a vision (image-input) prompt.\n *\n * @remarks\n * Extends {@link IAiImageData} with an OpenAI-specific `detail` hint that is\n * silently ignored by Anthropic, Gemini, and other providers.\n *\n * @public\n */\nexport interface IAiImageAttachment extends IAiImageData {\n /**\n * OpenAI vision detail hint:\n * - `'low'`: faster, cheaper, lower fidelity\n * - `'high'`: slower, more expensive, higher fidelity\n * - `'auto'` (default): provider chooses\n *\n * Ignored by providers other than OpenAI.\n */\n readonly detail?: 'low' | 'high' | 'auto';\n}\n\n// ============================================================================\n// AiPrompt\n// ============================================================================\n\n/**\n * A structured AI prompt with system/user split for direct API calls,\n * and a lazily-constructed combined version for copy/paste workflows.\n * @public\n */\nexport class AiPrompt {\n /** System instructions: schema documentation, format rules, general guidance. */\n public readonly system: string;\n /** User request: the specific entity generation request. */\n public readonly user: string;\n /**\n * Optional image attachments. When present, vision-capable providers will\n * include them in the user message; non-vision providers will reject the\n * call up front (see {@link AiAssist.IAiProviderDescriptor.acceptsImageInput}).\n */\n public readonly attachments: ReadonlyArray<IAiImageAttachment>;\n\n public constructor(user: string, system: string, attachments?: ReadonlyArray<IAiImageAttachment>) {\n this.system = system;\n this.user = user;\n this.attachments = attachments ?? [];\n }\n\n /**\n * Combined single-string version (user + system joined) for copy/paste.\n * When attachments are present, includes a sentinel noting they aren't\n * part of the copied text.\n */\n public get combined(): string {\n const sentinel =\n this.attachments.length > 0\n ? `\\n\\n[${this.attachments.length} image attachment(s) — not included in copied text]`\n : '';\n return `${this.user}${sentinel}\\n\\n${this.system}`;\n }\n}\n\n// ============================================================================\n// Chat Message\n// ============================================================================\n\n/**\n * A single chat message in OpenAI format.\n * @public\n */\nexport interface IChatMessage {\n /** Message role */\n readonly role: 'system' | 'user' | 'assistant';\n /** Message content */\n readonly content: string;\n}\n\n// ============================================================================\n// Server-Side Tools\n// ============================================================================\n\n/**\n * Built-in server-side tool types supported across providers.\n * @public\n */\nexport type AiServerToolType = 'web_search';\n\n/**\n * Configuration specific to web search tools.\n * @public\n */\nexport interface IAiWebSearchToolConfig {\n readonly type: 'web_search';\n /** Optional: restrict search to these domains. */\n readonly allowedDomains?: ReadonlyArray<string>;\n /** Optional: exclude these domains from search. */\n readonly blockedDomains?: ReadonlyArray<string>;\n /** Optional: max number of searches per request. */\n readonly maxUses?: number;\n /**\n * Optional: enable image understanding during web search.\n * When true, the model can view and analyze images found during search.\n * Currently supported by xAI only; ignored by other providers.\n */\n readonly enableImageUnderstanding?: boolean;\n}\n\n/**\n * Union of all server-side tool configurations. Discriminated on `type`.\n * @public\n */\nexport type AiServerToolConfig = IAiWebSearchToolConfig;\n\n/**\n * Declares a tool as enabled/disabled in provider settings.\n * Tools are disabled by default — consuming apps must opt in explicitly.\n * @public\n */\nexport interface IAiToolEnablement {\n /** Which tool type. */\n readonly type: AiServerToolType;\n /** Whether this tool is enabled by default for this provider. */\n readonly enabled: boolean;\n /** Optional tool-specific configuration. */\n readonly config?: AiServerToolConfig;\n}\n\n// ============================================================================\n// Model Specification\n// ============================================================================\n\n/**\n * Known context keys for model specification maps.\n * @public\n */\nexport type ModelSpecKey = 'base' | 'tools' | 'image' | 'thinking';\n\n/**\n * All valid {@link ModelSpecKey} values.\n * @public\n */\nexport const allModelSpecKeys: ReadonlyArray<ModelSpecKey> = ['base', 'tools', 'image', 'thinking'];\n\n/**\n * Default context key used as fallback when resolving a {@link ModelSpec}.\n * @public\n */\nexport const MODEL_SPEC_BASE_KEY: ModelSpecKey = 'base';\n\n/**\n * A model specification: either a simple model string or a record mapping\n * context keys to nested model specs.\n *\n * @remarks\n * A bare string is equivalent to `{ base: string }`. This keeps the simple\n * case simple while allowing context-aware model selection (e.g. different\n * models for tool-augmented vs. base completions).\n *\n * @example\n * ```typescript\n * // Simple — same model for all contexts:\n * const simple: ModelSpec = 'grok-4.3';\n *\n * // Context-aware — different model for tools and thinking:\n * const split: ModelSpec = { base: 'grok-4.3', tools: 'grok-4.3', thinking: 'grok-4.3' };\n *\n * // Future nested — per-tool model selection:\n * const nested: ModelSpec = { base: 'grok-fast', tools: { base: 'grok-r', image: 'grok-v' } };\n * ```\n * @public\n */\nexport interface IModelSpecMap {\n readonly [key: string]: ModelSpec;\n}\n\n/**\n * @public\n */\nexport type ModelSpec = string | IModelSpecMap;\n\n/**\n * Resolves a {@link ModelSpec} to a concrete model string given an optional context key.\n *\n * @remarks\n * Resolution rules:\n * 1. If the spec is a string, return it directly (context is irrelevant).\n * 2. If the spec is an object and the context key exists, recurse into that branch.\n * 3. Otherwise, fall back to the {@link MODEL_SPEC_BASE_KEY | 'base'} key.\n * 4. If neither context nor `'base'` exists, use the first available value.\n *\n * @param spec - The model specification to resolve\n * @param context - Optional context key (e.g. `'tools'`)\n * @returns The resolved model string\n * @public\n */\nexport function resolveModel(spec: ModelSpec, context?: string): string {\n if (typeof spec === 'string') {\n return spec;\n }\n\n // Try the requested context key first\n if (context !== undefined && context in spec) {\n return resolveModel(spec[context]);\n }\n\n // Fall back to 'base'\n if (MODEL_SPEC_BASE_KEY in spec) {\n return resolveModel(spec[MODEL_SPEC_BASE_KEY]);\n }\n\n // Last resort: first value in the record\n const first = Object.values(spec)[0];\n /* c8 ignore next 3 - defensive: only reachable with empty object (prevented by converter) */\n if (first === undefined) {\n return '';\n }\n return resolveModel(first);\n}\n\n// ============================================================================\n// Provider Descriptor\n// ============================================================================\n\n/**\n * All known AI provider identifiers.\n * @public\n */\nexport type AiProviderId =\n | 'copy-paste'\n | 'xai-grok'\n | 'openai'\n | 'openai-compat'\n | 'anthropic'\n | 'google-gemini'\n | 'groq'\n | 'mistral'\n | 'ollama';\n\n/**\n * API format categories for provider routing.\n * @public\n */\nexport type AiApiFormat = 'openai' | 'anthropic' | 'gemini';\n\n/**\n * API format categories for image-generation provider routing.\n *\n * @remarks\n * - `'openai-images'` — OpenAI Images API. Routes to `/images/generations`\n * (text-only) or `/images/edits` (when reference images are present).\n * - `'xai-images'` — xAI Images API. Text-only JSON generation request.\n * - `'xai-images-edits'` — xAI Images API for Grok Imagine models. Uses JSON\n * body with `{ type: \"image_url\" }` objects (not multipart).\n * - `'gemini-imagen'` — Google Imagen `:predict` endpoint. Text-only.\n * - `'gemini-image-out'` — Google Gemini chat-style `:generateContent`\n * endpoint that returns image parts (Gemini 2.5 Flash Image / \"Nano\n * Banana\"). Accepts reference images.\n *\n * @public\n */\nexport type AiImageApiFormat =\n | 'openai-images'\n | 'gemini-imagen'\n | 'xai-images'\n | 'xai-images-edits'\n | 'gemini-image-out';\n\n// ============================================================================\n// Completion Response\n// ============================================================================\n\n/**\n * Result of an AI provider completion call.\n * @public\n */\nexport interface IAiCompletionResponse {\n /** The generated text content */\n readonly content: string;\n /** Whether the response was truncated due to token limits */\n readonly truncated: boolean;\n}\n\n// ============================================================================\n// Streaming Events\n// ============================================================================\n\n/**\n * A text-content delta arriving during a streaming completion.\n * @public\n */\nexport interface IAiStreamTextDelta {\n readonly type: 'text-delta';\n /** The newly arrived text fragment. */\n readonly delta: string;\n}\n\n/**\n * A server-side tool progress event arriving during a streaming completion.\n * Surfaced for providers that emit explicit tool-progress markers (OpenAI\n * Responses API, Anthropic). Gemini's grounding doesn't emit these.\n * @public\n */\nexport interface IAiStreamToolEvent {\n readonly type: 'tool-event';\n /** Which server-side tool this event describes. */\n readonly toolType: AiServerToolType;\n /** Tool lifecycle phase. */\n readonly phase: 'started' | 'completed';\n /**\n * Optional provider-specific detail. For web_search this is typically the\n * search query when available; format varies by provider.\n */\n readonly detail?: string;\n}\n\n/**\n * Terminal success event for a streaming completion. Carries the aggregated\n * full text and truncation status for callers that want both the progressive\n * UI and the complete result.\n * @public\n */\nexport interface IAiStreamDone {\n readonly type: 'done';\n /** Whether the response was truncated due to token limits. */\n readonly truncated: boolean;\n /** The full concatenated text from all `text-delta` events. */\n readonly fullText: string;\n}\n\n/**\n * Terminal failure event for a streaming completion. After this event no\n * further events are emitted.\n *\n * @remarks\n * Connection-time failures (auth, network, pre-flight CORS rejection) are\n * surfaced via the outer `Result.fail` returned by\n * `callProviderCompletionStream` rather than as an `error` event, so callers\n * can distinguish \"didn't start\" from \"started but errored mid-stream.\"\n *\n * @public\n */\nexport interface IAiStreamError {\n readonly type: 'error';\n readonly message: string;\n}\n\n/**\n * Discriminated union of events emitted by a streaming completion.\n * @public\n */\nexport type IAiStreamEvent = IAiStreamTextDelta | IAiStreamToolEvent | IAiStreamDone | IAiStreamError;\n\n/**\n * Thinking/reasoning mode support for a provider.\n * @public\n */\nexport type AiThinkingMode = 'optional' | 'required' | 'unsupported';\n\n/**\n * Describes a single AI provider — single source of truth for all metadata.\n * @public\n */\nexport interface IAiProviderDescriptor {\n /** Provider identifier (e.g. 'xai-grok', 'anthropic') */\n readonly id: AiProviderId;\n /** Human-readable label (e.g. \"xAI Grok\") */\n readonly label: string;\n /** Button label for action buttons (e.g. \"AI Assist | Grok\") */\n readonly buttonLabel: string;\n /** Whether this provider requires an API key secret */\n readonly needsSecret: boolean;\n /** Which API adapter format to use */\n readonly apiFormat: AiApiFormat;\n /** Base URL for the API (e.g. 'https://api.x.ai/v1') */\n readonly baseUrl: string;\n /** Default model specification — string or context-aware map. */\n readonly defaultModel: ModelSpec;\n /** Which server-side tools this provider supports (empty = none). */\n readonly supportedTools: ReadonlyArray<AiServerToolType>;\n /** Whether this provider's API enforces CORS restrictions that prevent direct browser calls. */\n readonly corsRestricted: boolean;\n /**\n * Whether this provider's streaming completion endpoint requires a proxy\n * for direct browser calls. Some providers gate streaming separately from\n * non-streaming (rare), so this is tracked independently from\n * {@link IAiProviderDescriptor.corsRestricted}.\n *\n * @remarks\n * When `true`, `callProviderCompletionStream` rejects up front unless the\n * call is being routed through a proxy.\n */\n readonly streamingCorsRestricted: boolean;\n /**\n * Whether this provider's chat completions API accepts image input\n * (i.e. supports vision prompts). When false, calls with\n * `prompt.attachments` are rejected up front.\n */\n readonly acceptsImageInput: boolean;\n /**\n * Whether this provider supports thinking/reasoning mode.\n * - 'optional': thinking can be enabled but is not required\n * - 'required': thinking is always active (e.g. o-series models)\n * - 'unsupported': thinking is not supported\n */\n readonly thinkingMode: AiThinkingMode;\n /**\n * Image-generation capabilities, scoped to model id prefixes. Empty or\n * undefined means the provider does not support image generation.\n *\n * @remarks\n * The dispatcher matches the resolved model id against each rule's\n * `modelPrefix` and selects the longest match (see\n * {@link AiAssist.resolveImageCapability}). An empty `modelPrefix` is the\n * catch-all and matches every model id.\n *\n * Multiple entries support providers that host more than one image-API\n * surface under one baseUrl. Google Gemini is the canonical case: the\n * `imagen-*` family is predict-only via `:predict`, while\n * `gemini-2.5-flash-image` uses chat-style `:generateContent` and accepts\n * reference images. Listing both lets callers pick the right model and the\n * dispatcher routes accordingly.\n *\n * Image-model selection reuses the existing `image` {@link ModelSpecKey}.\n * Providers that declare `imageGeneration` should declare a model in\n * `defaultModel.image`, e.g. `{ base: 'gpt-4o', image: 'dall-e-3' }`.\n */\n readonly imageGeneration?: ReadonlyArray<IAiImageModelCapability>;\n}\n\n/**\n * Image-generation capability for a model family within a provider. Used as\n * an entry in {@link IAiProviderDescriptor.imageGeneration}.\n *\n * @public\n */\nexport interface IAiImageModelCapability {\n /**\n * Prefix matched against the resolved image model id. The empty string is\n * the catch-all and matches every model. When multiple rules' prefixes\n * match a model id, the longest prefix wins; ties are broken by\n * first-encountered.\n */\n readonly modelPrefix: string;\n /** API format used to dispatch requests for matching models. */\n readonly format: AiImageApiFormat;\n /**\n * Whether matching models accept reference images via\n * {@link AiAssist.IAiImageGenerationParams.referenceImages}. When false or\n * undefined, calls that include reference images are rejected up front.\n */\n readonly acceptsImageReferenceInput?: boolean;\n /** Accepted size strings. When present, dispatcher pre-validates. */\n readonly acceptedSizes?: ReadonlyArray<string>;\n /** When true, quality param is sent. When false/undefined, don't send quality. */\n readonly supportsQualityParam?: boolean;\n /** Accepted quality values when supportsQualityParam is true. */\n readonly acceptedQualities?: ReadonlyArray<string>;\n /** Maximum count (n). When present, dispatcher pre-validates. */\n readonly maxCount?: number;\n /**\n * How to encode the output format on the wire:\n * - 'response-format': send response_format: 'b64_json' (dall-e-2, dall-e-3)\n * - 'output-format': send output_format (gpt-image-1)\n * - 'none': send neither (Imagen, Gemini Flash)\n */\n readonly outputParamStyle?: 'response-format' | 'output-format' | 'none';\n /** Default MIME type for response images. */\n readonly defaultOutputMimeType?: string;\n}\n\n// ============================================================================\n// Image Generation — Layered Options Types\n// ============================================================================\n\n/** Pixel dimension sizes accepted by dall-e-2. @public */\nexport type DallE2Size = '256x256' | '512x512' | '1024x1024';\n\n/** Pixel dimension sizes accepted by dall-e-3. @public */\nexport type DallE3Size = '1024x1024' | '1792x1024' | '1024x1792';\n\n/** Pixel dimension sizes accepted by gpt-image-1. @public */\nexport type GptImageSize = '1024x1024' | '1536x1024' | '1024x1536' | 'auto';\n\n/** All accepted image size strings across all providers. @public */\nexport type AiImageSize = DallE2Size | DallE3Size | GptImageSize;\n\n/** Quality values for dall-e-3. @public */\nexport type DallE3Quality = 'standard' | 'hd';\n\n/** Quality values for gpt-image-1. @public */\nexport type GptImageQuality = 'low' | 'medium' | 'high' | 'auto';\n\n/** All accepted quality strings across all providers. @public */\nexport type AiImageQuality = DallE3Quality | GptImageQuality;\n\n/** Model names in the DALL-E family. @public */\nexport type DallEModelNames = 'dall-e-2' | 'dall-e-3';\n\n/** Model names in the GPT Image family. @public */\nexport type GptImageModelNames = 'gpt-image-1';\n\n/** Model names in the xAI Grok Imagine family. @public */\nexport type GrokImagineModelNames = 'grok-imagine-image' | 'grok-imagine-image-quality';\n\n/** Model names in the Imagen 4 family. @public */\nexport type Imagen4ModelNames =\n | 'imagen-4.0-generate-001'\n | 'imagen-4.0-ultra-generate-001'\n | 'imagen-4.0-fast-generate-001';\n\n/** Model names in the Gemini Flash Image family. @public */\nexport type GeminiFlashImageModelNames = 'gemini-2.5-flash-image';\n\n// ---- Family-level config shapes ----\n\n/**\n * Provider-specific config for DALL-E models (dall-e-2, dall-e-3).\n * @remarks\n * style is only valid for dall-e-3; the runtime validator rejects it for dall-e-2.\n * @public\n */\nexport interface IDallEImageGenerationConfig {\n /** Image dimensions (dall-e-2: 256x256|512x512|1024x1024; dall-e-3: 1024x1024|1792x1024|1024x1792). */\n readonly size?: DallE2Size | DallE3Size;\n /** dall-e-3 only. Quality tier. */\n readonly quality?: DallE3Quality;\n /** dall-e-3 only. Visual style. */\n readonly style?: 'vivid' | 'natural';\n}\n\n/**\n * Provider-specific config for gpt-image-1.\n * @public\n */\nexport interface IGptImageGenerationConfig {\n /** Image dimensions. */\n readonly size?: GptImageSize;\n /** Quality tier. */\n readonly quality?: GptImageQuality;\n /** Output format (replaces response_format for this model). */\n readonly outputFormat?: 'png' | 'jpeg' | 'webp';\n /** JPEG/WebP compression level 0–100. */\n readonly outputCompression?: number;\n /** Background transparency control. */\n readonly background?: 'transparent' | 'opaque' | 'auto';\n /** Content moderation strictness. */\n readonly moderation?: 'low' | 'auto';\n}\n\n/**\n * Provider-specific config for xAI Grok Imagine models.\n * @public\n */\nexport interface IGrokImagineImageGenerationConfig {\n /** Aspect ratio string (xAI uses aspect ratios, not pixel dimensions). */\n readonly aspectRatio?: string;\n /** Resolution hint. */\n readonly resolution?: string;\n}\n\n/**\n * Provider-specific config for Google Imagen 4 models.\n * @public\n */\nexport interface IImagen4GenerationConfig {\n /** Aspect ratio string. */\n readonly aspectRatio?: '1:1' | '3:4' | '4:3' | '9:16' | '16:9';\n /** Output resolution. */\n readonly imageSize?: '1K' | '2K';\n /** Whether to add SynthID watermark. Must be false to use seed. */\n readonly addWatermark?: boolean;\n /** LLM-based prompt rewriting. */\n readonly enhancePrompt?: boolean;\n /** Output MIME type. */\n readonly outputMimeType?: 'image/jpeg' | 'image/png';\n /** JPEG compression quality. */\n readonly outputCompressionQuality?: number;\n /** Person generation policy. */\n readonly personGeneration?: 'allow_all' | 'allow_adult' | 'dont_allow';\n}\n\n/**\n * Provider-specific config for Gemini Flash Image.\n * @public\n */\nexport interface IGeminiFlashImageGenerationConfig {\n /** Aspect ratio string. */\n readonly aspectRatio?: string;\n}\n\n// ---- Model-family option blocks ----\n\n/**\n * Base shape shared by all named family option blocks.\n * Provides a typed `models` field for applicability filtering without unsafe casts.\n * @internal\n */\nexport interface INamedModelFamilyConfig {\n readonly models?: readonly string[];\n}\n\n/**\n * Options block scoped to DALL-E family models.\n * @public\n */\nexport interface IDallEModelOptions extends INamedModelFamilyConfig {\n /** Discriminator: openai provider lineage. */\n readonly provider: 'openai';\n /** Family identifier. */\n readonly family: 'dall-e';\n /** Optional model names this block applies to. Omit = applies to all DALL-E models. */\n readonly models?: DallEModelNames[];\n /** Family-specific config. */\n readonly config: IDallEImageGenerationConfig;\n}\n\n/**\n * Options block scoped to GPT Image family models.\n * @public\n */\nexport interface IGptImageModelOptions extends INamedModelFamilyConfig {\n readonly provider: 'openai';\n readonly family: 'gpt-image';\n readonly models?: GptImageModelNames[];\n readonly config: IGptImageGenerationConfig;\n}\n\n/**\n * Options block scoped to xAI Grok Imagine family models.\n * @public\n */\nexport interface IGrokImagineModelOptions extends INamedModelFamilyConfig {\n readonly provider: 'xai';\n readonly family: 'grok-imagine';\n readonly models?: GrokImagineModelNames[];\n readonly config: IGrokImagineImageGenerationConfig;\n}\n\n/**\n * Options block scoped to Google Imagen 4 models.\n * @public\n */\nexport interface IImagen4ModelOptions extends INamedModelFamilyConfig {\n readonly provider: 'google';\n readonly family: 'imagen-4';\n readonly models?: Imagen4ModelNames[];\n readonly config: IImagen4GenerationConfig;\n}\n\n/**\n * Options block scoped to Gemini Flash Image models.\n * @public\n */\nexport interface IGeminiFlashImageModelOptions extends INamedModelFamilyConfig {\n readonly provider: 'google';\n readonly family: 'gemini-flash-image';\n readonly models?: GeminiFlashImageModelNames[];\n readonly config: IGeminiFlashImageGenerationConfig;\n}\n\n/**\n * Escape-hatch options block for models not covered by a named family.\n * @remarks\n * `models` is required — there is no implicit \"all\" for unknown model families.\n * `config` is `JsonObject` — passed verbatim to the wire request with no validation.\n * This is the \"trust me, I know what I'm doing\" path for callers who need to send\n * wire params our typed configs don't yet expose.\n * @public\n */\nexport interface IOtherModelOptions {\n readonly provider: 'other';\n readonly models: string[];\n readonly config: JsonObject;\n}\n\n/**\n * Discriminated union of all model-family option blocks.\n * Discriminated on `provider` + `family` fields.\n * @public\n */\nexport type IModelFamilyConfig =\n | IDallEModelOptions\n | IGptImageModelOptions\n | IGrokImagineModelOptions\n | IImagen4ModelOptions\n | IGeminiFlashImageModelOptions\n | IOtherModelOptions;\n\n// ============================================================================\n// Image Generation\n// ============================================================================\n\n/**\n * Options for image generation requests.\n *\n * @remarks\n * Uses a layered architecture:\n * 1. Generic top-level options (size, count, quality, seed) apply across providers\n * via the resolved model's registry mapping.\n * 2. Optional `models` array contains model-family-scoped blocks; the resolver\n * picks applicable blocks based on the resolved model and applies them in\n * declaration order.\n *\n * **Merge precedence (later wins):**\n * 1. Generic top-level options (lowest precedence)\n * 2. Family-generic blocks (matching family, models field omitted)\n * 3. Model-specific blocks (models array includes resolved model name)\n * 4. Other blocks (provider: 'other', models array includes resolved model name)\n *\n * Provider-mismatch: blocks whose provider doesn't match the dispatcher's\n * provider lineage are silently skipped.\n *\n * @public\n */\nexport interface IAiImageGenerationOptions {\n /**\n * Image dimensions for OpenAI models (mapped to `size` field).\n * For xAI aspect ratio or Imagen aspect ratio, use the corresponding `models` family block.\n */\n readonly size?: AiImageSize;\n /** Number of images. Default 1. Some models enforce a maximum. */\n readonly count?: number;\n /**\n * Quality tier. Accepted values differ per model:\n * - dall-e-3: 'standard' | 'hd'\n * - gpt-image-1: 'low' | 'medium' | 'high' | 'auto'\n * Other models ignore this field.\n */\n readonly quality?: AiImageQuality;\n /** Reproducibility seed, where supported. */\n readonly seed?: number;\n /**\n * Optional precision via model-family-scoped blocks. The resolver picks\n * applicable blocks dynamically based on the resolved model.\n */\n readonly models?: ReadonlyArray<IModelFamilyConfig>;\n}\n\n/**\n * Parameters for an image-generation request.\n * @public\n */\nexport interface IAiImageGenerationParams {\n /** The text prompt describing the desired image. */\n readonly prompt: string;\n /** Optional generation options. */\n readonly options?: IAiImageGenerationOptions;\n /**\n * Optional reference images. When present, the provider will use them as\n * visual context (e.g. to preserve a character's appearance across multiple\n * generations). The dispatcher resolves the\n * {@link AiAssist.IAiImageModelCapability} for the requested model and\n * rejects the call up front if `acceptsImageReferenceInput` is not set on\n * the matching capability. An empty array is treated identically to\n * `undefined`.\n */\n readonly referenceImages?: ReadonlyArray<IAiImageAttachment>;\n}\n\n/**\n * A single generated image.\n * @public\n */\nexport interface IAiGeneratedImage extends IAiImageData {\n /**\n * The prompt as rewritten by the provider, if any. OpenAI's image models\n * commonly rewrite prompts; other providers do not.\n */\n readonly revisedPrompt?: string;\n}\n\n// ============================================================================\n// Model Catalog (listModels)\n// ============================================================================\n\n/**\n * Capability vocabulary used to describe what a model can do. Used as both\n * a filter and as a tag in {@link AiAssist.IAiModelInfo.capabilities}.\n *\n * @remarks\n * Adding a new capability is cheap; adding the *first* one after consumers\n * already exist forces churn. The initial vocabulary is intentionally broad\n * even though only `image-generation` is fully exercised today.\n *\n * @public\n */\nexport type AiModelCapability = 'chat' | 'tools' | 'vision' | 'image-generation' | 'thinking';\n\n/**\n * Information about a single model returned by a provider's list endpoint,\n * with capabilities already resolved (native + config rules).\n * @public\n */\nexport interface IAiModelInfo {\n /** Provider-native model identifier. */\n readonly id: string;\n /** Resolved capability set — union of native declarations and config rules. */\n readonly capabilities: ReadonlySet<AiModelCapability>;\n /** Friendly name for display, when known. */\n readonly displayName?: string;\n}\n\n/**\n * One rule in an {@link IAiModelCapabilityConfig}. Multiple rules can match\n * a single model — their capability arrays are unioned.\n * @public\n */\nexport interface IAiModelCapabilityRule {\n /** RegExp tested against the model id (using `.test`). */\n readonly idPattern: RegExp;\n /** Capabilities this rule attributes to matching models. */\n readonly capabilities: ReadonlyArray<AiModelCapability>;\n /**\n * Friendly display-name override for matching models. The function form\n * lets one rule format many ids (e.g. `(id) => id.toUpperCase()`).\n * If multiple matching rules supply `displayName`, the first match wins.\n */\n readonly displayName?: string | ((id: string) => string);\n}\n\n/**\n * Configuration that maps model id patterns to capabilities. Used to\n * augment (or, where the provider supplies no capability info, fully\n * derive) the capability set for each listed model.\n * @public\n */\nexport interface IAiModelCapabilityConfig {\n /** Per-provider rules. Tried before {@link AiAssist.IAiModelCapabilityConfig.global}. */\n readonly perProvider?: { readonly [P in AiProviderId]?: ReadonlyArray<IAiModelCapabilityRule> };\n /** Cross-provider fallback rules. */\n readonly global?: ReadonlyArray<IAiModelCapabilityRule>;\n}\n\n/**\n * Result of an image-generation call.\n * @public\n */\nexport interface IAiImageGenerationResponse {\n /** The generated images, in provider-returned order. */\n readonly images: ReadonlyArray<IAiGeneratedImage>;\n}\n\n// ============================================================================\n// Thinking / Reasoning Config — Layered Options Types\n// ============================================================================\n\n/**\n * Model IDs for Anthropic thinking-capable models.\n * @public\n */\nexport type AnthropicThinkingModelNames =\n | 'claude-sonnet-4-5'\n | 'claude-sonnet-4-6'\n | 'claude-opus-4-6'\n | 'claude-opus-4-7';\n\n/**\n * Model IDs for OpenAI thinking-capable models.\n * @public\n */\nexport type OpenAiThinkingModelNames =\n | 'o3'\n | 'o4-mini'\n | 'o3-deep-research'\n | 'o4-mini-deep-research'\n | 'gpt-5'\n | 'gpt-5.1'\n | 'gpt-5.2'\n | 'gpt-5.5'\n | 'gpt-5-pro';\n\n/**\n * Model IDs for Google Gemini thinking-capable models.\n * @public\n */\nexport type GeminiThinkingModelNames = 'gemini-2.5-pro' | 'gemini-2.5-flash' | 'gemini-2.5-flash-lite';\n\n/**\n * Model IDs for xAI thinking-capable models.\n * @public\n */\nexport type XAiThinkingModelNames = 'grok-3-mini' | 'grok-4.3' | 'grok-4';\n\n/**\n * Anthropic-specific thinking configuration.\n * @public\n */\nexport interface IAnthropicThinkingConfig {\n /**\n * Anthropic effort level. Maps 1:1 to `output_config.effort` on the wire.\n * - 'low' | 'medium' | 'high': all thinking-capable models\n * - 'max': Opus 4.6 only\n */\n readonly effort?: 'low' | 'medium' | 'high' | 'max';\n}\n\n/**\n * OpenAI-specific thinking configuration.\n * @remarks\n * Maps to `reasoning_effort` (Chat Completions path) or `reasoning.effort`\n * (Responses API path) on the wire. The adapter selects the correct field.\n * @public\n */\nexport interface IOpenAiThinkingConfig {\n /**\n * OpenAI reasoning effort. Maps 1:1 to the wire field.\n * - 'none': disables reasoning (gpt-5.x only; rejected by o-series)\n * - 'minimal': fastest (gpt-5.x)\n * - 'low' | 'medium' | 'high': standard tiers\n * - 'xhigh': highest (select gpt-5.x models only)\n *\n * @remarks\n * When effective effort is 'none', reasoning is disabled and temperature is\n * accepted by gpt-5.x models. This is the only case where temperature and\n * thinking config co-exist without a Result.fail.\n */\n readonly effort?: 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';\n}\n\n/**\n * Google Gemini-specific thinking configuration.\n * @public\n */\nexport interface IGeminiThinkingConfig {\n /**\n * Token budget for thinking. Maps 1:1 to `thinkingBudget` on the wire.\n * - 0: disable thinking (Flash and Flash-Lite only; error on Pro)\n * - positive integer: soft token cap\n * - -1: dynamic\n * - omitted: model default\n */\n readonly thinkingBudget?: number;\n /**\n * Whether to include thought summaries in the response.\n * @remarks\n * INERT in phase B. Adapters never send `includeThoughts: true`.\n * Wired up by the followup stream `ai-assist-thinking-events`.\n */\n readonly includeThoughts?: boolean;\n}\n\n/**\n * xAI-specific thinking configuration.\n * @public\n */\nexport interface IXAiThinkingConfig {\n /**\n * xAI reasoning effort. Maps 1:1 to `reasoning_effort` on the wire.\n * For grok-4, the adapter omits this field (grok-4 always reasons and\n * rejects the parameter).\n */\n readonly effort?: 'none' | 'low' | 'medium' | 'high';\n}\n\n/**\n * Anthropic-specific thinking options block.\n * @public\n */\nexport interface IAnthropicThinkingOptions {\n readonly provider: 'anthropic';\n readonly models?: ReadonlyArray<AnthropicThinkingModelNames>;\n readonly config: IAnthropicThinkingConfig;\n}\n\n/**\n * OpenAI-specific thinking options block.\n * @public\n */\nexport interface IOpenAiThinkingOptions {\n readonly provider: 'openai';\n readonly models?: ReadonlyArray<OpenAiThinkingModelNames>;\n readonly config: IOpenAiThinkingConfig;\n}\n\n/**\n * Google Gemini-specific thinking options block.\n * @public\n */\nexport interface IGeminiThinkingOptions {\n readonly provider: 'google';\n readonly models?: ReadonlyArray<GeminiThinkingModelNames>;\n readonly config: IGeminiThinkingConfig;\n}\n\n/**\n * xAI-specific thinking options block.\n * @public\n */\nexport interface IXAiThinkingOptions {\n readonly provider: 'xai';\n readonly models?: ReadonlyArray<XAiThinkingModelNames>;\n readonly config: IXAiThinkingConfig;\n}\n\n/**\n * Escape-hatch options block for providers not covered by typed configs.\n * @remarks\n * `models` is required — no implicit \"all\" for unknown providers.\n * `config` fields are merged verbatim into the wire request.\n * @public\n */\nexport interface IOtherThinkingOptions {\n readonly provider: 'other';\n readonly models: ReadonlyArray<string>;\n readonly config: JsonObject;\n}\n\n/**\n * Discriminated union of per-provider thinking config blocks.\n * @public\n */\nexport type IThinkingProviderConfig =\n | IAnthropicThinkingOptions\n | IOpenAiThinkingOptions\n | IGeminiThinkingOptions\n | IXAiThinkingOptions\n | IOtherThinkingOptions;\n\n/**\n * Thinking/reasoning mode configuration for a completion request.\n *\n * @remarks\n * The generic `effort` field covers the common-subset cross-provider vocabulary.\n * For provider-specific precision (Anthropic 'max', OpenAI 'xhigh', Gemini token\n * budgets, xAI effort-level tuning), use the `providers` array.\n *\n * Absence (or undefined) means \"no thinking mode\" — existing callers are unaffected.\n *\n * @public\n */\nexport interface IThinkingConfig {\n /**\n * Cross-provider effort level. Common-subset mapping:\n * - 'low': Anthropic effort:low | OpenAI effort:low | Gemini thinkingBudget:1024 | xAI reasoning_effort:low\n * - 'medium': effort:medium | effort:medium | thinkingBudget:4096 | reasoning_effort:medium\n * - 'high': effort:high | effort:high | thinkingBudget:8192 | reasoning_effort:high\n */\n readonly effort?: 'low' | 'medium' | 'high';\n /**\n * Optional per-provider precision blocks. Blocks for providers that don't\n * match the resolved model's provider are silently skipped.\n */\n readonly providers?: ReadonlyArray<IThinkingProviderConfig>;\n}\n\n// ============================================================================\n// Settings\n// ============================================================================\n\n/**\n * Configuration for a single AI assist provider.\n * @public\n */\nexport interface IAiAssistProviderConfig {\n /** Which provider this configures */\n readonly provider: AiProviderId;\n /** For API-based providers: the keystore secret name holding the API key */\n readonly secretName?: string;\n /** Optional model override — string or context-aware map. */\n readonly model?: ModelSpec;\n /** Tool enablement/configuration. Tools are disabled unless explicitly enabled. */\n readonly tools?: ReadonlyArray<IAiToolEnablement>;\n /**\n * Optional caller-supplied endpoint URL (http/https). Overrides\n * `descriptor.baseUrl` for this provider. Used to point a provider at a\n * self-hosted server (Ollama, LM Studio, llama.cpp's openai-server) or a\n * local proxy. Validation lives in `@fgv/ts-extras` — query strings,\n * fragments, and userinfo are rejected.\n */\n readonly endpoint?: string;\n}\n\n/**\n * AI assist settings — which providers are enabled and their configuration.\n * @public\n */\nexport interface IAiAssistSettings {\n /** Enabled providers and their configuration. */\n readonly providers: ReadonlyArray<IAiAssistProviderConfig>;\n /** Which enabled provider is the default for the main button. Falls back to first in list. */\n readonly defaultProvider?: AiProviderId;\n /** Optional proxy URL for routing API requests through a backend server (e.g. `http://localhost:3002`). */\n readonly proxyUrl?: string;\n /** When true, route all providers through the proxy. When false (default), only CORS-restricted providers use the proxy. */\n readonly proxyAllProviders?: boolean;\n}\n\n/**\n * Default AI assist settings (copy-paste only).\n * @public\n */\nexport const DEFAULT_AI_ASSIST: IAiAssistSettings = {\n providers: [{ provider: 'copy-paste' }]\n};\n\n// ============================================================================\n// Keystore Interface\n// ============================================================================\n\n/**\n * Minimal keystore interface for AI assist API key resolution.\n * Satisfied structurally by the concrete `KeyStore` class from `@fgv/ts-extras`.\n * @public\n */\nexport interface IAiAssistKeyStore {\n /** Whether the keystore is currently unlocked */\n readonly isUnlocked: boolean;\n /** Check if a named secret exists */\n hasSecret(name: string): Result<boolean>;\n /** Get an API key by secret name */\n getApiKey(name: string): Result<string>;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/packlets/ai-assist/registry.ts"],"names":[],"mappings":"AAoBA;;;GAGG;AAEH,OAAO,EAAQ,MAAM,EAAW,MAAM,eAAe,CAAC;AAEtD,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,EAC3B,MAAM,SAAS,CAAC;AAiKjB;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,aAAa,CAAC,YAAY,CAAsC,CAAC;AAE9F;;;;GAIG;AACH,wBAAgB,sBAAsB,IAAI,aAAa,CAAC,qBAAqB,CAAC,CAE7E;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC,qBAAqB,CAAC,CAM/E;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,qBAAqB,GAAG,OAAO,CAElF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,qBAAqB,EACjC,OAAO,EAAE,MAAM,GACd,uBAAuB,GAAG,SAAS,CAOrC;AAMD;;;;;;;GAOG;AACH,eAAO,MAAM,+BAA+B,EAAE,wBAwB7C,CAAC"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/packlets/ai-assist/registry.ts"],"names":[],"mappings":"AAoBA;;;GAGG;AAEH,OAAO,EAAQ,MAAM,EAAW,MAAM,eAAe,CAAC;AAEtD,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,EAC3B,MAAM,SAAS,CAAC;AA0PjB;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,aAAa,CAAC,YAAY,CAAsC,CAAC;AAE9F;;;;GAIG;AACH,wBAAgB,sBAAsB,IAAI,aAAa,CAAC,qBAAqB,CAAC,CAE7E;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC,qBAAqB,CAAC,CAM/E;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,qBAAqB,GAAG,OAAO,CAElF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,qBAAqB,EACjC,OAAO,EAAE,MAAM,GACd,uBAAuB,GAAG,SAAS,CAOrC;AAMD;;;;;;;GAOG;AACH,eAAO,MAAM,+BAA+B,EAAE,wBAiC7C,CAAC"}
@@ -48,7 +48,8 @@ const BUILTIN_PROVIDERS = [
48
48
  supportedTools: [],
49
49
  corsRestricted: false,
50
50
  streamingCorsRestricted: false,
51
- acceptsImageInput: false
51
+ acceptsImageInput: false,
52
+ thinkingMode: 'unsupported'
52
53
  },
53
54
  {
54
55
  id: 'anthropic',
@@ -61,7 +62,8 @@ const BUILTIN_PROVIDERS = [
61
62
  supportedTools: ['web_search'],
62
63
  corsRestricted: false,
63
64
  streamingCorsRestricted: false,
64
- acceptsImageInput: true
65
+ acceptsImageInput: true,
66
+ thinkingMode: 'optional'
65
67
  },
66
68
  {
67
69
  id: 'google-gemini',
@@ -75,11 +77,38 @@ const BUILTIN_PROVIDERS = [
75
77
  corsRestricted: false,
76
78
  streamingCorsRestricted: false,
77
79
  acceptsImageInput: true,
80
+ thinkingMode: 'optional',
78
81
  imageGeneration: [
79
- // imagen-* models are predict-only and do not accept reference images;
80
- // everything else uses chat-style :generateContent with refs.
81
- { modelPrefix: 'imagen-', format: 'gemini-imagen' },
82
- { modelPrefix: '', format: 'gemini-image-out', acceptsImageReferenceInput: true }
82
+ {
83
+ // Imagen 4 Ultra: max 1 image
84
+ modelPrefix: 'imagen-4.0-ultra-',
85
+ format: 'gemini-imagen',
86
+ acceptsImageReferenceInput: false,
87
+ supportsQualityParam: false,
88
+ maxCount: 1,
89
+ outputParamStyle: 'none',
90
+ defaultOutputMimeType: 'image/png'
91
+ },
92
+ {
93
+ // All other Imagen 4 models: max 4 images
94
+ modelPrefix: 'imagen-',
95
+ format: 'gemini-imagen',
96
+ acceptsImageReferenceInput: false,
97
+ supportsQualityParam: false,
98
+ maxCount: 4,
99
+ outputParamStyle: 'none',
100
+ defaultOutputMimeType: 'image/png'
101
+ },
102
+ {
103
+ // Gemini Flash Image: chat-style generateContent
104
+ modelPrefix: '',
105
+ format: 'gemini-image-out',
106
+ acceptsImageReferenceInput: true,
107
+ supportsQualityParam: false,
108
+ maxCount: 1,
109
+ outputParamStyle: 'none',
110
+ defaultOutputMimeType: 'image/jpeg'
111
+ }
83
112
  ]
84
113
  },
85
114
  {
@@ -93,7 +122,8 @@ const BUILTIN_PROVIDERS = [
93
122
  supportedTools: [],
94
123
  corsRestricted: false,
95
124
  streamingCorsRestricted: false,
96
- acceptsImageInput: false
125
+ acceptsImageInput: false,
126
+ thinkingMode: 'unsupported'
97
127
  },
98
128
  {
99
129
  id: 'mistral',
@@ -106,7 +136,8 @@ const BUILTIN_PROVIDERS = [
106
136
  supportedTools: [],
107
137
  corsRestricted: false,
108
138
  streamingCorsRestricted: false,
109
- acceptsImageInput: false
139
+ acceptsImageInput: false,
140
+ thinkingMode: 'unsupported'
110
141
  },
111
142
  {
112
143
  id: 'ollama',
@@ -119,7 +150,8 @@ const BUILTIN_PROVIDERS = [
119
150
  supportedTools: [],
120
151
  corsRestricted: false,
121
152
  streamingCorsRestricted: false,
122
- acceptsImageInput: false
153
+ acceptsImageInput: false,
154
+ thinkingMode: 'unsupported'
123
155
  },
124
156
  {
125
157
  id: 'openai',
@@ -133,13 +165,46 @@ const BUILTIN_PROVIDERS = [
133
165
  corsRestricted: false,
134
166
  streamingCorsRestricted: false,
135
167
  acceptsImageInput: true,
168
+ thinkingMode: 'optional',
136
169
  imageGeneration: [
137
- // gpt-image-1 supports /images/edits with reference images. dall-e-3
138
- // (the default image model) does not, so the catch-all rule omits
139
- // acceptsImageReferenceInput; callers selecting dall-e-3 with refs hit
140
- // the up-front rejection rather than a provider 400.
141
- { modelPrefix: 'gpt-image-', format: 'openai-images', acceptsImageReferenceInput: true },
142
- { modelPrefix: '', format: 'openai-images' }
170
+ {
171
+ modelPrefix: 'gpt-image-',
172
+ format: 'openai-images',
173
+ acceptsImageReferenceInput: true,
174
+ acceptedSizes: ['1024x1024', '1536x1024', '1024x1536', 'auto'],
175
+ supportsQualityParam: true,
176
+ acceptedQualities: ['low', 'medium', 'high', 'auto'],
177
+ maxCount: 10,
178
+ outputParamStyle: 'output-format',
179
+ defaultOutputMimeType: 'image/png'
180
+ },
181
+ {
182
+ modelPrefix: 'dall-e-3',
183
+ format: 'openai-images',
184
+ acceptsImageReferenceInput: false,
185
+ acceptedSizes: ['1024x1024', '1792x1024', '1024x1792'],
186
+ supportsQualityParam: true,
187
+ acceptedQualities: ['standard', 'hd'],
188
+ maxCount: 1,
189
+ outputParamStyle: 'response-format',
190
+ defaultOutputMimeType: 'image/png'
191
+ },
192
+ {
193
+ modelPrefix: 'dall-e-2',
194
+ format: 'openai-images',
195
+ acceptsImageReferenceInput: false,
196
+ acceptedSizes: ['256x256', '512x512', '1024x1024'],
197
+ supportsQualityParam: false,
198
+ maxCount: 10,
199
+ outputParamStyle: 'response-format',
200
+ defaultOutputMimeType: 'image/png'
201
+ },
202
+ {
203
+ modelPrefix: '',
204
+ format: 'openai-images',
205
+ outputParamStyle: 'response-format',
206
+ defaultOutputMimeType: 'image/png'
207
+ }
143
208
  ]
144
209
  },
145
210
  {
@@ -153,7 +218,8 @@ const BUILTIN_PROVIDERS = [
153
218
  supportedTools: [],
154
219
  corsRestricted: false,
155
220
  streamingCorsRestricted: false,
156
- acceptsImageInput: false
221
+ acceptsImageInput: false,
222
+ thinkingMode: 'unsupported'
157
223
  },
158
224
  {
159
225
  id: 'xai-grok',
@@ -163,15 +229,38 @@ const BUILTIN_PROVIDERS = [
163
229
  apiFormat: 'openai',
164
230
  baseUrl: 'https://api.x.ai/v1',
165
231
  defaultModel: {
166
- base: 'grok-4-1-fast',
167
- tools: 'grok-4-1-fast-reasoning',
168
- image: 'grok-2-image-1212'
232
+ base: 'grok-4.3',
233
+ tools: 'grok-4.3',
234
+ thinking: 'grok-4.3',
235
+ image: 'grok-imagine-image-quality'
169
236
  },
170
237
  supportedTools: ['web_search'],
171
238
  corsRestricted: true,
172
239
  streamingCorsRestricted: true,
173
240
  acceptsImageInput: true,
174
- imageGeneration: [{ modelPrefix: '', format: 'xai-images' }]
241
+ thinkingMode: 'optional',
242
+ imageGeneration: [
243
+ {
244
+ // grok-imagine models use JSON edits with image_url objects (different wire format)
245
+ modelPrefix: 'grok-imagine-',
246
+ format: 'xai-images-edits',
247
+ acceptsImageReferenceInput: true,
248
+ supportsQualityParam: false,
249
+ maxCount: 10,
250
+ outputParamStyle: 'response-format',
251
+ defaultOutputMimeType: 'image/jpeg'
252
+ },
253
+ {
254
+ // catch-all for other xai image models
255
+ modelPrefix: '',
256
+ format: 'xai-images',
257
+ acceptsImageReferenceInput: false,
258
+ supportsQualityParam: false,
259
+ maxCount: 10,
260
+ outputParamStyle: 'response-format',
261
+ defaultOutputMimeType: 'image/jpeg'
262
+ }
263
+ ]
175
264
  }
176
265
  ];
177
266
  /**
@@ -256,22 +345,31 @@ exports.DEFAULT_MODEL_CAPABILITY_CONFIG = {
256
345
  openai: [
257
346
  { idPattern: /^dall-e/, capabilities: ['image-generation'] },
258
347
  { idPattern: /^gpt-image/, capabilities: ['image-generation'] },
348
+ { idPattern: /^gpt-5/, capabilities: ['chat', 'tools', 'vision', 'thinking'] },
259
349
  { idPattern: /^gpt-4/, capabilities: ['chat', 'tools', 'vision'] },
260
350
  { idPattern: /^gpt-3\.5/, capabilities: ['chat'] },
261
- { idPattern: /^o\d/, capabilities: ['chat', 'tools'] }
351
+ { idPattern: /^o\d/, capabilities: ['chat', 'tools', 'thinking'] }
262
352
  ],
263
353
  'xai-grok': [
264
354
  { idPattern: /-image/, capabilities: ['image-generation'] },
355
+ { idPattern: /^grok-4\.3/, capabilities: ['chat', 'tools', 'thinking'] },
356
+ { idPattern: /^grok-4$/, capabilities: ['chat', 'tools', 'thinking'] },
265
357
  { idPattern: /^grok-4/, capabilities: ['chat', 'tools', 'vision'] },
358
+ { idPattern: /^grok-3-mini/, capabilities: ['chat', 'tools', 'thinking'] },
266
359
  { idPattern: /^grok-3/, capabilities: ['chat', 'tools'] },
267
360
  { idPattern: /^grok-2/, capabilities: ['chat', 'vision'] }
268
361
  ],
269
362
  'google-gemini': [
270
363
  { idPattern: /^imagen/, capabilities: ['image-generation'] },
271
364
  { idPattern: /^gemini-.*-image/, capabilities: ['image-generation'] },
365
+ { idPattern: /^gemini-2\.5/, capabilities: ['chat', 'tools', 'vision', 'thinking'] },
272
366
  { idPattern: /^gemini-/, capabilities: ['chat', 'tools', 'vision'] }
273
367
  ],
274
- anthropic: [{ idPattern: /^claude-/, capabilities: ['chat', 'tools', 'vision'] }],
368
+ anthropic: [
369
+ { idPattern: /^claude-opus-4/, capabilities: ['chat', 'tools', 'vision', 'thinking'] },
370
+ { idPattern: /^claude-sonnet-4/, capabilities: ['chat', 'tools', 'vision', 'thinking'] },
371
+ { idPattern: /^claude-/, capabilities: ['chat', 'tools', 'vision'] }
372
+ ],
275
373
  groq: [{ idPattern: /./, capabilities: ['chat'] }],
276
374
  mistral: [{ idPattern: /./, capabilities: ['chat'] }]
277
375
  }
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/registry.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n/**\n * Centralized provider registry — single source of truth for all AI provider metadata.\n * @packageDocumentation\n */\n\nimport { fail, Result, succeed } from '@fgv/ts-utils';\n\nimport {\n type AiProviderId,\n type IAiImageModelCapability,\n type IAiModelCapabilityConfig,\n type IAiProviderDescriptor\n} from './model';\n\n// ============================================================================\n// Built-in providers\n// ============================================================================\n\n/**\n * All known AI provider descriptors. Copy-paste first, then alphabetical.\n * @internal\n */\nconst BUILTIN_PROVIDERS: ReadonlyArray<IAiProviderDescriptor> = [\n {\n id: 'copy-paste',\n label: 'Copy / Paste',\n buttonLabel: 'AI Assist | Copy',\n needsSecret: false,\n apiFormat: 'openai',\n baseUrl: '',\n defaultModel: '',\n supportedTools: [],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: false\n },\n {\n id: 'anthropic',\n label: 'Anthropic Claude',\n buttonLabel: 'AI Assist | Claude',\n needsSecret: true,\n apiFormat: 'anthropic',\n baseUrl: 'https://api.anthropic.com/v1',\n defaultModel: 'claude-sonnet-4-5-20250929',\n supportedTools: ['web_search'],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: true\n },\n {\n id: 'google-gemini',\n label: 'Google Gemini',\n buttonLabel: 'AI Assist | Gemini',\n needsSecret: true,\n apiFormat: 'gemini',\n baseUrl: 'https://generativelanguage.googleapis.com/v1beta',\n defaultModel: { base: 'gemini-2.5-flash', image: 'gemini-2.5-flash-image' },\n supportedTools: ['web_search'],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: true,\n imageGeneration: [\n // imagen-* models are predict-only and do not accept reference images;\n // everything else uses chat-style :generateContent with refs.\n { modelPrefix: 'imagen-', format: 'gemini-imagen' },\n { modelPrefix: '', format: 'gemini-image-out', acceptsImageReferenceInput: true }\n ]\n },\n {\n id: 'groq',\n label: 'Groq',\n buttonLabel: 'AI Assist | Groq',\n needsSecret: true,\n apiFormat: 'openai',\n baseUrl: 'https://api.groq.com/openai/v1',\n defaultModel: 'llama-3.3-70b-versatile',\n supportedTools: [],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: false\n },\n {\n id: 'mistral',\n label: 'Mistral',\n buttonLabel: 'AI Assist | Mistral',\n needsSecret: true,\n apiFormat: 'openai',\n baseUrl: 'https://api.mistral.ai/v1',\n defaultModel: 'mistral-large-latest',\n supportedTools: [],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: false\n },\n {\n id: 'ollama',\n label: 'Ollama (self-hosted)',\n buttonLabel: 'AI Assist | Ollama',\n needsSecret: false,\n apiFormat: 'openai',\n baseUrl: 'http://localhost:11434/v1',\n defaultModel: '',\n supportedTools: [],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: false\n },\n {\n id: 'openai',\n label: 'OpenAI',\n buttonLabel: 'AI Assist | OpenAI',\n needsSecret: true,\n apiFormat: 'openai',\n baseUrl: 'https://api.openai.com/v1',\n defaultModel: { base: 'gpt-4o', image: 'dall-e-3' },\n supportedTools: ['web_search'],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: true,\n imageGeneration: [\n // gpt-image-1 supports /images/edits with reference images. dall-e-3\n // (the default image model) does not, so the catch-all rule omits\n // acceptsImageReferenceInput; callers selecting dall-e-3 with refs hit\n // the up-front rejection rather than a provider 400.\n { modelPrefix: 'gpt-image-', format: 'openai-images', acceptsImageReferenceInput: true },\n { modelPrefix: '', format: 'openai-images' }\n ]\n },\n {\n id: 'openai-compat',\n label: 'OpenAI-compatible (self-hosted)',\n buttonLabel: 'AI Assist | OpenAI-compat',\n needsSecret: false,\n apiFormat: 'openai',\n baseUrl: '',\n defaultModel: '',\n supportedTools: [],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: false\n },\n {\n id: 'xai-grok',\n label: 'xAI Grok',\n buttonLabel: 'AI Assist | Grok',\n needsSecret: true,\n apiFormat: 'openai',\n baseUrl: 'https://api.x.ai/v1',\n defaultModel: {\n base: 'grok-4-1-fast',\n tools: 'grok-4-1-fast-reasoning',\n image: 'grok-2-image-1212'\n },\n supportedTools: ['web_search'],\n corsRestricted: true,\n streamingCorsRestricted: true,\n acceptsImageInput: true,\n imageGeneration: [{ modelPrefix: '', format: 'xai-images' }]\n }\n];\n\n/**\n * Index for O(1) lookup by id.\n * @internal\n */\nconst PROVIDER_BY_ID: ReadonlyMap<string, IAiProviderDescriptor> = new Map(\n BUILTIN_PROVIDERS.map((d) => [d.id, d])\n);\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * All valid provider ID values, in the same order as the registry.\n * @public\n */\nexport const allProviderIds: ReadonlyArray<AiProviderId> = BUILTIN_PROVIDERS.map((d) => d.id);\n\n/**\n * Get all known provider descriptors. Copy-paste first, then alphabetical.\n * @returns All built-in provider descriptors\n * @public\n */\nexport function getProviderDescriptors(): ReadonlyArray<IAiProviderDescriptor> {\n return BUILTIN_PROVIDERS;\n}\n\n/**\n * Get a provider descriptor by id.\n * @param id - The provider identifier\n * @returns The descriptor, or a failure if the provider is unknown\n * @public\n */\nexport function getProviderDescriptor(id: string): Result<IAiProviderDescriptor> {\n const descriptor = PROVIDER_BY_ID.get(id);\n if (!descriptor) {\n return fail(`unknown AI provider: ${id}`);\n }\n return succeed(descriptor);\n}\n\n/**\n * Whether a provider declares any image-generation capability at all.\n *\n * @param descriptor - The provider descriptor\n * @returns `true` when {@link IAiProviderDescriptor.imageGeneration} has at\n * least one entry; `false` otherwise.\n * @public\n */\nexport function supportsImageGeneration(descriptor: IAiProviderDescriptor): boolean {\n return (descriptor.imageGeneration?.length ?? 0) > 0;\n}\n\n/**\n * Resolve the image-generation capability that applies to a given model id\n * for a provider. Returns the entry from\n * {@link IAiProviderDescriptor.imageGeneration} whose `modelPrefix` is the\n * longest prefix of `modelId`. Ties are broken by first-encountered, so rule\n * order does not matter for correctness — only for tie-breaking among rules\n * with identical-length prefixes (an unusual case).\n *\n * @param descriptor - The provider descriptor\n * @param modelId - The resolved image model id\n * @returns The matching capability, or `undefined` when no rule matches or\n * the provider declares no image-generation capabilities.\n * @public\n */\nexport function resolveImageCapability(\n descriptor: IAiProviderDescriptor,\n modelId: string\n): IAiImageModelCapability | undefined {\n return (descriptor.imageGeneration ?? [])\n .filter((cap) => modelId.startsWith(cap.modelPrefix))\n .reduce<IAiImageModelCapability | undefined>(\n (best, cap) => (best && best.modelPrefix.length >= cap.modelPrefix.length ? best : cap),\n undefined\n );\n}\n\n// ============================================================================\n// Default model capability config\n// ============================================================================\n\n/**\n * Default capability config used by `callProviderListModels` when callers\n * don't supply their own. Patterns are intentionally narrow — false\n * positives are worse than missing a model. Caller can override per call\n * via {@link IProviderListModelsParams.capabilityConfig}.\n *\n * @public\n */\nexport const DEFAULT_MODEL_CAPABILITY_CONFIG: IAiModelCapabilityConfig = {\n perProvider: {\n openai: [\n { idPattern: /^dall-e/, capabilities: ['image-generation'] },\n { idPattern: /^gpt-image/, capabilities: ['image-generation'] },\n { idPattern: /^gpt-4/, capabilities: ['chat', 'tools', 'vision'] },\n { idPattern: /^gpt-3\\.5/, capabilities: ['chat'] },\n { idPattern: /^o\\d/, capabilities: ['chat', 'tools'] }\n ],\n 'xai-grok': [\n { idPattern: /-image/, capabilities: ['image-generation'] },\n { idPattern: /^grok-4/, capabilities: ['chat', 'tools', 'vision'] },\n { idPattern: /^grok-3/, capabilities: ['chat', 'tools'] },\n { idPattern: /^grok-2/, capabilities: ['chat', 'vision'] }\n ],\n 'google-gemini': [\n { idPattern: /^imagen/, capabilities: ['image-generation'] },\n { idPattern: /^gemini-.*-image/, capabilities: ['image-generation'] },\n { idPattern: /^gemini-/, capabilities: ['chat', 'tools', 'vision'] }\n ],\n anthropic: [{ idPattern: /^claude-/, capabilities: ['chat', 'tools', 'vision'] }],\n groq: [{ idPattern: /./, capabilities: ['chat'] }],\n mistral: [{ idPattern: /./, capabilities: ['chat'] }]\n }\n};\n"]}
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/registry.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n/**\n * Centralized provider registry — single source of truth for all AI provider metadata.\n * @packageDocumentation\n */\n\nimport { fail, Result, succeed } from '@fgv/ts-utils';\n\nimport {\n type AiProviderId,\n type IAiImageModelCapability,\n type IAiModelCapabilityConfig,\n type IAiProviderDescriptor\n} from './model';\n\n// ============================================================================\n// Built-in providers\n// ============================================================================\n\n/**\n * All known AI provider descriptors. Copy-paste first, then alphabetical.\n * @internal\n */\nconst BUILTIN_PROVIDERS: ReadonlyArray<IAiProviderDescriptor> = [\n {\n id: 'copy-paste',\n label: 'Copy / Paste',\n buttonLabel: 'AI Assist | Copy',\n needsSecret: false,\n apiFormat: 'openai',\n baseUrl: '',\n defaultModel: '',\n supportedTools: [],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: false,\n thinkingMode: 'unsupported'\n },\n {\n id: 'anthropic',\n label: 'Anthropic Claude',\n buttonLabel: 'AI Assist | Claude',\n needsSecret: true,\n apiFormat: 'anthropic',\n baseUrl: 'https://api.anthropic.com/v1',\n defaultModel: 'claude-sonnet-4-5-20250929',\n supportedTools: ['web_search'],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: true,\n thinkingMode: 'optional'\n },\n {\n id: 'google-gemini',\n label: 'Google Gemini',\n buttonLabel: 'AI Assist | Gemini',\n needsSecret: true,\n apiFormat: 'gemini',\n baseUrl: 'https://generativelanguage.googleapis.com/v1beta',\n defaultModel: { base: 'gemini-2.5-flash', image: 'gemini-2.5-flash-image' },\n supportedTools: ['web_search'],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: true,\n thinkingMode: 'optional',\n imageGeneration: [\n {\n // Imagen 4 Ultra: max 1 image\n modelPrefix: 'imagen-4.0-ultra-',\n format: 'gemini-imagen',\n acceptsImageReferenceInput: false,\n supportsQualityParam: false,\n maxCount: 1,\n outputParamStyle: 'none',\n defaultOutputMimeType: 'image/png'\n },\n {\n // All other Imagen 4 models: max 4 images\n modelPrefix: 'imagen-',\n format: 'gemini-imagen',\n acceptsImageReferenceInput: false,\n supportsQualityParam: false,\n maxCount: 4,\n outputParamStyle: 'none',\n defaultOutputMimeType: 'image/png'\n },\n {\n // Gemini Flash Image: chat-style generateContent\n modelPrefix: '',\n format: 'gemini-image-out',\n acceptsImageReferenceInput: true,\n supportsQualityParam: false,\n maxCount: 1,\n outputParamStyle: 'none',\n defaultOutputMimeType: 'image/jpeg'\n }\n ]\n },\n {\n id: 'groq',\n label: 'Groq',\n buttonLabel: 'AI Assist | Groq',\n needsSecret: true,\n apiFormat: 'openai',\n baseUrl: 'https://api.groq.com/openai/v1',\n defaultModel: 'llama-3.3-70b-versatile',\n supportedTools: [],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: false,\n thinkingMode: 'unsupported'\n },\n {\n id: 'mistral',\n label: 'Mistral',\n buttonLabel: 'AI Assist | Mistral',\n needsSecret: true,\n apiFormat: 'openai',\n baseUrl: 'https://api.mistral.ai/v1',\n defaultModel: 'mistral-large-latest',\n supportedTools: [],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: false,\n thinkingMode: 'unsupported'\n },\n {\n id: 'ollama',\n label: 'Ollama (self-hosted)',\n buttonLabel: 'AI Assist | Ollama',\n needsSecret: false,\n apiFormat: 'openai',\n baseUrl: 'http://localhost:11434/v1',\n defaultModel: '',\n supportedTools: [],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: false,\n thinkingMode: 'unsupported'\n },\n {\n id: 'openai',\n label: 'OpenAI',\n buttonLabel: 'AI Assist | OpenAI',\n needsSecret: true,\n apiFormat: 'openai',\n baseUrl: 'https://api.openai.com/v1',\n defaultModel: { base: 'gpt-4o', image: 'dall-e-3' },\n supportedTools: ['web_search'],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: true,\n thinkingMode: 'optional',\n imageGeneration: [\n {\n modelPrefix: 'gpt-image-',\n format: 'openai-images',\n acceptsImageReferenceInput: true,\n acceptedSizes: ['1024x1024', '1536x1024', '1024x1536', 'auto'],\n supportsQualityParam: true,\n acceptedQualities: ['low', 'medium', 'high', 'auto'],\n maxCount: 10,\n outputParamStyle: 'output-format',\n defaultOutputMimeType: 'image/png'\n },\n {\n modelPrefix: 'dall-e-3',\n format: 'openai-images',\n acceptsImageReferenceInput: false,\n acceptedSizes: ['1024x1024', '1792x1024', '1024x1792'],\n supportsQualityParam: true,\n acceptedQualities: ['standard', 'hd'],\n maxCount: 1,\n outputParamStyle: 'response-format',\n defaultOutputMimeType: 'image/png'\n },\n {\n modelPrefix: 'dall-e-2',\n format: 'openai-images',\n acceptsImageReferenceInput: false,\n acceptedSizes: ['256x256', '512x512', '1024x1024'],\n supportsQualityParam: false,\n maxCount: 10,\n outputParamStyle: 'response-format',\n defaultOutputMimeType: 'image/png'\n },\n {\n modelPrefix: '',\n format: 'openai-images',\n outputParamStyle: 'response-format',\n defaultOutputMimeType: 'image/png'\n }\n ]\n },\n {\n id: 'openai-compat',\n label: 'OpenAI-compatible (self-hosted)',\n buttonLabel: 'AI Assist | OpenAI-compat',\n needsSecret: false,\n apiFormat: 'openai',\n baseUrl: '',\n defaultModel: '',\n supportedTools: [],\n corsRestricted: false,\n streamingCorsRestricted: false,\n acceptsImageInput: false,\n thinkingMode: 'unsupported'\n },\n {\n id: 'xai-grok',\n label: 'xAI Grok',\n buttonLabel: 'AI Assist | Grok',\n needsSecret: true,\n apiFormat: 'openai',\n baseUrl: 'https://api.x.ai/v1',\n defaultModel: {\n base: 'grok-4.3',\n tools: 'grok-4.3',\n thinking: 'grok-4.3',\n image: 'grok-imagine-image-quality'\n },\n supportedTools: ['web_search'],\n corsRestricted: true,\n streamingCorsRestricted: true,\n acceptsImageInput: true,\n thinkingMode: 'optional',\n imageGeneration: [\n {\n // grok-imagine models use JSON edits with image_url objects (different wire format)\n modelPrefix: 'grok-imagine-',\n format: 'xai-images-edits',\n acceptsImageReferenceInput: true,\n supportsQualityParam: false,\n maxCount: 10,\n outputParamStyle: 'response-format',\n defaultOutputMimeType: 'image/jpeg'\n },\n {\n // catch-all for other xai image models\n modelPrefix: '',\n format: 'xai-images',\n acceptsImageReferenceInput: false,\n supportsQualityParam: false,\n maxCount: 10,\n outputParamStyle: 'response-format',\n defaultOutputMimeType: 'image/jpeg'\n }\n ]\n }\n];\n\n/**\n * Index for O(1) lookup by id.\n * @internal\n */\nconst PROVIDER_BY_ID: ReadonlyMap<string, IAiProviderDescriptor> = new Map(\n BUILTIN_PROVIDERS.map((d) => [d.id, d])\n);\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * All valid provider ID values, in the same order as the registry.\n * @public\n */\nexport const allProviderIds: ReadonlyArray<AiProviderId> = BUILTIN_PROVIDERS.map((d) => d.id);\n\n/**\n * Get all known provider descriptors. Copy-paste first, then alphabetical.\n * @returns All built-in provider descriptors\n * @public\n */\nexport function getProviderDescriptors(): ReadonlyArray<IAiProviderDescriptor> {\n return BUILTIN_PROVIDERS;\n}\n\n/**\n * Get a provider descriptor by id.\n * @param id - The provider identifier\n * @returns The descriptor, or a failure if the provider is unknown\n * @public\n */\nexport function getProviderDescriptor(id: string): Result<IAiProviderDescriptor> {\n const descriptor = PROVIDER_BY_ID.get(id);\n if (!descriptor) {\n return fail(`unknown AI provider: ${id}`);\n }\n return succeed(descriptor);\n}\n\n/**\n * Whether a provider declares any image-generation capability at all.\n *\n * @param descriptor - The provider descriptor\n * @returns `true` when {@link IAiProviderDescriptor.imageGeneration} has at\n * least one entry; `false` otherwise.\n * @public\n */\nexport function supportsImageGeneration(descriptor: IAiProviderDescriptor): boolean {\n return (descriptor.imageGeneration?.length ?? 0) > 0;\n}\n\n/**\n * Resolve the image-generation capability that applies to a given model id\n * for a provider. Returns the entry from\n * {@link IAiProviderDescriptor.imageGeneration} whose `modelPrefix` is the\n * longest prefix of `modelId`. Ties are broken by first-encountered, so rule\n * order does not matter for correctness — only for tie-breaking among rules\n * with identical-length prefixes (an unusual case).\n *\n * @param descriptor - The provider descriptor\n * @param modelId - The resolved image model id\n * @returns The matching capability, or `undefined` when no rule matches or\n * the provider declares no image-generation capabilities.\n * @public\n */\nexport function resolveImageCapability(\n descriptor: IAiProviderDescriptor,\n modelId: string\n): IAiImageModelCapability | undefined {\n return (descriptor.imageGeneration ?? [])\n .filter((cap) => modelId.startsWith(cap.modelPrefix))\n .reduce<IAiImageModelCapability | undefined>(\n (best, cap) => (best && best.modelPrefix.length >= cap.modelPrefix.length ? best : cap),\n undefined\n );\n}\n\n// ============================================================================\n// Default model capability config\n// ============================================================================\n\n/**\n * Default capability config used by `callProviderListModels` when callers\n * don't supply their own. Patterns are intentionally narrow — false\n * positives are worse than missing a model. Caller can override per call\n * via {@link IProviderListModelsParams.capabilityConfig}.\n *\n * @public\n */\nexport const DEFAULT_MODEL_CAPABILITY_CONFIG: IAiModelCapabilityConfig = {\n perProvider: {\n openai: [\n { idPattern: /^dall-e/, capabilities: ['image-generation'] },\n { idPattern: /^gpt-image/, capabilities: ['image-generation'] },\n { idPattern: /^gpt-5/, capabilities: ['chat', 'tools', 'vision', 'thinking'] },\n { idPattern: /^gpt-4/, capabilities: ['chat', 'tools', 'vision'] },\n { idPattern: /^gpt-3\\.5/, capabilities: ['chat'] },\n { idPattern: /^o\\d/, capabilities: ['chat', 'tools', 'thinking'] }\n ],\n 'xai-grok': [\n { idPattern: /-image/, capabilities: ['image-generation'] },\n { idPattern: /^grok-4\\.3/, capabilities: ['chat', 'tools', 'thinking'] },\n { idPattern: /^grok-4$/, capabilities: ['chat', 'tools', 'thinking'] },\n { idPattern: /^grok-4/, capabilities: ['chat', 'tools', 'vision'] },\n { idPattern: /^grok-3-mini/, capabilities: ['chat', 'tools', 'thinking'] },\n { idPattern: /^grok-3/, capabilities: ['chat', 'tools'] },\n { idPattern: /^grok-2/, capabilities: ['chat', 'vision'] }\n ],\n 'google-gemini': [\n { idPattern: /^imagen/, capabilities: ['image-generation'] },\n { idPattern: /^gemini-.*-image/, capabilities: ['image-generation'] },\n { idPattern: /^gemini-2\\.5/, capabilities: ['chat', 'tools', 'vision', 'thinking'] },\n { idPattern: /^gemini-/, capabilities: ['chat', 'tools', 'vision'] }\n ],\n anthropic: [\n { idPattern: /^claude-opus-4/, capabilities: ['chat', 'tools', 'vision', 'thinking'] },\n { idPattern: /^claude-sonnet-4/, capabilities: ['chat', 'tools', 'vision', 'thinking'] },\n { idPattern: /^claude-/, capabilities: ['chat', 'tools', 'vision'] }\n ],\n groq: [{ idPattern: /./, capabilities: ['chat'] }],\n mistral: [{ idPattern: /./, capabilities: ['chat'] }]\n }\n};\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"sseParser.d.ts","sourceRoot":"","sources":["../../../src/packlets/ai-assist/sseParser.ts"],"names":[],"mappings":"AAoBA;;;;;;;GAOG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,+DAA+D;IAC/D,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAgBlE;AAED;;;;;;GAMG;AACH,wBAAuB,aAAa,CAAC,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,cAAc,CAAC,SAAS,CAAC,CAkChG;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CASnE"}
1
+ {"version":3,"file":"sseParser.d.ts","sourceRoot":"","sources":["../../../src/packlets/ai-assist/sseParser.ts"],"names":[],"mappings":"AAoBA;;;;;;;GAOG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,+DAA+D;IAC/D,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAgBlE;AAED;;;;;;GAMG;AACH,wBAAuB,aAAa,CAAC,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,cAAc,CAAC,SAAS,CAAC,CAmChG;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CASnE"}
@@ -92,6 +92,7 @@ function readSseEvents(body) {
92
92
  const normalized = buffer.replace(/\r\n/g, '\n');
93
93
  const parts = normalized.split('\n\n');
94
94
  // Last element is the partial chunk (no terminating blank line yet); buffer it.
95
+ /* c8 ignore next 1 - defensive: parts.pop() returning undefined is unreachable after split */
95
96
  buffer = (_a = parts.pop()) !== null && _a !== void 0 ? _a : '';
96
97
  for (const chunk of parts) {
97
98
  const event = parseSseEvent(chunk);
@@ -1 +1 @@
1
- {"version":3,"file":"sseParser.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/sseParser.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n/**\n * Server-Sent Events (SSE) parser primitives shared by the streaming format\n * adapters in `streamingClient.ts`. Implements the subset of the SSE wire\n * format that LLM providers actually emit: optional `event:` line, one or\n * more `data:` lines per message, messages separated by a blank line.\n *\n * @packageDocumentation\n */\n\n/**\n * One parsed SSE message (the SSE spec calls each blank-line-terminated\n * record an \"event\").\n *\n * @internal\n */\nexport interface ISseEvent {\n /** The `event:` field, if present (some providers omit it). */\n readonly event?: string;\n /** Concatenated contents of the message's `data:` lines. */\n readonly data: string;\n}\n\n/**\n * Parses a single SSE message (the text between blank-line separators) into\n * an {@link ISseEvent}. Returns undefined for messages with no `data:` lines\n * (comments, heartbeats).\n *\n * @internal\n */\nexport function parseSseEvent(chunk: string): ISseEvent | undefined {\n let event: string | undefined;\n const dataLines: string[] = [];\n for (const line of chunk.split('\\n')) {\n if (line.startsWith('event:')) {\n event = line.slice(6).trim();\n } else if (line.startsWith('data:')) {\n // Per the SSE spec the value starts after the colon, with one optional leading space stripped.\n const value = line.slice(5);\n dataLines.push(value.startsWith(' ') ? value.slice(1) : value);\n }\n }\n if (dataLines.length === 0) {\n return undefined;\n }\n return event !== undefined ? { event, data: dataLines.join('\\n') } : { data: dataLines.join('\\n') };\n}\n\n/**\n * Reads an SSE response body and yields parsed events. Buffers across read()\n * boundaries so a message split mid-chunk still parses cleanly. Terminates\n * when the stream closes (normal EOF or aborted fetch).\n *\n * @internal\n */\nexport async function* readSseEvents(body: ReadableStream<Uint8Array>): AsyncGenerator<ISseEvent> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n try {\n let streaming = true;\n while (streaming) {\n const { value, done } = await reader.read();\n if (done) {\n streaming = false;\n if (buffer.length > 0) {\n const tail = parseSseEvent(buffer.replace(/\\r\\n/g, '\\n'));\n if (tail) {\n yield tail;\n }\n }\n break;\n }\n buffer += decoder.decode(value, { stream: true });\n // SSE messages are separated by a blank line; some servers use \\r\\n.\n const normalized = buffer.replace(/\\r\\n/g, '\\n');\n const parts = normalized.split('\\n\\n');\n // Last element is the partial chunk (no terminating blank line yet); buffer it.\n buffer = parts.pop() ?? '';\n for (const chunk of parts) {\n const event = parseSseEvent(chunk);\n if (event) {\n yield event;\n }\n }\n }\n } finally {\n reader.releaseLock();\n }\n}\n\n/**\n * Parses the `data` payload of an SSE event as JSON. Returns undefined for\n * the OpenAI `[DONE]` sentinel and for any payload that fails to parse —\n * adapters treat both as \"skip this event.\"\n *\n * @internal\n */\nexport function parseSseEventJson(data: string): unknown | undefined {\n if (data === '[DONE]') {\n return undefined;\n }\n try {\n return JSON.parse(data);\n } catch {\n return undefined;\n }\n}\n"]}
1
+ {"version":3,"file":"sseParser.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/sseParser.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n/**\n * Server-Sent Events (SSE) parser primitives shared by the streaming format\n * adapters in `streamingClient.ts`. Implements the subset of the SSE wire\n * format that LLM providers actually emit: optional `event:` line, one or\n * more `data:` lines per message, messages separated by a blank line.\n *\n * @packageDocumentation\n */\n\n/**\n * One parsed SSE message (the SSE spec calls each blank-line-terminated\n * record an \"event\").\n *\n * @internal\n */\nexport interface ISseEvent {\n /** The `event:` field, if present (some providers omit it). */\n readonly event?: string;\n /** Concatenated contents of the message's `data:` lines. */\n readonly data: string;\n}\n\n/**\n * Parses a single SSE message (the text between blank-line separators) into\n * an {@link ISseEvent}. Returns undefined for messages with no `data:` lines\n * (comments, heartbeats).\n *\n * @internal\n */\nexport function parseSseEvent(chunk: string): ISseEvent | undefined {\n let event: string | undefined;\n const dataLines: string[] = [];\n for (const line of chunk.split('\\n')) {\n if (line.startsWith('event:')) {\n event = line.slice(6).trim();\n } else if (line.startsWith('data:')) {\n // Per the SSE spec the value starts after the colon, with one optional leading space stripped.\n const value = line.slice(5);\n dataLines.push(value.startsWith(' ') ? value.slice(1) : value);\n }\n }\n if (dataLines.length === 0) {\n return undefined;\n }\n return event !== undefined ? { event, data: dataLines.join('\\n') } : { data: dataLines.join('\\n') };\n}\n\n/**\n * Reads an SSE response body and yields parsed events. Buffers across read()\n * boundaries so a message split mid-chunk still parses cleanly. Terminates\n * when the stream closes (normal EOF or aborted fetch).\n *\n * @internal\n */\nexport async function* readSseEvents(body: ReadableStream<Uint8Array>): AsyncGenerator<ISseEvent> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n try {\n let streaming = true;\n while (streaming) {\n const { value, done } = await reader.read();\n if (done) {\n streaming = false;\n if (buffer.length > 0) {\n const tail = parseSseEvent(buffer.replace(/\\r\\n/g, '\\n'));\n if (tail) {\n yield tail;\n }\n }\n break;\n }\n buffer += decoder.decode(value, { stream: true });\n // SSE messages are separated by a blank line; some servers use \\r\\n.\n const normalized = buffer.replace(/\\r\\n/g, '\\n');\n const parts = normalized.split('\\n\\n');\n // Last element is the partial chunk (no terminating blank line yet); buffer it.\n /* c8 ignore next 1 - defensive: parts.pop() returning undefined is unreachable after split */\n buffer = parts.pop() ?? '';\n for (const chunk of parts) {\n const event = parseSseEvent(chunk);\n if (event) {\n yield event;\n }\n }\n }\n } finally {\n reader.releaseLock();\n }\n}\n\n/**\n * Parses the `data` payload of an SSE event as JSON. Returns undefined for\n * the OpenAI `[DONE]` sentinel and for any payload that fails to parse —\n * adapters treat both as \"skip this event.\"\n *\n * @internal\n */\nexport function parseSseEventJson(data: string): unknown | undefined {\n if (data === '[DONE]') {\n return undefined;\n }\n try {\n return JSON.parse(data);\n } catch {\n return undefined;\n }\n}\n"]}
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import { type Logging, Result } from '@fgv/ts-utils';
9
9
  import { AiPrompt, type AiServerToolConfig, type IAiStreamEvent, type IChatMessage } from '../model';
10
+ import { type IResolvedThinkingConfig } from '../thinkingOptionsResolver';
10
11
  import { IStreamApiConfig } from './common';
11
12
  /**
12
13
  * Issues a streaming Anthropic Messages request and returns the
@@ -14,5 +15,5 @@ import { IStreamApiConfig } from './common';
14
15
  *
15
16
  * @internal
16
17
  */
17
- export declare function callAnthropicStream(config: IStreamApiConfig, prompt: AiPrompt, messagesBefore: ReadonlyArray<IChatMessage> | undefined, temperature: number, tools: ReadonlyArray<AiServerToolConfig> | undefined, logger?: Logging.ILogger, signal?: AbortSignal): Promise<Result<AsyncIterable<IAiStreamEvent>>>;
18
+ export declare function callAnthropicStream(config: IStreamApiConfig, prompt: AiPrompt, messagesBefore: ReadonlyArray<IChatMessage> | undefined, temperature: number, tools: ReadonlyArray<AiServerToolConfig> | undefined, logger?: Logging.ILogger, signal?: AbortSignal, resolvedThinking?: IResolvedThinkingConfig): Promise<Result<AsyncIterable<IAiStreamEvent>>>;
18
19
  //# sourceMappingURL=anthropic.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../../../src/packlets/ai-assist/streamingAdapters/anthropic.ts"],"names":[],"mappings":"AAoBA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,EAAuC,MAAM,eAAe,CAAC;AAG1F,OAAO,EAAE,QAAQ,EAAE,KAAK,kBAAkB,EAAE,KAAK,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AAGrG,OAAO,EAAE,gBAAgB,EAA2C,MAAM,UAAU,CAAC;AA2KrF;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,QAAQ,EAChB,cAAc,EAAE,aAAa,CAAC,YAAY,CAAC,GAAG,SAAS,EACvD,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,aAAa,CAAC,kBAAkB,CAAC,GAAG,SAAS,EACpD,MAAM,CAAC,EAAE,OAAO,CAAC,OAAO,EACxB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC,CAAC,CA0BhD"}
1
+ {"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../../../src/packlets/ai-assist/streamingAdapters/anthropic.ts"],"names":[],"mappings":"AAoBA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,EAAuC,MAAM,eAAe,CAAC;AAG1F,OAAO,EAAE,QAAQ,EAAE,KAAK,kBAAkB,EAAE,KAAK,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AAGrG,OAAO,EAAE,KAAK,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAC1E,OAAO,EAAE,gBAAgB,EAA2C,MAAM,UAAU,CAAC;AA+KrF;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,QAAQ,EAChB,cAAc,EAAE,aAAa,CAAC,YAAY,CAAC,GAAG,SAAS,EACvD,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,aAAa,CAAC,kBAAkB,CAAC,GAAG,SAAS,EACpD,MAAM,CAAC,EAAE,OAAO,CAAC,OAAO,EACxB,MAAM,CAAC,EAAE,WAAW,EACpB,gBAAgB,CAAC,EAAE,uBAAuB,GACzC,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC,CAAC,CAkChD"}