@fgv/ts-extras 5.1.0-32 → 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.
- package/dist/packlets/ai-assist/apiClient.js +4 -4
- package/dist/packlets/ai-assist/apiClient.js.map +1 -1
- package/dist/packlets/ai-assist/chatRequestBuilders.js +86 -3
- package/dist/packlets/ai-assist/chatRequestBuilders.js.map +1 -1
- package/dist/packlets/ai-assist/converters.js +31 -1
- package/dist/packlets/ai-assist/converters.js.map +1 -1
- package/dist/packlets/ai-assist/index.js +3 -2
- package/dist/packlets/ai-assist/index.js.map +1 -1
- package/dist/packlets/ai-assist/model.js.map +1 -1
- package/dist/packlets/ai-assist/streamingAdapters/anthropic.js +176 -32
- package/dist/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -1
- package/dist/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js +511 -0
- package/dist/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js.map +1 -0
- package/dist/packlets/ai-assist/streamingAdapters/common.js +95 -0
- package/dist/packlets/ai-assist/streamingAdapters/common.js.map +1 -1
- package/dist/packlets/ai-assist/streamingAdapters/gemini.js +34 -10
- package/dist/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -1
- package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js +215 -15
- package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -1
- package/dist/packlets/ai-assist/streamingClient.js +18 -0
- package/dist/packlets/ai-assist/streamingClient.js.map +1 -1
- package/dist/packlets/ai-assist/thinkingOptionsResolver.js +23 -0
- package/dist/packlets/ai-assist/thinkingOptionsResolver.js.map +1 -1
- package/dist/packlets/ai-assist/toolFormats.js +106 -10
- package/dist/packlets/ai-assist/toolFormats.js.map +1 -1
- package/dist/packlets/crypto-utils/index.browser.js +3 -2
- package/dist/packlets/crypto-utils/index.browser.js.map +1 -1
- package/dist/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.js +287 -0
- package/dist/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.js.map +1 -0
- package/dist/packlets/crypto-utils/keystore/index.browser.js +36 -0
- package/dist/packlets/crypto-utils/keystore/index.browser.js.map +1 -0
- package/dist/packlets/crypto-utils/keystore/index.js +2 -0
- package/dist/packlets/crypto-utils/keystore/index.js.map +1 -1
- package/dist/packlets/crypto-utils/keystore/privateKeyStorage.js.map +1 -1
- package/dist/ts-extras.d.ts +492 -6
- package/lib/packlets/ai-assist/apiClient.d.ts.map +1 -1
- package/lib/packlets/ai-assist/apiClient.js +3 -3
- package/lib/packlets/ai-assist/apiClient.js.map +1 -1
- package/lib/packlets/ai-assist/chatRequestBuilders.d.ts +29 -5
- package/lib/packlets/ai-assist/chatRequestBuilders.d.ts.map +1 -1
- package/lib/packlets/ai-assist/chatRequestBuilders.js +86 -3
- package/lib/packlets/ai-assist/chatRequestBuilders.js.map +1 -1
- package/lib/packlets/ai-assist/converters.d.ts +9 -1
- package/lib/packlets/ai-assist/converters.d.ts.map +1 -1
- package/lib/packlets/ai-assist/converters.js +31 -1
- package/lib/packlets/ai-assist/converters.js.map +1 -1
- package/lib/packlets/ai-assist/index.d.ts +4 -3
- package/lib/packlets/ai-assist/index.d.ts.map +1 -1
- package/lib/packlets/ai-assist/index.js +5 -1
- package/lib/packlets/ai-assist/index.js.map +1 -1
- package/lib/packlets/ai-assist/model.d.ts +183 -3
- package/lib/packlets/ai-assist/model.d.ts.map +1 -1
- package/lib/packlets/ai-assist/model.js.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts +58 -5
- package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/anthropic.js +175 -31
- package/lib/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.d.ts +158 -0
- package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.d.ts.map +1 -0
- package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js +517 -0
- package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js.map +1 -0
- package/lib/packlets/ai-assist/streamingAdapters/common.d.ts +51 -0
- package/lib/packlets/ai-assist/streamingAdapters/common.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/common.js +97 -0
- package/lib/packlets/ai-assist/streamingAdapters/common.js.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts +16 -2
- package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/gemini.js +34 -10
- package/lib/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts +15 -2
- package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js +214 -14
- package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -1
- package/lib/packlets/ai-assist/streamingClient.d.ts +17 -0
- package/lib/packlets/ai-assist/streamingClient.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingClient.js +20 -1
- package/lib/packlets/ai-assist/streamingClient.js.map +1 -1
- package/lib/packlets/ai-assist/thinkingOptionsResolver.d.ts +18 -2
- package/lib/packlets/ai-assist/thinkingOptionsResolver.d.ts.map +1 -1
- package/lib/packlets/ai-assist/thinkingOptionsResolver.js +24 -0
- package/lib/packlets/ai-assist/thinkingOptionsResolver.js.map +1 -1
- package/lib/packlets/ai-assist/toolFormats.d.ts +40 -9
- package/lib/packlets/ai-assist/toolFormats.d.ts.map +1 -1
- package/lib/packlets/ai-assist/toolFormats.js +107 -10
- package/lib/packlets/ai-assist/toolFormats.js.map +1 -1
- package/lib/packlets/crypto-utils/index.browser.d.ts +1 -1
- package/lib/packlets/crypto-utils/index.browser.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/index.browser.js +3 -2
- package/lib/packlets/crypto-utils/index.browser.js.map +1 -1
- package/lib/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.d.ts +148 -0
- package/lib/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.d.ts.map +1 -0
- package/lib/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.js +324 -0
- package/lib/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.js.map +1 -0
- package/lib/packlets/crypto-utils/keystore/index.browser.d.ts +10 -0
- package/lib/packlets/crypto-utils/keystore/index.browser.d.ts.map +1 -0
- package/lib/packlets/crypto-utils/keystore/index.browser.js +76 -0
- package/lib/packlets/crypto-utils/keystore/index.browser.js.map +1 -0
- package/lib/packlets/crypto-utils/keystore/index.d.ts +1 -0
- package/lib/packlets/crypto-utils/keystore/index.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/keystore/index.js +4 -1
- package/lib/packlets/crypto-utils/keystore/index.js.map +1 -1
- package/lib/packlets/crypto-utils/keystore/privateKeyStorage.d.ts +6 -3
- package/lib/packlets/crypto-utils/keystore/privateKeyStorage.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/keystore/privateKeyStorage.js.map +1 -1
- package/package.json +15 -10
|
@@ -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
|