@fgv/ts-extras 5.1.0-33 → 5.1.0-34

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 (77) hide show
  1. package/dist/packlets/ai-assist/apiClient.js +4 -4
  2. package/dist/packlets/ai-assist/apiClient.js.map +1 -1
  3. package/dist/packlets/ai-assist/chatRequestBuilders.js +86 -3
  4. package/dist/packlets/ai-assist/chatRequestBuilders.js.map +1 -1
  5. package/dist/packlets/ai-assist/converters.js +31 -1
  6. package/dist/packlets/ai-assist/converters.js.map +1 -1
  7. package/dist/packlets/ai-assist/index.js +3 -2
  8. package/dist/packlets/ai-assist/index.js.map +1 -1
  9. package/dist/packlets/ai-assist/model.js.map +1 -1
  10. package/dist/packlets/ai-assist/streamingAdapters/anthropic.js +176 -32
  11. package/dist/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -1
  12. package/dist/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js +511 -0
  13. package/dist/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js.map +1 -0
  14. package/dist/packlets/ai-assist/streamingAdapters/common.js +95 -0
  15. package/dist/packlets/ai-assist/streamingAdapters/common.js.map +1 -1
  16. package/dist/packlets/ai-assist/streamingAdapters/gemini.js +34 -10
  17. package/dist/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -1
  18. package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js +215 -15
  19. package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -1
  20. package/dist/packlets/ai-assist/streamingClient.js +18 -0
  21. package/dist/packlets/ai-assist/streamingClient.js.map +1 -1
  22. package/dist/packlets/ai-assist/thinkingOptionsResolver.js +23 -0
  23. package/dist/packlets/ai-assist/thinkingOptionsResolver.js.map +1 -1
  24. package/dist/packlets/ai-assist/toolFormats.js +106 -10
  25. package/dist/packlets/ai-assist/toolFormats.js.map +1 -1
  26. package/dist/ts-extras.d.ts +339 -3
  27. package/lib/packlets/ai-assist/apiClient.d.ts.map +1 -1
  28. package/lib/packlets/ai-assist/apiClient.js +3 -3
  29. package/lib/packlets/ai-assist/apiClient.js.map +1 -1
  30. package/lib/packlets/ai-assist/chatRequestBuilders.d.ts +29 -5
  31. package/lib/packlets/ai-assist/chatRequestBuilders.d.ts.map +1 -1
  32. package/lib/packlets/ai-assist/chatRequestBuilders.js +86 -3
  33. package/lib/packlets/ai-assist/chatRequestBuilders.js.map +1 -1
  34. package/lib/packlets/ai-assist/converters.d.ts +9 -1
  35. package/lib/packlets/ai-assist/converters.d.ts.map +1 -1
  36. package/lib/packlets/ai-assist/converters.js +31 -1
  37. package/lib/packlets/ai-assist/converters.js.map +1 -1
  38. package/lib/packlets/ai-assist/index.d.ts +4 -3
  39. package/lib/packlets/ai-assist/index.d.ts.map +1 -1
  40. package/lib/packlets/ai-assist/index.js +5 -1
  41. package/lib/packlets/ai-assist/index.js.map +1 -1
  42. package/lib/packlets/ai-assist/model.d.ts +183 -3
  43. package/lib/packlets/ai-assist/model.d.ts.map +1 -1
  44. package/lib/packlets/ai-assist/model.js.map +1 -1
  45. package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts +58 -5
  46. package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts.map +1 -1
  47. package/lib/packlets/ai-assist/streamingAdapters/anthropic.js +175 -31
  48. package/lib/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -1
  49. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.d.ts +158 -0
  50. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.d.ts.map +1 -0
  51. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js +517 -0
  52. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js.map +1 -0
  53. package/lib/packlets/ai-assist/streamingAdapters/common.d.ts +51 -0
  54. package/lib/packlets/ai-assist/streamingAdapters/common.d.ts.map +1 -1
  55. package/lib/packlets/ai-assist/streamingAdapters/common.js +97 -0
  56. package/lib/packlets/ai-assist/streamingAdapters/common.js.map +1 -1
  57. package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts +16 -2
  58. package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts.map +1 -1
  59. package/lib/packlets/ai-assist/streamingAdapters/gemini.js +34 -10
  60. package/lib/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -1
  61. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts +15 -2
  62. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts.map +1 -1
  63. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js +214 -14
  64. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -1
  65. package/lib/packlets/ai-assist/streamingClient.d.ts +17 -0
  66. package/lib/packlets/ai-assist/streamingClient.d.ts.map +1 -1
  67. package/lib/packlets/ai-assist/streamingClient.js +20 -1
  68. package/lib/packlets/ai-assist/streamingClient.js.map +1 -1
  69. package/lib/packlets/ai-assist/thinkingOptionsResolver.d.ts +18 -2
  70. package/lib/packlets/ai-assist/thinkingOptionsResolver.d.ts.map +1 -1
  71. package/lib/packlets/ai-assist/thinkingOptionsResolver.js +24 -0
  72. package/lib/packlets/ai-assist/thinkingOptionsResolver.js.map +1 -1
  73. package/lib/packlets/ai-assist/toolFormats.d.ts +40 -9
  74. package/lib/packlets/ai-assist/toolFormats.d.ts.map +1 -1
  75. package/lib/packlets/ai-assist/toolFormats.js +107 -10
  76. package/lib/packlets/ai-assist/toolFormats.js.map +1 -1
  77. package/package.json +7 -7
@@ -24,18 +24,71 @@
24
24
  *
25
25
  * @packageDocumentation
26
26
  */
27
+ import { isJsonObject } from '@fgv/ts-json-base';
28
+ import { Converters } from '@fgv/ts-utils';
27
29
  import { toDataUrl } from './model';
30
+ /**
31
+ * Converter for a rawTail message entry. Narrows a `JsonObject` to
32
+ * `{ role: string; content: string | unknown[] }` at runtime using the
33
+ * Converter pattern. Entries that fail validation are silently skipped — the
34
+ * surrounding function is infallible, and a malformed continuation message is
35
+ * better omitted than transmitted verbatim.
36
+ * @internal
37
+ */
38
+ const rawTailMessageConverter = Converters.object({
39
+ role: Converters.enumeratedValue(['user', 'assistant']),
40
+ content: Converters.oneOf([
41
+ Converters.string,
42
+ Converters.isA('array', (v) => Array.isArray(v))
43
+ ])
44
+ }, { strict: false });
45
+ /**
46
+ * Converter for an OpenAI / xAI Responses API `rawTail` item. These are
47
+ * provider-native input items (`function_call`, `function_call_output`) whose
48
+ * fields differ per item type, so — unlike the Anthropic `{ role, content }`
49
+ * projection — the whole object is preserved verbatim.
50
+ *
51
+ * The static input is already typed `JsonObject`, so the `isJsonObject` guard
52
+ * is a runtime backstop, not a compile-time narrowing: continuation messages
53
+ * originate from a prior turn's `IAiClientToolContinuation.messages` and a
54
+ * consumer may persist and reload them through untyped JSON before passing them
55
+ * back. The guard preserves the same "a malformed continuation message is
56
+ * better omitted than transmitted verbatim" posture as the Anthropic path —
57
+ * non-object entries fail conversion and are skipped by the caller.
58
+ * @internal
59
+ */
60
+ const openAiRawTailItemConverter = Converters.isA('JsonObject', (v) => isJsonObject(v));
61
+ /**
62
+ * Converter for a Gemini `rawTail` item. Gemini continuation messages are
63
+ * `{ role, parts }` turns (a model turn with `functionCall` parts followed by a
64
+ * user turn with `functionResponse` parts). Narrows a `JsonObject` to
65
+ * `{ role: 'user' | 'model'; parts: Array<Record<string, unknown>> }`; entries
66
+ * that fail validation are skipped by the caller.
67
+ * @internal
68
+ */
69
+ const geminiRawTailMessageConverter = Converters.object({
70
+ role: Converters.enumeratedValue(['user', 'model']),
71
+ // `parts` is preserved verbatim and serialized into the request body, so the
72
+ // element shape is not narrowed here — `Array.isArray` soundly guarantees
73
+ // `unknown[]` (narrowing to `Record<string, unknown>[]` would be an unchecked cast).
74
+ parts: Converters.isA('array', (v) => Array.isArray(v))
75
+ }, { strict: false });
28
76
  /**
29
77
  * Builds the messages array from prompt + optional head/tail messages.
30
78
  * The caller supplies the user content (string for text-only, parts array
31
79
  * for vision prompts) since the parts shape differs by format.
32
80
  *
81
+ * `rawTail` items (OpenAI / xAI Responses `function_call` /
82
+ * `function_call_output` continuation items) are appended verbatim after the
83
+ * user message — their fields differ per item `type`, so they are preserved
84
+ * rather than projected. The return type is `Array<Record<string, unknown>>`
85
+ * to accommodate both `{ role, content }` messages and these heterogeneous
86
+ * input items.
87
+ *
33
88
  * @internal
34
89
  */
