skytells 1.0.2 → 1.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.
Files changed (47) hide show
  1. package/README.md +332 -121
  2. package/dist/chat.cjs +33 -0
  3. package/dist/chat.d.ts +82 -0
  4. package/dist/chat.js +33 -0
  5. package/dist/client.cjs +1151 -35
  6. package/dist/client.d.ts +776 -27
  7. package/dist/client.js +1151 -35
  8. package/dist/embeddings.cjs +40 -0
  9. package/dist/embeddings.d.ts +38 -0
  10. package/dist/embeddings.js +40 -0
  11. package/dist/endpoints.cjs +25 -7
  12. package/dist/endpoints.d.ts +18 -0
  13. package/dist/endpoints.js +25 -7
  14. package/dist/http.cjs +638 -53
  15. package/dist/http.d.ts +136 -2
  16. package/dist/http.js +638 -53
  17. package/dist/index.cjs +52 -6
  18. package/dist/index.d.ts +46 -7
  19. package/dist/index.js +52 -6
  20. package/dist/orchestrator.cjs +202 -0
  21. package/dist/orchestrator.d.ts +142 -0
  22. package/dist/orchestrator.js +202 -0
  23. package/dist/prediction-urls.cjs +38 -0
  24. package/dist/prediction-urls.d.ts +18 -0
  25. package/dist/prediction-urls.js +38 -0
  26. package/dist/responses.cjs +23 -0
  27. package/dist/responses.d.ts +60 -0
  28. package/dist/responses.js +23 -0
  29. package/dist/safety.cjs +323 -0
  30. package/dist/safety.d.ts +91 -0
  31. package/dist/safety.js +323 -0
  32. package/dist/types/index.d.ts +2 -0
  33. package/dist/types/index.js +2 -0
  34. package/dist/types/inference.types.d.ts +583 -0
  35. package/dist/types/inference.types.js +65 -0
  36. package/dist/types/model.types.d.ts +58 -4
  37. package/dist/types/model.types.js +13 -0
  38. package/dist/types/orchestrator.types.d.ts +61 -0
  39. package/dist/types/orchestrator.types.js +7 -0
  40. package/dist/types/predict.types.d.ts +258 -16
  41. package/dist/types/predict.types.js +22 -0
  42. package/dist/types/shared.types.d.ts +156 -3
  43. package/dist/types/shared.types.js +73 -2
  44. package/dist/webhooks.cjs +310 -0
  45. package/dist/webhooks.d.ts +171 -0
  46. package/dist/webhooks.js +310 -0
  47. package/package.json +26 -2
package/dist/http.cjs CHANGED
@@ -1,27 +1,179 @@
1
+ /**
2
+ * Internal HTTP transport: JSON REST, retries, per-request timeouts, SSE chat streams, NDJSON, raw text/binary.
3
+ *
4
+ * **Not a public import** — use {@link SkytellsClient} (`Skytells()` factory). This module documents behavior for
5
+ * maintainers, LLMs, and IDE hover.
6
+ *
7
+ * | Concern | Behavior |
8
+ * |--------|------------|
9
+ * | **Timeouts** | `AbortController` + `clearTimeout` in `finally` (no orphaned timers). |
10
+ * | **Retries** | Only non-streaming JSON/text/buffer helpers; linear delay `retryDelay * (attempt + 1)`. |
11
+ * | **Streams** | `ReadableStream` reader `cancel()` in `finally`; body `cancel()` if `getReader()` missing. |
12
+ * | **Auth** | `skytells`: `x-api-key` + `Authorization`. `orchestrator`: Bearer only (see `transport`). |
13
+ * | **Errors** | Throws {@link SkytellsError} (`errorId`, `httpStatus`, optional `requestId`). |
14
+ *
15
+ * @see docs/Reliability.md — resource and option clamping.
16
+ * @module http
17
+ */
1
18
  const { API_BASE_URL } = require("./endpoints.js");
2
19
  const { SkytellsError } = require("./types/shared.types.js");
