@fgv/ts-extras 5.1.0-33 → 5.1.0-35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/packlets/ai-assist/apiClient.js +58 -112
- package/dist/packlets/ai-assist/apiClient.js.map +1 -1
- package/dist/packlets/ai-assist/chatRequestBuilders.js +131 -35
- 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/embeddingClient.js +346 -0
- package/dist/packlets/ai-assist/embeddingClient.js.map +1 -0
- package/dist/packlets/ai-assist/http.js +75 -0
- package/dist/packlets/ai-assist/http.js.map +1 -0
- package/dist/packlets/ai-assist/index.js +6 -4
- package/dist/packlets/ai-assist/index.js.map +1 -1
- package/dist/packlets/ai-assist/jsonCompletion.js +6 -8
- package/dist/packlets/ai-assist/jsonCompletion.js.map +1 -1
- package/dist/packlets/ai-assist/model.js +36 -1
- package/dist/packlets/ai-assist/model.js.map +1 -1
- package/dist/packlets/ai-assist/registry.js +77 -7
- package/dist/packlets/ai-assist/registry.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 +528 -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/streamingAdapters/proxy.js +15 -8
- package/dist/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -1
- package/dist/packlets/ai-assist/streamingClient.js +29 -5
- 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/ts-extras.d.ts +682 -48
- package/lib/packlets/ai-assist/apiClient.d.ts +24 -34
- package/lib/packlets/ai-assist/apiClient.d.ts.map +1 -1
- package/lib/packlets/ai-assist/apiClient.js +67 -121
- package/lib/packlets/ai-assist/apiClient.js.map +1 -1
- package/lib/packlets/ai-assist/chatRequestBuilders.d.ts +82 -22
- package/lib/packlets/ai-assist/chatRequestBuilders.d.ts.map +1 -1
- package/lib/packlets/ai-assist/chatRequestBuilders.js +132 -34
- 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/embeddingClient.d.ts +69 -0
- package/lib/packlets/ai-assist/embeddingClient.d.ts.map +1 -0
- package/lib/packlets/ai-assist/embeddingClient.js +350 -0
- package/lib/packlets/ai-assist/embeddingClient.js.map +1 -0
- package/lib/packlets/ai-assist/http.d.ts +24 -0
- package/lib/packlets/ai-assist/http.d.ts.map +1 -0
- package/lib/packlets/ai-assist/http.js +78 -0
- package/lib/packlets/ai-assist/http.js.map +1 -0
- package/lib/packlets/ai-assist/index.d.ts +6 -4
- package/lib/packlets/ai-assist/index.d.ts.map +1 -1
- package/lib/packlets/ai-assist/index.js +11 -1
- package/lib/packlets/ai-assist/index.js.map +1 -1
- package/lib/packlets/ai-assist/jsonCompletion.d.ts.map +1 -1
- package/lib/packlets/ai-assist/jsonCompletion.js +6 -8
- package/lib/packlets/ai-assist/jsonCompletion.js.map +1 -1
- package/lib/packlets/ai-assist/model.d.ts +377 -5
- package/lib/packlets/ai-assist/model.d.ts.map +1 -1
- package/lib/packlets/ai-assist/model.js +37 -2
- package/lib/packlets/ai-assist/model.js.map +1 -1
- package/lib/packlets/ai-assist/registry.d.ts +23 -1
- package/lib/packlets/ai-assist/registry.d.ts.map +1 -1
- package/lib/packlets/ai-assist/registry.js +79 -7
- package/lib/packlets/ai-assist/registry.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 +172 -0
- package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.d.ts.map +1 -0
- package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js +534 -0
- package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js.map +1 -0
- package/lib/packlets/ai-assist/streamingAdapters/common.d.ts +59 -11
- 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/streamingAdapters/proxy.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/proxy.js +14 -7
- package/lib/packlets/ai-assist/streamingAdapters/proxy.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 +31 -6
- 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/package.json +7 -7
|
@@ -43,6 +43,9 @@ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _ar
|
|
|
43
43
|
* Completions doesn't support tool progress events, but the Responses API
|
|
44
44
|
* does.
|
|
45
45
|
*
|
|
46
|
+
* Client-defined tools (`function_call` type) are accumulated per call ID
|
|
47
|
+
* and emitted as `client-tool-call-done` events when complete.
|
|
48
|
+
*
|
|
46
49
|
* @packageDocumentation
|
|
47
50
|
*/
|
|
48
51
|
import { succeed, Validators } from '@fgv/ts-utils';
|
|
@@ -50,12 +53,31 @@ import { buildMessages, buildOpenAiResponsesUserContent } from '../chatRequestBu
|
|
|
50
53
|
import { bearerAuthHeader } from '../endpoint';
|
|
51
54
|
import { parseSseEventJson, readSseEvents } from '../sseParser';
|
|
52
55
|
import { toResponsesApiTools } from '../toolFormats';
|
|
53
|
-
import { openSseConnection, validateEventPayload } from './common';
|
|
56
|
+
import { UNRECOGNIZED_EVENT_WARN_TAG, formatUnrecognizedEventPayloadPreview, openSseConnection, validateEventPayload } from './common';
|
|
54
57
|
const responsesDeltaPayload = Validators.object({
|
|
55
58
|
delta: Validators.string
|
|
56
59
|
});
|
|
60
|
+
const responsesOutputItemInner = Validators.object({
|
|
61
|
+
type: Validators.string.optional(),
|
|
62
|
+
id: Validators.string.optional(),
|
|
63
|
+
name: Validators.string.optional(),
|
|
64
|
+
call_id: Validators.string.optional()
|
|
65
|
+
}, { options: { optionalFields: ['type', 'id', 'name', 'call_id'] } });
|
|
66
|
+
const responsesOutputItemAddedPayload = Validators.object({ item: responsesOutputItemInner });
|
|
67
|
+
const responsesFunctionCallArgsDeltaPayload = Validators.object({
|
|
68
|
+
item_id: Validators.string,
|
|
69
|
+
delta: Validators.string
|
|
70
|
+
});
|
|
71
|
+
const responsesFunctionCallArgsDonePayload = Validators.object({
|
|
72
|
+
item_id: Validators.string,
|
|
73
|
+
arguments: Validators.string
|
|
74
|
+
});
|
|
75
|
+
const responsesIncompleteDetails = Validators.object({ reason: Validators.string.optional() }, { options: { optionalFields: ['reason'] } });
|
|
57
76
|
const responsesCompletedPayload = Validators.object({
|
|
58
|
-
response: Validators.object({
|
|
77
|
+
response: Validators.object({
|
|
78
|
+
status: Validators.string.optional(),
|
|
79
|
+
incomplete_details: responsesIncompleteDetails.optional()
|
|
80
|
+
}, { options: { optionalFields: ['status', 'incomplete_details'] } })
|
|
59
81
|
});
|
|
60
82
|
const responsesErrorInner = Validators.object({ message: Validators.string.optional() }, { options: { optionalFields: ['message'] } });
|
|
61
83
|
const responsesErrorPayload = Validators.object({
|
|
@@ -65,26 +87,117 @@ const responsesErrorPayload = Validators.object({
|
|
|
65
87
|
// ============================================================================
|
|
66
88
|
// Stream translator
|
|
67
89
|
// ============================================================================
|
|
90
|
+
/**
|
|
91
|
+
* Recognized OpenAI / xAI Responses API SSE event names. An event in this set is either
|
|
92
|
+
* handled below or intentionally ignored (lifecycle / reasoning content discarded by design /
|
|
93
|
+
* server-tool channels not yet surfaced). Any event whose name is NOT in this set surfaces
|
|
94
|
+
* a one-time `logger.warn` per stream — making provider drift (new event types from a model
|
|
95
|
+
* update) visible empirically instead of silently no-op'd.
|
|
96
|
+
*
|
|
97
|
+
* Source: official `openai-node` SDK source (`src/resources/responses/responses.ts`)
|
|
98
|
+
* event-type literal-string sweep, plus the xAI Responses superset. Add to this set when a
|
|
99
|
+
* new event type is observed and confirmed safe to ignore.
|
|
100
|
+
*
|
|
101
|
+
* @internal
|
|
102
|
+
*/
|
|
103
|
+
const RECOGNIZED_OPENAI_RESPONSES_EVENTS = new Set([
|
|
104
|
+
// ---- handled by the translator ----
|
|
105
|
+
'response.output_text.delta',
|
|
106
|
+
'response.web_search_call.in_progress',
|
|
107
|
+
'response.web_search_call.completed',
|
|
108
|
+
'response.output_item.added',
|
|
109
|
+
'response.function_call_arguments.delta',
|
|
110
|
+
'response.function_call_arguments.done',
|
|
111
|
+
'response.completed',
|
|
112
|
+
'response.failed',
|
|
113
|
+
'error',
|
|
114
|
+
// ---- lifecycle events: intentionally silent ----
|
|
115
|
+
'response.created',
|
|
116
|
+
'response.in_progress',
|
|
117
|
+
'response.queued',
|
|
118
|
+
'response.incomplete',
|
|
119
|
+
// ---- text content lifecycle: handled implicitly via delta accumulation ----
|
|
120
|
+
'response.output_text.done',
|
|
121
|
+
'response.output_text.annotation.added',
|
|
122
|
+
'response.content_part.added',
|
|
123
|
+
'response.content_part.done',
|
|
124
|
+
'response.output_item.done',
|
|
125
|
+
// ---- reasoning content: discarded by design (see ai-assist-thinking-events follow-on) ----
|
|
126
|
+
'response.reasoning_summary_part.added',
|
|
127
|
+
'response.reasoning_summary_part.done',
|
|
128
|
+
'response.reasoning_summary_text.delta',
|
|
129
|
+
'response.reasoning_summary_text.done',
|
|
130
|
+
'response.reasoning_text.delta',
|
|
131
|
+
'response.reasoning_text.done',
|
|
132
|
+
// ---- refusals: caller currently sees these via the model's final text ----
|
|
133
|
+
'response.refusal.delta',
|
|
134
|
+
'response.refusal.done',
|
|
135
|
+
// ---- server-tool channels not surfaced as tool-event yet ----
|
|
136
|
+
'response.web_search_call.searching',
|
|
137
|
+
'response.file_search_call.in_progress',
|
|
138
|
+
'response.file_search_call.searching',
|
|
139
|
+
'response.file_search_call.completed',
|
|
140
|
+
'response.code_interpreter_call.in_progress',
|
|
141
|
+
'response.code_interpreter_call.interpreting',
|
|
142
|
+
'response.code_interpreter_call.completed',
|
|
143
|
+
'response.code_interpreter_call_code.delta',
|
|
144
|
+
'response.code_interpreter_call_code.done',
|
|
145
|
+
'response.image_generation_call.in_progress',
|
|
146
|
+
'response.image_generation_call.generating',
|
|
147
|
+
'response.image_generation_call.completed',
|
|
148
|
+
'response.image_generation_call.partial_image',
|
|
149
|
+
'response.mcp_call.in_progress',
|
|
150
|
+
'response.mcp_call.completed',
|
|
151
|
+
'response.mcp_call.failed',
|
|
152
|
+
'response.mcp_call_arguments.delta',
|
|
153
|
+
'response.mcp_call_arguments.done',
|
|
154
|
+
'response.mcp_list_tools.in_progress',
|
|
155
|
+
'response.mcp_list_tools.completed',
|
|
156
|
+
'response.mcp_list_tools.failed',
|
|
157
|
+
'response.custom_tool_call_input.delta',
|
|
158
|
+
'response.custom_tool_call_input.done',
|
|
159
|
+
// ---- audio (not used in chat/tool flows here) ----
|
|
160
|
+
'response.audio.delta',
|
|
161
|
+
'response.audio.done',
|
|
162
|
+
'response.audio.transcript.delta',
|
|
163
|
+
'response.audio.transcript.done'
|
|
164
|
+
]);
|
|
68
165
|
/**
|
|
69
166
|
* Translates an OpenAI Responses API SSE stream into unified events.
|
|
70
167
|
*
|
|
168
|
+
* Maintains a per-call-ID accumulation map for client-defined function calls.
|
|
169
|
+
* The map is exposed via the passed-in `functionCallMap` parameter for the
|
|
170
|
+
* C3 continuation builder to read after the stream completes.
|
|
171
|
+
*
|
|
172
|
+
* Unrecognized event names are reported once per stream via `logger?.warn` —
|
|
173
|
+
* see {@link RECOGNIZED_OPENAI_RESPONSES_EVENTS}.
|
|
174
|
+
*
|
|
71
175
|
* @internal
|
|
72
176
|
*/
|
|
73
|
-
function translateOpenAiResponsesStream(response) {
|
|
177
|
+
function translateOpenAiResponsesStream(response, functionCallMap, logger) {
|
|
74
178
|
return __asyncGenerator(this, arguments, function* translateOpenAiResponsesStream_1() {
|
|
75
179
|
var _a, e_1, _b, _c;
|
|
76
|
-
var _d, _e, _f;
|
|
180
|
+
var _d, _e, _f, _g, _h;
|
|
77
181
|
let fullText = '';
|
|
78
182
|
let truncated = false;
|
|
79
183
|
let completed = false;
|
|
184
|
+
let incompleteReason;
|
|
185
|
+
// OpenAI / xAI Responses API emits function_call_arguments.{delta,done} events keyed by
|
|
186
|
+
// `item_id` (the fc_* output-item id). The harness and continuation builder key by
|
|
187
|
+
// `call_id` (the call_* id). This map correlates the two — populated when the
|
|
188
|
+
// function_call item is announced in `response.output_item.added`.
|
|
189
|
+
const itemIdToCallId = new Map();
|
|
190
|
+
// Track unrecognized event names we have already warned about, so a hot stream of
|
|
191
|
+
// an unknown event type produces exactly one log line per name per stream.
|
|
192
|
+
const warnedEvents = new Set();
|
|
80
193
|
try {
|
|
81
194
|
/* c8 ignore next - body is non-null at this point per openSseConnection */
|
|
82
195
|
if (!response.body)
|
|
83
196
|
return yield __await(void 0);
|
|
84
197
|
try {
|
|
85
|
-
for (var
|
|
86
|
-
_c =
|
|
87
|
-
|
|
198
|
+
for (var _j = true, _k = __asyncValues(readSseEvents(response.body)), _l; _l = yield __await(_k.next()), _a = _l.done, !_a; _j = true) {
|
|
199
|
+
_c = _l.value;
|
|
200
|
+
_j = false;
|
|
88
201
|
const message = _c;
|
|
89
202
|
const eventName = message.event;
|
|
90
203
|
if (eventName === 'response.output_text.delta') {
|
|
@@ -102,26 +215,111 @@ function translateOpenAiResponsesStream(response) {
|
|
|
102
215
|
else if (eventName === 'response.web_search_call.completed') {
|
|
103
216
|
yield yield __await({ type: 'tool-event', toolType: 'web_search', phase: 'completed' });
|
|
104
217
|
}
|
|
218
|
+
else if (eventName === 'response.output_item.added') {
|
|
219
|
+
const payload = validateEventPayload(parseSseEventJson(message.data), responsesOutputItemAddedPayload);
|
|
220
|
+
/* c8 ignore next 1 - defensive: payload null branch unreachable after validation */
|
|
221
|
+
const item = payload === null || payload === void 0 ? void 0 : payload.item;
|
|
222
|
+
/* c8 ignore next 1 - defensive: falsy call_id/name branches are protocol violations */
|
|
223
|
+
if ((item === null || item === void 0 ? void 0 : item.type) === 'function_call' && item.call_id && item.name) {
|
|
224
|
+
functionCallMap.set(item.call_id, { id: item.call_id, name: item.name, argsBuffer: '' });
|
|
225
|
+
// Reasoning models (and the standard flow) reference this function_call in subsequent
|
|
226
|
+
// arguments.{delta,done} events by `item_id`, never by `call_id`. Record the mapping
|
|
227
|
+
// so the arg-accumulation handlers can resolve the call.
|
|
228
|
+
if (item.id) {
|
|
229
|
+
itemIdToCallId.set(item.id, item.call_id);
|
|
230
|
+
}
|
|
231
|
+
yield yield __await({ type: 'client-tool-call-start', toolName: item.name, callId: item.call_id });
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
else if (eventName === 'response.function_call_arguments.delta') {
|
|
235
|
+
const payload = validateEventPayload(parseSseEventJson(message.data), responsesFunctionCallArgsDeltaPayload);
|
|
236
|
+
/* c8 ignore next 1 - defensive: payload null branch unreachable after validation */
|
|
237
|
+
const itemId = payload === null || payload === void 0 ? void 0 : payload.item_id;
|
|
238
|
+
if (itemId !== undefined) {
|
|
239
|
+
const callId = itemIdToCallId.get(itemId);
|
|
240
|
+
/* c8 ignore next 1 - defensive: orphan delta (no preceding output_item.added) is a protocol violation */
|
|
241
|
+
const call = callId !== undefined ? functionCallMap.get(callId) : undefined;
|
|
242
|
+
/* c8 ignore next 1 - defensive: call always present and delta always string in valid stream */
|
|
243
|
+
if (call && typeof (payload === null || payload === void 0 ? void 0 : payload.delta) === 'string') {
|
|
244
|
+
call.argsBuffer += payload.delta;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else if (eventName === 'response.function_call_arguments.done') {
|
|
249
|
+
const payload = validateEventPayload(parseSseEventJson(message.data), responsesFunctionCallArgsDonePayload);
|
|
250
|
+
/* c8 ignore next 1 - defensive: payload null branch unreachable after validation */
|
|
251
|
+
const itemId = payload === null || payload === void 0 ? void 0 : payload.item_id;
|
|
252
|
+
if (itemId !== undefined) {
|
|
253
|
+
const callId = itemIdToCallId.get(itemId);
|
|
254
|
+
/* c8 ignore next 1 - defensive: orphan done (no preceding output_item.added) is a protocol violation */
|
|
255
|
+
const call = callId !== undefined ? functionCallMap.get(callId) : undefined;
|
|
256
|
+
if (call && callId !== undefined) {
|
|
257
|
+
/* c8 ignore next 1 - defensive: payload?.arguments null branch unreachable after validation */
|
|
258
|
+
const canonicalArgs = (_d = payload === null || payload === void 0 ? void 0 : payload.arguments) !== null && _d !== void 0 ? _d : '{}';
|
|
259
|
+
// Sync the accumulation entry with the canonical arguments from the .done event.
|
|
260
|
+
// Delta events may carry partial/empty payloads; the .done event carries the
|
|
261
|
+
// authoritative final arguments string used by the continuation builder.
|
|
262
|
+
call.argsBuffer = canonicalArgs;
|
|
263
|
+
let args;
|
|
264
|
+
try {
|
|
265
|
+
const parsed = JSON.parse(canonicalArgs);
|
|
266
|
+
/* c8 ignore start - defensive: non-object/malformed parse defaults to empty object */
|
|
267
|
+
args =
|
|
268
|
+
parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
269
|
+
? parsed
|
|
270
|
+
: {};
|
|
271
|
+
}
|
|
272
|
+
catch (_m) {
|
|
273
|
+
args = {};
|
|
274
|
+
}
|
|
275
|
+
/* c8 ignore stop */
|
|
276
|
+
yield yield __await({ type: 'client-tool-call-done', toolName: call.name, callId, args });
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
105
280
|
else if (eventName === 'response.completed') {
|
|
106
281
|
const payload = validateEventPayload(parseSseEventJson(message.data), responsesCompletedPayload);
|
|
107
|
-
/* c8 ignore next 1 - defensive: payload
|
|
108
|
-
|
|
282
|
+
/* c8 ignore next 1 - defensive: payload null branch unreachable after validation */
|
|
283
|
+
if (payload) {
|
|
284
|
+
truncated = payload.response.status === 'incomplete';
|
|
285
|
+
// Per IAiStreamDone.incompleteReason's contract, the reason is meaningful only
|
|
286
|
+
// when the response was truncated. Move both fields together on every completed
|
|
287
|
+
// event so a stray incomplete_details on a non-incomplete payload never leaks
|
|
288
|
+
// through, and a later (defensive) completed event can't leave a stale reason.
|
|
289
|
+
incompleteReason = truncated ? (_e = payload.response.incomplete_details) === null || _e === void 0 ? void 0 : _e.reason : undefined;
|
|
290
|
+
}
|
|
109
291
|
completed = true;
|
|
110
292
|
/* c8 ignore next 1 - defensive: eventName === 'error' alternative not exercised in tests */
|
|
111
293
|
}
|
|
112
294
|
else if (eventName === 'response.failed' || eventName === 'error') {
|
|
113
295
|
const payload = validateEventPayload(parseSseEventJson(message.data), responsesErrorPayload);
|
|
114
296
|
/* c8 ignore next 1 - defensive: payload?.error and payload?.message null branches unreachable */
|
|
115
|
-
const errMsg = (
|
|
297
|
+
const errMsg = (_h = (_g = (_f = payload === null || payload === void 0 ? void 0 : payload.error) === null || _f === void 0 ? void 0 : _f.message) !== null && _g !== void 0 ? _g : payload === null || payload === void 0 ? void 0 : payload.message) !== null && _h !== void 0 ? _h : 'Responses API stream failed';
|
|
116
298
|
yield yield __await({ type: 'error', message: errMsg });
|
|
117
299
|
return yield __await(void 0);
|
|
118
300
|
}
|
|
301
|
+
else if (typeof eventName === 'string' &&
|
|
302
|
+
!RECOGNIZED_OPENAI_RESPONSES_EVENTS.has(eventName) &&
|
|
303
|
+
!warnedEvents.has(eventName)) {
|
|
304
|
+
// Empirical drift instrument: an unrecognized SSE event from the Responses API
|
|
305
|
+
// surfaces as a one-time warning per stream. If a provider adds a new event type
|
|
306
|
+
// (or changes an existing event's name), the warning fires the first time the
|
|
307
|
+
// event arrives. Update RECOGNIZED_OPENAI_RESPONSES_EVENTS once the new event is
|
|
308
|
+
// either handled or confirmed safe to ignore.
|
|
309
|
+
warnedEvents.add(eventName);
|
|
310
|
+
logger === null || logger === void 0 ? void 0 : logger.warn(`${UNRECOGNIZED_EVENT_WARN_TAG} OpenAI Responses adapter: unrecognized SSE event ` +
|
|
311
|
+
`'${eventName}'. ` +
|
|
312
|
+
`payload preview: ${formatUnrecognizedEventPayloadPreview(message.data)}. ` +
|
|
313
|
+
`This may indicate provider drift — if the new event carries data the adapter ` +
|
|
314
|
+
`should surface, add a handler; otherwise add the name to ` +
|
|
315
|
+
`RECOGNIZED_OPENAI_RESPONSES_EVENTS.`);
|
|
316
|
+
}
|
|
119
317
|
}
|
|
120
318
|
}
|
|
121
319
|
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
122
320
|
finally {
|
|
123
321
|
try {
|
|
124
|
-
if (!
|
|
322
|
+
if (!_j && !_a && (_b = _k.return)) yield __await(_b.call(_k));
|
|
125
323
|
}
|
|
126
324
|
finally { if (e_1) throw e_1.error; }
|
|
127
325
|
}
|
|
@@ -131,7 +329,7 @@ function translateOpenAiResponsesStream(response) {
|
|
|
131
329
|
return yield __await(void 0);
|
|
132
330
|
} /* c8 ignore stop */
|
|
133
331
|
if (completed) {
|
|
134
|
-
yield yield __await({ type: 'done', truncated, fullText });
|
|
332
|
+
yield yield __await({ type: 'done', truncated, fullText, incompleteReason });
|
|
135
333
|
}
|
|
136
334
|
else {
|
|
137
335
|
yield yield __await({ type: 'error', message: 'Responses API stream ended without a completed event' });
|
|
@@ -147,11 +345,12 @@ function translateOpenAiResponsesStream(response) {
|
|
|
147
345
|
*
|
|
148
346
|
* @internal
|
|
149
347
|
*/
|
|
150
|
-
export async function callOpenAiResponsesStream(config, prompt, tools, messagesBefore, temperature, logger, signal, resolvedThinking) {
|
|
348
|
+
export async function callOpenAiResponsesStream(config, prompt, tools, messagesBefore, temperature, logger, signal, resolvedThinking, functionCallMap, continuationMessages) {
|
|
151
349
|
var _a;
|
|
152
350
|
const url = `${config.baseUrl}/responses`;
|
|
153
351
|
const input = buildMessages(prompt.system, buildOpenAiResponsesUserContent(prompt), {
|
|
154
|
-
head: messagesBefore
|
|
352
|
+
head: messagesBefore,
|
|
353
|
+
rawTail: continuationMessages
|
|
155
354
|
});
|
|
156
355
|
const effort = (_a = resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.openAiEffort) !== null && _a !== void 0 ? _a : resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.xaiEffort;
|
|
157
356
|
const supportsReasoning = config.model !== 'grok-4';
|
|
@@ -173,7 +372,8 @@ export async function callOpenAiResponsesStream(config, prompt, tools, messagesB
|
|
|
173
372
|
const headers = bearerAuthHeader(config.apiKey);
|
|
174
373
|
/* c8 ignore next 3 - optional logger */
|
|
175
374
|
logger === null || logger === void 0 ? void 0 : logger.info(`OpenAI Responses streaming: model=${config.model}, tools=${tools.map((t) => t.type).join(',')}`);
|
|
375
|
+
const callMap = functionCallMap !== null && functionCallMap !== void 0 ? functionCallMap : new Map();
|
|
176
376
|
const conn = await openSseConnection(url, headers, body, logger, signal);
|
|
177
|
-
return conn.onSuccess((response) => succeed(translateOpenAiResponsesStream(response)));
|
|
377
|
+
return conn.onSuccess((response) => succeed(translateOpenAiResponsesStream(response, callMap, logger)));
|
|
178
378
|
}
|
|
179
379
|
//# sourceMappingURL=openaiResponses.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openaiResponses.js","sourceRoot":"","sources":["../../../../src/packlets/ai-assist/streamingAdapters/openaiResponses.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 * Streaming adapter for the OpenAI / xAI Responses API. This is the format\n * used when server-side tools (e.g. web_search) are requested — Chat\n * Completions doesn't support tool progress events, but the Responses API\n * does.\n *\n * @packageDocumentation\n */\n\nimport { type Logging, Result, succeed, type Validator, Validators } from '@fgv/ts-utils';\n\nimport { buildMessages, buildOpenAiResponsesUserContent } from '../chatRequestBuilders';\nimport { bearerAuthHeader } from '../endpoint';\nimport { AiPrompt, type AiServerToolConfig, type IAiStreamEvent, type IChatMessage } from '../model';\nimport { parseSseEventJson, readSseEvents } from '../sseParser';\nimport { toResponsesApiTools } from '../toolFormats';\nimport { type IResolvedThinkingConfig } from '../thinkingOptionsResolver';\nimport { IStreamApiConfig, openSseConnection, validateEventPayload } from './common';\n\n// ============================================================================\n// Event payload shapes\n// ============================================================================\n\n/**\n * Payload of a `response.output_text.delta` SSE event.\n *\n * @internal\n */\ninterface IResponsesDeltaPayload {\n readonly delta: string;\n}\n\n/**\n * Payload of a `response.completed` SSE event. `status === 'incomplete'`\n * signals the stream was cut short (max output tokens, etc.).\n *\n * @internal\n */\ninterface IResponsesCompletedPayload {\n readonly response: { readonly status?: string };\n}\n\n/**\n * Payload of a `response.failed` or `error` SSE event. Both shapes appear\n * in the wild — sometimes `error.message`, sometimes a top-level `message`.\n *\n * @internal\n */\ninterface IResponsesErrorPayload {\n readonly error?: { readonly message?: string };\n readonly message?: string;\n}\n\nconst responsesDeltaPayload: Validator<IResponsesDeltaPayload> = Validators.object<IResponsesDeltaPayload>({\n delta: Validators.string\n});\n\nconst responsesCompletedPayload: Validator<IResponsesCompletedPayload> =\n Validators.object<IResponsesCompletedPayload>({\n response: Validators.object<{ status?: string }>(\n { status: Validators.string.optional() },\n { options: { optionalFields: ['status'] } }\n )\n });\n\nconst responsesErrorInner: Validator<{ message?: string }> = Validators.object<{ message?: string }>(\n { message: Validators.string.optional() },\n { options: { optionalFields: ['message'] } }\n);\n\nconst responsesErrorPayload: Validator<IResponsesErrorPayload> = Validators.object<IResponsesErrorPayload>(\n {\n error: responsesErrorInner.optional(),\n message: Validators.string.optional()\n },\n { options: { optionalFields: ['error', 'message'] } }\n);\n\n// ============================================================================\n// Stream translator\n// ============================================================================\n\n/**\n * Translates an OpenAI Responses API SSE stream into unified events.\n *\n * @internal\n */\nasync function* translateOpenAiResponsesStream(response: Response): AsyncGenerator<IAiStreamEvent> {\n let fullText = '';\n let truncated = false;\n let completed = false;\n\n try {\n /* c8 ignore next - body is non-null at this point per openSseConnection */\n if (!response.body) return;\n for await (const message of readSseEvents(response.body)) {\n const eventName = message.event;\n if (eventName === 'response.output_text.delta') {\n const payload = validateEventPayload(parseSseEventJson(message.data), responsesDeltaPayload);\n /* c8 ignore next 1 - defensive: payload?.delta null branch unreachable after validation */\n const delta = payload?.delta;\n if (typeof delta === 'string' && delta.length > 0) {\n fullText += delta;\n yield { type: 'text-delta', delta };\n }\n } else if (eventName === 'response.web_search_call.in_progress') {\n yield { type: 'tool-event', toolType: 'web_search', phase: 'started' };\n } else if (eventName === 'response.web_search_call.completed') {\n yield { type: 'tool-event', toolType: 'web_search', phase: 'completed' };\n } else if (eventName === 'response.completed') {\n const payload = validateEventPayload(parseSseEventJson(message.data), responsesCompletedPayload);\n /* c8 ignore next 1 - defensive: payload?.response null branch unreachable after validation */\n truncated = payload?.response.status === 'incomplete';\n completed = true;\n /* c8 ignore next 1 - defensive: eventName === 'error' alternative not exercised in tests */\n } else if (eventName === 'response.failed' || eventName === 'error') {\n const payload = validateEventPayload(parseSseEventJson(message.data), responsesErrorPayload);\n /* c8 ignore next 1 - defensive: payload?.error and payload?.message null branches unreachable */\n const errMsg = payload?.error?.message ?? payload?.message ?? 'Responses API stream failed';\n yield { type: 'error', message: errMsg };\n return;\n }\n }\n } catch (err: unknown) /* c8 ignore start - defensive: stream errors are always Error instances */ {\n yield { type: 'error', message: err instanceof Error ? err.message : String(err) };\n return;\n } /* c8 ignore stop */\n\n if (completed) {\n yield { type: 'done', truncated, fullText };\n } else {\n yield { type: 'error', message: 'Responses API stream ended without a completed event' };\n }\n}\n\n// ============================================================================\n// Per-format request caller\n// ============================================================================\n\n/**\n * Issues a streaming Responses API request (with tools) and returns the\n * unified-event iterable on success.\n *\n * @internal\n */\nexport async function callOpenAiResponsesStream(\n config: IStreamApiConfig,\n prompt: AiPrompt,\n tools: ReadonlyArray<AiServerToolConfig>,\n messagesBefore: ReadonlyArray<IChatMessage> | undefined,\n temperature: number,\n logger?: Logging.ILogger,\n signal?: AbortSignal,\n resolvedThinking?: IResolvedThinkingConfig\n): Promise<Result<AsyncIterable<IAiStreamEvent>>> {\n const url = `${config.baseUrl}/responses`;\n const input = buildMessages(prompt.system, buildOpenAiResponsesUserContent(prompt), {\n head: messagesBefore\n });\n const effort = resolvedThinking?.openAiEffort ?? resolvedThinking?.xaiEffort;\n const supportsReasoning = config.model !== 'grok-4';\n const body: Record<string, unknown> = {\n model: config.model,\n input,\n tools: toResponsesApiTools(tools),\n stream: true\n };\n if (effort !== undefined && supportsReasoning) {\n body.reasoning = { effort };\n }\n if (effort === undefined || effort === 'none') {\n body.temperature = temperature;\n }\n if (resolvedThinking?.otherParams !== undefined) {\n Object.assign(body, resolvedThinking.otherParams);\n }\n const headers: Record<string, string> = bearerAuthHeader(config.apiKey);\n /* c8 ignore next 3 - optional logger */\n logger?.info(\n `OpenAI Responses streaming: model=${config.model}, tools=${tools.map((t) => t.type).join(',')}`\n );\n const conn = await openSseConnection(url, headers, body, logger, signal);\n return conn.onSuccess((response) => succeed(translateOpenAiResponsesStream(response)));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"openaiResponses.js","sourceRoot":"","sources":["../../../../src/packlets/ai-assist/streamingAdapters/openaiResponses.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 * Streaming adapter for the OpenAI / xAI Responses API. This is the format\n * used when server-side tools (e.g. web_search) are requested — Chat\n * Completions doesn't support tool progress events, but the Responses API\n * does.\n *\n * Client-defined tools (`function_call` type) are accumulated per call ID\n * and emitted as `client-tool-call-done` events when complete.\n *\n * @packageDocumentation\n */\n\nimport { type Logging, Result, succeed, type Validator, Validators } from '@fgv/ts-utils';\nimport { type JsonObject } from '@fgv/ts-json-base';\n\nimport { buildMessages, buildOpenAiResponsesUserContent } from '../chatRequestBuilders';\nimport { bearerAuthHeader } from '../endpoint';\nimport { AiPrompt, type AiToolConfig, type IAiStreamEvent, type IChatMessage } from '../model';\nimport { parseSseEventJson, readSseEvents } from '../sseParser';\nimport { toResponsesApiTools } from '../toolFormats';\nimport { type IResolvedThinkingConfig } from '../thinkingOptionsResolver';\nimport {\n IStreamApiConfig,\n UNRECOGNIZED_EVENT_WARN_TAG,\n formatUnrecognizedEventPayloadPreview,\n openSseConnection,\n validateEventPayload\n} from './common';\n\n// ============================================================================\n// Accumulated call state (internal — used by C3 continuation builder)\n// ============================================================================\n\n/**\n * Accumulated state for a single function_call in the OpenAI Responses API stream.\n * @internal\n */\nexport interface IAccumulatedFunctionCall {\n readonly id: string;\n readonly name: string;\n argsBuffer: string;\n}\n\n// ============================================================================\n// Event payload shapes\n// ============================================================================\n\n/**\n * Payload of a `response.output_text.delta` SSE event.\n * @internal\n */\ninterface IResponsesDeltaPayload {\n readonly delta: string;\n}\n\n/**\n * Payload of a `response.output_item.added` SSE event.\n * @internal\n */\ninterface IResponsesOutputItemAddedPayload {\n readonly item: {\n readonly type?: string;\n readonly id?: string;\n readonly name?: string;\n readonly call_id?: string;\n };\n}\n\n/**\n * Payload of a `response.function_call_arguments.delta` SSE event.\n *\n * The live OpenAI / xAI Responses API emits these events keyed by `item_id` (the\n * `fc_*` output-item id), NOT by `call_id` (the `call_*` id used in continuation\n * input items). The adapter correlates `item_id` → `call_id` via the\n * `response.output_item.added` event that introduced the function_call item.\n *\n * @internal\n */\ninterface IResponsesFunctionCallArgsDeltaPayload {\n readonly item_id: string;\n readonly delta: string;\n}\n\n/**\n * Payload of a `response.function_call_arguments.done` SSE event.\n *\n * Keyed by `item_id` like the delta event — see {@link IResponsesFunctionCallArgsDeltaPayload}.\n *\n * @internal\n */\ninterface IResponsesFunctionCallArgsDonePayload {\n readonly item_id: string;\n readonly arguments: string;\n}\n\n/**\n * Payload of a `response.completed` SSE event.\n * @internal\n */\ninterface IResponsesCompletedPayload {\n readonly response: {\n readonly status?: string;\n readonly incomplete_details?: { readonly reason?: string };\n };\n}\n\n/**\n * Payload of a `response.failed` or `error` SSE event.\n * @internal\n */\ninterface IResponsesErrorPayload {\n readonly error?: { readonly message?: string };\n readonly message?: string;\n}\n\nconst responsesDeltaPayload: Validator<IResponsesDeltaPayload> = Validators.object<IResponsesDeltaPayload>({\n delta: Validators.string\n});\n\nconst responsesOutputItemInner: Validator<{\n type?: string;\n id?: string;\n name?: string;\n call_id?: string;\n}> = Validators.object<{ type?: string; id?: string; name?: string; call_id?: string }>(\n {\n type: Validators.string.optional(),\n id: Validators.string.optional(),\n name: Validators.string.optional(),\n call_id: Validators.string.optional()\n },\n { options: { optionalFields: ['type', 'id', 'name', 'call_id'] } }\n);\n\nconst responsesOutputItemAddedPayload: Validator<IResponsesOutputItemAddedPayload> =\n Validators.object<IResponsesOutputItemAddedPayload>({ item: responsesOutputItemInner });\n\nconst responsesFunctionCallArgsDeltaPayload: Validator<IResponsesFunctionCallArgsDeltaPayload> =\n Validators.object<IResponsesFunctionCallArgsDeltaPayload>({\n item_id: Validators.string,\n delta: Validators.string\n });\n\nconst responsesFunctionCallArgsDonePayload: Validator<IResponsesFunctionCallArgsDonePayload> =\n Validators.object<IResponsesFunctionCallArgsDonePayload>({\n item_id: Validators.string,\n arguments: Validators.string\n });\n\nconst responsesIncompleteDetails: Validator<{ reason?: string }> = Validators.object<{ reason?: string }>(\n { reason: Validators.string.optional() },\n { options: { optionalFields: ['reason'] } }\n);\n\nconst responsesCompletedPayload: Validator<IResponsesCompletedPayload> =\n Validators.object<IResponsesCompletedPayload>({\n response: Validators.object<{ status?: string; incomplete_details?: { reason?: string } }>(\n {\n status: Validators.string.optional(),\n incomplete_details: responsesIncompleteDetails.optional()\n },\n { options: { optionalFields: ['status', 'incomplete_details'] } }\n )\n });\n\nconst responsesErrorInner: Validator<{ message?: string }> = Validators.object<{ message?: string }>(\n { message: Validators.string.optional() },\n { options: { optionalFields: ['message'] } }\n);\n\nconst responsesErrorPayload: Validator<IResponsesErrorPayload> = Validators.object<IResponsesErrorPayload>(\n {\n error: responsesErrorInner.optional(),\n message: Validators.string.optional()\n },\n { options: { optionalFields: ['error', 'message'] } }\n);\n\n// ============================================================================\n// Stream translator\n// ============================================================================\n\n/**\n * Recognized OpenAI / xAI Responses API SSE event names. An event in this set is either\n * handled below or intentionally ignored (lifecycle / reasoning content discarded by design /\n * server-tool channels not yet surfaced). Any event whose name is NOT in this set surfaces\n * a one-time `logger.warn` per stream — making provider drift (new event types from a model\n * update) visible empirically instead of silently no-op'd.\n *\n * Source: official `openai-node` SDK source (`src/resources/responses/responses.ts`)\n * event-type literal-string sweep, plus the xAI Responses superset. Add to this set when a\n * new event type is observed and confirmed safe to ignore.\n *\n * @internal\n */\nconst RECOGNIZED_OPENAI_RESPONSES_EVENTS: ReadonlySet<string> = new Set<string>([\n // ---- handled by the translator ----\n 'response.output_text.delta',\n 'response.web_search_call.in_progress',\n 'response.web_search_call.completed',\n 'response.output_item.added',\n 'response.function_call_arguments.delta',\n 'response.function_call_arguments.done',\n 'response.completed',\n 'response.failed',\n 'error',\n // ---- lifecycle events: intentionally silent ----\n 'response.created',\n 'response.in_progress',\n 'response.queued',\n 'response.incomplete',\n // ---- text content lifecycle: handled implicitly via delta accumulation ----\n 'response.output_text.done',\n 'response.output_text.annotation.added',\n 'response.content_part.added',\n 'response.content_part.done',\n 'response.output_item.done',\n // ---- reasoning content: discarded by design (see ai-assist-thinking-events follow-on) ----\n 'response.reasoning_summary_part.added',\n 'response.reasoning_summary_part.done',\n 'response.reasoning_summary_text.delta',\n 'response.reasoning_summary_text.done',\n 'response.reasoning_text.delta',\n 'response.reasoning_text.done',\n // ---- refusals: caller currently sees these via the model's final text ----\n 'response.refusal.delta',\n 'response.refusal.done',\n // ---- server-tool channels not surfaced as tool-event yet ----\n 'response.web_search_call.searching',\n 'response.file_search_call.in_progress',\n 'response.file_search_call.searching',\n 'response.file_search_call.completed',\n 'response.code_interpreter_call.in_progress',\n 'response.code_interpreter_call.interpreting',\n 'response.code_interpreter_call.completed',\n 'response.code_interpreter_call_code.delta',\n 'response.code_interpreter_call_code.done',\n 'response.image_generation_call.in_progress',\n 'response.image_generation_call.generating',\n 'response.image_generation_call.completed',\n 'response.image_generation_call.partial_image',\n 'response.mcp_call.in_progress',\n 'response.mcp_call.completed',\n 'response.mcp_call.failed',\n 'response.mcp_call_arguments.delta',\n 'response.mcp_call_arguments.done',\n 'response.mcp_list_tools.in_progress',\n 'response.mcp_list_tools.completed',\n 'response.mcp_list_tools.failed',\n 'response.custom_tool_call_input.delta',\n 'response.custom_tool_call_input.done',\n // ---- audio (not used in chat/tool flows here) ----\n 'response.audio.delta',\n 'response.audio.done',\n 'response.audio.transcript.delta',\n 'response.audio.transcript.done'\n]);\n\n/**\n * Translates an OpenAI Responses API SSE stream into unified events.\n *\n * Maintains a per-call-ID accumulation map for client-defined function calls.\n * The map is exposed via the passed-in `functionCallMap` parameter for the\n * C3 continuation builder to read after the stream completes.\n *\n * Unrecognized event names are reported once per stream via `logger?.warn` —\n * see {@link RECOGNIZED_OPENAI_RESPONSES_EVENTS}.\n *\n * @internal\n */\nasync function* translateOpenAiResponsesStream(\n response: Response,\n functionCallMap: Map<string, IAccumulatedFunctionCall>,\n logger?: Logging.ILogger\n): AsyncGenerator<IAiStreamEvent> {\n let fullText = '';\n let truncated = false;\n let completed = false;\n let incompleteReason: string | undefined;\n // OpenAI / xAI Responses API emits function_call_arguments.{delta,done} events keyed by\n // `item_id` (the fc_* output-item id). The harness and continuation builder key by\n // `call_id` (the call_* id). This map correlates the two — populated when the\n // function_call item is announced in `response.output_item.added`.\n const itemIdToCallId = new Map<string, string>();\n // Track unrecognized event names we have already warned about, so a hot stream of\n // an unknown event type produces exactly one log line per name per stream.\n const warnedEvents = new Set<string>();\n\n try {\n /* c8 ignore next - body is non-null at this point per openSseConnection */\n if (!response.body) return;\n for await (const message of readSseEvents(response.body)) {\n const eventName = message.event;\n if (eventName === 'response.output_text.delta') {\n const payload = validateEventPayload(parseSseEventJson(message.data), responsesDeltaPayload);\n /* c8 ignore next 1 - defensive: payload?.delta null branch unreachable after validation */\n const delta = payload?.delta;\n if (typeof delta === 'string' && delta.length > 0) {\n fullText += delta;\n yield { type: 'text-delta', delta };\n }\n } else if (eventName === 'response.web_search_call.in_progress') {\n yield { type: 'tool-event', toolType: 'web_search', phase: 'started' };\n } else if (eventName === 'response.web_search_call.completed') {\n yield { type: 'tool-event', toolType: 'web_search', phase: 'completed' };\n } else if (eventName === 'response.output_item.added') {\n const payload = validateEventPayload(\n parseSseEventJson(message.data),\n responsesOutputItemAddedPayload\n );\n /* c8 ignore next 1 - defensive: payload null branch unreachable after validation */\n const item = payload?.item;\n /* c8 ignore next 1 - defensive: falsy call_id/name branches are protocol violations */\n if (item?.type === 'function_call' && item.call_id && item.name) {\n functionCallMap.set(item.call_id, { id: item.call_id, name: item.name, argsBuffer: '' });\n // Reasoning models (and the standard flow) reference this function_call in subsequent\n // arguments.{delta,done} events by `item_id`, never by `call_id`. Record the mapping\n // so the arg-accumulation handlers can resolve the call.\n if (item.id) {\n itemIdToCallId.set(item.id, item.call_id);\n }\n yield { type: 'client-tool-call-start', toolName: item.name, callId: item.call_id };\n }\n } else if (eventName === 'response.function_call_arguments.delta') {\n const payload = validateEventPayload(\n parseSseEventJson(message.data),\n responsesFunctionCallArgsDeltaPayload\n );\n /* c8 ignore next 1 - defensive: payload null branch unreachable after validation */\n const itemId = payload?.item_id;\n if (itemId !== undefined) {\n const callId = itemIdToCallId.get(itemId);\n /* c8 ignore next 1 - defensive: orphan delta (no preceding output_item.added) is a protocol violation */\n const call = callId !== undefined ? functionCallMap.get(callId) : undefined;\n /* c8 ignore next 1 - defensive: call always present and delta always string in valid stream */\n if (call && typeof payload?.delta === 'string') {\n call.argsBuffer += payload.delta;\n }\n }\n } else if (eventName === 'response.function_call_arguments.done') {\n const payload = validateEventPayload(\n parseSseEventJson(message.data),\n responsesFunctionCallArgsDonePayload\n );\n /* c8 ignore next 1 - defensive: payload null branch unreachable after validation */\n const itemId = payload?.item_id;\n if (itemId !== undefined) {\n const callId = itemIdToCallId.get(itemId);\n /* c8 ignore next 1 - defensive: orphan done (no preceding output_item.added) is a protocol violation */\n const call = callId !== undefined ? functionCallMap.get(callId) : undefined;\n if (call && callId !== undefined) {\n /* c8 ignore next 1 - defensive: payload?.arguments null branch unreachable after validation */\n const canonicalArgs = payload?.arguments ?? '{}';\n // Sync the accumulation entry with the canonical arguments from the .done event.\n // Delta events may carry partial/empty payloads; the .done event carries the\n // authoritative final arguments string used by the continuation builder.\n call.argsBuffer = canonicalArgs;\n let args: JsonObject;\n try {\n const parsed = JSON.parse(canonicalArgs) as unknown;\n /* c8 ignore start - defensive: non-object/malformed parse defaults to empty object */\n args =\n parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as JsonObject)\n : {};\n } catch {\n args = {};\n }\n /* c8 ignore stop */\n yield { type: 'client-tool-call-done', toolName: call.name, callId, args };\n }\n }\n } else if (eventName === 'response.completed') {\n const payload = validateEventPayload(parseSseEventJson(message.data), responsesCompletedPayload);\n /* c8 ignore next 1 - defensive: payload null branch unreachable after validation */\n if (payload) {\n truncated = payload.response.status === 'incomplete';\n // Per IAiStreamDone.incompleteReason's contract, the reason is meaningful only\n // when the response was truncated. Move both fields together on every completed\n // event so a stray incomplete_details on a non-incomplete payload never leaks\n // through, and a later (defensive) completed event can't leave a stale reason.\n incompleteReason = truncated ? payload.response.incomplete_details?.reason : undefined;\n }\n completed = true;\n /* c8 ignore next 1 - defensive: eventName === 'error' alternative not exercised in tests */\n } else if (eventName === 'response.failed' || eventName === 'error') {\n const payload = validateEventPayload(parseSseEventJson(message.data), responsesErrorPayload);\n /* c8 ignore next 1 - defensive: payload?.error and payload?.message null branches unreachable */\n const errMsg = payload?.error?.message ?? payload?.message ?? 'Responses API stream failed';\n yield { type: 'error', message: errMsg };\n return;\n } else if (\n typeof eventName === 'string' &&\n !RECOGNIZED_OPENAI_RESPONSES_EVENTS.has(eventName) &&\n !warnedEvents.has(eventName)\n ) {\n // Empirical drift instrument: an unrecognized SSE event from the Responses API\n // surfaces as a one-time warning per stream. If a provider adds a new event type\n // (or changes an existing event's name), the warning fires the first time the\n // event arrives. Update RECOGNIZED_OPENAI_RESPONSES_EVENTS once the new event is\n // either handled or confirmed safe to ignore.\n warnedEvents.add(eventName);\n logger?.warn(\n `${UNRECOGNIZED_EVENT_WARN_TAG} OpenAI Responses adapter: unrecognized SSE event ` +\n `'${eventName}'. ` +\n `payload preview: ${formatUnrecognizedEventPayloadPreview(message.data)}. ` +\n `This may indicate provider drift — if the new event carries data the adapter ` +\n `should surface, add a handler; otherwise add the name to ` +\n `RECOGNIZED_OPENAI_RESPONSES_EVENTS.`\n );\n }\n }\n } catch (err: unknown) /* c8 ignore start - defensive: stream errors are always Error instances */ {\n yield { type: 'error', message: err instanceof Error ? err.message : String(err) };\n return;\n } /* c8 ignore stop */\n\n if (completed) {\n yield { type: 'done', truncated, fullText, incompleteReason };\n } else {\n yield { type: 'error', message: 'Responses API stream ended without a completed event' };\n }\n}\n\n// ============================================================================\n// Per-format request caller\n// ============================================================================\n\n/**\n * Issues a streaming Responses API request (with tools) and returns the\n * unified-event iterable on success.\n *\n * @internal\n */\nexport async function callOpenAiResponsesStream(\n config: IStreamApiConfig,\n prompt: AiPrompt,\n tools: ReadonlyArray<AiToolConfig>,\n messagesBefore: ReadonlyArray<IChatMessage> | undefined,\n temperature: number,\n logger?: Logging.ILogger,\n signal?: AbortSignal,\n resolvedThinking?: IResolvedThinkingConfig,\n functionCallMap?: Map<string, IAccumulatedFunctionCall>,\n continuationMessages?: ReadonlyArray<JsonObject>\n): Promise<Result<AsyncIterable<IAiStreamEvent>>> {\n const url = `${config.baseUrl}/responses`;\n const input = buildMessages(prompt.system, buildOpenAiResponsesUserContent(prompt), {\n head: messagesBefore,\n rawTail: continuationMessages\n });\n const effort = resolvedThinking?.openAiEffort ?? resolvedThinking?.xaiEffort;\n const supportsReasoning = config.model !== 'grok-4';\n const body: Record<string, unknown> = {\n model: config.model,\n input,\n tools: toResponsesApiTools(tools),\n stream: true\n };\n if (effort !== undefined && supportsReasoning) {\n body.reasoning = { effort };\n }\n if (effort === undefined || effort === 'none') {\n body.temperature = temperature;\n }\n if (resolvedThinking?.otherParams !== undefined) {\n Object.assign(body, resolvedThinking.otherParams);\n }\n const headers: Record<string, string> = bearerAuthHeader(config.apiKey);\n /* c8 ignore next 3 - optional logger */\n logger?.info(\n `OpenAI Responses streaming: model=${config.model}, tools=${tools.map((t) => t.type).join(',')}`\n );\n const callMap = functionCallMap ?? new Map<string, IAccumulatedFunctionCall>();\n const conn = await openSseConnection(url, headers, body, logger, signal);\n return conn.onSuccess((response) => succeed(translateOpenAiResponsesStream(response, callMap, logger)));\n}\n"]}
|
|
@@ -45,7 +45,8 @@ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _ar
|
|
|
45
45
|
*
|
|
46
46
|
* @packageDocumentation
|
|
47
47
|
*/
|
|
48
|
-
import { succeed, Validators } from '@fgv/ts-utils';
|
|
48
|
+
import { fail, succeed, Validators } from '@fgv/ts-utils';
|
|
49
|
+
import { normalizeOutboundMessages, splitChatRequest } from '../chatRequestBuilders';
|
|
49
50
|
import { parseSseEventJson, readSseEvents } from '../sseParser';
|
|
50
51
|
import { openSseConnection, validateEventPayload } from './common';
|
|
51
52
|
const proxyEventTypes = ['text-delta', 'tool-event', 'done', 'error'];
|
|
@@ -129,21 +130,27 @@ function translateProxyStream(response) {
|
|
|
129
130
|
* @public
|
|
130
131
|
*/
|
|
131
132
|
export async function callProxiedCompletionStream(proxyUrl, params) {
|
|
132
|
-
const { descriptor, apiKey,
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
133
|
+
const { descriptor, apiKey, system, messages, temperature, modelOverride, logger, tools, signal, thinking } = params;
|
|
134
|
+
// Enforce the same unified-request invariants the direct entry points apply
|
|
135
|
+
// (non-empty messages, trailing user turn) so an invalid request fails fast
|
|
136
|
+
// here rather than diverging at the proxy.
|
|
137
|
+
const splitResult = splitChatRequest(system, messages);
|
|
138
|
+
if (splitResult.isFailure()) {
|
|
139
|
+
return fail(splitResult.message);
|
|
140
|
+
}
|
|
141
|
+
if (splitResult.value.prompt.attachments.length > 0 && !descriptor.acceptsImageInput) {
|
|
142
|
+
return fail(`provider "${descriptor.id}" does not accept image input`);
|
|
136
143
|
}
|
|
137
144
|
const body = {
|
|
138
145
|
providerId: descriptor.id,
|
|
139
146
|
apiKey,
|
|
140
|
-
|
|
147
|
+
messages: normalizeOutboundMessages(splitResult.value),
|
|
141
148
|
/* c8 ignore next 1 - defensive: temperature always uses default 0.7 in proxy streaming tests */
|
|
142
149
|
temperature: temperature !== null && temperature !== void 0 ? temperature : 0.7,
|
|
143
150
|
stream: true
|
|
144
151
|
};
|
|
145
|
-
if (
|
|
146
|
-
body.
|
|
152
|
+
if (system !== undefined) {
|
|
153
|
+
body.system = system;
|
|
147
154
|
}
|
|
148
155
|
if (modelOverride !== undefined) {
|
|
149
156
|
body.modelOverride = modelOverride;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../../../../src/packlets/ai-assist/streamingAdapters/proxy.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 * Streaming adapter for a caller-provided proxy server. Unlike the\n * provider-specific adapters, the proxy speaks our own unified vocabulary\n * directly: each `data:` line is a JSON-serialized {@link AiAssist.IAiStreamEvent},\n * so this adapter only validates the event-type discriminator and forwards.\n *\n * @packageDocumentation\n */\n\nimport { Result, succeed, type Validator, Validators } from '@fgv/ts-utils';\n\nimport { type IAiStreamEvent } from '../model';\nimport { parseSseEventJson, readSseEvents } from '../sseParser';\nimport { IProviderCompletionStreamParams, openSseConnection, validateEventPayload } from './common';\n\n// ============================================================================\n// Event payload shape — a tagged-event envelope\n// ============================================================================\n\ntype ProxyEventType = 'text-delta' | 'tool-event' | 'done' | 'error';\n\n/**\n * Minimal envelope used to identify and discriminate proxy events. Once the\n * `type` is recognized, the event is forwarded as-is — the unified shape is\n * the proxy contract, so there's no further per-type validation here.\n *\n * @internal\n */\ninterface IProxyEventEnvelope {\n readonly type: ProxyEventType;\n}\n\nconst proxyEventTypes: ReadonlyArray<ProxyEventType> = ['text-delta', 'tool-event', 'done', 'error'];\n\nconst proxyEventEnvelope: Validator<IProxyEventEnvelope> = Validators.object<IProxyEventEnvelope>({\n type: Validators.enumeratedValue<ProxyEventType>(proxyEventTypes)\n});\n\n// ============================================================================\n// Stream translator\n// ============================================================================\n\n/**\n * Translates a proxied SSE stream back into {@link AiAssist.IAiStreamEvent} objects.\n * Validation is limited to the type discriminator; the proxy is contractually\n * required to emit shape-correct unified events.\n *\n * @internal\n */\nasync function* translateProxyStream(response: Response): AsyncGenerator<IAiStreamEvent> {\n try {\n /* c8 ignore next - body is non-null at this point per openSseConnection */\n if (!response.body) return;\n for await (const message of readSseEvents(response.body)) {\n const json = parseSseEventJson(message.data);\n /* c8 ignore next 3 - defensive: malformed SSE events skipped */\n if (json === undefined) {\n continue;\n }\n const envelope = validateEventPayload(json, proxyEventEnvelope);\n /* c8 ignore next 3 - defensive: SSE events without valid envelope skipped */\n if (!envelope) {\n continue;\n }\n const event = json as IAiStreamEvent;\n yield event;\n if (envelope.type === 'done' || envelope.type === 'error') {\n return;\n }\n }\n } catch (err: unknown) /* c8 ignore start - defensive: stream errors are always Error instances */ {\n yield { type: 'error', message: err instanceof Error ? err.message : String(err) };\n } /* c8 ignore stop */\n}\n\n// ============================================================================\n// Public entry point\n// ============================================================================\n\n/**\n * Calls the streaming chat endpoint on a proxy server instead of calling\n * the provider directly from the browser.\n *\n * @remarks\n * Proxy contract:\n * - Endpoint: `POST ${proxyUrl}/api/ai/completion-stream`\n * - Request body: same JSON as `/api/ai/completion` plus `\"stream\": true`\n * - Response: `Content-Type: text/event-stream`; body is the unified\n * {@link AiAssist.IAiStreamEvent} JSON-serialized one event per SSE `data:` line\n * (no `event:` line needed since the type discriminator is in the JSON).\n * - Error response (when the proxy can't even start): JSON `{error: string}`\n * with a non-2xx status, surfaced as `proxy: ${error}`.\n *\n * The proxy server is responsible for opening the upstream SSE connection,\n * translating provider-native events to the unified vocabulary, and\n * forwarding events as they arrive (no buffering). The library does not\n * ship a proxy implementation.\n *\n * @public\n */\nexport async function callProxiedCompletionStream(\n proxyUrl: string,\n params: IProviderCompletionStreamParams\n): Promise<Result<AsyncIterable<IAiStreamEvent>>> {\n const {\n descriptor,\n apiKey,\n prompt,\n messagesBefore,\n temperature,\n modelOverride,\n logger,\n tools,\n signal,\n thinking\n } = params;\n\n const promptBody: Record<string, unknown> = { system: prompt.system, user: prompt.user };\n if (prompt.attachments.length > 0) {\n promptBody.attachments = prompt.attachments;\n }\n const body: Record<string, unknown> = {\n providerId: descriptor.id,\n apiKey,\n prompt: promptBody,\n /* c8 ignore next 1 - defensive: temperature always uses default 0.7 in proxy streaming tests */\n temperature: temperature ?? 0.7,\n stream: true\n };\n if (messagesBefore && messagesBefore.length > 0) {\n body.messagesBefore = messagesBefore;\n }\n if (modelOverride !== undefined) {\n body.modelOverride = modelOverride;\n }\n if (tools && tools.length > 0) {\n body.tools = tools;\n }\n if (thinking !== undefined) {\n body.thinking = thinking;\n }\n\n /* c8 ignore next 1 - optional logger */\n logger?.info(`AI streaming proxy request: provider=${descriptor.id}, proxy=${proxyUrl}`);\n\n const url = `${proxyUrl}/api/ai/completion-stream`;\n const conn = await openSseConnection(url, {}, body, logger, signal);\n return conn.onSuccess((response) => succeed(translateProxyStream(response)));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../../../../src/packlets/ai-assist/streamingAdapters/proxy.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAClC,EAAE;AACF,+EAA+E;AAC/E,gFAAgF;AAChF,+EAA+E;AAC/E,4EAA4E;AAC5E,wEAAwE;AACxE,2DAA2D;AAC3D,EAAE;AACF,iFAAiF;AACjF,kDAAkD;AAClD,EAAE;AACF,6EAA6E;AAC7E,2EAA2E;AAC3E,8EAA8E;AAC9E,yEAAyE;AACzE,gFAAgF;AAChF,gFAAgF;AAChF,YAAY;;;;;;;;;;;;;;;;;;;;;AAEZ;;;;;;;GAOG;AAEH,OAAO,EAAE,IAAI,EAAU,OAAO,EAAkB,UAAU,EAAE,MAAM,eAAe,CAAC;AAElF,OAAO,EAAE,yBAAyB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAErF,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAmC,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAmBpG,MAAM,eAAe,GAAkC,CAAC,YAAY,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;AAErG,MAAM,kBAAkB,GAAmC,UAAU,CAAC,MAAM,CAAsB;IAChG,IAAI,EAAE,UAAU,CAAC,eAAe,CAAiB,eAAe,CAAC;CAClE,CAAC,CAAC;AAEH,+EAA+E;AAC/E,oBAAoB;AACpB,+EAA+E;AAE/E;;;;;;GAMG;AACH,SAAgB,oBAAoB,CAAC,QAAkB;;;QACrD,IAAI,CAAC;YACH,2EAA2E;YAC3E,IAAI,CAAC,QAAQ,CAAC,IAAI;gBAAE,6BAAO;;gBAC3B,KAA4B,eAAA,KAAA,cAAA,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA,IAAA,+DAAE,CAAC;oBAA/B,cAA4B;oBAA5B,WAA4B;oBAA7C,MAAM,OAAO,KAAA,CAAA;oBACtB,MAAM,IAAI,GAAG,iBAAiB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;oBAC7C,gEAAgE;oBAChE,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;wBACvB,SAAS;oBACX,CAAC;oBACD,MAAM,QAAQ,GAAG,oBAAoB,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC;oBAChE,6EAA6E;oBAC7E,IAAI,CAAC,QAAQ,EAAE,CAAC;wBACd,SAAS;oBACX,CAAC;oBACD,MAAM,KAAK,GAAG,IAAsB,CAAC;oBACrC,oBAAM,KAAK,CAAA,CAAC;oBACZ,IAAI,QAAQ,CAAC,IAAI,KAAK,MAAM,IAAI,QAAQ,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;wBAC1D,6BAAO;oBACT,CAAC;gBACH,CAAC;;;;;;;;;QACH,CAAC;QAAC,OAAO,GAAY,EAAE,2EAA2E,CAAC,CAAC;YAClG,oBAAM,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAA,CAAC;QACrF,CAAC,CAAC,oBAAoB;IACxB,CAAC;CAAA;AAED,+EAA+E;AAC/E,qBAAqB;AACrB,+EAA+E;AAE/E;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAC/C,QAAgB,EAChB,MAAuC;IAEvC,MAAM,EACJ,UAAU,EACV,MAAM,EACN,MAAM,EACN,QAAQ,EACR,WAAW,EACX,aAAa,EACb,MAAM,EACN,KAAK,EACL,MAAM,EACN,QAAQ,EACT,GAAG,MAAM,CAAC;IAEX,4EAA4E;IAC5E,4EAA4E;IAC5E,2CAA2C;IAC3C,MAAM,WAAW,GAAG,gBAAgB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACvD,IAAI,WAAW,CAAC,SAAS,EAAE,EAAE,CAAC;QAC5B,OAAO,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IACnC,CAAC;IACD,IAAI,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC;QACrF,OAAO,IAAI,CAAC,aAAa,UAAU,CAAC,EAAE,+BAA+B,CAAC,CAAC;IACzE,CAAC;IAED,MAAM,IAAI,GAA4B;QACpC,UAAU,EAAE,UAAU,CAAC,EAAE;QACzB,MAAM;QACN,QAAQ,EAAE,yBAAyB,CAAC,WAAW,CAAC,KAAK,CAAC;QACtD,gGAAgG;QAChG,WAAW,EAAE,WAAW,aAAX,WAAW,cAAX,WAAW,GAAI,GAAG;QAC/B,MAAM,EAAE,IAAI;KACb,CAAC;IACF,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IACD,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;QAChC,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;IACrC,CAAC;IACD,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IACD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAED,wCAAwC;IACxC,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,IAAI,CAAC,wCAAwC,UAAU,CAAC,EAAE,WAAW,QAAQ,EAAE,CAAC,CAAC;IAEzF,MAAM,GAAG,GAAG,GAAG,QAAQ,2BAA2B,CAAC;IACnD,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAAC,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACpE,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,OAAO,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;AAC/E,CAAC","sourcesContent":["// Copyright (c) 2026 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n/**\n * Streaming adapter for a caller-provided proxy server. Unlike the\n * provider-specific adapters, the proxy speaks our own unified vocabulary\n * directly: each `data:` line is a JSON-serialized {@link AiAssist.IAiStreamEvent},\n * so this adapter only validates the event-type discriminator and forwards.\n *\n * @packageDocumentation\n */\n\nimport { fail, Result, succeed, type Validator, Validators } from '@fgv/ts-utils';\n\nimport { normalizeOutboundMessages, splitChatRequest } from '../chatRequestBuilders';\nimport { type IAiStreamEvent } from '../model';\nimport { parseSseEventJson, readSseEvents } from '../sseParser';\nimport { IProviderCompletionStreamParams, openSseConnection, validateEventPayload } from './common';\n\n// ============================================================================\n// Event payload shape — a tagged-event envelope\n// ============================================================================\n\ntype ProxyEventType = 'text-delta' | 'tool-event' | 'done' | 'error';\n\n/**\n * Minimal envelope used to identify and discriminate proxy events. Once the\n * `type` is recognized, the event is forwarded as-is — the unified shape is\n * the proxy contract, so there's no further per-type validation here.\n *\n * @internal\n */\ninterface IProxyEventEnvelope {\n readonly type: ProxyEventType;\n}\n\nconst proxyEventTypes: ReadonlyArray<ProxyEventType> = ['text-delta', 'tool-event', 'done', 'error'];\n\nconst proxyEventEnvelope: Validator<IProxyEventEnvelope> = Validators.object<IProxyEventEnvelope>({\n type: Validators.enumeratedValue<ProxyEventType>(proxyEventTypes)\n});\n\n// ============================================================================\n// Stream translator\n// ============================================================================\n\n/**\n * Translates a proxied SSE stream back into {@link AiAssist.IAiStreamEvent} objects.\n * Validation is limited to the type discriminator; the proxy is contractually\n * required to emit shape-correct unified events.\n *\n * @internal\n */\nasync function* translateProxyStream(response: Response): AsyncGenerator<IAiStreamEvent> {\n try {\n /* c8 ignore next - body is non-null at this point per openSseConnection */\n if (!response.body) return;\n for await (const message of readSseEvents(response.body)) {\n const json = parseSseEventJson(message.data);\n /* c8 ignore next 3 - defensive: malformed SSE events skipped */\n if (json === undefined) {\n continue;\n }\n const envelope = validateEventPayload(json, proxyEventEnvelope);\n /* c8 ignore next 3 - defensive: SSE events without valid envelope skipped */\n if (!envelope) {\n continue;\n }\n const event = json as IAiStreamEvent;\n yield event;\n if (envelope.type === 'done' || envelope.type === 'error') {\n return;\n }\n }\n } catch (err: unknown) /* c8 ignore start - defensive: stream errors are always Error instances */ {\n yield { type: 'error', message: err instanceof Error ? err.message : String(err) };\n } /* c8 ignore stop */\n}\n\n// ============================================================================\n// Public entry point\n// ============================================================================\n\n/**\n * Calls the streaming chat endpoint on a proxy server instead of calling\n * the provider directly from the browser.\n *\n * @remarks\n * Proxy contract:\n * - Endpoint: `POST ${proxyUrl}/api/ai/completion-stream`\n * - Request body: same JSON as `/api/ai/completion` plus `\"stream\": true`\n * - Response: `Content-Type: text/event-stream`; body is the unified\n * {@link AiAssist.IAiStreamEvent} JSON-serialized one event per SSE `data:` line\n * (no `event:` line needed since the type discriminator is in the JSON).\n * - Error response (when the proxy can't even start): JSON `{error: string}`\n * with a non-2xx status, surfaced as `proxy: ${error}`.\n *\n * The proxy server is responsible for opening the upstream SSE connection,\n * translating provider-native events to the unified vocabulary, and\n * forwarding events as they arrive (no buffering). The library does not\n * ship a proxy implementation.\n *\n * @public\n */\nexport async function callProxiedCompletionStream(\n proxyUrl: string,\n params: IProviderCompletionStreamParams\n): Promise<Result<AsyncIterable<IAiStreamEvent>>> {\n const {\n descriptor,\n apiKey,\n system,\n messages,\n temperature,\n modelOverride,\n logger,\n tools,\n signal,\n thinking\n } = params;\n\n // Enforce the same unified-request invariants the direct entry points apply\n // (non-empty messages, trailing user turn) so an invalid request fails fast\n // here rather than diverging at the proxy.\n const splitResult = splitChatRequest(system, messages);\n if (splitResult.isFailure()) {\n return fail(splitResult.message);\n }\n if (splitResult.value.prompt.attachments.length > 0 && !descriptor.acceptsImageInput) {\n return fail(`provider \"${descriptor.id}\" does not accept image input`);\n }\n\n const body: Record<string, unknown> = {\n providerId: descriptor.id,\n apiKey,\n messages: normalizeOutboundMessages(splitResult.value),\n /* c8 ignore next 1 - defensive: temperature always uses default 0.7 in proxy streaming tests */\n temperature: temperature ?? 0.7,\n stream: true\n };\n if (system !== undefined) {\n body.system = system;\n }\n if (modelOverride !== undefined) {\n body.modelOverride = modelOverride;\n }\n if (tools && tools.length > 0) {\n body.tools = tools;\n }\n if (thinking !== undefined) {\n body.thinking = thinking;\n }\n\n /* c8 ignore next 1 - optional logger */\n logger?.info(`AI streaming proxy request: provider=${descriptor.id}, proxy=${proxyUrl}`);\n\n const url = `${proxyUrl}/api/ai/completion-stream`;\n const conn = await openSseConnection(url, {}, body, logger, signal);\n return conn.onSuccess((response) => succeed(translateProxyStream(response)));\n}\n"]}
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
* @packageDocumentation
|
|
27
27
|
*/
|
|
28
28
|
import { fail } from '@fgv/ts-utils';
|
|
29
|
+
import { splitChatRequest } from './chatRequestBuilders';
|
|
29
30
|
import { resolveEffectiveBaseUrl } from './endpoint';
|
|
30
31
|
import { resolveModel } from './model';
|
|
31
32
|
import { callAnthropicStream } from './streamingAdapters/anthropic';
|
|
@@ -34,6 +35,24 @@ import { callOpenAiChatStream } from './streamingAdapters/openaiChat';
|
|
|
34
35
|
import { callOpenAiResponsesStream } from './streamingAdapters/openaiResponses';
|
|
35
36
|
import { checkTemperatureConflict, mergeThinkingConfig, providerDiscriminatorForId } from './thinkingOptionsResolver';
|
|
36
37
|
export { callProxiedCompletionStream } from './streamingAdapters/proxy';
|
|
38
|
+
/**
|
|
39
|
+
* Re-export `executeClientToolTurn` from the continuation builder so callers
|
|
40
|
+
* can import it directly from the streaming-client surface.
|
|
41
|
+
*
|
|
42
|
+
* @remarks
|
|
43
|
+
* **Why `clientTools` does not flow through `callProviderCompletionStream`:**
|
|
44
|
+
* Client tools require a stateful per-stream accumulation buffer (for thinking
|
|
45
|
+
* blocks, tool-use args, Gemini functionCall accumulation) that is owned by the
|
|
46
|
+
* continuation builder's execution context. Passing `clientTools` through the
|
|
47
|
+
* generic `callProviderCompletionStream` entry would require the caller to manage
|
|
48
|
+
* the accumulation buffer externally. `executeClientToolTurn` is therefore the
|
|
49
|
+
* canonical (and only) call path for client tools in Phase C. Callers that need
|
|
50
|
+
* both server tools and client tools in the same request should use
|
|
51
|
+
* `executeClientToolTurn` with both `tools` (server) and `clientTools` present;
|
|
52
|
+
* `callProviderCompletionStream` is for server-tools-only or no-tools requests.
|
|
53
|
+
*/
|
|
54
|
+
/* c8 ignore next 5 - barrel re-export; function is covered by clientToolContinuationBuilder tests */
|
|
55
|
+
export { executeClientToolTurn } from './streamingAdapters/clientToolContinuationBuilder';
|
|
37
56
|
/**
|
|
38
57
|
* Calls the appropriate streaming chat completion API for a given provider.
|
|
39
58
|
*
|
|
@@ -54,7 +73,12 @@ export { callProxiedCompletionStream } from './streamingAdapters/proxy';
|
|
|
54
73
|
*/
|
|
55
74
|
export async function callProviderCompletionStream(params) {
|
|
56
75
|
var _a;
|
|
57
|
-
const { descriptor, apiKey,
|
|
76
|
+
const { descriptor, apiKey, system, messages, temperature, modelOverride, logger, tools, signal, endpoint, thinking } = params;
|
|
77
|
+
const splitResult = splitChatRequest(system, messages);
|
|
78
|
+
if (splitResult.isFailure()) {
|
|
79
|
+
return fail(splitResult.message);
|
|
80
|
+
}
|
|
81
|
+
const { prompt, head } = splitResult.value;
|
|
58
82
|
const baseUrlResult = resolveEffectiveBaseUrl(descriptor, endpoint);
|
|
59
83
|
if (baseUrlResult.isFailure()) {
|
|
60
84
|
return fail(baseUrlResult.message);
|
|
@@ -99,13 +123,13 @@ export async function callProviderCompletionStream(params) {
|
|
|
99
123
|
switch (descriptor.apiFormat) {
|
|
100
124
|
case 'openai':
|
|
101
125
|
if (hasTools) {
|
|
102
|
-
return callOpenAiResponsesStream(config, prompt, tools,
|
|
126
|
+
return callOpenAiResponsesStream(config, prompt, tools, head, effectiveTemperature, logger, signal, resolvedThinking);
|
|
103
127
|
}
|
|
104
|
-
return callOpenAiChatStream(config, prompt,
|
|
128
|
+
return callOpenAiChatStream(config, prompt, head, effectiveTemperature, logger, signal, resolvedThinking);
|
|
105
129
|
case 'anthropic':
|
|
106
|
-
return callAnthropicStream(config, prompt,
|
|
130
|
+
return callAnthropicStream(config, prompt, head, effectiveTemperature, tools, logger, signal, resolvedThinking);
|
|
107
131
|
case 'gemini':
|
|
108
|
-
return callGeminiStream(config, prompt,
|
|
132
|
+
return callGeminiStream(config, prompt, head, effectiveTemperature, tools, logger, signal, resolvedThinking);
|
|
109
133
|
/* c8 ignore next 4 - defensive coding: exhaustive switch guaranteed by TypeScript */
|
|
110
134
|
default: {
|
|
111
135
|
const _exhaustive = descriptor.apiFormat;
|