35
90
  export function buildMessages(systemPrompt, userContent, options) {
36
- const messages = [
37
- { role: 'system', content: systemPrompt }
38
- ];
91
+ const messages = [{ role: 'system', content: systemPrompt }];
39
92
  /* c8 ignore next 4 - head branch: options?.head short-circuit not reached from current call sites */
40
93
  if (options === null || options === void 0 ? void 0 : options.head) {
41
94
  for (const msg of options.head) {
@@ -49,6 +102,17 @@ export function buildMessages(systemPrompt, userContent, options) {
49
102
  messages.push({ role: msg.role, content: msg.content });
50
103
  }
51
104
  }
105
+ // OpenAI / xAI Responses continuation items (function_call /
106
+ // function_call_output) are appended verbatim — their field set differs per
107
+ // item type, so the whole object is preserved rather than projected.
108
+ if (options === null || options === void 0 ? void 0 : options.rawTail) {
109
+ for (const item of options.rawTail) {
110
+ const converted = openAiRawTailItemConverter.convert(item);
111
+ if (converted.isSuccess()) {
112
+ messages.push(converted.value);
113
+ }
114
+ }
115
+ }
52
116
  return messages;
53
117
  }
54
118
  /**
@@ -146,6 +210,15 @@ export function buildAnthropicMessages(prompt, options) {
146
210
  }
147
211
  }
148
212
  }
213
+ /* c8 ignore next 7 - options?.rawTail optional-chain short-circuit (options=undefined) not reached in unit tests */
214
+ if (options === null || options === void 0 ? void 0 : options.rawTail) {
215
+ for (const msg of options.rawTail) {
216
+ const converted = rawTailMessageConverter.convert(msg);
217
+ if (converted.isSuccess()) {
218
+ messages.push(converted.value);
219
+ }
220
+ }
221
+ }
149
222
  return messages;
150
223
  }
151
224
  /**
@@ -181,6 +254,16 @@ export function buildGeminiContents(prompt, options) {
181
254
  }
182
255
  }
183
256
  }
257
+ // Gemini continuation turns (model `functionCall` parts + user
258
+ // `functionResponse` parts) are projected to `{ role, parts }`.
259
+ if (options === null || options === void 0 ? void 0 : options.rawTail) {
260
+ for (const item of options.rawTail) {
261
+ const converted = geminiRawTailMessageConverter.convert(item);
262
+ if (converted.isSuccess()) {
263
+ contents.push(converted.value);
264
+ }
265
+ }
266
+ }
184
267
  return contents;
185
268
  }
186
269
  //# sourceMappingURL=chatRequestBuilders.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"chatRequestBuilders.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/chatRequestBuilders.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 * Per-format chat request shape builders. Shared between the synchronous\n * (`apiClient.ts`) and streaming (`streamingClient.ts`) paths so the wire\n * shapes stay consistent.\n *\n * @packageDocumentation\n */\n\nimport { AiPrompt, type IAiImageAttachment, type IChatMessage, toDataUrl } from './model';\n\n/**\n * Optional head/tail messages to weave around the prompt's user message.\n *\n * @internal\n */\nexport interface IBuildMessagesOptions {\n /**\n * Messages inserted between the system prompt and the prompt's user\n * message (e.g. prior conversation history for multi-turn chat).\n */\n readonly head?: ReadonlyArray<IChatMessage>;\n /**\n * Messages appended after the prompt's user message (e.g. assistant\n * + correction turns for the JSON-validation retry loop).\n */\n readonly tail?: ReadonlyArray<IChatMessage>;\n}\n\n/**\n * Builds the messages array from prompt + optional head/tail messages.\n * The caller supplies the user content (string for text-only, parts array\n * for vision prompts) since the parts shape differs by format.\n *\n * @internal\n */\nexport function buildMessages(\n systemPrompt: string,\n userContent: string | unknown[],\n options?: IBuildMessagesOptions\n): Array<{ role: string; content: string | unknown[] }> {\n const messages: Array<{ role: string; content: string | unknown[] }> = [\n { role: 'system', content: systemPrompt }\n ];\n /* c8 ignore next 4 - head branch: options?.head short-circuit not reached from current call sites */\n if (options?.head) {\n for (const msg of options.head) {\n messages.push({ role: msg.role, content: msg.content });\n }\n }\n messages.push({ role: 'user', content: userContent });\n /* c8 ignore next 4 - tail branch: options?.tail short-circuit not reached from current call sites */\n if (options?.tail) {\n for (const msg of options.tail) {\n messages.push({ role: msg.role, content: msg.content });\n }\n }\n return messages;\n}\n\n/**\n * Builds the user content for OpenAI Chat Completions when attachments are\n * present. Returns a string when there are no attachments.\n *\n * @internal\n */\nexport function buildOpenAiChatUserContent(prompt: AiPrompt): string | unknown[] {\n if (prompt.attachments.length === 0) {\n return prompt.user;\n }\n return [\n { type: 'text', text: prompt.user },\n ...prompt.attachments.map((att: IAiImageAttachment) => ({\n type: 'image_url',\n image_url: {\n url: toDataUrl(att),\n ...(att.detail !== undefined ? { detail: att.detail } : {})\n }\n }))\n ];\n}\n\n/**\n * Builds the user content for OpenAI / xAI Responses API when attachments\n * are present. Responses API uses `input_text` / `input_image` part types,\n * distinct from Chat Completions' `text` / `image_url`.\n *\n * @internal\n */\nexport function buildOpenAiResponsesUserContent(prompt: AiPrompt): string | unknown[] {\n if (prompt.attachments.length === 0) {\n return prompt.user;\n }\n return [\n { type: 'input_text', text: prompt.user },\n ...prompt.attachments.map((att: IAiImageAttachment) => ({\n type: 'input_image',\n image_url: toDataUrl(att),\n ...(att.detail !== undefined ? { detail: att.detail } : {})\n }))\n ];\n}\n\n/**\n * Builds the user-message content for Anthropic when attachments are present.\n *\n * @internal\n */\nexport function buildAnthropicUserContent(prompt: AiPrompt): string | unknown[] {\n if (prompt.attachments.length === 0) {\n return prompt.user;\n }\n return [\n { type: 'text', text: prompt.user },\n ...prompt.attachments.map((att: IAiImageAttachment) => ({\n type: 'image',\n source: {\n type: 'base64',\n media_type: att.mimeType,\n data: att.base64\n }\n }))\n ];\n}\n\n/**\n * Builds the Gemini `parts` array for the user turn, including any image\n * attachments as `inlineData` parts.\n *\n * @internal\n */\nexport function buildGeminiUserParts(prompt: AiPrompt): Array<Record<string, unknown>> {\n const parts: Array<Record<string, unknown>> = [{ text: prompt.user }];\n for (const att of prompt.attachments) {\n parts.push({ inlineData: { mimeType: att.mimeType, data: att.base64 } });\n }\n return parts;\n}\n\n/**\n * Builds the Anthropic messages array, weaving any `head` messages between\n * implicit system + the prompt's user message and appending `tail` messages\n * after. System messages are filtered out (Anthropic uses a top-level system\n * field).\n *\n * @internal\n */\nexport function buildAnthropicMessages(\n prompt: AiPrompt,\n options?: IBuildMessagesOptions\n): Array<{ role: string; content: string | unknown[] }> {\n const messages: Array<{ role: string; content: string | unknown[] }> = [];\n /* c8 ignore next 5 - head branch: options?.head short-circuit not reached from current call sites */\n if (options?.head) {\n for (const msg of options.head) {\n if (msg.role !== 'system') {\n messages.push({ role: msg.role, content: msg.content });\n }\n }\n }\n messages.push({ role: 'user', content: buildAnthropicUserContent(prompt) });\n /* c8 ignore next 5 - tail branch: options?.tail short-circuit not reached from current call sites */\n if (options?.tail) {\n for (const msg of options.tail) {\n if (msg.role !== 'system') {\n messages.push({ role: msg.role, content: msg.content });\n }\n }\n }\n return messages;\n}\n\n/**\n * Builds the Gemini `contents` array, weaving any `head` messages before the\n * prompt's user parts and appending `tail` messages after. System messages\n * are filtered out (Gemini uses a top-level systemInstruction field) and\n * assistant roles are mapped to Gemini's `model` role.\n *\n * @internal\n */\nexport function buildGeminiContents(\n prompt: AiPrompt,\n options?: IBuildMessagesOptions\n): Array<{ role: string; parts: Array<Record<string, unknown>> }> {\n const contents: Array<{ role: string; parts: Array<Record<string, unknown>> }> = [];\n /* c8 ignore next 7 - head branch: options?.head short-circuit not reached from current call sites */\n if (options?.head) {\n for (const msg of options.head) {\n if (msg.role !== 'system') {\n contents.push({\n role: msg.role === 'assistant' ? 'model' : msg.role,\n parts: [{ text: msg.content }]\n });\n }\n }\n }\n contents.push({ role: 'user', parts: buildGeminiUserParts(prompt) });\n /* c8 ignore next 7 - tail branch: options?.tail short-circuit not reached from current call sites */\n if (options?.tail) {\n for (const msg of options.tail) {\n if (msg.role !== 'system') {\n contents.push({\n role: msg.role === 'assistant' ? 'model' : msg.role,\n parts: [{ text: msg.content }]\n });\n }\n }\n }\n return contents;\n}\n"]}
