@fgv/ts-extras 5.1.0-16 → 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 (50) 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/zip-file-tree/zipFileTreeAccessors.js +2 -2
  17. package/dist/ts-extras.d.ts +512 -5
  18. package/lib/index.browser.d.ts +2 -1
  19. package/lib/index.browser.js +3 -1
  20. package/lib/packlets/ai-assist/apiClient.d.ts +103 -1
  21. package/lib/packlets/ai-assist/apiClient.js +574 -58
  22. package/lib/packlets/ai-assist/chatRequestBuilders.d.ts +89 -0
  23. package/lib/packlets/ai-assist/chatRequestBuilders.js +189 -0
  24. package/lib/packlets/ai-assist/index.d.ts +4 -3
  25. package/lib/packlets/ai-assist/index.js +10 -1
  26. package/lib/packlets/ai-assist/model.d.ts +271 -2
  27. package/lib/packlets/ai-assist/model.js +21 -3
  28. package/lib/packlets/ai-assist/registry.d.ts +10 -1
  29. package/lib/packlets/ai-assist/registry.js +67 -11
  30. package/lib/packlets/ai-assist/sseParser.d.ts +45 -0
  31. package/lib/packlets/ai-assist/sseParser.js +127 -0
  32. package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts +18 -0
  33. package/lib/packlets/ai-assist/streamingAdapters/anthropic.js +195 -0
  34. package/lib/packlets/ai-assist/streamingAdapters/common.d.ts +71 -0
  35. package/lib/packlets/ai-assist/streamingAdapters/common.js +81 -0
  36. package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts +19 -0
  37. package/lib/packlets/ai-assist/streamingAdapters/gemini.js +163 -0
  38. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts +18 -0
  39. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js +152 -0
  40. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts +19 -0
  41. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js +166 -0
  42. package/lib/packlets/ai-assist/streamingAdapters/proxy.d.ts +34 -0
  43. package/lib/packlets/ai-assist/streamingAdapters/proxy.js +160 -0
  44. package/lib/packlets/ai-assist/streamingClient.d.ts +33 -0
  45. package/lib/packlets/ai-assist/streamingClient.js +93 -0
  46. package/lib/packlets/conversion/converters.d.ts +1 -1
  47. package/lib/packlets/conversion/converters.js +1 -1
  48. package/lib/packlets/zip-file-tree/zipFileTreeAccessors.d.ts +2 -2
  49. package/lib/packlets/zip-file-tree/zipFileTreeAccessors.js +2 -2
  50. package/package.json +7 -7
@@ -3,7 +3,7 @@
3
3
  * @packageDocumentation
4
4
  */
5
5
  import { Result } from '@fgv/ts-utils';
6
- import { type AiProviderId, type IAiProviderDescriptor } from './model';
6
+ import { type AiProviderId, type IAiModelCapabilityConfig, type IAiProviderDescriptor } from './model';
7
7
  /**
8
8
  * All valid provider ID values, in the same order as the registry.
9
9
  * @public
@@ -22,4 +22,13 @@ export declare function getProviderDescriptors(): ReadonlyArray<IAiProviderDescr
22
22
  * @public
23
23
  */
24
24
  export declare function getProviderDescriptor(id: string): Result<IAiProviderDescriptor>;
25
+ /**
26
+ * Default capability config used by `callProviderListModels` when callers
27
+ * don't supply their own. Patterns are intentionally narrow — false
28
+ * positives are worse than missing a model. Caller can override per call
29
+ * via {@link IProviderListModelsParams.capabilityConfig}.
30
+ *
31
+ * @public
32
+ */
33
+ export declare const DEFAULT_MODEL_CAPABILITY_CONFIG: IAiModelCapabilityConfig;
25
34
  //# sourceMappingURL=registry.d.ts.map
@@ -19,7 +19,7 @@
19
19
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
20
  // SOFTWARE.
21
21
  Object.defineProperty(exports, "__esModule", { value: true });
22
- exports.allProviderIds = void 0;
22
+ exports.DEFAULT_MODEL_CAPABILITY_CONFIG = exports.allProviderIds = void 0;
23
23
  exports.getProviderDescriptors = getProviderDescriptors;
24
24
  exports.getProviderDescriptor = getProviderDescriptor;
