@fgv/ts-extras 5.1.0-37 → 5.1.0-39

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 (128) hide show
  1. package/dist/packlets/ai-assist/apiClient.js +66 -111
  2. package/dist/packlets/ai-assist/apiClient.js.map +1 -1
  3. package/dist/packlets/ai-assist/converters.js +16 -3
  4. package/dist/packlets/ai-assist/converters.js.map +1 -1
  5. package/dist/packlets/ai-assist/embeddingClient.js +5 -6
  6. package/dist/packlets/ai-assist/embeddingClient.js.map +1 -1
  7. package/dist/packlets/ai-assist/imageOptionsResolver.js +0 -19
  8. package/dist/packlets/ai-assist/imageOptionsResolver.js.map +1 -1
  9. package/dist/packlets/ai-assist/index.js +2 -2
  10. package/dist/packlets/ai-assist/index.js.map +1 -1
  11. package/dist/packlets/ai-assist/model.js +139 -7
  12. package/dist/packlets/ai-assist/model.js.map +1 -1
  13. package/dist/packlets/ai-assist/registry.js +58 -51
  14. package/dist/packlets/ai-assist/registry.js.map +1 -1
  15. package/dist/packlets/ai-assist/streamingAdapters/anthropic.js +38 -11
  16. package/dist/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -1
  17. package/dist/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js +187 -36
  18. package/dist/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js.map +1 -1
  19. package/dist/packlets/ai-assist/streamingAdapters/common.js +13 -0
  20. package/dist/packlets/ai-assist/streamingAdapters/common.js.map +1 -1
  21. package/dist/packlets/ai-assist/streamingAdapters/gemini.js +12 -4
  22. package/dist/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -1
  23. package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js +6 -1
  24. package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -1
  25. package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js +10 -3
  26. package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -1
  27. package/dist/packlets/ai-assist/streamingAdapters/proxy.js +5 -2
  28. package/dist/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -1
  29. package/dist/packlets/ai-assist/streamingClient.js +16 -16
  30. package/dist/packlets/ai-assist/streamingClient.js.map +1 -1
  31. package/dist/packlets/crypto-utils/converters.js +18 -0
  32. package/dist/packlets/crypto-utils/converters.js.map +1 -1
  33. package/dist/packlets/crypto-utils/index.browser.js +2 -2
  34. package/dist/packlets/crypto-utils/index.browser.js.map +1 -1
  35. package/dist/packlets/crypto-utils/index.js +2 -2
  36. package/dist/packlets/crypto-utils/index.js.map +1 -1
  37. package/dist/packlets/crypto-utils/keystore/keyStore.js +19 -4
  38. package/dist/packlets/crypto-utils/keystore/keyStore.js.map +1 -1
  39. package/dist/packlets/crypto-utils/keystore/model.js.map +1 -1
  40. package/dist/packlets/crypto-utils/model.js.map +1 -1
  41. package/dist/packlets/crypto-utils/spkiHelpers.js +104 -25
  42. package/dist/packlets/crypto-utils/spkiHelpers.js.map +1 -1
  43. package/dist/ts-extras.d.ts +377 -132
  44. package/dist/tsdoc-metadata.json +1 -1
  45. package/lib/packlets/ai-assist/apiClient.d.ts +12 -2
  46. package/lib/packlets/ai-assist/apiClient.d.ts.map +1 -1
  47. package/lib/packlets/ai-assist/apiClient.js +65 -110
  48. package/lib/packlets/ai-assist/apiClient.js.map +1 -1
  49. package/lib/packlets/ai-assist/converters.d.ts +8 -2
  50. package/lib/packlets/ai-assist/converters.d.ts.map +1 -1
  51. package/lib/packlets/ai-assist/converters.js +17 -4
  52. package/lib/packlets/ai-assist/converters.js.map +1 -1
  53. package/lib/packlets/ai-assist/embeddingClient.d.ts.map +1 -1
  54. package/lib/packlets/ai-assist/embeddingClient.js +4 -5
  55. package/lib/packlets/ai-assist/embeddingClient.js.map +1 -1
  56. package/lib/packlets/ai-assist/imageOptionsResolver.d.ts +0 -8
  57. package/lib/packlets/ai-assist/imageOptionsResolver.d.ts.map +1 -1
  58. package/lib/packlets/ai-assist/imageOptionsResolver.js +0 -19
  59. package/lib/packlets/ai-assist/imageOptionsResolver.js.map +1 -1
  60. package/lib/packlets/ai-assist/index.d.ts +3 -3
  61. package/lib/packlets/ai-assist/index.d.ts.map +1 -1
  62. package/lib/packlets/ai-assist/index.js +6 -1
  63. package/lib/packlets/ai-assist/index.js.map +1 -1
  64. package/lib/packlets/ai-assist/model.d.ts +181 -98
  65. package/lib/packlets/ai-assist/model.d.ts.map +1 -1
  66. package/lib/packlets/ai-assist/model.js +143 -8
  67. package/lib/packlets/ai-assist/model.js.map +1 -1
  68. package/lib/packlets/ai-assist/registry.d.ts.map +1 -1
  69. package/lib/packlets/ai-assist/registry.js +58 -51
  70. package/lib/packlets/ai-assist/registry.js.map +1 -1
  71. package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts +1 -1
  72. package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts.map +1 -1
  73. package/lib/packlets/ai-assist/streamingAdapters/anthropic.js +37 -10
  74. package/lib/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -1
  75. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.d.ts +82 -3
  76. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.d.ts.map +1 -1
  77. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js +187 -35
  78. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js.map +1 -1
  79. package/lib/packlets/ai-assist/streamingAdapters/common.d.ts +24 -1
  80. package/lib/packlets/ai-assist/streamingAdapters/common.d.ts.map +1 -1
  81. package/lib/packlets/ai-assist/streamingAdapters/common.js +14 -1
  82. package/lib/packlets/ai-assist/streamingAdapters/common.js.map +1 -1
  83. package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts +10 -1
  84. package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts.map +1 -1
  85. package/lib/packlets/ai-assist/streamingAdapters/gemini.js +12 -4
  86. package/lib/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -1
  87. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts +1 -1
  88. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts.map +1 -1
  89. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js +6 -1
  90. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -1
  91. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts +1 -1
  92. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts.map +1 -1
  93. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js +10 -3
  94. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -1
  95. package/lib/packlets/ai-assist/streamingAdapters/proxy.d.ts.map +1 -1
  96. package/lib/packlets/ai-assist/streamingAdapters/proxy.js +5 -2
  97. package/lib/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -1
  98. package/lib/packlets/ai-assist/streamingClient.d.ts +1 -1
  99. package/lib/packlets/ai-assist/streamingClient.d.ts.map +1 -1
  100. package/lib/packlets/ai-assist/streamingClient.js +15 -15
  101. package/lib/packlets/ai-assist/streamingClient.js.map +1 -1
  102. package/lib/packlets/crypto-utils/converters.d.ts +9 -1
  103. package/lib/packlets/crypto-utils/converters.d.ts.map +1 -1
  104. package/lib/packlets/crypto-utils/converters.js +19 -1
  105. package/lib/packlets/crypto-utils/converters.js.map +1 -1
  106. package/lib/packlets/crypto-utils/index.browser.d.ts +1 -1
  107. package/lib/packlets/crypto-utils/index.browser.d.ts.map +1 -1
  108. package/lib/packlets/crypto-utils/index.browser.js +6 -2
  109. package/lib/packlets/crypto-utils/index.browser.js.map +1 -1
  110. package/lib/packlets/crypto-utils/index.d.ts +1 -1
  111. package/lib/packlets/crypto-utils/index.d.ts.map +1 -1
  112. package/lib/packlets/crypto-utils/index.js +6 -2
  113. package/lib/packlets/crypto-utils/index.js.map +1 -1
  114. package/lib/packlets/crypto-utils/keystore/keyStore.d.ts +9 -2
  115. package/lib/packlets/crypto-utils/keystore/keyStore.d.ts.map +1 -1
  116. package/lib/packlets/crypto-utils/keystore/keyStore.js +19 -4
  117. package/lib/packlets/crypto-utils/keystore/keyStore.js.map +1 -1
  118. package/lib/packlets/crypto-utils/keystore/model.d.ts +7 -0
  119. package/lib/packlets/crypto-utils/keystore/model.d.ts.map +1 -1
  120. package/lib/packlets/crypto-utils/keystore/model.js.map +1 -1
  121. package/lib/packlets/crypto-utils/model.d.ts +11 -1
  122. package/lib/packlets/crypto-utils/model.d.ts.map +1 -1
  123. package/lib/packlets/crypto-utils/model.js.map +1 -1
  124. package/lib/packlets/crypto-utils/spkiHelpers.d.ts +59 -2
  125. package/lib/packlets/crypto-utils/spkiHelpers.d.ts.map +1 -1
  126. package/lib/packlets/crypto-utils/spkiHelpers.js +108 -25
  127. package/lib/packlets/crypto-utils/spkiHelpers.js.map +1 -1
  128. package/package.json +7 -7
@@ -55,7 +55,7 @@ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _ar
55
55
  * @packageDocumentation
56
56
  */
57
57
  import { captureAsyncResult, fail, succeed } from '@fgv/ts-utils';
58
- import { resolveModel } from '../model';
58
+ import { resolveProviderModel } from '../model';
59
59
  import { splitChatRequest } from '../chatRequestBuilders';