1
+ {"version":3,"file":"chatRequestBuilders.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/chatRequestBuilders.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 * Per-format chat request shape builders. Shared between the synchronous\n * (`apiClient.ts`) and streaming (`streamingClient.ts`) paths so the wire\n * shapes stay consistent.\n *\n * @packageDocumentation\n */\n\nimport { isJsonObject, type JsonObject } from '@fgv/ts-json-base';\nimport { type Converter, Converters } from '@fgv/ts-utils';\n\nimport { AiPrompt, type IAiImageAttachment, type IChatMessage, toDataUrl } from './model';\n\n/**\n * Converter for a rawTail message entry. Narrows a `JsonObject` to\n * `{ role: string; content: string | unknown[] }` at runtime using the\n * Converter pattern. Entries that fail validation are silently skipped — the\n * surrounding function is infallible, and a malformed continuation message is\n * better omitted than transmitted verbatim.\n * @internal\n */\nconst rawTailMessageConverter: Converter<{ role: 'user' | 'assistant'; content: string | unknown[] }> =\n Converters.object<{ role: 'user' | 'assistant'; content: string | unknown[] }>(\n {\n role: Converters.enumeratedValue<'user' | 'assistant'>(['user', 'assistant']),\n content: Converters.oneOf<string | unknown[]>([\n Converters.string,\n Converters.isA('array', (v): v is unknown[] => Array.isArray(v))\n ])\n },\n { strict: false }\n );\n\n/**\n * Converter for an OpenAI / xAI Responses API `rawTail` item. These are\n * provider-native input items (`function_call`, `function_call_output`) whose\n * fields differ per item type, so — unlike the Anthropic `{ role, content }`\n * projection — the whole object is preserved verbatim.\n *\n * The static input is already typed `JsonObject`, so the `isJsonObject` guard\n * is a runtime backstop, not a compile-time narrowing: continuation messages\n * originate from a prior turn's `IAiClientToolContinuation.messages` and a\n * consumer may persist and reload them through untyped JSON before passing them\n * back. The guard preserves the same \"a malformed continuation message is\n * better omitted than transmitted verbatim\" posture as the Anthropic path —\n * non-object entries fail conversion and are skipped by the caller.\n * @internal\n */\nconst openAiRawTailItemConverter: Converter<JsonObject> = Converters.isA<JsonObject>(\n 'JsonObject',\n (v): v is JsonObject => isJsonObject(v)\n);\n\n/**\n * Converter for a Gemini `rawTail` item. Gemini continuation messages are\n * `{ role, parts }` turns (a model turn with `functionCall` parts followed by a\n * user turn with `functionResponse` parts). Narrows a `JsonObject` to\n * `{ role: 'user' | 'model'; parts: Array<Record<string, unknown>> }`; entries\n * that fail validation are skipped by the caller.\n * @internal\n */\nconst geminiRawTailMessageConverter: Converter<{\n role: 'user' | 'model';\n parts: unknown[];\n}> = Converters.object<{ role: 'user' | 'model'; parts: unknown[] }>(\n {\n role: Converters.enumeratedValue<'user' | 'model'>(['user', 'model']),\n // `parts` is preserved verbatim and serialized into the request body, so the\n // element shape is not narrowed here — `Array.isArray` soundly guarantees\n // `unknown[]` (narrowing to `Record<string, unknown>[]` would be an unchecked cast).\n parts: Converters.isA('array', (v): v is unknown[] => Array.isArray(v))\n },\n { strict: false }\n);\n\n/**\n * Optional head/tail messages to weave around the prompt's user message.\n *\n * @internal\n */\nexport interface IBuildMessagesOptions {\n /**\n * Messages inserted between the system prompt and the prompt's user\n * message (e.g. prior conversation history for multi-turn chat).\n */\n readonly head?: ReadonlyArray<IChatMessage>;\n /**\n * Messages appended after the prompt's user message (e.g. assistant\n * + correction turns for the JSON-validation retry loop).\n */\n readonly tail?: ReadonlyArray<IChatMessage>;\n /**\n * Raw JSON objects appended after the prompt's user message. Used to\n * inject provider-specific continuation messages (e.g. Anthropic assistant\n * turns with thinking blocks, OpenAI Responses `function_call` /\n * `function_call_output` items, Gemini `functionCall` / `functionResponse`\n * turns) that cannot be expressed as plain {@link IChatMessage} objects.\n *\n * Each builder applies its own provider-specific shape guard:\n * - {@link buildAnthropicMessages} projects each entry to `{ role, content }`.\n * - {@link buildMessages} (OpenAI / xAI Responses) preserves each item\n * verbatim (item fields differ per `type`), guarding only that it is a\n * JSON object.\n * - {@link buildGeminiContents} projects each entry to `{ role, parts }`.\n *\n * Entries that fail their builder's shape check are silently skipped (the\n * caller is responsible for supplying well-formed continuation messages).\n * Takes precedence over (and is appended after) `tail`.\n */\n readonly rawTail?: ReadonlyArray<JsonObject>;\n}\n\n/**\n * Builds the messages array from prompt + optional head/tail messages.\n * The caller supplies the user content (string for text-only, parts array\n * for vision prompts) since the parts shape differs by format.\n *\n * `rawTail` items (OpenAI / xAI Responses `function_call` /\n * `function_call_output` continuation items) are appended verbatim after the\n * user message — their fields differ per item `type`, so they are preserved\n * rather than projected. The return type is `Array<Record<string, unknown>>`\n * to accommodate both `{ role, content }` messages and these heterogeneous\n * input items.\n *\n * @internal\n */\nexport function buildMessages(\n systemPrompt: string,\n userContent: string | unknown[],\n options?: IBuildMessagesOptions\n): Array<Record<string, unknown>> {\n const messages: Array<Record<string, unknown>> = [{ role: 'system', content: systemPrompt }];\n /* c8 ignore next 4 - head branch: options?.head short-circuit not reached from current call sites */\n if (options?.head) {\n for (const msg of options.head) {\n messages.push({ role: msg.role, content: msg.content });\n }\n }\n messages.push({ role: 'user', content: userContent });\n /* c8 ignore next 4 - tail branch: options?.tail short-circuit not reached from current call sites */\n if (options?.tail) {\n for (const msg of options.tail) {\n messages.push({ role: msg.role, content: msg.content });\n }\n }\n // OpenAI / xAI Responses continuation items (function_call /\n // function_call_output) are appended verbatim — their field set differs per\n // item type, so the whole object is preserved rather than projected.\n if (options?.rawTail) {\n for (const item of options.rawTail) {\n const converted = openAiRawTailItemConverter.convert(item);\n if (converted.isSuccess()) {\n messages.push(converted.value);\n }\n }\n }\n return messages;\n}\n\n/**\n * Builds the user content for OpenAI Chat Completions when attachments are\n * present. Returns a string when there are no attachments.\n *\n * @internal\n */\nexport function buildOpenAiChatUserContent(prompt: AiPrompt): string | unknown[] {\n if (prompt.attachments.length === 0) {\n return prompt.user;\n }\n return [\n { type: 'text', text: prompt.user },\n ...prompt.attachments.map((att: IAiImageAttachment) => ({\n type: 'image_url',\n image_url: {\n url: toDataUrl(att),\n ...(att.detail !== undefined ? { detail: att.detail } : {})\n }\n }))\n ];\n}\n\n/**\n * Builds the user content for OpenAI / xAI Responses API when attachments\n * are present. Responses API uses `input_text` / `input_image` part types,\n * distinct from Chat Completions' `text` / `image_url`.\n *\n * @internal\n */\nexport function buildOpenAiResponsesUserContent(prompt: AiPrompt): string | unknown[] {\n if (prompt.attachments.length === 0) {\n return prompt.user;\n }\n return [\n { type: 'input_text', text: prompt.user },\n ...prompt.attachments.map((att: IAiImageAttachment) => ({\n type: 'input_image',\n image_url: toDataUrl(att),\n ...(att.detail !== undefined ? { detail: att.detail } : {})\n }))\n ];\n}\n\n/**\n * Builds the user-message content for Anthropic when attachments are present.\n *\n * @internal\n */\nexport function buildAnthropicUserContent(prompt: AiPrompt): string | unknown[] {\n if (prompt.attachments.length === 0) {\n return prompt.user;\n }\n return [\n { type: 'text', text: prompt.user },\n ...prompt.attachments.map((att: IAiImageAttachment) => ({\n type: 'image',\n source: {\n type: 'base64',\n media_type: att.mimeType,\n data: att.base64\n }\n }))\n ];\n}\n\n/**\n * Builds the Gemini `parts` array for the user turn, including any image\n * attachments as `inlineData` parts.\n *\n * @internal\n */\nexport function buildGeminiUserParts(prompt: AiPrompt): Array<Record<string, unknown>> {\n const parts: Array<Record<string, unknown>> = [{ text: prompt.user }];\n for (const att of prompt.attachments) {\n parts.push({ inlineData: { mimeType: att.mimeType, data: att.base64 } });\n }\n return parts;\n}\n\n/**\n * Builds the Anthropic messages array, weaving any `head` messages between\n * implicit system + the prompt's user message and appending `tail` messages\n * after. System messages are filtered out (Anthropic uses a top-level system\n * field).\n *\n * @internal\n */\nexport function buildAnthropicMessages(\n prompt: AiPrompt,\n options?: IBuildMessagesOptions\n): Array<{ role: string; content: string | unknown[] }> {\n const messages: Array<{ role: string; content: string | unknown[] }> = [];\n /* c8 ignore next 5 - head branch: options?.head short-circuit not reached from current call sites */\n if (options?.head) {\n for (const msg of options.head) {\n if (msg.role !== 'system') {\n messages.push({ role: msg.role, content: msg.content });\n }\n }\n }\n messages.push({ role: 'user', content: buildAnthropicUserContent(prompt) });\n /* c8 ignore next 5 - tail branch: options?.tail short-circuit not reached from current call sites */\n if (options?.tail) {\n for (const msg of options.tail) {\n if (msg.role !== 'system') {\n messages.push({ role: msg.role, content: msg.content });\n }\n }\n }\n /* c8 ignore next 7 - options?.rawTail optional-chain short-circuit (options=undefined) not reached in unit tests */\n if (options?.rawTail) {\n for (const msg of options.rawTail) {\n const converted = rawTailMessageConverter.convert(msg);\n if (converted.isSuccess()) {\n messages.push(converted.value);\n }\n }\n }\n return messages;\n}\n\n/**\n * Builds the Gemini `contents` array, weaving any `head` messages before the\n * prompt's user parts and appending `tail` messages after. System messages\n * are filtered out (Gemini uses a top-level systemInstruction field) and\n * assistant roles are mapped to Gemini's `model` role.\n *\n * @internal\n */\nexport function buildGeminiContents(\n prompt: AiPrompt,\n options?: IBuildMessagesOptions\n): Array<{ role: string; parts: unknown[] }> {\n const contents: Array<{ role: string; parts: unknown[] }> = [];\n /* c8 ignore next 7 - head branch: options?.head short-circuit not reached from current call sites */\n if (options?.head) {\n for (const msg of options.head) {\n if (msg.role !== 'system') {\n contents.push({\n role: msg.role === 'assistant' ? 'model' : msg.role,\n parts: [{ text: msg.content }]\n });\n }\n }\n }\n contents.push({ role: 'user', parts: buildGeminiUserParts(prompt) });\n /* c8 ignore next 7 - tail branch: options?.tail short-circuit not reached from current call sites */\n if (options?.tail) {\n for (const msg of options.tail) {\n if (msg.role !== 'system') {\n contents.push({\n role: msg.role === 'assistant' ? 'model' : msg.role,\n parts: [{ text: msg.content }]\n });\n }\n }\n }\n // Gemini continuation turns (model `functionCall` parts + user\n // `functionResponse` parts) are projected to `{ role, parts }`.\n if (options?.rawTail) {\n for (const item of options.rawTail) {\n const converted = geminiRawTailMessageConverter.convert(item);\n if (converted.isSuccess()) {\n contents.push(converted.value);\n }\n }\n }\n return contents;\n}\n"]}
@@ -21,7 +21,7 @@
21
21
  * Converters for AI assist settings types.
