@fgv/ts-extras 5.1.0-15 → 5.1.0-17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/index.browser.js +2 -1
  2. package/dist/packlets/ai-assist/apiClient.js +570 -58
  3. package/dist/packlets/ai-assist/chatRequestBuilders.js +180 -0
  4. package/dist/packlets/ai-assist/index.js +4 -3
  5. package/dist/packlets/ai-assist/model.js +20 -3
  6. package/dist/packlets/ai-assist/registry.js +66 -10
  7. package/dist/packlets/ai-assist/sseParser.js +122 -0
  8. package/dist/packlets/ai-assist/streamingAdapters/anthropic.js +192 -0
  9. package/dist/packlets/ai-assist/streamingAdapters/common.js +77 -0
  10. package/dist/packlets/ai-assist/streamingAdapters/gemini.js +160 -0
  11. package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js +149 -0
  12. package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js +163 -0
  13. package/dist/packlets/ai-assist/streamingAdapters/proxy.js +157 -0
  14. package/dist/packlets/ai-assist/streamingClient.js +88 -0
  15. package/dist/packlets/conversion/converters.js +1 -1
  16. package/dist/packlets/crypto-utils/keystore/keyStore.js +74 -42
  17. package/dist/packlets/zip-file-tree/zipFileTreeAccessors.js +2 -2
  18. package/dist/ts-extras.d.ts +531 -5
  19. package/lib/index.browser.d.ts +2 -1
  20. package/lib/index.browser.js +3 -1
  21. package/lib/packlets/ai-assist/apiClient.d.ts +103 -1
  22. package/lib/packlets/ai-assist/apiClient.js +574 -58
  23. package/lib/packlets/ai-assist/chatRequestBuilders.d.ts +89 -0
  24. package/lib/packlets/ai-assist/chatRequestBuilders.js +189 -0
  25. package/lib/packlets/ai-assist/index.d.ts +4 -3
  26. package/lib/packlets/ai-assist/index.js +10 -1
  27. package/lib/packlets/ai-assist/model.d.ts +271 -2
  28. package/lib/packlets/ai-assist/model.js +21 -3
  29. package/lib/packlets/ai-assist/registry.d.ts +10 -1
  30. package/lib/packlets/ai-assist/registry.js +67 -11
  31. package/lib/packlets/ai-assist/sseParser.d.ts +45 -0
  32. package/lib/packlets/ai-assist/sseParser.js +127 -0
  33. package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts +18 -0
  34. package/lib/packlets/ai-assist/streamingAdapters/anthropic.js +195 -0
  35. package/lib/packlets/ai-assist/streamingAdapters/common.d.ts +71 -0
  36. package/lib/packlets/ai-assist/streamingAdapters/common.js +81 -0
  37. package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts +19 -0
  38. package/lib/packlets/ai-assist/streamingAdapters/gemini.js +163 -0
  39. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts +18 -0
  40. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js +152 -0
  41. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts +19 -0
  42. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js +166 -0
  43. package/lib/packlets/ai-assist/streamingAdapters/proxy.d.ts +34 -0
  44. package/lib/packlets/ai-assist/streamingAdapters/proxy.js +160 -0
  45. package/lib/packlets/ai-assist/streamingClient.d.ts +33 -0
  46. package/lib/packlets/ai-assist/streamingClient.js +93 -0
  47. package/lib/packlets/conversion/converters.d.ts +1 -1
  48. package/lib/packlets/conversion/converters.js +1 -1
  49. package/lib/packlets/crypto-utils/keystore/keyStore.d.ts +19 -0
  50. package/lib/packlets/crypto-utils/keystore/keyStore.js +74 -42
  51. package/lib/packlets/zip-file-tree/zipFileTreeAccessors.d.ts +2 -2
  52. package/lib/packlets/zip-file-tree/zipFileTreeAccessors.js +2 -2
  53. package/package.json +7 -7
