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.
- package/README.md +332 -121
- package/dist/chat.cjs +33 -0
- package/dist/chat.d.ts +82 -0
- package/dist/chat.js +33 -0
- package/dist/client.cjs +1151 -35
- package/dist/client.d.ts +776 -27
- package/dist/client.js +1151 -35
- package/dist/embeddings.cjs +40 -0
- package/dist/embeddings.d.ts +38 -0
- package/dist/embeddings.js +40 -0
- package/dist/endpoints.cjs +25 -7
- package/dist/endpoints.d.ts +18 -0
- package/dist/endpoints.js +25 -7
- package/dist/http.cjs +638 -53
- package/dist/http.d.ts +136 -2
- package/dist/http.js +638 -53
- package/dist/index.cjs +52 -6
- package/dist/index.d.ts +46 -7
- package/dist/index.js +52 -6
- package/dist/orchestrator.cjs +202 -0
- package/dist/orchestrator.d.ts +142 -0
- package/dist/orchestrator.js +202 -0
- package/dist/prediction-urls.cjs +38 -0
- package/dist/prediction-urls.d.ts +18 -0
- package/dist/prediction-urls.js +38 -0
- package/dist/responses.cjs +23 -0
- package/dist/responses.d.ts +60 -0
- package/dist/responses.js +23 -0
- package/dist/safety.cjs +323 -0
- package/dist/safety.d.ts +91 -0
- package/dist/safety.js +323 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +2 -0
- package/dist/types/inference.types.d.ts +583 -0
- package/dist/types/inference.types.js +65 -0
- package/dist/types/model.types.d.ts +58 -4
- package/dist/types/model.types.js +13 -0
- package/dist/types/orchestrator.types.d.ts +61 -0
- package/dist/types/orchestrator.types.js +7 -0
- package/dist/types/predict.types.d.ts +258 -16
- package/dist/types/predict.types.js +22 -0
- package/dist/types/shared.types.d.ts +156 -3
- package/dist/types/shared.types.js +73 -2
- package/dist/webhooks.cjs +310 -0
- package/dist/webhooks.d.ts +171 -0
- package/dist/webhooks.js +310 -0
- 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
|
-
|
|
4
|
-
|
|
5
|
-
const
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
196
|
+
const bodyText = await response.text();
|
|
45
197
|
if (!isJsonResponse) {
|
|
46
|
-
|
|
47
|
-
|
|
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 =
|
|
203
|
+
responseData =
|
|
204
|
+
bodyText.length === 0 ? null : JSON.parse(bodyText);
|
|
64
205
|
}
|
|
65
|
-
catch (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
|
239
|
+
else if (responseData === null || responseData === void 0 ? void 0 : responseData.response) {
|
|
87
240
|
// Simple error with just a response message
|
|
88
|
-
|
|
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
|
|
100
|
-
throw new SkytellsError(`Request timed out after ${this.timeout}ms`, 'REQUEST_TIMEOUT', `The request took longer than ${this.timeout}ms to complete`, 408
|
|
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
|
|
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
|
}
|