@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,517 @@
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 anthropic_1 = require("./anthropic");
66
+ const openaiResponses_1 = require("./openaiResponses");
67
+ const gemini_1 = require("./gemini");
68
+ // ============================================================================
69
+ // Anthropic continuation builder
70
+ // ============================================================================
71
+ /**
72
+ * Builds the Anthropic follow-up messages for a client-tool round-trip.
73
+ *
74
+ * Reconstructs the assistant turn from the ordered accumulation buffer
75
+ * (all block types in original stream order) and appends a user turn
76
+ * with `tool_result` blocks for each executed tool call.
77
+ *
78
+ * **Constraint (E3):** The returned continuation does NOT include a forced
79
+ * `tool_choice` field. When thinking is active, Anthropic rejects
80
+ * `tool_choice: { type: 'any' }` and `tool_choice: { type: 'tool', ... }`
81
+ * with an HTTP 400 error. Only `tool_choice: { type: 'auto' }` (the default,
82
+ * i.e. omitted) is compatible with extended thinking.
83
+ *
84
+ * @internal
85
+ */
86
+ function buildAnthropicContinuation(accBuffer, toolResults) {
87
+ // Reconstruct the assistant turn from the ordered accumulation buffer.
88
+ // Sort by buffer key (SSE index) to restore original stream order.
89
+ const sortedKeys = Array.from(accBuffer.keys()).sort((a, b) => a - b);
90
+ const assistantContent = [];
91
+ for (const key of sortedKeys) {
92
+ const block = accBuffer.get(key);
93
+ /* c8 ignore next 1 - defensive: key always exists in map since we iterate its keys */
94
+ if (!block)
95
+ continue;
96
+ if (block.type === 'thinking') {
97
+ assistantContent.push({
98
+ type: 'thinking',
99
+ thinking: block.thinking,
100
+ signature: block.signature
101
+ });
102
+ }
103
+ else if (block.type === 'redacted_thinking') {
104
+ assistantContent.push({
105
+ type: 'redacted_thinking',
106
+ data: block.data
107
+ });
108
+ }
109
+ else if (block.type === 'text') {
110
+ if (block.text.length > 0) {
111
+ assistantContent.push({ type: 'text', text: block.text });
112
+ }
113
+ }
114
+ else if (block.type === 'tool_use') {
115
+ let parsedInput;
116
+ try {
117
+ /* c8 ignore next 1 - defensive: argsBuffer is JSON-parsed in the adapter before emitting client-tool-call-done */
118
+ parsedInput = JSON.parse(block.argsBuffer || '{}');
119
+ /* c8 ignore start - defensive: malformed argsBuffer defaults to empty object */
120
+ }
121
+ catch (_a) {
122
+ parsedInput = {};
123
+ }
124
+ /* c8 ignore stop */
125
+ assistantContent.push({
126
+ type: 'tool_use',
127
+ id: block.id,
128
+ name: block.name,
129
+ input: parsedInput
130
+ });
131
+ }
132
+ }
133
+ // Build user turn with tool_result blocks for each tool call.
134
+ const userContent = toolResults.map((r) => {
135
+ var _a;
136
+ const block = {
137
+ type: 'tool_result',
138
+ // eslint-disable-next-line @typescript-eslint/naming-convention
139
+ tool_use_id: (_a = r.callId) !== null && _a !== void 0 ? _a : r.toolName,
140
+ content: r.result
141
+ };
142
+ if (r.isError) {
143
+ return Object.assign(Object.assign({}, block), { is_error: true });
144
+ }
145
+ return block;
146
+ });
147
+ const assistantMessage = {
148
+ role: 'assistant',
149
+ content: assistantContent
150
+ };
151
+ const userMessage = {
152
+ role: 'user',
153
+ content: userContent
154
+ };
155
+ return {
156
+ messages: [assistantMessage, userMessage],
157
+ toolCallsSummary: toolResults.map((r) => ({
158
+ toolName: r.toolName,
159
+ callId: r.callId,
160
+ args: r.args,
161
+ result: r.result,
162
+ isError: r.isError
163
+ }))
164
+ };
165
+ }
166
+ // ============================================================================
167
+ // OpenAI / xAI Responses API continuation builder
168
+ // ============================================================================
169
+ /**
170
+ * Builds the OpenAI / xAI Responses API follow-up input items for a
171
+ * client-tool round-trip.
172
+ *
173
+ * Emits `function_call` items (the model's call) followed by
174
+ * `function_call_output` items (the harness execution result), one pair
175
+ * per executed tool call.
176
+ *
177
+ * @internal
178
+ */
179
+ function buildOpenAiContinuation(calls, toolResults) {
180
+ var _a;
181
+ const items = [];
182
+ // Emit function_call items for each call (model's side). Per the Responses API spec
183
+ // (ResponseFunctionToolCall), `call_id` is the required correlation field — it must
184
+ // match the matching function_call_output's `call_id` below. The optional `id` field
185
+ // is the output-item id (`fc_*`) used to reference the streamed item; we omit it
186
+ // because it is not load-bearing for input items.
187
+ for (const [callId, call] of calls) {
188
+ items.push({
189
+ type: 'function_call',
190
+ call_id: callId,
191
+ name: call.name,
192
+ arguments: call.argsBuffer
193
+ });
194
+ }
195
+ // Emit function_call_output items (harness execution results).
196
+ for (const r of toolResults) {
197
+ items.push({
198
+ type: 'function_call_output',
199
+ call_id: (_a = r.callId) !== null && _a !== void 0 ? _a : r.toolName,
200
+ output: r.result
201
+ });
202
+ }
203
+ return {
204
+ messages: items,
205
+ toolCallsSummary: toolResults.map((r) => ({
206
+ toolName: r.toolName,
207
+ callId: r.callId,
208
+ args: r.args,
209
+ result: r.result,
210
+ isError: r.isError
211
+ }))
212
+ };
213
+ }
214
+ // ============================================================================
215
+ // Gemini continuation builder
216
+ // ============================================================================
217
+ /**
218
+ * Builds the Gemini follow-up contents for a client-tool round-trip.
219
+ *
220
+ * Emits a model turn with `functionCall` parts (one per tool call) and a
221
+ * user turn with `functionResponse` parts (correlation by tool name, since
222
+ * Gemini does not assign call IDs).
223
+ *
224
+ * @internal
225
+ */
226
+ function buildGeminiContinuation(calls, toolResults) {
227
+ // Model turn: functionCall parts for each call.
228
+ const modelParts = calls.map((call) => ({
229
+ functionCall: {
230
+ name: call.name,
231
+ args: call.args
232
+ }
233
+ }));
234
+ // User turn: functionResponse parts for each executed result.
235
+ // Correlation is by name since Gemini has no call IDs.
236
+ const userParts = toolResults.map((r) => ({
237
+ functionResponse: {
238
+ name: r.toolName,
239
+ response: Object.assign({ content: r.result }, (r.isError ? { error: true } : {}))
240
+ }
241
+ }));
242
+ const modelMessage = {
243
+ role: 'model',
244
+ parts: modelParts
245
+ };
246
+ const userMessage = {
247
+ role: 'user',
248
+ parts: userParts
249
+ };
250
+ return {
251
+ messages: [modelMessage, userMessage],
252
+ toolCallsSummary: toolResults.map((r) => ({
253
+ toolName: r.toolName,
254
+ callId: r.callId,
255
+ args: r.args,
256
+ result: r.result,
257
+ isError: r.isError
258
+ }))
259
+ };
260
+ }
261
+ // ============================================================================
262
+ // executeClientToolTurn
263
+ // ============================================================================
264
+ /**
265
+ * Orchestrates a single client-tool streaming turn for any supported provider.
266
+ *
267
+ * Starts a streaming request, iterates the underlying provider stream, and:
268
+ * - Forwards `text-delta`, `tool-event`, `client-tool-call-start`, and
269
+ * `client-tool-call-done` events through to the consumer.
270
+ * - For each `client-tool-call-done` event: validates the raw args against the
271
+ * tool's `parametersSchema`, invokes `execute(typedArgs)`, and emits a
272
+ * `client-tool-result` event.
273
+ * - After stream completion: builds the per-provider continuation (or
274
+ * `{ continuation: undefined }` when no tool calls occurred) and resolves
275
+ * `nextTurn`.
276
+ *
277
+ * **Anthropic constraint (E3):** The continuation for Anthropic does not set
278
+ * a forced `tool_choice`. Only `tool_choice: 'auto'` (the default, i.e.
279
+ * omitted) is compatible with extended thinking.
280
+ *
281
+ * @param params - Turn parameters
282
+ * @returns `{ events, nextTurn }` — stream iterable + completion promise
283
+ * @public
284
+ */
285
+ function executeClientToolTurn(params) {
286
+ const { descriptor, apiKey, prompt, messagesBefore, continuationMessages, temperature, tools, clientTools, signal, logger, resolvedThinking, model } = params;
287
+ // Build a lookup map of client tools by name for fast access.
288
+ // Fail fast on duplicate names — silently overwriting would cause one tool
289
+ // to shadow another with no observable signal.
290
+ const toolsByName = new Map();
291
+ for (const tool of clientTools) {
292
+ if (toolsByName.has(tool.config.name)) {
293
+ return (0, ts_utils_1.fail)(`executeClientToolTurn: duplicate client tool name '${tool.config.name}'`);
294
+ }
295
+ toolsByName.set(tool.config.name, tool);
296
+ }
297
+ // Merge server tools and client tool configs into a single array for the provider.
298
+ // This is the fix for P1-1: client tools were never sent to the provider because
299
+ // the adapters only received `tools` (server tools). Both must coexist per design §2.5.
300
+ const effectiveTools = clientTools.length > 0 ? [...(tools !== null && tools !== void 0 ? tools : []), ...clientTools.map((t) => t.config)] : tools;
301
+ const effectiveTemperature = temperature !== null && temperature !== void 0 ? temperature : 0.7;
302
+ const resolvedModel = model !== null && model !== void 0 ? model : (0, model_1.resolveModel)(descriptor.defaultModel);
303
+ const config = {
304
+ baseUrl: descriptor.baseUrl,
305
+ apiKey,
306
+ model: resolvedModel
307
+ };
308
+ // Accumulation buffers — populated by the adapter, read by the builder.
309
+ const anthropicBuffer = new Map();
310
+ const openAiCallMap = new Map();
311
+ const geminiCalls = [];
312
+ // Collected tool results, populated as each client-tool-call-done is processed.
313
+ const toolResults = [];
314
+ // Stream start: open the underlying adapter stream.
315
+ const streamPromise = (() => {
316
+ switch (descriptor.apiFormat) {
317
+ case 'anthropic':
318
+ return (0, anthropic_1.callAnthropicStream)(config, prompt, messagesBefore, effectiveTemperature, effectiveTools, logger, signal, resolvedThinking, anthropicBuffer, continuationMessages);
319
+ case 'openai':
320
+ return (0, openaiResponses_1.callOpenAiResponsesStream)(config, prompt,
321
+ /* c8 ignore next 1 - defensive: openai path requires tools; empty array fallback unreachable in practice */
322
+ effectiveTools !== null && effectiveTools !== void 0 ? effectiveTools : [], messagesBefore, effectiveTemperature, logger, signal, resolvedThinking, openAiCallMap, continuationMessages);
323
+ case 'gemini':
324
+ return (0, gemini_1.callGeminiStream)(config, prompt, messagesBefore, effectiveTemperature, effectiveTools, logger, signal, resolvedThinking, geminiCalls, continuationMessages);
325
+ /* c8 ignore next 4 - defensive coding: exhaustive switch guaranteed by TypeScript */
326
+ default: {
327
+ const _exhaustive = descriptor.apiFormat;
328
+ return Promise.resolve((0, ts_utils_1.fail)(`unsupported API format: ${String(_exhaustive)}`));
329
+ }
330
+ }
331
+ })();
332
+ // Resolve controls for `nextTurn`.
333
+ let resolveNextTurn;
334
+ const nextTurn = new Promise((resolve) => {
335
+ resolveNextTurn = resolve;
336
+ });
337
+ // The unified-event generator: opens the stream, proxies events, executes tools.
338
+ function eventGenerator() {
339
+ return __asyncGenerator(this, arguments, function* eventGenerator_1() {
340
+ var _a, e_1, _b, _c;
341
+ const streamResult = yield __await(streamPromise);
342
+ if (streamResult.isFailure()) {
343
+ resolveNextTurn((0, ts_utils_1.fail)(streamResult.message));
344
+ yield yield __await({ type: 'error', message: streamResult.message });
345
+ return yield __await(void 0);
346
+ }
347
+ let truncated = false;
348
+ let fullText = '';
349
+ let streamError;
350
+ try {
351
+ for (var _d = true, _e = __asyncValues(streamResult.value), _f; _f = yield __await(_e.next()), _a = _f.done, !_a; _d = true) {
352
+ _c = _f.value;
353
+ _d = false;
354
+ const event = _c;
355
+ if (event.type === 'done') {
356
+ truncated = event.truncated;
357
+ fullText = event.fullText;
358
+ yield yield __await(event);
359
+ continue;
360
+ }
361
+ if (event.type === 'error') {
362
+ streamError = event.message;
363
+ yield yield __await(event);
364
+ continue;
365
+ }
366
+ if (event.type === 'text-delta') {
367
+ yield yield __await(event);
368
+ continue;
369
+ }
370
+ if (event.type === 'tool-event') {
371
+ yield yield __await(event);
372
+ continue;
373
+ }
374
+ if (event.type === 'client-tool-call-start') {
375
+ yield yield __await(event);
376
+ continue;
377
+ }
378
+ if (event.type === 'client-tool-call-done') {
379
+ yield yield __await(event);
380
+ const { toolName, callId, args } = event;
381
+ const tool = toolsByName.get(toolName);
382
+ if (!tool) {
383
+ const errMsg = `model called unknown tool: ${toolName}`;
384
+ const resultEvent = {
385
+ type: 'client-tool-result',
386
+ toolName,
387
+ callId,
388
+ result: errMsg,
389
+ isError: true
390
+ };
391
+ yield yield __await(resultEvent);
392
+ toolResults.push({ toolName, callId, args, result: errMsg, isError: true });
393
+ resolveNextTurn((0, ts_utils_1.fail)(errMsg));
394
+ return yield __await(void 0);
395
+ }
396
+ const validationResult = tool.config.parametersSchema.validate(args);
397
+ if (validationResult.isFailure()) {
398
+ const errMsg = `${toolName} (callId=${callId}): ${validationResult.message}`;
399
+ const resultEvent = {
400
+ type: 'client-tool-result',
401
+ toolName,
402
+ callId,
403
+ result: errMsg,
404
+ isError: true
405
+ };
406
+ yield yield __await(resultEvent);
407
+ toolResults.push({ toolName, callId, args, result: errMsg, isError: true });
408
+ continue;
409
+ }
410
+ const executeResult = yield __await((0, ts_utils_1.captureAsyncResult)(async () => tool.execute(validationResult.value)));
411
+ const executionResult = executeResult.isSuccess()
412
+ ? executeResult.value
413
+ : executeResult;
414
+ if (executionResult.isFailure()) {
415
+ const errMsg = `${toolName} (callId=${callId}): ${executionResult.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
+ // JSON.stringify can throw (circular references) or return undefined
428
+ // (e.g. for a bare `undefined` value, or a value of type `function`).
429
+ // Either outcome violates the client-tool-result event contract, so
430
+ // emit an isError result with diagnostic context instead.
431
+ let resultStr;
432
+ try {
433
+ const stringified = JSON.stringify(executionResult.value);
434
+ if (stringified === undefined) {
435
+ const errMsg = `${toolName} (callId=${callId}): tool returned a non-serializable value (JSON.stringify produced undefined)`;
436
+ const resultEvent = {
437
+ type: 'client-tool-result',
438
+ toolName,
439
+ callId,
440
+ result: errMsg,
441
+ isError: true
442
+ };
443
+ yield yield __await(resultEvent);
444
+ toolResults.push({ toolName, callId, args, result: errMsg, isError: true });
445
+ continue;
446
+ }
447
+ resultStr = stringified;
448
+ }
449
+ catch (e) {
450
+ const errMsg = `${toolName} (callId=${callId}): failed to serialize tool result: ${e.message}`;
451
+ const resultEvent = {
452
+ type: 'client-tool-result',
453
+ toolName,
454
+ callId,
455
+ result: errMsg,
456
+ isError: true
457
+ };
458
+ yield yield __await(resultEvent);
459
+ toolResults.push({ toolName, callId, args, result: errMsg, isError: true });
460
+ continue;
461
+ }
462
+ const resultEvent = {
463
+ type: 'client-tool-result',
464
+ toolName,
465
+ callId,
466
+ result: resultStr,
467
+ isError: false
468
+ };
469
+ yield yield __await(resultEvent);
470
+ toolResults.push({ toolName, callId, args, result: resultStr, isError: false });
471
+ continue;
472
+ }
473
+ }
474
+ }
475
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
476
+ finally {
477
+ try {
478
+ if (!_d && !_a && (_b = _e.return)) yield __await(_b.call(_e));
479
+ }
480
+ finally { if (e_1) throw e_1.error; }
481
+ }
482
+ // Stream has ended. Build the continuation.
483
+ if (streamError !== undefined) {
484
+ resolveNextTurn((0, ts_utils_1.fail)(streamError));
485
+ return yield __await(void 0);
486
+ }
487
+ if (toolResults.length === 0) {
488
+ resolveNextTurn((0, ts_utils_1.succeed)({ continuation: undefined, truncated, fullText }));
489
+ return yield __await(void 0);
490
+ }
491
+ let continuation;
492
+ switch (descriptor.apiFormat) {
493
+ case 'anthropic':
494
+ continuation = buildAnthropicContinuation(anthropicBuffer, toolResults);
495
+ break;
496
+ case 'openai':
497
+ continuation = buildOpenAiContinuation(openAiCallMap, toolResults);
498
+ break;
499
+ case 'gemini':
500
+ continuation = buildGeminiContinuation(geminiCalls, toolResults);
501
+ break;
502
+ /* c8 ignore next 5 - defensive coding: exhaustive switch guaranteed by TypeScript */
503
+ default: {
504
+ const _exhaustive = descriptor.apiFormat;
505
+ resolveNextTurn((0, ts_utils_1.fail)(`unsupported API format: ${String(_exhaustive)}`));
506
+ return yield __await(void 0);
507
+ }
508
+ }
509
+ resolveNextTurn((0, ts_utils_1.succeed)({ continuation, truncated, fullText }));
510
+ });
511
+ }
512
+ return (0, ts_utils_1.succeed)({
513
+ events: { [Symbol.asyncIterator]: () => eventGenerator() },
514
+ nextTurn
515
+ });
516
+ }
517
+ //# 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"]}
@@ -62,6 +62,57 @@ export interface IStreamApiConfig {
62
62
  readonly apiKey: string;
63
63
  readonly model: string;
64
64
  }
65
+ /**
66
+ * Stable log-line prefix that every streaming adapter's "unrecognized SSE event"
67
+ * warning starts with. Production deployments can filter / alert on this exact
68
+ * substring without coupling to the per-adapter detail message. Surfacing this
69
+ * as a shared constant ensures all adapters emit the same prefix; loosening or
70
+ * renaming the prefix is a coordinated, intentional change rather than a per-file
71
+ * accident.
72
+ *
73
+ * @internal
74
+ */
75
+ export declare const UNRECOGNIZED_EVENT_WARN_TAG: string;
76
+ /**
77
+ * Environment variable that opts in to **raw payload preview** in the
78
+ * {@link UNRECOGNIZED_EVENT_WARN_TAG} warning. Any non-empty, non-`'0'` value
79
+ * activates the raw preview. **Default behavior** (env var absent / empty /
80
+ * `'0'`) is **structural-only** preview — top-level JSON keys + payload byte
81
+ * length, never the values.
82
+ *
83
+ * The default-safe posture exists because unrecognized SSE event payloads can
84
+ * carry tool arguments, tool results, user-conversation text, or other
85
+ * potentially sensitive content. Emitting them verbatim at `warn` level — which
86
+ * is the level explicitly designed to surface in production logs / alerting —
87
+ * is a PII leak waiting to happen.
88
+ *
89
+ * Ops triaging an active drift signal (`ai-assist:unrecognized-event` warnings
90
+ * appearing in production logs after a provider API evolution) can set this
91
+ * env var to widen the preview and see the actual payload shape during
92
+ * investigation, then unset it once the new event family is handled.
93
+ *
94
+ * @internal
95
+ */
96
+ export declare const UNRECOGNIZED_EVENT_FULL_PAYLOAD_ENV_VAR: string;
97
+ /**
98
+ * Renders an SSE `data:` payload for inclusion in an unrecognized-event
99
+ * warning.
100
+ *
101
+ * - Empty payload: returns `<no payload>`.
102
+ * - Default (env var unset): returns structural-only preview (e.g.
103
+ * `{ keys: [type, data], length: 1234 }` for a JSON object payload;
104
+ * `<array payload, length=N>` / `<{type} payload, length=N>` /
105
+ * `<non-JSON payload, length=N>` for other shapes). Never includes
106
+ * field values.
107
+ * - With {@link UNRECOGNIZED_EVENT_FULL_PAYLOAD_ENV_VAR} set to a truthy
108
+ * value: returns the raw payload, newlines collapsed to spaces, capped
109
+ * at {@link UNRECOGNIZED_EVENT_PAYLOAD_PREVIEW_MAX} chars with a trailing
110
+ * ellipsis on truncation. **Use this mode only for active drift triage**
111
+ * — it leaks payload content into warn logs.
112
+ *
113
+ * @internal
114
+ */
115
+ export declare function formatUnrecognizedEventPayloadPreview(data: string): string;
65
116
  /**
66
117
  * Opens an SSE-style POST connection. Returns the underlying Response on a
67
118
  * 2xx; failures (network, non-2xx, missing body) surface as Result.fail