@zorveus/sdk 0.1.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.mjs ADDED
@@ -0,0 +1,1158 @@
1
+ // src/http/headers.ts
2
+ function buildHeaders(options) {
3
+ const headers = {
4
+ Accept: "application/json"
5
+ };
6
+ if (typeof window === "undefined") {
7
+ headers["User-Agent"] = "@zorveus/sdk/0.1.0";
8
+ }
9
+ if (options.contentType) {
10
+ headers["Content-Type"] = options.contentType;
11
+ }
12
+ if (options.apiKey) {
13
+ headers["Authorization"] = `Bearer ${options.apiKey}`;
14
+ }
15
+ if (options.defaultHeaders) {
16
+ Object.assign(headers, options.defaultHeaders);
17
+ }
18
+ if (options.customHeaders) {
19
+ Object.assign(headers, options.customHeaders);
20
+ }
21
+ return headers;
22
+ }
23
+
24
+ // src/errors/zorveus-error.ts
25
+ var ZorveusError = class extends Error {
26
+ status;
27
+ code;
28
+ param;
29
+ type;
30
+ headers;
31
+ rawBody;
32
+ constructor(message, options = {}) {
33
+ super(message);
34
+ this.name = "ZorveusError";
35
+ this.status = options.status;
36
+ this.code = options.code;
37
+ this.param = options.param;
38
+ this.type = options.type;
39
+ this.headers = options.headers;
40
+ this.rawBody = options.rawBody;
41
+ if (options.cause) {
42
+ this.cause = options.cause;
43
+ }
44
+ Object.setPrototypeOf(this, new.target.prototype);
45
+ }
46
+ };
47
+ var APIConnectionError = class extends ZorveusError {
48
+ constructor(message = "Connection to Zorveus API failed", options = {}) {
49
+ super(message, options);
50
+ this.name = "APIConnectionError";
51
+ if (options.cause) {
52
+ this.cause = options.cause;
53
+ }
54
+ }
55
+ };
56
+ var APIStatusError = class extends ZorveusError {
57
+ constructor(message, options) {
58
+ super(message, options);
59
+ this.name = "APIStatusError";
60
+ }
61
+ };
62
+ var AuthenticationError = class extends APIStatusError {
63
+ constructor(message = "Invalid or expired Zorveus credentials", options = {}) {
64
+ super(message, { ...options, status: options.status ?? 401 });
65
+ this.name = "AuthenticationError";
66
+ }
67
+ };
68
+ var PermissionDeniedError = class extends APIStatusError {
69
+ constructor(message = "Permission denied for this operation or model", options = {}) {
70
+ super(message, { ...options, status: options.status ?? 403 });
71
+ this.name = "PermissionDeniedError";
72
+ }
73
+ };
74
+ var NotFoundError = class extends APIStatusError {
75
+ constructor(message = "Resource not found", options = {}) {
76
+ super(message, { ...options, status: options.status ?? 404 });
77
+ this.name = "NotFoundError";
78
+ }
79
+ };
80
+ var UnprocessableEntityError = class extends APIStatusError {
81
+ constructor(message = "Request validation failed", options = {}) {
82
+ super(message, { ...options, status: options.status ?? 422 });
83
+ this.name = "UnprocessableEntityError";
84
+ }
85
+ };
86
+ var RateLimitError = class extends APIStatusError {
87
+ constructor(message = "Rate limit exceeded. Please retry after some time.", options = {}) {
88
+ super(message, { ...options, status: options.status ?? 429 });
89
+ this.name = "RateLimitError";
90
+ }
91
+ };
92
+ var InternalServerError = class extends APIStatusError {
93
+ constructor(message = "Zorveus internal server error", options = {}) {
94
+ super(message, { ...options, status: options.status ?? 500 });
95
+ this.name = "InternalServerError";
96
+ }
97
+ };
98
+ var ZorveusBusinessError = class extends APIStatusError {
99
+ constructor(message, options) {
100
+ super(message, options);
101
+ this.name = "ZorveusBusinessError";
102
+ }
103
+ };
104
+ var InsufficientFundsError = class extends ZorveusBusinessError {
105
+ constructor(message = "Wallet balance exhausted. Top up required.", options = {}) {
106
+ super(message, { ...options, status: options.status ?? 402, code: options.code ?? "insufficient_funds" });
107
+ this.name = "InsufficientFundsError";
108
+ }
109
+ };
110
+ var CapExceededError = class extends ZorveusBusinessError {
111
+ constructor(message = "Spending cap limit reached", options = {}) {
112
+ super(message, { ...options, status: options.status ?? 402, code: options.code ?? "cap_exceeded" });
113
+ this.name = "CapExceededError";
114
+ }
115
+ };
116
+ var CreditGrantExpiredError = class extends ZorveusBusinessError {
117
+ constructor(message = "Product user credit grant has expired", options = {}) {
118
+ super(message, { ...options, status: options.status ?? 403, code: options.code ?? "credit_grant_expired" });
119
+ this.name = "CreditGrantExpiredError";
120
+ }
121
+ };
122
+ function createAPIError(status, body, headers) {
123
+ let message = `Request failed with status ${status}`;
124
+ let code;
125
+ let param;
126
+ let type;
127
+ let parsedBody = body;
128
+ if (typeof body === "string") {
129
+ try {
130
+ parsedBody = JSON.parse(body);
131
+ } catch {
132
+ try {
133
+ parsedBody = JSON.parse(body.replace(/'/g, '"'));
134
+ } catch {
135
+ const codeMatch = body.match(/['"]code['"]\s*:\s*['"]([^'"]+)['"]/);
136
+ const msgMatch = body.match(/['"]message['"]\s*:\s*['"]([^'"]+)['"]/);
137
+ if (msgMatch?.[1]) message = msgMatch[1];
138
+ if (codeMatch?.[1]) code = codeMatch[1];
139
+ }
140
+ }
141
+ }
142
+ if (parsedBody && typeof parsedBody === "object") {
143
+ const obj = parsedBody;
144
+ if (obj.error && typeof obj.error === "object") {
145
+ const err = obj.error;
146
+ if (typeof err.message === "string") message = err.message;
147
+ if (typeof err.code === "string") code = err.code;
148
+ if (typeof err.param === "string") param = err.param;
149
+ if (typeof err.type === "string") type = err.type;
150
+ } else if (typeof obj.detail === "string") {
151
+ message = obj.detail;
152
+ } else if (Array.isArray(obj.detail) && obj.detail.length > 0) {
153
+ const first = obj.detail[0];
154
+ if (first && typeof first.msg === "string") {
155
+ message = first.msg;
156
+ }
157
+ } else if (typeof obj.message === "string") {
158
+ message = obj.message;
159
+ }
160
+ }
161
+ const options = { status, code, param, type, headers, rawBody: body };
162
+ const normalizedCode = (code || "").toLowerCase();
163
+ if (normalizedCode.includes("cap_exceed") || normalizedCode.includes("spend_cap") || message.toLowerCase().includes("spending cap")) {
164
+ return new CapExceededError(message, options);
165
+ }
166
+ if (normalizedCode.includes("insufficient_funds") || normalizedCode.includes("balance_exhausted") || normalizedCode.includes("insufficient_balance") || normalizedCode.includes("wallet_empty") || message.toLowerCase().includes("insufficient funds") || message.toLowerCase().includes("balance exhausted") || message.toLowerCase().includes("wallet is empty")) {
167
+ return new InsufficientFundsError(message, options);
168
+ }
169
+ if (normalizedCode.includes("grant_expired")) {
170
+ return new CreditGrantExpiredError(message, options);
171
+ }
172
+ if (status === 401) {
173
+ return new AuthenticationError(message, options);
174
+ }
175
+ if (status === 402) {
176
+ return new InsufficientFundsError(message, options);
177
+ }
178
+ if (status === 403) {
179
+ return new PermissionDeniedError(message, options);
180
+ }
181
+ if (status === 404) {
182
+ return new NotFoundError(message, options);
183
+ }
184
+ if (status === 422) {
185
+ return new UnprocessableEntityError(message, options);
186
+ }
187
+ if (status === 429) {
188
+ return new RateLimitError(message, options);
189
+ }
190
+ if (status >= 500) {
191
+ return new InternalServerError(message, options);
192
+ }
193
+ return new APIStatusError(message, options);
194
+ }
195
+
196
+ // src/http/transport.ts
197
+ var HTTPTransport = class {
198
+ apiKey;
199
+ baseURL;
200
+ gatewayBaseURL;
201
+ timeout;
202
+ maxRetries;
203
+ defaultHeaders;
204
+ fetchFn;
205
+ constructor(options) {
206
+ if (!options || !options.apiKey) {
207
+ throw new Error("HTTPTransport initialized without an apiKey.");
208
+ }
209
+ this.apiKey = options.apiKey;
210
+ this.baseURL = (options.baseURL || "https://api.zorveus.com").replace(/\/+$/, "");
211
+ this.gatewayBaseURL = (options.gatewayBaseURL || `${this.baseURL}/v1`).replace(/\/+$/, "");
212
+ this.timeout = options.timeout ?? 6e4;
213
+ this.maxRetries = options.maxRetries ?? 2;
214
+ this.defaultHeaders = options.defaultHeaders || {};
215
+ this.fetchFn = options.fetch || globalThis.fetch.bind(globalThis);
216
+ }
217
+ /**
218
+ * Executes an HTTP request with timeout, strict idempotency-aware retries, and error mapping.
219
+ */
220
+ async request(path, options = {}) {
221
+ const method = options.method || "GET";
222
+ const isGateway = options.isGateway ?? false;
223
+ const rootUrl = isGateway ? this.gatewayBaseURL : this.baseURL;
224
+ const url = this.buildUrl(rootUrl, path, options.query);
225
+ const isJsonBody = options.body !== void 0 && !(options.body instanceof FormData);
226
+ const contentType = isJsonBody ? "application/json" : void 0;
227
+ const customHeaders = { ...options.headers };
228
+ if (options.idempotencyKey) {
229
+ customHeaders["X-Idempotency-Key"] = options.idempotencyKey;
230
+ }
231
+ const headers = buildHeaders({
232
+ apiKey: this.apiKey,
233
+ defaultHeaders: this.defaultHeaders,
234
+ customHeaders,
235
+ contentType
236
+ });
237
+ const isSafeMethod = method === "GET" || method === "HEAD" || method === "OPTIONS";
238
+ const isIdempotent = isSafeMethod || Boolean(options.isIdempotent || options.idempotencyKey);
239
+ const maxRetries = isIdempotent ? options.maxRetries ?? this.maxRetries : 0;
240
+ const timeoutMs = options.timeout ?? this.timeout;
241
+ if (options.signal?.aborted) {
242
+ throw new APIConnectionError("Request was cancelled by user");
243
+ }
244
+ let attempt = 0;
245
+ while (true) {
246
+ const controller = new AbortController();
247
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
248
+ const onAbort = () => controller.abort();
249
+ if (options.signal) {
250
+ options.signal.addEventListener("abort", onAbort, { once: true });
251
+ }
252
+ try {
253
+ const bodyContent = isJsonBody ? JSON.stringify(options.body) : options.body;
254
+ const response = await this.fetchFn(url.toString(), {
255
+ method,
256
+ headers,
257
+ body: bodyContent,
258
+ signal: controller.signal
259
+ });
260
+ if (options.stream) {
261
+ if (!response.ok) {
262
+ const errorBody = await this.parseResponseBody(response);
263
+ throw createAPIError(response.status, errorBody, this.extractHeaders(response.headers));
264
+ }
265
+ if (!response.body) {
266
+ throw new APIConnectionError("Streaming response body is null");
267
+ }
268
+ return response.body;
269
+ }
270
+ if (!response.ok) {
271
+ const errorBody = await this.parseResponseBody(response);
272
+ const shouldRetry = isIdempotent && this.shouldRetryStatus(response.status) && attempt < maxRetries;
273
+ if (shouldRetry) {
274
+ attempt++;
275
+ const delay = this.calculateRetryDelay(attempt, response.headers.get("retry-after"));
276
+ await this.sleep(delay);
277
+ continue;
278
+ }
279
+ throw createAPIError(response.status, errorBody, this.extractHeaders(response.headers));
280
+ }
281
+ if (response.status === 204) {
282
+ return void 0;
283
+ }
284
+ const data = await this.parseResponseBody(response);
285
+ return data;
286
+ } catch (error) {
287
+ if (error && typeof error === "object" && "status" in error) {
288
+ throw error;
289
+ }
290
+ const isAbortError = error instanceof Error && error.name === "AbortError";
291
+ const wasUserAborted = options.signal?.aborted;
292
+ if (isAbortError && wasUserAborted) {
293
+ throw new APIConnectionError("Request was cancelled by user", { cause: error });
294
+ }
295
+ const isTimeout = isAbortError && !wasUserAborted;
296
+ const errorMessage = isTimeout ? `Request timed out after ${timeoutMs}ms` : "Network request failed";
297
+ if (isIdempotent && attempt < maxRetries) {
298
+ attempt++;
299
+ const delay = this.calculateRetryDelay(attempt);
300
+ await this.sleep(delay);
301
+ continue;
302
+ }
303
+ throw new APIConnectionError(errorMessage, { cause: error });
304
+ } finally {
305
+ clearTimeout(timeoutId);
306
+ if (options.signal) {
307
+ options.signal.removeEventListener("abort", onAbort);
308
+ }
309
+ }
310
+ }
311
+ }
312
+ buildUrl(base, path, query) {
313
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
314
+ const url = new URL(`${base}${normalizedPath}`);
315
+ if (query) {
316
+ for (const [key, value] of Object.entries(query)) {
317
+ if (value === void 0 || value === null) {
318
+ continue;
319
+ }
320
+ if (Array.isArray(value)) {
321
+ for (const item of value) {
322
+ if (item !== void 0 && item !== null) {
323
+ url.searchParams.append(key, String(item));
324
+ }
325
+ }
326
+ } else {
327
+ url.searchParams.append(key, String(value));
328
+ }
329
+ }
330
+ }
331
+ return url;
332
+ }
333
+ async parseResponseBody(response) {
334
+ const text = await response.text();
335
+ if (!text) {
336
+ return null;
337
+ }
338
+ try {
339
+ return JSON.parse(text);
340
+ } catch {
341
+ return text;
342
+ }
343
+ }
344
+ extractHeaders(headers) {
345
+ const result = {};
346
+ headers.forEach((value, key) => {
347
+ result[key] = value;
348
+ });
349
+ return result;
350
+ }
351
+ shouldRetryStatus(status) {
352
+ return status === 429 || status === 500 || status === 502 || status === 503 || status === 504;
353
+ }
354
+ calculateRetryDelay(attempt, retryAfterHeader) {
355
+ if (retryAfterHeader) {
356
+ const parsedSeconds = parseInt(retryAfterHeader, 10);
357
+ if (!isNaN(parsedSeconds) && parsedSeconds > 0) {
358
+ return parsedSeconds * 1e3;
359
+ }
360
+ const parsedDate = Date.parse(retryAfterHeader);
361
+ if (!isNaN(parsedDate)) {
362
+ const diff = parsedDate - Date.now();
363
+ if (diff > 0) {
364
+ return diff;
365
+ }
366
+ }
367
+ }
368
+ const baseDelay = 500;
369
+ const maxBackoff = Math.min(1e4, baseDelay * Math.pow(2, Math.max(0, attempt - 1)));
370
+ const jitter = Math.floor(Math.random() * maxBackoff);
371
+ return Math.max(baseDelay, jitter);
372
+ }
373
+ sleep(ms) {
374
+ return new Promise((resolve) => setTimeout(resolve, ms));
375
+ }
376
+ };
377
+
378
+ // src/http/sse.ts
379
+ async function* parseSSEStream(stream) {
380
+ const reader = stream.getReader();
381
+ const decoder = new TextDecoder("utf-8");
382
+ let buffer = "";
383
+ try {
384
+ while (true) {
385
+ const { done, value } = await reader.read();
386
+ if (done) {
387
+ break;
388
+ }
389
+ buffer += decoder.decode(value, { stream: true });
390
+ const lines = buffer.split(/\r?\n/);
391
+ buffer = lines.pop() ?? "";
392
+ for (const line of lines) {
393
+ const trimmed = line.trim();
394
+ if (!trimmed || trimmed.startsWith(":")) {
395
+ continue;
396
+ }
397
+ if (trimmed.startsWith("data:")) {
398
+ const dataContent = trimmed.slice(5).trim();
399
+ if (dataContent === "[DONE]") {
400
+ return;
401
+ }
402
+ try {
403
+ const parsed = JSON.parse(dataContent);
404
+ yield parsed;
405
+ } catch {
406
+ }
407
+ }
408
+ }
409
+ }
410
+ if (buffer.trim()) {
411
+ const trimmed = buffer.trim();
412
+ if (trimmed.startsWith("data:")) {
413
+ const dataContent = trimmed.slice(5).trim();
414
+ if (dataContent !== "[DONE]") {
415
+ try {
416
+ const parsed = JSON.parse(dataContent);
417
+ yield parsed;
418
+ } catch {
419
+ }
420
+ }
421
+ }
422
+ }
423
+ } finally {
424
+ try {
425
+ await reader.cancel();
426
+ } catch {
427
+ }
428
+ reader.releaseLock();
429
+ }
430
+ }
431
+
432
+ // src/types/chat.ts
433
+ function formatGatewayMetadata(meta) {
434
+ if (!meta) return void 0;
435
+ const result = {};
436
+ if (meta.externalUserId) {
437
+ result.external_user_id = meta.externalUserId;
438
+ }
439
+ if (meta.displayName !== void 0 || meta.userEmail !== void 0 || meta.metadata !== void 0) {
440
+ result.product_user = {
441
+ display_name: meta.displayName ?? null,
442
+ email: meta.userEmail ?? null,
443
+ metadata: meta.metadata ?? null
444
+ };
445
+ }
446
+ return Object.keys(result).length > 0 ? result : void 0;
447
+ }
448
+
449
+ // src/resources/chat/completions.ts
450
+ var Completions = class {
451
+ transport;
452
+ constructor(transport) {
453
+ this.transport = transport;
454
+ }
455
+ async create(params, options = {}) {
456
+ const isStreaming = Boolean(params.stream);
457
+ const { zorveusMetadata, metadata: explicitMetadata, ...requestBody } = params;
458
+ const gatewayMetadata = formatGatewayMetadata(zorveusMetadata);
459
+ const combinedMetadata = explicitMetadata ? { ...gatewayMetadata, ...explicitMetadata } : gatewayMetadata;
460
+ const payload = {
461
+ ...requestBody,
462
+ ...combinedMetadata ? { metadata: combinedMetadata } : {}
463
+ };
464
+ if (isStreaming) {
465
+ const responseStream = await this.transport.request(
466
+ "/chat/completions",
467
+ {
468
+ method: "POST",
469
+ body: { ...payload, stream: true },
470
+ isGateway: true,
471
+ stream: true,
472
+ ...options
473
+ }
474
+ );
475
+ return parseSSEStream(responseStream);
476
+ }
477
+ return this.transport.request("/chat/completions", {
478
+ method: "POST",
479
+ body: payload,
480
+ isGateway: true,
481
+ stream: false,
482
+ ...options
483
+ });
484
+ }
485
+ };
486
+
487
+ // src/resources/chat/index.ts
488
+ var Chat = class {
489
+ completions;
490
+ constructor(transport) {
491
+ this.completions = new Completions(transport);
492
+ }
493
+ };
494
+
495
+ // src/resources/embeddings.ts
496
+ var Embeddings = class {
497
+ transport;
498
+ constructor(transport) {
499
+ this.transport = transport;
500
+ }
501
+ /**
502
+ * Creates an embedding vector representing the input text.
503
+ */
504
+ async create(params, options = {}) {
505
+ const { zorveusMetadata, ...requestBody } = params;
506
+ const gatewayMetadata = formatGatewayMetadata(zorveusMetadata);
507
+ const payload = {
508
+ ...requestBody,
509
+ ...gatewayMetadata ? { metadata: gatewayMetadata } : {}
510
+ };
511
+ return this.transport.request("/embeddings", {
512
+ method: "POST",
513
+ body: payload,
514
+ isGateway: true,
515
+ ...options
516
+ });
517
+ }
518
+ };
519
+
520
+ // src/resources/models.ts
521
+ var Models = class {
522
+ transport;
523
+ constructor(transport) {
524
+ this.transport = transport;
525
+ }
526
+ /**
527
+ * Lists available models on the Zorveus gateway.
528
+ */
529
+ async list(params = {}, options = {}) {
530
+ const query = {};
531
+ if (params.routeStatus) {
532
+ query.route_status = params.routeStatus;
533
+ }
534
+ return this.transport.request("/models", {
535
+ method: "GET",
536
+ query,
537
+ isGateway: true,
538
+ ...options
539
+ });
540
+ }
541
+ /**
542
+ * Retrieves information about a specific model.
543
+ */
544
+ async retrieve(modelId, options = {}) {
545
+ return this.transport.request(`/models/${encodeURIComponent(modelId)}`, {
546
+ method: "GET",
547
+ isGateway: true,
548
+ ...options
549
+ });
550
+ }
551
+ };
552
+
553
+ // src/client.ts
554
+ var ZorveusInferenceClient = class {
555
+ chat;
556
+ embeddings;
557
+ models;
558
+ transport;
559
+ constructor(options) {
560
+ if (!options || !options.apiKey) {
561
+ throw new Error(
562
+ "ZorveusInferenceClient requires an 'apiKey' (Inference Key or OAuth Access Token)."
563
+ );
564
+ }
565
+ const baseURL = options.baseURL ? options.baseURL.replace(/\/+$/, "") : options.gatewayBaseURL ? options.gatewayBaseURL.replace(/\/v1\/?$/, "").replace(/\/+$/, "") : "https://api.zorveus.com";
566
+ const gatewayBaseURL = (options.gatewayBaseURL || `${baseURL}/v1`).replace(/\/+$/, "");
567
+ this.transport = new HTTPTransport({
568
+ apiKey: options.apiKey,
569
+ baseURL,
570
+ gatewayBaseURL,
571
+ timeout: options.timeout,
572
+ maxRetries: options.maxRetries,
573
+ defaultHeaders: options.defaultHeaders,
574
+ fetch: options.fetch
575
+ });
576
+ this.chat = new Chat(this.transport);
577
+ this.embeddings = new Embeddings(this.transport);
578
+ this.models = new Models(this.transport);
579
+ }
580
+ /**
581
+ * Retrieves live spend, budget cap, and balance for the active inference key (`GET /inference-keys/usage`).
582
+ */
583
+ async getUsage(options = {}) {
584
+ return this.transport.request("/inference-keys/usage", {
585
+ method: "GET",
586
+ ...options
587
+ });
588
+ }
589
+ };
590
+ var Zorveus = class extends ZorveusInferenceClient {
591
+ };
592
+
593
+ // src/utils/decimal.ts
594
+ function isValidDecimalString(value) {
595
+ if (typeof value !== "string") {
596
+ return false;
597
+ }
598
+ const trimmed = value.trim();
599
+ if (!trimmed) {
600
+ return false;
601
+ }
602
+ const decimalRegex = /^-?\d+(\.\d+)?$/;
603
+ return decimalRegex.test(trimmed);
604
+ }
605
+ function assertDecimalString(value, fieldName) {
606
+ if (typeof value === "number") {
607
+ return value.toFixed(4);
608
+ }
609
+ if (isValidDecimalString(value)) {
610
+ return value.trim();
611
+ }
612
+ throw new TypeError(
613
+ `Field '${fieldName}' must be a valid decimal string (e.g. "15.0000"), received: ${JSON.stringify(value)}`
614
+ );
615
+ }
616
+
617
+ // src/resources/product-users.ts
618
+ var ProductUsers = class {
619
+ transport;
620
+ constructor(transport) {
621
+ this.transport = transport;
622
+ }
623
+ /**
624
+ * Upserts a product user by external user ID (`PUT /product-users/by-external-id`).
625
+ * Creates the user if they do not exist, or updates their profile if they do.
626
+ */
627
+ async createOrUpdate(params, options = {}) {
628
+ const payload = {
629
+ ...params.appId ? { app_id: params.appId } : {},
630
+ external_user_id: params.externalUserId,
631
+ display_name: params.displayName ?? null,
632
+ email: params.email ?? null,
633
+ metadata: params.metadata ?? null
634
+ };
635
+ const query = params.orgId ? { org_id: params.orgId } : void 0;
636
+ return this.transport.request("/product-users/by-external-id", {
637
+ method: "PUT",
638
+ body: payload,
639
+ query,
640
+ ...options
641
+ });
642
+ }
643
+ /**
644
+ * Alias for `createOrUpdate` (`PUT /product-users/by-external-id`).
645
+ */
646
+ async upsert(params, options = {}) {
647
+ return this.createOrUpdate(params, options);
648
+ }
649
+ /**
650
+ * Retrieves a single product end-user by ID (`GET /product-users/{product_end_user_id}`).
651
+ */
652
+ async get(productEndUserId, options = {}) {
653
+ return this.transport.request(
654
+ `/product-users/${encodeURIComponent(productEndUserId)}`,
655
+ {
656
+ method: "GET",
657
+ ...options
658
+ }
659
+ );
660
+ }
661
+ /**
662
+ * Retrieves a product user profile by external ID (`GET /product-users/by-external-id`).
663
+ * Returns complete profile with usage, active cap, and live credits.
664
+ */
665
+ async getByExternalId(params, options = {}) {
666
+ const query = {
667
+ app_id: params.appId,
668
+ external_user_id: params.externalUserId
669
+ };
670
+ if (params.orgId) {
671
+ query.org_id = params.orgId;
672
+ }
673
+ return this.transport.request("/product-users/by-external-id", {
674
+ method: "GET",
675
+ query,
676
+ ...options
677
+ });
678
+ }
679
+ /**
680
+ * Retrieves a product user's live credit summary by external ID (`GET /product-users/by-external-id/credit-summary`).
681
+ */
682
+ async getCreditSummaryByExternalId(params, options = {}) {
683
+ const query = {
684
+ app_id: params.appId,
685
+ external_user_id: params.externalUserId
686
+ };
687
+ if (params.currency) {
688
+ query.currency = params.currency;
689
+ }
690
+ if (params.orgId) {
691
+ query.org_id = params.orgId;
692
+ }
693
+ return this.transport.request(
694
+ "/product-users/by-external-id/credit-summary",
695
+ {
696
+ method: "GET",
697
+ query,
698
+ ...options
699
+ }
700
+ );
701
+ }
702
+ /**
703
+ * Lists product end-users for an organization (`GET /product-users`).
704
+ */
705
+ async list(params = {}, options = {}) {
706
+ const query = {};
707
+ if (params.orgId) query.org_id = params.orgId;
708
+ if (params.limit !== void 0) query.limit = params.limit;
709
+ if (params.offset !== void 0) query.offset = params.offset;
710
+ return this.transport.request("/product-users", {
711
+ method: "GET",
712
+ query,
713
+ ...options
714
+ });
715
+ }
716
+ /**
717
+ * Grants startup-funded AI credits to a product user (`POST /product-users/{id}/credit-grants`).
718
+ * Validates amount as a strict financial decimal string.
719
+ */
720
+ async grantCredit(productEndUserId, params, options = {}) {
721
+ const amount = assertDecimalString(params.amount, "grantCredit.amount");
722
+ const payload = {
723
+ app_id: params.appId,
724
+ amount,
725
+ currency: params.currency || "USD",
726
+ reason: params.reason ?? null,
727
+ expires_at: params.expiresAt ?? null,
728
+ metadata: params.metadata ?? null
729
+ };
730
+ const query = params.orgId ? { org_id: params.orgId } : void 0;
731
+ return this.transport.request(
732
+ `/product-users/${encodeURIComponent(productEndUserId)}/credit-grants`,
733
+ {
734
+ method: "POST",
735
+ body: payload,
736
+ query,
737
+ ...options
738
+ }
739
+ );
740
+ }
741
+ /**
742
+ * Grants credits to an end user by external ID (`POST /product-users/by-external-id/credit-grants`).
743
+ * Automatically provisions the product user if they do not exist yet.
744
+ */
745
+ async grantCreditByExternalId(params, options = {}) {
746
+ const amount = assertDecimalString(params.amount, "grantCreditByExternalId.amount");
747
+ const payload = {
748
+ app_id: params.appId,
749
+ external_user_id: params.externalUserId,
750
+ display_name: params.displayName ?? null,
751
+ email: params.email ?? null,
752
+ amount,
753
+ currency: params.currency || "USD",
754
+ source: params.source ?? null,
755
+ reason: params.reason ?? null,
756
+ expires_at: params.expiresAt ?? null,
757
+ metadata: params.metadata ?? null
758
+ };
759
+ const query = params.orgId ? { org_id: params.orgId } : void 0;
760
+ return this.transport.request(
761
+ "/product-users/by-external-id/credit-grants",
762
+ {
763
+ method: "POST",
764
+ body: payload,
765
+ query,
766
+ ...options
767
+ }
768
+ );
769
+ }
770
+ /**
771
+ * Lists credit grants for a product user (`GET /product-users/{id}/credit-grants`).
772
+ * Accepts either an external user ID or a Zorveus product user ID.
773
+ */
774
+ async listCreditGrants(userIdentifier, params = {}, options = {}) {
775
+ const query = {};
776
+ if (params.appId) query.app_id = params.appId;
777
+ if (params.orgId) query.org_id = params.orgId;
778
+ if (params.limit !== void 0) query.limit = params.limit;
779
+ if (params.offset !== void 0) query.offset = params.offset;
780
+ return this.transport.request(
781
+ `/product-users/${encodeURIComponent(userIdentifier)}/credit-grants`,
782
+ {
783
+ method: "GET",
784
+ query,
785
+ ...options
786
+ }
787
+ );
788
+ }
789
+ /**
790
+ * Lists credit grants for a product user by external ID (`GET /product-users/by-external-id/credit-grants`).
791
+ */
792
+ async listCreditGrantsByExternalId(params, options = {}) {
793
+ const query = {
794
+ app_id: params.appId,
795
+ external_user_id: params.externalUserId
796
+ };
797
+ if (params.status) query.status = params.status;
798
+ if (params.source) query.source = params.source;
799
+ if (params.limit !== void 0) query.limit = params.limit;
800
+ if (params.orgId) query.org_id = params.orgId;
801
+ return this.transport.request(
802
+ "/product-users/by-external-id/credit-grants",
803
+ {
804
+ method: "GET",
805
+ query,
806
+ ...options
807
+ }
808
+ );
809
+ }
810
+ /**
811
+ * Revokes an active credit grant (`POST /product-users/{id}/credit-grants/{grantId}/revoke`).
812
+ */
813
+ async revokeCredit(productEndUserId, creditGrantId, options = {}) {
814
+ return this.transport.request(
815
+ `/product-users/${encodeURIComponent(productEndUserId)}/credit-grants/${encodeURIComponent(creditGrantId)}/revoke`,
816
+ {
817
+ method: "POST",
818
+ ...options
819
+ }
820
+ );
821
+ }
822
+ };
823
+
824
+ // src/resources/provider-credentials.ts
825
+ var ProviderCredentials = class {
826
+ transport;
827
+ constructor(transport) {
828
+ this.transport = transport;
829
+ }
830
+ /**
831
+ * Registers an organization BYOK provider credential via Service Key (`POST /provider-credentials/org-programmatic`).
832
+ */
833
+ async create(params, options = {}) {
834
+ const payload = {
835
+ provider: params.provider,
836
+ credential_name: params.credentialName,
837
+ api_key: params.apiKey,
838
+ secret_kind: params.secretKind || "api_key",
839
+ model_policies: params.modelPolicies || [],
840
+ provider_config: params.providerConfig ?? null,
841
+ routing_mode: params.routingMode || "auto_resolve",
842
+ routing_priority: params.routingPriority ?? 100
843
+ };
844
+ const query = params.orgId ? { org_id: params.orgId } : void 0;
845
+ return this.transport.request(
846
+ "/provider-credentials/org-programmatic",
847
+ {
848
+ method: "POST",
849
+ body: payload,
850
+ query,
851
+ ...options
852
+ }
853
+ );
854
+ }
855
+ /**
856
+ * Lists BYOK provider credentials for an organization (`GET /provider-credentials/org-programmatic`).
857
+ */
858
+ async list(params = {}, options = {}) {
859
+ const query = {};
860
+ if (params.orgId) query.org_id = params.orgId;
861
+ if (params.status) query.status = params.status;
862
+ return this.transport.request(
863
+ "/provider-credentials/org-programmatic",
864
+ {
865
+ method: "GET",
866
+ query,
867
+ ...options
868
+ }
869
+ );
870
+ }
871
+ /**
872
+ * Rotates a provider credential secret (`POST /provider-credentials/org-programmatic/{id}/rotate`).
873
+ */
874
+ async rotate(providerCredentialId, params, options = {}) {
875
+ const payload = {
876
+ api_key: params.apiKey,
877
+ secret_kind: params.secretKind || "api_key"
878
+ };
879
+ const query = params.orgId ? { org_id: params.orgId } : void 0;
880
+ return this.transport.request(
881
+ `/provider-credentials/org-programmatic/${encodeURIComponent(providerCredentialId)}/rotate`,
882
+ {
883
+ method: "POST",
884
+ body: payload,
885
+ query,
886
+ ...options
887
+ }
888
+ );
889
+ }
890
+ /**
891
+ * Deletes a provider credential (`DELETE /provider-credentials/org-programmatic/{id}`).
892
+ */
893
+ async delete(providerCredentialId, options = {}) {
894
+ return this.transport.request(
895
+ `/provider-credentials/org-programmatic/${encodeURIComponent(providerCredentialId)}`,
896
+ {
897
+ method: "DELETE",
898
+ ...options
899
+ }
900
+ );
901
+ }
902
+ /**
903
+ * Lists supported AI provider catalog (`GET /provider-credentials/providers`).
904
+ */
905
+ async listProviders(options = {}) {
906
+ return this.transport.request(
907
+ "/provider-credentials/providers",
908
+ {
909
+ method: "GET",
910
+ ...options
911
+ }
912
+ );
913
+ }
914
+ };
915
+
916
+ // src/service-client.ts
917
+ var ZorveusServiceClient = class {
918
+ productUsers;
919
+ providerCredentials;
920
+ transport;
921
+ constructor(options) {
922
+ if (!options || !options.apiKey) {
923
+ throw new Error(
924
+ "ZorveusServiceClient requires an 'apiKey' (Organization Service Key 'zrv_service_...')."
925
+ );
926
+ }
927
+ const baseURL = (options.baseURL || "https://api.zorveus.com").replace(/\/+$/, "");
928
+ this.transport = new HTTPTransport({
929
+ apiKey: options.apiKey,
930
+ baseURL,
931
+ timeout: options.timeout,
932
+ maxRetries: options.maxRetries,
933
+ defaultHeaders: options.defaultHeaders,
934
+ fetch: options.fetch
935
+ });
936
+ this.productUsers = new ProductUsers(this.transport);
937
+ this.providerCredentials = new ProviderCredentials(this.transport);
938
+ }
939
+ };
940
+
941
+ // src/utils/crypto.ts
942
+ function getCrypto() {
943
+ if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
944
+ return globalThis.crypto;
945
+ }
946
+ throw new Error("Web Crypto API (crypto.subtle) is not available in the current environment.");
947
+ }
948
+ function base64UrlEncode(buffer) {
949
+ const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
950
+ let binary = "";
951
+ for (let i = 0; i < bytes.byteLength; i++) {
952
+ binary += String.fromCharCode(bytes[i]);
953
+ }
954
+ const base64 = typeof btoa === "function" ? btoa(binary) : Buffer.from(bytes).toString("base64");
955
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
956
+ }
957
+ function generateRandomString(byteLength = 32) {
958
+ const crypto = getCrypto();
959
+ const randomBytes = new Uint8Array(byteLength);
960
+ crypto.getRandomValues(randomBytes);
961
+ return base64UrlEncode(randomBytes);
962
+ }
963
+ async function sha256Base64Url(plainText) {
964
+ const crypto = getCrypto();
965
+ const encoder = new TextEncoder();
966
+ const data = encoder.encode(plainText);
967
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
968
+ return base64UrlEncode(hashBuffer);
969
+ }
970
+
971
+ // src/oauth.ts
972
+ var ZorveusOAuth = class {
973
+ /**
974
+ * Generates an RFC 7636 PKCE code_verifier, code_challenge (S256), and CSRF state parameter.
975
+ */
976
+ static async generatePKCE(byteLength = 32) {
977
+ const codeVerifier = generateRandomString(byteLength);
978
+ const codeChallenge = await sha256Base64Url(codeVerifier);
979
+ const state = generateRandomString(32);
980
+ return {
981
+ codeVerifier,
982
+ codeChallenge,
983
+ state
984
+ };
985
+ }
986
+ /**
987
+ * Constructs the Zorveus OAuth PKCE consent URL.
988
+ */
989
+ static getAuthorizationUrl(params) {
990
+ const baseUrl = (params.baseURL || "https://api.zorveus.com").replace(/\/+$/, "");
991
+ const url = new URL(`${baseUrl}/oauth/authorize`);
992
+ url.searchParams.set("client_id", params.clientId);
993
+ url.searchParams.set("redirect_uri", params.redirectUri);
994
+ url.searchParams.set("state", params.state);
995
+ url.searchParams.set("code_challenge", params.codeChallenge);
996
+ url.searchParams.set("code_challenge_method", "S256");
997
+ url.searchParams.set("response_type", "code");
998
+ const scopes = params.scopes ? Array.isArray(params.scopes) ? params.scopes.join(" ") : params.scopes : "inference:write models:*";
999
+ url.searchParams.set("scope", scopes);
1000
+ return url.toString();
1001
+ }
1002
+ /**
1003
+ * Validates OAuth redirect parameters against expected CSRF state.
1004
+ */
1005
+ static validateCallback(options) {
1006
+ let params;
1007
+ if (typeof options.urlOrParams === "string") {
1008
+ const urlStr = options.urlOrParams;
1009
+ const queryIdx = urlStr.indexOf("?");
1010
+ const search = queryIdx !== -1 ? urlStr.slice(queryIdx) : urlStr;
1011
+ params = new URLSearchParams(search);
1012
+ } else if (options.urlOrParams instanceof URLSearchParams) {
1013
+ params = options.urlOrParams;
1014
+ } else if (options.urlOrParams && typeof options.urlOrParams === "object") {
1015
+ params = new URLSearchParams();
1016
+ for (const [key, value] of Object.entries(options.urlOrParams)) {
1017
+ if (value !== void 0 && value !== null && value !== "undefined" && value !== "null") {
1018
+ params.set(key, String(value));
1019
+ }
1020
+ }
1021
+ } else {
1022
+ params = new URLSearchParams();
1023
+ }
1024
+ const rawError = params.get("error");
1025
+ const isRealError = rawError && rawError !== "undefined" && rawError !== "null";
1026
+ if (isRealError) {
1027
+ const rawDesc = params.get("error_description");
1028
+ const errorDescription = rawDesc && rawDesc !== "undefined" && rawDesc !== "null" ? rawDesc : void 0;
1029
+ return {
1030
+ valid: false,
1031
+ error: rawError,
1032
+ errorDescription
1033
+ };
1034
+ }
1035
+ const rawCode = params.get("code");
1036
+ const code = rawCode && rawCode !== "undefined" && rawCode !== "null" ? rawCode : void 0;
1037
+ if (!code) {
1038
+ return {
1039
+ valid: false,
1040
+ error: "invalid_response",
1041
+ errorDescription: "Missing authorization code in redirect params"
1042
+ };
1043
+ }
1044
+ const rawState = params.get("state");
1045
+ const state = rawState && rawState !== "undefined" && rawState !== "null" ? rawState : void 0;
1046
+ if (options.expectedState && state !== options.expectedState) {
1047
+ return {
1048
+ valid: false,
1049
+ error: "state_mismatch",
1050
+ errorDescription: "State parameter does not match expected CSRF token"
1051
+ };
1052
+ }
1053
+ return {
1054
+ valid: true,
1055
+ code,
1056
+ state
1057
+ };
1058
+ }
1059
+ /**
1060
+ * Exchanges an OAuth authorization code for a Zorveus inference key using application/x-www-form-urlencoded.
1061
+ */
1062
+ static async exchangeToken(params) {
1063
+ const baseUrl = (params.baseURL || "https://api.zorveus.com").replace(/\/+$/, "");
1064
+ const url = `${baseUrl}/oauth/token`;
1065
+ const bodyParams = new URLSearchParams();
1066
+ bodyParams.append("grant_type", "authorization_code");
1067
+ bodyParams.append("client_id", params.clientId);
1068
+ bodyParams.append("code", params.code);
1069
+ bodyParams.append("code_verifier", params.codeVerifier);
1070
+ bodyParams.append("redirect_uri", params.redirectUri);
1071
+ if (params.clientSecret) {
1072
+ bodyParams.append("client_secret", params.clientSecret);
1073
+ }
1074
+ const headers = {
1075
+ "Content-Type": "application/x-www-form-urlencoded",
1076
+ Accept: "application/json"
1077
+ };
1078
+ if (typeof window === "undefined") {
1079
+ headers["User-Agent"] = "@zorveus/sdk/0.1.0";
1080
+ }
1081
+ const response = await fetch(url, {
1082
+ method: "POST",
1083
+ headers,
1084
+ body: bodyParams.toString()
1085
+ });
1086
+ if (!response.ok) {
1087
+ let errorBody;
1088
+ try {
1089
+ errorBody = await response.json();
1090
+ } catch {
1091
+ errorBody = await response.text();
1092
+ }
1093
+ throw createAPIError(response.status, errorBody);
1094
+ }
1095
+ const tokenData = await response.json();
1096
+ return tokenData;
1097
+ }
1098
+ /**
1099
+ * Revokes an existing OAuth token or app connection using application/x-www-form-urlencoded.
1100
+ */
1101
+ static async revokeToken(params) {
1102
+ const baseUrl = (params.baseURL || "https://api.zorveus.com").replace(/\/+$/, "");
1103
+ const url = `${baseUrl}/oauth/revoke`;
1104
+ const bodyParams = new URLSearchParams();
1105
+ bodyParams.append("token", params.token);
1106
+ if (params.clientId) {
1107
+ bodyParams.append("client_id", params.clientId);
1108
+ }
1109
+ if (params.clientSecret) {
1110
+ bodyParams.append("client_secret", params.clientSecret);
1111
+ }
1112
+ const headers = {
1113
+ "Content-Type": "application/x-www-form-urlencoded",
1114
+ Accept: "application/json"
1115
+ };
1116
+ if (typeof window === "undefined") {
1117
+ headers["User-Agent"] = "@zorveus/sdk/0.1.0";
1118
+ }
1119
+ const response = await fetch(url, {
1120
+ method: "POST",
1121
+ headers,
1122
+ body: bodyParams.toString()
1123
+ });
1124
+ if (!response.ok) {
1125
+ let errorBody;
1126
+ try {
1127
+ errorBody = await response.json();
1128
+ } catch {
1129
+ errorBody = await response.text();
1130
+ }
1131
+ throw createAPIError(response.status, errorBody);
1132
+ }
1133
+ }
1134
+ };
1135
+ export {
1136
+ APIConnectionError,
1137
+ APIStatusError,
1138
+ AuthenticationError,
1139
+ CapExceededError,
1140
+ CreditGrantExpiredError,
1141
+ InsufficientFundsError,
1142
+ InternalServerError,
1143
+ NotFoundError,
1144
+ PermissionDeniedError,
1145
+ RateLimitError,
1146
+ UnprocessableEntityError,
1147
+ Zorveus,
1148
+ ZorveusBusinessError,
1149
+ ZorveusError,
1150
+ ZorveusInferenceClient,
1151
+ ZorveusOAuth,
1152
+ ZorveusServiceClient,
1153
+ assertDecimalString,
1154
+ createAPIError,
1155
+ formatGatewayMetadata,
1156
+ isValidDecimalString
1157
+ };
1158
+ //# sourceMappingURL=index.mjs.map