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

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 (108) hide show
  1. package/dist/packlets/ai-assist/apiClient.js +58 -112
  2. package/dist/packlets/ai-assist/apiClient.js.map +1 -1
  3. package/dist/packlets/ai-assist/chatRequestBuilders.js +131 -35
  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/embeddingClient.js +346 -0
  8. package/dist/packlets/ai-assist/embeddingClient.js.map +1 -0
  9. package/dist/packlets/ai-assist/http.js +75 -0
  10. package/dist/packlets/ai-assist/http.js.map +1 -0
  11. package/dist/packlets/ai-assist/index.js +6 -4
  12. package/dist/packlets/ai-assist/index.js.map +1 -1
  13. package/dist/packlets/ai-assist/jsonCompletion.js +6 -8
  14. package/dist/packlets/ai-assist/jsonCompletion.js.map +1 -1
  15. package/dist/packlets/ai-assist/model.js +36 -1
  16. package/dist/packlets/ai-assist/model.js.map +1 -1
  17. package/dist/packlets/ai-assist/registry.js +77 -7
  18. package/dist/packlets/ai-assist/registry.js.map +1 -1
  19. package/dist/packlets/ai-assist/streamingAdapters/anthropic.js +176 -32
  20. package/dist/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -1
  21. package/dist/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js +528 -0
  22. package/dist/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js.map +1 -0
  23. package/dist/packlets/ai-assist/streamingAdapters/common.js +95 -0
  24. package/dist/packlets/ai-assist/streamingAdapters/common.js.map +1 -1
  25. package/dist/packlets/ai-assist/streamingAdapters/gemini.js +34 -10
  26. package/dist/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -1
  27. package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js +215 -15
  28. package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -1
  29. package/dist/packlets/ai-assist/streamingAdapters/proxy.js +15 -8
  30. package/dist/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -1
  31. package/dist/packlets/ai-assist/streamingClient.js +29 -5
  32. package/dist/packlets/ai-assist/streamingClient.js.map +1 -1
  33. package/dist/packlets/ai-assist/thinkingOptionsResolver.js +23 -0
  34. package/dist/packlets/ai-assist/thinkingOptionsResolver.js.map +1 -1
  35. package/dist/packlets/ai-assist/toolFormats.js +106 -10
  36. package/dist/packlets/ai-assist/toolFormats.js.map +1 -1
  37. package/dist/ts-extras.d.ts +682 -48
  38. package/lib/packlets/ai-assist/apiClient.d.ts +24 -34
  39. package/lib/packlets/ai-assist/apiClient.d.ts.map +1 -1
  40. package/lib/packlets/ai-assist/apiClient.js +67 -121
  41. package/lib/packlets/ai-assist/apiClient.js.map +1 -1
  42. package/lib/packlets/ai-assist/chatRequestBuilders.d.ts +82 -22
  43. package/lib/packlets/ai-assist/chatRequestBuilders.d.ts.map +1 -1
  44. package/lib/packlets/ai-assist/chatRequestBuilders.js +132 -34
  45. package/lib/packlets/ai-assist/chatRequestBuilders.js.map +1 -1
  46. package/lib/packlets/ai-assist/converters.d.ts +9 -1
  47. package/lib/packlets/ai-assist/converters.d.ts.map +1 -1
  48. package/lib/packlets/ai-assist/converters.js +31 -1
  49. package/lib/packlets/ai-assist/converters.js.map +1 -1
  50. package/lib/packlets/ai-assist/embeddingClient.d.ts +69 -0
  51. package/lib/packlets/ai-assist/embeddingClient.d.ts.map +1 -0
  52. package/lib/packlets/ai-assist/embeddingClient.js +350 -0
  53. package/lib/packlets/ai-assist/embeddingClient.js.map +1 -0
  54. package/lib/packlets/ai-assist/http.d.ts +24 -0
  55. package/lib/packlets/ai-assist/http.d.ts.map +1 -0
  56. package/lib/packlets/ai-assist/http.js +78 -0
  57. package/lib/packlets/ai-assist/http.js.map +1 -0
  58. package/lib/packlets/ai-assist/index.d.ts +6 -4
  59. package/lib/packlets/ai-assist/index.d.ts.map +1 -1
  60. package/lib/packlets/ai-assist/index.js +11 -1
  61. package/lib/packlets/ai-assist/index.js.map +1 -1
  62. package/lib/packlets/ai-assist/jsonCompletion.d.ts.map +1 -1
  63. package/lib/packlets/ai-assist/jsonCompletion.js +6 -8
  64. package/lib/packlets/ai-assist/jsonCompletion.js.map +1 -1
  65. package/lib/packlets/ai-assist/model.d.ts +377 -5
  66. package/lib/packlets/ai-assist/model.d.ts.map +1 -1
  67. package/lib/packlets/ai-assist/model.js +37 -2
  68. package/lib/packlets/ai-assist/model.js.map +1 -1
  69. package/lib/packlets/ai-assist/registry.d.ts +23 -1
  70. package/lib/packlets/ai-assist/registry.d.ts.map +1 -1
  71. package/lib/packlets/ai-assist/registry.js +79 -7
  72. package/lib/packlets/ai-assist/registry.js.map +1 -1
  73. package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts +58 -5
  74. package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts.map +1 -1
  75. package/lib/packlets/ai-assist/streamingAdapters/anthropic.js +175 -31
  76. package/lib/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -1
  77. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.d.ts +172 -0
  78. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.d.ts.map +1 -0
  79. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js +534 -0
  80. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js.map +1 -0
  81. package/lib/packlets/ai-assist/streamingAdapters/common.d.ts +59 -11
  82. package/lib/packlets/ai-assist/streamingAdapters/common.d.ts.map +1 -1
  83. package/lib/packlets/ai-assist/streamingAdapters/common.js +97 -0
  84. package/lib/packlets/ai-assist/streamingAdapters/common.js.map +1 -1
  85. package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts +16 -2
  86. package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts.map +1 -1
  87. package/lib/packlets/ai-assist/streamingAdapters/gemini.js +34 -10
  88. package/lib/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -1
  89. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts +15 -2
  90. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts.map +1 -1
  91. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js +214 -14
  92. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -1
  93. package/lib/packlets/ai-assist/streamingAdapters/proxy.d.ts.map +1 -1
  94. package/lib/packlets/ai-assist/streamingAdapters/proxy.js +14 -7
  95. package/lib/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -1
  96. package/lib/packlets/ai-assist/streamingClient.d.ts +17 -0
  97. package/lib/packlets/ai-assist/streamingClient.d.ts.map +1 -1
  98. package/lib/packlets/ai-assist/streamingClient.js +31 -6
  99. package/lib/packlets/ai-assist/streamingClient.js.map +1 -1
  100. package/lib/packlets/ai-assist/thinkingOptionsResolver.d.ts +18 -2
  101. package/lib/packlets/ai-assist/thinkingOptionsResolver.d.ts.map +1 -1
  102. package/lib/packlets/ai-assist/thinkingOptionsResolver.js +24 -0
  103. package/lib/packlets/ai-assist/thinkingOptionsResolver.js.map +1 -1
  104. package/lib/packlets/ai-assist/toolFormats.d.ts +40 -9
  105. package/lib/packlets/ai-assist/toolFormats.d.ts.map +1 -1
  106. package/lib/packlets/ai-assist/toolFormats.js +107 -10
  107. package/lib/packlets/ai-assist/toolFormats.js.map +1 -1
  108. package/package.json +7 -7
