@meistrari/tela-sdk-js 0.0.2 → 0.0.4

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/streaming.js DELETED
@@ -1,462 +0,0 @@
1
- /* eslint-disable node/prefer-global/buffer */
2
- import { ReadableStream } from 'node:stream/web';
3
- import { APIError, TelaError } from './errors';
4
- import { DEFAULT_FIELDS_TRANSFORMATION_EXCLUSIONS } from './utils/constants';
5
- import { transformObjectFromSnakeCaseToCamelCase } from './utils/data-transformation';
6
- /**
7
- * Represents a stream of items that can be asynchronously iterated over.
8
- */
9
- export class Stream {
10
- iterator;
11
- /**
12
- * The AbortController associated with this stream.
13
- */
14
- controller;
15
- /**
16
- * Creates a new Stream instance.
17
- *
18
- * @param iterator - A function that returns an AsyncIterator<Item>.
19
- * @param controller - The AbortController for this stream.
20
- */
21
- constructor(iterator, controller) {
22
- this.iterator = iterator;
23
- this.controller = controller;
24
- }
25
- /**
26
- * Creates a Stream from a server-sent events (SSE) Response.
27
- *
28
- * @param response - The Response object containing the SSE data.
29
- * @param controller - The AbortController for this stream.
30
- * @returns A new Stream instance.
31
- * @throws {Error} If the stream is consumed more than once.
32
- * @throws {APIError} If the SSE data contains an error.
33
- */
34
- static fromSSEResponse(response, controller) {
35
- let consumed = false;
36
- async function* iterator() {
37
- if (consumed) {
38
- throw new Error('Cannot iterate over a consumed stream, use `.tee()` to split the stream.');
39
- }
40
- consumed = true;
41
- let done = false;
42
- try {
43
- for await (const sse of _iterSSEMessages(response, controller)) {
44
- if (done)
45
- continue;
46
- if (sse.data.startsWith('[DONE]')) {
47
- done = true;
48
- continue;
49
- }
50
- if (sse.event === null) {
51
- let data;
52
- try {
53
- data = JSON.parse(sse.data);
54
- data = transformObjectFromSnakeCaseToCamelCase(data, DEFAULT_FIELDS_TRANSFORMATION_EXCLUSIONS);
55
- }
56
- catch (e) {
57
- console.error(`Could not parse message into JSON:`, sse.data);
58
- console.error(`From chunk:`, sse.raw);
59
- throw e;
60
- }
61
- if (data && data.error) {
62
- throw new APIError(undefined, data.error, undefined);
63
- }
64
- yield data;
65
- }
66
- else {
67
- let data;
68
- try {
69
- data = JSON.parse(sse.data);
70
- }
71
- catch (e) {
72
- console.error(`Could not parse message into JSON:`, sse.data);
73
- console.error(`From chunk:`, sse.raw);
74
- throw e;
75
- }
76
- if (sse.event === 'error') {
77
- throw new APIError(undefined, data.error, data.message);
78
- }
79
- yield { event: sse.event, data };
80
- }
81
- }
82
- done = true;
83
- }
84
- catch (e) {
85
- // If the user calls `stream.controller.abort()`, we should exit without throwing.
86
- if (e instanceof Error && e.name === 'AbortError')
87
- return;
88
- throw e;
89
- }
90
- finally {
91
- // If the user `break`s, abort the ongoing request.
92
- if (!done)
93
- controller.abort();
94
- }
95
- }
96
- return new Stream(iterator, controller);
97
- }
98
- /**
99
- * Creates a Stream from a newline-separated ReadableStream where each item is a JSON value.
100
- *
101
- * @param readableStream - The ReadableStream containing newline-separated JSON data.
102
- * @param controller - The AbortController for this stream.
103
- * @returns A new Stream instance.
104
- * @throws {Error} If the stream is consumed more than once.
105
- */
106
- static fromReadableStream(readableStream, controller) {
107
- let consumed = false;
108
- async function* iterLines() {
109
- const lineDecoder = new LineDecoder();
110
- const iter = readableStreamAsyncIterable(readableStream);
111
- for await (const chunk of iter) {
112
- for (const line of lineDecoder.decode(chunk)) {
113
- yield line;
114
- }
115
- }
116
- for (const line of lineDecoder.flush()) {
117
- yield line;
118
- }
119
- }
120
- async function* iterator() {
121
- if (consumed) {
122
- throw new Error('Cannot iterate over a consumed stream, use `.tee()` to split the stream.');
123
- }
124
- consumed = true;
125
- let done = false;
126
- try {
127
- for await (const line of iterLines()) {
128
- if (done)
129
- continue;
130
- if (line)
131
- yield JSON.parse(line);
132
- }
133
- done = true;
134
- }
135
- catch (e) {
136
- // If the user calls `stream.controller.abort()`, we should exit without throwing.
137
- if (e instanceof Error && e.name === 'AbortError')
138
- return;
139
- throw e;
140
- }
141
- finally {
142
- // If the user `break`s, abort the ongoing request.
143
- if (!done)
144
- controller.abort();
145
- }
146
- }
147
- return new Stream(iterator, controller);
148
- }
149
- /**
150
- * Implements the AsyncIterable interface, allowing the Stream to be used in a for-await-of loop.
151
- *
152
- * @returns An AsyncIterator for the stream items.
153
- */
154
- [Symbol.asyncIterator]() {
155
- return this.iterator();
156
- }
157
- /**
158
- * Splits the stream into two streams which can be independently read from at different speeds.
159
- *
160
- * @returns A tuple containing two new Stream instances.
161
- */
162
- tee() {
163
- const left = [];
164
- const right = [];
165
- const iterator = this.iterator();
166
- const teeIterator = (queue) => {
167
- return {
168
- next: () => {
169
- if (queue.length === 0) {
170
- const result = iterator.next();
171
- left.push(result);
172
- right.push(result);
173
- }
174
- return queue.shift();
175
- },
176
- };
177
- };
178
- return [
179
- new Stream(() => teeIterator(left), this.controller),
180
- new Stream(() => teeIterator(right), this.controller),
181
- ];
182
- }
183
- /**
184
- * Converts this stream to a newline-separated ReadableStream of JSON stringified values.
185
- * The resulting stream can be converted back to a Stream using `Stream.fromReadableStream()`.
186
- *
187
- * @returns A ReadableStream containing newline-separated JSON data.
188
- */
189
- toReadableStream() {
190
- // eslint-disable-next-line ts/no-this-alias
191
- const self = this;
192
- let iter;
193
- const encoder = new TextEncoder();
194
- return new ReadableStream({
195
- async start() {
196
- iter = self[Symbol.asyncIterator]();
197
- },
198
- async pull(ctrl) {
199
- try {
200
- const { value, done } = await iter.next();
201
- if (done)
202
- return ctrl.close();
203
- const bytes = encoder.encode(`${JSON.stringify(value)}\n`);
204
- ctrl.enqueue(bytes);
205
- }
206
- catch (err) {
207
- ctrl.error(err);
208
- }
209
- },
210
- async cancel() {
211
- await iter.return?.();
212
- },
213
- });
214
- }
215
- }
216
- export async function* _iterSSEMessages(response, controller) {
217
- if (!response.body) {
218
- controller.abort();
219
- throw new TelaError(`Attempted to iterate over a response with no body`);
220
- }
221
- const sseDecoder = new SSEDecoder();
222
- const lineDecoder = new LineDecoder();
223
- const iter = readableStreamAsyncIterable(response.body);
224
- for await (const sseChunk of iterSSEChunks(iter)) {
225
- for (const line of lineDecoder.decode(sseChunk)) {
226
- const sse = sseDecoder.decode(line);
227
- if (sse)
228
- yield sse;
229
- }
230
- }
231
- for (const line of lineDecoder.flush()) {
232
- const sse = sseDecoder.decode(line);
233
- if (sse)
234
- yield sse;
235
- }
236
- }
237
- /**
238
- * Given an async iterable iterator, iterates over it and yields full
239
- * SSE chunks, i.e. yields when a double new-line is encountered.
240
- */
241
- async function* iterSSEChunks(iterator) {
242
- let data = new Uint8Array();
243
- for await (const chunk of iterator) {
244
- if (chunk == null) {
245
- continue;
246
- }
247
- const binaryChunk = chunk instanceof ArrayBuffer
248
- ? new Uint8Array(chunk)
249
- : typeof chunk === 'string'
250
- ? new TextEncoder().encode(chunk)
251
- : chunk;
252
- const newData = new Uint8Array(data.length + binaryChunk.length);
253
- newData.set(data);
254
- newData.set(binaryChunk, data.length);
255
- data = newData;
256
- let patternIndex = findDoubleNewlineIndex(data);
257
- while (patternIndex !== -1) {
258
- yield data.slice(0, patternIndex);
259
- data = data.slice(patternIndex);
260
- patternIndex = findDoubleNewlineIndex(data);
261
- }
262
- }
263
- if (data.length > 0) {
264
- yield data;
265
- }
266
- }
267
- function findDoubleNewlineIndex(buffer) {
268
- // This function searches the buffer for the end patterns (\r\r, \n\n, \r\n\r\n)
269
- // and returns the index right after the first occurrence of any pattern,
270
- // or -1 if none of the patterns are found.
271
- const newline = 0x0A; // \n
272
- const carriage = 0x0D; // \r
273
- for (let i = 0; i < buffer.length - 2; i++) {
274
- if (buffer[i] === newline && buffer[i + 1] === newline) {
275
- // \n\n
276
- return i + 2;
277
- }
278
- if (buffer[i] === carriage && buffer[i + 1] === carriage) {
279
- // \r\r
280
- return i + 2;
281
- }
282
- if (buffer[i] === carriage
283
- && buffer[i + 1] === newline
284
- && i + 3 < buffer.length
285
- && buffer[i + 2] === carriage
286
- && buffer[i + 3] === newline) {
287
- // \r\n\r\n
288
- return i + 4;
289
- }
290
- }
291
- return -1;
292
- }
293
- class SSEDecoder {
294
- data;
295
- event;
296
- chunks;
297
- constructor() {
298
- this.event = null;
299
- this.data = [];
300
- this.chunks = [];
301
- }
302
- decode(line) {
303
- if (line.endsWith('\r')) {
304
- line = line.substring(0, line.length - 1);
305
- }
306
- if (!line) {
307
- // empty line and we didn't previously encounter any messages
308
- if (!this.event && !this.data.length)
309
- return null;
310
- const sse = {
311
- event: this.event,
312
- data: this.data.join('\n'),
313
- raw: this.chunks,
314
- };
315
- this.event = null;
316
- this.data = [];
317
- this.chunks = [];
318
- return sse;
319
- }
320
- this.chunks.push(line);
321
- if (line.startsWith(':')) {
322
- return null;
323
- }
324
- let [fieldname, _, value] = partition(line, ':');
325
- if (value.startsWith(' ')) {
326
- value = value.substring(1);
327
- }
328
- if (fieldname === 'event') {
329
- this.event = value;
330
- }
331
- else if (fieldname === 'data') {
332
- this.data.push(value);
333
- }
334
- return null;
335
- }
336
- }
337
- /**
338
- * A re-implementation of httpx's `LineDecoder` in Python that handles incrementally
339
- * reading lines from text.
340
- *
341
- * https://github.com/encode/httpx/blob/920333ea98118e9cf617f246905d7b202510941c/httpx/_decoders.py#L258
342
- */
343
- class LineDecoder {
344
- // prettier-ignore
345
- static NEWLINE_CHARS = new Set(['\n', '\r']);
346
- static NEWLINE_REGEXP = /\r\n|[\n\r]/g;
347
- buffer;
348
- trailingCR;
349
- textDecoder; // TextDecoder found in browsers; not typed to avoid pulling in either "dom" or "node" types.
350
- constructor() {
351
- this.buffer = [];
352
- this.trailingCR = false;
353
- }
354
- decode(chunk) {
355
- let text = this.decodeText(chunk);
356
- if (this.trailingCR) {
357
- text = `\r${text}`;
358
- this.trailingCR = false;
359
- }
360
- if (text.endsWith('\r')) {
361
- this.trailingCR = true;
362
- text = text.slice(0, -1);
363
- }
364
- if (!text) {
365
- return [];
366
- }
367
- const trailingNewline = LineDecoder.NEWLINE_CHARS.has(text[text.length - 1] || '');
368
- let lines = text.split(LineDecoder.NEWLINE_REGEXP);
369
- // if there is a trailing new line then the last entry will be an empty
370
- // string which we don't care about
371
- if (trailingNewline) {
372
- lines.pop();
373
- }
374
- if (lines.length === 1 && !trailingNewline) {
375
- this.buffer.push(lines[0]);
376
- return [];
377
- }
378
- if (this.buffer.length > 0) {
379
- lines = [this.buffer.join('') + lines[0], ...lines.slice(1)];
380
- this.buffer = [];
381
- }
382
- if (!trailingNewline) {
383
- this.buffer = [lines.pop() || ''];
384
- }
385
- return lines;
386
- }
387
- decodeText(bytes) {
388
- if (bytes == null)
389
- return '';
390
- if (typeof bytes === 'string')
391
- return bytes;
392
- // Node:
393
- if (typeof Buffer !== 'undefined') {
394
- if (bytes instanceof Buffer) {
395
- return bytes.toString();
396
- }
397
- if (bytes instanceof Uint8Array) {
398
- return Buffer.from(bytes).toString();
399
- }
400
- throw new TelaError(`Unexpected: received non-Uint8Array (${bytes.constructor.name}) stream chunk in an environment with a global "Buffer" defined, which this library assumes to be Node. Please report this error.`);
401
- }
402
- // Browser
403
- if (typeof TextDecoder !== 'undefined') {
404
- if (bytes instanceof Uint8Array || bytes instanceof ArrayBuffer) {
405
- this.textDecoder ??= new TextDecoder('utf8');
406
- return this.textDecoder.decode(bytes);
407
- }
408
- throw new TelaError(`Unexpected: received non-Uint8Array/ArrayBuffer (${bytes.constructor.name}) in a web platform. Please report this error.`);
409
- }
410
- throw new TelaError(`Unexpected: neither Buffer nor TextDecoder are available as globals. Please report this error.`);
411
- }
412
- flush() {
413
- if (!this.buffer.length && !this.trailingCR) {
414
- return [];
415
- }
416
- const lines = [this.buffer.join('')];
417
- this.buffer = [];
418
- this.trailingCR = false;
419
- return lines;
420
- }
421
- }
422
- function partition(str, delimiter) {
423
- const index = str.indexOf(delimiter);
424
- if (index !== -1) {
425
- return [str.substring(0, index), delimiter, str.substring(index + delimiter.length)];
426
- }
427
- return [str, '', ''];
428
- }
429
- /**
430
- * Most browsers don't yet have async iterable support for ReadableStream,
431
- * and Node has a very different way of reading bytes from its "ReadableStream".
432
- *
433
- * This polyfill was pulled from https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490
434
- */
435
- export function readableStreamAsyncIterable(stream) {
436
- if (stream[Symbol.asyncIterator])
437
- return stream;
438
- const reader = stream.getReader();
439
- return {
440
- async next() {
441
- try {
442
- const result = await reader.read();
443
- if (result?.done)
444
- reader.releaseLock(); // release lock when stream becomes closed
445
- return result;
446
- }
447
- catch (e) {
448
- reader.releaseLock(); // release lock when stream becomes errored
449
- throw e;
450
- }
451
- },
452
- async return() {
453
- const cancelPromise = reader.cancel();
454
- reader.releaseLock();
455
- await cancelPromise;
456
- return { done: true, value: undefined };
457
- },
458
- [Symbol.asyncIterator]() {
459
- return this;
460
- },
461
- };
462
- }
@@ -1 +0,0 @@
1
- export declare const DEFAULT_FIELDS_TRANSFORMATION_EXCLUSIONS: string[];
@@ -1,21 +0,0 @@
1
- export const DEFAULT_FIELDS_TRANSFORMATION_EXCLUSIONS = [
2
- // Inputs
3
- 'variables.*(!(*fileUrl))',
4
- 'override.structuredOutput.schema.properties.*',
5
- 'override.functions.*.parameters.properties.*',
6
- // Outputs
7
- 'choices.*.message.content.*',
8
- 'choices.*.message.function_call.arguments.*',
9
- 'choices.*.message.tool_calls.*.function.arguments.*',
10
- 'message.content.*',
11
- 'message.function_call.arguments.*',
12
- 'message.tool_calls.*.function.arguments.*',
13
- 'message.structured_content.*',
14
- 'input_content.variables.*',
15
- 'input_content.variables.*(!(*file_url))',
16
- 'input_content.messages.*.function_call.arguments.*',
17
- 'output_content.tool_calls.*.arguments.*',
18
- 'output_content.content.*',
19
- 'raw_input.override.structured_output.schema.properties.*',
20
- 'raw_input.override.functions.*.parameters.properties.*',
21
- ];
@@ -1,2 +0,0 @@
1
- export declare function transformObjectFromCamelCaseToSnakeCase(obj: Record<string, unknown>, exclusions?: string[]): Record<string, unknown>;
2
- export declare function transformObjectFromSnakeCaseToCamelCase(obj: Record<string, unknown>, exclusions?: string[]): Record<string, unknown>;
@@ -1,79 +0,0 @@
1
- import * as changeCase from 'change-case';
2
- import micromatch from 'micromatch';
3
- // Helper function to check exclusions with wildcard support
4
- function isExcluded(path, exclusions) {
5
- return exclusions.some((pattern) => {
6
- const isMatch = micromatch.isMatch(path, pattern);
7
- return isMatch;
8
- });
9
- }
10
- export function transformObjectFromCamelCaseToSnakeCase(obj, exclusions = []) {
11
- const result = Array.isArray(obj) ? [] : {};
12
- const stack = [
13
- { source: obj, target: result, path: '' },
14
- ];
15
- while (stack.length > 0) {
16
- const { source, target, path } = stack.pop();
17
- if (Array.isArray(source)) {
18
- source.forEach((item, index) => {
19
- const newPath = path ? `${path}.*` : '*';
20
- if (Array.isArray(item) || (typeof item === 'object' && item !== null)) {
21
- target[index] = Array.isArray(item) ? [] : {};
22
- stack.push({ source: item, target: target[index], path: newPath });
23
- }
24
- else {
25
- target[index] = item;
26
- }
27
- });
28
- }
29
- else if (typeof source === 'object' && source !== null) {
30
- for (const [key, value] of Object.entries(source)) {
31
- const newPath = path ? `${path}.${key}` : key;
32
- const snakeKey = isExcluded(newPath, exclusions) ? key : changeCase.snakeCase(key);
33
- if (Array.isArray(value) || (typeof value === 'object' && value !== null)) {
34
- target[snakeKey] = Array.isArray(value) ? [] : {};
35
- stack.push({ source: value, target: target[snakeKey], path: newPath });
36
- }
37
- else {
38
- target[snakeKey] = value;
39
- }
40
- }
41
- }
42
- }
43
- return result;
44
- }
45
- export function transformObjectFromSnakeCaseToCamelCase(obj, exclusions = []) {
46
- const result = Array.isArray(obj) ? [] : {};
47
- const stack = [
48
- { source: obj, target: result, path: '' },
49
- ];
50
- while (stack.length > 0) {
51
- const { source, target, path } = stack.pop();
52
- if (Array.isArray(source)) {
53
- source.forEach((item, index) => {
54
- const newPath = path ? `${path}.*` : '*';
55
- if (Array.isArray(item) || (typeof item === 'object' && item !== null)) {
56
- target[index] = Array.isArray(item) ? [] : {};
57
- stack.push({ source: item, target: target[index], path: newPath });
58
- }
59
- else {
60
- target[index] = item;
61
- }
62
- });
63
- }
64
- else if (typeof source === 'object' && source !== null) {
65
- for (const [key, value] of Object.entries(source)) {
66
- const newPath = path ? `${path}.${key}` : key;
67
- const camelKey = isExcluded(newPath, exclusions) ? key : changeCase.camelCase(key);
68
- if (Array.isArray(value) || (typeof value === 'object' && value !== null)) {
69
- target[camelKey] = Array.isArray(value) ? [] : {};
70
- stack.push({ source: value, target: target[camelKey], path: newPath });
71
- }
72
- else {
73
- target[camelKey] = value;
74
- }
75
- }
76
- }
77
- }
78
- return result;
79
- }
@@ -1,2 +0,0 @@
1
- import type { ReadableStream } from 'node:stream/web';
2
- export declare function getStreamSize(stream: ReadableStream): Promise<number>;
@@ -1,11 +0,0 @@
1
- export async function getStreamSize(stream) {
2
- let size = 0;
3
- const reader = stream.getReader();
4
- while (true) {
5
- const { done, value } = await reader.read();
6
- if (done)
7
- break;
8
- size += value.length;
9
- }
10
- return size;
11
- }