@canarycoders/ai 0.3.0
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/LICENSE +21 -0
- package/README.md +174 -0
- package/dist/compat.cjs +17 -0
- package/dist/compat.cjs.map +1 -0
- package/dist/compat.d.cts +19 -0
- package/dist/compat.d.ts +19 -0
- package/dist/compat.js +14 -0
- package/dist/compat.js.map +1 -0
- package/dist/index.cjs +1658 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1070 -0
- package/dist/index.d.ts +1068 -0
- package/dist/index.js +1638 -0
- package/dist/index.js.map +1 -0
- package/dist/realtime-BWDkXcj9.d.cts +61 -0
- package/dist/realtime-BWDkXcj9.d.ts +61 -0
- package/dist/realtime-client/index.cjs +82 -0
- package/dist/realtime-client/index.cjs.map +1 -0
- package/dist/realtime-client/index.d.cts +30 -0
- package/dist/realtime-client/index.d.ts +30 -0
- package/dist/realtime-client/index.js +80 -0
- package/dist/realtime-client/index.js.map +1 -0
- package/package.json +81 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1638 @@
|
|
|
1
|
+
// src/compat.ts
|
|
2
|
+
function v1(baseURL) {
|
|
3
|
+
return `${baseURL.replace(/\/+$/, "")}/v1`;
|
|
4
|
+
}
|
|
5
|
+
function openaiTarget(baseURL, apiKey) {
|
|
6
|
+
return { baseURL: v1(baseURL), apiKey };
|
|
7
|
+
}
|
|
8
|
+
function anthropicTarget(baseURL, apiKey) {
|
|
9
|
+
return { baseURL: v1(baseURL), apiKey };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// src/core/errors.ts
|
|
13
|
+
var APIError = class extends Error {
|
|
14
|
+
status;
|
|
15
|
+
code;
|
|
16
|
+
requestId;
|
|
17
|
+
headers;
|
|
18
|
+
details;
|
|
19
|
+
raw;
|
|
20
|
+
constructor(message, init = {}) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = new.target.name;
|
|
23
|
+
this.status = init.status;
|
|
24
|
+
this.code = init.code;
|
|
25
|
+
this.requestId = init.requestId;
|
|
26
|
+
this.headers = init.headers;
|
|
27
|
+
this.details = init.details;
|
|
28
|
+
this.raw = init.raw;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var BadRequestError = class extends APIError {
|
|
32
|
+
/** parsed Zod issues from the server, when present */
|
|
33
|
+
validationIssues;
|
|
34
|
+
constructor(message, init = {}) {
|
|
35
|
+
super(message, init);
|
|
36
|
+
this.validationIssues = extractIssues(init.details);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var AuthenticationError = class extends APIError {
|
|
40
|
+
};
|
|
41
|
+
var PermissionError = class extends APIError {
|
|
42
|
+
};
|
|
43
|
+
var NotFoundError = class extends APIError {
|
|
44
|
+
};
|
|
45
|
+
var ConflictError = class extends APIError {
|
|
46
|
+
};
|
|
47
|
+
var UnprocessableEntityError = class extends APIError {
|
|
48
|
+
};
|
|
49
|
+
var RateLimitError = class extends APIError {
|
|
50
|
+
/** remaining request budget; only sent by the server in development */
|
|
51
|
+
remaining;
|
|
52
|
+
retryAfterMs;
|
|
53
|
+
constructor(message, init = {}) {
|
|
54
|
+
super(message, init);
|
|
55
|
+
const d = init.details;
|
|
56
|
+
this.remaining = d?.remaining;
|
|
57
|
+
this.retryAfterMs = parseRetryAfter(init.headers?.get("retry-after"));
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var InternalServerError = class extends APIError {
|
|
61
|
+
};
|
|
62
|
+
var APIConnectionError = class extends APIError {
|
|
63
|
+
};
|
|
64
|
+
var APIConnectionTimeoutError = class extends APIConnectionError {
|
|
65
|
+
phase;
|
|
66
|
+
};
|
|
67
|
+
function extractIssues(details) {
|
|
68
|
+
if (details && typeof details === "object" && "issues" in details) {
|
|
69
|
+
return details.issues;
|
|
70
|
+
}
|
|
71
|
+
if (Array.isArray(details)) return details;
|
|
72
|
+
return void 0;
|
|
73
|
+
}
|
|
74
|
+
function parseRetryAfter(value) {
|
|
75
|
+
if (!value) return void 0;
|
|
76
|
+
const secs = Number(value);
|
|
77
|
+
if (!Number.isNaN(secs)) return Math.max(0, secs * 1e3);
|
|
78
|
+
const date = Date.parse(value);
|
|
79
|
+
if (!Number.isNaN(date)) return Math.max(0, date - Date.now());
|
|
80
|
+
return void 0;
|
|
81
|
+
}
|
|
82
|
+
async function safeBody(response) {
|
|
83
|
+
const text = await response.text().catch(() => "");
|
|
84
|
+
if (!text) return void 0;
|
|
85
|
+
try {
|
|
86
|
+
return JSON.parse(text);
|
|
87
|
+
} catch {
|
|
88
|
+
return text;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function toAPIError(response) {
|
|
92
|
+
const body = await safeBody(response);
|
|
93
|
+
return buildError(response.status, body, response.headers);
|
|
94
|
+
}
|
|
95
|
+
function buildError(status, body, headers) {
|
|
96
|
+
const requestId = headers?.get("x-request-id") ?? headers?.get("x-canary-request-id") ?? void 0;
|
|
97
|
+
let message = `HTTP ${status ?? "error"}`;
|
|
98
|
+
let code;
|
|
99
|
+
let details;
|
|
100
|
+
if (body && typeof body === "object") {
|
|
101
|
+
const b = body;
|
|
102
|
+
if ("success" in b && typeof b.error === "string") {
|
|
103
|
+
message = b.error;
|
|
104
|
+
code = b.code;
|
|
105
|
+
details = b.details;
|
|
106
|
+
} else if (b.error && typeof b.error === "object" && typeof b.error.message === "string") {
|
|
107
|
+
const e = b.error;
|
|
108
|
+
message = e.message;
|
|
109
|
+
code = e.code ?? e.type;
|
|
110
|
+
} else if (b.type === "error" && b.error && typeof b.error.message === "string") {
|
|
111
|
+
const e = b.error;
|
|
112
|
+
message = e.message;
|
|
113
|
+
code = e.type;
|
|
114
|
+
} else if (typeof b.message === "string") {
|
|
115
|
+
message = b.message;
|
|
116
|
+
code = b.code;
|
|
117
|
+
}
|
|
118
|
+
} else if (typeof body === "string" && body) {
|
|
119
|
+
message = body;
|
|
120
|
+
}
|
|
121
|
+
return errorFromStatus(status, message, {
|
|
122
|
+
status,
|
|
123
|
+
code,
|
|
124
|
+
requestId,
|
|
125
|
+
headers,
|
|
126
|
+
details,
|
|
127
|
+
raw: body
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function errorFromStatus(status, message, init) {
|
|
131
|
+
switch (true) {
|
|
132
|
+
case status === 400:
|
|
133
|
+
return new BadRequestError(message, init);
|
|
134
|
+
case status === 401:
|
|
135
|
+
return new AuthenticationError(message, init);
|
|
136
|
+
case status === 403:
|
|
137
|
+
return new PermissionError(message, init);
|
|
138
|
+
case status === 404:
|
|
139
|
+
return new NotFoundError(message, init);
|
|
140
|
+
case status === 409:
|
|
141
|
+
return new ConflictError(message, init);
|
|
142
|
+
case status === 422:
|
|
143
|
+
return new UnprocessableEntityError(message, init);
|
|
144
|
+
case status === 429:
|
|
145
|
+
return new RateLimitError(message, init);
|
|
146
|
+
case (typeof status === "number" && status >= 500):
|
|
147
|
+
return new InternalServerError(message, init);
|
|
148
|
+
default:
|
|
149
|
+
return new APIError(message, init);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function statusFromCode(code) {
|
|
153
|
+
if (typeof code === "number") return code;
|
|
154
|
+
switch (code) {
|
|
155
|
+
case "RATE_LIMIT_EXCEEDED":
|
|
156
|
+
case "RATE_LIMITED":
|
|
157
|
+
return 429;
|
|
158
|
+
case "INVALID_API_KEY":
|
|
159
|
+
case "MISSING_API_KEY":
|
|
160
|
+
case "EXPIRED_KEY":
|
|
161
|
+
case "INACTIVE_KEY":
|
|
162
|
+
return 401;
|
|
163
|
+
case "INSUFFICIENT_PERMISSIONS":
|
|
164
|
+
case "ADMIN_REQUIRED":
|
|
165
|
+
return 403;
|
|
166
|
+
case "VALIDATION_ERROR":
|
|
167
|
+
return 400;
|
|
168
|
+
case "NOT_FOUND":
|
|
169
|
+
case "TASK_NOT_FOUND":
|
|
170
|
+
return 404;
|
|
171
|
+
default:
|
|
172
|
+
return void 0;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function streamError(data) {
|
|
176
|
+
if (data && typeof data === "object") {
|
|
177
|
+
const d = data;
|
|
178
|
+
if (typeof d.error === "string") {
|
|
179
|
+
return buildError(statusFromCode(d.code), {
|
|
180
|
+
success: false,
|
|
181
|
+
error: d.error,
|
|
182
|
+
code: d.code
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
if (d.error && typeof d.error === "object") {
|
|
186
|
+
const e = d.error;
|
|
187
|
+
return buildError(statusFromCode(e.code), data);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return new APIError("Stream error", { raw: data });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/core/fetch.ts
|
|
194
|
+
function resolveFetch(custom) {
|
|
195
|
+
if (custom) return custom;
|
|
196
|
+
if (typeof globalThis.fetch === "function") {
|
|
197
|
+
return globalThis.fetch.bind(globalThis);
|
|
198
|
+
}
|
|
199
|
+
throw new Error(
|
|
200
|
+
"No global fetch found. Run on Node >= 18 or Bun, or pass a `fetch` implementation in the client options."
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
function detectRuntime() {
|
|
204
|
+
if (typeof globalThis.Bun !== "undefined") return "bun";
|
|
205
|
+
if (typeof process !== "undefined" && typeof process.versions?.node === "string") {
|
|
206
|
+
return "node";
|
|
207
|
+
}
|
|
208
|
+
return "unknown";
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// src/core/retry.ts
|
|
212
|
+
var DEFAULT_RETRY_POLICY = {
|
|
213
|
+
maxRetries: 2,
|
|
214
|
+
baseDelayMs: 500,
|
|
215
|
+
maxDelayMs: 8e3,
|
|
216
|
+
factor: 2
|
|
217
|
+
};
|
|
218
|
+
var RETRY_IDEMPOTENT = {
|
|
219
|
+
onStatus: true,
|
|
220
|
+
on429: true,
|
|
221
|
+
onConnect: true,
|
|
222
|
+
onTimeout: true
|
|
223
|
+
};
|
|
224
|
+
var RETRY_SUBMIT = {
|
|
225
|
+
onStatus: false,
|
|
226
|
+
on429: true,
|
|
227
|
+
onConnect: true,
|
|
228
|
+
onTimeout: false
|
|
229
|
+
};
|
|
230
|
+
var RETRY_NONE = {
|
|
231
|
+
onStatus: false,
|
|
232
|
+
on429: false,
|
|
233
|
+
onConnect: false,
|
|
234
|
+
onTimeout: false
|
|
235
|
+
};
|
|
236
|
+
function shouldRetry(err, flags) {
|
|
237
|
+
if (err instanceof RateLimitError) return flags.on429;
|
|
238
|
+
if (err instanceof APIConnectionTimeoutError) return flags.onTimeout;
|
|
239
|
+
if (err instanceof APIConnectionError) return flags.onConnect;
|
|
240
|
+
if (err instanceof APIError && typeof err.status === "number") {
|
|
241
|
+
return flags.onStatus && (err.status === 408 || err.status >= 500);
|
|
242
|
+
}
|
|
243
|
+
if (err instanceof TypeError) return flags.onConnect;
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
function backoffDelay(attempt, policy, retryAfterMs) {
|
|
247
|
+
if (retryAfterMs !== void 0) return retryAfterMs;
|
|
248
|
+
const ceiling = Math.min(
|
|
249
|
+
policy.maxDelayMs,
|
|
250
|
+
policy.baseDelayMs * Math.pow(policy.factor, attempt)
|
|
251
|
+
);
|
|
252
|
+
return Math.random() * ceiling;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/utils/sleep.ts
|
|
256
|
+
function sleep(ms, signal) {
|
|
257
|
+
return new Promise((resolve, reject) => {
|
|
258
|
+
if (signal?.aborted) {
|
|
259
|
+
reject(signal.reason);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const timer = setTimeout(resolve, ms);
|
|
263
|
+
signal?.addEventListener(
|
|
264
|
+
"abort",
|
|
265
|
+
() => {
|
|
266
|
+
clearTimeout(timer);
|
|
267
|
+
reject(signal.reason);
|
|
268
|
+
},
|
|
269
|
+
{ once: true }
|
|
270
|
+
);
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// src/core/http.ts
|
|
275
|
+
var RETRY_FLAGS = {
|
|
276
|
+
idempotent: RETRY_IDEMPOTENT,
|
|
277
|
+
submit: RETRY_SUBMIT,
|
|
278
|
+
none: RETRY_NONE
|
|
279
|
+
};
|
|
280
|
+
var Transport = class {
|
|
281
|
+
baseURL;
|
|
282
|
+
timeoutMs;
|
|
283
|
+
maxRetries;
|
|
284
|
+
apiKey;
|
|
285
|
+
authStyle;
|
|
286
|
+
fetchImpl;
|
|
287
|
+
defaultHeaders;
|
|
288
|
+
defaultTag;
|
|
289
|
+
userAgent;
|
|
290
|
+
retryPolicy;
|
|
291
|
+
constructor(config) {
|
|
292
|
+
this.baseURL = config.baseURL.replace(/\/+$/, "");
|
|
293
|
+
this.apiKey = config.apiKey;
|
|
294
|
+
this.authStyle = config.authStyle;
|
|
295
|
+
this.timeoutMs = config.timeoutMs;
|
|
296
|
+
this.maxRetries = config.maxRetries;
|
|
297
|
+
this.fetchImpl = resolveFetch(config.fetch);
|
|
298
|
+
this.defaultHeaders = config.defaultHeaders ?? {};
|
|
299
|
+
this.defaultTag = config.defaultTag;
|
|
300
|
+
this.userAgent = config.userAgent ?? `canaryllm-sdk (${detectRuntime()})`;
|
|
301
|
+
this.retryPolicy = { ...DEFAULT_RETRY_POLICY, maxRetries: config.maxRetries };
|
|
302
|
+
}
|
|
303
|
+
async json(method, path, opts = {}) {
|
|
304
|
+
const res = await this.send(method, path, opts);
|
|
305
|
+
if (!res.ok) throw await toAPIError(res);
|
|
306
|
+
if (res.status === 204) return void 0;
|
|
307
|
+
const body = await res.json().catch(() => void 0);
|
|
308
|
+
return this.maybeUnwrap(body, opts.unwrap);
|
|
309
|
+
}
|
|
310
|
+
/** Perform the request and return the Response without throwing on non-2xx. */
|
|
311
|
+
async raw(method, path, opts = {}) {
|
|
312
|
+
return this.send(method, path, opts);
|
|
313
|
+
}
|
|
314
|
+
/** Perform the request and return the raw text body (CSV, YAML, …). */
|
|
315
|
+
async text(method, path, opts = {}) {
|
|
316
|
+
const res = await this.send(method, path, opts);
|
|
317
|
+
if (!res.ok) throw await toAPIError(res);
|
|
318
|
+
return res.text();
|
|
319
|
+
}
|
|
320
|
+
/** Open a streaming response. Throws if the initial response is an error. */
|
|
321
|
+
async stream(method, path, opts = {}) {
|
|
322
|
+
const res = await this.send(method, path, {
|
|
323
|
+
...opts,
|
|
324
|
+
timeoutMs: opts.timeoutMs ?? 0
|
|
325
|
+
});
|
|
326
|
+
if (!res.ok) throw await toAPIError(res);
|
|
327
|
+
return res;
|
|
328
|
+
}
|
|
329
|
+
maybeUnwrap(body, unwrap) {
|
|
330
|
+
if (unwrap === false) return body;
|
|
331
|
+
if (body && typeof body === "object" && "success" in body && "data" in body) {
|
|
332
|
+
return body.data;
|
|
333
|
+
}
|
|
334
|
+
return body;
|
|
335
|
+
}
|
|
336
|
+
async send(method, path, opts) {
|
|
337
|
+
const url = this.buildUrl(path, opts.query);
|
|
338
|
+
const hasBody = opts.body !== void 0;
|
|
339
|
+
const isForm = hasBody && isFormData(opts.body);
|
|
340
|
+
const headers = this.buildHeaders(opts.headers, hasBody && !isForm);
|
|
341
|
+
const payload = !hasBody ? void 0 : isForm ? opts.body : JSON.stringify(this.withDefaultTag(opts.body));
|
|
342
|
+
const flags = RETRY_FLAGS[opts.retry ?? "idempotent"];
|
|
343
|
+
const timeoutMs = opts.timeoutMs ?? this.timeoutMs;
|
|
344
|
+
return this.withRetry(
|
|
345
|
+
() => this.execute(method, url, headers, payload, opts.signal, timeoutMs),
|
|
346
|
+
flags,
|
|
347
|
+
opts.signal
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
withDefaultTag(body) {
|
|
351
|
+
if (this.defaultTag && body && typeof body === "object" && !Array.isArray(body) && body.tag === void 0) {
|
|
352
|
+
return { ...body, tag: this.defaultTag };
|
|
353
|
+
}
|
|
354
|
+
return body;
|
|
355
|
+
}
|
|
356
|
+
async withRetry(fn, flags, signal) {
|
|
357
|
+
let attempt = 0;
|
|
358
|
+
for (; ; ) {
|
|
359
|
+
try {
|
|
360
|
+
const res = await fn();
|
|
361
|
+
if (!res.ok && attempt < this.maxRetries) {
|
|
362
|
+
const candidate = await toAPIError(res.clone());
|
|
363
|
+
if (shouldRetry(candidate, flags)) {
|
|
364
|
+
const retryAfter = candidate instanceof RateLimitError ? candidate.retryAfterMs : void 0;
|
|
365
|
+
await sleep(backoffDelay(attempt, this.retryPolicy, retryAfter), signal);
|
|
366
|
+
attempt++;
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return res;
|
|
371
|
+
} catch (err) {
|
|
372
|
+
if (attempt < this.maxRetries && shouldRetry(err, flags)) {
|
|
373
|
+
await sleep(backoffDelay(attempt, this.retryPolicy), signal);
|
|
374
|
+
attempt++;
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
throw err;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
async execute(method, url, headers, body, signal, timeoutMs) {
|
|
382
|
+
const { signal: composite, cleanup } = linkedSignal(signal, timeoutMs);
|
|
383
|
+
try {
|
|
384
|
+
return await this.fetchImpl(url, {
|
|
385
|
+
method,
|
|
386
|
+
headers,
|
|
387
|
+
body,
|
|
388
|
+
signal: composite
|
|
389
|
+
});
|
|
390
|
+
} catch (err) {
|
|
391
|
+
if (signal?.aborted) {
|
|
392
|
+
throw signal.reason ?? new APIConnectionError("Request aborted");
|
|
393
|
+
}
|
|
394
|
+
if (isTimeoutAbort(err)) {
|
|
395
|
+
const e = new APIConnectionTimeoutError("Request timed out");
|
|
396
|
+
e.phase = "total";
|
|
397
|
+
throw e;
|
|
398
|
+
}
|
|
399
|
+
if (err instanceof APIError) throw err;
|
|
400
|
+
throw new APIConnectionError(
|
|
401
|
+
err instanceof Error ? err.message : "Connection error"
|
|
402
|
+
);
|
|
403
|
+
} finally {
|
|
404
|
+
cleanup();
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
buildHeaders(extra, hasBody) {
|
|
408
|
+
const h = {
|
|
409
|
+
Accept: "application/json",
|
|
410
|
+
...this.defaultHeaders,
|
|
411
|
+
...extra ?? {}
|
|
412
|
+
};
|
|
413
|
+
if (hasBody && !("Content-Type" in h)) {
|
|
414
|
+
h["Content-Type"] = "application/json";
|
|
415
|
+
}
|
|
416
|
+
if (this.apiKey) {
|
|
417
|
+
if (this.authStyle === "x-api-key") h["X-API-Key"] = this.apiKey;
|
|
418
|
+
else h["Authorization"] = `Bearer ${this.apiKey}`;
|
|
419
|
+
}
|
|
420
|
+
if (!("User-Agent" in h)) h["User-Agent"] = this.userAgent;
|
|
421
|
+
return h;
|
|
422
|
+
}
|
|
423
|
+
buildUrl(path, query) {
|
|
424
|
+
let url = /^https?:\/\//.test(path) ? path : `${this.baseURL}${path.startsWith("/") ? "" : "/"}${path}`;
|
|
425
|
+
if (query) {
|
|
426
|
+
const qs = new URLSearchParams();
|
|
427
|
+
for (const [k, v] of Object.entries(query)) {
|
|
428
|
+
if (v !== void 0) qs.set(k, String(v));
|
|
429
|
+
}
|
|
430
|
+
const s = qs.toString();
|
|
431
|
+
if (s) url += (url.includes("?") ? "&" : "?") + s;
|
|
432
|
+
}
|
|
433
|
+
return url;
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
function isTimeoutAbort(err) {
|
|
437
|
+
return err instanceof Error && err.name === "TimeoutError";
|
|
438
|
+
}
|
|
439
|
+
function isFormData(v) {
|
|
440
|
+
return typeof FormData !== "undefined" && v instanceof FormData;
|
|
441
|
+
}
|
|
442
|
+
function linkedSignal(userSignal, timeoutMs) {
|
|
443
|
+
const controller = new AbortController();
|
|
444
|
+
const cleanups = [];
|
|
445
|
+
if (userSignal) {
|
|
446
|
+
if (userSignal.aborted) {
|
|
447
|
+
controller.abort(userSignal.reason);
|
|
448
|
+
} else {
|
|
449
|
+
const onAbort = () => controller.abort(userSignal.reason);
|
|
450
|
+
userSignal.addEventListener("abort", onAbort, { once: true });
|
|
451
|
+
cleanups.push(() => userSignal.removeEventListener("abort", onAbort));
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
if (timeoutMs && timeoutMs > 0) {
|
|
455
|
+
const timer = setTimeout(
|
|
456
|
+
() => controller.abort(new DOMException("Request timed out", "TimeoutError")),
|
|
457
|
+
timeoutMs
|
|
458
|
+
);
|
|
459
|
+
cleanups.push(() => clearTimeout(timer));
|
|
460
|
+
}
|
|
461
|
+
return {
|
|
462
|
+
signal: controller.signal,
|
|
463
|
+
cleanup: () => {
|
|
464
|
+
for (const c of cleanups) c();
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// src/core/poller.ts
|
|
470
|
+
var DEFAULT_MAX_WAIT_MS = {
|
|
471
|
+
completion: 35 * 6e4,
|
|
472
|
+
image: 6 * 6e4,
|
|
473
|
+
video: 65 * 6e4,
|
|
474
|
+
tts: 6 * 6e4,
|
|
475
|
+
stt: 12 * 6e4,
|
|
476
|
+
"sound-effect": 6 * 6e4,
|
|
477
|
+
music: 17 * 6e4,
|
|
478
|
+
dialogue: 6 * 6e4,
|
|
479
|
+
vision: 6 * 6e4,
|
|
480
|
+
embedding: 6 * 6e4
|
|
481
|
+
};
|
|
482
|
+
async function pollOnce(transport, queueId, signal) {
|
|
483
|
+
const res = await transport.raw("POST", "/api/llm/queue/result", {
|
|
484
|
+
body: { queueId },
|
|
485
|
+
signal,
|
|
486
|
+
retry: "idempotent"
|
|
487
|
+
});
|
|
488
|
+
if (res.status === 202) {
|
|
489
|
+
const body = await res.json().catch(() => ({}));
|
|
490
|
+
return {
|
|
491
|
+
kind: "processing",
|
|
492
|
+
status: body.data?.status ?? "processing",
|
|
493
|
+
position: body.data?.position
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
if (res.ok) {
|
|
497
|
+
const body = await res.json().catch(() => ({}));
|
|
498
|
+
return { kind: "completed", result: body.data?.result };
|
|
499
|
+
}
|
|
500
|
+
return { kind: "failed", error: await toAPIError(res) };
|
|
501
|
+
}
|
|
502
|
+
async function pollToResult(transport, queueId, taskKind, opts = {}) {
|
|
503
|
+
const initial = opts.initialIntervalMs ?? 500;
|
|
504
|
+
const factor = opts.backoffFactor ?? 1.5;
|
|
505
|
+
const maxInterval = opts.maxIntervalMs ?? 5e3;
|
|
506
|
+
const jitter = opts.jitter ?? 0.2;
|
|
507
|
+
const maxWait = opts.maxWaitMs ?? DEFAULT_MAX_WAIT_MS[taskKind];
|
|
508
|
+
const signal = opts.signal;
|
|
509
|
+
const start = Date.now();
|
|
510
|
+
const deadline = start + maxWait;
|
|
511
|
+
let interval = initial;
|
|
512
|
+
for (; ; ) {
|
|
513
|
+
signal?.throwIfAborted();
|
|
514
|
+
let outcome;
|
|
515
|
+
try {
|
|
516
|
+
outcome = await pollOnce(transport, queueId, signal);
|
|
517
|
+
} catch (err) {
|
|
518
|
+
if (signal?.aborted) throw err;
|
|
519
|
+
if (err instanceof APIConnectionError) {
|
|
520
|
+
outcome = { kind: "processing", status: "processing" };
|
|
521
|
+
} else {
|
|
522
|
+
throw err;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
if (outcome.kind === "completed") return outcome.result;
|
|
526
|
+
if (outcome.kind === "failed") throw outcome.error;
|
|
527
|
+
opts.onPoll?.({
|
|
528
|
+
status: outcome.status,
|
|
529
|
+
position: outcome.position,
|
|
530
|
+
elapsedMs: Date.now() - start
|
|
531
|
+
});
|
|
532
|
+
const now = Date.now();
|
|
533
|
+
if (now >= deadline) {
|
|
534
|
+
const err = new APIConnectionTimeoutError(
|
|
535
|
+
`Queue task ${queueId} did not complete within ${maxWait}ms (last status: ${outcome.status})`
|
|
536
|
+
);
|
|
537
|
+
err.phase = "total";
|
|
538
|
+
throw err;
|
|
539
|
+
}
|
|
540
|
+
const base = Math.min(maxInterval, interval);
|
|
541
|
+
const jittered = base * (1 - jitter * Math.random());
|
|
542
|
+
const wait = Math.min(jittered, deadline - now);
|
|
543
|
+
await sleep(Math.max(0, wait), signal);
|
|
544
|
+
interval *= factor;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// src/core/sse.ts
|
|
549
|
+
async function* iterateSSE(body, options = {}) {
|
|
550
|
+
const { signal, idleMs } = options;
|
|
551
|
+
const reader = body.getReader();
|
|
552
|
+
const decoder = new TextDecoder("utf-8");
|
|
553
|
+
let buffer = "";
|
|
554
|
+
let idleFired = false;
|
|
555
|
+
let userAborted = false;
|
|
556
|
+
const onAbort = () => {
|
|
557
|
+
userAborted = true;
|
|
558
|
+
void reader.cancel(signal?.reason).catch(() => {
|
|
559
|
+
});
|
|
560
|
+
};
|
|
561
|
+
if (signal) {
|
|
562
|
+
if (signal.aborted) {
|
|
563
|
+
reader.releaseLock();
|
|
564
|
+
throw signal.reason ?? new APIConnectionError("Stream aborted");
|
|
565
|
+
}
|
|
566
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
567
|
+
}
|
|
568
|
+
try {
|
|
569
|
+
for (; ; ) {
|
|
570
|
+
let timer;
|
|
571
|
+
if (idleMs && idleMs > 0) {
|
|
572
|
+
timer = setTimeout(() => {
|
|
573
|
+
idleFired = true;
|
|
574
|
+
void reader.cancel().catch(() => {
|
|
575
|
+
});
|
|
576
|
+
}, idleMs);
|
|
577
|
+
}
|
|
578
|
+
let done = false;
|
|
579
|
+
let chunk;
|
|
580
|
+
try {
|
|
581
|
+
const r = await reader.read();
|
|
582
|
+
done = r.done;
|
|
583
|
+
chunk = r.value;
|
|
584
|
+
} finally {
|
|
585
|
+
if (timer) clearTimeout(timer);
|
|
586
|
+
}
|
|
587
|
+
if (done) break;
|
|
588
|
+
if (!chunk) continue;
|
|
589
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
590
|
+
buffer = buffer.replace(/\r\n/g, "\n");
|
|
591
|
+
let sep;
|
|
592
|
+
while ((sep = buffer.indexOf("\n\n")) !== -1) {
|
|
593
|
+
const rawFrame = buffer.slice(0, sep);
|
|
594
|
+
buffer = buffer.slice(sep + 2);
|
|
595
|
+
const frame = parseFrame(rawFrame);
|
|
596
|
+
if (frame) yield frame;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
buffer += decoder.decode();
|
|
600
|
+
if (buffer.trim()) {
|
|
601
|
+
const frame = parseFrame(buffer);
|
|
602
|
+
if (frame) yield frame;
|
|
603
|
+
}
|
|
604
|
+
} finally {
|
|
605
|
+
if (signal) signal.removeEventListener("abort", onAbort);
|
|
606
|
+
try {
|
|
607
|
+
reader.releaseLock();
|
|
608
|
+
} catch {
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
if (userAborted) {
|
|
612
|
+
throw signal?.reason ?? new APIConnectionError("Stream aborted");
|
|
613
|
+
}
|
|
614
|
+
if (idleFired) {
|
|
615
|
+
const err = new APIConnectionTimeoutError("Stream idle timeout");
|
|
616
|
+
err.phase = "read";
|
|
617
|
+
throw err;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
function parseFrame(raw) {
|
|
621
|
+
let event;
|
|
622
|
+
let id;
|
|
623
|
+
const dataLines = [];
|
|
624
|
+
let sawData = false;
|
|
625
|
+
for (const line of raw.split("\n")) {
|
|
626
|
+
if (line === "") continue;
|
|
627
|
+
if (line.startsWith(":")) continue;
|
|
628
|
+
const idx = line.indexOf(":");
|
|
629
|
+
const field = idx === -1 ? line : line.slice(0, idx);
|
|
630
|
+
let value = idx === -1 ? "" : line.slice(idx + 1);
|
|
631
|
+
if (value.startsWith(" ")) value = value.slice(1);
|
|
632
|
+
if (field === "event") event = value;
|
|
633
|
+
else if (field === "id") id = value;
|
|
634
|
+
else if (field === "data") {
|
|
635
|
+
dataLines.push(value);
|
|
636
|
+
sawData = true;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
if (!sawData && event === void 0) return null;
|
|
640
|
+
return { event, id, data: dataLines.join("\n") };
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// src/core/stream-adapters.ts
|
|
644
|
+
function tryParse(data) {
|
|
645
|
+
try {
|
|
646
|
+
return JSON.parse(data);
|
|
647
|
+
} catch {
|
|
648
|
+
return void 0;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
function streamResponse(response, protocol, options = {}) {
|
|
652
|
+
if (!response.body) {
|
|
653
|
+
throw new APIError("Response has no body to stream", {
|
|
654
|
+
status: response.status
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
const frames = iterateSSE(response.body, options);
|
|
658
|
+
const includeRaw = options.includeRaw ?? false;
|
|
659
|
+
switch (protocol) {
|
|
660
|
+
case "openai":
|
|
661
|
+
return openAIAdapter(frames, includeRaw);
|
|
662
|
+
case "anthropic":
|
|
663
|
+
return anthropicAdapter(frames, includeRaw);
|
|
664
|
+
case "responses":
|
|
665
|
+
return responsesAdapter(frames, includeRaw);
|
|
666
|
+
case "native":
|
|
667
|
+
default:
|
|
668
|
+
return nativeQueueAdapter(frames, includeRaw);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
async function* nativeQueueAdapter(frames, includeRaw) {
|
|
672
|
+
const raw = (v) => includeRaw ? v : void 0;
|
|
673
|
+
let lastUsage;
|
|
674
|
+
let lastFinish;
|
|
675
|
+
for await (const frame of frames) {
|
|
676
|
+
switch (frame.event) {
|
|
677
|
+
case "start":
|
|
678
|
+
yield { type: "start", raw: raw(frame.data) };
|
|
679
|
+
break;
|
|
680
|
+
case "error":
|
|
681
|
+
throw streamError(tryParse(frame.data));
|
|
682
|
+
case "done":
|
|
683
|
+
yield { type: "done", finishReason: lastFinish, usage: lastUsage };
|
|
684
|
+
return;
|
|
685
|
+
case "chunk": {
|
|
686
|
+
const chunk = tryParse(frame.data);
|
|
687
|
+
if (!chunk) break;
|
|
688
|
+
if (chunk.usage) {
|
|
689
|
+
lastUsage = chunk.usage;
|
|
690
|
+
yield { type: "usage", usage: chunk.usage, raw: raw(chunk) };
|
|
691
|
+
}
|
|
692
|
+
if (chunk.finishReason) lastFinish = chunk.finishReason;
|
|
693
|
+
if (chunk.toolCallDeltas) {
|
|
694
|
+
for (const d of chunk.toolCallDeltas) {
|
|
695
|
+
yield {
|
|
696
|
+
type: "tool_call",
|
|
697
|
+
index: d.index,
|
|
698
|
+
id: d.id,
|
|
699
|
+
name: d.function?.name,
|
|
700
|
+
argsDelta: d.function?.arguments,
|
|
701
|
+
raw: raw(d)
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
if (chunk.delta) {
|
|
706
|
+
const isThinking = Boolean(
|
|
707
|
+
chunk.metadata?.isThinking
|
|
708
|
+
);
|
|
709
|
+
yield isThinking ? { type: "thinking", delta: chunk.delta, raw: raw(chunk) } : { type: "text", delta: chunk.delta, raw: raw(chunk) };
|
|
710
|
+
}
|
|
711
|
+
break;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
yield { type: "done", finishReason: lastFinish, usage: lastUsage };
|
|
716
|
+
}
|
|
717
|
+
async function* openAIAdapter(frames, includeRaw) {
|
|
718
|
+
const raw = (v) => includeRaw ? v : void 0;
|
|
719
|
+
let started = false;
|
|
720
|
+
let lastUsage;
|
|
721
|
+
let lastFinish;
|
|
722
|
+
for await (const frame of frames) {
|
|
723
|
+
const data = frame.data.trim();
|
|
724
|
+
if (data === "[DONE]") {
|
|
725
|
+
yield { type: "done", finishReason: lastFinish, usage: lastUsage };
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
const chunk = tryParse(data);
|
|
729
|
+
if (!chunk) continue;
|
|
730
|
+
if (chunk.error) throw streamError(chunk);
|
|
731
|
+
if (!started) {
|
|
732
|
+
started = true;
|
|
733
|
+
yield { type: "start", raw: raw(chunk) };
|
|
734
|
+
}
|
|
735
|
+
const choice = chunk.choices?.[0];
|
|
736
|
+
if (choice) {
|
|
737
|
+
const delta = choice.delta ?? {};
|
|
738
|
+
if (delta.reasoning_content) {
|
|
739
|
+
yield { type: "thinking", delta: delta.reasoning_content, raw: raw(chunk) };
|
|
740
|
+
}
|
|
741
|
+
if (typeof delta.content === "string" && delta.content) {
|
|
742
|
+
yield { type: "text", delta: delta.content, raw: raw(chunk) };
|
|
743
|
+
}
|
|
744
|
+
if (Array.isArray(delta.tool_calls)) {
|
|
745
|
+
for (const tc of delta.tool_calls) {
|
|
746
|
+
yield {
|
|
747
|
+
type: "tool_call",
|
|
748
|
+
index: tc.index ?? 0,
|
|
749
|
+
id: tc.id,
|
|
750
|
+
name: tc.function?.name,
|
|
751
|
+
argsDelta: tc.function?.arguments,
|
|
752
|
+
raw: raw(tc)
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
if (choice.finish_reason) lastFinish = choice.finish_reason;
|
|
757
|
+
}
|
|
758
|
+
if (chunk.usage) {
|
|
759
|
+
lastUsage = mapOpenAIUsage(chunk.usage);
|
|
760
|
+
yield { type: "usage", usage: lastUsage, raw: raw(chunk) };
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
yield { type: "done", finishReason: lastFinish, usage: lastUsage };
|
|
764
|
+
}
|
|
765
|
+
async function* anthropicAdapter(frames, includeRaw) {
|
|
766
|
+
const raw = (v) => includeRaw ? v : void 0;
|
|
767
|
+
let lastUsage;
|
|
768
|
+
let lastFinish;
|
|
769
|
+
for await (const frame of frames) {
|
|
770
|
+
if (frame.event === "ping") continue;
|
|
771
|
+
const data = tryParse(frame.data);
|
|
772
|
+
if (frame.event === "error") {
|
|
773
|
+
throw streamError(data ?? { error: { message: "stream error" } });
|
|
774
|
+
}
|
|
775
|
+
switch (frame.event) {
|
|
776
|
+
case "message_start":
|
|
777
|
+
yield { type: "start", raw: raw(data) };
|
|
778
|
+
break;
|
|
779
|
+
case "content_block_start": {
|
|
780
|
+
const cb = data?.content_block;
|
|
781
|
+
if (cb?.type === "tool_use") {
|
|
782
|
+
yield {
|
|
783
|
+
type: "tool_call",
|
|
784
|
+
index: data?.index ?? 0,
|
|
785
|
+
id: cb.id,
|
|
786
|
+
name: cb.name,
|
|
787
|
+
raw: raw(data)
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
break;
|
|
791
|
+
}
|
|
792
|
+
case "content_block_delta": {
|
|
793
|
+
const d = data?.delta;
|
|
794
|
+
if (d?.type === "text_delta" && d.text) {
|
|
795
|
+
yield { type: "text", delta: d.text, raw: raw(data) };
|
|
796
|
+
} else if (d?.type === "thinking_delta" && d.thinking) {
|
|
797
|
+
yield { type: "thinking", delta: d.thinking, raw: raw(data) };
|
|
798
|
+
} else if (d?.type === "input_json_delta" && d.partial_json) {
|
|
799
|
+
yield {
|
|
800
|
+
type: "tool_call",
|
|
801
|
+
index: data?.index ?? 0,
|
|
802
|
+
argsDelta: d.partial_json,
|
|
803
|
+
raw: raw(data)
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
break;
|
|
807
|
+
}
|
|
808
|
+
case "message_delta":
|
|
809
|
+
if (data?.delta?.stop_reason) lastFinish = data.delta.stop_reason;
|
|
810
|
+
if (data?.usage) lastUsage = mapAnthropicUsage(data.usage);
|
|
811
|
+
break;
|
|
812
|
+
case "message_stop":
|
|
813
|
+
yield { type: "done", finishReason: lastFinish, usage: lastUsage };
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
yield { type: "done", finishReason: lastFinish, usage: lastUsage };
|
|
818
|
+
}
|
|
819
|
+
async function* responsesAdapter(frames, includeRaw) {
|
|
820
|
+
const raw = (v) => includeRaw ? v : void 0;
|
|
821
|
+
let started = false;
|
|
822
|
+
let lastUsage;
|
|
823
|
+
for await (const frame of frames) {
|
|
824
|
+
const data = tryParse(frame.data);
|
|
825
|
+
if (frame.event === "error") throw streamError(data ?? {});
|
|
826
|
+
switch (frame.event) {
|
|
827
|
+
case "response.created":
|
|
828
|
+
case "response.in_progress":
|
|
829
|
+
if (!started) {
|
|
830
|
+
started = true;
|
|
831
|
+
yield { type: "start", raw: raw(data) };
|
|
832
|
+
}
|
|
833
|
+
break;
|
|
834
|
+
case "response.output_text.delta":
|
|
835
|
+
if (typeof data?.delta === "string") {
|
|
836
|
+
yield { type: "text", delta: data.delta, raw: raw(data) };
|
|
837
|
+
}
|
|
838
|
+
break;
|
|
839
|
+
case "response.reasoning_summary_text.delta":
|
|
840
|
+
case "response.reasoning_text.delta":
|
|
841
|
+
if (typeof data?.delta === "string") {
|
|
842
|
+
yield { type: "thinking", delta: data.delta, raw: raw(data) };
|
|
843
|
+
}
|
|
844
|
+
break;
|
|
845
|
+
case "response.function_call_arguments.delta":
|
|
846
|
+
yield {
|
|
847
|
+
type: "tool_call",
|
|
848
|
+
index: data?.output_index ?? 0,
|
|
849
|
+
argsDelta: typeof data?.delta === "string" ? data.delta : void 0,
|
|
850
|
+
raw: raw(data)
|
|
851
|
+
};
|
|
852
|
+
break;
|
|
853
|
+
case "response.completed":
|
|
854
|
+
if (data?.response?.usage) lastUsage = mapResponsesUsage(data.response.usage);
|
|
855
|
+
yield { type: "done", finishReason: "stop", usage: lastUsage };
|
|
856
|
+
return;
|
|
857
|
+
case "response.failed":
|
|
858
|
+
throw streamError(
|
|
859
|
+
data?.response?.error ? { error: data.response.error } : {}
|
|
860
|
+
);
|
|
861
|
+
default:
|
|
862
|
+
yield { type: "raw", event: frame.event, data };
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
yield { type: "done", usage: lastUsage };
|
|
866
|
+
}
|
|
867
|
+
function mapOpenAIUsage(u) {
|
|
868
|
+
const input = u.prompt_tokens ?? 0;
|
|
869
|
+
const output = u.completion_tokens ?? 0;
|
|
870
|
+
return {
|
|
871
|
+
inputTokens: input,
|
|
872
|
+
outputTokens: output,
|
|
873
|
+
totalTokens: u.total_tokens ?? input + output,
|
|
874
|
+
cachedTokens: u.prompt_tokens_details?.cached_tokens,
|
|
875
|
+
reasoningTokens: u.completion_tokens_details?.reasoning_tokens
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
function mapAnthropicUsage(u) {
|
|
879
|
+
const input = u.input_tokens ?? 0;
|
|
880
|
+
const output = u.output_tokens ?? 0;
|
|
881
|
+
return {
|
|
882
|
+
inputTokens: input,
|
|
883
|
+
outputTokens: output,
|
|
884
|
+
totalTokens: input + output,
|
|
885
|
+
cachedTokens: u.cache_read_input_tokens
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
function mapResponsesUsage(u) {
|
|
889
|
+
const input = u.input_tokens ?? 0;
|
|
890
|
+
const output = u.output_tokens ?? 0;
|
|
891
|
+
return {
|
|
892
|
+
inputTokens: input,
|
|
893
|
+
outputTokens: output,
|
|
894
|
+
totalTokens: u.total_tokens ?? input + output,
|
|
895
|
+
cachedTokens: u.input_tokens_details?.cached_tokens,
|
|
896
|
+
reasoningTokens: u.output_tokens_details?.reasoning_tokens
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// src/core/job.ts
|
|
901
|
+
var Job = class {
|
|
902
|
+
id;
|
|
903
|
+
transport;
|
|
904
|
+
taskKind;
|
|
905
|
+
defaultPoll;
|
|
906
|
+
cachedValue;
|
|
907
|
+
constructor(id, transport, taskKind, defaultPoll) {
|
|
908
|
+
this.id = id;
|
|
909
|
+
this.transport = transport;
|
|
910
|
+
this.taskKind = taskKind;
|
|
911
|
+
this.defaultPoll = defaultPoll;
|
|
912
|
+
}
|
|
913
|
+
/** One-shot status probe. Throws `NotFoundError` unless `allowMissing`. */
|
|
914
|
+
async status(opts = {}) {
|
|
915
|
+
const res = await this.transport.raw("POST", "/api/llm/queue/status", {
|
|
916
|
+
body: { queueId: this.id },
|
|
917
|
+
signal: opts.signal
|
|
918
|
+
});
|
|
919
|
+
if (!res.ok) throw await toAPIError(res);
|
|
920
|
+
const body = await res.json().catch(() => ({}));
|
|
921
|
+
const data = body.data ?? {};
|
|
922
|
+
const snapshot = {
|
|
923
|
+
id: this.id,
|
|
924
|
+
status: data.status ?? "not_found",
|
|
925
|
+
position: data.position,
|
|
926
|
+
createdAt: data.createdAt,
|
|
927
|
+
startedAt: data.startedAt,
|
|
928
|
+
completedAt: data.completedAt,
|
|
929
|
+
provider: data.provider,
|
|
930
|
+
model: data.model,
|
|
931
|
+
error: data.error
|
|
932
|
+
};
|
|
933
|
+
if (snapshot.status === "not_found" && !opts.allowMissing) {
|
|
934
|
+
throw new NotFoundError(`Queue task ${this.id} not found`, {
|
|
935
|
+
status: 404,
|
|
936
|
+
code: "TASK_NOT_FOUND"
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
return snapshot;
|
|
940
|
+
}
|
|
941
|
+
/** Poll to completion and resolve the typed result (cached after success). */
|
|
942
|
+
async result(opts = {}) {
|
|
943
|
+
if (this.cachedValue) return this.cachedValue.value;
|
|
944
|
+
const merged = { ...this.defaultPoll, ...opts };
|
|
945
|
+
const cancelOnAbort = merged.cancelServerOnAbort ?? true;
|
|
946
|
+
try {
|
|
947
|
+
const value = await pollToResult(
|
|
948
|
+
this.transport,
|
|
949
|
+
this.id,
|
|
950
|
+
this.taskKind,
|
|
951
|
+
merged
|
|
952
|
+
);
|
|
953
|
+
this.cachedValue = { value };
|
|
954
|
+
return value;
|
|
955
|
+
} catch (err) {
|
|
956
|
+
if (cancelOnAbort && merged.signal?.aborted) {
|
|
957
|
+
void this.cancel().catch(() => {
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
throw err;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
/** Stream the task's chunks via `/queue/stream` (for `stream: true` tasks). */
|
|
964
|
+
stream(opts = {}) {
|
|
965
|
+
const transport = this.transport;
|
|
966
|
+
const id = this.id;
|
|
967
|
+
return (async function* () {
|
|
968
|
+
const res = await transport.stream("POST", "/api/llm/queue/stream", {
|
|
969
|
+
body: { queueId: id },
|
|
970
|
+
signal: opts.signal
|
|
971
|
+
});
|
|
972
|
+
yield* streamResponse(res, "native", {
|
|
973
|
+
signal: opts.signal,
|
|
974
|
+
includeRaw: opts.includeRaw,
|
|
975
|
+
idleMs: opts.idleMs ?? 6e4
|
|
976
|
+
});
|
|
977
|
+
})();
|
|
978
|
+
}
|
|
979
|
+
/** Best-effort server cancel. Safe to call more than once. */
|
|
980
|
+
async cancel(opts = {}) {
|
|
981
|
+
await this.transport.raw("POST", "/api/llm/queue/cancel", {
|
|
982
|
+
body: { queueId: this.id },
|
|
983
|
+
signal: opts.signal
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
async function submitJob(transport, path, body, taskKind, signal, defaultPoll) {
|
|
988
|
+
const data = await transport.json("POST", path, {
|
|
989
|
+
body,
|
|
990
|
+
signal,
|
|
991
|
+
retry: "submit"
|
|
992
|
+
});
|
|
993
|
+
return new Job(data.queueId, transport, taskKind, defaultPoll);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// src/resources/base.ts
|
|
997
|
+
var BaseResource = class {
|
|
998
|
+
transport;
|
|
999
|
+
defaultPoll;
|
|
1000
|
+
constructor(transport, defaultPoll) {
|
|
1001
|
+
this.transport = transport;
|
|
1002
|
+
this.defaultPoll = defaultPoll;
|
|
1003
|
+
}
|
|
1004
|
+
submitQueued(path, body, kind, signal) {
|
|
1005
|
+
return submitJob(this.transport, path, body, kind, signal, this.defaultPoll);
|
|
1006
|
+
}
|
|
1007
|
+
async runQueued(path, body, kind, poll) {
|
|
1008
|
+
const job = await this.submitQueued(path, body, kind, poll?.signal);
|
|
1009
|
+
return job.result(poll);
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
|
|
1013
|
+
// src/resources/agents.ts
|
|
1014
|
+
var AgentsResource = class extends BaseResource {
|
|
1015
|
+
/** Mint a short-lived signed URL for an ElevenLabs agent. */
|
|
1016
|
+
signedUrl(params, signal) {
|
|
1017
|
+
return this.transport.json("POST", "/api/agents/signed-url", {
|
|
1018
|
+
body: params,
|
|
1019
|
+
signal,
|
|
1020
|
+
retry: "submit"
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
|
|
1025
|
+
// src/resources/audio.ts
|
|
1026
|
+
var AudioResource = class extends BaseResource {
|
|
1027
|
+
/** Text-to-speech. */
|
|
1028
|
+
speech(params, poll) {
|
|
1029
|
+
return this.runQueued(
|
|
1030
|
+
"/api/llm/generate-audio",
|
|
1031
|
+
params,
|
|
1032
|
+
"tts",
|
|
1033
|
+
poll
|
|
1034
|
+
);
|
|
1035
|
+
}
|
|
1036
|
+
speechJob(params, signal) {
|
|
1037
|
+
return this.submitQueued(
|
|
1038
|
+
"/api/llm/generate-audio",
|
|
1039
|
+
params,
|
|
1040
|
+
"tts",
|
|
1041
|
+
signal
|
|
1042
|
+
);
|
|
1043
|
+
}
|
|
1044
|
+
/** Speech-to-text. */
|
|
1045
|
+
transcribe(params, poll) {
|
|
1046
|
+
return this.runQueued(
|
|
1047
|
+
"/api/llm/transcribe",
|
|
1048
|
+
params,
|
|
1049
|
+
"stt",
|
|
1050
|
+
poll
|
|
1051
|
+
);
|
|
1052
|
+
}
|
|
1053
|
+
transcribeJob(params, signal) {
|
|
1054
|
+
return this.submitQueued(
|
|
1055
|
+
"/api/llm/transcribe",
|
|
1056
|
+
params,
|
|
1057
|
+
"stt",
|
|
1058
|
+
signal
|
|
1059
|
+
);
|
|
1060
|
+
}
|
|
1061
|
+
soundEffect(params, poll) {
|
|
1062
|
+
return this.runQueued(
|
|
1063
|
+
"/api/llm/generate-sound-effect",
|
|
1064
|
+
params,
|
|
1065
|
+
"sound-effect",
|
|
1066
|
+
poll
|
|
1067
|
+
);
|
|
1068
|
+
}
|
|
1069
|
+
soundEffectJob(params, signal) {
|
|
1070
|
+
return this.submitQueued(
|
|
1071
|
+
"/api/llm/generate-sound-effect",
|
|
1072
|
+
params,
|
|
1073
|
+
"sound-effect",
|
|
1074
|
+
signal
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1077
|
+
music(params, poll) {
|
|
1078
|
+
return this.runQueued(
|
|
1079
|
+
"/api/llm/generate-music",
|
|
1080
|
+
params,
|
|
1081
|
+
"music",
|
|
1082
|
+
poll
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
musicJob(params, signal) {
|
|
1086
|
+
return this.submitQueued(
|
|
1087
|
+
"/api/llm/generate-music",
|
|
1088
|
+
params,
|
|
1089
|
+
"music",
|
|
1090
|
+
signal
|
|
1091
|
+
);
|
|
1092
|
+
}
|
|
1093
|
+
dialogue(params, poll) {
|
|
1094
|
+
return this.runQueued(
|
|
1095
|
+
"/api/llm/generate-dialogue",
|
|
1096
|
+
params,
|
|
1097
|
+
"dialogue",
|
|
1098
|
+
poll
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
dialogueJob(params, signal) {
|
|
1102
|
+
return this.submitQueued(
|
|
1103
|
+
"/api/llm/generate-dialogue",
|
|
1104
|
+
params,
|
|
1105
|
+
"dialogue",
|
|
1106
|
+
signal
|
|
1107
|
+
);
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
// src/resources/chat.ts
|
|
1112
|
+
var ChatResource = class extends BaseResource {
|
|
1113
|
+
/** Submit a completion and poll until the final result is ready. */
|
|
1114
|
+
complete(params, poll) {
|
|
1115
|
+
return this.runQueued(
|
|
1116
|
+
"/api/llm/complete",
|
|
1117
|
+
params,
|
|
1118
|
+
"completion",
|
|
1119
|
+
poll
|
|
1120
|
+
);
|
|
1121
|
+
}
|
|
1122
|
+
/** Submit a completion and return the `Job` handle without waiting. */
|
|
1123
|
+
submit(params, signal) {
|
|
1124
|
+
return this.submitQueued(
|
|
1125
|
+
"/api/llm/complete",
|
|
1126
|
+
params,
|
|
1127
|
+
"completion",
|
|
1128
|
+
signal
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
/** Stream a completion as normalized chat events. */
|
|
1132
|
+
stream(params, opts = {}) {
|
|
1133
|
+
const transport = this.transport;
|
|
1134
|
+
const defaultPoll = this.defaultPoll;
|
|
1135
|
+
return (async function* () {
|
|
1136
|
+
const job = await submitJob(
|
|
1137
|
+
transport,
|
|
1138
|
+
"/api/llm/complete",
|
|
1139
|
+
{ ...params, stream: true },
|
|
1140
|
+
"completion",
|
|
1141
|
+
opts.signal,
|
|
1142
|
+
defaultPoll
|
|
1143
|
+
);
|
|
1144
|
+
yield* job.stream({ signal: opts.signal, includeRaw: opts.includeRaw });
|
|
1145
|
+
})();
|
|
1146
|
+
}
|
|
1147
|
+
};
|
|
1148
|
+
|
|
1149
|
+
// src/resources/conversations.ts
|
|
1150
|
+
var TemplatesAPI = class extends BaseResource {
|
|
1151
|
+
create(params, signal) {
|
|
1152
|
+
return this.transport.json("POST", "/api/convagents/templates", {
|
|
1153
|
+
body: params,
|
|
1154
|
+
signal,
|
|
1155
|
+
retry: "submit"
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
list(signal) {
|
|
1159
|
+
return this.transport.json("GET", "/api/convagents/templates", { signal });
|
|
1160
|
+
}
|
|
1161
|
+
async get(id, signal) {
|
|
1162
|
+
const data = await this.transport.json("GET", `/api/convagents/templates/${id}`, { signal });
|
|
1163
|
+
return data.template;
|
|
1164
|
+
}
|
|
1165
|
+
update(id, params, signal) {
|
|
1166
|
+
return this.transport.json("PUT", `/api/convagents/templates/${id}`, {
|
|
1167
|
+
body: params,
|
|
1168
|
+
signal,
|
|
1169
|
+
retry: "submit"
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
delete(id, signal) {
|
|
1173
|
+
return this.transport.json("DELETE", `/api/convagents/templates/${id}`, {
|
|
1174
|
+
signal
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1177
|
+
};
|
|
1178
|
+
var SessionsAPI = class extends BaseResource {
|
|
1179
|
+
create(params, signal) {
|
|
1180
|
+
return this.transport.json("POST", "/api/convagents/sessions", {
|
|
1181
|
+
body: params,
|
|
1182
|
+
signal,
|
|
1183
|
+
retry: "submit"
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
list(opts = {}, signal) {
|
|
1187
|
+
return this.transport.json("GET", "/api/convagents/sessions", {
|
|
1188
|
+
query: { templateId: opts.templateId },
|
|
1189
|
+
signal
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
async get(id, signal) {
|
|
1193
|
+
const data = await this.transport.json("GET", `/api/convagents/sessions/${id}`, { signal });
|
|
1194
|
+
return data.session;
|
|
1195
|
+
}
|
|
1196
|
+
};
|
|
1197
|
+
var ConversationsResource = class extends BaseResource {
|
|
1198
|
+
templates = new TemplatesAPI(this.transport, this.defaultPoll);
|
|
1199
|
+
sessions = new SessionsAPI(this.transport, this.defaultPoll);
|
|
1200
|
+
};
|
|
1201
|
+
|
|
1202
|
+
// src/resources/discovery.ts
|
|
1203
|
+
var DiscoveryResource = class extends BaseResource {
|
|
1204
|
+
async providers(signal) {
|
|
1205
|
+
const data = await this.transport.json(
|
|
1206
|
+
"GET",
|
|
1207
|
+
"/api/llm/providers",
|
|
1208
|
+
{ signal }
|
|
1209
|
+
);
|
|
1210
|
+
return data.providers;
|
|
1211
|
+
}
|
|
1212
|
+
async models(provider, signal) {
|
|
1213
|
+
const data = await this.transport.json(
|
|
1214
|
+
"GET",
|
|
1215
|
+
"/api/llm/models",
|
|
1216
|
+
{ query: { provider }, signal }
|
|
1217
|
+
);
|
|
1218
|
+
return data.models;
|
|
1219
|
+
}
|
|
1220
|
+
async voices(provider, signal) {
|
|
1221
|
+
const data = await this.transport.json(
|
|
1222
|
+
"GET",
|
|
1223
|
+
"/api/llm/voices",
|
|
1224
|
+
{ query: { provider }, signal }
|
|
1225
|
+
);
|
|
1226
|
+
return data.voices;
|
|
1227
|
+
}
|
|
1228
|
+
capabilities(signal) {
|
|
1229
|
+
return this.transport.json("GET", "/api/llm/capabilities", { signal });
|
|
1230
|
+
}
|
|
1231
|
+
concurrency(signal) {
|
|
1232
|
+
return this.transport.json("GET", "/api/llm/concurrency", { signal });
|
|
1233
|
+
}
|
|
1234
|
+
concurrencyFor(provider, signal) {
|
|
1235
|
+
return this.transport.json(
|
|
1236
|
+
"GET",
|
|
1237
|
+
`/api/llm/concurrency/${encodeURIComponent(provider)}`,
|
|
1238
|
+
{ signal }
|
|
1239
|
+
);
|
|
1240
|
+
}
|
|
1241
|
+
};
|
|
1242
|
+
|
|
1243
|
+
// src/resources/embeddings.ts
|
|
1244
|
+
var EmbeddingsResource = class extends BaseResource {
|
|
1245
|
+
/**
|
|
1246
|
+
* Embed one or more text inputs into vectors via a local embedding model
|
|
1247
|
+
* (LM Studio). Submits to the queue and resolves with the vectors. Content is
|
|
1248
|
+
* processed transiently by the gateway and never stored — intended for
|
|
1249
|
+
* customer-side RAG ingestion and retrieval.
|
|
1250
|
+
*/
|
|
1251
|
+
create(params, poll) {
|
|
1252
|
+
return this.runQueued(
|
|
1253
|
+
"/api/llm/embeddings",
|
|
1254
|
+
params,
|
|
1255
|
+
"embedding",
|
|
1256
|
+
poll
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1259
|
+
/** Handle form: returns a {@link Job} you can poll or cancel yourself. */
|
|
1260
|
+
createJob(params, signal) {
|
|
1261
|
+
return this.submitQueued(
|
|
1262
|
+
"/api/llm/embeddings",
|
|
1263
|
+
params,
|
|
1264
|
+
"embedding",
|
|
1265
|
+
signal
|
|
1266
|
+
);
|
|
1267
|
+
}
|
|
1268
|
+
};
|
|
1269
|
+
|
|
1270
|
+
// src/resources/images.ts
|
|
1271
|
+
var ImagesResource = class extends BaseResource {
|
|
1272
|
+
/** Generate one or more images and wait for the result. */
|
|
1273
|
+
generate(params, poll) {
|
|
1274
|
+
return this.runQueued(
|
|
1275
|
+
"/api/llm/generate-image",
|
|
1276
|
+
params,
|
|
1277
|
+
"image",
|
|
1278
|
+
poll
|
|
1279
|
+
);
|
|
1280
|
+
}
|
|
1281
|
+
/** Submit an image generation and return the `Job` handle. */
|
|
1282
|
+
generateJob(params, signal) {
|
|
1283
|
+
return this.submitQueued(
|
|
1284
|
+
"/api/llm/generate-image",
|
|
1285
|
+
params,
|
|
1286
|
+
"image",
|
|
1287
|
+
signal
|
|
1288
|
+
);
|
|
1289
|
+
}
|
|
1290
|
+
};
|
|
1291
|
+
|
|
1292
|
+
// src/resources/keys.ts
|
|
1293
|
+
var KeysResource = class extends BaseResource {
|
|
1294
|
+
/** Info about the key the client is authenticated with. */
|
|
1295
|
+
info(signal) {
|
|
1296
|
+
return this.transport.json("GET", "/api/keys/info", { signal });
|
|
1297
|
+
}
|
|
1298
|
+
/** Validate an API key. Returns `{ valid: false }` rather than throwing. */
|
|
1299
|
+
validate(apiKey, signal) {
|
|
1300
|
+
return this.transport.json("POST", "/api/keys/validate", {
|
|
1301
|
+
body: { apiKey },
|
|
1302
|
+
signal,
|
|
1303
|
+
unwrap: false
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
};
|
|
1307
|
+
|
|
1308
|
+
// src/resources/portal.ts
|
|
1309
|
+
var PortalResource = class extends BaseResource {
|
|
1310
|
+
info(signal) {
|
|
1311
|
+
return this.transport.json("GET", "/api/portal/info", { signal });
|
|
1312
|
+
}
|
|
1313
|
+
overview(period = {}, signal) {
|
|
1314
|
+
return this.transport.json("GET", "/api/portal/overview", {
|
|
1315
|
+
query: { year: period.year, month: period.month },
|
|
1316
|
+
signal
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
usageDaily(period = {}, signal) {
|
|
1320
|
+
return this.transport.json("GET", "/api/portal/usage/daily", {
|
|
1321
|
+
query: { year: period.year, month: period.month },
|
|
1322
|
+
signal
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
usageByModel(period = {}, signal) {
|
|
1326
|
+
return this.transport.json("GET", "/api/portal/usage/by-model", {
|
|
1327
|
+
query: { year: period.year, month: period.month },
|
|
1328
|
+
signal
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
/** Export a month of usage as CSV text. */
|
|
1332
|
+
exportUsage(params, signal) {
|
|
1333
|
+
return this.transport.text("GET", "/api/portal/export/usage", {
|
|
1334
|
+
query: { month: params.month, year: params.year },
|
|
1335
|
+
signal
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
};
|
|
1339
|
+
|
|
1340
|
+
// src/resources/public.ts
|
|
1341
|
+
var PublicResource = class extends BaseResource {
|
|
1342
|
+
/** All providers with their models and public pricing. */
|
|
1343
|
+
models(signal) {
|
|
1344
|
+
return this.transport.json("GET", "/api/public/models", { signal });
|
|
1345
|
+
}
|
|
1346
|
+
/** Available voices per provider. */
|
|
1347
|
+
voices(signal) {
|
|
1348
|
+
return this.transport.json("GET", "/api/public/voices", { signal });
|
|
1349
|
+
}
|
|
1350
|
+
/** A short spoken preview of a voice (base64 audio). */
|
|
1351
|
+
voicePreview(params, signal) {
|
|
1352
|
+
return this.transport.json("POST", "/api/public/voices/preview", {
|
|
1353
|
+
body: params,
|
|
1354
|
+
signal
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1357
|
+
/** The raw OpenAPI specification (YAML). */
|
|
1358
|
+
openapi(signal) {
|
|
1359
|
+
return this.transport.text("GET", "/api/public/openapi.yaml", { signal });
|
|
1360
|
+
}
|
|
1361
|
+
};
|
|
1362
|
+
|
|
1363
|
+
// src/resources/queue.ts
|
|
1364
|
+
var QueueResource = class extends BaseResource {
|
|
1365
|
+
/** Reconstruct a `Job` handle from a known queue id. */
|
|
1366
|
+
job(queueId, taskKind = "completion") {
|
|
1367
|
+
return new Job(queueId, this.transport, taskKind, this.defaultPoll);
|
|
1368
|
+
}
|
|
1369
|
+
status(queueId, opts) {
|
|
1370
|
+
return this.job(queueId).status(opts);
|
|
1371
|
+
}
|
|
1372
|
+
result(queueId, opts) {
|
|
1373
|
+
return this.job(queueId).result(opts);
|
|
1374
|
+
}
|
|
1375
|
+
stream(queueId, opts) {
|
|
1376
|
+
return this.job(queueId).stream(opts);
|
|
1377
|
+
}
|
|
1378
|
+
cancel(queueId, opts) {
|
|
1379
|
+
return this.job(queueId).cancel(opts);
|
|
1380
|
+
}
|
|
1381
|
+
};
|
|
1382
|
+
|
|
1383
|
+
// src/resources/realtime.ts
|
|
1384
|
+
var RealtimeSessionsAPI = class extends BaseResource {
|
|
1385
|
+
/** Mint an ephemeral realtime credential. The response is NOT enveloped. */
|
|
1386
|
+
create(params, signal) {
|
|
1387
|
+
return this.transport.json("POST", "/api/realtime/sessions", {
|
|
1388
|
+
body: params,
|
|
1389
|
+
signal,
|
|
1390
|
+
retry: "submit",
|
|
1391
|
+
unwrap: false
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
async get(id, signal) {
|
|
1395
|
+
const data = await this.transport.json(
|
|
1396
|
+
"GET",
|
|
1397
|
+
`/api/realtime/sessions/${id}`,
|
|
1398
|
+
{ signal }
|
|
1399
|
+
);
|
|
1400
|
+
return data.session;
|
|
1401
|
+
}
|
|
1402
|
+
async end(id, params = {}, signal) {
|
|
1403
|
+
const data = await this.transport.json(
|
|
1404
|
+
"POST",
|
|
1405
|
+
`/api/realtime/sessions/${id}/end`,
|
|
1406
|
+
{ body: params, signal }
|
|
1407
|
+
);
|
|
1408
|
+
return data.session;
|
|
1409
|
+
}
|
|
1410
|
+
};
|
|
1411
|
+
var RealtimeResource = class extends BaseResource {
|
|
1412
|
+
sessions = new RealtimeSessionsAPI(this.transport, this.defaultPoll);
|
|
1413
|
+
};
|
|
1414
|
+
function toBrokeredCredential(session) {
|
|
1415
|
+
if ("clientSecret" in session) {
|
|
1416
|
+
return {
|
|
1417
|
+
kind: "openai-realtime",
|
|
1418
|
+
sessionId: session.sessionId,
|
|
1419
|
+
token: session.clientSecret,
|
|
1420
|
+
url: session.webrtcUrl,
|
|
1421
|
+
expiresAt: session.expiresAt,
|
|
1422
|
+
expiresInMs: Math.max(0, Date.parse(session.expiresAt) - Date.now())
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
const expiresInMs = session.expiresIn * 1e3;
|
|
1426
|
+
return {
|
|
1427
|
+
kind: "elevenlabs-convai",
|
|
1428
|
+
sessionId: session.session.id,
|
|
1429
|
+
url: session.signedUrl,
|
|
1430
|
+
expiresAt: new Date(Date.now() + expiresInMs).toISOString(),
|
|
1431
|
+
expiresInMs
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
// src/resources/usage.ts
|
|
1436
|
+
var UsageResource = class extends BaseResource {
|
|
1437
|
+
/** Current-month usage summary. */
|
|
1438
|
+
current(signal) {
|
|
1439
|
+
return this.transport.json("GET", "/api/llm/usage", { signal });
|
|
1440
|
+
}
|
|
1441
|
+
/** Monthly usage broken down by model/provider. */
|
|
1442
|
+
monthly(signal) {
|
|
1443
|
+
return this.transport.json("GET", "/api/llm/usage/monthly", { signal });
|
|
1444
|
+
}
|
|
1445
|
+
/** Daily usage breakdown. */
|
|
1446
|
+
daily(signal) {
|
|
1447
|
+
return this.transport.json("GET", "/api/llm/usage/daily", { signal });
|
|
1448
|
+
}
|
|
1449
|
+
};
|
|
1450
|
+
|
|
1451
|
+
// src/resources/video.ts
|
|
1452
|
+
var VideoResource = class extends BaseResource {
|
|
1453
|
+
generate(params, poll) {
|
|
1454
|
+
return this.runQueued(
|
|
1455
|
+
"/api/llm/generate-video",
|
|
1456
|
+
params,
|
|
1457
|
+
"video",
|
|
1458
|
+
poll
|
|
1459
|
+
);
|
|
1460
|
+
}
|
|
1461
|
+
generateJob(params, signal) {
|
|
1462
|
+
return this.submitQueued(
|
|
1463
|
+
"/api/llm/generate-video",
|
|
1464
|
+
params,
|
|
1465
|
+
"video",
|
|
1466
|
+
signal
|
|
1467
|
+
);
|
|
1468
|
+
}
|
|
1469
|
+
/** Upload a seed video for image/video-to-video; returns a `fileId`. */
|
|
1470
|
+
async upload(file, opts = {}) {
|
|
1471
|
+
const form = new FormData();
|
|
1472
|
+
const blob = file instanceof Blob ? file : new Blob([file], { type: opts.mimeType });
|
|
1473
|
+
form.append("video", blob, opts.filename ?? "video");
|
|
1474
|
+
return this.transport.json(
|
|
1475
|
+
"POST",
|
|
1476
|
+
"/api/llm/upload-video",
|
|
1477
|
+
{ body: form, signal: opts.signal, retry: "submit" }
|
|
1478
|
+
);
|
|
1479
|
+
}
|
|
1480
|
+
};
|
|
1481
|
+
|
|
1482
|
+
// src/resources/vision.ts
|
|
1483
|
+
var VisionResource = class extends BaseResource {
|
|
1484
|
+
detect(params, poll) {
|
|
1485
|
+
return this.runQueued("/api/vision/detect", params, "vision", poll);
|
|
1486
|
+
}
|
|
1487
|
+
detectJob(params, signal) {
|
|
1488
|
+
return this.submitQueued("/api/vision/detect", params, "vision", signal);
|
|
1489
|
+
}
|
|
1490
|
+
zeroShot(params, poll) {
|
|
1491
|
+
return this.runQueued(
|
|
1492
|
+
"/api/vision/detect/zero-shot",
|
|
1493
|
+
params,
|
|
1494
|
+
"vision",
|
|
1495
|
+
poll
|
|
1496
|
+
);
|
|
1497
|
+
}
|
|
1498
|
+
zeroShotJob(params, signal) {
|
|
1499
|
+
return this.submitQueued(
|
|
1500
|
+
"/api/vision/detect/zero-shot",
|
|
1501
|
+
params,
|
|
1502
|
+
"vision",
|
|
1503
|
+
signal
|
|
1504
|
+
);
|
|
1505
|
+
}
|
|
1506
|
+
faces(params, poll) {
|
|
1507
|
+
return this.runQueued("/api/vision/detect/faces", params, "vision", poll);
|
|
1508
|
+
}
|
|
1509
|
+
facesJob(params, signal) {
|
|
1510
|
+
return this.submitQueued(
|
|
1511
|
+
"/api/vision/detect/faces",
|
|
1512
|
+
params,
|
|
1513
|
+
"vision",
|
|
1514
|
+
signal
|
|
1515
|
+
);
|
|
1516
|
+
}
|
|
1517
|
+
web(params, poll) {
|
|
1518
|
+
return this.runQueued("/api/vision/detect/web", params, "vision", poll);
|
|
1519
|
+
}
|
|
1520
|
+
webJob(params, signal) {
|
|
1521
|
+
return this.submitQueued("/api/vision/detect/web", params, "vision", signal);
|
|
1522
|
+
}
|
|
1523
|
+
autoLabel(params, poll) {
|
|
1524
|
+
return this.runQueued("/api/vision/auto-label", params, "vision", poll);
|
|
1525
|
+
}
|
|
1526
|
+
autoLabelJob(params, signal) {
|
|
1527
|
+
return this.submitQueued("/api/vision/auto-label", params, "vision", signal);
|
|
1528
|
+
}
|
|
1529
|
+
autoTrain(params, poll) {
|
|
1530
|
+
return this.runQueued("/api/vision/auto-train", params, "vision", poll);
|
|
1531
|
+
}
|
|
1532
|
+
autoTrainJob(params, signal) {
|
|
1533
|
+
return this.submitQueued("/api/vision/auto-train", params, "vision", signal);
|
|
1534
|
+
}
|
|
1535
|
+
/** Start a dataset training run (synchronous; returns a job record). */
|
|
1536
|
+
train(params, signal) {
|
|
1537
|
+
return this.transport.json("POST", "/api/vision/train", {
|
|
1538
|
+
body: params,
|
|
1539
|
+
signal,
|
|
1540
|
+
retry: "submit"
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1543
|
+
getTraining(jobId, signal) {
|
|
1544
|
+
return this.transport.json(
|
|
1545
|
+
"GET",
|
|
1546
|
+
`/api/vision/train/${encodeURIComponent(jobId)}`,
|
|
1547
|
+
{ signal }
|
|
1548
|
+
);
|
|
1549
|
+
}
|
|
1550
|
+
listTraining(signal) {
|
|
1551
|
+
return this.transport.json("GET", "/api/vision/train", {
|
|
1552
|
+
signal
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
models(signal) {
|
|
1556
|
+
return this.transport.json("GET", "/api/vision/models", {
|
|
1557
|
+
signal
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
deleteModel(modelId, signal) {
|
|
1561
|
+
return this.transport.json(
|
|
1562
|
+
"DELETE",
|
|
1563
|
+
`/api/vision/models/${encodeURIComponent(modelId)}`,
|
|
1564
|
+
{ signal }
|
|
1565
|
+
);
|
|
1566
|
+
}
|
|
1567
|
+
};
|
|
1568
|
+
|
|
1569
|
+
// src/client.ts
|
|
1570
|
+
var DEFAULT_BASE_URL = "https://api.ai.canarycoders.es";
|
|
1571
|
+
function readEnv(key) {
|
|
1572
|
+
if (typeof process !== "undefined" && process.env) return process.env[key];
|
|
1573
|
+
return void 0;
|
|
1574
|
+
}
|
|
1575
|
+
var CanaryLLM = class {
|
|
1576
|
+
chat;
|
|
1577
|
+
queue;
|
|
1578
|
+
images;
|
|
1579
|
+
video;
|
|
1580
|
+
audio;
|
|
1581
|
+
embeddings;
|
|
1582
|
+
vision;
|
|
1583
|
+
conversations;
|
|
1584
|
+
agents;
|
|
1585
|
+
realtime;
|
|
1586
|
+
discovery;
|
|
1587
|
+
usage;
|
|
1588
|
+
portal;
|
|
1589
|
+
keys;
|
|
1590
|
+
public;
|
|
1591
|
+
transport;
|
|
1592
|
+
baseURL;
|
|
1593
|
+
apiKey;
|
|
1594
|
+
constructor(options = {}) {
|
|
1595
|
+
this.apiKey = options.apiKey ?? readEnv("CANARY_AI_API_KEY") ?? readEnv("CANARYLLM_API_KEY");
|
|
1596
|
+
this.baseURL = options.baseURL ?? readEnv("CANARY_AI_BASE_URL") ?? readEnv("CANARYLLM_BASE_URL") ?? DEFAULT_BASE_URL;
|
|
1597
|
+
this.transport = new Transport({
|
|
1598
|
+
apiKey: this.apiKey,
|
|
1599
|
+
baseURL: this.baseURL,
|
|
1600
|
+
authStyle: options.authStyle ?? "bearer",
|
|
1601
|
+
timeoutMs: options.timeoutMs ?? 6e4,
|
|
1602
|
+
maxRetries: options.maxRetries ?? 2,
|
|
1603
|
+
fetch: options.fetch,
|
|
1604
|
+
defaultHeaders: options.defaultHeaders,
|
|
1605
|
+
defaultTag: options.defaultTag
|
|
1606
|
+
});
|
|
1607
|
+
const poll = options.poll;
|
|
1608
|
+
this.chat = new ChatResource(this.transport, poll);
|
|
1609
|
+
this.queue = new QueueResource(this.transport, poll);
|
|
1610
|
+
this.images = new ImagesResource(this.transport, poll);
|
|
1611
|
+
this.video = new VideoResource(this.transport, poll);
|
|
1612
|
+
this.audio = new AudioResource(this.transport, poll);
|
|
1613
|
+
this.embeddings = new EmbeddingsResource(this.transport, poll);
|
|
1614
|
+
this.vision = new VisionResource(this.transport, poll);
|
|
1615
|
+
this.conversations = new ConversationsResource(this.transport, poll);
|
|
1616
|
+
this.agents = new AgentsResource(this.transport, poll);
|
|
1617
|
+
this.realtime = new RealtimeResource(this.transport, poll);
|
|
1618
|
+
this.discovery = new DiscoveryResource(this.transport, poll);
|
|
1619
|
+
this.usage = new UsageResource(this.transport, poll);
|
|
1620
|
+
this.portal = new PortalResource(this.transport, poll);
|
|
1621
|
+
this.keys = new KeysResource(this.transport, poll);
|
|
1622
|
+
this.public = new PublicResource(this.transport, poll);
|
|
1623
|
+
}
|
|
1624
|
+
/** Targets to plug into the official `openai` / `@anthropic-ai/sdk` clients. */
|
|
1625
|
+
get compat() {
|
|
1626
|
+
return {
|
|
1627
|
+
openai: () => openaiTarget(this.baseURL, this.apiKey ?? ""),
|
|
1628
|
+
anthropic: () => anthropicTarget(this.baseURL, this.apiKey ?? "")
|
|
1629
|
+
};
|
|
1630
|
+
}
|
|
1631
|
+
};
|
|
1632
|
+
|
|
1633
|
+
// src/index.ts
|
|
1634
|
+
var src_default = CanaryLLM;
|
|
1635
|
+
|
|
1636
|
+
export { APIConnectionError, APIConnectionTimeoutError, APIError, AuthenticationError, BadRequestError, CanaryLLM, ConflictError, InternalServerError, Job, NotFoundError, PermissionError, RateLimitError, UnprocessableEntityError, anthropicTarget, src_default as default, openaiTarget, toBrokeredCredential };
|
|
1637
|
+
//# sourceMappingURL=index.js.map
|
|
1638
|
+
//# sourceMappingURL=index.js.map
|