22
22
  * @packageDocumentation
23
23
  */
24
- import { Converters } from '@fgv/ts-utils';
24
+ import { Converters, Validators } from '@fgv/ts-utils';
25
25
  import { allModelSpecKeys } from './model';
26
26
  import { allProviderIds } from './registry';
27
27
  // ============================================================================
@@ -63,6 +63,36 @@ export const aiWebSearchToolConfig = Converters.strictObject({
63
63
  export const aiServerToolConfig = Converters.discriminatedObject('type', {
64
64
  web_search: aiWebSearchToolConfig
65
65
  });
66
+ // ============================================================================
67
+ // Client-Defined Tool Converters
68
+ // ============================================================================
69
+ /**
70
+ * Validator for the `parametersSchema` field of a client tool config.
71
+ * Checks that the value is a non-null object exposing both `validate` and `toJson`
72
+ * as callable functions — the runtime presence check for {@link JsonSchema.ISchemaValidator}.
73
+ * Does not inspect the inner JSON Schema structure.
74
+ * @internal
75
+ */
76
+ const parametersSchemaValidator = Validators.isA('ISchemaValidator (must expose .validate() and .toJson())', (v) => v !== null &&
77
+ v !== undefined &&
78
+ typeof v === 'object' &&
79
+ typeof v.validate === 'function' &&
80
+ typeof v.toJson === 'function');
81
+ /**
82
+ * Converter for {@link AiAssist.IAiClientToolConfig}. Validates the wrapper shape: `type`,
83
+ * `name`, `description`, and the presence of a usable `parametersSchema`.
84
+ * Does not inspect the inner JSON Schema structure — `JsonSchema.object(...)` already
85
+ * guarantees the schema is valid.
86
+ * @public
87
+ */
88
+ export const aiClientToolConfig = Converters.object({
89
+ type: Converters.enumeratedValue(['client_tool']),
90
+ name: Converters.string.withConstraint((s) => s.length > 0, {
91
+ description: 'name must be a non-empty string'
92
+ }),
93
+ description: Converters.string,
94
+ parametersSchema: parametersSchemaValidator
95
+ });
66
96
  /**
67
97
  * Converter for {@link IAiToolEnablement}.
68
98
  * @public
@@ -1 +1 @@
1
- {"version":3,"file":"converters.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/converters.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 * Converters for AI assist settings types.\n * @packageDocumentation\n */\n\nimport { type Converter, Converters } from '@fgv/ts-utils';\n\nimport {\n type AiProviderId,\n type AiServerToolConfig,\n type AiServerToolType,\n type IAiAssistProviderConfig,\n type IAiAssistSettings,\n type IAiToolEnablement,\n type IAiWebSearchToolConfig,\n type ModelSpec,\n type ModelSpecKey,\n allModelSpecKeys\n} from './model';\nimport { allProviderIds } from './registry';\n\n// ============================================================================\n// Provider ID\n// ============================================================================\n\n/**\n * Converter for {@link AiProviderId}.\n * @public\n */\nexport const aiProviderId: Converter<AiProviderId> = Converters.enumeratedValue<AiProviderId>(allProviderIds);\n\n// ============================================================================\n// Server-Side Tool Converters\n// ============================================================================\n\n/**\n * All known server-side tool type values.\n * @internal\n */\nconst allServerToolTypes: ReadonlyArray<AiServerToolType> = ['web_search'];\n\n/**\n * Converter for {@link AiServerToolType}.\n * @public\n */\nexport const aiServerToolType: Converter<AiServerToolType> =\n Converters.enumeratedValue<AiServerToolType>(allServerToolTypes);\n\n/**\n * Converter for {@link IAiWebSearchToolConfig}.\n * @public\n */\nexport const aiWebSearchToolConfig: Converter<IAiWebSearchToolConfig> =\n Converters.strictObject<IAiWebSearchToolConfig>({\n type: Converters.enumeratedValue<'web_search'>(['web_search']),\n allowedDomains: Converters.arrayOf(Converters.string).optional(),\n blockedDomains: Converters.arrayOf(Converters.string).optional(),\n maxUses: Converters.number.optional(),\n enableImageUnderstanding: Converters.boolean.optional()\n });\n\n/**\n * Converter for {@link AiServerToolConfig} (discriminated union on `type`).\n * @public\n */\nexport const aiServerToolConfig: Converter<AiServerToolConfig> =\n Converters.discriminatedObject<AiServerToolConfig>('type', {\n web_search: aiWebSearchToolConfig\n });\n\n/**\n * Converter for {@link IAiToolEnablement}.\n * @public\n */\nexport const aiToolEnablement: Converter<IAiToolEnablement> = Converters.strictObject<IAiToolEnablement>({\n type: aiServerToolType,\n enabled: Converters.boolean,\n config: aiServerToolConfig.optional()\n});\n\n// ============================================================================\n// Model Specification\n// ============================================================================\n\n/**\n * Converter for {@link ModelSpecKey}.\n * @public\n */\nexport const modelSpecKey: Converter<ModelSpecKey> =\n Converters.enumeratedValue<ModelSpecKey>(allModelSpecKeys);\n\n/**\n * Recursive converter for {@link ModelSpec}.\n * Accepts a string or an object whose values are themselves ModelSpec values,\n * with keys constrained to known {@link ModelSpecKey} values.\n * Uses the `self` parameter from `Converters.generic` for recursion.\n * @public\n */\nexport const modelSpec: Converter<ModelSpec> = Converters.generic<ModelSpec>(\n (from: unknown, self: Converter<ModelSpec>) => {\n return Converters.oneOf<ModelSpec>([\n Converters.string,\n Converters.recordOf(self, { keyConverter: modelSpecKey })\n ])\n .withFormattedError(() => 'expected model spec (string or object with keys: base, tools, image)')\n .convert(from);\n }\n);\n\n// ============================================================================\n// Provider Config & Settings\n// ============================================================================\n\n/**\n * Converter for {@link IAiAssistProviderConfig}.\n * @public\n */\nexport const aiAssistProviderConfig: Converter<IAiAssistProviderConfig> =\n Converters.strictObject<IAiAssistProviderConfig>({\n provider: aiProviderId,\n secretName: Converters.string.optional(),\n model: modelSpec.optional(),\n tools: Converters.arrayOf(aiToolEnablement).optional(),\n endpoint: Converters.string.optional()\n });\n\n/**\n * Converter for {@link IAiAssistSettings}.\n * @public\n */\nexport const aiAssistSettings: Converter<IAiAssistSettings> = Converters.strictObject<IAiAssistSettings>({\n providers: Converters.arrayOf(aiAssistProviderConfig),\n defaultProvider: aiProviderId.optional(),\n proxyUrl: Converters.string.optional(),\n proxyAllProviders: Converters.boolean.optional()\n});\n"]}
1
+ {"version":3,"file":"converters.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/converters.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 * Converters for AI assist settings types.\n * @packageDocumentation\n */\n\nimport { type Converter, Converters, type Validator, Validators } from '@fgv/ts-utils';\nimport { type JsonSchema } from '@fgv/ts-json-base';\n\nimport {\n type AiProviderId,\n type AiServerToolConfig,\n type AiServerToolType,\n type IAiAssistProviderConfig,\n type IAiAssistSettings,\n type IAiClientToolConfig,\n type IAiToolEnablement,\n type IAiWebSearchToolConfig,\n type ModelSpec,\n type ModelSpecKey,\n allModelSpecKeys\n} from './model';\nimport { allProviderIds } from './registry';\n\n// ============================================================================\n// Provider ID\n// ============================================================================\n\n/**\n * Converter for {@link AiProviderId}.\n * @public\n */\nexport const aiProviderId: Converter<AiProviderId> = Converters.enumeratedValue<AiProviderId>(allProviderIds);\n\n// ============================================================================\n// Server-Side Tool Converters\n// ============================================================================\n\n/**\n * All known server-side tool type values.\n * @internal\n */\nconst allServerToolTypes: ReadonlyArray<AiServerToolType> = ['web_search'];\n\n/**\n * Converter for {@link AiServerToolType}.\n * @public\n */\nexport const aiServerToolType: Converter<AiServerToolType> =\n Converters.enumeratedValue<AiServerToolType>(allServerToolTypes);\n\n/**\n * Converter for {@link IAiWebSearchToolConfig}.\n * @public\n */\nexport const aiWebSearchToolConfig: Converter<IAiWebSearchToolConfig> =\n Converters.strictObject<IAiWebSearchToolConfig>({\n type: Converters.enumeratedValue<'web_search'>(['web_search']),\n allowedDomains: Converters.arrayOf(Converters.string).optional(),\n blockedDomains: Converters.arrayOf(Converters.string).optional(),\n maxUses: Converters.number.optional(),\n enableImageUnderstanding: Converters.boolean.optional()\n });\n\n/**\n * Converter for {@link AiServerToolConfig} (discriminated union on `type`).\n * @public\n */\nexport const aiServerToolConfig: Converter<AiServerToolConfig> =\n Converters.discriminatedObject<AiServerToolConfig>('type', {\n web_search: aiWebSearchToolConfig\n });\n\n// ============================================================================\n// Client-Defined Tool Converters\n// ============================================================================\n\n/**\n * Validator for the `parametersSchema` field of a client tool config.\n * Checks that the value is a non-null object exposing both `validate` and `toJson`\n * as callable functions — the runtime presence check for {@link JsonSchema.ISchemaValidator}.\n * Does not inspect the inner JSON Schema structure.\n * @internal\n */\nconst parametersSchemaValidator: Validator<JsonSchema.ISchemaValidator<unknown>> = Validators.isA<\n JsonSchema.ISchemaValidator<unknown>\n>(\n 'ISchemaValidator (must expose .validate() and .toJson())',\n (v): v is JsonSchema.ISchemaValidator<unknown> =>\n v !== null &&\n v !== undefined &&\n typeof v === 'object' &&\n typeof (v as Record<string, unknown>).validate === 'function' &&\n typeof (v as Record<string, unknown>).toJson === 'function'\n);\n\n/**\n * Converter for {@link AiAssist.IAiClientToolConfig}. Validates the wrapper shape: `type`,\n * `name`, `description`, and the presence of a usable `parametersSchema`.\n * Does not inspect the inner JSON Schema structure — `JsonSchema.object(...)` already\n * guarantees the schema is valid.\n * @public\n */\nexport const aiClientToolConfig: Converter<IAiClientToolConfig> = Converters.object<IAiClientToolConfig>({\n type: Converters.enumeratedValue<'client_tool'>(['client_tool']),\n name: Converters.string.withConstraint((s) => s.length > 0, {\n description: 'name must be a non-empty string'\n }),\n description: Converters.string,\n parametersSchema: parametersSchemaValidator\n});\n\n/**\n * Converter for {@link IAiToolEnablement}.\n * @public\n */\nexport const aiToolEnablement: Converter<IAiToolEnablement> = Converters.strictObject<IAiToolEnablement>({\n type: aiServerToolType,\n enabled: Converters.boolean,\n config: aiServerToolConfig.optional()\n});\n\n// ============================================================================\n// Model Specification\n// ============================================================================\n\n/**\n * Converter for {@link ModelSpecKey}.\n * @public\n */\nexport const modelSpecKey: Converter<ModelSpecKey> =\n Converters.enumeratedValue<ModelSpecKey>(allModelSpecKeys);\n\n/**\n * Recursive converter for {@link ModelSpec}.\n * Accepts a string or an object whose values are themselves ModelSpec values,\n * with keys constrained to known {@link ModelSpecKey} values.\n * Uses the `self` parameter from `Converters.generic` for recursion.\n * @public\n */\nexport const modelSpec: Converter<ModelSpec> = Converters.generic<ModelSpec>(\n (from: unknown, self: Converter<ModelSpec>) => {\n return Converters.oneOf<ModelSpec>([\n Converters.string,\n Converters.recordOf(self, { keyConverter: modelSpecKey })\n ])\n .withFormattedError(() => 'expected model spec (string or object with keys: base, tools, image)')\n .convert(from);\n }\n);\n\n// ============================================================================\n// Provider Config & Settings\n// ============================================================================\n\n/**\n * Converter for {@link IAiAssistProviderConfig}.\n * @public\n */\nexport const aiAssistProviderConfig: Converter<IAiAssistProviderConfig> =\n Converters.strictObject<IAiAssistProviderConfig>({\n provider: aiProviderId,\n secretName: Converters.string.optional(),\n model: modelSpec.optional(),\n tools: Converters.arrayOf(aiToolEnablement).optional(),\n endpoint: Converters.string.optional()\n });\n\n/**\n * Converter for {@link IAiAssistSettings}.\n * @public\n */\nexport const aiAssistSettings: Converter<IAiAssistSettings> = Converters.strictObject<IAiAssistSettings>({\n providers: Converters.arrayOf(aiAssistProviderConfig),\n defaultProvider: aiProviderId.optional(),\n proxyUrl: Converters.string.optional(),\n proxyAllProviders: Converters.boolean.optional()\n});\n"]}
@@ -6,9 +6,10 @@ export { AiPrompt, DEFAULT_AI_ASSIST, allModelSpecKeys, MODEL_SPEC_BASE_KEY, res
6
6
  export { resolveImageOptions, validateResolvedOptions } from './imageOptionsResolver';
7
7
  export { allProviderIds, getProviderDescriptors, getProviderDescriptor, resolveImageCapability, supportsImageGeneration, DEFAULT_MODEL_CAPABILITY_CONFIG } from './registry';
8
8
  export { callProviderCompletion, callProxiedCompletion, callProviderImageGeneration, callProxiedImageGeneration, callProviderListModels, callProxiedListModels } from './apiClient';
9
- export { callProviderCompletionStream, callProxiedCompletionStream } from './streamingClient';
10
- export { aiProviderId, aiServerToolType, aiWebSearchToolConfig, aiServerToolConfig, aiToolEnablement, aiAssistProviderConfig, aiAssistSettings, modelSpecKey, modelSpec } from './converters';
9
+ export { callProviderCompletionStream, callProxiedCompletionStream, executeClientToolTurn } from './streamingClient';
10
+ export { aiProviderId, aiServerToolType, aiWebSearchToolConfig, aiServerToolConfig, aiClientToolConfig, aiToolEnablement, aiAssistProviderConfig, aiAssistSettings, modelSpecKey, modelSpec } from './converters';
11
11
  export { resolveEffectiveTools } from './toolFormats';
12
12
  export { extractJsonText, fencedStringifiedJson } from './jsonResponse';
13
13
  export { generateJsonCompletion, SMART_JSON_PROMPT_HINT } from './jsonCompletion';
14
+ export { anthropicEffortToBudgetTokens } from './thinkingOptionsResolver';
14
15
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,QAAQ,EAeR,iBAAiB,EA2CjB,gBAAgB,EAChB,mBAAmB,EACnB,YAAY,EACZ,SAAS,EAiBV,MAAM,SAAS,CAAC;AAEjB,OAAO,EAEL,mBAAmB,EACnB,uBAAuB,EACxB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,cAAc,EACd,sBAAsB,EACtB,qBAAqB,EACrB,sBAAsB,EACtB,uBAAuB,EACvB,+BAA+B,EAChC,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,2BAA2B,EAC3B,0BAA0B,EAC1B,sBAAsB,EACtB,qBAAqB,EAItB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,4BAA4B,EAC5B,2BAA2B,EAE5B,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,qBAAqB,EACrB,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,gBAAgB,EAChB,YAAY,EACZ,SAAS,EACV,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAEtD,OAAO,EACL,eAAe,EACf,qBAAqB,EAItB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EAIvB,MAAM,kBAAkB,CAAC","sourcesContent":["/**\n * AI assist packlet - provider registry, prompt class, settings, and API client.\n * @packageDocumentation\n */\n\nexport {\n AiPrompt,\n type AiModelCapability,\n type AiProviderId,\n type AiServerToolType,\n type AiServerToolConfig,\n type IAiWebSearchToolConfig,\n type IAiToolEnablement,\n type IAiCompletionResponse,\n type IChatMessage,\n type AiApiFormat,\n type AiImageApiFormat,\n type IAiImageModelCapability,\n type IAiProviderDescriptor,\n type IAiAssistProviderConfig,\n type IAiAssistSettings,\n DEFAULT_AI_ASSIST,\n type IAiAssistKeyStore,\n type IAiImageAttachment,\n type IAiImageData,\n type AiImageSize,\n type AiImageQuality,\n type DallE2Size,\n type DallE3Size,\n type GptImageSize,\n type DallE3Quality,\n type GptImageQuality,\n type DallEModelNames,\n type GptImageModelNames,\n type GrokImagineModelNames,\n type Imagen4ModelNames,\n type GeminiFlashImageModelNames,\n type IDallEImageGenerationConfig,\n type IGptImageGenerationConfig,\n type IGrokImagineImageGenerationConfig,\n type IImagen4GenerationConfig,\n type IGeminiFlashImageGenerationConfig,\n type IDallEModelOptions,\n type IGptImageModelOptions,\n type IGrokImagineModelOptions,\n type IImagen4ModelOptions,\n type IGeminiFlashImageModelOptions,\n type IOtherModelOptions,\n type IModelFamilyConfig,\n type IAiImageGenerationOptions,\n type IAiImageGenerationParams,\n type IAiGeneratedImage,\n type IAiImageGenerationResponse,\n type IAiModelCapabilityRule,\n type IAiModelCapabilityConfig,\n type IAiModelInfo,\n type IAiStreamEvent,\n type IAiStreamTextDelta,\n type IAiStreamToolEvent,\n type IAiStreamDone,\n type IAiStreamError,\n type ModelSpec,\n type ModelSpecKey,\n type IModelSpecMap,\n allModelSpecKeys,\n MODEL_SPEC_BASE_KEY,\n resolveModel,\n toDataUrl,\n type AiThinkingMode,\n type IThinkingConfig,\n type IThinkingProviderConfig,\n type IAnthropicThinkingOptions,\n type IOpenAiThinkingOptions,\n type IGeminiThinkingOptions,\n type IXAiThinkingOptions,\n type IOtherThinkingOptions,\n type IAnthropicThinkingConfig,\n type IOpenAiThinkingConfig,\n type IGeminiThinkingConfig,\n type IXAiThinkingConfig,\n type AnthropicThinkingModelNames,\n type OpenAiThinkingModelNames,\n type GeminiThinkingModelNames,\n type XAiThinkingModelNames\n} from './model';\n\nexport {\n type IResolvedImageOptions,\n resolveImageOptions,\n validateResolvedOptions\n} from './imageOptionsResolver';\n\nexport {\n allProviderIds,\n getProviderDescriptors,\n getProviderDescriptor,\n resolveImageCapability,\n supportsImageGeneration,\n DEFAULT_MODEL_CAPABILITY_CONFIG\n} from './registry';\n\nexport {\n callProviderCompletion,\n callProxiedCompletion,\n callProviderImageGeneration,\n callProxiedImageGeneration,\n callProviderListModels,\n callProxiedListModels,\n type IProviderCompletionParams,\n type IProviderImageGenerationParams,\n type IProviderListModelsParams\n} from './apiClient';\n\nexport {\n callProviderCompletionStream,\n callProxiedCompletionStream,\n type IProviderCompletionStreamParams\n} from './streamingClient';\n\nexport {\n aiProviderId,\n aiServerToolType,\n aiWebSearchToolConfig,\n aiServerToolConfig,\n aiToolEnablement,\n aiAssistProviderConfig,\n aiAssistSettings,\n modelSpecKey,\n modelSpec\n} from './converters';\n\nexport { resolveEffectiveTools } from './toolFormats';\n\nexport {\n extractJsonText,\n fencedStringifiedJson,\n type IFencedStringifiedJsonExtractorOptions,\n type IFencedStringifiedJsonOptions,\n type JsonTextExtractor\n} from './jsonResponse';\n\nexport {\n generateJsonCompletion,\n SMART_JSON_PROMPT_HINT,\n type IGenerateJsonCompletionParams,\n type IGenerateJsonCompletionResult,\n type JsonPromptHint\n} from './jsonCompletion';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,QAAQ,EAqBR,iBAAiB,EA8CjB,gBAAgB,EAChB,mBAAmB,EACnB,YAAY,EACZ,SAAS,EAiBV,MAAM,SAAS,CAAC;AAEjB,OAAO,EAEL,mBAAmB,EACnB,uBAAuB,EACxB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,cAAc,EACd,sBAAsB,EACtB,qBAAqB,EACrB,sBAAsB,EACtB,uBAAuB,EACvB,+BAA+B,EAChC,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,2BAA2B,EAC3B,0BAA0B,EAC1B,sBAAsB,EACtB,qBAAqB,EAItB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,4BAA4B,EAC5B,2BAA2B,EAE3B,qBAAqB,EAGtB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,qBAAqB,EACrB,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,gBAAgB,EAChB,YAAY,EACZ,SAAS,EACV,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAEtD,OAAO,EACL,eAAe,EACf,qBAAqB,EAItB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EAIvB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,6BAA6B,EAAgC,MAAM,2BAA2B,CAAC","sourcesContent":["/**\n * AI assist packlet - provider registry, prompt class, settings, and API client.\n * @packageDocumentation\n */\n\nexport {\n AiPrompt,\n type AiModelCapability,\n type AiProviderId,\n type AiServerToolType,\n type AiServerToolConfig,\n type AiToolConfig,\n type IAiWebSearchToolConfig,\n type IAiClientToolConfig,\n type IAiClientTool,\n type IAiClientToolCallSummary,\n type IAiClientToolContinuation,\n type IAiClientToolTurnResult,\n type IAiToolEnablement,\n type IAiCompletionResponse,\n type IChatMessage,\n type AiApiFormat,\n type AiImageApiFormat,\n type IAiImageModelCapability,\n type IAiProviderDescriptor,\n type IAiAssistProviderConfig,\n type IAiAssistSettings,\n DEFAULT_AI_ASSIST,\n type IAiAssistKeyStore,\n type IAiImageAttachment,\n type IAiImageData,\n type AiImageSize,\n type AiImageQuality,\n type DallE2Size,\n type DallE3Size,\n type GptImageSize,\n type DallE3Quality,\n type GptImageQuality,\n type DallEModelNames,\n type GptImageModelNames,\n type GrokImagineModelNames,\n type Imagen4ModelNames,\n type GeminiFlashImageModelNames,\n type IDallEImageGenerationConfig,\n type IGptImageGenerationConfig,\n type IGrokImagineImageGenerationConfig,\n type IImagen4GenerationConfig,\n type IGeminiFlashImageGenerationConfig,\n type IDallEModelOptions,\n type IGptImageModelOptions,\n type IGrokImagineModelOptions,\n type IImagen4ModelOptions,\n type IGeminiFlashImageModelOptions,\n type IOtherModelOptions,\n type IModelFamilyConfig,\n type IAiImageGenerationOptions,\n type IAiImageGenerationParams,\n type IAiGeneratedImage,\n type IAiImageGenerationResponse,\n type IAiModelCapabilityRule,\n type IAiModelCapabilityConfig,\n type IAiModelInfo,\n type IAiStreamEvent,\n type IAiStreamTextDelta,\n type IAiStreamToolEvent,\n type IAiStreamToolUseStart,\n type IAiStreamToolUseDelta,\n type IAiStreamToolUseComplete,\n type IAiStreamDone,\n type IAiStreamError,\n type ModelSpec,\n type ModelSpecKey,\n type IModelSpecMap,\n allModelSpecKeys,\n MODEL_SPEC_BASE_KEY,\n resolveModel,\n toDataUrl,\n type AiThinkingMode,\n type IThinkingConfig,\n type IThinkingProviderConfig,\n type IAnthropicThinkingOptions,\n type IOpenAiThinkingOptions,\n type IGeminiThinkingOptions,\n type IXAiThinkingOptions,\n type IOtherThinkingOptions,\n type IAnthropicThinkingConfig,\n type IOpenAiThinkingConfig,\n type IGeminiThinkingConfig,\n type IXAiThinkingConfig,\n type AnthropicThinkingModelNames,\n type OpenAiThinkingModelNames,\n type GeminiThinkingModelNames,\n type XAiThinkingModelNames\n} from './model';\n\nexport {\n type IResolvedImageOptions,\n resolveImageOptions,\n validateResolvedOptions\n} from './imageOptionsResolver';\n\nexport {\n allProviderIds,\n getProviderDescriptors,\n getProviderDescriptor,\n resolveImageCapability,\n supportsImageGeneration,\n DEFAULT_MODEL_CAPABILITY_CONFIG\n} from './registry';\n\nexport {\n callProviderCompletion,\n callProxiedCompletion,\n callProviderImageGeneration,\n callProxiedImageGeneration,\n callProviderListModels,\n callProxiedListModels,\n type IProviderCompletionParams,\n type IProviderImageGenerationParams,\n type IProviderListModelsParams\n} from './apiClient';\n\nexport {\n callProviderCompletionStream,\n callProxiedCompletionStream,\n type IProviderCompletionStreamParams,\n executeClientToolTurn,\n type IExecuteClientToolTurnParams,\n type IExecuteClientToolTurnResult\n} from './streamingClient';\n\nexport {\n aiProviderId,\n aiServerToolType,\n aiWebSearchToolConfig,\n aiServerToolConfig,\n aiClientToolConfig,\n aiToolEnablement,\n aiAssistProviderConfig,\n aiAssistSettings,\n modelSpecKey,\n modelSpec\n} from './converters';\n\nexport { resolveEffectiveTools } from './toolFormats';\n\nexport {\n extractJsonText,\n fencedStringifiedJson,\n type IFencedStringifiedJsonExtractorOptions,\n type IFencedStringifiedJsonOptions,\n type JsonTextExtractor\n} from './jsonResponse';\n\nexport {\n generateJsonCompletion,\n SMART_JSON_PROMPT_HINT,\n type IGenerateJsonCompletionParams,\n type IGenerateJsonCompletionResult,\n type JsonPromptHint\n} from './jsonCompletion';\n\nexport { anthropicEffortToBudgetTokens, type IResolvedThinkingConfig } from './thinkingOptionsResolver';\n"]}
@@ -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;AA+BZ;;;;;GAKG;AACH,MAAM,UAAU,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,MAAM,OAAO,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;AA6ED;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAgC,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;AAEpG;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAiB,MAAM,CAAC;AAiCxD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,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,mBAAmB,IAAI,IAAI,EAAE,CAAC;QAChC,OAAO,YAAY,CAAC,IAAI,CAAC,mBAAmB,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;AACH,MAAM,CAAC,MAAM,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
+ {"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;AA+BZ;;;;;GAKG;AACH,MAAM,UAAU,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,MAAM,OAAO,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;AAqQD;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAgC,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;AAEpG;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAiB,MAAM,CAAC;AAiCxD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,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,mBAAmB,IAAI,IAAI,EAAE,CAAC;QAChC,OAAO,YAAY,CAAC,IAAI,CAAC,mBAAmB,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;AA83BD;;;GAGG;AACH,MAAM,CAAC,MAAM,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, type JsonSchema } 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// Client-Defined Tools\n// ============================================================================\n\n/**\n * Configuration for a client-defined (harness-supplied) tool.\n *\n * @remarks\n * The `parametersSchema` is the single source of truth for both the wire-format\n * JSON Schema sent to the provider (via `.toJson()`) and the runtime argument\n * validation (via `.validate(rawArgs)`). Use `JsonSchema.object(...)` from\n * `@fgv/ts-json-base` to author the schema as a const (e.g. `const mySchema = JsonSchema.object({...})`);\n * the static type `TParams` is then derived via `JsonSchema.Static<typeof mySchema>` —\n * no drift between wire schema and runtime validation.\n *\n * @public\n */\nexport interface IAiClientToolConfig<TParams = unknown> {\n /** Discriminator — always `'client_tool'`. */\n readonly type: 'client_tool';\n /** Tool name sent to the model (must be unique within a call). */\n readonly name: string;\n /** Human-readable description of what the tool does, shown to the model. */\n readonly description: string;\n /**\n * JSON Schema validator for the tool's parameters. Emits wire format via\n * `.toJson()` and validates model-returned args via `.validate(rawArgs)`.\n */\n readonly parametersSchema: JsonSchema.ISchemaValidator<TParams>;\n}\n\n/**\n * A client-defined tool: configuration + execution callback pair.\n *\n * @remarks\n * The `execute` callback receives typed `TParams` (already validated by\n * `config.parametersSchema.validate()`) and returns a `Promise<Result<unknown>>`.\n * Thrown errors are caught via `captureAsyncResult` in the round-trip helper.\n *\n * @public\n */\nexport interface IAiClientTool<TParams = unknown> {\n /** The tool's configuration (name, description, parameters schema). */\n readonly config: IAiClientToolConfig<TParams>;\n /**\n * Execute the tool with validated parameters.\n * @param args - Typed arguments, already validated against `config.parametersSchema`.\n * @returns A `Promise<Result<unknown>>` — the result is stringified and sent back to the model.\n */\n readonly execute: (args: TParams) => Promise<Result<unknown>>;\n}\n\n/**\n * Union of all tool configurations: server-side or client-defined.\n * Discriminated on `type`.\n * @public\n */\nexport type AiToolConfig = AiServerToolConfig | IAiClientToolConfig;\n\n// ============================================================================\n// Client Tool Streaming Events\n// ============================================================================\n\n/**\n * Emitted when a client-defined tool call begins streaming. Carries the tool name\n * and optional provider-assigned call ID (Anthropic / OpenAI Responses API; absent\n * for Gemini which does not assign call IDs).\n * @public\n */\nexport interface IAiStreamToolUseStart {\n readonly type: 'client-tool-call-start';\n /** The name of the client tool being called. */\n readonly toolName: string;\n /**\n * Provider-assigned call identifier (Anthropic: `toolu_*`; OpenAI: `call_*`).\n * Absent for Gemini (correlation by name).\n */\n readonly callId?: string;\n}\n\n/**\n * Emitted when a client-defined tool call is complete and its arguments are fully\n * accumulated. The `args` object is the fully parsed JSON object — no further\n * streaming deltas follow for this call.\n * @public\n */\nexport interface IAiStreamToolUseDelta {\n readonly type: 'client-tool-call-done';\n /** The name of the client tool being called. */\n readonly toolName: string;\n /**\n * Provider-assigned call identifier. Absent for Gemini.\n */\n readonly callId?: string;\n /** The fully accumulated and parsed tool arguments. */\n readonly args: JsonObject;\n}\n\n/**\n * Emitted after a client-defined tool has been executed and the result is ready\n * to be fed back to the model in the round-trip continuation.\n * @public\n */\nexport interface IAiStreamToolUseComplete {\n readonly type: 'client-tool-result';\n /** The name of the client tool that was executed. */\n readonly toolName: string;\n /**\n * Provider-assigned call identifier. Absent for Gemini.\n */\n readonly callId?: string;\n /** The stringified result returned by the tool's execute callback. */\n readonly result: string;\n /** Whether the tool execution failed (schema validation failure, execute error, or unknown tool). */\n readonly isError: boolean;\n}\n\n// ============================================================================\n// Client Tool Round-Trip Types\n// ============================================================================\n\n/**\n * Summary of a single client tool call within a turn: the tool name, call ID,\n * raw arguments, execution result, and whether the execution was an error.\n * @public\n */\nexport interface IAiClientToolCallSummary {\n /** The name of the tool that was called. */\n readonly toolName: string;\n /** Provider-assigned call identifier (absent for Gemini). */\n readonly callId?: string;\n /** The fully accumulated raw arguments object as parsed JSON. */\n readonly args: JsonObject;\n /** The stringified result (success value or error message). */\n readonly result: string;\n /** Whether execution failed (schema validation failure, execute error, or unknown tool). */\n readonly isError: boolean;\n}\n\n/**\n * The provider-specific continuation data needed to build the follow-up request\n * for the next round of the conversation.\n *\n * @remarks\n * `messages` are provider-native request objects (Anthropic: content-block arrays,\n * OpenAI Responses API: input items, Gemini: content parts). The continuation\n * builder in `clientToolContinuationBuilder.ts` populates this.\n *\n * @public\n */\nexport interface IAiClientToolContinuation {\n /**\n * Provider-native wire-format message objects to supply back on the next\n * streaming call via `IExecuteClientToolTurnParams.continuationMessages`\n * (which is forwarded as `rawTail` to the underlying call). The exact\n * shape depends on the provider format and may contain provider-specific\n * blocks (e.g. Anthropic thinking/redacted_thinking/tool_use). These are\n * NOT `IChatMessage[]` and must not be prepended via `messagesBefore` —\n * the normalized-message path would strip the provider-native fields\n * (signatures, redacted thinking) that the server requires for\n * continuation validation.\n */\n readonly messages: ReadonlyArray<JsonObject>;\n /** Summary of each tool call that was executed in this turn. */\n readonly toolCallsSummary: ReadonlyArray<IAiClientToolCallSummary>;\n}\n\n/**\n * The result of a single client-tool turn: the optional continuation for the next\n * call (absent when no tool calls occurred) and whether the stream was truncated.\n * @public\n */\nexport interface IAiClientToolTurnResult {\n /**\n * The continuation data for the next round-trip. `undefined` when the model\n * completed without invoking any client tools.\n */\n readonly continuation: IAiClientToolContinuation | undefined;\n /** Whether the stream was truncated (token limit or stop reason). */\n readonly truncated: boolean;\n /** The full concatenated text from all `text-delta` events in this turn. */\n readonly fullText: string;\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 * Provider-reported reason a truncated response was cut short (e.g.\n * `'max_output_tokens'`, `'content_filter'`), when the provider supplies one.\n * Currently populated only by the OpenAI / xAI Responses adapter, from the\n * completed payload's `incomplete_details.reason`. Meaningful only when\n * `truncated === true`; `undefined` otherwise (and whenever the provider\n * reports truncation without a reason).\n */\n readonly incompleteReason?: 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 *\n * @remarks\n * **Exhaustive-switch consumers must handle all variants.** The three\n * `client-tool-*` variants were added when client-tool support shipped;\n * update every exhaustive switch over this union in lockstep.\n *\n * @public\n */\nexport type IAiStreamEvent =\n | IAiStreamTextDelta\n | IAiStreamToolEvent\n | IAiStreamToolUseStart\n | IAiStreamToolUseDelta\n | IAiStreamToolUseComplete\n | IAiStreamDone\n | 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. The emit-site converts to `thinking.budget_tokens`\n * (the integer budget the Anthropic API requires). Mapping policy: low = 2048,\n * medium = 8192, high = 24000, max = 32000.\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"]}