@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
@@ -0,0 +1,511 @@
1
+ // Copyright (c) 2026 Erik Fortune
2
+ //
3
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ // of this software and associated documentation files (the "Software"), to deal
5
+ // in the Software without restriction, including without limitation the rights
6
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ // copies of the Software, and to permit persons to whom the Software is
8
+ // furnished to do so, subject to the following conditions:
9
+ //
10
+ // The above copyright notice and this permission notice shall be included in all
11
+ // copies or substantial portions of the Software.
12
+ //
13
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ // SOFTWARE.
20
+ var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
21
+ var __asyncValues = (this && this.__asyncValues) || function (o) {
22
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
23
+ var m = o[Symbol.asyncIterator], i;
24
+ 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);
25
+ 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); }); }; }
26
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
27
+ };
28
+ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
29
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
30
+ var g = generator.apply(thisArg, _arguments || []), i, q = [];
31
+ return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
32
+ function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
33
+ 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]); } }
34
+ function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
35
+ function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
36
+ function fulfill(value) { resume("next", value); }
37
+ function reject(value) { resume("throw", value); }
38
+ function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
39
+ };
40
+ /**
41
+ * Per-provider continuation message builders and the `executeClientToolTurn`
42
+ * helper for orchestrating a single client-tool round-trip.
43
+ *
44
+ * Each provider requires a different wire format for the follow-up request:
45
+ * - **Anthropic**: assistant turn reconstructed from the ordered accumulation
46
+ * buffer (thinking / redacted_thinking / text / tool_use in original stream
47
+ * order) + user turn with `tool_result` blocks. When thinking is active, the
48
+ * follow-up request must NOT set `tool_choice: { type: 'any' }` or
49
+ * `tool_choice: { type: 'tool', ... }` (E3 / §5.4 of b4-spike-findings).
50
+ * - **OpenAI / xAI Responses API**: `function_call` input items +
51
+ * `function_call_output` input items.
52
+ * - **Gemini**: model turn with `functionCall` parts + user turn with
53
+ * `functionResponse` parts (correlation by tool name).
54
+ *
55
+ * @packageDocumentation
56
+ */
57
+ import { captureAsyncResult, fail, succeed } from '@fgv/ts-utils';
58
+ import { resolveModel } from '../model';
59
+ import { callAnthropicStream } from './anthropic';
60
+ import { callOpenAiResponsesStream } from './openaiResponses';
61
+ import { callGeminiStream } from './gemini';
62
+ // ============================================================================
63
+ // Anthropic continuation builder
64
+ // ============================================================================
65
+ /**
66
+ * Builds the Anthropic follow-up messages for a client-tool round-trip.
67
+ *
68
+ * Reconstructs the assistant turn from the ordered accumulation buffer
69
+ * (all block types in original stream order) and appends a user turn
70
+ * with `tool_result` blocks for each executed tool call.
71
+ *
72
+ * **Constraint (E3):** The returned continuation does NOT include a forced
73
+ * `tool_choice` field. When thinking is active, Anthropic rejects
74
+ * `tool_choice: { type: 'any' }` and `tool_choice: { type: 'tool', ... }`
75
+ * with an HTTP 400 error. Only `tool_choice: { type: 'auto' }` (the default,
76
+ * i.e. omitted) is compatible with extended thinking.
77
+ *
78
+ * @internal
79
+ */
80
+ export function buildAnthropicContinuation(accBuffer, toolResults) {
81
+ // Reconstruct the assistant turn from the ordered accumulation buffer.
82
+ // Sort by buffer key (SSE index) to restore original stream order.
83
+ const sortedKeys = Array.from(accBuffer.keys()).sort((a, b) => a - b);
84
+ const assistantContent = [];
85
+ for (const key of sortedKeys) {
86
+ const block = accBuffer.get(key);
87
+ /* c8 ignore next 1 - defensive: key always exists in map since we iterate its keys */
88
+ if (!block)
89
+ continue;
90
+ if (block.type === 'thinking') {
91
+ assistantContent.push({
92
+ type: 'thinking',
93
+ thinking: block.thinking,
94
+ signature: block.signature
95
+ });
96
+ }
97
+ else if (block.type === 'redacted_thinking') {
98
+ assistantContent.push({
99
+ type: 'redacted_thinking',
100
+ data: block.data
101
+ });
102
+ }
103
+ else if (block.type === 'text') {
104
+ if (block.text.length > 0) {
105
+ assistantContent.push({ type: 'text', text: block.text });
106
+ }
107
+ }
108
+ else if (block.type === 'tool_use') {
109
+ let parsedInput;
110
+ try {
111
+ /* c8 ignore next 1 - defensive: argsBuffer is JSON-parsed in the adapter before emitting client-tool-call-done */
112
+ parsedInput = JSON.parse(block.argsBuffer || '{}');
113
+ /* c8 ignore start - defensive: malformed argsBuffer defaults to empty object */
114
+ }
115
+ catch (_a) {
116
+ parsedInput = {};
117
+ }
118
+ /* c8 ignore stop */
119
+ assistantContent.push({
120
+ type: 'tool_use',
121
+ id: block.id,
122
+ name: block.name,
123
+ input: parsedInput
124
+ });
125
+ }
126
+ }
127
+ // Build user turn with tool_result blocks for each tool call.
128
+ const userContent = toolResults.map((r) => {
129
+ var _a;
130
+ const block = {
131
+ type: 'tool_result',
132
+ // eslint-disable-next-line @typescript-eslint/naming-convention
133
+ tool_use_id: (_a = r.callId) !== null && _a !== void 0 ? _a : r.toolName,
134
+ content: r.result
135
+ };
136
+ if (r.isError) {
137
+ return Object.assign(Object.assign({}, block), { is_error: true });
138
+ }
139
+ return block;
140
+ });
141
+ const assistantMessage = {
142
+ role: 'assistant',
143
+ content: assistantContent
144
+ };
145
+ const userMessage = {
146
+ role: 'user',
147
+ content: userContent
148
+ };
149
+ return {
150
+ messages: [assistantMessage, userMessage],
151
+ toolCallsSummary: toolResults.map((r) => ({
152
+ toolName: r.toolName,
153
+ callId: r.callId,
154
+ args: r.args,
155
+ result: r.result,
156
+ isError: r.isError
157
+ }))
158
+ };
159
+ }
160
+ // ============================================================================
161
+ // OpenAI / xAI Responses API continuation builder
162
+ // ============================================================================
163
+ /**
164
+ * Builds the OpenAI / xAI Responses API follow-up input items for a
165
+ * client-tool round-trip.
166
+ *
167
+ * Emits `function_call` items (the model's call) followed by
168
+ * `function_call_output` items (the harness execution result), one pair
169
+ * per executed tool call.
170
+ *
171
+ * @internal
172
+ */
173
+ export function buildOpenAiContinuation(calls, toolResults) {
174
+ var _a;
175
+ const items = [];
176
+ // Emit function_call items for each call (model's side). Per the Responses API spec
177
+ // (ResponseFunctionToolCall), `call_id` is the required correlation field — it must
178
+ // match the matching function_call_output's `call_id` below. The optional `id` field
179
+ // is the output-item id (`fc_*`) used to reference the streamed item; we omit it
180
+ // because it is not load-bearing for input items.
181
+ for (const [callId, call] of calls) {
182
+ items.push({
183
+ type: 'function_call',
184
+ call_id: callId,
185
+ name: call.name,
186
+ arguments: call.argsBuffer
187
+ });
188
+ }
189
+ // Emit function_call_output items (harness execution results).
190
+ for (const r of toolResults) {
191
+ items.push({
192
+ type: 'function_call_output',
193
+ call_id: (_a = r.callId) !== null && _a !== void 0 ? _a : r.toolName,
194
+ output: r.result
195
+ });
196
+ }
197
+ return {
198
+ messages: items,
199
+ toolCallsSummary: toolResults.map((r) => ({
200
+ toolName: r.toolName,
201
+ callId: r.callId,
202
+ args: r.args,
203
+ result: r.result,
204
+ isError: r.isError
205
+ }))
206
+ };
207
+ }
208
+ // ============================================================================
209
+ // Gemini continuation builder
210
+ // ============================================================================
211
+ /**
212
+ * Builds the Gemini follow-up contents for a client-tool round-trip.
213
+ *
214
+ * Emits a model turn with `functionCall` parts (one per tool call) and a
215
+ * user turn with `functionResponse` parts (correlation by tool name, since
216
+ * Gemini does not assign call IDs).
217
+ *
218
+ * @internal
219
+ */
220
+ export function buildGeminiContinuation(calls, toolResults) {
221
+ // Model turn: functionCall parts for each call.
222
+ const modelParts = calls.map((call) => ({
223
+ functionCall: {
224
+ name: call.name,
225
+ args: call.args
226
+ }
227
+ }));
228
+ // User turn: functionResponse parts for each executed result.
229
+ // Correlation is by name since Gemini has no call IDs.
230
+ const userParts = toolResults.map((r) => ({
231
+ functionResponse: {
232
+ name: r.toolName,
233
+ response: Object.assign({ content: r.result }, (r.isError ? { error: true } : {}))
234
+ }
235
+ }));
236
+ const modelMessage = {
237
+ role: 'model',
238
+ parts: modelParts
239
+ };
240
+ const userMessage = {
241
+ role: 'user',
242
+ parts: userParts
243
+ };
244
+ return {
245
+ messages: [modelMessage, userMessage],
246
+ toolCallsSummary: toolResults.map((r) => ({
247
+ toolName: r.toolName,
248
+ callId: r.callId,
249
+ args: r.args,
250
+ result: r.result,
251
+ isError: r.isError
252
+ }))
253
+ };
254
+ }
255
+ // ============================================================================
256
+ // executeClientToolTurn
257
+ // ============================================================================
258
+ /**
259
+ * Orchestrates a single client-tool streaming turn for any supported provider.
260
+ *
261
+ * Starts a streaming request, iterates the underlying provider stream, and:
262
+ * - Forwards `text-delta`, `tool-event`, `client-tool-call-start`, and
263
+ * `client-tool-call-done` events through to the consumer.
264
+ * - For each `client-tool-call-done` event: validates the raw args against the
265
+ * tool's `parametersSchema`, invokes `execute(typedArgs)`, and emits a
266
+ * `client-tool-result` event.
267
+ * - After stream completion: builds the per-provider continuation (or
268
+ * `{ continuation: undefined }` when no tool calls occurred) and resolves
269
+ * `nextTurn`.
270
+ *
271
+ * **Anthropic constraint (E3):** The continuation for Anthropic does not set
272
+ * a forced `tool_choice`. Only `tool_choice: 'auto'` (the default, i.e.
273
+ * omitted) is compatible with extended thinking.
274
+ *
275
+ * @param params - Turn parameters
276
+ * @returns `{ events, nextTurn }` — stream iterable + completion promise
277
+ * @public
278
+ */
279
+ export function executeClientToolTurn(params) {
280
+ const { descriptor, apiKey, prompt, messagesBefore, continuationMessages, temperature, tools, clientTools, signal, logger, resolvedThinking, model } = params;
281
+ // Build a lookup map of client tools by name for fast access.
282
+ // Fail fast on duplicate names — silently overwriting would cause one tool
283
+ // to shadow another with no observable signal.
284
+ const toolsByName = new Map();
285
+ for (const tool of clientTools) {
286
+ if (toolsByName.has(tool.config.name)) {
287
+ return fail(`executeClientToolTurn: duplicate client tool name '${tool.config.name}'`);
288
+ }
289
+ toolsByName.set(tool.config.name, tool);
290
+ }
291
+ // Merge server tools and client tool configs into a single array for the provider.
292
+ // This is the fix for P1-1: client tools were never sent to the provider because
293
+ // the adapters only received `tools` (server tools). Both must coexist per design §2.5.
294
+ const effectiveTools = clientTools.length > 0 ? [...(tools !== null && tools !== void 0 ? tools : []), ...clientTools.map((t) => t.config)] : tools;
295
+ const effectiveTemperature = temperature !== null && temperature !== void 0 ? temperature : 0.7;
296
+ const resolvedModel = model !== null && model !== void 0 ? model : resolveModel(descriptor.defaultModel);
297
+ const config = {
298
+ baseUrl: descriptor.baseUrl,
299
+ apiKey,
300
+ model: resolvedModel
301
+ };
302
+ // Accumulation buffers — populated by the adapter, read by the builder.
303
+ const anthropicBuffer = new Map();
304
+ const openAiCallMap = new Map();
305
+ const geminiCalls = [];
306
+ // Collected tool results, populated as each client-tool-call-done is processed.
307
+ const toolResults = [];
308
+ // Stream start: open the underlying adapter stream.
309
+ const streamPromise = (() => {
310
+ switch (descriptor.apiFormat) {
311
+ case 'anthropic':
312
+ return callAnthropicStream(config, prompt, messagesBefore, effectiveTemperature, effectiveTools, logger, signal, resolvedThinking, anthropicBuffer, continuationMessages);
313
+ case 'openai':
314
+ return callOpenAiResponsesStream(config, prompt,
315
+ /* c8 ignore next 1 - defensive: openai path requires tools; empty array fallback unreachable in practice */
316
+ effectiveTools !== null && effectiveTools !== void 0 ? effectiveTools : [], messagesBefore, effectiveTemperature, logger, signal, resolvedThinking, openAiCallMap, continuationMessages);
317
+ case 'gemini':
318
+ return callGeminiStream(config, prompt, messagesBefore, effectiveTemperature, effectiveTools, logger, signal, resolvedThinking, geminiCalls, continuationMessages);
319
+ /* c8 ignore next 4 - defensive coding: exhaustive switch guaranteed by TypeScript */
320
+ default: {
321
+ const _exhaustive = descriptor.apiFormat;
322
+ return Promise.resolve(fail(`unsupported API format: ${String(_exhaustive)}`));
323
+ }
324
+ }
325
+ })();
326
+ // Resolve controls for `nextTurn`.
327
+ let resolveNextTurn;
328
+ const nextTurn = new Promise((resolve) => {
329
+ resolveNextTurn = resolve;
330
+ });
331
+ // The unified-event generator: opens the stream, proxies events, executes tools.
332
+ function eventGenerator() {
333
+ return __asyncGenerator(this, arguments, function* eventGenerator_1() {
334
+ var _a, e_1, _b, _c;
335
+ const streamResult = yield __await(streamPromise);
336
+ if (streamResult.isFailure()) {
337
+ resolveNextTurn(fail(streamResult.message));
338
+ yield yield __await({ type: 'error', message: streamResult.message });
339
+ return yield __await(void 0);
340
+ }
341
+ let truncated = false;
342
+ let fullText = '';
343
+ let streamError;
344
+ try {
345
+ for (var _d = true, _e = __asyncValues(streamResult.value), _f; _f = yield __await(_e.next()), _a = _f.done, !_a; _d = true) {
346
+ _c = _f.value;
347
+ _d = false;
348
+ const event = _c;
349
+ if (event.type === 'done') {
350
+ truncated = event.truncated;
351
+ fullText = event.fullText;
352
+ yield yield __await(event);
353
+ continue;
354
+ }
355
+ if (event.type === 'error') {
356
+ streamError = event.message;
357
+ yield yield __await(event);
358
+ continue;
359
+ }
360
+ if (event.type === 'text-delta') {
361
+ yield yield __await(event);
362
+ continue;
363
+ }
364
+ if (event.type === 'tool-event') {
365
+ yield yield __await(event);
366
+ continue;
367
+ }
368
+ if (event.type === 'client-tool-call-start') {
369
+ yield yield __await(event);
370
+ continue;
371
+ }
372
+ if (event.type === 'client-tool-call-done') {
373
+ yield yield __await(event);
374
+ const { toolName, callId, args } = event;
375
+ const tool = toolsByName.get(toolName);
376
+ if (!tool) {
377
+ const errMsg = `model called unknown tool: ${toolName}`;
378
+ const resultEvent = {
379
+ type: 'client-tool-result',
380
+ toolName,
381
+ callId,
382
+ result: errMsg,
383
+ isError: true
384
+ };
385
+ yield yield __await(resultEvent);
386
+ toolResults.push({ toolName, callId, args, result: errMsg, isError: true });
387
+ resolveNextTurn(fail(errMsg));
388
+ return yield __await(void 0);
389
+ }
390
+ const validationResult = tool.config.parametersSchema.validate(args);
391
+ if (validationResult.isFailure()) {
392
+ const errMsg = `${toolName} (callId=${callId}): ${validationResult.message}`;
393
+ const resultEvent = {
394
+ type: 'client-tool-result',
395
+ toolName,
396
+ callId,
397
+ result: errMsg,
398
+ isError: true
399
+ };
400
+ yield yield __await(resultEvent);
401
+ toolResults.push({ toolName, callId, args, result: errMsg, isError: true });
402
+ continue;
403
+ }
404
+ const executeResult = yield __await(captureAsyncResult(async () => tool.execute(validationResult.value)));
405
+ const executionResult = executeResult.isSuccess()
406
+ ? executeResult.value
407
+ : executeResult;
408
+ if (executionResult.isFailure()) {
409
+ const errMsg = `${toolName} (callId=${callId}): ${executionResult.message}`;
410
+ const resultEvent = {
411
+ type: 'client-tool-result',
412
+ toolName,
413
+ callId,
414
+ result: errMsg,
415
+ isError: true
416
+ };
417
+ yield yield __await(resultEvent);
418
+ toolResults.push({ toolName, callId, args, result: errMsg, isError: true });
419
+ continue;
420
+ }
421
+ // JSON.stringify can throw (circular references) or return undefined
422
+ // (e.g. for a bare `undefined` value, or a value of type `function`).
423
+ // Either outcome violates the client-tool-result event contract, so
424
+ // emit an isError result with diagnostic context instead.
425
+ let resultStr;
426
+ try {
427
+ const stringified = JSON.stringify(executionResult.value);
428
+ if (stringified === undefined) {
429
+ const errMsg = `${toolName} (callId=${callId}): tool returned a non-serializable value (JSON.stringify produced undefined)`;
430
+ const resultEvent = {
431
+ type: 'client-tool-result',
432
+ toolName,
433
+ callId,
434
+ result: errMsg,
435
+ isError: true
436
+ };
437
+ yield yield __await(resultEvent);
438
+ toolResults.push({ toolName, callId, args, result: errMsg, isError: true });
439
+ continue;
440
+ }
441
+ resultStr = stringified;
442
+ }
443
+ catch (e) {
444
+ const errMsg = `${toolName} (callId=${callId}): failed to serialize tool result: ${e.message}`;
445
+ const resultEvent = {
446
+ type: 'client-tool-result',
447
+ toolName,
448
+ callId,
449
+ result: errMsg,
450
+ isError: true
451
+ };
452
+ yield yield __await(resultEvent);
453
+ toolResults.push({ toolName, callId, args, result: errMsg, isError: true });
454
+ continue;
455
+ }
456
+ const resultEvent = {
457
+ type: 'client-tool-result',
458
+ toolName,
459
+ callId,
460
+ result: resultStr,
461
+ isError: false
462
+ };
463
+ yield yield __await(resultEvent);
464
+ toolResults.push({ toolName, callId, args, result: resultStr, isError: false });
465
+ continue;
466
+ }
467
+ }
468
+ }
469
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
470
+ finally {
471
+ try {
472
+ if (!_d && !_a && (_b = _e.return)) yield __await(_b.call(_e));
473
+ }
474
+ finally { if (e_1) throw e_1.error; }
475
+ }
476
+ // Stream has ended. Build the continuation.
477
+ if (streamError !== undefined) {
478
+ resolveNextTurn(fail(streamError));
479
+ return yield __await(void 0);
480
+ }
481
+ if (toolResults.length === 0) {
482
+ resolveNextTurn(succeed({ continuation: undefined, truncated, fullText }));
483
+ return yield __await(void 0);
484
+ }
485
+ let continuation;
486
+ switch (descriptor.apiFormat) {
487
+ case 'anthropic':
488
+ continuation = buildAnthropicContinuation(anthropicBuffer, toolResults);
489
+ break;
490
+ case 'openai':
491
+ continuation = buildOpenAiContinuation(openAiCallMap, toolResults);
492
+ break;
493
+ case 'gemini':
494
+ continuation = buildGeminiContinuation(geminiCalls, toolResults);
495
+ break;
496
+ /* c8 ignore next 5 - defensive coding: exhaustive switch guaranteed by TypeScript */
497
+ default: {
498
+ const _exhaustive = descriptor.apiFormat;
499
+ resolveNextTurn(fail(`unsupported API format: ${String(_exhaustive)}`));
500
+ return yield __await(void 0);
501
+ }
502
+ }
503
+ resolveNextTurn(succeed({ continuation, truncated, fullText }));
504
+ });
505
+ }
506
+ return succeed({
507
+ events: { [Symbol.asyncIterator]: () => eventGenerator() },
508
+ nextTurn
509
+ });
510
+ }
511
+ //# sourceMappingURL=clientToolContinuationBuilder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clientToolContinuationBuilder.js","sourceRoot":"","sources":["../../../../src/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.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-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 AiPrompt,\n type AiServerToolConfig,\n type AiToolConfig,\n type IAiClientTool,\n type IAiClientToolContinuation,\n type IAiClientToolTurnResult,\n type IAiStreamEvent,\n type IChatMessage,\n type IAiProviderDescriptor,\n resolveModel\n} from '../model';\nimport { type IResolvedThinkingConfig } from '../thinkingOptionsResolver';\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 * @public\n */\nexport interface IExecuteClientToolTurnParams {\n /** The provider descriptor for routing (Anthropic / OpenAI / Gemini). */\n readonly descriptor: IAiProviderDescriptor;\n /** API key for authentication. */\n readonly apiKey: string;\n /** The structured prompt. */\n readonly prompt: AiPrompt;\n /** Prior conversation history (excluding the current turn). */\n readonly messagesBefore?: ReadonlyArray<IChatMessage>;\n /**\n * Provider-specific continuation messages to append after the prompt's 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 /** 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 prompt,\n messagesBefore,\n continuationMessages,\n temperature,\n tools,\n clientTools,\n signal,\n logger,\n resolvedThinking,\n model\n } = params;\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 const config: IStreamApiConfig = {\n baseUrl: descriptor.baseUrl,\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 messagesBefore,\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 messagesBefore,\n effectiveTemperature,\n logger,\n signal,\n resolvedThinking,\n openAiCallMap,\n continuationMessages\n );\n case 'gemini':\n return callGeminiStream(\n config,\n prompt,\n messagesBefore,\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"]}