25
25
  /**
@@ -44,7 +44,9 @@ const BUILTIN_PROVIDERS = [
44
44
  baseUrl: '',
45
45
  defaultModel: '',
46
46
  supportedTools: [],
47
- corsRestricted: false
47
+ corsRestricted: false,
48
+ streamingCorsRestricted: false,
49
+ acceptsImageInput: false
48
50
  },
49
51
  {
50
52
  id: 'anthropic',
@@ -55,7 +57,9 @@ const BUILTIN_PROVIDERS = [
55
57
  baseUrl: 'https://api.anthropic.com/v1',
56
58
  defaultModel: 'claude-sonnet-4-5-20250929',
57
59
  supportedTools: ['web_search'],
58
- corsRestricted: false
60
+ corsRestricted: false,
61
+ streamingCorsRestricted: false,
62
+ acceptsImageInput: true
59
63
  },
60
64
  {
61
65
  id: 'google-gemini',
@@ -64,9 +68,12 @@ const BUILTIN_PROVIDERS = [
64
68
  needsSecret: true,
65
69
  apiFormat: 'gemini',
66
70
  baseUrl: 'https://generativelanguage.googleapis.com/v1beta',
67
- defaultModel: 'gemini-2.5-flash',
71
+ defaultModel: { base: 'gemini-2.5-flash', image: 'imagen-3.0-generate-002' },
68
72
  supportedTools: ['web_search'],
69
- corsRestricted: false
73
+ corsRestricted: false,
74
+ streamingCorsRestricted: false,
75
+ acceptsImageInput: true,
76
+ imageApiFormat: 'gemini-imagen'
70
77
  },
71
78
  {
72
79
  id: 'groq',
@@ -77,7 +84,9 @@ const BUILTIN_PROVIDERS = [
77
84
  baseUrl: 'https://api.groq.com/openai/v1',
78
85
  defaultModel: 'llama-3.3-70b-versatile',
79
86
  supportedTools: [],
80
- corsRestricted: false
87
+ corsRestricted: false,
88
+ streamingCorsRestricted: false,
89
+ acceptsImageInput: false
81
90
  },
82
91
  {
83
92
  id: 'mistral',
@@ -88,7 +97,9 @@ const BUILTIN_PROVIDERS = [
88
97
  baseUrl: 'https://api.mistral.ai/v1',
89
98
  defaultModel: 'mistral-large-latest',
90
99
  supportedTools: [],
91
- corsRestricted: false
100
+ corsRestricted: false,
101
+ streamingCorsRestricted: false,
102
+ acceptsImageInput: false
92
103
  },
93
104
  {
94
105
  id: 'openai',
@@ -97,9 +108,12 @@ const BUILTIN_PROVIDERS = [
97
108
  needsSecret: true,
98
109
  apiFormat: 'openai',
99
110
  baseUrl: 'https://api.openai.com/v1',
100
- defaultModel: 'gpt-4o',
111
+ defaultModel: { base: 'gpt-4o', image: 'dall-e-3' },
101
112
  supportedTools: ['web_search'],
102
- corsRestricted: false
113
+ corsRestricted: false,
114
+ streamingCorsRestricted: false,
115
+ acceptsImageInput: true,
116
+ imageApiFormat: 'openai-images'
103
117
  },
104
118
  {
105
119
  id: 'xai-grok',
@@ -108,9 +122,16 @@ const BUILTIN_PROVIDERS = [
108
122
  needsSecret: true,
109
123
  apiFormat: 'openai',
110
124
  baseUrl: 'https://api.x.ai/v1',
111
- defaultModel: { base: 'grok-4-1-fast', tools: 'grok-4-1-fast-reasoning' },
125
+ defaultModel: {
126
+ base: 'grok-4-1-fast',
127
+ tools: 'grok-4-1-fast-reasoning',
128
+ image: 'grok-2-image-1212'
129
+ },
112
130
  supportedTools: ['web_search'],
113
- corsRestricted: true
131
+ corsRestricted: true,
132
+ streamingCorsRestricted: true,
133
+ acceptsImageInput: true,
134
+ imageApiFormat: 'xai-images'
114
135
  }
115
136
  ];
116
137
  /**
@@ -147,4 +168,39 @@ function getProviderDescriptor(id) {
147
168
  }
148
169
  return (0, ts_utils_1.succeed)(descriptor);
149
170
  }
171
+ // ============================================================================
172
+ // Default model capability config
173
+ // ============================================================================
174
+ /**
175
+ * Default capability config used by `callProviderListModels` when callers
176
+ * don't supply their own. Patterns are intentionally narrow — false
177
+ * positives are worse than missing a model. Caller can override per call
178
+ * via {@link IProviderListModelsParams.capabilityConfig}.
179
+ *
180
+ * @public
181
+ */
182
+ exports.DEFAULT_MODEL_CAPABILITY_CONFIG = {
183
+ perProvider: {
184
+ openai: [
185
+ { idPattern: /^dall-e/, capabilities: ['image-generation'] },
186
+ { idPattern: /^gpt-image/, capabilities: ['image-generation'] },
187
+ { idPattern: /^gpt-4/, capabilities: ['chat', 'tools', 'vision'] },
188
+ { idPattern: /^gpt-3\.5/, capabilities: ['chat'] },
189
+ { idPattern: /^o\d/, capabilities: ['chat', 'tools'] }
190
+ ],
191
+ 'xai-grok': [
192
+ { idPattern: /-image/, capabilities: ['image-generation'] },
193
+ { idPattern: /^grok-4/, capabilities: ['chat', 'tools', 'vision'] },
194
+ { idPattern: /^grok-3/, capabilities: ['chat', 'tools'] },
195
+ { idPattern: /^grok-2/, capabilities: ['chat', 'vision'] }
196
+ ],
197
+ 'google-gemini': [
198
+ { idPattern: /^imagen/, capabilities: ['image-generation'] },
199
+ { idPattern: /^gemini-/, capabilities: ['chat', 'tools', 'vision'] }
200
+ ],
201
+ anthropic: [{ idPattern: /^claude-/, capabilities: ['chat', 'tools', 'vision'] }],
202
+ groq: [{ idPattern: /./, capabilities: ['chat'] }],
203
+ mistral: [{ idPattern: /./, capabilities: ['chat'] }]
204
+ }
205
+ };
150
206
  //# sourceMappingURL=registry.js.map
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Server-Sent Events (SSE) parser primitives shared by the streaming format
3
+ * adapters in `streamingClient.ts`. Implements the subset of the SSE wire
4
+ * format that LLM providers actually emit: optional `event:` line, one or
5
+ * more `data:` lines per message, messages separated by a blank line.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ /**
10
+ * One parsed SSE message (the SSE spec calls each blank-line-terminated
11
+ * record an "event").
12
+ *
13
+ * @internal
14
+ */
15
+ export interface ISseEvent {
16
+ /** The `event:` field, if present (some providers omit it). */
17
+ readonly event?: string;
18
+ /** Concatenated contents of the message's `data:` lines. */
19
+ readonly data: string;
20
+ }
21
+ /**
22
+ * Parses a single SSE message (the text between blank-line separators) into
23
+ * an {@link ISseEvent}. Returns undefined for messages with no `data:` lines
24
+ * (comments, heartbeats).
25
+ *
26
+ * @internal
27
+ */
28
+ export declare function parseSseEvent(chunk: string): ISseEvent | undefined;
29
+ /**
30
+ * Reads an SSE response body and yields parsed events. Buffers across read()
31
+ * boundaries so a message split mid-chunk still parses cleanly. Terminates
32
+ * when the stream closes (normal EOF or aborted fetch).
33
+ *
34
+ * @internal
35
+ */
36
+ export declare function readSseEvents(body: ReadableStream<Uint8Array>): AsyncGenerator<ISseEvent>;
37
+ /**
38
+ * Parses the `data` payload of an SSE event as JSON. Returns undefined for
39
+ * the OpenAI `[DONE]` sentinel and for any payload that fails to parse —
40
+ * adapters treat both as "skip this event."
41
+ *
42
+ * @internal
43
+ */
44
+ export declare function parseSseEventJson(data: string): unknown | undefined;
45
+ //# sourceMappingURL=sseParser.d.ts.map
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ // Copyright (c) 2026 Erik Fortune
3
+ //
4
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ // of this software and associated documentation files (the "Software"), to deal
6
+ // in the Software without restriction, including without limitation the rights
7
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ // copies of the Software, and to permit persons to whom the Software is
9
+ // furnished to do so, subject to the following conditions:
10
+ //
11
+ // The above copyright notice and this permission notice shall be included in all
12
+ // copies or substantial portions of the Software.
13
+ //
14
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ // SOFTWARE.
21
+ var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
22
+ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
23
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
24
+ var g = generator.apply(thisArg, _arguments || []), i, q = [];
25
+ return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
26
+ function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
27
+ 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]); } }
28
+ function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
29
+ function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
30
+ function fulfill(value) { resume("next", value); }
31
+ function reject(value) { resume("throw", value); }
32
+ function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
33
+ };
34
+ Object.defineProperty(exports, "__esModule", { value: true });
35
+ exports.parseSseEvent = parseSseEvent;
36
+ exports.readSseEvents = readSseEvents;
37
+ exports.parseSseEventJson = parseSseEventJson;
38
+ /**
39
+ * Parses a single SSE message (the text between blank-line separators) into
40
+ * an {@link ISseEvent}. Returns undefined for messages with no `data:` lines
41
+ * (comments, heartbeats).
42
+ *
43
+ * @internal
44
+ */
45
+ function parseSseEvent(chunk) {
46
+ let event;
47
+ const dataLines = [];
48
+ for (const line of chunk.split('\n')) {
49
+ if (line.startsWith('event:')) {
50
+ event = line.slice(6).trim();
51
+ }
52
+ else if (line.startsWith('data:')) {
53
+ // Per the SSE spec the value starts after the colon, with one optional leading space stripped.
54
+ const value = line.slice(5);
55
+ dataLines.push(value.startsWith(' ') ? value.slice(1) : value);
56
+ }
57
+ }
58
+ if (dataLines.length === 0) {
59
+ return undefined;
60
+ }
61
+ return event !== undefined ? { event, data: dataLines.join('\n') } : { data: dataLines.join('\n') };
62
+ }
63
+ /**
64
+ * Reads an SSE response body and yields parsed events. Buffers across read()
65
+ * boundaries so a message split mid-chunk still parses cleanly. Terminates
66
+ * when the stream closes (normal EOF or aborted fetch).
67
+ *
68
+ * @internal
69
+ */
70
+ function readSseEvents(body) {
71
+ return __asyncGenerator(this, arguments, function* readSseEvents_1() {
72
+ var _a;
73
+ const reader = body.getReader();
74
+ const decoder = new TextDecoder();
75
+ let buffer = '';
76
+ try {
77
+ let streaming = true;
78
+ while (streaming) {
79
+ const { value, done } = yield __await(reader.read());
80
+ if (done) {
81
+ streaming = false;
82
+ if (buffer.length > 0) {
83
+ const tail = parseSseEvent(buffer.replace(/\r\n/g, '\n'));
84
+ if (tail) {
85
+ yield yield __await(tail);
86
+ }
87
+ }
88
+ break;
89
+ }
90
+ buffer += decoder.decode(value, { stream: true });
91
+ // SSE messages are separated by a blank line; some servers use \r\n.
92
+ const normalized = buffer.replace(/\r\n/g, '\n');
93
+ const parts = normalized.split('\n\n');
94
+ // Last element is the partial chunk (no terminating blank line yet); buffer it.
95
+ buffer = (_a = parts.pop()) !== null && _a !== void 0 ? _a : '';
96
+ for (const chunk of parts) {
97
+ const event = parseSseEvent(chunk);
98
+ if (event) {
99
+ yield yield __await(event);
100
+ }
101
+ }
102
+ }
103
+ }
104
+ finally {
105
+ reader.releaseLock();
106
+ }
107
+ });
108
+ }
109
+ /**
110
+ * Parses the `data` payload of an SSE event as JSON. Returns undefined for
111
+ * the OpenAI `[DONE]` sentinel and for any payload that fails to parse —
112
+ * adapters treat both as "skip this event."
113
+ *
114
+ * @internal
115
+ */
116
+ function parseSseEventJson(data) {
117
+ if (data === '[DONE]') {
118
+ return undefined;
119
+ }
120
+ try {
121
+ return JSON.parse(data);
122
+ }
123
+ catch (_a) {
124
+ return undefined;
125
+ }
126
+ }
127
+ //# sourceMappingURL=sseParser.js.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Streaming adapter for the Anthropic Messages API. Surfaces server tool
3
+ * use (e.g. `web_search`) as unified `tool-event` markers based on the
4
+ * `content_block_start` / `content_block_stop` lifecycle.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+ import { type Logging, Result } from '@fgv/ts-utils';
9
+ import { AiPrompt, type AiServerToolConfig, type IAiStreamEvent, type IChatMessage } from '../model';
10
+ import { IStreamApiConfig } from './common';
11
+ /**
12
+ * Issues a streaming Anthropic Messages request and returns the
13
+ * unified-event iterable on success.
14
+ *
15
+ * @internal
16
+ */
17
+ export declare function callAnthropicStream(config: IStreamApiConfig, prompt: AiPrompt, messagesBefore: ReadonlyArray<IChatMessage> | undefined, temperature: number, tools: ReadonlyArray<AiServerToolConfig> | undefined, logger?: Logging.ILogger, signal?: AbortSignal): Promise<Result<AsyncIterable<IAiStreamEvent>>>;
18
+ //# sourceMappingURL=anthropic.d.ts.map
@@ -0,0 +1,195 @@
1
+ "use strict";
2
+ // Copyright (c) 2026 Erik Fortune
3
+ //
4
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ // of this software and associated documentation files (the "Software"), to deal
6
+ // in the Software without restriction, including without limitation the rights
7
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ // copies of the Software, and to permit persons to whom the Software is
9
+ // furnished to do so, subject to the following conditions:
10
+ //
11
+ // The above copyright notice and this permission notice shall be included in all
12
+ // copies or substantial portions of the Software.
13
+ //
14
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ // SOFTWARE.
21
+ var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
22
+ var __asyncValues = (this && this.__asyncValues) || function (o) {
23
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
24
+ var m = o[Symbol.asyncIterator], i;
25
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
26
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
27
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
28
+ };
29
+ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
30
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
31
+ var g = generator.apply(thisArg, _arguments || []), i, q = [];
32
+ return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
33
+ function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
34
+ function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
35
+ function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
36
+ function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
37
+ function fulfill(value) { resume("next", value); }
38
+ function reject(value) { resume("throw", value); }
39
+ function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
40
+ };
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.callAnthropicStream = callAnthropicStream;
43
+ /**
44
+ * Streaming adapter for the Anthropic Messages API. Surfaces server tool
45
+ * use (e.g. `web_search`) as unified `tool-event` markers based on the
46
+ * `content_block_start` / `content_block_stop` lifecycle.
47
+ *
48
+ * @packageDocumentation
49
+ */
50
+ const ts_utils_1 = require("@fgv/ts-utils");
51
+ const chatRequestBuilders_1 = require("../chatRequestBuilders");
52
+ const sseParser_1 = require("../sseParser");
53
+ const toolFormats_1 = require("../toolFormats");
54
+ const common_1 = require("./common");
55
+ const anthropicContentBlockInner = ts_utils_1.Validators.object({
56
+ type: ts_utils_1.Validators.string.optional(),
57
+ name: ts_utils_1.Validators.string.optional()
58
+ }, { options: { optionalFields: ['type', 'name'] } });
59
+ const anthropicContentBlockStartPayload = ts_utils_1.Validators.object({
60
+ content_block: anthropicContentBlockInner
61
+ });
62
+ const anthropicContentBlockDeltaInner = ts_utils_1.Validators.object({
63
+ type: ts_utils_1.Validators.string.optional(),
64
+ text: ts_utils_1.Validators.string.optional()
65
+ }, { options: { optionalFields: ['type', 'text'] } });
66
+ const anthropicContentBlockDeltaPayload = ts_utils_1.Validators.object({
67
+ delta: anthropicContentBlockDeltaInner
68
+ });
69
+ const anthropicMessageDeltaInner = ts_utils_1.Validators.object({ stop_reason: ts_utils_1.Validators.string.optional() }, { options: { optionalFields: ['stop_reason'] } });
70
+ const anthropicMessageDeltaPayload = ts_utils_1.Validators.object({
71
+ delta: anthropicMessageDeltaInner
72
+ });
73
+ const anthropicErrorInner = ts_utils_1.Validators.object({ message: ts_utils_1.Validators.string.optional() }, { options: { optionalFields: ['message'] } });
74
+ const anthropicErrorPayload = ts_utils_1.Validators.object({ error: anthropicErrorInner.optional() }, { options: { optionalFields: ['error'] } });
75
+ // ============================================================================
76
+ // Stream translator
77
+ // ============================================================================
78
+ /**
79
+ * Translates an Anthropic Messages API SSE stream into unified events.
80
+ *
81
+ * @internal
82
+ */
83
+ function translateAnthropicStream(response) {
84
+ return __asyncGenerator(this, arguments, function* translateAnthropicStream_1() {
85
+ var _a, e_1, _b, _c;
86
+ var _d, _e;
87
+ let fullText = '';
88
+ let truncated = false;
89
+ let stopped = false;
90
+ try {
91
+ /* c8 ignore next - body is non-null at this point per openSseConnection */
92
+ if (!response.body)
93
+ return yield __await(void 0);
94
+ try {
95
+ for (var _f = true, _g = __asyncValues((0, sseParser_1.readSseEvents)(response.body)), _h; _h = yield __await(_g.next()), _a = _h.done, !_a; _f = true) {
96
+ _c = _h.value;
97
+ _f = false;
98
+ const message = _c;
99
+ const eventName = message.event;
100
+ if (eventName === 'content_block_start') {
101
+ const payload = (0, common_1.validateEventPayload)((0, sseParser_1.parseSseEventJson)(message.data), anthropicContentBlockStartPayload);
102
+ const block = payload === null || payload === void 0 ? void 0 : payload.content_block;
103
+ if ((block === null || block === void 0 ? void 0 : block.type) === 'server_tool_use' && block.name === 'web_search') {
104
+ yield yield __await({ type: 'tool-event', toolType: 'web_search', phase: 'started' });
105
+ }
106
+ else if ((block === null || block === void 0 ? void 0 : block.type) === 'web_search_tool_result') {
107
+ yield yield __await({ type: 'tool-event', toolType: 'web_search', phase: 'completed' });
108
+ }
109
+ }
110
+ else if (eventName === 'content_block_delta') {
111
+ const payload = (0, common_1.validateEventPayload)((0, sseParser_1.parseSseEventJson)(message.data), anthropicContentBlockDeltaPayload);
112
+ if ((payload === null || payload === void 0 ? void 0 : payload.delta.type) === 'text_delta' && typeof payload.delta.text === 'string') {
113
+ const delta = payload.delta.text;
114
+ if (delta.length > 0) {
115
+ fullText += delta;
116
+ yield yield __await({ type: 'text-delta', delta });
117
+ }
118
+ }
119
+ }
120
+ else if (eventName === 'message_delta') {
121
+ const payload = (0, common_1.validateEventPayload)((0, sseParser_1.parseSseEventJson)(message.data), anthropicMessageDeltaPayload);
122
+ if ((payload === null || payload === void 0 ? void 0 : payload.delta.stop_reason) === 'max_tokens') {
123
+ truncated = true;
124
+ }
125
+ }
126
+ else if (eventName === 'message_stop') {
127
+ stopped = true;
128
+ }
129
+ else if (eventName === 'error') {
130
+ const payload = (0, common_1.validateEventPayload)((0, sseParser_1.parseSseEventJson)(message.data), anthropicErrorPayload);
131
+ yield yield __await({
132
+ type: 'error',
133
+ 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'
134
+ });
135
+ return yield __await(void 0);
136
+ }
137
+ }
138
+ }
139
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
140
+ finally {
141
+ try {
142
+ if (!_f && !_a && (_b = _g.return)) yield __await(_b.call(_g));
143
+ }
144
+ finally { if (e_1) throw e_1.error; }
145
+ }
146
+ }
147
+ catch (err) {
148
+ yield yield __await({ type: 'error', message: err instanceof Error ? err.message : String(err) });
149
+ return yield __await(void 0);
150
+ }
151
+ if (stopped) {
152
+ yield yield __await({ type: 'done', truncated, fullText });
153
+ }
154
+ else {
155
+ yield yield __await({ type: 'error', message: 'Anthropic stream ended without a message_stop event' });
156
+ }
157
+ });
158
+ }
159
+ // ============================================================================
160
+ // Per-format request caller
161
+ // ============================================================================
162
+ /**
163
+ * Issues a streaming Anthropic Messages request and returns the
164
+ * unified-event iterable on success.
165
+ *
166
+ * @internal
167
+ */
168
+ async function callAnthropicStream(config, prompt, messagesBefore, temperature, tools, logger, signal) {
169
+ const url = `${config.baseUrl}/messages`;
170
+ const messages = (0, chatRequestBuilders_1.buildAnthropicMessages)(prompt, { head: messagesBefore });
171
+ const body = {
172
+ model: config.model,
173
+ system: prompt.system,
174
+ messages,
175
+ max_tokens: 4096,
176
+ temperature,
177
+ stream: true
178
+ };
179
+ if (tools && tools.length > 0) {
180
+ body.tools = (0, toolFormats_1.toAnthropicTools)(tools);
181
+ }
182
+ const headers = {
183
+ 'x-api-key': config.apiKey,
184
+ 'anthropic-version': '2023-06-01',
185
+ 'anthropic-dangerous-direct-browser-access': 'true'
186
+ };
187
+ /* c8 ignore next 3 - optional logger diagnostic output */
188
+ if (logger) {
189
+ const toolTypes = tools && tools.length > 0 ? tools.map((t) => t.type).join(',') : 'none';
190
+ logger.info(`Anthropic streaming: model=${config.model}, tools=${toolTypes}`);
191
+ }
192
+ const conn = await (0, common_1.openSseConnection)(url, headers, body, logger, signal);
193
+ return conn.onSuccess((response) => (0, ts_utils_1.succeed)(translateAnthropicStream(response)));
194
+ }
195
+ //# sourceMappingURL=anthropic.js.map
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Shared infrastructure for the per-format streaming adapters: HTTP connection
3
+ * helper, common config and request-params types, and a typed-validation
4
+ * helper for SSE event payloads.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+ import { type Logging, Result, type Validator } from '@fgv/ts-utils';
9
+ import { AiPrompt, type AiServerToolConfig, type IAiProviderDescriptor, type IChatMessage, type ModelSpec } from '../model';
10
+ /**
11
+ * Parameters for a streaming completion request. Structurally identical to
12
+ * the non-streaming `IProviderCompletionParams`; kept as its own interface
13
+ * so callers can be explicit about which path they're invoking.
14
+ *
15
+ * @public
16
+ */
17
+ export interface IProviderCompletionStreamParams {
18
+ /** The provider descriptor */
19
+ readonly descriptor: IAiProviderDescriptor;
20
+ /** API key for authentication */
21
+ readonly apiKey: string;
22
+ /** The structured prompt to send */
23
+ readonly prompt: AiPrompt;
24
+ /**
25
+ * Prior conversation history to insert between the system prompt and the
26
+ * prompt's user message. The new user turn (carried by `prompt.user`) is
27
+ * always sent last, so the wire shape becomes
28
+ * `[system, ...messagesBefore, user=prompt.user]`.
29
+ */
30
+ readonly messagesBefore?: ReadonlyArray<IChatMessage>;
31
+ /** Sampling temperature (default: 0.7) */
32
+ readonly temperature?: number;
33
+ /** Optional model override — string or context-aware map. */
34
+ readonly modelOverride?: ModelSpec;
35
+ /** Optional logger for request/response observability. */
36
+ readonly logger?: Logging.ILogger;
37
+ /** Server-side tools to include in the request. */
38
+ readonly tools?: ReadonlyArray<AiServerToolConfig>;
39
+ /** Optional abort signal for cancelling the in-flight stream. */
40
+ readonly signal?: AbortSignal;
41
+ }
42
+ /**
43
+ * Configuration for a single per-format streaming call: where to POST, what
44
+ * key to authenticate with, and which model to request.
45
+ *
46
+ * @internal
47
+ */
48
+ export interface IStreamApiConfig {
49
+ readonly baseUrl: string;
50
+ readonly apiKey: string;
51
+ readonly model: string;
52
+ }
53
+ /**
54
+ * Opens an SSE-style POST connection. Returns the underlying Response on a
55
+ * 2xx; failures (network, non-2xx, missing body) surface as Result.fail
56
+ * carrying the body text.
57
+ *
58
+ * @internal
59
+ */
60
+ export declare function openSseConnection(url: string, headers: Record<string, string>, body: unknown, logger?: Logging.ILogger, signal?: AbortSignal): Promise<Result<Response>>;
61
+ /**
62
+ * Validates a parsed SSE event payload against a typed shape, returning the
63
+ * validated value or `undefined` if validation fails. Adapters use the
64
+ * `undefined` return to skip unrecognized event shapes — the same lenient
65
+ * behavior the inline casts had, but with a real type-checked path on the
66
+ * happy case.
67
+ *
68
+ * @internal
69
+ */
70
+ export declare function validateEventPayload<T>(json: unknown, validator: Validator<T>): T | undefined;
71
+ //# sourceMappingURL=common.d.ts.map