60
60
  import { resolveEffectiveBaseUrl } from '../endpoint';
61
61
  import { callAnthropicStream } from './anthropic';
@@ -64,6 +64,19 @@ import { callGeminiStream } from './gemini';
64
64
  // ============================================================================
65
65
  // Anthropic continuation builder
66
66
  // ============================================================================
67
+ /**
68
+ * Returns `true` when `id` is a usable correlation id — present and non-empty.
69
+ *
70
+ * `??` is NOT sufficient for this check: an empty string is a *bad* id (a
71
+ * `tool_use_id: ''` is rejected by Anthropic as a "malformed identifier") but
72
+ * `'' ?? fallback` yields `''`, silently passing the empty id through. Every id
73
+ * correlation in this module must use this predicate, never `??`.
74
+ *
75
+ * @internal
76
+ */
77
+ function isUsableId(id) {
78
+ return id !== undefined && id.length > 0;
79
+ }
67
80
  /**
68
81
  * Builds the Anthropic follow-up messages for a client-tool round-trip.
69
82
  *
@@ -71,6 +84,15 @@ import { callGeminiStream } from './gemini';
71
84
  * (all block types in original stream order) and appends a user turn
72
85
  * with `tool_result` blocks for each executed tool call.
73
86
  *
87
+ * **Single source of truth for the id (id-correlation fix).** Each
88
+ * `tool_result.tool_use_id` is drawn from — and validated against — the set of
89
+ * assistant `tool_use.id`s actually present in the accumulation buffer. The id
90
+ * is NEVER derived from the tool name: a tool name can never match a `toolu_*`
91
+ * id, so a name fallback produces exactly the "malformed identifier" the
92
+ * provider rejects. If any tool result cannot be correlated to a buffered
93
+ * `tool_use` block with a non-empty id, the build fails loud (naming the tool)
94
+ * rather than emitting a malformed continuation.
95
+ *
74
96
  * **Constraint (E3):** The returned continuation does NOT include a forced
75
97
  * `tool_choice` field. When thinking is active, Anthropic rejects
76
98
  * `tool_choice: { type: 'any' }` and `tool_choice: { type: 'tool', ... }`
@@ -79,11 +101,14 @@ import { callGeminiStream } from './gemini';
79
101
  *
80
102
  * @internal
81
103
  */
