@pymthouse/builder-sdk 0.0.8

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/env.js ADDED
@@ -0,0 +1,612 @@
1
+ import { genericTokenEndpointRequest, processGenericTokenEndpointResponse, clientCredentialsGrantRequest, processClientCredentialsResponse, customFetch, allowInsecureRequests, discoveryRequest, processDiscoveryResponse, ResponseBodyError, OperationProcessingError } from 'oauth4webapi';
2
+
3
+ // src/client.ts
4
+
5
+ // src/encoding.ts
6
+ function encodeClientSecretBasic(clientId, clientSecret) {
7
+ const raw = `${clientId}:${clientSecret}`;
8
+ const b64 = typeof Buffer !== "undefined" ? Buffer.from(raw, "utf8").toString("base64") : btoa(Array.from(new TextEncoder().encode(raw), (c) => String.fromCharCode(c)).join(""));
9
+ return `Basic ${b64}`;
10
+ }
11
+
12
+ // src/errors.ts
13
+ var PmtHouseError = class extends Error {
14
+ status;
15
+ code;
16
+ details;
17
+ constructor(message, {
18
+ status = 500,
19
+ code = "pymthouse_error",
20
+ details
21
+ } = {}) {
22
+ super(message);
23
+ this.name = "PmtHouseError";
24
+ this.status = status;
25
+ this.code = code;
26
+ this.details = details;
27
+ }
28
+ };
29
+
30
+ // src/string-utils.ts
31
+ function stripTrailingSlashes(value) {
32
+ let end = value.length;
33
+ while (end > 0 && value.charCodeAt(end - 1) === 47) {
34
+ end--;
35
+ }
36
+ return value.slice(0, end);
37
+ }
38
+
39
+ // src/discovery.ts
40
+ function authorizationServerToOidcDocument(as) {
41
+ const tokenEndpoint = as.token_endpoint;
42
+ const jwksUri = as.jwks_uri;
43
+ if (!tokenEndpoint || !jwksUri) {
44
+ throw new PmtHouseError("OIDC discovery document is missing token_endpoint or jwks_uri", {
45
+ status: 500,
46
+ code: "oidc_discovery_invalid"
47
+ });
48
+ }
49
+ return {
50
+ issuer: as.issuer,
51
+ authorization_endpoint: as.authorization_endpoint ?? "",
52
+ token_endpoint: tokenEndpoint,
53
+ jwks_uri: jwksUri,
54
+ userinfo_endpoint: as.userinfo_endpoint,
55
+ device_authorization_endpoint: as.device_authorization_endpoint
56
+ };
57
+ }
58
+ var CACHE_TTL_MS = 5 * 60 * 1e3;
59
+ var discoveryCache = /* @__PURE__ */ new Map();
60
+ function normalizedIssuerKey(issuerUrl) {
61
+ return stripTrailingSlashes(issuerUrl);
62
+ }
63
+ async function loadAuthorizationServer(issuerUrl, fetchImpl, options = {}) {
64
+ const key = normalizedIssuerKey(issuerUrl);
65
+ const now = Date.now();
66
+ const cached = discoveryCache.get(key);
67
+ if (!options.force && cached && now - cached.fetchedAt < CACHE_TTL_MS) {
68
+ return cached.as;
69
+ }
70
+ const issuerIdentifier = new URL(key);
71
+ const discoveryOpts = {
72
+ algorithm: "oidc",
73
+ [customFetch]: fetchImpl
74
+ };
75
+ if (options.allowInsecureHttp) {
76
+ discoveryOpts[allowInsecureRequests] = true;
77
+ }
78
+ let response;
79
+ try {
80
+ response = await discoveryRequest(issuerIdentifier, discoveryOpts);
81
+ } catch (e) {
82
+ throw mapDiscoveryNetworkError(e);
83
+ }
84
+ let as;
85
+ try {
86
+ as = await processDiscoveryResponse(issuerIdentifier, response);
87
+ } catch (e) {
88
+ throw mapOAuthDiscoveryError(e);
89
+ }
90
+ discoveryCache.set(key, { as, fetchedAt: now });
91
+ return as;
92
+ }
93
+ function mapOAuthDiscoveryError(error) {
94
+ if (error instanceof PmtHouseError) {
95
+ return error;
96
+ }
97
+ if (error instanceof Error) {
98
+ return new PmtHouseError(error.message, {
99
+ status: 500,
100
+ code: "oidc_discovery_invalid",
101
+ details: { cause: error.cause }
102
+ });
103
+ }
104
+ return new PmtHouseError("OIDC discovery failed", {
105
+ status: 500,
106
+ code: "oidc_discovery_invalid"
107
+ });
108
+ }
109
+ function mapDiscoveryNetworkError(error) {
110
+ if (error instanceof PmtHouseError) {
111
+ return error;
112
+ }
113
+ if (error instanceof Error) {
114
+ return new PmtHouseError(`Failed to load OIDC discovery: ${error.message}`, {
115
+ status: 502,
116
+ code: "oidc_discovery_failed"
117
+ });
118
+ }
119
+ return new PmtHouseError("Failed to load OIDC discovery", {
120
+ status: 502,
121
+ code: "oidc_discovery_failed"
122
+ });
123
+ }
124
+ var ACCEPTED_ISSUED_TOKEN_TYPES = /* @__PURE__ */ new Set([
125
+ "urn:ietf:params:oauth:token-type:access_token",
126
+ "urn:pmth:token-type:remote-signer-session"
127
+ ]);
128
+ function mapOAuthError(error) {
129
+ if (error instanceof PmtHouseError) {
130
+ return error;
131
+ }
132
+ if (error instanceof ResponseBodyError) {
133
+ const cause = error.cause;
134
+ const description = typeof error.error_description === "string" ? error.error_description : error.message;
135
+ const details = { ...cause };
136
+ if (typeof cause.error_uri === "string") {
137
+ details.error_uri = cause.error_uri;
138
+ }
139
+ return new PmtHouseError(description, {
140
+ status: error.status,
141
+ code: error.error,
142
+ details
143
+ });
144
+ }
145
+ if (error instanceof OperationProcessingError) {
146
+ return new PmtHouseError(error.message, {
147
+ status: 502,
148
+ code: error.code ?? "oauth_processing_error",
149
+ details: { cause: error.cause }
150
+ });
151
+ }
152
+ if (error instanceof Error) {
153
+ return new PmtHouseError(error.message, {
154
+ status: 500,
155
+ code: "unexpected_error"
156
+ });
157
+ }
158
+ return new PmtHouseError("Unexpected error", {
159
+ status: 500,
160
+ code: "unexpected_error"
161
+ });
162
+ }
163
+ function tokenEndpointResponseToExchange(tr) {
164
+ const issued = tr.issued_token_type;
165
+ if (typeof issued !== "string" || !ACCEPTED_ISSUED_TOKEN_TYPES.has(issued)) {
166
+ throw new PmtHouseError("Token exchange returned an unexpected issued_token_type", {
167
+ status: 502,
168
+ code: "invalid_token_response",
169
+ details: { issued_token_type: issued }
170
+ });
171
+ }
172
+ const tt = tr.token_type;
173
+ if (typeof tt !== "string" || tt.toLowerCase() !== "bearer") {
174
+ throw new PmtHouseError("Token endpoint returned a non-Bearer token_type", {
175
+ status: 502,
176
+ code: "invalid_token_response",
177
+ details: { token_type: tt }
178
+ });
179
+ }
180
+ const expiresIn = tr.expires_in;
181
+ if (typeof expiresIn !== "number") {
182
+ throw new PmtHouseError("Token response missing expires_in", {
183
+ status: 502,
184
+ code: "invalid_token_response"
185
+ });
186
+ }
187
+ const scope = typeof tr.scope === "string" ? tr.scope : "";
188
+ return {
189
+ access_token: tr.access_token,
190
+ token_type: "Bearer",
191
+ expires_in: expiresIn,
192
+ scope,
193
+ issued_token_type: issued
194
+ };
195
+ }
196
+ function tokenEndpointResponseToClientCredentials(tr) {
197
+ const tt = tr.token_type;
198
+ if (typeof tt !== "string" || tt.toLowerCase() !== "bearer") {
199
+ throw new PmtHouseError("Token endpoint returned a non-Bearer token_type", {
200
+ status: 502,
201
+ code: "invalid_token_response",
202
+ details: { token_type: tt }
203
+ });
204
+ }
205
+ return {
206
+ access_token: tr.access_token,
207
+ token_type: "Bearer",
208
+ expires_in: tr.expires_in,
209
+ scope: typeof tr.scope === "string" ? tr.scope : void 0
210
+ };
211
+ }
212
+ function m2mClient(clientId) {
213
+ return { client_id: clientId };
214
+ }
215
+
216
+ // src/client.ts
217
+ var TOKEN_EXCHANGE_GRANT = "urn:ietf:params:oauth:grant-type:token-exchange";
218
+ var SUBJECT_ACCESS_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token";
219
+ var REQUESTED_ACCESS_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token";
220
+ var DEVICE_RESOURCE_PREFIX = "urn:pmth:device_code:";
221
+ function normalizeUserCode(value) {
222
+ return value.replace(/[a-z]/g, (char) => char.toUpperCase()).replace(/\W/g, "");
223
+ }
224
+ function buildDeviceCodeResource(userCode) {
225
+ return `${DEVICE_RESOURCE_PREFIX}${normalizeUserCode(userCode)}`;
226
+ }
227
+ var PmtHouseClient = class {
228
+ issuerUrl;
229
+ publicClientId;
230
+ m2mClientId;
231
+ m2mClientSecret;
232
+ fetchImpl;
233
+ logger;
234
+ allowInsecureHttp;
235
+ constructor(options) {
236
+ this.issuerUrl = stripTrailingSlashes(options.issuerUrl);
237
+ this.publicClientId = options.publicClientId;
238
+ this.m2mClientId = options.m2mClientId;
239
+ this.m2mClientSecret = options.m2mClientSecret;
240
+ this.fetchImpl = options.fetch ?? fetch;
241
+ this.logger = options.logger;
242
+ this.allowInsecureHttp = options.allowInsecureHttp ?? false;
243
+ }
244
+ async getDiscovery(options = {}) {
245
+ const as = await loadAuthorizationServer(this.issuerUrl, this.fetchImpl, {
246
+ force: options.force,
247
+ allowInsecureHttp: this.allowInsecureHttp
248
+ });
249
+ return authorizationServerToOidcDocument(as);
250
+ }
251
+ verifyIssuer(iss) {
252
+ const candidate = stripTrailingSlashes(iss.trim());
253
+ return candidate === this.issuerUrl;
254
+ }
255
+ parseDeviceApprovalRedirect(searchParams) {
256
+ const issuer = searchParams.get("iss")?.trim() ?? "";
257
+ const targetLinkUri = searchParams.get("target_link_uri")?.trim() ?? "";
258
+ if (!issuer || !targetLinkUri) {
259
+ throw new PmtHouseError("Missing iss or target_link_uri", {
260
+ status: 400,
261
+ code: "invalid_request"
262
+ });
263
+ }
264
+ if (!this.verifyIssuer(issuer)) {
265
+ throw new PmtHouseError("Issuer mismatch for initiate login", {
266
+ status: 400,
267
+ code: "invalid_issuer"
268
+ });
269
+ }
270
+ let targetUrl;
271
+ try {
272
+ targetUrl = new URL(targetLinkUri);
273
+ } catch {
274
+ throw new PmtHouseError("target_link_uri is not a valid URL", {
275
+ status: 400,
276
+ code: "invalid_target"
277
+ });
278
+ }
279
+ const issuerOrigin = new URL(this.issuerUrl).origin;
280
+ if (targetUrl.origin !== issuerOrigin || targetUrl.pathname !== "/oidc/device") {
281
+ throw new PmtHouseError(
282
+ "target_link_uri does not point to the issuer device path",
283
+ {
284
+ status: 400,
285
+ code: "invalid_target"
286
+ }
287
+ );
288
+ }
289
+ const userCode = normalizeUserCode(targetUrl.searchParams.get("user_code") ?? "");
290
+ const clientId = targetUrl.searchParams.get("client_id")?.trim() ?? "";
291
+ if (!userCode || !clientId) {
292
+ throw new PmtHouseError("target_link_uri is missing user_code or client_id", {
293
+ status: 400,
294
+ code: "invalid_target"
295
+ });
296
+ }
297
+ return {
298
+ issuer,
299
+ targetLinkUri,
300
+ userCode,
301
+ clientId
302
+ };
303
+ }
304
+ async listAppUsers() {
305
+ const url = `${this.getAppsBaseUrl()}/users`;
306
+ return this.requestJson(url, {
307
+ method: "GET",
308
+ headers: this.builderHeaders(),
309
+ cache: "no-store"
310
+ });
311
+ }
312
+ async upsertAppUser(input) {
313
+ const payload = {
314
+ externalUserId: input.externalUserId
315
+ };
316
+ if (input.email) payload.email = input.email;
317
+ if (input.status) payload.status = input.status;
318
+ const url = `${this.getAppsBaseUrl()}/users`;
319
+ return this.requestJson(url, {
320
+ method: "POST",
321
+ headers: this.builderHeaders(),
322
+ body: JSON.stringify(payload),
323
+ cache: "no-store"
324
+ });
325
+ }
326
+ async deleteAppUser(params) {
327
+ const url = new URL(`${this.getAppsBaseUrl()}/users`);
328
+ url.searchParams.set("externalUserId", params.externalUserId);
329
+ return this.requestJson(url.toString(), {
330
+ method: "DELETE",
331
+ headers: this.builderHeaders(),
332
+ cache: "no-store"
333
+ });
334
+ }
335
+ async mintUserAccessToken(input) {
336
+ const url = `${this.getAppsBaseUrl()}/users/${encodeURIComponent(input.externalUserId)}/token`;
337
+ const body = input.scope ? { scope: input.scope } : {};
338
+ return this.requestJson(url, {
339
+ method: "POST",
340
+ headers: this.builderHeaders(),
341
+ body: JSON.stringify(body),
342
+ cache: "no-store"
343
+ });
344
+ }
345
+ async completeDeviceApproval(input) {
346
+ const as = await loadAuthorizationServer(this.issuerUrl, this.fetchImpl, {
347
+ allowInsecureHttp: this.allowInsecureHttp
348
+ });
349
+ const client = m2mClient(this.m2mClientId);
350
+ const clientAuth = this.m2mClientAuth();
351
+ const params = new URLSearchParams();
352
+ params.set("subject_token", input.userJwt);
353
+ params.set("subject_token_type", SUBJECT_ACCESS_TOKEN_TYPE);
354
+ params.set("resource", buildDeviceCodeResource(input.userCode));
355
+ try {
356
+ const response = await genericTokenEndpointRequest(
357
+ as,
358
+ client,
359
+ clientAuth,
360
+ TOKEN_EXCHANGE_GRANT,
361
+ params,
362
+ this.tokenEndpointFetchOptions()
363
+ );
364
+ const tr = await processGenericTokenEndpointResponse(
365
+ as,
366
+ client,
367
+ response
368
+ );
369
+ return tokenEndpointResponseToExchange(tr);
370
+ } catch (e) {
371
+ throw mapOAuthError(e);
372
+ }
373
+ }
374
+ async issueMachineAccessToken(scope = "sign:job") {
375
+ const as = await loadAuthorizationServer(this.issuerUrl, this.fetchImpl, {
376
+ allowInsecureHttp: this.allowInsecureHttp
377
+ });
378
+ const client = m2mClient(this.m2mClientId);
379
+ const clientAuth = this.m2mClientAuth();
380
+ const params = new URLSearchParams();
381
+ params.set("scope", scope);
382
+ try {
383
+ const response = await clientCredentialsGrantRequest(
384
+ as,
385
+ client,
386
+ clientAuth,
387
+ params,
388
+ this.tokenEndpointFetchOptions()
389
+ );
390
+ const tr = await processClientCredentialsResponse(
391
+ as,
392
+ client,
393
+ response
394
+ );
395
+ return tokenEndpointResponseToClientCredentials(tr);
396
+ } catch (e) {
397
+ throw mapOAuthError(e);
398
+ }
399
+ }
400
+ async exchangeForSignerSession(input) {
401
+ const as = await loadAuthorizationServer(this.issuerUrl, this.fetchImpl, {
402
+ allowInsecureHttp: this.allowInsecureHttp
403
+ });
404
+ const client = m2mClient(this.m2mClientId);
405
+ const clientAuth = this.m2mClientAuth();
406
+ const params = new URLSearchParams();
407
+ params.set("subject_token", input.userJwt);
408
+ params.set("subject_token_type", SUBJECT_ACCESS_TOKEN_TYPE);
409
+ params.set("requested_token_type", REQUESTED_ACCESS_TOKEN_TYPE);
410
+ const resourceCandidate = typeof input.resource === "string" && input.resource.trim() !== "" ? input.resource.trim() : this.issuerUrl;
411
+ params.set("resource", stripTrailingSlashes(resourceCandidate));
412
+ try {
413
+ const response = await genericTokenEndpointRequest(
414
+ as,
415
+ client,
416
+ clientAuth,
417
+ TOKEN_EXCHANGE_GRANT,
418
+ params,
419
+ this.tokenEndpointFetchOptions()
420
+ );
421
+ const tr = await processGenericTokenEndpointResponse(
422
+ as,
423
+ client,
424
+ response
425
+ );
426
+ return tokenEndpointResponseToExchange(tr);
427
+ } catch (e) {
428
+ throw mapOAuthError(e);
429
+ }
430
+ }
431
+ /**
432
+ * Mint a short-lived per-user JWT with the Builder API, then exchange it for
433
+ * a long-lived opaque signer session token at the PymtHouse OIDC token endpoint.
434
+ */
435
+ async mintUserSignerSessionToken(input) {
436
+ const userToken = await this.mintUserAccessToken({
437
+ externalUserId: input.externalUserId,
438
+ scope: input.scope ?? "sign:job"
439
+ });
440
+ return this.exchangeForSignerSession({
441
+ userJwt: userToken.access_token,
442
+ resource: input.resource
443
+ });
444
+ }
445
+ async createSignerSessionToken(params) {
446
+ if (params.userJwt) {
447
+ try {
448
+ return await this.exchangeForSignerSession({ userJwt: params.userJwt });
449
+ } catch (error) {
450
+ const err = this.asError(error);
451
+ this.logger?.warn?.("User JWT exchange failed, falling back to machine exchange", {
452
+ code: err.code,
453
+ status: err.status
454
+ });
455
+ }
456
+ }
457
+ const machineToken = await this.issueMachineAccessToken("sign:job");
458
+ if (!machineToken.access_token) {
459
+ throw new PmtHouseError("Client credentials flow did not return access_token", {
460
+ status: 502,
461
+ code: "invalid_token_response"
462
+ });
463
+ }
464
+ return this.exchangeForSignerSession({ userJwt: machineToken.access_token });
465
+ }
466
+ async getUsage(input = {}) {
467
+ const url = new URL(`${this.getAppsBaseUrl()}/usage`);
468
+ if (input.startDate) url.searchParams.set("startDate", input.startDate);
469
+ if (input.endDate) url.searchParams.set("endDate", input.endDate);
470
+ if (input.groupBy) url.searchParams.set("groupBy", input.groupBy);
471
+ if (input.userId) url.searchParams.set("userId", input.userId);
472
+ if (input.gatewayRequestId) url.searchParams.set("gatewayRequestId", input.gatewayRequestId);
473
+ return this.requestJson(url.toString(), {
474
+ method: "GET",
475
+ headers: this.builderHeaders(),
476
+ cache: "no-store"
477
+ });
478
+ }
479
+ tokenEndpointFetchOptions() {
480
+ const o = {
481
+ [customFetch]: this.fetchImpl
482
+ };
483
+ if (this.allowInsecureHttp) {
484
+ o[allowInsecureRequests] = true;
485
+ }
486
+ return o;
487
+ }
488
+ getAppsBaseUrl() {
489
+ return `${this.getIssuerOrigin()}/api/v1/apps/${encodeURIComponent(this.publicClientId)}`;
490
+ }
491
+ getIssuerOrigin() {
492
+ return new URL(this.issuerUrl).origin;
493
+ }
494
+ builderHeaders() {
495
+ return {
496
+ Authorization: encodeClientSecretBasic(this.m2mClientId, this.m2mClientSecret),
497
+ "Content-Type": "application/json",
498
+ Accept: "application/json"
499
+ };
500
+ }
501
+ m2mClientAuth() {
502
+ return (_as, _client, _body, headers) => {
503
+ headers.set("Authorization", encodeClientSecretBasic(this.m2mClientId, this.m2mClientSecret));
504
+ };
505
+ }
506
+ async requestJson(url, init) {
507
+ this.logger?.debug?.("PmtHouse request", {
508
+ method: init.method ?? "GET",
509
+ url
510
+ });
511
+ const response = await this.fetchImpl(url, init);
512
+ const raw = await response.text();
513
+ const ct = response.headers.get("content-type") ?? "";
514
+ const looksJson = ct.includes("application/json") || ct.includes("json");
515
+ const parsed = raw && looksJson ? this.safeParseJson(raw) : raw ? null : null;
516
+ if (!response.ok) {
517
+ const details = parsed ?? {};
518
+ const description = typeof details.error_description === "string" ? details.error_description : typeof details.error === "string" ? details.error : `Request failed (${response.status})`;
519
+ throw new PmtHouseError(description, {
520
+ status: response.status,
521
+ code: typeof details.error === "string" ? details.error : "pymthouse_http_error",
522
+ details
523
+ });
524
+ }
525
+ if (!looksJson || parsed === null) {
526
+ throw new PmtHouseError("Expected JSON response from Builder or Usage API", {
527
+ status: 502,
528
+ code: "invalid_response",
529
+ details: { contentType: ct, preview: raw.slice(0, 200) }
530
+ });
531
+ }
532
+ if (!parsed) {
533
+ return {};
534
+ }
535
+ return parsed;
536
+ }
537
+ safeParseJson(value) {
538
+ try {
539
+ return JSON.parse(value);
540
+ } catch {
541
+ return null;
542
+ }
543
+ }
544
+ asError(error) {
545
+ if (error instanceof PmtHouseError) {
546
+ return error;
547
+ }
548
+ if (error instanceof Error) {
549
+ return new PmtHouseError(error.message, {
550
+ code: "unexpected_error",
551
+ status: 500
552
+ });
553
+ }
554
+ return new PmtHouseError("Unexpected error", {
555
+ code: "unexpected_error",
556
+ status: 500
557
+ });
558
+ }
559
+ };
560
+
561
+ // src/env.ts
562
+ function assertEnvModuleServerOnly() {
563
+ if (typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined") {
564
+ throw new Error(
565
+ "@pymthouse/builder-sdk/env is server-only: do not import createPmtHouseClientFromEnv or getPymthouseBaseUrl in client-side code. Use a Route Handler, Server Action, or other server/runtime; keep M2M credentials out of the browser bundle."
566
+ );
567
+ }
568
+ }
569
+ assertEnvModuleServerOnly();
570
+ var cachedClient = null;
571
+ function requiredEnv(name) {
572
+ const value = process.env[name];
573
+ if (value && value.trim()) {
574
+ return value.trim();
575
+ }
576
+ throw new PmtHouseError(`Missing required environment variable: ${name}`, {
577
+ status: 500,
578
+ code: "missing_env"
579
+ });
580
+ }
581
+ function getPymthouseBaseUrl() {
582
+ const issuerUrl = requiredEnv("PYMTHOUSE_ISSUER_URL");
583
+ return new URL(stripTrailingSlashes(issuerUrl)).origin;
584
+ }
585
+ function createPmtHouseClientFromEnv() {
586
+ if (cachedClient) {
587
+ return cachedClient;
588
+ }
589
+ const issuerUrl = requiredEnv("PYMTHOUSE_ISSUER_URL");
590
+ cachedClient = new PmtHouseClient({
591
+ issuerUrl,
592
+ publicClientId: requiredEnv("PYMTHOUSE_PUBLIC_CLIENT_ID"),
593
+ m2mClientId: requiredEnv("PYMTHOUSE_M2M_CLIENT_ID"),
594
+ m2mClientSecret: requiredEnv("PYMTHOUSE_M2M_CLIENT_SECRET"),
595
+ allowInsecureHttp: issuerUrl.startsWith("http:"),
596
+ logger: {
597
+ debug: (message, details) => {
598
+ if (process.env.NODE_ENV !== "production") {
599
+ console.debug(`[pymthouse] ${message}`, details ?? {});
600
+ }
601
+ },
602
+ warn: (message, details) => {
603
+ console.warn(`[pymthouse] ${message}`, details ?? {});
604
+ }
605
+ }
606
+ });
607
+ return cachedClient;
608
+ }
609
+
610
+ export { createPmtHouseClientFromEnv, getPymthouseBaseUrl };
611
+ //# sourceMappingURL=env.js.map
612
+ //# sourceMappingURL=env.js.map