@@ -0,0 +1,192 @@
1
+ // Copyright (c) 2026 Erik Fortune
2
+ //
3
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ // of this software and associated documentation files (the "Software"), to deal
5
+ // in the Software without restriction, including without limitation the rights
6
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ // copies of the Software, and to permit persons to whom the Software is
8
+ // furnished to do so, subject to the following conditions:
9
+ //
10
+ // The above copyright notice and this permission notice shall be included in all
11
+ // copies or substantial portions of the Software.
12
+ //
13
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ // SOFTWARE.
20
+ var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
21
+ var __asyncValues = (this && this.__asyncValues) || function (o) {
22
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
23
+ var m = o[Symbol.asyncIterator], i;
24
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
25
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
26
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
27
+ };
28
+ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
29
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
30
+ var g = generator.apply(thisArg, _arguments || []), i, q = [];
31
+ return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
32
+ function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
33
+ function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
34
+ function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
35
+ function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
36
+ function fulfill(value) { resume("next", value); }
37
+ function reject(value) { resume("throw", value); }
38
+ function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
39
+ };
40
+ /**
41
+ * Streaming adapter for the Anthropic Messages API. Surfaces server tool
42
+ * use (e.g. `web_search`) as unified `tool-event` markers based on the
43
+ * `content_block_start` / `content_block_stop` lifecycle.
44
+ *
45
+ * @packageDocumentation
46
+ */
47
+ import { succeed, Validators } from '@fgv/ts-utils';
48
+ import { buildAnthropicMessages } from '../chatRequestBuilders';
49
+ import { parseSseEventJson, readSseEvents } from '../sseParser';
50
+ import { toAnthropicTools } from '../toolFormats';
51
+ import { openSseConnection, validateEventPayload } from './common';
52
+ const anthropicContentBlockInner = Validators.object({
53
+ type: Validators.string.optional(),
54
+ name: Validators.string.optional()
55
+ }, { options: { optionalFields: ['type', 'name'] } });
56
+ const anthropicContentBlockStartPayload = Validators.object({
57
+ content_block: anthropicContentBlockInner
58
+ });
59
+ const anthropicContentBlockDeltaInner = Validators.object({
60
+ type: Validators.string.optional(),
61
+ text: Validators.string.optional()
62
+ }, { options: { optionalFields: ['type', 'text'] } });
63
+ const anthropicContentBlockDeltaPayload = Validators.object({
64
+ delta: anthropicContentBlockDeltaInner
65
+ });
66
+ const anthropicMessageDeltaInner = Validators.object({ stop_reason: Validators.string.optional() }, { options: { optionalFields: ['stop_reason'] } });
67
+ const anthropicMessageDeltaPayload = Validators.object({
68
+ delta: anthropicMessageDeltaInner
69
+ });
70
+ const anthropicErrorInner = Validators.object({ message: Validators.string.optional() }, { options: { optionalFields: ['message'] } });
71
+ const anthropicErrorPayload = Validators.object({ error: anthropicErrorInner.optional() }, { options: { optionalFields: ['error'] } });
72
+ // ============================================================================
73
+ // Stream translator
74
+ // ============================================================================
75
+ /**
76
+ * Translates an Anthropic Messages API SSE stream into unified events.
77
+ *
78
+ * @internal
79
+ */
80
+ function translateAnthropicStream(response) {
81
+ return __asyncGenerator(this, arguments, function* translateAnthropicStream_1() {
82
+ var _a, e_1, _b, _c;
83
+ var _d, _e;
84
+ let fullText = '';
85
+ let truncated = false;
86
+ let stopped = false;
87
+ try {
88
+ /* c8 ignore next - body is non-null at this point per openSseConnection */
89
+ if (!response.body)
90
+ return yield __await(void 0);
91
+ try {
92
+ for (var _f = true, _g = __asyncValues(readSseEvents(response.body)), _h; _h = yield __await(_g.next()), _a = _h.done, !_a; _f = true) {
93
+ _c = _h.value;
94
+ _f = false;
95
+ const message = _c;
96
+ const eventName = message.event;
97
+ if (eventName === 'content_block_start') {
98
+ const payload = validateEventPayload(parseSseEventJson(message.data), anthropicContentBlockStartPayload);
99
+ const block = payload === null || payload === void 0 ? void 0 : payload.content_block;
100
+ if ((block === null || block === void 0 ? void 0 : block.type) === 'server_tool_use' && block.name === 'web_search') {
101
+ yield yield __await({ type: 'tool-event', toolType: 'web_search', phase: 'started' });
102
+ }
103
+ else if ((block === null || block === void 0 ? void 0 : block.type) === 'web_search_tool_result') {
104
+ yield yield __await({ type: 'tool-event', toolType: 'web_search', phase: 'completed' });
105
+ }
106
+ }
107
+ else if (eventName === 'content_block_delta') {
108
+ const payload = validateEventPayload(parseSseEventJson(message.data), anthropicContentBlockDeltaPayload);
109
+ if ((payload === null || payload === void 0 ? void 0 : payload.delta.type) === 'text_delta' && typeof payload.delta.text === 'string') {
110
+ const delta = payload.delta.text;
111
+ if (delta.length > 0) {
112
+ fullText += delta;
113
+ yield yield __await({ type: 'text-delta', delta });
114
+ }
115
+ }
116
+ }
117
+ else if (eventName === 'message_delta') {
118
+ const payload = validateEventPayload(parseSseEventJson(message.data), anthropicMessageDeltaPayload);
119
+ if ((payload === null || payload === void 0 ? void 0 : payload.delta.stop_reason) === 'max_tokens') {
120
+ truncated = true;
121
+ }
122
+ }
123
+ else if (eventName === 'message_stop') {
124
+ stopped = true;
125
+ }
126
+ else if (eventName === 'error') {
127
+ const payload = validateEventPayload(parseSseEventJson(message.data), anthropicErrorPayload);
128
+ yield yield __await({
129
+ type: 'error',
130
+ message: (_e = (_d = payload === null || payload === void 0 ? void 0 : payload.error) === null || _d === void 0 ? void 0 : _d.message) !== null && _e !== void 0 ? _e : 'Anthropic stream returned an error event'
131
+ });
132
+ return yield __await(void 0);
133
+ }
134
+ }
135
+ }
136
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
137
+ finally {
138
+ try {
139
+ if (!_f && !_a && (_b = _g.return)) yield __await(_b.call(_g));
140
+ }
141
+ finally { if (e_1) throw e_1.error; }
142
+ }
143
+ }
144
+ catch (err) {
145
+ yield yield __await({ type: 'error', message: err instanceof Error ? err.message : String(err) });
146
+ return yield __await(void 0);
147
+ }
148
+ if (stopped) {
149
+ yield yield __await({ type: 'done', truncated, fullText });
150
+ }
151
+ else {
152
+ yield yield __await({ type: 'error', message: 'Anthropic stream ended without a message_stop event' });
153
+ }
154
+ });
155
+ }
156
+ // ============================================================================
157
+ // Per-format request caller
158
+ // ============================================================================
159
+ /**
160
+ * Issues a streaming Anthropic Messages request and returns the
161
+ * unified-event iterable on success.
162
+ *
163
+ * @internal
164
+ */
165
+ export async function callAnthropicStream(config, prompt, messagesBefore, temperature, tools, logger, signal) {
166
+ const url = `${config.baseUrl}/messages`;
167
+ const messages = buildAnthropicMessages(prompt, { head: messagesBefore });
168
+ const body = {
169
+ model: config.model,
170
+ system: prompt.system,
171
+ messages,
172
+ max_tokens: 4096,
173
+ temperature,
174
+ stream: true
175
+ };
176
+ if (tools && tools.length > 0) {
177
+ body.tools = toAnthropicTools(tools);
178
+ }
179
+ const headers = {
180
+ 'x-api-key': config.apiKey,
181
+ 'anthropic-version': '2023-06-01',
182
+ 'anthropic-dangerous-direct-browser-access': 'true'
183
+ };
184
+ /* c8 ignore next 3 - optional logger diagnostic output */
185
+ if (logger) {
186
+ const toolTypes = tools && tools.length > 0 ? tools.map((t) => t.type).join(',') : 'none';
187
+ logger.info(`Anthropic streaming: model=${config.model}, tools=${toolTypes}`);
188
+ }
189
+ const conn = await openSseConnection(url, headers, body, logger, signal);
190
+ return conn.onSuccess((response) => succeed(translateAnthropicStream(response)));
191
+ }
192
+ //# sourceMappingURL=anthropic.js.map
@@ -0,0 +1,77 @@
1
+ // Copyright (c) 2026 Erik Fortune
2
+ //
3
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ // of this software and associated documentation files (the "Software"), to deal
5
+ // in the Software without restriction, including without limitation the rights
6
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ // copies of the Software, and to permit persons to whom the Software is
8
+ // furnished to do so, subject to the following conditions:
9
+ //
10
+ // The above copyright notice and this permission notice shall be included in all
11
+ // copies or substantial portions of the Software.
12
+ //
13
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ // SOFTWARE.
20
+ /**
21
+ * Shared infrastructure for the per-format streaming adapters: HTTP connection
22
+ * helper, common config and request-params types, and a typed-validation
23
+ * helper for SSE event payloads.
24
+ *
25
+ * @packageDocumentation
26
+ */
27
+ import { fail, succeed } from '@fgv/ts-utils';
28
+ /**
29
+ * Opens an SSE-style POST connection. Returns the underlying Response on a
30
+ * 2xx; failures (network, non-2xx, missing body) surface as Result.fail
31
+ * carrying the body text.
32
+ *
33
+ * @internal
34
+ */
35
+ export async function openSseConnection(url, headers, body, logger, signal) {
36
+ /* c8 ignore next 1 - optional logger */
37
+ logger === null || logger === void 0 ? void 0 : logger.detail(`AI streaming request: POST ${url}`);
38
+ let response;
39
+ try {
40
+ response = await fetch(url, {
41
+ method: 'POST',
42
+ headers: Object.assign({ 'Content-Type': 'application/json', Accept: 'text/event-stream' }, headers),
43
+ body: JSON.stringify(body),
44
+ signal
45
+ });
46
+ }
47
+ catch (err) {
48
+ const detail = err instanceof Error ? err.message : String(err);
49
+ /* c8 ignore next 1 - optional logger */
50
+ logger === null || logger === void 0 ? void 0 : logger.error(`AI streaming request failed: ${detail}`);
51
+ return fail(`AI streaming request failed: ${detail}`);
52
+ }
53
+ if (!response.ok) {
54
+ const errorText = await response.text().catch(() => 'unknown error');
55
+ /* c8 ignore next 1 - optional logger */
56
+ logger === null || logger === void 0 ? void 0 : logger.error(`AI streaming API returned ${response.status}: ${errorText}`);
57
+ return fail(`AI streaming API returned ${response.status}: ${errorText}`);
58
+ }
59
+ if (!response.body) {
60
+ return fail('AI streaming API returned an empty body');
61
+ }
62
+ return succeed(response);
63
+ }
64
+ /**
65
+ * Validates a parsed SSE event payload against a typed shape, returning the
66
+ * validated value or `undefined` if validation fails. Adapters use the
67
+ * `undefined` return to skip unrecognized event shapes — the same lenient
68
+ * behavior the inline casts had, but with a real type-checked path on the
69
+ * happy case.
70
+ *
71
+ * @internal
72
+ */
73
+ export function validateEventPayload(json, validator) {
74
+ const result = validator.validate(json);
75
+ return result.isSuccess() ? result.value : undefined;
76
+ }
77
+ //# sourceMappingURL=common.js.map
@@ -0,0 +1,160 @@
1
+ // Copyright (c) 2026 Erik Fortune
2
+ //
3
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ // of this software and associated documentation files (the "Software"), to deal
5
+ // in the Software without restriction, including without limitation the rights
6
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ // copies of the Software, and to permit persons to whom the Software is
8
+ // furnished to do so, subject to the following conditions:
9
+ //
10
+ // The above copyright notice and this permission notice shall be included in all
11
+ // copies or substantial portions of the Software.
12
+ //
13
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ // SOFTWARE.
20
+ var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
21
+ var __asyncValues = (this && this.__asyncValues) || function (o) {
22
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
23
+ var m = o[Symbol.asyncIterator], i;
24
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
25
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
26
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
27
+ };
28
+ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
29
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
30
+ var g = generator.apply(thisArg, _arguments || []), i, q = [];
31
+ return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
32
+ function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
33
+ function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
34
+ function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
35
+ function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
36
+ function fulfill(value) { resume("next", value); }
37
+ function reject(value) { resume("throw", value); }
38
+ function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
39
+ };
40
+ /**
41
+ * Streaming adapter for Gemini's `streamGenerateContent` endpoint. Gemini
42
+ * emits no explicit tool-progress events even when `google_search` is
43
+ * enabled — grounding metadata arrives attached to text chunks — so this
44
+ * adapter never yields `tool-event`s.
45
+ *
46
+ * @packageDocumentation
47
+ */
48
+ import { succeed, Validators } from '@fgv/ts-utils';
49
+ import { buildGeminiContents } from '../chatRequestBuilders';
50
+ import { parseSseEventJson, readSseEvents } from '../sseParser';
51
+ import { toGeminiTools } from '../toolFormats';
52
+ import { openSseConnection, validateEventPayload } from './common';
53
+ const geminiStreamPart = Validators.object({ text: Validators.string.optional() }, { options: { optionalFields: ['text'] } });
54
+ const geminiStreamContent = Validators.object({ parts: Validators.arrayOf(geminiStreamPart).optional() }, { options: { optionalFields: ['parts'] } });
55
+ const geminiStreamCandidate = Validators.object({
56
+ content: geminiStreamContent.optional(),
57
+ finishReason: Validators.string.optional()
58
+ }, { options: { optionalFields: ['content', 'finishReason'] } });
59
+ const geminiStreamChunk = Validators.object({
60
+ candidates: Validators.arrayOf(geminiStreamCandidate)
61
+ });
62
+ // ============================================================================
63
+ // Stream translator
64
+ // ============================================================================
65
+ /**
66
+ * Translates a Gemini streamGenerateContent SSE stream into unified events.
67
+ *
68
+ * @internal
69
+ */
70
+ function translateGeminiStream(response) {
71
+ return __asyncGenerator(this, arguments, function* translateGeminiStream_1() {
72
+ var _a, e_1, _b, _c;
73
+ var _d;
74
+ let fullText = '';
75
+ let truncated = false;
76
+ let receivedFinishReason = false;
77
+ try {
78
+ /* c8 ignore next - body is non-null at this point per openSseConnection */
79
+ if (!response.body)
80
+ return yield __await(void 0);
81
+ try {
82
+ for (var _e = true, _f = __asyncValues(readSseEvents(response.body)), _g; _g = yield __await(_f.next()), _a = _g.done, !_a; _e = true) {
83
+ _c = _g.value;
84
+ _e = false;
85
+ const message = _c;
86
+ const json = parseSseEventJson(message.data);
87
+ if (json === undefined) {
88
+ continue;
89
+ }
90
+ const chunk = validateEventPayload(json, geminiStreamChunk);
91
+ const candidate = chunk === null || chunk === void 0 ? void 0 : chunk.candidates[0];
92
+ if (!candidate) {
93
+ continue;
94
+ }
95
+ const parts = (_d = candidate.content) === null || _d === void 0 ? void 0 : _d.parts;
96
+ if (parts) {
97
+ for (const part of parts) {
98
+ if (typeof part.text === 'string' && part.text.length > 0) {
99
+ fullText += part.text;
100
+ yield yield __await({ type: 'text-delta', delta: part.text });
101
+ }
102
+ }
103
+ }
104
+ const finishReason = candidate.finishReason;
105
+ if (typeof finishReason === 'string' && finishReason.length > 0) {
106
+ truncated = finishReason === 'MAX_TOKENS';
107
+ receivedFinishReason = true;
108
+ }
109
+ }
110
+ }
111
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
112
+ finally {
113
+ try {
114
+ if (!_e && !_a && (_b = _f.return)) yield __await(_b.call(_f));
115
+ }
116
+ finally { if (e_1) throw e_1.error; }
117
+ }
118
+ }
119
+ catch (err) {
120
+ yield yield __await({ type: 'error', message: err instanceof Error ? err.message : String(err) });
121
+ return yield __await(void 0);
122
+ }
123
+ if (receivedFinishReason) {
124
+ yield yield __await({ type: 'done', truncated, fullText });
125
+ }
126
+ else {
127
+ yield yield __await({ type: 'error', message: 'Gemini stream ended without a finishReason' });
128
+ }
129
+ });
130
+ }
131
+ // ============================================================================
132
+ // Per-format request caller
133
+ // ============================================================================
134
+ /**
135
+ * Issues a streaming Gemini request and returns the unified-event iterable
136
+ * on success.
137
+ *
138
+ * @internal
139
+ */
140
+ export async function callGeminiStream(config, prompt, messagesBefore, temperature, tools, logger, signal) {
141
+ const url = `${config.baseUrl}/models/${config.model}:streamGenerateContent?alt=sse`;
142
+ const contents = buildGeminiContents(prompt, { head: messagesBefore });
143
+ const body = {
144
+ systemInstruction: { parts: [{ text: prompt.system }] },
145
+ contents,
146
+ generationConfig: { temperature }
147
+ };
148
+ if (tools && tools.length > 0) {
149
+ body.tools = toGeminiTools(tools);
150
+ }
151
+ const headers = { 'x-goog-api-key': config.apiKey };
152
+ /* c8 ignore next 3 - optional logger diagnostic output */
153
+ if (logger) {
154
+ const toolTypes = tools && tools.length > 0 ? tools.map((t) => t.type).join(',') : 'none';
155
+ logger.info(`Gemini streaming: model=${config.model}, tools=${toolTypes}`);
156
+ }
157
+ const conn = await openSseConnection(url, headers, body, logger, signal);
158
+ return conn.onSuccess((response) => succeed(translateGeminiStream(response)));
159
+ }
160
+ //# sourceMappingURL=gemini.js.map
@@ -0,0 +1,149 @@
1
+ // Copyright (c) 2026 Erik Fortune
2
+ //
3
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ // of this software and associated documentation files (the "Software"), to deal
5
+ // in the Software without restriction, including without limitation the rights
6
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ // copies of the Software, and to permit persons to whom the Software is
8
+ // furnished to do so, subject to the following conditions:
9
+ //
10
+ // The above copyright notice and this permission notice shall be included in all
11
+ // copies or substantial portions of the Software.
12
+ //
13
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ // SOFTWARE.
20
+ var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
21
+ var __asyncValues = (this && this.__asyncValues) || function (o) {
22
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
23
+ var m = o[Symbol.asyncIterator], i;
24
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
25
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
26
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
27
+ };
28
+ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
29
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
30
+ var g = generator.apply(thisArg, _arguments || []), i, q = [];
31
+ return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
32
+ function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
33
+ function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
34
+ function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
35
+ function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
36
+ function fulfill(value) { resume("next", value); }
37
+ function reject(value) { resume("throw", value); }
38
+ function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
39
+ };
40
+ /**
41
+ * Streaming adapter for OpenAI Chat Completions (also used for Groq, Mistral,
42
+ * and other Chat-Completions-compatible providers when no tools are
43
+ * requested).
44
+ *
45
+ * @packageDocumentation
46
+ */
47
+ import { succeed, Validators } from '@fgv/ts-utils';
48
+ import { buildMessages, buildOpenAiChatUserContent } from '../chatRequestBuilders';
49
+ import { parseSseEventJson, readSseEvents } from '../sseParser';
50
+ import { openSseConnection, validateEventPayload } from './common';
51
+ // eslint-disable-next-line @rushstack/no-new-null
52
+ const stringOrNull = Validators.isA('string-or-null',
53
+ // eslint-disable-next-line @rushstack/no-new-null
54
+ (v) => typeof v === 'string' || v === null);
55
+ const openAiChatStreamChoice = Validators.object({
56
+ delta: Validators.object({ content: Validators.string }, { options: { optionalFields: ['content'] } }).optional(),
57
+ finish_reason: stringOrNull.optional()
58
+ }, { options: { optionalFields: ['delta', 'finish_reason'] } });
59
+ const openAiChatStreamChunk = Validators.object({
60
+ choices: Validators.arrayOf(openAiChatStreamChoice)
61
+ });
62
+ // ============================================================================
63
+ // Stream translator
64
+ // ============================================================================
65
+ /**
66
+ * Translates an OpenAI Chat Completions SSE stream into unified events.
67
+ *
68
+ * @internal
69
+ */
70
+ function translateOpenAiChatStream(response) {
71
+ return __asyncGenerator(this, arguments, function* translateOpenAiChatStream_1() {
72
+ var _a, e_1, _b, _c;
73
+ var _d;
74
+ let fullText = '';
75
+ let truncated = false;
76
+ let receivedDone = false;
77
+ try {
78
+ /* c8 ignore next - body is non-null at this point per openSseConnection */
79
+ if (!response.body)
80
+ return yield __await(void 0);
81
+ try {
82
+ for (var _e = true, _f = __asyncValues(readSseEvents(response.body)), _g; _g = yield __await(_f.next()), _a = _g.done, !_a; _e = true) {
83
+ _c = _g.value;
84
+ _e = false;
85
+ const message = _c;
86
+ const json = parseSseEventJson(message.data);
87
+ if (json === undefined) {
88
+ // [DONE] sentinel or unparseable; skip
89
+ continue;
90
+ }
91
+ const chunk = validateEventPayload(json, openAiChatStreamChunk);
92
+ const choice = chunk === null || chunk === void 0 ? void 0 : chunk.choices[0];
93
+ if (!choice) {
94
+ continue;
95
+ }
96
+ const delta = (_d = choice.delta) === null || _d === void 0 ? void 0 : _d.content;
97
+ if (typeof delta === 'string' && delta.length > 0) {
98
+ fullText += delta;
99
+ yield yield __await({ type: 'text-delta', delta });
100
+ }
101
+ const finish = choice.finish_reason;
102
+ if (typeof finish === 'string' && finish.length > 0) {
103
+ truncated = finish === 'length';
104
+ receivedDone = true;
105
+ }
106
+ }
107
+ }
108
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
109
+ finally {
110
+ try {
111
+ if (!_e && !_a && (_b = _f.return)) yield __await(_b.call(_f));
112
+ }
113
+ finally { if (e_1) throw e_1.error; }
114
+ }
115
+ }
116
+ catch (err) {
117
+ yield yield __await({ type: 'error', message: err instanceof Error ? err.message : String(err) });
118
+ return yield __await(void 0);
119
+ }
120
+ if (receivedDone) {
121
+ yield yield __await({ type: 'done', truncated, fullText });
122
+ }
123
+ else {
124
+ yield yield __await({ type: 'error', message: 'OpenAI stream ended without a finish_reason' });
125
+ }
126
+ });
127
+ }
128
+ // ============================================================================
129
+ // Per-format request caller
130
+ // ============================================================================
131
+ /**
132
+ * Issues a streaming Chat Completions request and returns the unified-event
133
+ * iterable on success.
134
+ *
135
+ * @internal
136
+ */
137
+ export async function callOpenAiChatStream(config, prompt, messagesBefore, temperature, logger, signal) {
138
+ const url = `${config.baseUrl}/chat/completions`;
139
+ const messages = buildMessages(prompt.system, buildOpenAiChatUserContent(prompt), {
140
+ head: messagesBefore
141
+ });
142
+ const body = { model: config.model, messages, temperature, stream: true };
143
+ const headers = { Authorization: `Bearer ${config.apiKey}` };
144
+ /* c8 ignore next 1 - optional logger */
145
+ logger === null || logger === void 0 ? void 0 : logger.info(`OpenAI streaming completion: model=${config.model}`);
146
+ const conn = await openSseConnection(url, headers, body, logger, signal);
147
+ return conn.onSuccess((response) => succeed(translateOpenAiChatStream(response)));
148
+ }
149
+ //# sourceMappingURL=openaiChat.js.map