@@ -0,0 +1,534 @@
1
+ "use strict";
2
+ // Copyright (c) 2026 Erik Fortune
3
+ //
4
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ // of this software and associated documentation files (the "Software"), to deal
6
+ // in the Software without restriction, including without limitation the rights
7
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ // copies of the Software, and to permit persons to whom the Software is
9
+ // furnished to do so, subject to the following conditions:
10
+ //
11
+ // The above copyright notice and this permission notice shall be included in all
12
+ // copies or substantial portions of the Software.
13
+ //
14
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ // SOFTWARE.
21
+ var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
22
+ var __asyncValues = (this && this.__asyncValues) || function (o) {
23
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
24
+ var m = o[Symbol.asyncIterator], i;
25
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
26
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
27
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
28
+ };
29
+ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
30
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
31
+ var g = generator.apply(thisArg, _arguments || []), i, q = [];
32
+ return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
33
+ function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
34
+ function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
35
+ function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
36
+ function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
37
+ function fulfill(value) { resume("next", value); }
38
+ function reject(value) { resume("throw", value); }
39
+ function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
40
+ };
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.buildAnthropicContinuation = buildAnthropicContinuation;
43
+ exports.buildOpenAiContinuation = buildOpenAiContinuation;
44
+ exports.buildGeminiContinuation = buildGeminiContinuation;
45
+ exports.executeClientToolTurn = executeClientToolTurn;
46
+ /**
47
+ * Per-provider continuation message builders and the `executeClientToolTurn`
48
+ * helper for orchestrating a single client-tool round-trip.
49
+ *
50
+ * Each provider requires a different wire format for the follow-up request:
51
+ * - **Anthropic**: assistant turn reconstructed from the ordered accumulation
52
+ * buffer (thinking / redacted_thinking / text / tool_use in original stream
53
+ * order) + user turn with `tool_result` blocks. When thinking is active, the
54
+ * follow-up request must NOT set `tool_choice: { type: 'any' }` or
55
+ * `tool_choice: { type: 'tool', ... }` (E3 / §5.4 of b4-spike-findings).
56
+ * - **OpenAI / xAI Responses API**: `function_call` input items +
57
+ * `function_call_output` input items.
58
+ * - **Gemini**: model turn with `functionCall` parts + user turn with
59
+ * `functionResponse` parts (correlation by tool name).
60
+ *
61
+ * @packageDocumentation
62
+ */
63
+ const ts_utils_1 = require("@fgv/ts-utils");
64
+ const model_1 = require("../model");
65
+ const chatRequestBuilders_1 = require("../chatRequestBuilders");
66
+ const endpoint_1 = require("../endpoint");
67
+ const anthropic_1 = require("./anthropic");
68
+ const openaiResponses_1 = require("./openaiResponses");
69
+ const gemini_1 = require("./gemini");
70
+ // ============================================================================
71
+ // Anthropic continuation builder
72
+ // ============================================================================
73
+ /**
74
+ * Builds the Anthropic follow-up messages for a client-tool round-trip.
75
+ *
76
+ * Reconstructs the assistant turn from the ordered accumulation buffer
77
+ * (all block types in original stream order) and appends a user turn
78
+ * with `tool_result` blocks for each executed tool call.
79
+ *
80
+ * **Constraint (E3):** The returned continuation does NOT include a forced
81
+ * `tool_choice` field. When thinking is active, Anthropic rejects
82
+ * `tool_choice: { type: 'any' }` and `tool_choice: { type: 'tool', ... }`
83
+ * with an HTTP 400 error. Only `tool_choice: { type: 'auto' }` (the default,
84
+ * i.e. omitted) is compatible with extended thinking.
85
+ *
86
+ * @internal
87
+ */
88
+ function buildAnthropicContinuation(accBuffer, toolResults) {
89
+ // Reconstruct the assistant turn from the ordered accumulation buffer.
90
+ // Sort by buffer key (SSE index) to restore original stream order.
91
+ const sortedKeys = Array.from(accBuffer.keys()).sort((a, b) => a - b);
92
+ const assistantContent = [];
93
+ for (const key of sortedKeys) {
94
+ const block = accBuffer.get(key);
95
+ /* c8 ignore next 1 - defensive: key always exists in map since we iterate its keys */
96
+ if (!block)
97
+ continue;
98
+ if (block.type === 'thinking') {
99
+ assistantContent.push({
100
+ type: 'thinking',
101
+ thinking: block.thinking,
102
+ signature: block.signature
103
+ });
104
+ }
105
+ else if (block.type === 'redacted_thinking') {
106
+ assistantContent.push({
107
+ type: 'redacted_thinking',
108
+ data: block.data
109
+ });
110
+ }
111
+ else if (block.type === 'text') {
112
+ if (block.text.length > 0) {
113
+ assistantContent.push({ type: 'text', text: block.text });
114
+ }
115
+ }
116
+ else if (block.type === 'tool_use') {
117
+ let parsedInput;
118
+ try {
119
+ /* c8 ignore next 1 - defensive: argsBuffer is JSON-parsed in the adapter before emitting client-tool-call-done */
120
+ parsedInput = JSON.parse(block.argsBuffer || '{}');
121
+ /* c8 ignore start - defensive: malformed argsBuffer defaults to empty object */
122
+ }
123
+ catch (_a) {
124
+ parsedInput = {};
125
+ }
126
+ /* c8 ignore stop */
127
+ assistantContent.push({
128
+ type: 'tool_use',
129
+ id: block.id,
130
+ name: block.name,
131
+ input: parsedInput
132
+ });
133
+ }
134
+ }
135
+ // Build user turn with tool_result blocks for each tool call.
136
+ const userContent = toolResults.map((r) => {
137
+ var _a;
138
+ const block = {
139
+ type: 'tool_result',
140
+ // eslint-disable-next-line @typescript-eslint/naming-convention
141
+ tool_use_id: (_a = r.callId) !== null && _a !== void 0 ? _a : r.toolName,
142
+ content: r.result
143
+ };
144
+ if (r.isError) {
145
+ return Object.assign(Object.assign({}, block), { is_error: true });
146
+ }
147
+ return block;
148
+ });
149
+ const assistantMessage = {
150
+ role: 'assistant',
151
+ content: assistantContent
152
+ };
153
+ const userMessage = {
154
+ role: 'user',
155
+ content: userContent
156
+ };
157
+ return {
158
+ messages: [assistantMessage, userMessage],
159
+ toolCallsSummary: toolResults.map((r) => ({
160
+ toolName: r.toolName,
161
+ callId: r.callId,
162
+ args: r.args,
163
+ result: r.result,
164
+ isError: r.isError
165
+ }))
166
+ };
167
+ }
168
+ // ============================================================================
169
+ // OpenAI / xAI Responses API continuation builder
170
+ // ============================================================================
171
+ /**
172
+ * Builds the OpenAI / xAI Responses API follow-up input items for a
173
+ * client-tool round-trip.
174
+ *
175
+ * Emits `function_call` items (the model's call) followed by
176
+ * `function_call_output` items (the harness execution result), one pair
177
+ * per executed tool call.
178
+ *
179
+ * @internal
180
+ */
181
+ function buildOpenAiContinuation(calls, toolResults) {
182
+ var _a;
183
+ const items = [];
184
+ // Emit function_call items for each call (model's side). Per the Responses API spec
185
+ // (ResponseFunctionToolCall), `call_id` is the required correlation field — it must
186
+ // match the matching function_call_output's `call_id` below. The optional `id` field
187
+ // is the output-item id (`fc_*`) used to reference the streamed item; we omit it
188
+ // because it is not load-bearing for input items.
189
+ for (const [callId, call] of calls) {
190
+ items.push({
191
+ type: 'function_call',
192
+ call_id: callId,
193
+ name: call.name,
194
+ arguments: call.argsBuffer
195
+ });
196
+ }
197
+ // Emit function_call_output items (harness execution results).
198
+ for (const r of toolResults) {
199
+ items.push({
200
+ type: 'function_call_output',
201
+ call_id: (_a = r.callId) !== null && _a !== void 0 ? _a : r.toolName,
202
+ output: r.result
203
+ });
204
+ }
205
+ return {
206
+ messages: items,
207
+ toolCallsSummary: toolResults.map((r) => ({
208
+ toolName: r.toolName,
209
+ callId: r.callId,
210
+ args: r.args,
211
+ result: r.result,
212
+ isError: r.isError
213
+ }))
214
+ };
215
+ }
216
+ // ============================================================================
217
+ // Gemini continuation builder
218
+ // ============================================================================
219
+ /**
220
+ * Builds the Gemini follow-up contents for a client-tool round-trip.
221
+ *
222
+ * Emits a model turn with `functionCall` parts (one per tool call) and a
223
+ * user turn with `functionResponse` parts (correlation by tool name, since
224
+ * Gemini does not assign call IDs).
225
+ *
226
+ * @internal
227
+ */
228
+ function buildGeminiContinuation(calls, toolResults) {
229
+ // Model turn: functionCall parts for each call.
230
+ const modelParts = calls.map((call) => ({
231
+ functionCall: {
232
+ name: call.name,
233
+ args: call.args
234
+ }
235
+ }));
236
+ // User turn: functionResponse parts for each executed result.
237
+ // Correlation is by name since Gemini has no call IDs.
238
+ const userParts = toolResults.map((r) => ({
239
+ functionResponse: {
240
+ name: r.toolName,
241
+ response: Object.assign({ content: r.result }, (r.isError ? { error: true } : {}))
242
+ }
243
+ }));
244
+ const modelMessage = {
245
+ role: 'model',
246
+ parts: modelParts
247
+ };
248
+ const userMessage = {
249
+ role: 'user',
250
+ parts: userParts
251
+ };
252
+ return {
253
+ messages: [modelMessage, userMessage],
254
+ toolCallsSummary: toolResults.map((r) => ({
255
+ toolName: r.toolName,
256
+ callId: r.callId,
257
+ args: r.args,
258
+ result: r.result,
259
+ isError: r.isError
260
+ }))
261
+ };
262
+ }
263
+ // ============================================================================
264
+ // executeClientToolTurn
265
+ // ============================================================================
266
+ /**
267
+ * Orchestrates a single client-tool streaming turn for any supported provider.
268
+ *
269
+ * Starts a streaming request, iterates the underlying provider stream, and:
270
+ * - Forwards `text-delta`, `tool-event`, `client-tool-call-start`, and
271
+ * `client-tool-call-done` events through to the consumer.
272
+ * - For each `client-tool-call-done` event: validates the raw args against the
273
+ * tool's `parametersSchema`, invokes `execute(typedArgs)`, and emits a
274
+ * `client-tool-result` event.
275
+ * - After stream completion: builds the per-provider continuation (or
276
+ * `{ continuation: undefined }` when no tool calls occurred) and resolves
277
+ * `nextTurn`.
278
+ *
279
+ * **Anthropic constraint (E3):** The continuation for Anthropic does not set
280
+ * a forced `tool_choice`. Only `tool_choice: 'auto'` (the default, i.e.
281
+ * omitted) is compatible with extended thinking.
282
+ *
283
+ * @param params - Turn parameters
284
+ * @returns `{ events, nextTurn }` — stream iterable + completion promise
285
+ * @public
286
+ */
287
+ function executeClientToolTurn(params) {
288
+ const { descriptor, apiKey, system, messages, continuationMessages, temperature, tools, clientTools, signal, logger, resolvedThinking, model, endpoint } = params;
289
+ const splitResult = (0, chatRequestBuilders_1.splitChatRequest)(system, messages);
290
+ if (splitResult.isFailure()) {
291
+ return (0, ts_utils_1.fail)(splitResult.message);
292
+ }
293
+ const { prompt, head } = splitResult.value;
294
+ if (prompt.attachments.length > 0 && !descriptor.acceptsImageInput) {
295
+ return (0, ts_utils_1.fail)(`provider "${descriptor.id}" does not accept image input`);
296
+ }
297
+ // Build a lookup map of client tools by name for fast access.
298
+ // Fail fast on duplicate names — silently overwriting would cause one tool
299
+ // to shadow another with no observable signal.
300
+ const toolsByName = new Map();
301
+ for (const tool of clientTools) {
302
+ if (toolsByName.has(tool.config.name)) {
303
+ return (0, ts_utils_1.fail)(`executeClientToolTurn: duplicate client tool name '${tool.config.name}'`);
304
+ }
305
+ toolsByName.set(tool.config.name, tool);
306
+ }
307
+ // Merge server tools and client tool configs into a single array for the provider.
308
+ // This is the fix for P1-1: client tools were never sent to the provider because
309
+ // the adapters only received `tools` (server tools). Both must coexist per design §2.5.
310
+ const effectiveTools = clientTools.length > 0 ? [...(tools !== null && tools !== void 0 ? tools : []), ...clientTools.map((t) => t.config)] : tools;
311
+ const effectiveTemperature = temperature !== null && temperature !== void 0 ? temperature : 0.7;
312
+ const resolvedModel = model !== null && model !== void 0 ? model : (0, model_1.resolveModel)(descriptor.defaultModel);
313
+ if (resolvedModel.length === 0) {
314
+ return (0, ts_utils_1.fail)(`provider "${descriptor.id}": no model resolved; pass model or set descriptor.defaultModel`);
315
+ }
316
+ const baseUrlResult = (0, endpoint_1.resolveEffectiveBaseUrl)(descriptor, endpoint);
317
+ if (baseUrlResult.isFailure()) {
318
+ return (0, ts_utils_1.fail)(baseUrlResult.message);
319
+ }
320
+ const config = {
321
+ baseUrl: baseUrlResult.value,
322
+ apiKey,
323
+ model: resolvedModel
324
+ };
325
+ // Accumulation buffers — populated by the adapter, read by the builder.
326
+ const anthropicBuffer = new Map();
327
+ const openAiCallMap = new Map();
328
+ const geminiCalls = [];
329
+ // Collected tool results, populated as each client-tool-call-done is processed.
330
+ const toolResults = [];
331
+ // Stream start: open the underlying adapter stream.
332
+ const streamPromise = (() => {
333
+ switch (descriptor.apiFormat) {
334
+ case 'anthropic':
335
+ return (0, anthropic_1.callAnthropicStream)(config, prompt, head, effectiveTemperature, effectiveTools, logger, signal, resolvedThinking, anthropicBuffer, continuationMessages);
336
+ case 'openai':
337
+ return (0, openaiResponses_1.callOpenAiResponsesStream)(config, prompt,
338
+ /* c8 ignore next 1 - defensive: openai path requires tools; empty array fallback unreachable in practice */
339
+ effectiveTools !== null && effectiveTools !== void 0 ? effectiveTools : [], head, effectiveTemperature, logger, signal, resolvedThinking, openAiCallMap, continuationMessages);
340
+ case 'gemini':
341
+ return (0, gemini_1.callGeminiStream)(config, prompt, head, effectiveTemperature, effectiveTools, logger, signal, resolvedThinking, geminiCalls, continuationMessages);
342
+ /* c8 ignore next 4 - defensive coding: exhaustive switch guaranteed by TypeScript */
343
+ default: {
344
+ const _exhaustive = descriptor.apiFormat;
345
+ return Promise.resolve((0, ts_utils_1.fail)(`unsupported API format: ${String(_exhaustive)}`));
346
+ }
347
+ }
348
+ })();
349
+ // Resolve controls for `nextTurn`.
350
+ let resolveNextTurn;
351
+ const nextTurn = new Promise((resolve) => {
352
+ resolveNextTurn = resolve;
353
+ });
354
+ // The unified-event generator: opens the stream, proxies events, executes tools.
355
+ function eventGenerator() {
356
+ return __asyncGenerator(this, arguments, function* eventGenerator_1() {
357
+ var _a, e_1, _b, _c;
358
+ const streamResult = yield __await(streamPromise);
359
+ if (streamResult.isFailure()) {
360
+ resolveNextTurn((0, ts_utils_1.fail)(streamResult.message));
361
+ yield yield __await({ type: 'error', message: streamResult.message });
362
+ return yield __await(void 0);
363
+ }
364
+ let truncated = false;
365
+ let fullText = '';
366
+ let streamError;
367
+ try {
368
+ for (var _d = true, _e = __asyncValues(streamResult.value), _f; _f = yield __await(_e.next()), _a = _f.done, !_a; _d = true) {
369
+ _c = _f.value;
370
+ _d = false;
371
+ const event = _c;
372
+ if (event.type === 'done') {
373
+ truncated = event.truncated;
374
+ fullText = event.fullText;
375
+ yield yield __await(event);
376
+ continue;
377
+ }
378
+ if (event.type === 'error') {
379
+ streamError = event.message;
380
+ yield yield __await(event);
381
+ continue;
382
+ }
383
+ if (event.type === 'text-delta') {
384
+ yield yield __await(event);
385
+ continue;
386
+ }
387
+ if (event.type === 'tool-event') {
388
+ yield yield __await(event);
389
+ continue;
390
+ }
391
+ if (event.type === 'client-tool-call-start') {
392
+ yield yield __await(event);
393
+ continue;
394
+ }
395
+ if (event.type === 'client-tool-call-done') {
396
+ yield yield __await(event);
397
+ const { toolName, callId, args } = event;
398
+ const tool = toolsByName.get(toolName);
399
+ if (!tool) {
400
+ const errMsg = `model called unknown tool: ${toolName}`;
401
+ const resultEvent = {
402
+ type: 'client-tool-result',
403
+ toolName,
404
+ callId,
405
+ result: errMsg,
406
+ isError: true
407
+ };
408
+ yield yield __await(resultEvent);
409
+ toolResults.push({ toolName, callId, args, result: errMsg, isError: true });
410
+ resolveNextTurn((0, ts_utils_1.fail)(errMsg));
411
+ return yield __await(void 0);
412
+ }
413
+ const validationResult = tool.config.parametersSchema.validate(args);
414
+ if (validationResult.isFailure()) {
415
+ const errMsg = `${toolName} (callId=${callId}): ${validationResult.message}`;
416
+ const resultEvent = {
417
+ type: 'client-tool-result',
418
+ toolName,
419
+ callId,
420
+ result: errMsg,
421
+ isError: true
422
+ };
423
+ yield yield __await(resultEvent);
424
+ toolResults.push({ toolName, callId, args, result: errMsg, isError: true });
425
+ continue;
426
+ }
427
+ const executeResult = yield __await((0, ts_utils_1.captureAsyncResult)(async () => tool.execute(validationResult.value)));
428
+ const executionResult = executeResult.isSuccess()
429
+ ? executeResult.value
430
+ : executeResult;
431
+ if (executionResult.isFailure()) {
432
+ const errMsg = `${toolName} (callId=${callId}): ${executionResult.message}`;
433
+ const resultEvent = {
434
+ type: 'client-tool-result',
435
+ toolName,
436
+ callId,
437
+ result: errMsg,
438
+ isError: true
439
+ };
440
+ yield yield __await(resultEvent);
441
+ toolResults.push({ toolName, callId, args, result: errMsg, isError: true });
442
+ continue;
443
+ }
444
+ // JSON.stringify can throw (circular references) or return undefined
445
+ // (e.g. for a bare `undefined` value, or a value of type `function`).
446
+ // Either outcome violates the client-tool-result event contract, so
447
+ // emit an isError result with diagnostic context instead.
448
+ let resultStr;
449
+ try {
450
+ const stringified = JSON.stringify(executionResult.value);
451
+ if (stringified === undefined) {
452
+ const errMsg = `${toolName} (callId=${callId}): tool returned a non-serializable value (JSON.stringify produced undefined)`;
453
+ const resultEvent = {
454
+ type: 'client-tool-result',
455
+ toolName,
456
+ callId,
457
+ result: errMsg,
458
+ isError: true
459
+ };
460
+ yield yield __await(resultEvent);
461
+ toolResults.push({ toolName, callId, args, result: errMsg, isError: true });
462
+ continue;
463
+ }
464
+ resultStr = stringified;
465
+ }
466
+ catch (e) {
467
+ const errMsg = `${toolName} (callId=${callId}): failed to serialize tool result: ${e.message}`;
468
+ const resultEvent = {
469
+ type: 'client-tool-result',
470
+ toolName,
471
+ callId,
472
+ result: errMsg,
473
+ isError: true
474
+ };
475
+ yield yield __await(resultEvent);
476
+ toolResults.push({ toolName, callId, args, result: errMsg, isError: true });
477
+ continue;
478
+ }
479
+ const resultEvent = {
480
+ type: 'client-tool-result',
481
+ toolName,
482
+ callId,
483
+ result: resultStr,
484
+ isError: false
485
+ };
486
+ yield yield __await(resultEvent);
487
+ toolResults.push({ toolName, callId, args, result: resultStr, isError: false });
488
+ continue;
489
+ }
490
+ }
491
+ }
492
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
493
+ finally {
494
+ try {
495
+ if (!_d && !_a && (_b = _e.return)) yield __await(_b.call(_e));
496
+ }
497
+ finally { if (e_1) throw e_1.error; }
498
+ }
499
+ // Stream has ended. Build the continuation.
500
+ if (streamError !== undefined) {
501
+ resolveNextTurn((0, ts_utils_1.fail)(streamError));
502
+ return yield __await(void 0);
503
+ }
504
+ if (toolResults.length === 0) {
505
+ resolveNextTurn((0, ts_utils_1.succeed)({ continuation: undefined, truncated, fullText }));
506
+ return yield __await(void 0);
507
+ }
508
+ let continuation;
509
+ switch (descriptor.apiFormat) {
510
+ case 'anthropic':
511
+ continuation = buildAnthropicContinuation(anthropicBuffer, toolResults);
512
+ break;
513
+ case 'openai':
514
+ continuation = buildOpenAiContinuation(openAiCallMap, toolResults);
515
+ break;
516
+ case 'gemini':
517
+ continuation = buildGeminiContinuation(geminiCalls, toolResults);
518
+ break;
519
+ /* c8 ignore next 5 - defensive coding: exhaustive switch guaranteed by TypeScript */
520
+ default: {
521
+ const _exhaustive = descriptor.apiFormat;
522
+ resolveNextTurn((0, ts_utils_1.fail)(`unsupported API format: ${String(_exhaustive)}`));
523
+ return yield __await(void 0);
524
+ }
525
+ }
526
+ resolveNextTurn((0, ts_utils_1.succeed)({ continuation, truncated, fullText }));
527
+ });
528
+ }
529
+ return (0, ts_utils_1.succeed)({
530
+ events: { [Symbol.asyncIterator]: () => eventGenerator() },
531
+ nextTurn
532
+ });
533
+ }
534
+ //# sourceMappingURL=clientToolContinuationBuilder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clientToolContinuationBuilder.js","sourceRoot":"","sources":["../../../../src/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.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;;;;;;;;;;;;;;;;;;;;;;AAgFZ,gEAgFC;AAgBD,0DAuCC;AAeD,0DA+CC;AA6GD,sDAsTC;AAtrBD;;;;;;;;;;;;;;;;GAgBG;AAEH,4CAAwF;AAGxF,oCAUkB;AAElB,gEAA0D;AAC1D,0CAAsD;AAItD,2CAAkD;AAClD,uDAA8D;AAC9D,qCAA4C;AAmB5C,+EAA+E;AAC/E,iCAAiC;AACjC,+EAA+E;AAE/E;;;;;;;;;;;;;;GAcG;AACH,SAAgB,0BAA0B,CACxC,SAAyC,EACzC,WAA8B;IAE9B,uEAAuE;IACvE,mEAAmE;IACnE,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACtE,MAAM,gBAAgB,GAAc,EAAE,CAAC;IAEvC,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACjC,sFAAsF;QACtF,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YAC9B,gBAAgB,CAAC,IAAI,CAAC;gBACpB,IAAI,EAAE,UAAU;gBAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,SAAS,EAAE,KAAK,CAAC,SAAS;aAC3B,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,mBAAmB,EAAE,CAAC;YAC9C,gBAAgB,CAAC,IAAI,CAAC;gBACpB,IAAI,EAAE,mBAAmB;gBACzB,IAAI,EAAE,KAAK,CAAC,IAAI;aACjB,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACjC,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC1B,gBAAgB,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;YAC5D,CAAC;QACH,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YACrC,IAAI,WAAuB,CAAC;YAC5B,IAAI,CAAC;gBACH,kHAAkH;gBAClH,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,IAAI,IAAI,CAAe,CAAC;gBACjE,gFAAgF;YAClF,CAAC;YAAC,WAAM,CAAC;gBACP,WAAW,GAAG,EAAE,CAAC;YACnB,CAAC;YACD,oBAAoB;YACpB,gBAAgB,CAAC,IAAI,CAAC;gBACpB,IAAI,EAAE,UAAU;gBAChB,EAAE,EAAE,KAAK,CAAC,EAAE;gBACZ,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,KAAK,EAAE,WAAW;aACnB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,8DAA8D;IAC9D,MAAM,WAAW,GAAc,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAc,EAAE;;QAC/D,MAAM,KAAK,GAAe;YACxB,IAAI,EAAE,aAAa;YACnB,gEAAgE;YAChE,WAAW,EAAE,MAAA,CAAC,CAAC,MAAM,mCAAI,CAAC,CAAC,QAAQ;YACnC,OAAO,EAAE,CAAC,CAAC,MAAM;SAClB,CAAC;QACF,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;YACd,uCAAY,KAAK,KAAE,QAAQ,EAAE,IAAI,IAAG;QACtC,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC,CAAC,CAAC;IAEH,MAAM,gBAAgB,GAAe;QACnC,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,gBAAgB;KAC1B,CAAC;IACF,MAAM,WAAW,GAAe;QAC9B,IAAI,EAAE,MAAM;QACZ,OAAO,EAAE,WAAW;KACrB,CAAC;IAEF,OAAO;QACL,QAAQ,EAAE,CAAC,gBAAgB,EAAE,WAAW,CAAC;QACzC,gBAAgB,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACxC,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,OAAO,EAAE,CAAC,CAAC,OAAO;SACnB,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,kDAAkD;AAClD,+EAA+E;AAE/E;;;;;;;;;GASG;AACH,SAAgB,uBAAuB,CACrC,KAA4C,EAC5C,WAA8B;;IAE9B,MAAM,KAAK,GAAiB,EAAE,CAAC;IAE/B,oFAAoF;IACpF,oFAAoF;IACpF,qFAAqF;IACrF,iFAAiF;IACjF,kDAAkD;IAClD,KAAK,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,KAAK,EAAE,CAAC;QACnC,KAAK,CAAC,IAAI,CAAC;YACT,IAAI,EAAE,eAAe;YACrB,OAAO,EAAE,MAAM;YACf,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,SAAS,EAAE,IAAI,CAAC,UAAU;SAC3B,CAAC,CAAC;IACL,CAAC;IAED,+DAA+D;IAC/D,KAAK,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC;QAC5B,KAAK,CAAC,IAAI,CAAC;YACT,IAAI,EAAE,sBAAsB;YAC5B,OAAO,EAAE,MAAA,CAAC,CAAC,MAAM,mCAAI,CAAC,CAAC,QAAQ;YAC/B,MAAM,EAAE,CAAC,CAAC,MAAM;SACjB,CAAC,CAAC;IACL,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,KAAK;QACf,gBAAgB,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACxC,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,OAAO,EAAE,CAAC,CAAC,OAAO;SACnB,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,8BAA8B;AAC9B,+EAA+E;AAE/E;;;;;;;;GAQG;AACH,SAAgB,uBAAuB,CACrC,KAAuC,EACvC,WAA8B;IAE9B,gDAAgD;IAChD,MAAM,UAAU,GAAc,KAAK,CAAC,GAAG,CACrC,CAAC,IAAI,EAAc,EAAE,CAAC,CAAC;QACrB,YAAY,EAAE;YACZ,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,IAAI,EAAE,IAAI,CAAC,IAAI;SAChB;KACF,CAAC,CACH,CAAC;IAEF,8DAA8D;IAC9D,uDAAuD;IACvD,MAAM,SAAS,GAAc,WAAW,CAAC,GAAG,CAC1C,CAAC,CAAC,EAAc,EAAE,CAAC,CAAC;QAClB,gBAAgB,EAAE;YAChB,IAAI,EAAE,CAAC,CAAC,QAAQ;YAChB,QAAQ,kBACN,OAAO,EAAE,CAAC,CAAC,MAAM,IACd,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CACtC;SACF;KACF,CAAC,CACH,CAAC;IAEF,MAAM,YAAY,GAAe;QAC/B,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,UAAU;KAClB,CAAC;IACF,MAAM,WAAW,GAAe;QAC9B,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,SAAS;KACjB,CAAC;IAEF,OAAO;QACL,QAAQ,EAAE,CAAC,YAAY,EAAE,WAAW,CAAC;QACrC,gBAAgB,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACxC,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,OAAO,EAAE,CAAC,CAAC,OAAO;SACnB,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC;AAoFD,+EAA+E;AAC/E,wBAAwB;AACxB,+EAA+E;AAE/E;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,SAAgB,qBAAqB,CACnC,MAAoC;IAEpC,MAAM,EACJ,UAAU,EACV,MAAM,EACN,MAAM,EACN,QAAQ,EACR,oBAAoB,EACpB,WAAW,EACX,KAAK,EACL,WAAW,EACX,MAAM,EACN,MAAM,EACN,gBAAgB,EAChB,KAAK,EACL,QAAQ,EACT,GAAG,MAAM,CAAC;IAEX,MAAM,WAAW,GAAG,IAAA,sCAAgB,EAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACvD,IAAI,WAAW,CAAC,SAAS,EAAE,EAAE,CAAC;QAC5B,OAAO,IAAA,eAAI,EAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IACnC,CAAC;IACD,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,WAAW,CAAC,KAAK,CAAC;IAC3C,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC;QACnE,OAAO,IAAA,eAAI,EAAC,aAAa,UAAU,CAAC,EAAE,+BAA+B,CAAC,CAAC;IACzE,CAAC;IAED,8DAA8D;IAC9D,2EAA2E;IAC3E,+CAA+C;IAC/C,MAAM,WAAW,GAAG,IAAI,GAAG,EAAyB,CAAC;IACrD,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,IAAI,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YACtC,OAAO,IAAA,eAAI,EAAC,sDAAsD,IAAI,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC;QACzF,CAAC;QACD,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC1C,CAAC;IAED,mFAAmF;IACnF,iFAAiF;IACjF,wFAAwF;IACxF,MAAM,cAAc,GAClB,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,aAAL,KAAK,cAAL,KAAK,GAAI,EAAE,CAAC,EAAE,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAE3F,MAAM,oBAAoB,GAAG,WAAW,aAAX,WAAW,cAAX,WAAW,GAAI,GAAG,CAAC;IAChD,MAAM,aAAa,GAAG,KAAK,aAAL,KAAK,cAAL,KAAK,GAAI,IAAA,oBAAY,EAAC,UAAU,CAAC,YAAY,CAAC,CAAC;IACrE,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,OAAO,IAAA,eAAI,EAAC,aAAa,UAAU,CAAC,EAAE,iEAAiE,CAAC,CAAC;IAC3G,CAAC;IACD,MAAM,aAAa,GAAG,IAAA,kCAAuB,EAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IACpE,IAAI,aAAa,CAAC,SAAS,EAAE,EAAE,CAAC;QAC9B,OAAO,IAAA,eAAI,EAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC;IACD,MAAM,MAAM,GAAqB;QAC/B,OAAO,EAAE,aAAa,CAAC,KAAK;QAC5B,MAAM;QACN,KAAK,EAAE,aAAa;KACrB,CAAC;IAEF,wEAAwE;IACxE,MAAM,eAAe,GAAG,IAAI,GAAG,EAA6B,CAAC;IAC7D,MAAM,aAAa,GAAG,IAAI,GAAG,EAAoC,CAAC;IAClE,MAAM,WAAW,GAAqC,EAAE,CAAC;IAEzD,gFAAgF;IAChF,MAAM,WAAW,GAAsB,EAAE,CAAC;IAE1C,oDAAoD;IACpD,MAAM,aAAa,GAAmD,CAAC,GAAG,EAAE;QAC1E,QAAQ,UAAU,CAAC,SAAS,EAAE,CAAC;YAC7B,KAAK,WAAW;gBACd,OAAO,IAAA,+BAAmB,EACxB,MAAM,EACN,MAAM,EACN,IAAI,EACJ,oBAAoB,EACpB,cAAc,EACd,MAAM,EACN,MAAM,EACN,gBAAgB,EAChB,eAAe,EACf,oBAAoB,CACrB,CAAC;YACJ,KAAK,QAAQ;gBACX,OAAO,IAAA,2CAAyB,EAC9B,MAAM,EACN,MAAM;gBACN,4GAA4G;gBAC5G,cAAc,aAAd,cAAc,cAAd,cAAc,GAAI,EAAE,EACpB,IAAI,EACJ,oBAAoB,EACpB,MAAM,EACN,MAAM,EACN,gBAAgB,EAChB,aAAa,EACb,oBAAoB,CACrB,CAAC;YACJ,KAAK,QAAQ;gBACX,OAAO,IAAA,yBAAgB,EACrB,MAAM,EACN,MAAM,EACN,IAAI,EACJ,oBAAoB,EACpB,cAAc,EACd,MAAM,EACN,MAAM,EACN,gBAAgB,EAChB,WAAW,EACX,oBAAoB,CACrB,CAAC;YACJ,qFAAqF;YACrF,OAAO,CAAC,CAAC,CAAC;gBACR,MAAM,WAAW,GAAU,UAAU,CAAC,SAAS,CAAC;gBAChD,OAAO,OAAO,CAAC,OAAO,CAAC,IAAA,eAAI,EAAC,2BAA2B,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC;YACjF,CAAC;QACH,CAAC;IACH,CAAC,CAAC,EAAE,CAAC;IAEL,mCAAmC;IACnC,IAAI,eAAmE,CAAC;IACxE,MAAM,QAAQ,GAAG,IAAI,OAAO,CAAkC,CAAC,OAAO,EAAE,EAAE;QACxE,eAAe,GAAG,OAAO,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,iFAAiF;IACjF,SAAgB,cAAc;;;YAC5B,MAAM,YAAY,GAAG,cAAM,aAAa,CAAA,CAAC;YACzC,IAAI,YAAY,CAAC,SAAS,EAAE,EAAE,CAAC;gBAC7B,eAAe,CAAC,IAAA,eAAI,EAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;gBAC5C,oBAAM,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,OAAO,EAAE,CAAA,CAAC;gBACvD,6BAAO;YACT,CAAC;YAED,IAAI,SAAS,GAAG,KAAK,CAAC;YACtB,IAAI,QAAQ,GAAG,EAAE,CAAC;YAClB,IAAI,WAA+B,CAAC;;gBAEpC,KAA0B,eAAA,KAAA,cAAA,YAAY,CAAC,KAAK,CAAA,IAAA,+DAAE,CAAC;oBAArB,cAAkB;oBAAlB,WAAkB;oBAAjC,MAAM,KAAK,KAAA,CAAA;oBACpB,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;wBAC1B,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC;wBAC5B,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC;wBAC1B,oBAAM,KAAK,CAAA,CAAC;wBACZ,SAAS;oBACX,CAAC;oBAED,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;wBAC3B,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC;wBAC5B,oBAAM,KAAK,CAAA,CAAC;wBACZ,SAAS;oBACX,CAAC;oBAED,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;wBAChC,oBAAM,KAAK,CAAA,CAAC;wBACZ,SAAS;oBACX,CAAC;oBAED,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;wBAChC,oBAAM,KAAK,CAAA,CAAC;wBACZ,SAAS;oBACX,CAAC;oBAED,IAAI,KAAK,CAAC,IAAI,KAAK,wBAAwB,EAAE,CAAC;wBAC5C,oBAAM,KAAK,CAAA,CAAC;wBACZ,SAAS;oBACX,CAAC;oBAED,IAAI,KAAK,CAAC,IAAI,KAAK,uBAAuB,EAAE,CAAC;wBAC3C,oBAAM,KAAK,CAAA,CAAC;wBAEZ,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC;wBACzC,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;wBAEvC,IAAI,CAAC,IAAI,EAAE,CAAC;4BACV,MAAM,MAAM,GAAG,8BAA8B,QAAQ,EAAE,CAAC;4BACxD,MAAM,WAAW,GAAmB;gCAClC,IAAI,EAAE,oBAAoB;gCAC1B,QAAQ;gCACR,MAAM;gCACN,MAAM,EAAE,MAAM;gCACd,OAAO,EAAE,IAAI;6BACd,CAAC;4BACF,oBAAM,WAAW,CAAA,CAAC;4BAClB,WAAW,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;4BAC5E,eAAe,CAAC,IAAA,eAAI,EAAC,MAAM,CAAC,CAAC,CAAC;4BAC9B,6BAAO;wBACT,CAAC;wBAED,MAAM,gBAAgB,GAAG,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;wBACrE,IAAI,gBAAgB,CAAC,SAAS,EAAE,EAAE,CAAC;4BACjC,MAAM,MAAM,GAAG,GAAG,QAAQ,YAAY,MAAM,MAAM,gBAAgB,CAAC,OAAO,EAAE,CAAC;4BAC7E,MAAM,WAAW,GAAmB;gCAClC,IAAI,EAAE,oBAAoB;gCAC1B,QAAQ;gCACR,MAAM;gCACN,MAAM,EAAE,MAAM;gCACd,OAAO,EAAE,IAAI;6BACd,CAAC;4BACF,oBAAM,WAAW,CAAA,CAAC;4BAClB,WAAW,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;4BAC5E,SAAS;wBACX,CAAC;wBAED,MAAM,aAAa,GAAG,cAAM,IAAA,6BAAkB,EAAC,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAA,CAAC;wBACjG,MAAM,eAAe,GAAoB,aAAa,CAAC,SAAS,EAAE;4BAChE,CAAC,CAAC,aAAa,CAAC,KAAK;4BACrB,CAAC,CAAC,aAAa,CAAC;wBAElB,IAAI,eAAe,CAAC,SAAS,EAAE,EAAE,CAAC;4BAChC,MAAM,MAAM,GAAG,GAAG,QAAQ,YAAY,MAAM,MAAM,eAAe,CAAC,OAAO,EAAE,CAAC;4BAC5E,MAAM,WAAW,GAAmB;gCAClC,IAAI,EAAE,oBAAoB;gCAC1B,QAAQ;gCACR,MAAM;gCACN,MAAM,EAAE,MAAM;gCACd,OAAO,EAAE,IAAI;6BACd,CAAC;4BACF,oBAAM,WAAW,CAAA,CAAC;4BAClB,WAAW,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;4BAC5E,SAAS;wBACX,CAAC;wBAED,qEAAqE;wBACrE,sEAAsE;wBACtE,oEAAoE;wBACpE,0DAA0D;wBAC1D,IAAI,SAAiB,CAAC;wBACtB,IAAI,CAAC;4BACH,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;4BAC1D,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;gCAC9B,MAAM,MAAM,GAAG,GAAG,QAAQ,YAAY,MAAM,+EAA+E,CAAC;gCAC5H,MAAM,WAAW,GAAmB;oCAClC,IAAI,EAAE,oBAAoB;oCAC1B,QAAQ;oCACR,MAAM;oCACN,MAAM,EAAE,MAAM;oCACd,OAAO,EAAE,IAAI;iCACd,CAAC;gCACF,oBAAM,WAAW,CAAA,CAAC;gCAClB,WAAW,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;gCAC5E,SAAS;4BACX,CAAC;4BACD,SAAS,GAAG,WAAW,CAAC;wBAC1B,CAAC;wBAAC,OAAO,CAAC,EAAE,CAAC;4BACX,MAAM,MAAM,GAAG,GAAG,QAAQ,YAAY,MAAM,uCACzC,CAAW,CAAC,OACf,EAAE,CAAC;4BACH,MAAM,WAAW,GAAmB;gCAClC,IAAI,EAAE,oBAAoB;gCAC1B,QAAQ;gCACR,MAAM;gCACN,MAAM,EAAE,MAAM;gCACd,OAAO,EAAE,IAAI;6BACd,CAAC;4BACF,oBAAM,WAAW,CAAA,CAAC;4BAClB,WAAW,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;4BAC5E,SAAS;wBACX,CAAC;wBACD,MAAM,WAAW,GAAmB;4BAClC,IAAI,EAAE,oBAAoB;4BAC1B,QAAQ;4BACR,MAAM;4BACN,MAAM,EAAE,SAAS;4BACjB,OAAO,EAAE,KAAK;yBACf,CAAC;wBACF,oBAAM,WAAW,CAAA,CAAC;wBAClB,WAAW,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;wBAChF,SAAS;oBACX,CAAC;gBAGH,CAAC;;;;;;;;;YAED,4CAA4C;YAC5C,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;gBAC9B,eAAe,CAAC,IAAA,eAAI,EAAC,WAAW,CAAC,CAAC,CAAC;gBACnC,6BAAO;YACT,CAAC;YAED,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC7B,eAAe,CAAC,IAAA,kBAAO,EAAC,EAAE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;gBAC3E,6BAAO;YACT,CAAC;YAED,IAAI,YAAuC,CAAC;YAC5C,QAAQ,UAAU,CAAC,SAAS,EAAE,CAAC;gBAC7B,KAAK,WAAW;oBACd,YAAY,GAAG,0BAA0B,CAAC,eAAe,EAAE,WAAW,CAAC,CAAC;oBACxE,MAAM;gBACR,KAAK,QAAQ;oBACX,YAAY,GAAG,uBAAuB,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;oBACnE,MAAM;gBACR,KAAK,QAAQ;oBACX,YAAY,GAAG,uBAAuB,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;oBACjE,MAAM;gBACR,qFAAqF;gBACrF,OAAO,CAAC,CAAC,CAAC;oBACR,MAAM,WAAW,GAAU,UAAU,CAAC,SAAS,CAAC;oBAChD,eAAe,CAAC,IAAA,eAAI,EAAC,2BAA2B,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC;oBACxE,6BAAO;gBACT,CAAC;YACH,CAAC;YAED,eAAe,CAAC,IAAA,kBAAO,EAAC,EAAE,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;QAClE,CAAC;KAAA;IAED,OAAO,IAAA,kBAAO,EAAC;QACb,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,GAAG,EAAE,CAAC,cAAc,EAAE,EAAE;QAC1D,QAAQ;KACT,CAAC,CAAC;AACL,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 * Per-provider continuation message builders and the `executeClientToolTurn`\n * helper for orchestrating a single client-tool round-trip.\n *\n * Each provider requires a different wire format for the follow-up request:\n * - **Anthropic**: assistant turn reconstructed from the ordered accumulation\n * buffer (thinking / redacted_thinking / text / tool_use in original stream\n * order) + user turn with `tool_result` blocks. When thinking is active, the\n * follow-up request must NOT set `tool_choice: { type: 'any' }` or\n * `tool_choice: { type: 'tool', ... }` (E3 / §5.4 of b4-spike-findings).\n * - **OpenAI / xAI Responses API**: `function_call` input items +\n * `function_call_output` input items.\n * - **Gemini**: model turn with `functionCall` parts + user turn with\n * `functionResponse` parts (correlation by tool name).\n *\n * @packageDocumentation\n */\n\nimport { captureAsyncResult, fail, type Logging, Result, succeed } from '@fgv/ts-utils';\nimport { type JsonArray, type JsonObject } from '@fgv/ts-json-base';\n\nimport {\n type AiServerToolConfig,\n type AiToolConfig,\n type IAiClientTool,\n type IAiClientToolContinuation,\n type IAiClientToolTurnResult,\n type IAiStreamEvent,\n type IChatRequest,\n type IAiProviderDescriptor,\n resolveModel\n} from '../model';\nimport { type IResolvedThinkingConfig } from '../thinkingOptionsResolver';\nimport { splitChatRequest } from '../chatRequestBuilders';\nimport { resolveEffectiveBaseUrl } from '../endpoint';\nimport { type IAccumulatedBlock } from './anthropic';\nimport { type IAccumulatedFunctionCall } from './openaiResponses';\nimport { type IAccumulatedGeminiFunctionCall } from './gemini';\nimport { callAnthropicStream } from './anthropic';\nimport { callOpenAiResponsesStream } from './openaiResponses';\nimport { callGeminiStream } from './gemini';\nimport { type IStreamApiConfig } from './common';\n\n// ============================================================================\n// Tool-result accumulation (internal)\n// ============================================================================\n\n/**\n * Accumulated result for a single tool call, collected during stream iteration.\n * @internal\n */\ninterface IToolCallResult {\n readonly toolName: string;\n readonly callId?: string;\n readonly args: JsonObject;\n readonly result: string;\n readonly isError: boolean;\n}\n\n// ============================================================================\n// Anthropic continuation builder\n// ============================================================================\n\n/**\n * Builds the Anthropic follow-up messages for a client-tool round-trip.\n *\n * Reconstructs the assistant turn from the ordered accumulation buffer\n * (all block types in original stream order) and appends a user turn\n * with `tool_result` blocks for each executed tool call.\n *\n * **Constraint (E3):** The returned continuation does NOT include a forced\n * `tool_choice` field. When thinking is active, Anthropic rejects\n * `tool_choice: { type: 'any' }` and `tool_choice: { type: 'tool', ... }`\n * with an HTTP 400 error. Only `tool_choice: { type: 'auto' }` (the default,\n * i.e. omitted) is compatible with extended thinking.\n *\n * @internal\n */\nexport function buildAnthropicContinuation(\n accBuffer: Map<number, IAccumulatedBlock>,\n toolResults: IToolCallResult[]\n): IAiClientToolContinuation {\n // Reconstruct the assistant turn from the ordered accumulation buffer.\n // Sort by buffer key (SSE index) to restore original stream order.\n const sortedKeys = Array.from(accBuffer.keys()).sort((a, b) => a - b);\n const assistantContent: JsonArray = [];\n\n for (const key of sortedKeys) {\n const block = accBuffer.get(key);\n /* c8 ignore next 1 - defensive: key always exists in map since we iterate its keys */\n if (!block) continue;\n if (block.type === 'thinking') {\n assistantContent.push({\n type: 'thinking',\n thinking: block.thinking,\n signature: block.signature\n });\n } else if (block.type === 'redacted_thinking') {\n assistantContent.push({\n type: 'redacted_thinking',\n data: block.data\n });\n } else if (block.type === 'text') {\n if (block.text.length > 0) {\n assistantContent.push({ type: 'text', text: block.text });\n }\n } else if (block.type === 'tool_use') {\n let parsedInput: JsonObject;\n try {\n /* c8 ignore next 1 - defensive: argsBuffer is JSON-parsed in the adapter before emitting client-tool-call-done */\n parsedInput = JSON.parse(block.argsBuffer || '{}') as JsonObject;\n /* c8 ignore start - defensive: malformed argsBuffer defaults to empty object */\n } catch {\n parsedInput = {};\n }\n /* c8 ignore stop */\n assistantContent.push({\n type: 'tool_use',\n id: block.id,\n name: block.name,\n input: parsedInput\n });\n }\n }\n\n // Build user turn with tool_result blocks for each tool call.\n const userContent: JsonArray = toolResults.map((r): JsonObject => {\n const block: JsonObject = {\n type: 'tool_result',\n // eslint-disable-next-line @typescript-eslint/naming-convention\n tool_use_id: r.callId ?? r.toolName,\n content: r.result\n };\n if (r.isError) {\n return { ...block, is_error: true };\n }\n return block;\n });\n\n const assistantMessage: JsonObject = {\n role: 'assistant',\n content: assistantContent\n };\n const userMessage: JsonObject = {\n role: 'user',\n content: userContent\n };\n\n return {\n messages: [assistantMessage, userMessage],\n toolCallsSummary: toolResults.map((r) => ({\n toolName: r.toolName,\n callId: r.callId,\n args: r.args,\n result: r.result,\n isError: r.isError\n }))\n };\n}\n\n// ============================================================================\n// OpenAI / xAI Responses API continuation builder\n// ============================================================================\n\n/**\n * Builds the OpenAI / xAI Responses API follow-up input items for a\n * client-tool round-trip.\n *\n * Emits `function_call` items (the model's call) followed by\n * `function_call_output` items (the harness execution result), one pair\n * per executed tool call.\n *\n * @internal\n */\nexport function buildOpenAiContinuation(\n calls: Map<string, IAccumulatedFunctionCall>,\n toolResults: IToolCallResult[]\n): IAiClientToolContinuation {\n const items: JsonObject[] = [];\n\n // Emit function_call items for each call (model's side). Per the Responses API spec\n // (ResponseFunctionToolCall), `call_id` is the required correlation field — it must\n // match the matching function_call_output's `call_id` below. The optional `id` field\n // is the output-item id (`fc_*`) used to reference the streamed item; we omit it\n // because it is not load-bearing for input items.\n for (const [callId, call] of calls) {\n items.push({\n type: 'function_call',\n call_id: callId,\n name: call.name,\n arguments: call.argsBuffer\n });\n }\n\n // Emit function_call_output items (harness execution results).\n for (const r of toolResults) {\n items.push({\n type: 'function_call_output',\n call_id: r.callId ?? r.toolName,\n output: r.result\n });\n }\n\n return {\n messages: items,\n toolCallsSummary: toolResults.map((r) => ({\n toolName: r.toolName,\n callId: r.callId,\n args: r.args,\n result: r.result,\n isError: r.isError\n }))\n };\n}\n\n// ============================================================================\n// Gemini continuation builder\n// ============================================================================\n\n/**\n * Builds the Gemini follow-up contents for a client-tool round-trip.\n *\n * Emits a model turn with `functionCall` parts (one per tool call) and a\n * user turn with `functionResponse` parts (correlation by tool name, since\n * Gemini does not assign call IDs).\n *\n * @internal\n */\nexport function buildGeminiContinuation(\n calls: IAccumulatedGeminiFunctionCall[],\n toolResults: IToolCallResult[]\n): IAiClientToolContinuation {\n // Model turn: functionCall parts for each call.\n const modelParts: JsonArray = calls.map(\n (call): JsonObject => ({\n functionCall: {\n name: call.name,\n args: call.args\n }\n })\n );\n\n // User turn: functionResponse parts for each executed result.\n // Correlation is by name since Gemini has no call IDs.\n const userParts: JsonArray = toolResults.map(\n (r): JsonObject => ({\n functionResponse: {\n name: r.toolName,\n response: {\n content: r.result,\n ...(r.isError ? { error: true } : {})\n }\n }\n })\n );\n\n const modelMessage: JsonObject = {\n role: 'model',\n parts: modelParts\n };\n const userMessage: JsonObject = {\n role: 'user',\n parts: userParts\n };\n\n return {\n messages: [modelMessage, userMessage],\n toolCallsSummary: toolResults.map((r) => ({\n toolName: r.toolName,\n callId: r.callId,\n args: r.args,\n result: r.result,\n isError: r.isError\n }))\n };\n}\n\n// ============================================================================\n// executeClientToolTurn parameters\n// ============================================================================\n\n/**\n * Parameters for {@link AiAssist.executeClientToolTurn}.\n *\n * @remarks\n * Carries the unified {@link AiAssist.IChatRequest} shape (`system?` + ordered\n * `messages`): the last message is the current user turn and the preceding\n * messages are history, linearized before the current turn — identically to the\n * completion and streaming paths. {@link IExecuteClientToolTurnParams.continuationMessages}\n * remains a distinct post-current-turn axis (see below).\n *\n * @public\n */\nexport interface IExecuteClientToolTurnParams extends IChatRequest {\n /** The provider descriptor for routing (Anthropic / OpenAI / Gemini). */\n readonly descriptor: IAiProviderDescriptor;\n /** API key for authentication. */\n readonly apiKey: string;\n /**\n * Provider-specific continuation messages to append after the current user\n * message. Used to supply the output of {@link AiAssist.IAiClientToolContinuation}'s\n * `messages` field from a prior turn back to the provider in the follow-up request.\n *\n * Each provider applies its own shape guard to the supplied wire objects:\n * - Anthropic: projects each entry to `{ role, content }` (sufficient for\n * thinking blocks and `tool_result` arrays).\n * - OpenAI / xAI Responses: passes each item verbatim (`function_call` /\n * `function_call_output` items carry distinct fields per `type`); only guards\n * that each entry is a JSON object.\n * - Gemini: projects each entry to `{ role, parts }`.\n *\n * Entries that fail their provider's shape check are silently skipped.\n */\n readonly continuationMessages?: ReadonlyArray<JsonObject>;\n /** Temperature (default: 0.7). */\n readonly temperature?: number;\n /** Server-side tools to include. */\n readonly tools?: ReadonlyArray<AiServerToolConfig>;\n /** Client-defined tools available for the model to call. */\n readonly clientTools: ReadonlyArray<IAiClientTool>;\n /** Optional abort signal. */\n readonly signal?: AbortSignal;\n /**\n * Optional override of the descriptor's default base URL. Same semantics as\n * the non-streaming completion path and `callProviderCompletionStream`: a\n * well-formed `http`/`https` URL is substituted for `descriptor.baseUrl`\n * when composing the per-format request, with the per-format suffix appended\n * unchanged. Validated at the dispatcher; auth shape is unaffected. Use this\n * to point a client-tool turn at a local / LAN OpenAI-compatible server\n * (Ollama, LM Studio, llama.cpp).\n */\n readonly endpoint?: string;\n /** Optional logger for diagnostics. */\n readonly logger?: Logging.ILogger;\n /** Optional resolved thinking config (pre-resolved by the caller). */\n readonly resolvedThinking?: IResolvedThinkingConfig;\n /** Resolved model string (pre-resolved by the caller). When omitted, uses the descriptor's default model. */\n readonly model?: string;\n}\n\n/**\n * Return value of {@link AiAssist.executeClientToolTurn}.\n * @public\n */\nexport interface IExecuteClientToolTurnResult {\n /**\n * The unified-event iterable. Callers iterate this to drive the streaming UI.\n * The iterable forwards `text-delta`, `tool-event`, `client-tool-call-start`,\n * `client-tool-call-done`, and `client-tool-result` events through.\n */\n readonly events: AsyncIterable<IAiStreamEvent>;\n /**\n * Resolves when the stream terminates. On success, carries the\n * {@link AiAssist.IAiClientToolTurnResult} with the optional continuation for the\n * next round. On failure, carries the error message.\n */\n readonly nextTurn: Promise<Result<IAiClientToolTurnResult>>;\n}\n\n// ============================================================================\n// executeClientToolTurn\n// ============================================================================\n\n/**\n * Orchestrates a single client-tool streaming turn for any supported provider.\n *\n * Starts a streaming request, iterates the underlying provider stream, and:\n * - Forwards `text-delta`, `tool-event`, `client-tool-call-start`, and\n * `client-tool-call-done` events through to the consumer.\n * - For each `client-tool-call-done` event: validates the raw args against the\n * tool's `parametersSchema`, invokes `execute(typedArgs)`, and emits a\n * `client-tool-result` event.\n * - After stream completion: builds the per-provider continuation (or\n * `{ continuation: undefined }` when no tool calls occurred) and resolves\n * `nextTurn`.\n *\n * **Anthropic constraint (E3):** The continuation for Anthropic does not set\n * a forced `tool_choice`. Only `tool_choice: 'auto'` (the default, i.e.\n * omitted) is compatible with extended thinking.\n *\n * @param params - Turn parameters\n * @returns `{ events, nextTurn }` — stream iterable + completion promise\n * @public\n */\nexport function executeClientToolTurn(\n params: IExecuteClientToolTurnParams\n): Result<IExecuteClientToolTurnResult> {\n const {\n descriptor,\n apiKey,\n system,\n messages,\n continuationMessages,\n temperature,\n tools,\n clientTools,\n signal,\n logger,\n resolvedThinking,\n model,\n endpoint\n } = params;\n\n const splitResult = splitChatRequest(system, messages);\n if (splitResult.isFailure()) {\n return fail(splitResult.message);\n }\n const { prompt, head } = splitResult.value;\n if (prompt.attachments.length > 0 && !descriptor.acceptsImageInput) {\n return fail(`provider \"${descriptor.id}\" does not accept image input`);\n }\n\n // Build a lookup map of client tools by name for fast access.\n // Fail fast on duplicate names — silently overwriting would cause one tool\n // to shadow another with no observable signal.\n const toolsByName = new Map<string, IAiClientTool>();\n for (const tool of clientTools) {\n if (toolsByName.has(tool.config.name)) {\n return fail(`executeClientToolTurn: duplicate client tool name '${tool.config.name}'`);\n }\n toolsByName.set(tool.config.name, tool);\n }\n\n // Merge server tools and client tool configs into a single array for the provider.\n // This is the fix for P1-1: client tools were never sent to the provider because\n // the adapters only received `tools` (server tools). Both must coexist per design §2.5.\n const effectiveTools: ReadonlyArray<AiToolConfig> | undefined =\n clientTools.length > 0 ? [...(tools ?? []), ...clientTools.map((t) => t.config)] : tools;\n\n const effectiveTemperature = temperature ?? 0.7;\n const resolvedModel = model ?? resolveModel(descriptor.defaultModel);\n if (resolvedModel.length === 0) {\n return fail(`provider \"${descriptor.id}\": no model resolved; pass model or set descriptor.defaultModel`);\n }\n const baseUrlResult = resolveEffectiveBaseUrl(descriptor, endpoint);\n if (baseUrlResult.isFailure()) {\n return fail(baseUrlResult.message);\n }\n const config: IStreamApiConfig = {\n baseUrl: baseUrlResult.value,\n apiKey,\n model: resolvedModel\n };\n\n // Accumulation buffers — populated by the adapter, read by the builder.\n const anthropicBuffer = new Map<number, IAccumulatedBlock>();\n const openAiCallMap = new Map<string, IAccumulatedFunctionCall>();\n const geminiCalls: IAccumulatedGeminiFunctionCall[] = [];\n\n // Collected tool results, populated as each client-tool-call-done is processed.\n const toolResults: IToolCallResult[] = [];\n\n // Stream start: open the underlying adapter stream.\n const streamPromise: Promise<Result<AsyncIterable<IAiStreamEvent>>> = (() => {\n switch (descriptor.apiFormat) {\n case 'anthropic':\n return callAnthropicStream(\n config,\n prompt,\n head,\n effectiveTemperature,\n effectiveTools,\n logger,\n signal,\n resolvedThinking,\n anthropicBuffer,\n continuationMessages\n );\n case 'openai':\n return callOpenAiResponsesStream(\n config,\n prompt,\n /* c8 ignore next 1 - defensive: openai path requires tools; empty array fallback unreachable in practice */\n effectiveTools ?? [],\n head,\n effectiveTemperature,\n logger,\n signal,\n resolvedThinking,\n openAiCallMap,\n continuationMessages\n );\n case 'gemini':\n return callGeminiStream(\n config,\n prompt,\n head,\n effectiveTemperature,\n effectiveTools,\n logger,\n signal,\n resolvedThinking,\n geminiCalls,\n continuationMessages\n );\n /* c8 ignore next 4 - defensive coding: exhaustive switch guaranteed by TypeScript */\n default: {\n const _exhaustive: never = descriptor.apiFormat;\n return Promise.resolve(fail(`unsupported API format: ${String(_exhaustive)}`));\n }\n }\n })();\n\n // Resolve controls for `nextTurn`.\n let resolveNextTurn!: (result: Result<IAiClientToolTurnResult>) => void;\n const nextTurn = new Promise<Result<IAiClientToolTurnResult>>((resolve) => {\n resolveNextTurn = resolve;\n });\n\n // The unified-event generator: opens the stream, proxies events, executes tools.\n async function* eventGenerator(): AsyncGenerator<IAiStreamEvent> {\n const streamResult = await streamPromise;\n if (streamResult.isFailure()) {\n resolveNextTurn(fail(streamResult.message));\n yield { type: 'error', message: streamResult.message };\n return;\n }\n\n let truncated = false;\n let fullText = '';\n let streamError: string | undefined;\n\n for await (const event of streamResult.value) {\n if (event.type === 'done') {\n truncated = event.truncated;\n fullText = event.fullText;\n yield event;\n continue;\n }\n\n if (event.type === 'error') {\n streamError = event.message;\n yield event;\n continue;\n }\n\n if (event.type === 'text-delta') {\n yield event;\n continue;\n }\n\n if (event.type === 'tool-event') {\n yield event;\n continue;\n }\n\n if (event.type === 'client-tool-call-start') {\n yield event;\n continue;\n }\n\n if (event.type === 'client-tool-call-done') {\n yield event;\n\n const { toolName, callId, args } = event;\n const tool = toolsByName.get(toolName);\n\n if (!tool) {\n const errMsg = `model called unknown tool: ${toolName}`;\n const resultEvent: IAiStreamEvent = {\n type: 'client-tool-result',\n toolName,\n callId,\n result: errMsg,\n isError: true\n };\n yield resultEvent;\n toolResults.push({ toolName, callId, args, result: errMsg, isError: true });\n resolveNextTurn(fail(errMsg));\n return;\n }\n\n const validationResult = tool.config.parametersSchema.validate(args);\n if (validationResult.isFailure()) {\n const errMsg = `${toolName} (callId=${callId}): ${validationResult.message}`;\n const resultEvent: IAiStreamEvent = {\n type: 'client-tool-result',\n toolName,\n callId,\n result: errMsg,\n isError: true\n };\n yield resultEvent;\n toolResults.push({ toolName, callId, args, result: errMsg, isError: true });\n continue;\n }\n\n const executeResult = await captureAsyncResult(async () => tool.execute(validationResult.value));\n const executionResult: Result<unknown> = executeResult.isSuccess()\n ? executeResult.value\n : executeResult;\n\n if (executionResult.isFailure()) {\n const errMsg = `${toolName} (callId=${callId}): ${executionResult.message}`;\n const resultEvent: IAiStreamEvent = {\n type: 'client-tool-result',\n toolName,\n callId,\n result: errMsg,\n isError: true\n };\n yield resultEvent;\n toolResults.push({ toolName, callId, args, result: errMsg, isError: true });\n continue;\n }\n\n // JSON.stringify can throw (circular references) or return undefined\n // (e.g. for a bare `undefined` value, or a value of type `function`).\n // Either outcome violates the client-tool-result event contract, so\n // emit an isError result with diagnostic context instead.\n let resultStr: string;\n try {\n const stringified = JSON.stringify(executionResult.value);\n if (stringified === undefined) {\n const errMsg = `${toolName} (callId=${callId}): tool returned a non-serializable value (JSON.stringify produced undefined)`;\n const resultEvent: IAiStreamEvent = {\n type: 'client-tool-result',\n toolName,\n callId,\n result: errMsg,\n isError: true\n };\n yield resultEvent;\n toolResults.push({ toolName, callId, args, result: errMsg, isError: true });\n continue;\n }\n resultStr = stringified;\n } catch (e) {\n const errMsg = `${toolName} (callId=${callId}): failed to serialize tool result: ${\n (e as Error).message\n }`;\n const resultEvent: IAiStreamEvent = {\n type: 'client-tool-result',\n toolName,\n callId,\n result: errMsg,\n isError: true\n };\n yield resultEvent;\n toolResults.push({ toolName, callId, args, result: errMsg, isError: true });\n continue;\n }\n const resultEvent: IAiStreamEvent = {\n type: 'client-tool-result',\n toolName,\n callId,\n result: resultStr,\n isError: false\n };\n yield resultEvent;\n toolResults.push({ toolName, callId, args, result: resultStr, isError: false });\n continue;\n }\n\n // client-tool-result events are emitted by this layer, not the adapters — no passthrough needed.\n }\n\n // Stream has ended. Build the continuation.\n if (streamError !== undefined) {\n resolveNextTurn(fail(streamError));\n return;\n }\n\n if (toolResults.length === 0) {\n resolveNextTurn(succeed({ continuation: undefined, truncated, fullText }));\n return;\n }\n\n let continuation: IAiClientToolContinuation;\n switch (descriptor.apiFormat) {\n case 'anthropic':\n continuation = buildAnthropicContinuation(anthropicBuffer, toolResults);\n break;\n case 'openai':\n continuation = buildOpenAiContinuation(openAiCallMap, toolResults);\n break;\n case 'gemini':\n continuation = buildGeminiContinuation(geminiCalls, toolResults);\n break;\n /* c8 ignore next 5 - defensive coding: exhaustive switch guaranteed by TypeScript */\n default: {\n const _exhaustive: never = descriptor.apiFormat;\n resolveNextTurn(fail(`unsupported API format: ${String(_exhaustive)}`));\n return;\n }\n }\n\n resolveNextTurn(succeed({ continuation, truncated, fullText }));\n }\n\n return succeed({\n events: { [Symbol.asyncIterator]: () => eventGenerator() },\n nextTurn\n });\n}\n"]}