82
- export function buildAnthropicContinuation(accBuffer, toolResults) {
104
+ export function buildAnthropicContinuation(accBuffer, toolResults, logger) {
83
105
  // Reconstruct the assistant turn from the ordered accumulation buffer.
84
106
  // Sort by buffer key (SSE index) to restore original stream order.
85
107
  const sortedKeys = Array.from(accBuffer.keys()).sort((a, b) => a - b);
86
108
  const assistantContent = [];
109
+ // The set of assistant tool_use ids — the single source of truth every
110
+ // tool_result.tool_use_id must be drawn from.
111
+ const bufferedToolUseIds = new Set();
87
112
  for (const key of sortedKeys) {
88
113
  const block = accBuffer.get(key);
89
114
  /* c8 ignore next 1 - defensive: key always exists in map since we iterate its keys */
@@ -108,6 +133,12 @@ export function buildAnthropicContinuation(accBuffer, toolResults) {
108
133
  }
109
134
  }
110
135
  else if (block.type === 'tool_use') {
136
+ // A buffered tool_use with an empty id can never be referenced by a valid
137
+ // tool_result; emitting it would corrupt the assistant turn. Fail loud.
138
+ if (!isUsableId(block.id)) {
139
+ return fail(`Anthropic continuation: buffered tool_use block for tool '${block.name}' has an empty id; ` +
140
+ `cannot build a valid continuation`);
141
+ }
111
142
  let parsedInput;
112
143
  try {
113
144
  /* c8 ignore next 1 - defensive: argsBuffer is JSON-parsed in the adapter before emitting client-tool-call-done */
@@ -118,6 +149,7 @@ export function buildAnthropicContinuation(accBuffer, toolResults) {
118
149
  parsedInput = {};
119
150
  }
120
151
  /* c8 ignore stop */
152
+ bufferedToolUseIds.add(block.id);
121
153
  assistantContent.push({
122
154
  type: 'tool_use',
123
155
  id: block.id,
@@ -126,20 +158,36 @@ export function buildAnthropicContinuation(accBuffer, toolResults) {
126
158
  });
127
159
  }
128
160
  }
129
- // Build user turn with tool_result blocks for each tool call.
130
- const userContent = toolResults.map((r) => {
131
- var _a;
161
+ // Build user turn with tool_result blocks for each tool call. Correlate each
162
+ // result to a buffered tool_use id — the SAME id that keys the assistant
163
+ // tool_use block — and fail loud on a missing / empty / mismatched id. A
164
+ // missing callId is a bug to surface, not paper over with a name fallback.
165
+ const userContent = [];
166
+ for (const r of toolResults) {
167
+ if (!isUsableId(r.callId)) {
168
+ return fail(`Anthropic continuation: tool '${r.toolName}' result has no call id (missing or empty); ` +
169
+ `cannot correlate it to an assistant tool_use block`);
170
+ }
171
+ if (!bufferedToolUseIds.has(r.callId)) {
172
+ return fail(`Anthropic continuation: tool '${r.toolName}' call id '${r.callId}' does not match any ` +
173
+ `buffered assistant tool_use block id`);
174
+ }
132
175
  const block = {
133
176
  type: 'tool_result',
134
177
  // eslint-disable-next-line @typescript-eslint/naming-convention
135
- tool_use_id: (_a = r.callId) !== null && _a !== void 0 ? _a : r.toolName,
178
+ tool_use_id: r.callId,
136
179
  content: r.result
137
180
  };
138
- if (r.isError) {
139
- return Object.assign(Object.assign({}, block), { is_error: true });
140
- }
141
- return block;
142
- });
181
+ userContent.push(r.isError ? Object.assign(Object.assign({}, block), { is_error: true }) : block);
182
+ }
183
+ // Decisive diagnostic: the tool_use.id ↔ tool_result.tool_use_id pairing for
184
+ // the outgoing continuation. A failing turn is field-confirmable by capturing
185
+ // this line (the pairing is by construction matched here — divergence fails
186
+ // loud above, before reaching this point).
187
+ if (logger) {
188
+ const pairing = toolResults.map((r) => `${r.toolName}:${r.callId}`).join(', ');
189
+ logger.detail(`ai-assist:anthropic-continuation tool_use.id↔tool_result.tool_use_id [${pairing}]`);
190
+ }
143
191
  const assistantMessage = {
144
192
  role: 'assistant',
145
193
  content: assistantContent
@@ -148,7 +196,7 @@ export function buildAnthropicContinuation(accBuffer, toolResults) {
148
196
  role: 'user',
149
197
  content: userContent
150
198
  };
151
- return {
199
+ return succeed({
152
200
  messages: [assistantMessage, userMessage],
153
201
  toolCallsSummary: toolResults.map((r) => ({
154
202
  toolName: r.toolName,
@@ -157,7 +205,7 @@ export function buildAnthropicContinuation(accBuffer, toolResults) {
157
205
  result: r.result,
158
206
  isError: r.isError
159
207
  }))
160
- };
208
+ });
161
209
  }
162
210
  // ============================================================================
163
211
  // OpenAI / xAI Responses API continuation builder
@@ -170,17 +218,25 @@ export function buildAnthropicContinuation(accBuffer, toolResults) {
170
218
  * `function_call_output` items (the harness execution result), one pair
171
219
  * per executed tool call.
172
220
  *
221
+ * **Single source of truth for the id (id-correlation fix).** Each
222
+ * `function_call_output.call_id` is drawn from — and validated against — the
223
+ * set of `function_call.call_id`s in the accumulation map. It is NEVER derived
224
+ * from the tool name (the OpenAI parity of the Anthropic `tool_use_id` fix). A
225
+ * missing / empty / unmatched call id fails the build loud rather than emitting
226
+ * a continuation the provider would reject.
227
+ *
173
228
  * @internal
174
229
  */
175
- export function buildOpenAiContinuation(calls, toolResults) {
176
- var _a;
230
+ export function buildOpenAiContinuation(calls, toolResults, logger) {
177
231
  const items = [];
178
232
  // Emit function_call items for each call (model's side). Per the Responses API spec
179
233
  // (ResponseFunctionToolCall), `call_id` is the required correlation field — it must
180
234
  // match the matching function_call_output's `call_id` below. The optional `id` field
181
235
  // is the output-item id (`fc_*`) used to reference the streamed item; we omit it
182
236
  // because it is not load-bearing for input items.
237
+ const bufferedCallIds = new Set();
183
238
  for (const [callId, call] of calls) {
239
+ bufferedCallIds.add(callId);
184
240
  items.push({
185
241
  type: 'function_call',
186
242
  call_id: callId,
@@ -188,15 +244,31 @@ export function buildOpenAiContinuation(calls, toolResults) {
188
244
  arguments: call.argsBuffer
189
245
  });
190
246
  }
191
- // Emit function_call_output items (harness execution results).
247
+ // Emit function_call_output items (harness execution results). Correlate each
248
+ // to a buffered function_call call_id — never the tool name — and fail loud on
249
+ // a missing / empty / mismatched id.
192
250
  for (const r of toolResults) {
251
+ if (!isUsableId(r.callId)) {
252
+ return fail(`OpenAI continuation: tool '${r.toolName}' result has no call id (missing or empty); ` +
253
+ `cannot correlate it to a function_call item`);
254
+ }
255
+ if (!bufferedCallIds.has(r.callId)) {
256
+ return fail(`OpenAI continuation: tool '${r.toolName}' call id '${r.callId}' does not match any ` +
257
+ `accumulated function_call call_id`);
258
+ }
193
259
  items.push({
194
260
  type: 'function_call_output',
195
- call_id: (_a = r.callId) !== null && _a !== void 0 ? _a : r.toolName,
261
+ call_id: r.callId,
196
262
  output: r.result
197
263
  });
198
264
  }
199
- return {
265
+ // Decisive diagnostic: the function_call.call_id ↔ function_call_output.call_id
266
+ // pairing for the outgoing continuation (matched by construction here).
267
+ if (logger) {
268
+ const pairing = toolResults.map((r) => `${r.toolName}:${r.callId}`).join(', ');
269
+ logger.detail(`ai-assist:openai-continuation function_call.call_id↔output.call_id [${pairing}]`);
270
+ }
271
+ return succeed({
200
272
  messages: items,
201
273
  toolCallsSummary: toolResults.map((r) => ({
202
274
  toolName: r.toolName,
@@ -205,7 +277,7 @@ export function buildOpenAiContinuation(calls, toolResults) {
205
277
  result: r.result,
206
278
  isError: r.isError
207
279
  }))
208
- };
280
+ });
209
281
  }
210
282
  // ============================================================================
211
283
  // Gemini continuation builder
@@ -217,16 +289,23 @@ export function buildOpenAiContinuation(calls, toolResults) {
217
289
  * user turn with `functionResponse` parts (correlation by tool name, since
218
290
  * Gemini does not assign call IDs).
219
291
  *
292
+ * When thinking is enabled, Gemini stamps an opaque `thoughtSignature` on each
293
+ * `functionCall` part and requires it echoed back, verbatim, as a sibling of
294
+ * `functionCall` on the continuation's model turn — otherwise the follow-up is
295
+ * rejected with "Function call is missing a thought_signature in functionCall
296
+ * parts". The captured signature (see {@link IAccumulatedGeminiFunctionCall})
297
+ * is replayed here only when present; the key is omitted entirely when thinking
298
+ * was disabled. See https://ai.google.dev/gemini-api/docs/thought-signatures.
299
+ *
220
300
  * @internal
221
301
  */
222
302
  export function buildGeminiContinuation(calls, toolResults) {
223
- // Model turn: functionCall parts for each call.
224
- const modelParts = calls.map((call) => ({
225
- functionCall: {
303
+ // Model turn: functionCall parts for each call. Replay the part-level
304
+ // thoughtSignature as a sibling of functionCall only when present.
305
+ const modelParts = calls.map((call) => (Object.assign({ functionCall: {
226
306
  name: call.name,
227
307
  args: call.args
228
- }
229
- }));
308
+ } }, (call.thoughtSignature !== undefined ? { thoughtSignature: call.thoughtSignature } : {}))));
230
309
  // User turn: functionResponse parts for each executed result.
231
310
  // Correlation is by name since Gemini has no call IDs.
232
311
  const userParts = toolResults.map((r) => ({
@@ -257,6 +336,20 @@ export function buildGeminiContinuation(calls, toolResults) {
257
336
  // ============================================================================
258
337
  // executeClientToolTurn
259
338
  // ============================================================================
339
+ /**
340
+ * True when a request would combine Gemini built-in grounding (a `web_search`
341
+ * server tool) with client (function) tools — a combination Gemini's
342
+ * `generateContent` API rejects with HTTP 400 (`INVALID_ARGUMENT`). Callers gate
343
+ * on `descriptor.apiFormat === 'gemini'` before consulting this; other providers
344
+ * accept the mix. Kept as a pure predicate so the conflict rule is unit-testable
345
+ * without a live stream.
346
+ *
347
+ * @internal
348
+ */
349
+ export function hasGeminiToolConflict(tools, clientTools) {
350
+ var _a;
351
+ return clientTools.length > 0 && ((_a = tools === null || tools === void 0 ? void 0 : tools.some((t) => t.type === 'web_search')) !== null && _a !== void 0 ? _a : false);
352
+ }
260
353
  /**
261
354
  * Orchestrates a single client-tool streaming turn for any supported provider.
262
355
  *
@@ -279,7 +372,7 @@ export function buildGeminiContinuation(calls, toolResults) {
279
372
  * @public
280
373
  */
281
374
  export function executeClientToolTurn(params) {
282
- const { descriptor, apiKey, system, messages, continuationMessages, temperature, tools, clientTools, signal, logger, resolvedThinking, model, endpoint } = params;
375
+ const { descriptor, apiKey, system, messages, continuationMessages, temperature, tools, clientTools, signal, logger, resolvedThinking, model, endpoint, onBeforeToolExecute } = params;
283
376
  const splitResult = splitChatRequest(system, messages);
284
377
  if (splitResult.isFailure()) {
285
378
  return fail(splitResult.message);
@@ -302,11 +395,18 @@ export function executeClientToolTurn(params) {
302
395
  // This is the fix for P1-1: client tools were never sent to the provider because
303
396
  // the adapters only received `tools` (server tools). Both must coexist per design §2.5.
304
397
  const effectiveTools = clientTools.length > 0 ? [...(tools !== null && tools !== void 0 ? tools : []), ...clientTools.map((t) => t.config)] : tools;
305
- const effectiveTemperature = temperature !== null && temperature !== void 0 ? temperature : 0.7;
306
- const resolvedModel = model !== null && model !== void 0 ? model : resolveModel(descriptor.defaultModel);
307
- if (resolvedModel.length === 0) {
308
- return fail(`provider "${descriptor.id}": no model resolved; pass model or set descriptor.defaultModel`);
398
+ // Gemini pre-flight: its generateContent API HTTP-400s (INVALID_ARGUMENT) when
399
+ // built-in grounding (`web_search`) and function calling (client tools) are
400
+ // combined in one request. Fail fast with a clear, actionable message rather
401
+ // than letting the opaque wire 400 surface. Other providers accept the mix.
402
+ if (descriptor.apiFormat === 'gemini' && hasGeminiToolConflict(tools, clientTools)) {
403
+ return fail('executeClientToolTurn: Gemini cannot combine web_search grounding with client (function) tools in the same request; send one or the other');
404
+ }
405
+ const modelResult = resolveProviderModel(descriptor, model);
406
+ if (modelResult.isFailure()) {
407
+ return fail(modelResult.message);
309
408
  }
409
+ const resolvedModel = modelResult.value;
310
410
  const baseUrlResult = resolveEffectiveBaseUrl(descriptor, endpoint);
311
411
  if (baseUrlResult.isFailure()) {
312
412
  return fail(baseUrlResult.message);
@@ -326,13 +426,13 @@ export function executeClientToolTurn(params) {
326
426
  const streamPromise = (() => {
327
427
  switch (descriptor.apiFormat) {
328
428
  case 'anthropic':
329
- return callAnthropicStream(config, prompt, head, effectiveTemperature, effectiveTools, logger, signal, resolvedThinking, anthropicBuffer, continuationMessages);
429
+ return callAnthropicStream(config, prompt, head, temperature, effectiveTools, logger, signal, resolvedThinking, anthropicBuffer, continuationMessages);
330
430
  case 'openai':
331
431
  return callOpenAiResponsesStream(config, prompt,
332
432
  /* c8 ignore next 1 - defensive: openai path requires tools; empty array fallback unreachable in practice */
333
- effectiveTools !== null && effectiveTools !== void 0 ? effectiveTools : [], head, effectiveTemperature, logger, signal, resolvedThinking, openAiCallMap, continuationMessages);
433
+ effectiveTools !== null && effectiveTools !== void 0 ? effectiveTools : [], head, temperature, logger, signal, resolvedThinking, openAiCallMap, continuationMessages);
334
434
  case 'gemini':
335
- return callGeminiStream(config, prompt, head, effectiveTemperature, effectiveTools, logger, signal, resolvedThinking, geminiCalls, continuationMessages);
435
+ return callGeminiStream(config, prompt, head, temperature, effectiveTools, logger, signal, resolvedThinking, geminiCalls, continuationMessages);
336
436
  /* c8 ignore next 4 - defensive coding: exhaustive switch guaranteed by TypeScript */
337
437
  default: {
338
438
  const _exhaustive = descriptor.apiFormat;
@@ -418,6 +518,45 @@ export function executeClientToolTurn(params) {
418
518
  toolResults.push({ toolName, callId, args, result: errMsg, isError: true });
419
519
  continue;
420
520
  }
521
+ // Host gate: run after arg-validation, before execute. A `deny` decision
522
+ // synthesizes a denial tool-result and continues the turn; a `fail`/reject
523
+ // from the gate itself is a hard error (mirrors an execute failure), never
524
+ // a silent deny.
525
+ if (onBeforeToolExecute !== undefined) {
526
+ const decisionResult = (yield __await(captureAsyncResult(async () => onBeforeToolExecute(tool, validationResult.value)))).onSuccess((decision) => decision);
527
+ if (decisionResult.isFailure()) {
528
+ const errMsg = `${toolName} (callId=${callId}): ${decisionResult.message}`;
529
+ const resultEvent = {
530
+ type: 'client-tool-result',
531
+ toolName,
532
+ callId,
533
+ result: errMsg,
534
+ isError: true
535
+ };
536
+ yield yield __await(resultEvent);
537
+ // Gate-fail is a hard, turn-terminating error (unlike a non-fatal `deny`, whose
538
+ // generator continues). Emit an explicit `error` event so a consumer watching only
539
+ // `events` sees the fatal failure inline and can distinguish it from a deny — matching
540
+ // the stream-open / continuation hard-error paths.
541
+ yield yield __await({ type: 'error', message: errMsg });
542
+ toolResults.push({ toolName, callId, args, result: errMsg, isError: true });
543
+ resolveNextTurn(fail(errMsg));
544
+ return yield __await(void 0);
545
+ }
546
+ if (decisionResult.value.action === 'deny') {
547
+ const denialMsg = `${toolName} (callId=${callId}): tool execution denied: ${decisionResult.value.reason}`;
548
+ const resultEvent = {
549
+ type: 'client-tool-result',
550
+ toolName,
551
+ callId,
552
+ result: denialMsg,
553
+ isError: true
554
+ };
555
+ yield yield __await(resultEvent);
556
+ toolResults.push({ toolName, callId, args, result: denialMsg, isError: true });
557
+ continue;
558
+ }
559
+ }
421
560
  const executeResult = yield __await(captureAsyncResult(async () => tool.execute(validationResult.value)));
422
561
  const executionResult = executeResult.isSuccess()
423
562
  ? executeResult.value
@@ -499,16 +638,18 @@ export function executeClientToolTurn(params) {
499
638
  resolveNextTurn(succeed({ continuation: undefined, truncated, fullText }));
500
639
  return yield __await(void 0);
501
640
  }
502
- let continuation;
641
+ let continuationResult;
503
642
  switch (descriptor.apiFormat) {
504
643
  case 'anthropic':
505
- continuation = buildAnthropicContinuation(anthropicBuffer, toolResults);
644
+ continuationResult = buildAnthropicContinuation(anthropicBuffer, toolResults, logger);
506
645
  break;
507
646
  case 'openai':
508
- continuation = buildOpenAiContinuation(openAiCallMap, toolResults);
647
+ continuationResult = buildOpenAiContinuation(openAiCallMap, toolResults, logger);
509
648
  break;
510
649
  case 'gemini':
511
- continuation = buildGeminiContinuation(geminiCalls, toolResults);
650
+ // Gemini correlates by tool name by design (no call ids) — its builder
651
+ // cannot mis-key and stays non-fallible.
652
+ continuationResult = succeed(buildGeminiContinuation(geminiCalls, toolResults));
512
653
  break;
513
654
  /* c8 ignore next 5 - defensive coding: exhaustive switch guaranteed by TypeScript */
514
655
  default: {
@@ -517,6 +658,16 @@ export function executeClientToolTurn(params) {
517
658
  return yield __await(void 0);
518
659
  }
519
660
  }
661
+ // A bad id-correlation fails loud here rather than emitting a malformed
662
+ // continuation the provider would reject as a "malformed identifier". Mirror
663
+ // the stream-open-failure path above: surface an `error` event so a consumer
664
+ // iterating `events` sees the failure inline, not only via `nextTurn`.
665
+ if (continuationResult.isFailure()) {
666
+ resolveNextTurn(fail(continuationResult.message));
667
+ yield yield __await({ type: 'error', message: continuationResult.message });
668
+ return yield __await(void 0);
669
+ }
670
+ let continuation = continuationResult.value;
520
671
  // Prepend inbound continuationMessages so the returned continuation is cumulative.
521
672
  // A consumer that does `tail = outcome.continuation.messages` (replace) is then
522
673
  // correct for all N rounds — messages always contains the full wire tail from
@@ -1 +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 AiServerToolConfig,\n type AiToolConfig,\n type IAiClientTool,\n type IAiClientToolContinuation,\n type IAiClientToolTurnResult,\n type IAiStreamEvent,\n type IChatRequest,\n type IAiProviderDescriptor,\n resolveModel\n} from '../model';\nimport { type IResolvedThinkingConfig } from '../thinkingOptionsResolver';\nimport { splitChatRequest } from '../chatRequestBuilders';\nimport { resolveEffectiveBaseUrl } from '../endpoint';\nimport { type IAccumulatedBlock } from './anthropic';\nimport { type IAccumulatedFunctionCall } from './openaiResponses';\nimport { type IAccumulatedGeminiFunctionCall } from './gemini';\nimport { callAnthropicStream } from './anthropic';\nimport { callOpenAiResponsesStream } from './openaiResponses';\nimport { callGeminiStream } from './gemini';\nimport { type IStreamApiConfig } from './common';\n\n// ============================================================================\n// Tool-result accumulation (internal)\n// ============================================================================\n\n/**\n * Accumulated result for a single tool call, collected during stream iteration.\n * @internal\n */\ninterface IToolCallResult {\n readonly toolName: string;\n readonly callId?: string;\n readonly args: JsonObject;\n readonly result: string;\n readonly isError: boolean;\n}\n\n// ============================================================================\n// Anthropic continuation builder\n// ============================================================================\n\n/**\n * Builds the Anthropic follow-up messages for a client-tool round-trip.\n *\n * Reconstructs the assistant turn from the ordered accumulation buffer\n * (all block types in original stream order) and appends a user turn\n * with `tool_result` blocks for each executed tool call.\n *\n * **Constraint (E3):** The returned continuation does NOT include a forced\n * `tool_choice` field. When thinking is active, Anthropic rejects\n * `tool_choice: { type: 'any' }` and `tool_choice: { type: 'tool', ... }`\n * with an HTTP 400 error. Only `tool_choice: { type: 'auto' }` (the default,\n * i.e. omitted) is compatible with extended thinking.\n *\n * @internal\n */\nexport function buildAnthropicContinuation(\n accBuffer: Map<number, IAccumulatedBlock>,\n toolResults: IToolCallResult[]\n): IAiClientToolContinuation {\n // Reconstruct the assistant turn from the ordered accumulation buffer.\n // Sort by buffer key (SSE index) to restore original stream order.\n const sortedKeys = Array.from(accBuffer.keys()).sort((a, b) => a - b);\n const assistantContent: JsonArray = [];\n\n for (const key of sortedKeys) {\n const block = accBuffer.get(key);\n /* c8 ignore next 1 - defensive: key always exists in map since we iterate its keys */\n if (!block) continue;\n if (block.type === 'thinking') {\n assistantContent.push({\n type: 'thinking',\n thinking: block.thinking,\n signature: block.signature\n });\n } else if (block.type === 'redacted_thinking') {\n assistantContent.push({\n type: 'redacted_thinking',\n data: block.data\n });\n } else if (block.type === 'text') {\n if (block.text.length > 0) {\n assistantContent.push({ type: 'text', text: block.text });\n }\n } else if (block.type === 'tool_use') {\n let parsedInput: JsonObject;\n try {\n /* c8 ignore next 1 - defensive: argsBuffer is JSON-parsed in the adapter before emitting client-tool-call-done */\n parsedInput = JSON.parse(block.argsBuffer || '{}') as JsonObject;\n /* c8 ignore start - defensive: malformed argsBuffer defaults to empty object */\n } catch {\n parsedInput = {};\n }\n /* c8 ignore stop */\n assistantContent.push({\n type: 'tool_use',\n id: block.id,\n name: block.name,\n input: parsedInput\n });\n }\n }\n\n // Build user turn with tool_result blocks for each tool call.\n const userContent: JsonArray = toolResults.map((r): JsonObject => {\n const block: JsonObject = {\n type: 'tool_result',\n // eslint-disable-next-line @typescript-eslint/naming-convention\n tool_use_id: r.callId ?? r.toolName,\n content: r.result\n };\n if (r.isError) {\n return { ...block, is_error: true };\n }\n return block;\n });\n\n const assistantMessage: JsonObject = {\n role: 'assistant',\n content: assistantContent\n };\n const userMessage: JsonObject = {\n role: 'user',\n content: userContent\n };\n\n return {\n messages: [assistantMessage, userMessage],\n toolCallsSummary: toolResults.map((r) => ({\n toolName: r.toolName,\n callId: r.callId,\n args: r.args,\n result: r.result,\n isError: r.isError\n }))\n };\n}\n\n// ============================================================================\n// OpenAI / xAI Responses API continuation builder\n// ============================================================================\n\n/**\n * Builds the OpenAI / xAI Responses API follow-up input items for a\n * client-tool round-trip.\n *\n * Emits `function_call` items (the model's call) followed by\n * `function_call_output` items (the harness execution result), one pair\n * per executed tool call.\n *\n * @internal\n */\nexport function buildOpenAiContinuation(\n calls: Map<string, IAccumulatedFunctionCall>,\n toolResults: IToolCallResult[]\n): IAiClientToolContinuation {\n const items: JsonObject[] = [];\n\n // Emit function_call items for each call (model's side). Per the Responses API spec\n // (ResponseFunctionToolCall), `call_id` is the required correlation field — it must\n // match the matching function_call_output's `call_id` below. The optional `id` field\n // is the output-item id (`fc_*`) used to reference the streamed item; we omit it\n // because it is not load-bearing for input items.\n for (const [callId, call] of calls) {\n items.push({\n type: 'function_call',\n call_id: callId,\n name: call.name,\n arguments: call.argsBuffer\n });\n }\n\n // Emit function_call_output items (harness execution results).\n for (const r of toolResults) {\n items.push({\n type: 'function_call_output',\n call_id: r.callId ?? r.toolName,\n output: r.result\n });\n }\n\n return {\n messages: items,\n toolCallsSummary: toolResults.map((r) => ({\n toolName: r.toolName,\n callId: r.callId,\n args: r.args,\n result: r.result,\n isError: r.isError\n }))\n };\n}\n\n// ============================================================================\n// Gemini continuation builder\n// ============================================================================\n\n/**\n * Builds the Gemini follow-up contents for a client-tool round-trip.\n *\n * Emits a model turn with `functionCall` parts (one per tool call) and a\n * user turn with `functionResponse` parts (correlation by tool name, since\n * Gemini does not assign call IDs).\n *\n * @internal\n */\nexport function buildGeminiContinuation(\n calls: IAccumulatedGeminiFunctionCall[],\n toolResults: IToolCallResult[]\n): IAiClientToolContinuation {\n // Model turn: functionCall parts for each call.\n const modelParts: JsonArray = calls.map(\n (call): JsonObject => ({\n functionCall: {\n name: call.name,\n args: call.args\n }\n })\n );\n\n // User turn: functionResponse parts for each executed result.\n // Correlation is by name since Gemini has no call IDs.\n const userParts: JsonArray = toolResults.map(\n (r): JsonObject => ({\n functionResponse: {\n name: r.toolName,\n response: {\n content: r.result,\n ...(r.isError ? { error: true } : {})\n }\n }\n })\n );\n\n const modelMessage: JsonObject = {\n role: 'model',\n parts: modelParts\n };\n const userMessage: JsonObject = {\n role: 'user',\n parts: userParts\n };\n\n return {\n messages: [modelMessage, userMessage],\n toolCallsSummary: toolResults.map((r) => ({\n toolName: r.toolName,\n callId: r.callId,\n args: r.args,\n result: r.result,\n isError: r.isError\n }))\n };\n}\n\n// ============================================================================\n// executeClientToolTurn parameters\n// ============================================================================\n\n/**\n * Parameters for {@link AiAssist.executeClientToolTurn}.\n *\n * @remarks\n * Carries the unified {@link AiAssist.IChatRequest} shape (`system?` + ordered\n * `messages`): the last message is the current user turn and the preceding\n * messages are history, linearized before the current turn — identically to the\n * completion and streaming paths. {@link IExecuteClientToolTurnParams.continuationMessages}\n * remains a distinct post-current-turn axis (see below).\n *\n * @public\n */\nexport interface IExecuteClientToolTurnParams extends IChatRequest {\n /** The provider descriptor for routing (Anthropic / OpenAI / Gemini). */\n readonly descriptor: IAiProviderDescriptor;\n /** API key for authentication. */\n readonly apiKey: string;\n /**\n * The cumulative provider-native wire tail from the previous turn's\n * {@link AiAssist.IAiClientToolContinuation.messages}. Supply this as-is\n * each round — `messages` is already cumulative, so **replace** rather\n * than manually concatenate:\n *\n * ```ts\n * tail = outcome.continuation.messages; // replace — already cumulative\n * ```\n *\n * On the first turn this should be `undefined` (or omitted).\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\n * guards 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 * Do NOT place these objects in the `messages` parameter — the\n * normalized-message path strips provider-native fields.\n */\n readonly continuationMessages?: ReadonlyArray<JsonObject>;\n /** Temperature (default: 0.7). */\n readonly temperature?: number;\n /** Server-side tools to include. */\n readonly tools?: ReadonlyArray<AiServerToolConfig>;\n /** Client-defined tools available for the model to call. */\n readonly clientTools: ReadonlyArray<IAiClientTool>;\n /** Optional abort signal. */\n readonly signal?: AbortSignal;\n /**\n * Optional override of the descriptor's default base URL. Same semantics as\n * the non-streaming completion path and `callProviderCompletionStream`: a\n * well-formed `http`/`https` URL is substituted for `descriptor.baseUrl`\n * when composing the per-format request, with the per-format suffix appended\n * unchanged. Validated at the dispatcher; auth shape is unaffected. Use this\n * to point a client-tool turn at a local / LAN OpenAI-compatible server\n * (Ollama, LM Studio, llama.cpp).\n */\n readonly endpoint?: string;\n /** Optional logger for diagnostics. */\n readonly logger?: Logging.ILogger;\n /** Optional resolved thinking config (pre-resolved by the caller). */\n readonly resolvedThinking?: IResolvedThinkingConfig;\n /** Resolved model string (pre-resolved by the caller). When omitted, uses the descriptor's default model. */\n readonly model?: string;\n}\n\n/**\n * Return value of {@link AiAssist.executeClientToolTurn}.\n * @public\n */\nexport interface IExecuteClientToolTurnResult {\n /**\n * The unified-event iterable. Callers iterate this to drive the streaming UI.\n * The iterable forwards `text-delta`, `tool-event`, `client-tool-call-start`,\n * `client-tool-call-done`, and `client-tool-result` events through.\n */\n readonly events: AsyncIterable<IAiStreamEvent>;\n /**\n * Resolves when the stream terminates. On success, carries the\n * {@link AiAssist.IAiClientToolTurnResult} with the optional continuation for the\n * next round. On failure, carries the error message.\n */\n readonly nextTurn: Promise<Result<IAiClientToolTurnResult>>;\n}\n\n// ============================================================================\n// executeClientToolTurn\n// ============================================================================\n\n/**\n * Orchestrates a single client-tool streaming turn for any supported provider.\n *\n * Starts a streaming request, iterates the underlying provider stream, and:\n * - Forwards `text-delta`, `tool-event`, `client-tool-call-start`, and\n * `client-tool-call-done` events through to the consumer.\n * - For each `client-tool-call-done` event: validates the raw args against the\n * tool's `parametersSchema`, invokes `execute(typedArgs)`, and emits a\n * `client-tool-result` event.\n * - After stream completion: builds the per-provider continuation (or\n * `{ continuation: undefined }` when no tool calls occurred) and resolves\n * `nextTurn`.\n *\n * **Anthropic constraint (E3):** The continuation for Anthropic does not set\n * a forced `tool_choice`. Only `tool_choice: 'auto'` (the default, i.e.\n * omitted) is compatible with extended thinking.\n *\n * @param params - Turn parameters\n * @returns `{ events, nextTurn }` — stream iterable + completion promise\n * @public\n */\nexport function executeClientToolTurn(\n params: IExecuteClientToolTurnParams\n): Result<IExecuteClientToolTurnResult> {\n const {\n descriptor,\n apiKey,\n system,\n messages,\n continuationMessages,\n temperature,\n tools,\n clientTools,\n signal,\n logger,\n resolvedThinking,\n model,\n endpoint\n } = params;\n\n const splitResult = splitChatRequest(system, messages);\n if (splitResult.isFailure()) {\n return fail(splitResult.message);\n }\n const { prompt, head } = splitResult.value;\n if (prompt.attachments.length > 0 && !descriptor.acceptsImageInput) {\n return fail(`provider \"${descriptor.id}\" does not accept image input`);\n }\n\n // Build a lookup map of client tools by name for fast access.\n // Fail fast on duplicate names — silently overwriting would cause one tool\n // to shadow another with no observable signal.\n const toolsByName = new Map<string, IAiClientTool>();\n for (const tool of clientTools) {\n if (toolsByName.has(tool.config.name)) {\n return fail(`executeClientToolTurn: duplicate client tool name '${tool.config.name}'`);\n }\n toolsByName.set(tool.config.name, tool);\n }\n\n // Merge server tools and client tool configs into a single array for the provider.\n // This is the fix for P1-1: client tools were never sent to the provider because\n // the adapters only received `tools` (server tools). Both must coexist per design §2.5.\n const effectiveTools: ReadonlyArray<AiToolConfig> | undefined =\n clientTools.length > 0 ? [...(tools ?? []), ...clientTools.map((t) => t.config)] : tools;\n\n const effectiveTemperature = temperature ?? 0.7;\n const resolvedModel = model ?? resolveModel(descriptor.defaultModel);\n if (resolvedModel.length === 0) {\n return fail(`provider \"${descriptor.id}\": no model resolved; pass model or set descriptor.defaultModel`);\n }\n const baseUrlResult = resolveEffectiveBaseUrl(descriptor, endpoint);\n if (baseUrlResult.isFailure()) {\n return fail(baseUrlResult.message);\n }\n const config: IStreamApiConfig = {\n baseUrl: baseUrlResult.value,\n apiKey,\n model: resolvedModel\n };\n\n // Accumulation buffers — populated by the adapter, read by the builder.\n const anthropicBuffer = new Map<number, IAccumulatedBlock>();\n const openAiCallMap = new Map<string, IAccumulatedFunctionCall>();\n const geminiCalls: IAccumulatedGeminiFunctionCall[] = [];\n\n // Collected tool results, populated as each client-tool-call-done is processed.\n const toolResults: IToolCallResult[] = [];\n\n // Stream start: open the underlying adapter stream.\n const streamPromise: Promise<Result<AsyncIterable<IAiStreamEvent>>> = (() => {\n switch (descriptor.apiFormat) {\n case 'anthropic':\n return callAnthropicStream(\n config,\n prompt,\n head,\n effectiveTemperature,\n effectiveTools,\n logger,\n signal,\n resolvedThinking,\n anthropicBuffer,\n continuationMessages\n );\n case 'openai':\n return callOpenAiResponsesStream(\n config,\n prompt,\n /* c8 ignore next 1 - defensive: openai path requires tools; empty array fallback unreachable in practice */\n effectiveTools ?? [],\n head,\n effectiveTemperature,\n logger,\n signal,\n resolvedThinking,\n openAiCallMap,\n continuationMessages\n );\n case 'gemini':\n return callGeminiStream(\n config,\n prompt,\n head,\n effectiveTemperature,\n effectiveTools,\n logger,\n signal,\n resolvedThinking,\n geminiCalls,\n continuationMessages\n );\n /* c8 ignore next 4 - defensive coding: exhaustive switch guaranteed by TypeScript */\n default: {\n const _exhaustive: never = descriptor.apiFormat;\n return Promise.resolve(fail(`unsupported API format: ${String(_exhaustive)}`));\n }\n }\n })();\n\n // Resolve controls for `nextTurn`.\n let resolveNextTurn!: (result: Result<IAiClientToolTurnResult>) => void;\n const nextTurn = new Promise<Result<IAiClientToolTurnResult>>((resolve) => {\n resolveNextTurn = resolve;\n });\n\n // The unified-event generator: opens the stream, proxies events, executes tools.\n async function* eventGenerator(): AsyncGenerator<IAiStreamEvent> {\n const streamResult = await streamPromise;\n if (streamResult.isFailure()) {\n resolveNextTurn(fail(streamResult.message));\n yield { type: 'error', message: streamResult.message };\n return;\n }\n\n let truncated = false;\n let fullText = '';\n let streamError: string | undefined;\n\n for await (const event of streamResult.value) {\n if (event.type === 'done') {\n truncated = event.truncated;\n fullText = event.fullText;\n yield event;\n continue;\n }\n\n if (event.type === 'error') {\n streamError = event.message;\n yield event;\n continue;\n }\n\n if (event.type === 'text-delta') {\n yield event;\n continue;\n }\n\n if (event.type === 'tool-event') {\n yield event;\n continue;\n }\n\n if (event.type === 'client-tool-call-start') {\n yield event;\n continue;\n }\n\n if (event.type === 'client-tool-call-done') {\n yield event;\n\n const { toolName, callId, args } = event;\n const tool = toolsByName.get(toolName);\n\n if (!tool) {\n const errMsg = `model called unknown tool: ${toolName}`;\n const resultEvent: IAiStreamEvent = {\n type: 'client-tool-result',\n toolName,\n callId,\n result: errMsg,\n isError: true\n };\n yield resultEvent;\n toolResults.push({ toolName, callId, args, result: errMsg, isError: true });\n resolveNextTurn(fail(errMsg));\n return;\n }\n\n const validationResult = tool.config.parametersSchema.validate(args);\n if (validationResult.isFailure()) {\n const errMsg = `${toolName} (callId=${callId}): ${validationResult.message}`;\n const resultEvent: IAiStreamEvent = {\n type: 'client-tool-result',\n toolName,\n callId,\n result: errMsg,\n isError: true\n };\n yield resultEvent;\n toolResults.push({ toolName, callId, args, result: errMsg, isError: true });\n continue;\n }\n\n const executeResult = await captureAsyncResult(async () => tool.execute(validationResult.value));\n const executionResult: Result<unknown> = executeResult.isSuccess()\n ? executeResult.value\n : executeResult;\n\n if (executionResult.isFailure()) {\n const errMsg = `${toolName} (callId=${callId}): ${executionResult.message}`;\n const resultEvent: IAiStreamEvent = {\n type: 'client-tool-result',\n toolName,\n callId,\n result: errMsg,\n isError: true\n };\n yield resultEvent;\n toolResults.push({ toolName, callId, args, result: errMsg, isError: true });\n continue;\n }\n\n // JSON.stringify can throw (circular references) or return undefined\n // (e.g. for a bare `undefined` value, or a value of type `function`).\n // Either outcome violates the client-tool-result event contract, so\n // emit an isError result with diagnostic context instead.\n let resultStr: string;\n try {\n const stringified = JSON.stringify(executionResult.value);\n if (stringified === undefined) {\n const errMsg = `${toolName} (callId=${callId}): tool returned a non-serializable value (JSON.stringify produced undefined)`;\n const resultEvent: IAiStreamEvent = {\n type: 'client-tool-result',\n toolName,\n callId,\n result: errMsg,\n isError: true\n };\n yield resultEvent;\n toolResults.push({ toolName, callId, args, result: errMsg, isError: true });\n continue;\n }\n resultStr = stringified;\n } catch (e) {\n const errMsg = `${toolName} (callId=${callId}): failed to serialize tool result: ${\n (e as Error).message\n }`;\n const resultEvent: IAiStreamEvent = {\n type: 'client-tool-result',\n toolName,\n callId,\n result: errMsg,\n isError: true\n };\n yield resultEvent;\n toolResults.push({ toolName, callId, args, result: errMsg, isError: true });\n continue;\n }\n const resultEvent: IAiStreamEvent = {\n type: 'client-tool-result',\n toolName,\n callId,\n result: resultStr,\n isError: false\n };\n yield resultEvent;\n toolResults.push({ toolName, callId, args, result: resultStr, isError: false });\n continue;\n }\n\n // client-tool-result events are emitted by this layer, not the adapters — no passthrough needed.\n }\n\n // Stream has ended. Build the continuation.\n if (streamError !== undefined) {\n resolveNextTurn(fail(streamError));\n return;\n }\n\n if (toolResults.length === 0) {\n resolveNextTurn(succeed({ continuation: undefined, truncated, fullText }));\n return;\n }\n\n let continuation: IAiClientToolContinuation;\n switch (descriptor.apiFormat) {\n case 'anthropic':\n continuation = buildAnthropicContinuation(anthropicBuffer, toolResults);\n break;\n case 'openai':\n continuation = buildOpenAiContinuation(openAiCallMap, toolResults);\n break;\n case 'gemini':\n continuation = buildGeminiContinuation(geminiCalls, toolResults);\n break;\n /* c8 ignore next 5 - defensive coding: exhaustive switch guaranteed by TypeScript */\n default: {\n const _exhaustive: never = descriptor.apiFormat;\n resolveNextTurn(fail(`unsupported API format: ${String(_exhaustive)}`));\n return;\n }\n }\n\n // Prepend inbound continuationMessages so the returned continuation is cumulative.\n // A consumer that does `tail = outcome.continuation.messages` (replace) is then\n // correct for all N rounds — messages always contains the full wire tail from\n // round 1 through the current round. toolCallsSummary stays per-round (this\n // round's calls only); messages is the accumulated tail to re-send.\n if (continuationMessages && continuationMessages.length > 0) {\n continuation = { ...continuation, messages: [...continuationMessages, ...continuation.messages] };\n }\n\n resolveNextTurn(succeed({ continuation, truncated, fullText }));\n }\n\n return succeed({\n events: { [Symbol.asyncIterator]: () => eventGenerator() },\n nextTurn\n });\n}\n"]}
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 AiServerToolConfig,\n type AiToolConfig,\n type IAiClientTool,\n type IAiClientToolContinuation,\n type IAiClientToolTurnResult,\n type IAiStreamEvent,\n type IChatRequest,\n type IAiProviderDescriptor,\n resolveProviderModel\n} from '../model';\nimport { type IResolvedThinkingConfig } from '../thinkingOptionsResolver';\nimport { splitChatRequest } from '../chatRequestBuilders';\nimport { resolveEffectiveBaseUrl } from '../endpoint';\nimport { type IAccumulatedBlock } from './anthropic';\nimport { type IAccumulatedFunctionCall } from './openaiResponses';\nimport { type IAccumulatedGeminiFunctionCall } from './gemini';\nimport { callAnthropicStream } from './anthropic';\nimport { callOpenAiResponsesStream } from './openaiResponses';\nimport { callGeminiStream } from './gemini';\nimport { type IStreamApiConfig } from './common';\n\n// ============================================================================\n// Tool-result accumulation (internal)\n// ============================================================================\n\n/**\n * Accumulated result for a single tool call, collected during stream iteration.\n * @internal\n */\ninterface IToolCallResult {\n readonly toolName: string;\n readonly callId?: string;\n readonly args: JsonObject;\n readonly result: string;\n readonly isError: boolean;\n}\n\n// ============================================================================\n// Anthropic continuation builder\n// ============================================================================\n\n/**\n * Returns `true` when `id` is a usable correlation id — present and non-empty.\n *\n * `??` is NOT sufficient for this check: an empty string is a *bad* id (a\n * `tool_use_id: ''` is rejected by Anthropic as a \"malformed identifier\") but\n * `'' ?? fallback` yields `''`, silently passing the empty id through. Every id\n * correlation in this module must use this predicate, never `??`.\n *\n * @internal\n */\nfunction isUsableId(id: string | undefined): id is string {\n return id !== undefined && id.length > 0;\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 * **Single source of truth for the id (id-correlation fix).** Each\n * `tool_result.tool_use_id` is drawn from — and validated against — the set of\n * assistant `tool_use.id`s actually present in the accumulation buffer. The id\n * is NEVER derived from the tool name: a tool name can never match a `toolu_*`\n * id, so a name fallback produces exactly the \"malformed identifier\" the\n * provider rejects. If any tool result cannot be correlated to a buffered\n * `tool_use` block with a non-empty id, the build fails loud (naming the tool)\n * rather than emitting a malformed continuation.\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 logger?: Logging.ILogger\n): Result<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 // The set of assistant tool_use ids — the single source of truth every\n // tool_result.tool_use_id must be drawn from.\n const bufferedToolUseIds = new Set<string>();\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 // A buffered tool_use with an empty id can never be referenced by a valid\n // tool_result; emitting it would corrupt the assistant turn. Fail loud.\n if (!isUsableId(block.id)) {\n return fail(\n `Anthropic continuation: buffered tool_use block for tool '${block.name}' has an empty id; ` +\n `cannot build a valid continuation`\n );\n }\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 bufferedToolUseIds.add(block.id);\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. Correlate each\n // result to a buffered tool_use id — the SAME id that keys the assistant\n // tool_use block — and fail loud on a missing / empty / mismatched id. A\n // missing callId is a bug to surface, not paper over with a name fallback.\n const userContent: JsonArray = [];\n for (const r of toolResults) {\n if (!isUsableId(r.callId)) {\n return fail(\n `Anthropic continuation: tool '${r.toolName}' result has no call id (missing or empty); ` +\n `cannot correlate it to an assistant tool_use block`\n );\n }\n if (!bufferedToolUseIds.has(r.callId)) {\n return fail(\n `Anthropic continuation: tool '${r.toolName}' call id '${r.callId}' does not match any ` +\n `buffered assistant tool_use block id`\n );\n }\n const block: JsonObject = {\n type: 'tool_result',\n // eslint-disable-next-line @typescript-eslint/naming-convention\n tool_use_id: r.callId,\n content: r.result\n };\n userContent.push(r.isError ? { ...block, is_error: true } : block);\n }\n\n // Decisive diagnostic: the tool_use.id ↔ tool_result.tool_use_id pairing for\n // the outgoing continuation. A failing turn is field-confirmable by capturing\n // this line (the pairing is by construction matched here — divergence fails\n // loud above, before reaching this point).\n if (logger) {\n const pairing = toolResults.map((r) => `${r.toolName}:${r.callId}`).join(', ');\n logger.detail(`ai-assist:anthropic-continuation tool_use.id↔tool_result.tool_use_id [${pairing}]`);\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 succeed({\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 * **Single source of truth for the id (id-correlation fix).** Each\n * `function_call_output.call_id` is drawn from — and validated against — the\n * set of `function_call.call_id`s in the accumulation map. It is NEVER derived\n * from the tool name (the OpenAI parity of the Anthropic `tool_use_id` fix). A\n * missing / empty / unmatched call id fails the build loud rather than emitting\n * a continuation the provider would reject.\n *\n * @internal\n */\nexport function buildOpenAiContinuation(\n calls: Map<string, IAccumulatedFunctionCall>,\n toolResults: IToolCallResult[],\n logger?: Logging.ILogger\n): Result<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 const bufferedCallIds = new Set<string>();\n for (const [callId, call] of calls) {\n bufferedCallIds.add(callId);\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). Correlate each\n // to a buffered function_call call_id — never the tool name — and fail loud on\n // a missing / empty / mismatched id.\n for (const r of toolResults) {\n if (!isUsableId(r.callId)) {\n return fail(\n `OpenAI continuation: tool '${r.toolName}' result has no call id (missing or empty); ` +\n `cannot correlate it to a function_call item`\n );\n }\n if (!bufferedCallIds.has(r.callId)) {\n return fail(\n `OpenAI continuation: tool '${r.toolName}' call id '${r.callId}' does not match any ` +\n `accumulated function_call call_id`\n );\n }\n items.push({\n type: 'function_call_output',\n call_id: r.callId,\n output: r.result\n });\n }\n\n // Decisive diagnostic: the function_call.call_id ↔ function_call_output.call_id\n // pairing for the outgoing continuation (matched by construction here).\n if (logger) {\n const pairing = toolResults.map((r) => `${r.toolName}:${r.callId}`).join(', ');\n logger.detail(`ai-assist:openai-continuation function_call.call_id↔output.call_id [${pairing}]`);\n }\n\n return succeed({\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 * When thinking is enabled, Gemini stamps an opaque `thoughtSignature` on each\n * `functionCall` part and requires it echoed back, verbatim, as a sibling of\n * `functionCall` on the continuation's model turn — otherwise the follow-up is\n * rejected with \"Function call is missing a thought_signature in functionCall\n * parts\". The captured signature (see {@link IAccumulatedGeminiFunctionCall})\n * is replayed here only when present; the key is omitted entirely when thinking\n * was disabled. See https://ai.google.dev/gemini-api/docs/thought-signatures.\n *\n * @internal\n */\nexport function buildGeminiContinuation(\n calls: IAccumulatedGeminiFunctionCall[],\n toolResults: IToolCallResult[]\n): IAiClientToolContinuation {\n // Model turn: functionCall parts for each call. Replay the part-level\n // thoughtSignature as a sibling of functionCall only when present.\n const modelParts: JsonArray = calls.map(\n (call): JsonObject => ({\n functionCall: {\n name: call.name,\n args: call.args\n },\n ...(call.thoughtSignature !== undefined ? { thoughtSignature: call.thoughtSignature } : {})\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 * Decision returned by an {@link IExecuteClientToolTurnParams.onBeforeToolExecute}\n * gate for a single client-tool call.\n *\n * @remarks\n * - `{ action: 'proceed' }` — run the tool's `execute` normally.\n * - `{ action: 'deny', reason }` — do NOT run `execute`; a synthesized tool-result\n * carrying `reason` is fed into the continuation exactly like a normal result so\n * the model can react, and a `client-tool-result` event is emitted for the call.\n * The turn continues.\n *\n * A gate that wants to signal a hard error (as opposed to a deliberate deny) should\n * return `Result.fail` (or reject) from the callback. Note this is NOT the same as a\n * `tool.execute` failure: an `execute` failure yields an `isError` tool-result and the\n * **turn continues**, whereas a gate `fail`/reject **terminates the turn** — it fails\n * `nextTurn` (and emits an inline `error` event) rather than synthesizing a deny.\n *\n * @public\n */\nexport type IToolExecutionDecision =\n | { readonly action: 'proceed' }\n | { readonly action: 'deny'; readonly reason: string };\n\n/**\n * Parameters for {@link AiAssist.executeClientToolTurn}.\n *\n * @remarks\n * Carries the unified {@link AiAssist.IChatRequest} shape (`system?` + ordered\n * `messages`): the last message is the current user turn and the preceding\n * messages are history, linearized before the current turn — identically to the\n * completion and streaming paths. {@link IExecuteClientToolTurnParams.continuationMessages}\n * remains a distinct post-current-turn axis (see below).\n *\n * @public\n */\nexport interface IExecuteClientToolTurnParams extends IChatRequest {\n /** The provider descriptor for routing (Anthropic / OpenAI / Gemini). */\n readonly descriptor: IAiProviderDescriptor;\n /** API key for authentication. */\n readonly apiKey: string;\n /**\n * The cumulative provider-native wire tail from the previous turn's\n * {@link AiAssist.IAiClientToolContinuation.messages}. Supply this as-is\n * each round — `messages` is already cumulative, so **replace** rather\n * than manually concatenate:\n *\n * ```ts\n * tail = outcome.continuation.messages; // replace — already cumulative\n * ```\n *\n * On the first turn this should be `undefined` (or omitted).\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\n * guards 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 * Do NOT place these objects in the `messages` parameter — the\n * normalized-message path strips provider-native fields.\n */\n readonly continuationMessages?: ReadonlyArray<JsonObject>;\n /**\n * Sampling temperature. Sent to the provider only when explicitly provided; omitted otherwise\n * so the provider's own default applies (current-gen models reject a caller-supplied default).\n */\n readonly temperature?: number;\n /** Server-side tools to include. */\n readonly tools?: ReadonlyArray<AiServerToolConfig>;\n /** Client-defined tools available for the model to call. */\n readonly clientTools: ReadonlyArray<IAiClientTool>;\n /** Optional abort signal. */\n readonly signal?: AbortSignal;\n /**\n * Optional override of the descriptor's default base URL. Same semantics as\n * the non-streaming completion path and `callProviderCompletionStream`: a\n * well-formed `http`/`https` URL is substituted for `descriptor.baseUrl`\n * when composing the per-format request, with the per-format suffix appended\n * unchanged. Validated at the dispatcher; auth shape is unaffected. Use this\n * to point a client-tool turn at a local / LAN OpenAI-compatible server\n * (Ollama, LM Studio, llama.cpp).\n */\n readonly endpoint?: string;\n /** Optional logger for diagnostics. */\n readonly logger?: Logging.ILogger;\n /** Optional resolved thinking config (pre-resolved by the caller). */\n readonly resolvedThinking?: IResolvedThinkingConfig;\n /** Resolved model string (pre-resolved by the caller). When omitted, uses the descriptor's default model. */\n readonly model?: string;\n /**\n * Optional host gate invoked for each client-tool call **after** the model's raw\n * args have been validated against the tool's `parametersSchema` and **before**\n * `tool.execute(...)` runs. The full `tool` (including `tool.config.annotations`)\n * and the validated `args` are passed so gate logic can key off a tool's behavior\n * annotations (e.g. deny a `destructiveHint` call pending confirmation).\n *\n * Returning `{ action: 'proceed' }` (or omitting the callback) runs `execute`\n * normally. Returning `{ action: 'deny', reason }` skips `execute` and synthesizes\n * a denial tool-result carrying `reason` into the continuation (the model sees the\n * denial and can react); **the turn continues**. A callback that returns `Result.fail`\n * or rejects is a hard error that **terminates the turn** (fails `nextTurn` + emits an\n * inline `error` event) — distinct from a `tool.execute` failure, which continues the\n * turn with an `isError` result. A deny must therefore be explicit.\n */\n readonly onBeforeToolExecute?: (\n tool: IAiClientTool,\n args: unknown\n ) => Promise<Result<IToolExecutionDecision>>;\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 * True when a request would combine Gemini built-in grounding (a `web_search`\n * server tool) with client (function) tools — a combination Gemini's\n * `generateContent` API rejects with HTTP 400 (`INVALID_ARGUMENT`). Callers gate\n * on `descriptor.apiFormat === 'gemini'` before consulting this; other providers\n * accept the mix. Kept as a pure predicate so the conflict rule is unit-testable\n * without a live stream.\n *\n * @internal\n */\nexport function hasGeminiToolConflict(\n tools: ReadonlyArray<AiServerToolConfig> | undefined,\n clientTools: ReadonlyArray<IAiClientTool>\n): boolean {\n return clientTools.length > 0 && (tools?.some((t) => t.type === 'web_search') ?? false);\n}\n\n/**\n * Orchestrates a single client-tool streaming turn for any supported provider.\n *\n * Starts a streaming request, iterates the underlying provider stream, and:\n * - Forwards `text-delta`, `tool-event`, `client-tool-call-start`, and\n * `client-tool-call-done` events through to the consumer.\n * - For each `client-tool-call-done` event: validates the raw args against the\n * tool's `parametersSchema`, invokes `execute(typedArgs)`, and emits a\n * `client-tool-result` event.\n * - After stream completion: builds the per-provider continuation (or\n * `{ continuation: undefined }` when no tool calls occurred) and resolves\n * `nextTurn`.\n *\n * **Anthropic constraint (E3):** The continuation for Anthropic does not set\n * a forced `tool_choice`. Only `tool_choice: 'auto'` (the default, i.e.\n * omitted) is compatible with extended thinking.\n *\n * @param params - Turn parameters\n * @returns `{ events, nextTurn }` — stream iterable + completion promise\n * @public\n */\nexport function executeClientToolTurn(\n params: IExecuteClientToolTurnParams\n): Result<IExecuteClientToolTurnResult> {\n const {\n descriptor,\n apiKey,\n system,\n messages,\n continuationMessages,\n temperature,\n tools,\n clientTools,\n signal,\n logger,\n resolvedThinking,\n model,\n endpoint,\n onBeforeToolExecute\n } = params;\n\n const splitResult = splitChatRequest(system, messages);\n if (splitResult.isFailure()) {\n return fail(splitResult.message);\n }\n const { prompt, head } = splitResult.value;\n if (prompt.attachments.length > 0 && !descriptor.acceptsImageInput) {\n return fail(`provider \"${descriptor.id}\" does not accept image input`);\n }\n\n // Build a lookup map of client tools by name for fast access.\n // Fail fast on duplicate names — silently overwriting would cause one tool\n // to shadow another with no observable signal.\n const toolsByName = new Map<string, IAiClientTool>();\n for (const tool of clientTools) {\n if (toolsByName.has(tool.config.name)) {\n return fail(`executeClientToolTurn: duplicate client tool name '${tool.config.name}'`);\n }\n toolsByName.set(tool.config.name, tool);\n }\n\n // Merge server tools and client tool configs into a single array for the provider.\n // This is the fix for P1-1: client tools were never sent to the provider because\n // the adapters only received `tools` (server tools). Both must coexist per design §2.5.\n const effectiveTools: ReadonlyArray<AiToolConfig> | undefined =\n clientTools.length > 0 ? [...(tools ?? []), ...clientTools.map((t) => t.config)] : tools;\n\n // Gemini pre-flight: its generateContent API HTTP-400s (INVALID_ARGUMENT) when\n // built-in grounding (`web_search`) and function calling (client tools) are\n // combined in one request. Fail fast with a clear, actionable message rather\n // than letting the opaque wire 400 surface. Other providers accept the mix.\n if (descriptor.apiFormat === 'gemini' && hasGeminiToolConflict(tools, clientTools)) {\n return fail(\n 'executeClientToolTurn: Gemini cannot combine web_search grounding with client (function) tools in the same request; send one or the other'\n );\n }\n\n const modelResult = resolveProviderModel(descriptor, model);\n if (modelResult.isFailure()) {\n return fail(modelResult.message);\n }\n const resolvedModel = modelResult.value;\n const baseUrlResult = resolveEffectiveBaseUrl(descriptor, endpoint);\n if (baseUrlResult.isFailure()) {\n return fail(baseUrlResult.message);\n }\n const config: IStreamApiConfig = {\n baseUrl: baseUrlResult.value,\n apiKey,\n model: resolvedModel\n };\n\n // Accumulation buffers — populated by the adapter, read by the builder.\n const anthropicBuffer = new Map<number, IAccumulatedBlock>();\n const openAiCallMap = new Map<string, IAccumulatedFunctionCall>();\n const geminiCalls: IAccumulatedGeminiFunctionCall[] = [];\n\n // Collected tool results, populated as each client-tool-call-done is processed.\n const toolResults: IToolCallResult[] = [];\n\n // Stream start: open the underlying adapter stream.\n const streamPromise: Promise<Result<AsyncIterable<IAiStreamEvent>>> = (() => {\n switch (descriptor.apiFormat) {\n case 'anthropic':\n return callAnthropicStream(\n config,\n prompt,\n head,\n temperature,\n effectiveTools,\n logger,\n signal,\n resolvedThinking,\n anthropicBuffer,\n continuationMessages\n );\n case 'openai':\n return callOpenAiResponsesStream(\n config,\n prompt,\n /* c8 ignore next 1 - defensive: openai path requires tools; empty array fallback unreachable in practice */\n effectiveTools ?? [],\n head,\n temperature,\n logger,\n signal,\n resolvedThinking,\n openAiCallMap,\n continuationMessages\n );\n case 'gemini':\n return callGeminiStream(\n config,\n prompt,\n head,\n temperature,\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 // Host gate: run after arg-validation, before execute. A `deny` decision\n // synthesizes a denial tool-result and continues the turn; a `fail`/reject\n // from the gate itself is a hard error (mirrors an execute failure), never\n // a silent deny.\n if (onBeforeToolExecute !== undefined) {\n const decisionResult: Result<IToolExecutionDecision> = (\n await captureAsyncResult(async () => onBeforeToolExecute(tool, validationResult.value))\n ).onSuccess((decision) => decision);\n\n if (decisionResult.isFailure()) {\n const errMsg = `${toolName} (callId=${callId}): ${decisionResult.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 // Gate-fail is a hard, turn-terminating error (unlike a non-fatal `deny`, whose\n // generator continues). Emit an explicit `error` event so a consumer watching only\n // `events` sees the fatal failure inline and can distinguish it from a deny — matching\n // the stream-open / continuation hard-error paths.\n yield { type: 'error', message: errMsg };\n toolResults.push({ toolName, callId, args, result: errMsg, isError: true });\n resolveNextTurn(fail(errMsg));\n return;\n }\n\n if (decisionResult.value.action === 'deny') {\n const denialMsg = `${toolName} (callId=${callId}): tool execution denied: ${decisionResult.value.reason}`;\n const resultEvent: IAiStreamEvent = {\n type: 'client-tool-result',\n toolName,\n callId,\n result: denialMsg,\n isError: true\n };\n yield resultEvent;\n toolResults.push({ toolName, callId, args, result: denialMsg, isError: true });\n continue;\n }\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 continuationResult: Result<IAiClientToolContinuation>;\n switch (descriptor.apiFormat) {\n case 'anthropic':\n continuationResult = buildAnthropicContinuation(anthropicBuffer, toolResults, logger);\n break;\n case 'openai':\n continuationResult = buildOpenAiContinuation(openAiCallMap, toolResults, logger);\n break;\n case 'gemini':\n // Gemini correlates by tool name by design (no call ids) — its builder\n // cannot mis-key and stays non-fallible.\n continuationResult = succeed(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 // A bad id-correlation fails loud here rather than emitting a malformed\n // continuation the provider would reject as a \"malformed identifier\". Mirror\n // the stream-open-failure path above: surface an `error` event so a consumer\n // iterating `events` sees the failure inline, not only via `nextTurn`.\n if (continuationResult.isFailure()) {\n resolveNextTurn(fail(continuationResult.message));\n yield { type: 'error', message: continuationResult.message };\n return;\n }\n let continuation = continuationResult.value;\n\n // Prepend inbound continuationMessages so the returned continuation is cumulative.\n // A consumer that does `tail = outcome.continuation.messages` (replace) is then\n // correct for all N rounds — messages always contains the full wire tail from\n // round 1 through the current round. toolCallsSummary stays per-round (this\n // round's calls only); messages is the accumulated tail to re-send.\n if (continuationMessages && continuationMessages.length > 0) {\n continuation = { ...continuation, messages: [...continuationMessages, ...continuation.messages] };\n }\n\n resolveNextTurn(succeed({ continuation, truncated, fullText }));\n }\n\n return succeed({\n events: { [Symbol.asyncIterator]: () => eventGenerator() },\n nextTurn\n });\n}\n"]}