@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/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