3
- // Default timeout in milliseconds
4
- // 60 seconds
5
- const DEFAULT_TIMEOUT = 60000;
20
+ /** Default request timeout (ms) for JSON and SSE requests. */
21
+ const HTTP_DEFAULT_REQUEST_TIMEOUT_MS = module.exports.HTTP_DEFAULT_REQUEST_TIMEOUT_MS = 60000;
22
+ const DEFAULT_RETRY_ON = [429, 500, 502, 503, 504];
23
+ /** `setTimeout` is capped in practice (~2^31-1 ms); avoid overflow / infinite timers. */
24
+ const MAX_TIMER_MS = 2147483647;
25
+ function isAbsoluteHttpUrl(pathOrUrl) {
26
+ return pathOrUrl.startsWith('http://') || pathOrUrl.startsWith('https://');
27
+ }
28
+ /** Fetch / `AbortController` timeout — same `name` in browsers, Node, and Edge runtimes. */
29
+ function isAbortError(error) {
30
+ return (error !== null &&
31
+ typeof error === 'object' &&
32
+ 'name' in error &&
33
+ error.name === 'AbortError');
34
+ }
35
+ /**
36
+ * Append a decoded chunk, return complete `\n`-terminated lines (strip trailing `\r`) and the leftover tail.
37
+ * Avoids `split('\n')` on the full buffer each chunk (CPU + allocations on long SSE / NDJSON streams).
38
+ */
39
+ function appendAndExtractCompleteLines(buffer, chunk) {
40
+ const buf = buffer.length > 0 ? buffer + chunk : chunk;
41
+ const lines = [];
42
+ let start = 0;
43
+ let nl = buf.indexOf('\n', start);
44
+ while (nl >= 0) {
45
+ let line = buf.slice(start, nl);
46
+ if (line.endsWith('\r')) {
47
+ line = line.slice(0, -1);
48
+ }
49
+ lines.push(line);
50
+ start = nl + 1;
51
+ nl = buf.indexOf('\n', start);
52
+ }
53
+ return { lines, rest: start === 0 ? buf : buf.slice(start) };
54
+ }
55
+ /**
56
+ * Fetch-based client for one API origin (`baseUrl`) and one auth policy (`transport`).
57
+ *
58
+ * @internal Constructed only by {@link SkytellsClient} (and Orchestrator sub-client).
59
+ */
6
60
  export class HTTP {
7
- constructor(apiKey, baseUrl = API_BASE_URL, timeout = DEFAULT_TIMEOUT) {
61
+ /**
62
+ * @param apiKey - Platform `sk-…` or Orchestrator `wfb_…` depending on `transport`.
63
+ * @param baseUrl - API origin (`api.skytells.ai/v1` or `orchestrator.skytells.ai`).
64
+ * @param timeout - Per-request/stream abort time in ms.
65
+ * @param headers - Merged into every request (JSON and SSE).
66
+ * @param retry - Retries only for non-streaming {@link HTTP.request}.
67
+ * @param fetchFn - Inject mock/proxy/`cache: 'no-store'` fetch.
68
+ * @param transport - **`orchestrator`** for `client.orchestrator` only; default **`skytells`** for predictions/inference.
69
+ */
70
+ constructor(apiKey, baseUrl = API_BASE_URL, timeout = HTTP_DEFAULT_REQUEST_TIMEOUT_MS, headers = {}, retry = {}, fetchFn, transport = 'skytells') {
71
+ var _a, _b;
8
72
  this.apiKey = apiKey;
9
73
  this.baseUrl = baseUrl;
10
- this.timeout = timeout;
74
+ this.timeout =
75
+ timeout === Infinity
76
+ ? MAX_TIMER_MS
77
+ : Number.isFinite(timeout) && timeout >= 0
78
+ ? Math.min(timeout, MAX_TIMER_MS)
79
+ : HTTP_DEFAULT_REQUEST_TIMEOUT_MS;
80
+ this.customHeaders = headers;
81
+ this.transport = transport;
82
+ const rawRetries = (_a = retry.retries) !== null && _a !== void 0 ? _a : 0;
83
+ const rawDelay = (_b = retry.retryDelay) !== null && _b !== void 0 ? _b : 1000;
84
+ this.retry = {
85
+ retries: Math.max(0, Math.min(Math.floor(rawRetries), 100)),
86
+ retryDelay: Math.max(0, Math.min(Number.isFinite(rawDelay) ? rawDelay : 1000, MAX_TIMER_MS)),
87
+ retryOn: Array.isArray(retry.retryOn) && retry.retryOn.length > 0 ? retry.retryOn : DEFAULT_RETRY_ON,
88
+ };
89
+ this.fetchFn = fetchFn !== null && fetchFn !== void 0 ? fetchFn : globalThis.fetch.bind(globalThis);
90
+ }
91
+ /**
92
+ * Orchestrator transport must not send `x-api-key` — only `Authorization: Bearer wfb_…` per
93
+ * [Orchestrator webhooks](https://learn.skytells.ai/docs/products/orchestrator/webhooks). Strip any
94
+ * `x-api-key` copied from shared **`ClientOptions.headers`** so the Skytells `sk-…` key is never
95
+ * forwarded to `orchestrator.skytells.ai`.
96
+ */
97
+ applyAuthHeaders(headers) {
98
+ if (this.transport === 'orchestrator') {
99
+ delete headers['x-api-key'];
100
+ }
101
+ if (!this.apiKey) {
102
+ return;
103
+ }
104
+ if (this.transport === 'skytells') {
105
+ headers['x-api-key'] = this.apiKey;
106
+ }
107
+ headers['Authorization'] = `Bearer ${this.apiKey}`;
11
108
  }
109
+ /**
110
+ * Resolved API origin (no trailing slash), e.g. `https://api.skytells.ai/v1`.
111
+ *
112
+ * @returns Same string passed to the constructor (or default {@link API_BASE_URL}).
113
+ */
114
+ getBaseUrl() {
115
+ return this.baseUrl;
116
+ }
117
+ /**
118
+ * API key for this instance (`sk-…` or `wfb_…`), if any.
119
+ *
120
+ * @returns `undefined` for unauthenticated clients. Used by {@link SkytellsClient.webhookListener} for General HMAC.
121
+ */
122
+ getApiKey() {
123
+ return this.apiKey;
124
+ }
125
+ /**
126
+ * Non-streaming JSON `fetch`: read `response.text()` once, `JSON.parse`, map API errors to {@link SkytellsError}.
127
+ *
128
+ * @typeParam T - Expected successful JSON body shape (caller-supplied; not validated at runtime).
129
+ * @param method - HTTP verb. Body sent only for `POST`, `PATCH`, `PUT`.
130
+ * @param path - Relative to `baseUrl`, or absolute `http(s)://…`.
131
+ * @param data - Serializable JSON object. **Circular references** → `SDK_ERROR`. Omit for GET/DELETE.
132
+ * @returns Parsed response JSON.
133
+ * @throws {SkytellsError} `REQUEST_TIMEOUT`, `NETWORK_ERROR`, `INVALID_JSON`, `SERVER_ERROR`, or API `error.error_id`.
134
+ * @remarks Retries when `httpStatus` is in `retry.retryOn` and attempts remain. Skytells prediction “queued” envelopes are normalized inside the internal fetch path.
135
+ */
12
136
  async request(method, path, data) {
137
+ let lastError;
138
+ for (let attempt = 0; attempt <= this.retry.retries; attempt++) {
139
+ try {
140
+ return await this.executeRequest(method, path, data);
141
+ }
142
+ catch (error) {
143
+ if (error instanceof SkytellsError) {
144
+ lastError = error;
145
+ const isRetryable = this.retry.retryOn.includes(error.httpStatus);
146
+ if (isRetryable && attempt < this.retry.retries) {
147
+ await this.delay(Math.min(this.retry.retryDelay * (attempt + 1), MAX_TIMER_MS));
148
+ continue;
149
+ }
150
+ }
151
+ throw error;
152
+ }
153
+ }
154
+ throw (lastError !== null && lastError !== void 0 ? lastError : new SkytellsError('Request failed after retries', 'UNKNOWN_ERROR', 'No error details', 0));
155
+ }
156
+ delay(ms) {
157
+ return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
158
+ }
159
+ async executeRequest(method, path, data) {
160
+ var _a, _b, _c, _d;
13
161
  const headers = {
14
162
  'Content-Type': 'application/json',
163
+ ...this.customHeaders,
15
164
  };
16
- if (this.apiKey) {
17
- headers['x-api-key'] = this.apiKey;
18
- }
165
+ this.applyAuthHeaders(headers);
19
166
  const options = {
20
167
  method,
21
168
  headers,
22
169
  };
23
- if (method === 'POST' && data) {
24
- options.body = JSON.stringify(data);
170
+ if ((method === 'POST' || method === 'PATCH' || method === 'PUT') && data !== undefined) {
171
+ try {
172
+ options.body = JSON.stringify(data);
173
+ }
174
+ catch (_e) {
175
+ throw new SkytellsError('Request body could not be serialized to JSON', 'SDK_ERROR', 'Remove circular references or non-JSON values from the payload.', 0);
176
+ }
25
177
  }
26
178
  // Add AbortController for timeout handling
27
179
  const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
@@ -36,56 +188,58 @@ export class HTTP {
36
188
  }, this.timeout);
37
189
  }
38
190
  try {
191
+ const fetchUrl = isAbsoluteHttpUrl(path) ? path : `${this.baseUrl}${path}`;
39
192
  // Use native fetch which is available in modern browsers and edge environments
40
- const response = await fetch(`${this.baseUrl}${path}`, options);
41
- // Get the response content type to check if it's JSON
193
+ const response = await this.fetchFn(fetchUrl, options);
42
194
  const contentType = response.headers.get('content-type') || '';
43
195
  const isJsonResponse = contentType.includes('application/json');
44
- // Handle non-JSON responses properly
196
+ const bodyText = await response.text();
45
197
  if (!isJsonResponse) {
46
- // If not JSON, get the text for error details
47
- let responseText = '';
48
- try {
49
- responseText = await response.text();
50
- // Truncate if too long to avoid huge error messages
51
- if (responseText.length > 500) {
52
- responseText = responseText.substring(0, 500) + '... [truncated]';
53
- }
54
- }
55
- catch (textError) {
56
- responseText = 'Could not read response body';
57
- }
58
- throw new SkytellsError(`Server responded with non-JSON content (${contentType})`, 'SERVER_ERROR', `Status: ${response.status}, Content: ${responseText}`, response.status);
198
+ const responseText = bodyText.length > 500 ? `${bodyText.slice(0, 500)}... [truncated]` : bodyText;
199
+ throw new SkytellsError(`Server responded with non-JSON content (${contentType})`, 'SERVER_ERROR', `Status: ${response.status}, Content: ${responseText || '(empty)'}`, response.status);
59
200
  }
60
- // Try to parse as JSON
61
201
  let responseData;
62
202
  try {
63
- responseData = await response.json();
203
+ responseData =
204
+ bodyText.length === 0 ? null : JSON.parse(bodyText);
64
205
  }
65
- catch (error) {
66
- // Get response text for better error details
67
- let responseText = '';
68
- try {
69
- // Need to clone response since we already tried to read it as JSON
70
- responseText = await response.clone().text();
71
- if (responseText.length > 500) {
72
- responseText = responseText.substring(0, 500) + '... [truncated]';
73
- }
74
- }
75
- catch (textError) {
76
- responseText = 'Could not read response body';
77
- }
78
- throw new SkytellsError('Invalid JSON response', 'INVALID_JSON', `The server returned invalid JSON. Status: ${response.status}, Content: ${responseText}`, response.status);
206
+ catch (_f) {
207
+ const preview = bodyText.length > 500 ? `${bodyText.slice(0, 500)}... [truncated]` : bodyText;
208
+ throw new SkytellsError('Invalid JSON response', 'INVALID_JSON', `The server returned invalid JSON. Status: ${response.status}, Content: ${preview}`, response.status);
209
+ }
210
+ /**
211
+ * Prediction endpoints always return a prediction object with `id`. Some responses also set
212
+ * top-level `status: false` with `error.message` while the job is still queued — treat as success
213
+ * when HTTP is OK and `id` is present (matches dashboard `processing` / `started`).
214
+ */
215
+ const data = responseData;
216
+ if (this.transport === 'skytells' &&
217
+ response.ok &&
218
+ data &&
219
+ data.status === false &&
220
+ typeof data.id === 'string' &&
221
+ data.id.length > 0) {
222
+ return responseData;
79
223
  }
80
224
  // Check if the response indicates an error
81
225
  if (!response.ok || (responseData && responseData.status === false)) {
82
- if (responseData && responseData.error) {
83
- // API returned a structured error
84
- throw new SkytellsError(responseData.error.message || responseData.response || 'API error occurred', responseData.error.error_id || 'UNKNOWN_ERROR', responseData.error.details || responseData.response || 'No additional details', responseData.error.http_status || response.status);
226
+ const errObj = responseData === null || responseData === void 0 ? void 0 : responseData.error;
227
+ if (responseData && errObj) {
228
+ const httpStatus = (_b = (_a = errObj.status) !== null && _a !== void 0 ? _a : errObj.http_status) !== null && _b !== void 0 ? _b : response.status;
229
+ const requestId = errObj.request_id;
230
+ const err = new SkytellsError(errObj.message || responseData.response || 'API error occurred', errObj.error_id || 'UNKNOWN_ERROR', (_d = (_c = errObj.details) !== null && _c !== void 0 ? _c : responseData.response) !== null && _d !== void 0 ? _d : 'No additional details', httpStatus, requestId);
231
+ if (errObj.type) {
232
+ err.errorType = errObj.type;
233
+ }
234
+ if (errObj.code) {
235
+ err.errorCode = errObj.code;
236
+ }
237
+ throw err;
85
238
  }
86
- else if (responseData && responseData.response) {
239
+ else if (responseData === null || responseData === void 0 ? void 0 : responseData.response) {
87
240
  // Simple error with just a response message
88
- throw new SkytellsError(responseData.response, 'API_ERROR', responseData.response, response.status);
241
+ const msg = String(responseData.response);
242
+ throw new SkytellsError(msg, 'API_ERROR', msg, response.status);
89
243
  }
90
244
  else {
91
245
  // Generic HTTP error
@@ -96,23 +250,454 @@ export class HTTP {
96
250
  }
97
251
  catch (error) {
98
252
  // Check if it's an abort error (timeout)
99
- if (error && typeof error === 'object' && 'name' in error && error.name === 'AbortError') {
100
- throw new SkytellsError(`Request timed out after ${this.timeout}ms`, 'REQUEST_TIMEOUT', `The request took longer than ${this.timeout}ms to complete`, 408 // Request Timeout status code
101
- );
253
+ if (isAbortError(error)) {
254
+ throw new SkytellsError(`Request timed out after ${this.timeout}ms`, 'REQUEST_TIMEOUT', `The request took longer than ${this.timeout}ms to complete`, 408);
102
255
  }
103
256
  // Re-throw original error
104
257
  if (error instanceof SkytellsError) {
105
258
  throw error;
106
259
  }
107
260
  // Network or other errors
108
- throw new SkytellsError(error instanceof Error ? error.message : 'Network error occurred', 'NETWORK_ERROR', 'A network error occurred while communicating with the API', 0 // No HTTP status for network errors
109
- );
261
+ throw new SkytellsError(error instanceof Error ? error.message : 'Network error occurred', 'NETWORK_ERROR', 'A network error occurred while communicating with the API', 0);
262
+ }
263
+ finally {
264
+ if (timeoutId !== null) {
265
+ clearTimeout(timeoutId);
266
+ }
267
+ }
268
+ }
269
+ /**
270
+ * **SSE** streaming `POST`: parses `data: …` lines as JSON (chat completions when `stream: true`).
271
+ *
272
+ * @typeParam T - Usually {@link ChatCompletionChunk} from inference types.
273
+ * @param path - e.g. `/chat/completions`.
274
+ * @param data - Must include `stream: true`. Must be JSON-serializable or throws `SDK_ERROR`.
275
+ * @returns Async iterable; consume with `for await`. Malformed lines skipped; `[DONE]` ignored.
276
+ * @throws {SkytellsError} Non-OK HTTP, timeout (`REQUEST_TIMEOUT`), or missing body reader.
277
+ * @remarks **Not retried.** Abandoning the loop still runs `reader.cancel()` in `finally`. Timeout cleared in outer `finally`.
278
+ */
279
+ async *requestStream(path, data) {
280
+ var _a, _b, _c;
281
+ const headers = {
282
+ 'Content-Type': 'application/json',
283
+ Accept: 'text/event-stream',
284
+ ...this.customHeaders,
285
+ };
286
+ this.applyAuthHeaders(headers);
287
+ const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
288
+ let bodyJson;
289
+ try {
290
+ bodyJson = JSON.stringify(data);
291
+ }
292
+ catch (_d) {
293
+ throw new SkytellsError('Request body could not be serialized to JSON', 'SDK_ERROR', 'Remove circular references or non-JSON values from the payload.', 0);
294
+ }
295
+ const options = {
296
+ method: 'POST',
297
+ headers,
298
+ body: bodyJson,
299
+ signal: controller === null || controller === void 0 ? void 0 : controller.signal,
300
+ };
301
+ let timeoutId = null;
302
+ if (controller) {
303
+ timeoutId = setTimeout(() => controller.abort(), this.timeout);
304
+ }
305
+ try {
306
+ const fetchUrl = isAbsoluteHttpUrl(path) ? path : `${this.baseUrl}${path}`;
307
+ const response = await this.fetchFn(fetchUrl, options);
308
+ if (!response.ok) {
309
+ const text = await response.text();
310
+ let errData;
311
+ try {
312
+ errData = JSON.parse(text);
313
+ }
314
+ catch (_e) {
315
+ throw new SkytellsError(`HTTP ${response.status}: ${text.slice(0, 200)}`, 'HTTP_ERROR', text.slice(0, 500), response.status);
316
+ }
317
+ if (errData === null || errData === void 0 ? void 0 : errData.error) {
318
+ throw new SkytellsError(errData.error.message || 'API error', errData.error.error_id || 'UNKNOWN_ERROR', errData.error.message || '', (_a = errData.error.status) !== null && _a !== void 0 ? _a : response.status, errData.error.request_id);
319
+ }
320
+ throw new SkytellsError(`HTTP error ${response.status}`, 'HTTP_ERROR', text.slice(0, 500), response.status);
321
+ }
322
+ const reader = (_b = response.body) === null || _b === void 0 ? void 0 : _b.getReader();
323
+ if (!reader) {
324
+ try {
325
+ await ((_c = response.body) === null || _c === void 0 ? void 0 : _c.cancel());
326
+ }
327
+ catch (_f) {
328
+ /* ignore */
329
+ }
330
+ throw new SkytellsError('No response body', 'SERVER_ERROR', 'Stream not available', 0);
331
+ }
332
+ const decoder = new TextDecoder();
333
+ let buffer = '';
334
+ try {
335
+ while (true) {
336
+ const { done, value } = await reader.read();
337
+ if (done) {
338
+ break;
339
+ }
340
+ const { lines, rest } = appendAndExtractCompleteLines(buffer, decoder.decode(value, { stream: true }));
341
+ buffer = rest;
342
+ for (const line of lines) {
343
+ if (line.startsWith('data: ')) {
344
+ const payload = line.slice(6).trim();
345
+ if (payload === '[DONE]') {
346
+ continue;
347
+ }
348
+ try {
349
+ yield JSON.parse(payload);
350
+ }
351
+ catch (_g) {
352
+ // Skip malformed lines
353
+ }
354
+ }
355
+ }
356
+ }
357
+ // Flush any remaining buffered data after the stream closes (handles servers that omit
358
+ // the final newline — rare in SSE but safe to handle uniformly with requestNdjsonStream).
359
+ if (buffer.startsWith('data: ')) {
360
+ const payload = buffer.slice(6).trim();
361
+ if (payload && payload !== '[DONE]') {
362
+ try {
363
+ yield JSON.parse(payload);
364
+ }
365
+ catch (_h) {
366
+ // ignore incomplete trailing data
367
+ }
368
+ }
369
+ }
370
+ }
371
+ finally {
372
+ try {
373
+ await reader.cancel();
374
+ }
375
+ catch (_j) {
376
+ // Reader may already be closed; ignore
377
+ }
378
+ }
379
+ }
380
+ catch (error) {
381
+ if (error instanceof SkytellsError) {
382
+ throw error;
383
+ }
384
+ if (isAbortError(error)) {
385
+ throw new SkytellsError(`Request timed out after ${this.timeout}ms`, 'REQUEST_TIMEOUT', 'Stream timeout', 408);
386
+ }
387
+ throw new SkytellsError(error instanceof Error ? error.message : 'Stream error', 'NETWORK_ERROR', 'Stream failed', 0);
110
388
  }
111
389
  finally {
112
- // Clear timeout if it was set
113
390
  if (timeoutId !== null) {
114
391
  clearTimeout(timeoutId);
115
392
  }
116
393
  }
117
394
  }
395
+ /**
396
+ * Successful response read as **plain text** (not JSON-validated). Used for code export, etc.
397
+ *
398
+ * @param method - Currently **`GET`** only in SDK call sites.
399
+ * @param path - Relative or absolute URL.
400
+ * @returns Full response body string.
401
+ * @throws {SkytellsError} Same families as {@link HTTP.request} for HTTP/network/timeout errors.
402
+ * @remarks Uses same **retry** policy as {@link HTTP.request}.
403
+ */
404
+ async requestText(method, path) {
405
+ let lastError;
406
+ for (let attempt = 0; attempt <= this.retry.retries; attempt++) {
407
+ try {
408
+ return await this.executeRequestRawText(method, path);
409
+ }
410
+ catch (error) {
411
+ if (error instanceof SkytellsError) {
412
+ lastError = error;
413
+ const isRetryable = this.retry.retryOn.includes(error.httpStatus);
414
+ if (isRetryable && attempt < this.retry.retries) {
415
+ await this.delay(Math.min(this.retry.retryDelay * (attempt + 1), MAX_TIMER_MS));
416
+ continue;
417
+ }
418
+ }
419
+ throw error;
420
+ }
421
+ }
422
+ throw (lastError !== null && lastError !== void 0 ? lastError : new SkytellsError('Request failed after retries', 'UNKNOWN_ERROR', 'No error details', 0));
423
+ }
424
+ /**
425
+ * Successful response as **binary** (`arrayBuffer()`), e.g. ZIP downloads.
426
+ *
427
+ * @param method - **`GET`** in practice.
428
+ * @param path - Relative or absolute URL.
429
+ * @returns Raw bytes.
430
+ * @throws {SkytellsError} On HTTP error, timeout, or network failure.
431
+ * @remarks Same **retry** policy as {@link HTTP.request}.
432
+ */
433
+ async requestBuffer(method, path) {
434
+ let lastError;
435
+ for (let attempt = 0; attempt <= this.retry.retries; attempt++) {
436
+ try {
437
+ return await this.executeRequestRawBuffer(method, path);
438
+ }
439
+ catch (error) {
440
+ if (error instanceof SkytellsError) {
441
+ lastError = error;
442
+ const isRetryable = this.retry.retryOn.includes(error.httpStatus);
443
+ if (isRetryable && attempt < this.retry.retries) {
444
+ await this.delay(Math.min(this.retry.retryDelay * (attempt + 1), MAX_TIMER_MS));
445
+ continue;
446
+ }
447
+ }
448
+ throw error;
449
+ }
450
+ }
451
+ throw (lastError !== null && lastError !== void 0 ? lastError : new SkytellsError('Request failed after retries', 'UNKNOWN_ERROR', 'No error details', 0));
452
+ }
453
+ /**
454
+ * Fire **`OPTIONS`** (CORS preflight for Orchestrator webhooks). **No retries.**
455
+ *
456
+ * @param path - Full path under `baseUrl`.
457
+ * @throws {SkytellsError} If `!response.ok`, timeout, or network error.
458
+ */
459
+ async requestOptions(path) {
460
+ var _a;
461
+ const headers = { ...this.customHeaders };
462
+ this.applyAuthHeaders(headers);
463
+ const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
464
+ const options = {
465
+ method: 'OPTIONS',
466
+ headers,
467
+ signal: (_a = controller === null || controller === void 0 ? void 0 : controller.signal) !== null && _a !== void 0 ? _a : undefined,
468
+ };
469
+ let timeoutId = null;
470
+ if (controller) {
471
+ timeoutId = setTimeout(() => controller.abort(), this.timeout);
472
+ }
473
+ try {
474
+ const fetchUrl = isAbsoluteHttpUrl(path) ? path : `${this.baseUrl}${path}`;
475
+ const response = await this.fetchFn(fetchUrl, options);
476
+ if (!response.ok) {
477
+ const text = await response.text();
478
+ throw new SkytellsError(`HTTP error ${response.status}`, 'HTTP_ERROR', text.slice(0, 500) || `Status ${response.status}`, response.status);
479
+ }
480
+ }
481
+ catch (error) {
482
+ if (error instanceof SkytellsError) {
483
+ throw error;
484
+ }
485
+ if (isAbortError(error)) {
486
+ throw new SkytellsError(`Request timed out after ${this.timeout}ms`, 'REQUEST_TIMEOUT', 'OPTIONS request timed out', 408);
487
+ }
488
+ throw new SkytellsError(error instanceof Error ? error.message : 'Network error', 'NETWORK_ERROR', 'OPTIONS request failed', 0);
489
+ }
490
+ finally {
491
+ if (timeoutId !== null) {
492
+ clearTimeout(timeoutId);
493
+ }
494
+ }
495
+ }
496
+ /**
497
+ * **NDJSON / JSONL** `POST`: one JSON object per line (Orchestrator AI workflow generation).
498
+ *
499
+ * @typeParam T - Line shape; default `Record<string, unknown>`.
500
+ * @param path - e.g. `/api/ai/generate`.
501
+ * @param data - Serialized as JSON body; circular data → `SDK_ERROR`.
502
+ * @returns Async iterable of parsed lines; invalid JSON lines skipped.
503
+ * @throws {SkytellsError} HTTP error, timeout, or missing stream body.
504
+ * @remarks **Not retried** after bytes flow. Reader/body cleanup same as {@link HTTP.requestStream}.
505
+ */
506
+ async *requestNdjsonStream(path, data) {
507
+ var _a, _b, _c;
508
+ const headers = {
509
+ 'Content-Type': 'application/json',
510
+ Accept: 'application/x-ndjson, application/jsonlines+json, text/plain, */*',
511
+ ...this.customHeaders,
512
+ };
513
+ this.applyAuthHeaders(headers);
514
+ const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
515
+ let ndjsonBody;
516
+ try {
517
+ ndjsonBody = JSON.stringify(data);
518
+ }
519
+ catch (_d) {
520
+ throw new SkytellsError('Request body could not be serialized to JSON', 'SDK_ERROR', 'Remove circular references or non-JSON values from the payload.', 0);
521
+ }
522
+ const options = {
523
+ method: 'POST',
524
+ headers,
525
+ body: ndjsonBody,
526
+ signal: controller === null || controller === void 0 ? void 0 : controller.signal,
527
+ };
528
+ let timeoutId = null;
529
+ if (controller) {
530
+ timeoutId = setTimeout(() => controller.abort(), this.timeout);
531
+ }
532
+ try {
533
+ const fetchUrl = isAbsoluteHttpUrl(path) ? path : `${this.baseUrl}${path}`;
534
+ const response = await this.fetchFn(fetchUrl, options);
535
+ if (!response.ok) {
536
+ const text = await response.text();
537
+ let errData;
538
+ try {
539
+ errData = JSON.parse(text);
540
+ }
541
+ catch (_e) {
542
+ throw new SkytellsError(`HTTP ${response.status}: ${text.slice(0, 200)}`, 'HTTP_ERROR', text.slice(0, 500), response.status);
543
+ }
544
+ if (errData === null || errData === void 0 ? void 0 : errData.error) {
545
+ throw new SkytellsError(errData.error.message || 'API error', errData.error.error_id || 'UNKNOWN_ERROR', errData.error.message || '', (_a = errData.error.status) !== null && _a !== void 0 ? _a : response.status, errData.error.request_id);
546
+ }
547
+ throw new SkytellsError(`HTTP error ${response.status}`, 'HTTP_ERROR', text.slice(0, 500), response.status);
548
+ }
549
+ const reader = (_b = response.body) === null || _b === void 0 ? void 0 : _b.getReader();
550
+ if (!reader) {
551
+ try {
552
+ await ((_c = response.body) === null || _c === void 0 ? void 0 : _c.cancel());
553
+ }
554
+ catch (_f) {
555
+ /* ignore */
556
+ }
557
+ throw new SkytellsError('No response body', 'SERVER_ERROR', 'Stream not available', 0);
558
+ }
559
+ const decoder = new TextDecoder();
560
+ let buffer = '';
561
+ try {
562
+ while (true) {
563
+ const { done, value } = await reader.read();
564
+ if (done) {
565
+ break;
566
+ }
567
+ const { lines, rest } = appendAndExtractCompleteLines(buffer, decoder.decode(value, { stream: true }));
568
+ buffer = rest;
569
+ for (const line of lines) {
570
+ const trimmed = line.trim();
571
+ if (!trimmed) {
572
+ continue;
573
+ }
574
+ try {
575
+ yield JSON.parse(trimmed);
576
+ }
577
+ catch (_g) {
578
+ // skip malformed JSONL line
579
+ }
580
+ }
581
+ }
582
+ const tail = buffer.trim();
583
+ if (tail) {
584
+ try {
585
+ yield JSON.parse(tail);
586
+ }
587
+ catch (_h) {
588
+ // ignore trailing garbage
589
+ }
590
+ }
591
+ }
592
+ finally {
593
+ try {
594
+ await reader.cancel();
595
+ }
596
+ catch (_j) {
597
+ /* ignore */
598
+ }
599
+ }
600
+ }
601
+ catch (error) {
602
+ if (error instanceof SkytellsError) {
603
+ throw error;
604
+ }
605
+ if (isAbortError(error)) {
606
+ throw new SkytellsError(`Request timed out after ${this.timeout}ms`, 'REQUEST_TIMEOUT', 'NDJSON stream timeout', 408);
607
+ }
608
+ throw new SkytellsError(error instanceof Error ? error.message : 'Stream error', 'NETWORK_ERROR', 'NDJSON stream failed', 0);
609
+ }
610
+ finally {
611
+ if (timeoutId !== null) {
612
+ clearTimeout(timeoutId);
613
+ }
614
+ }
615
+ }
616
+ async executeRequestRawText(method, path) {
617
+ const headers = { Accept: '*/*', ...this.customHeaders };
618
+ this.applyAuthHeaders(headers);
619
+ const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
620
+ const options = { method, headers, signal: controller === null || controller === void 0 ? void 0 : controller.signal };
621
+ let timeoutId = null;
622
+ if (controller) {
623
+ timeoutId = setTimeout(() => controller.abort(), this.timeout);
624
+ }
625
+ try {
626
+ const fetchUrl = isAbsoluteHttpUrl(path) ? path : `${this.baseUrl}${path}`;
627
+ const response = await this.fetchFn(fetchUrl, options);
628
+ const bodyText = await response.text();
629
+ if (!response.ok) {
630
+ this.throwFromTextErrorResponse(response.status, bodyText);
631
+ }
632
+ return bodyText;
633
+ }
634
+ catch (error) {
635
+ if (error instanceof SkytellsError) {
636
+ throw error;
637
+ }
638
+ if (isAbortError(error)) {
639
+ throw new SkytellsError(`Request timed out after ${this.timeout}ms`, 'REQUEST_TIMEOUT', `The request took longer than ${this.timeout}ms to complete`, 408);
640
+ }
641
+ throw new SkytellsError(error instanceof Error ? error.message : 'Network error occurred', 'NETWORK_ERROR', 'A network error occurred while communicating with the API', 0);
642
+ }
643
+ finally {
644
+ if (timeoutId !== null) {
645
+ clearTimeout(timeoutId);
646
+ }
647
+ }
648
+ }
649
+ async executeRequestRawBuffer(method, path) {
650
+ const headers = { Accept: '*/*', ...this.customHeaders };
651
+ this.applyAuthHeaders(headers);
652
+ const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
653
+ const options = { method, headers, signal: controller === null || controller === void 0 ? void 0 : controller.signal };
654
+ let timeoutId = null;
655
+ if (controller) {
656
+ timeoutId = setTimeout(() => controller.abort(), this.timeout);
657
+ }
658
+ try {
659
+ const fetchUrl = isAbsoluteHttpUrl(path) ? path : `${this.baseUrl}${path}`;
660
+ const response = await this.fetchFn(fetchUrl, options);
661
+ if (!response.ok) {
662
+ const text = await response.text();
663
+ this.throwFromTextErrorResponse(response.status, text);
664
+ }
665
+ return await response.arrayBuffer();
666
+ }
667
+ catch (error) {
668
+ if (error instanceof SkytellsError) {
669
+ throw error;
670
+ }
671
+ if (isAbortError(error)) {
672
+ throw new SkytellsError(`Request timed out after ${this.timeout}ms`, 'REQUEST_TIMEOUT', `The request took longer than ${this.timeout}ms to complete`, 408);
673
+ }
674
+ throw new SkytellsError(error instanceof Error ? error.message : 'Network error occurred', 'NETWORK_ERROR', 'A network error occurred while communicating with the API', 0);
675
+ }
676
+ finally {
677
+ if (timeoutId !== null) {
678
+ clearTimeout(timeoutId);
679
+ }
680
+ }
681
+ }
682
+ throwFromTextErrorResponse(status, bodyText) {
683
+ var _a, _b;
684
+ let responseData = null;
685
+ try {
686
+ responseData =
687
+ bodyText.length === 0 ? null : JSON.parse(bodyText);
688
+ }
689
+ catch (_c) {
690
+ throw new SkytellsError(`HTTP error ${status}`, 'HTTP_ERROR', bodyText.slice(0, 500) || `Status ${status}`, status);
691
+ }
692
+ const errObj = responseData === null || responseData === void 0 ? void 0 : responseData.error;
693
+ if (responseData && errObj) {
694
+ const httpStatus = (_b = (_a = errObj.status) !== null && _a !== void 0 ? _a : errObj.http_status) !== null && _b !== void 0 ? _b : status;
695
+ throw new SkytellsError(errObj.message || responseData.response || 'API error occurred', errObj.error_id || 'UNKNOWN_ERROR', errObj.details || responseData.response || 'No additional details', httpStatus, errObj.request_id);
696
+ }
697
+ if (responseData === null || responseData === void 0 ? void 0 : responseData.response) {
698
+ const msg = String(responseData.response);
699
+ throw new SkytellsError(msg, 'API_ERROR', msg, status);
700
+ }
701
+ throw new SkytellsError(`HTTP error ${status}`, 'HTTP_ERROR', bodyText.slice(0, 500) || `The server returned status code ${status}`, status);
702
+ }
118
703
  }