@ralioco/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.cjs ADDED
@@ -0,0 +1,635 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
24
+ RalioAPIError: () => RalioAPIError,
25
+ RalioAuthError: () => RalioAuthError,
26
+ RalioClient: () => RalioClient,
27
+ RalioConfigError: () => RalioConfigError,
28
+ RalioError: () => RalioError,
29
+ RalioNotFoundError: () => RalioNotFoundError,
30
+ RalioPermissionError: () => RalioPermissionError,
31
+ RalioRateLimitError: () => RalioRateLimitError,
32
+ RalioRegistrationError: () => RalioRegistrationError,
33
+ RalioValidationError: () => RalioValidationError,
34
+ register: () => register
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+
38
+ // src/client.ts
39
+ var import_promises2 = require("fs/promises");
40
+
41
+ // src/crypto.ts
42
+ var import_node_crypto = require("crypto");
43
+ var import_jose = require("jose");
44
+ var CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
45
+ var CLIENT_ASSERTION_TTL_SECONDS = 60;
46
+ function b64url(raw) {
47
+ return Buffer.from(raw).toString("base64url");
48
+ }
49
+ function canonicalize(jwk) {
50
+ if (jwk.kty !== "EC" || jwk.crv !== "P-256" || !jwk.x || !jwk.y) {
51
+ throw new Error("Ralio credentials require a P-256 (EC) key");
52
+ }
53
+ return { crv: "P-256", kty: "EC", x: jwk.x, y: jwk.y };
54
+ }
55
+ async function jwkThumbprint(jwk) {
56
+ return (0, import_jose.calculateJwkThumbprint)(jwk, "sha256");
57
+ }
58
+ async function generateKeypair() {
59
+ const { privateKey } = await (0, import_jose.generateKeyPair)("ES256", { extractable: true });
60
+ return fromPrivateKey(privateKey);
61
+ }
62
+ async function fromPrivateKey(privateKey) {
63
+ const publicJwk = canonicalize(await (0, import_jose.exportJWK)(privateKey));
64
+ const kid = await jwkThumbprint(publicJwk);
65
+ return { privateKey, publicJwk, kid };
66
+ }
67
+ async function privateKeyToPem(privateKey) {
68
+ return (0, import_jose.exportPKCS8)(privateKey);
69
+ }
70
+ async function loadPrivateKey(pem) {
71
+ let key;
72
+ try {
73
+ key = await (0, import_jose.importPKCS8)(pem, "ES256", { extractable: true });
74
+ } catch (err) {
75
+ throw new Error(
76
+ `Ralio credentials require a P-256 (EC) private key: ${err.message}`,
77
+ { cause: err }
78
+ );
79
+ }
80
+ return fromPrivateKey(key);
81
+ }
82
+ async function signClientAssertion(privateKey, opts) {
83
+ const ttl = opts.ttlSeconds ?? CLIENT_ASSERTION_TTL_SECONDS;
84
+ const iat = Math.floor(Date.now() / 1e3);
85
+ return new import_jose.SignJWT({}).setProtectedHeader({ alg: "ES256", kid: opts.kid }).setIssuer(opts.clientId).setSubject(opts.clientId).setAudience(opts.audience).setIssuedAt(iat).setExpirationTime(iat + ttl).setJti(b64url((0, import_node_crypto.randomBytes)(16))).sign(privateKey);
86
+ }
87
+ async function signDpopProof(privateKey, opts) {
88
+ const iat = Math.floor(Date.now() / 1e3);
89
+ const ath = (0, import_node_crypto.createHash)("sha256").update(opts.accessToken, "ascii").digest("base64url");
90
+ return new import_jose.SignJWT({
91
+ htm: opts.method.toUpperCase(),
92
+ htu: opts.url,
93
+ ath
94
+ }).setProtectedHeader({ typ: "dpop+jwt", alg: "ES256", jwk: { ...opts.jwk } }).setIssuedAt(iat).setJti(b64url((0, import_node_crypto.randomBytes)(16))).sign(privateKey);
95
+ }
96
+
97
+ // src/errors.ts
98
+ var RalioError = class extends Error {
99
+ constructor(message) {
100
+ super(message);
101
+ this.name = new.target.name;
102
+ Object.setPrototypeOf(this, new.target.prototype);
103
+ }
104
+ };
105
+ var RalioConfigError = class extends RalioError {
106
+ };
107
+ var RalioRegistrationError = class extends RalioError {
108
+ };
109
+ var RalioAPIError = class extends RalioError {
110
+ statusCode;
111
+ detail;
112
+ wwwAuthenticate;
113
+ constructor(message, opts) {
114
+ super(message);
115
+ this.statusCode = opts.statusCode;
116
+ this.detail = opts.detail ?? null;
117
+ this.wwwAuthenticate = opts.wwwAuthenticate ?? null;
118
+ }
119
+ };
120
+ var RalioAuthError = class extends RalioAPIError {
121
+ };
122
+ var RalioPermissionError = class extends RalioAPIError {
123
+ };
124
+ var RalioNotFoundError = class extends RalioAPIError {
125
+ };
126
+ var RalioValidationError = class extends RalioAPIError {
127
+ };
128
+ var RalioRateLimitError = class extends RalioAPIError {
129
+ };
130
+ var STATUS_MAP = {
131
+ 401: RalioAuthError,
132
+ 403: RalioPermissionError,
133
+ 404: RalioNotFoundError,
134
+ 422: RalioValidationError,
135
+ 429: RalioRateLimitError
136
+ };
137
+ async function raiseForResponse(response) {
138
+ if (response.ok) return;
139
+ const detail = await extractDetail(response);
140
+ const Cls = STATUS_MAP[response.status] ?? RalioAPIError;
141
+ const message = detail ?? `HTTP ${response.status}`;
142
+ throw new Cls(message, {
143
+ statusCode: response.status,
144
+ detail,
145
+ wwwAuthenticate: response.headers.get("www-authenticate")
146
+ });
147
+ }
148
+ async function extractDetail(response) {
149
+ const text = await response.text().catch(() => "");
150
+ let payload;
151
+ try {
152
+ payload = JSON.parse(text);
153
+ } catch {
154
+ return text || null;
155
+ }
156
+ if (payload && typeof payload === "object") {
157
+ const obj = payload;
158
+ if (typeof obj.detail === "string") return obj.detail;
159
+ if (obj.detail != null) return JSON.stringify(obj.detail);
160
+ const oauth = obj.error_description ?? obj.error;
161
+ if (typeof oauth === "string") return oauth;
162
+ }
163
+ return text || null;
164
+ }
165
+
166
+ // src/auth.ts
167
+ var TokenManager = class {
168
+ clientId;
169
+ privateKey;
170
+ kid;
171
+ tokenUrl;
172
+ scopes;
173
+ leewayMs;
174
+ accessTokenValue = null;
175
+ refreshTokenValue = null;
176
+ expiresAtMs = 0;
177
+ // Serialises all token mutations: each exclusive section runs after the
178
+ // previous one settles, so two callers never mint/refresh concurrently.
179
+ chain = Promise.resolve();
180
+ constructor(opts) {
181
+ this.clientId = opts.clientId;
182
+ this.privateKey = opts.privateKey;
183
+ this.kid = opts.kid;
184
+ this.tokenUrl = opts.tokenUrl;
185
+ this.scopes = opts.scopes;
186
+ this.leewayMs = (opts.refreshLeewaySeconds ?? 300) * 1e3;
187
+ }
188
+ /** Return a valid access token, minting or refreshing as needed. */
189
+ async accessToken() {
190
+ if (this.accessTokenValue && Date.now() < this.expiresAtMs - this.leewayMs) {
191
+ return this.accessTokenValue;
192
+ }
193
+ return this.withLock(async () => {
194
+ if (this.accessTokenValue && Date.now() < this.expiresAtMs - this.leewayMs) {
195
+ return this.accessTokenValue;
196
+ }
197
+ return this.obtain();
198
+ });
199
+ }
200
+ /** Discard the cached token and obtain a fresh one. Used after a 401. */
201
+ async forceRefresh() {
202
+ return this.withLock(async () => {
203
+ this.accessTokenValue = null;
204
+ return this.obtain();
205
+ });
206
+ }
207
+ withLock(fn) {
208
+ const run = this.chain.then(fn, fn);
209
+ this.chain = run.then(
210
+ () => void 0,
211
+ () => void 0
212
+ );
213
+ return run;
214
+ }
215
+ async obtain() {
216
+ if (this.refreshTokenValue) {
217
+ try {
218
+ return await this.refresh();
219
+ } catch {
220
+ this.refreshTokenValue = null;
221
+ }
222
+ }
223
+ return this.mint();
224
+ }
225
+ async mint() {
226
+ const assertion = await signClientAssertion(this.privateKey, {
227
+ clientId: this.clientId,
228
+ audience: this.tokenUrl,
229
+ kid: this.kid
230
+ });
231
+ const data = {
232
+ grant_type: "client_credentials",
233
+ client_assertion_type: CLIENT_ASSERTION_TYPE,
234
+ client_assertion: assertion
235
+ };
236
+ if (this.scopes && this.scopes.length > 0) {
237
+ data.scope = this.scopes.join(" ");
238
+ }
239
+ return this.exchange(data);
240
+ }
241
+ async refresh() {
242
+ if (!this.refreshTokenValue) throw new Error("no refresh token");
243
+ return this.exchange({
244
+ grant_type: "refresh_token",
245
+ refresh_token: this.refreshTokenValue,
246
+ client_id: this.clientId
247
+ });
248
+ }
249
+ async exchange(data) {
250
+ const response = await fetch(this.tokenUrl, {
251
+ method: "POST",
252
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
253
+ body: new URLSearchParams(data).toString()
254
+ });
255
+ await raiseForResponse(response);
256
+ const body2 = await response.json();
257
+ this.accessTokenValue = body2.access_token;
258
+ this.refreshTokenValue = body2.refresh_token ?? this.refreshTokenValue;
259
+ this.expiresAtMs = Date.now() + (body2.expires_in ?? 1800) * 1e3;
260
+ return this.accessTokenValue;
261
+ }
262
+ };
263
+
264
+ // src/registration.ts
265
+ var import_node_crypto2 = require("crypto");
266
+ var import_promises = require("fs/promises");
267
+ var import_node_path = require("path");
268
+ var DEFAULT_BASE_URL = "https://api.ralio.co";
269
+ var TERMINAL = /* @__PURE__ */ new Set(["active", "rejected", "expired"]);
270
+ async function register(opts) {
271
+ const base = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
272
+ const pollIntervalMs = opts.pollIntervalMs ?? 3e3;
273
+ const timeoutMs = opts.timeoutMs ?? 9e5;
274
+ if (!opts.overwrite && await exists(opts.privateKeyPath)) {
275
+ throw new RalioRegistrationError(
276
+ `${opts.privateKeyPath} already exists; pass overwrite: true to replace it`
277
+ );
278
+ }
279
+ const { privateKey, publicJwk, kid } = await generateKeypair();
280
+ await savePrivateKey(opts.privateKeyPath, await privateKeyToPem(privateKey));
281
+ const pollToken = await submit(base, opts, publicJwk, kid);
282
+ const clientId = await poll(base, pollToken, pollIntervalMs, timeoutMs);
283
+ return { clientId, scopes: opts.requestedScopes ?? [] };
284
+ }
285
+ async function submit(base, opts, publicJwk, fingerprint) {
286
+ const body2 = { ticket: opts.ticket, public_key_jwk: publicJwk };
287
+ if (opts.requestedScopes) body2.requested_scopes = opts.requestedScopes;
288
+ if (opts.clientMetadata) body2.client_metadata = opts.clientMetadata;
289
+ const response = await fetch(`${base}/api/credential-bindings/registrations`, {
290
+ method: "POST",
291
+ headers: { "Content-Type": "application/json" },
292
+ body: JSON.stringify(body2)
293
+ });
294
+ await raiseForResponse(response);
295
+ const payload = await response.json();
296
+ if (payload.fingerprint && payload.fingerprint !== fingerprint) {
297
+ throw new RalioRegistrationError(
298
+ "fingerprint mismatch between local key and server response; aborting"
299
+ );
300
+ }
301
+ if (typeof payload.poll_token !== "string" || !payload.poll_token) {
302
+ throw new RalioRegistrationError("registration response did not include a poll_token");
303
+ }
304
+ return payload.poll_token;
305
+ }
306
+ async function poll(base, pollToken, intervalMs, timeoutMs) {
307
+ const deadline = Date.now() + timeoutMs;
308
+ const url = `${base}/api/credential-bindings/registrations/${pollToken}`;
309
+ for (; ; ) {
310
+ const response = await fetch(url);
311
+ if (response.status === 404) {
312
+ throw new RalioRegistrationError("registration expired before approval");
313
+ }
314
+ await raiseForResponse(response);
315
+ const body2 = await response.json();
316
+ const status = body2.status ?? "";
317
+ if (status === "active") {
318
+ if (typeof body2.client_id !== "string" || !body2.client_id) {
319
+ throw new RalioRegistrationError("binding active but no client_id returned");
320
+ }
321
+ return body2.client_id;
322
+ }
323
+ if (TERMINAL.has(status)) {
324
+ throw new RalioRegistrationError(`registration ${status}`);
325
+ }
326
+ if (Date.now() >= deadline) {
327
+ throw new RalioRegistrationError("timed out waiting for owner approval");
328
+ }
329
+ await sleep(intervalMs);
330
+ }
331
+ }
332
+ async function exists(path) {
333
+ try {
334
+ await (0, import_promises.access)(path);
335
+ return true;
336
+ } catch {
337
+ return false;
338
+ }
339
+ }
340
+ async function savePrivateKey(path, pem) {
341
+ const dir = (0, import_node_path.dirname)(path);
342
+ const tmp = (0, import_node_path.join)(dir, `.tmp-${(0, import_node_crypto2.randomBytes)(8).toString("hex")}`);
343
+ const handle = await (0, import_promises.open)(tmp, "wx", 384);
344
+ try {
345
+ await handle.writeFile(pem);
346
+ await handle.close();
347
+ await (0, import_promises.rename)(tmp, path);
348
+ } catch (err) {
349
+ await handle.close().catch(() => void 0);
350
+ await (0, import_promises.rm)(tmp, { force: true });
351
+ throw err;
352
+ }
353
+ }
354
+ function sleep(ms) {
355
+ return new Promise((resolve) => setTimeout(resolve, ms));
356
+ }
357
+
358
+ // src/types.ts
359
+ function str(value, fallback = "") {
360
+ return typeof value === "string" ? value : fallback;
361
+ }
362
+ function optStr(value) {
363
+ return typeof value === "string" ? value : null;
364
+ }
365
+ function parseMessage(data) {
366
+ return {
367
+ id: str(data.id),
368
+ role: str(data.role),
369
+ content: str(data.content),
370
+ createdAt: optStr(data.created_at)
371
+ };
372
+ }
373
+ function parseChatReply(data) {
374
+ const raw = Array.isArray(data.new_messages) ? data.new_messages : [];
375
+ return {
376
+ reply: str(data.reply),
377
+ conversationId: str(data.conversation_id),
378
+ newMessages: raw.map((m) => parseMessage(m ?? {}))
379
+ };
380
+ }
381
+ function buildStreamEvent(event, data) {
382
+ return { event, data, text: typeof data.text === "string" ? data.text : "" };
383
+ }
384
+ function parseTransaction(data) {
385
+ return {
386
+ id: str(data.id),
387
+ amount: str(data.amount),
388
+ currency: str(data.currency),
389
+ status: str(data.status),
390
+ date: optStr(data.date),
391
+ creditor: optStr(data.creditor),
392
+ debtor: optStr(data.debtor),
393
+ reference: optStr(data.reference),
394
+ paymentIntentId: optStr(data.payment_intent_id)
395
+ };
396
+ }
397
+
398
+ // src/resources/chat.ts
399
+ var ChatResource = class {
400
+ constructor(transport) {
401
+ this.transport = transport;
402
+ }
403
+ transport;
404
+ /**
405
+ * Send a message and wait for the agent's complete reply.
406
+ *
407
+ * Times out server-side after 120s. For interactive approval flows where a
408
+ * human may take longer, use {@link stream} instead.
409
+ */
410
+ async send(params) {
411
+ const response = await this.transport.request("POST", "/api/chat", {
412
+ jsonBody: body(params)
413
+ });
414
+ return parseChatReply(await response.json());
415
+ }
416
+ /**
417
+ * Stream the agent's reply as server-sent events.
418
+ *
419
+ * Yields `conversation`, `tool_started`/`tool_completed`/`tool_failed`,
420
+ * `text_delta`, and a final `reply` event.
421
+ */
422
+ stream(params) {
423
+ return this.transport.streamSse("POST", "/api/chat/stream", {
424
+ jsonBody: body(params)
425
+ });
426
+ }
427
+ };
428
+ function body(params) {
429
+ const out = {
430
+ agent_id: params.agentId,
431
+ message: params.message
432
+ };
433
+ if (params.conversationId !== void 0) out.conversation_id = params.conversationId;
434
+ return out;
435
+ }
436
+
437
+ // src/resources/transactions.ts
438
+ var TransactionsResource = class {
439
+ constructor(transport) {
440
+ this.transport = transport;
441
+ }
442
+ transport;
443
+ /** List transactions across the caller's agents, newest first. */
444
+ async list(params = {}) {
445
+ const response = await this.transport.request("GET", "/api/transactions", {
446
+ params: { limit: params.limit ?? 50, agent_id: params.agentId }
447
+ });
448
+ const items = await response.json();
449
+ return items.map(parseTransaction);
450
+ }
451
+ };
452
+
453
+ // src/transport.ts
454
+ var Transport = class {
455
+ baseUrl;
456
+ tokens;
457
+ privateKey;
458
+ publicJwk;
459
+ requestTimeoutMs;
460
+ constructor(opts) {
461
+ this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
462
+ this.tokens = opts.tokens;
463
+ this.privateKey = opts.privateKey;
464
+ this.publicJwk = opts.publicJwk;
465
+ this.requestTimeoutMs = opts.requestTimeoutMs;
466
+ }
467
+ async request(method, path, opts = {}) {
468
+ const url = this.buildUrl(path, opts.params);
469
+ const init = (headers) => ({
470
+ method,
471
+ headers: opts.jsonBody ? { ...headers, "Content-Type": "application/json" } : headers,
472
+ body: opts.jsonBody ? JSON.stringify(opts.jsonBody) : void 0,
473
+ signal: this.requestTimeoutMs ? AbortSignal.timeout(this.requestTimeoutMs) : void 0
474
+ });
475
+ let response = await fetch(url, init(await this.authHeaders(method, url)));
476
+ if (response.status === 401) {
477
+ await this.tokens.forceRefresh();
478
+ response = await fetch(url, init(await this.authHeaders(method, url)));
479
+ }
480
+ await raiseForResponse(response);
481
+ return response;
482
+ }
483
+ async *streamSse(method, path, opts = {}) {
484
+ const url = this.buildUrl(path);
485
+ const init = async () => ({
486
+ method,
487
+ headers: {
488
+ ...await this.authHeaders(method, url),
489
+ Accept: "text/event-stream",
490
+ ...opts.jsonBody ? { "Content-Type": "application/json" } : {}
491
+ },
492
+ body: opts.jsonBody ? JSON.stringify(opts.jsonBody) : void 0
493
+ });
494
+ let response = await fetch(url, await init());
495
+ if (response.status === 401) {
496
+ await response.body?.cancel();
497
+ await this.tokens.forceRefresh();
498
+ response = await fetch(url, await init());
499
+ }
500
+ await raiseForResponse(response);
501
+ yield* parseSse(response);
502
+ }
503
+ buildUrl(path, params) {
504
+ const url = new URL(`${this.baseUrl}${path}`);
505
+ if (params) {
506
+ for (const [key, value] of Object.entries(params)) {
507
+ if (value !== void 0) url.searchParams.set(key, String(value));
508
+ }
509
+ }
510
+ return url.toString();
511
+ }
512
+ async authHeaders(method, url) {
513
+ const token = await this.tokens.accessToken();
514
+ const proof = await signDpopProof(this.privateKey, {
515
+ method,
516
+ url: htu(url),
517
+ accessToken: token,
518
+ jwk: this.publicJwk
519
+ });
520
+ return { Authorization: `DPoP ${token}`, DPoP: proof };
521
+ }
522
+ };
523
+ function htu(url) {
524
+ const parsed = new URL(url);
525
+ return `${parsed.origin}${parsed.pathname}`;
526
+ }
527
+ async function* parseSse(response) {
528
+ if (!response.body) return;
529
+ const decoder = new TextDecoder();
530
+ let buffer = "";
531
+ let eventName = "message";
532
+ let dataLines = [];
533
+ const flush = () => {
534
+ if (dataLines.length === 0) return null;
535
+ const event = buildEvent(eventName, dataLines);
536
+ eventName = "message";
537
+ dataLines = [];
538
+ return event;
539
+ };
540
+ const handleLine = (line) => {
541
+ if (line === "") return flush();
542
+ if (line.startsWith(":")) return null;
543
+ const idx = line.indexOf(":");
544
+ const field = idx === -1 ? line : line.slice(0, idx);
545
+ let value = idx === -1 ? "" : line.slice(idx + 1);
546
+ if (value.startsWith(" ")) value = value.slice(1);
547
+ if (field === "event") eventName = value;
548
+ else if (field === "data") dataLines.push(value);
549
+ return null;
550
+ };
551
+ for await (const chunk of response.body) {
552
+ buffer += decoder.decode(chunk, { stream: true });
553
+ let newlineIdx;
554
+ while ((newlineIdx = buffer.indexOf("\n")) !== -1) {
555
+ const raw = buffer.slice(0, newlineIdx);
556
+ buffer = buffer.slice(newlineIdx + 1);
557
+ const line = raw.endsWith("\r") ? raw.slice(0, -1) : raw;
558
+ const event = handleLine(line);
559
+ if (event) yield event;
560
+ }
561
+ }
562
+ const tail = flush();
563
+ if (tail) yield tail;
564
+ }
565
+ function buildEvent(eventName, dataLines) {
566
+ const raw = dataLines.join("\n");
567
+ let payload;
568
+ try {
569
+ const parsed = JSON.parse(raw);
570
+ payload = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : { value: parsed };
571
+ } catch {
572
+ payload = { raw };
573
+ }
574
+ if (eventName === "error") {
575
+ const message = typeof payload.message === "string" ? payload.message : "stream error";
576
+ throw new RalioAPIError(message, { statusCode: 200, detail: message });
577
+ }
578
+ return buildStreamEvent(eventName, payload);
579
+ }
580
+
581
+ // src/client.ts
582
+ var RalioClient = class _RalioClient {
583
+ chat;
584
+ transactions;
585
+ constructor(transport) {
586
+ this.chat = new ChatResource(transport);
587
+ this.transactions = new TransactionsResource(transport);
588
+ }
589
+ /** Load the private key and build a ready-to-use client. */
590
+ static async create(opts) {
591
+ const baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
592
+ const pem = await (0, import_promises2.readFile)(opts.privateKeyPath, "utf8");
593
+ const { privateKey, publicJwk, kid } = await loadPrivateKey(pem);
594
+ const tokens = new TokenManager({
595
+ clientId: opts.clientId,
596
+ privateKey,
597
+ kid,
598
+ tokenUrl: `${baseUrl}/oauth/token`,
599
+ scopes: opts.scopes
600
+ });
601
+ const transport = new Transport({
602
+ baseUrl,
603
+ tokens,
604
+ privateKey,
605
+ publicJwk,
606
+ requestTimeoutMs: opts.timeoutMs ?? 3e4
607
+ });
608
+ return new _RalioClient(transport);
609
+ }
610
+ /**
611
+ * Release resources. Present for API symmetry and forward compatibility;
612
+ * the native `fetch` transport holds no long-lived connections of its own.
613
+ */
614
+ close() {
615
+ }
616
+ [Symbol.dispose]() {
617
+ this.close();
618
+ }
619
+ };
620
+ // Annotate the CommonJS export names for ESM import in node:
621
+ 0 && (module.exports = {
622
+ DEFAULT_BASE_URL,
623
+ RalioAPIError,
624
+ RalioAuthError,
625
+ RalioClient,
626
+ RalioConfigError,
627
+ RalioError,
628
+ RalioNotFoundError,
629
+ RalioPermissionError,
630
+ RalioRateLimitError,
631
+ RalioRegistrationError,
632
+ RalioValidationError,
633
+ register
634
+ });
635
+ //# sourceMappingURL=index.cjs.map