@pymthouse/builder-sdk 0.0.8 → 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.
Files changed (85) hide show
  1. package/README.md +106 -2
  2. package/dist/client-BHfjDvIe.d.ts +129 -0
  3. package/dist/client-CvhJEhjV.d.cts +129 -0
  4. package/dist/config.cjs +122 -0
  5. package/dist/config.cjs.map +1 -0
  6. package/dist/config.d.cts +29 -0
  7. package/dist/config.d.ts +29 -0
  8. package/dist/config.js +111 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/device-initiate.cjs +88 -0
  11. package/dist/device-initiate.cjs.map +1 -0
  12. package/dist/device-initiate.d.cts +32 -0
  13. package/dist/device-initiate.d.ts +32 -0
  14. package/dist/device-initiate.js +84 -0
  15. package/dist/device-initiate.js.map +1 -0
  16. package/dist/device.cjs +1 -1
  17. package/dist/device.cjs.map +1 -1
  18. package/dist/device.d.cts +1 -1
  19. package/dist/device.d.ts +1 -1
  20. package/dist/device.js +1 -1
  21. package/dist/device.js.map +1 -1
  22. package/dist/env.cjs +1071 -28
  23. package/dist/env.cjs.map +1 -1
  24. package/dist/env.d.cts +15 -2
  25. package/dist/env.d.ts +15 -2
  26. package/dist/env.js +1071 -28
  27. package/dist/env.js.map +1 -1
  28. package/dist/gateway/client/index.cjs +492 -0
  29. package/dist/gateway/client/index.cjs.map +1 -0
  30. package/dist/gateway/client/index.d.cts +63 -0
  31. package/dist/gateway/client/index.d.ts +63 -0
  32. package/dist/gateway/client/index.js +489 -0
  33. package/dist/gateway/client/index.js.map +1 -0
  34. package/dist/gateway/index.cjs +16 -0
  35. package/dist/gateway/index.cjs.map +1 -0
  36. package/dist/gateway/index.d.cts +52 -0
  37. package/dist/gateway/index.d.ts +52 -0
  38. package/dist/gateway/index.js +10 -0
  39. package/dist/gateway/index.js.map +1 -0
  40. package/dist/gateway/server/index.cjs +1248 -0
  41. package/dist/gateway/server/index.cjs.map +1 -0
  42. package/dist/gateway/server/index.d.cts +31 -0
  43. package/dist/gateway/server/index.d.ts +31 -0
  44. package/dist/gateway/server/index.js +1233 -0
  45. package/dist/gateway/server/index.js.map +1 -0
  46. package/dist/index.cjs +1401 -137
  47. package/dist/index.cjs.map +1 -1
  48. package/dist/index.d.cts +41 -5
  49. package/dist/index.d.ts +41 -5
  50. package/dist/index.js +1334 -105
  51. package/dist/index.js.map +1 -1
  52. package/dist/ingest-B3Yi8Tb1.d.cts +271 -0
  53. package/dist/ingest-DoKJTWU9.d.ts +271 -0
  54. package/dist/plan-pricing.cjs +108 -0
  55. package/dist/plan-pricing.cjs.map +1 -0
  56. package/dist/plan-pricing.d.cts +15 -0
  57. package/dist/plan-pricing.d.ts +15 -0
  58. package/dist/plan-pricing.js +98 -0
  59. package/dist/plan-pricing.js.map +1 -0
  60. package/dist/signer/server.cjs +1366 -0
  61. package/dist/signer/server.cjs.map +1 -0
  62. package/dist/signer/server.d.cts +73 -0
  63. package/dist/signer/server.d.ts +73 -0
  64. package/dist/signer/server.js +1331 -0
  65. package/dist/signer/server.js.map +1 -0
  66. package/dist/tokens.cjs +75 -0
  67. package/dist/tokens.cjs.map +1 -0
  68. package/dist/tokens.d.cts +48 -0
  69. package/dist/tokens.d.ts +48 -0
  70. package/dist/tokens.js +64 -0
  71. package/dist/tokens.js.map +1 -0
  72. package/dist/types-_R1AwEZp.d.cts +343 -0
  73. package/dist/types-_R1AwEZp.d.ts +343 -0
  74. package/dist/verify.cjs +1 -1
  75. package/dist/verify.cjs.map +1 -1
  76. package/dist/verify.d.cts +1 -1
  77. package/dist/verify.d.ts +1 -1
  78. package/dist/verify.js +1 -1
  79. package/dist/verify.js.map +1 -1
  80. package/gateway/proto/lp_rpc.proto +542 -0
  81. package/package.json +57 -1
  82. package/dist/env-4YmzarGJ.d.ts +0 -68
  83. package/dist/env-CZczUMzR.d.cts +0 -68
  84. package/dist/types-W9PJAspR.d.cts +0 -136
  85. package/dist/types-W9PJAspR.d.ts +0 -136
package/dist/index.js CHANGED
@@ -1,38 +1,15 @@
1
1
  import { discoveryRequest, processDiscoveryResponse, genericTokenEndpointRequest, processGenericTokenEndpointResponse, clientCredentialsGrantRequest, processClientCredentialsResponse, customFetch, allowInsecureRequests, ResponseBodyError, OperationProcessingError } from 'oauth4webapi';
2
+ import { createHash } from 'crypto';
2
3
 
3
- // src/usage.ts
4
- function aggregateUsageByExternalUserId(byUser, externalUserId) {
5
- const rows = byUser?.filter((row) => row.externalUserId === externalUserId) ?? [];
6
- if (rows.length === 0) {
7
- return {
8
- externalUserId,
9
- requestCount: 0,
10
- feeWei: "0"
11
- };
12
- }
13
- let feeWei = 0n;
14
- let requestCount = 0;
15
- for (const row of rows) {
16
- feeWei += BigInt(row.feeWei);
17
- requestCount += row.requestCount;
18
- }
19
- return {
20
- externalUserId,
21
- requestCount,
22
- feeWei: feeWei.toString()
23
- };
24
- }
25
- function summarizeUsageForExternalUser(usage, externalUserId) {
26
- return aggregateUsageByExternalUserId(usage.byUser, externalUserId);
27
- }
28
- function listUsageByPipelineModel(usage) {
29
- const rows = usage.byPipelineModel ?? [];
30
- return [...rows].sort((a, b) => {
31
- const p = a.pipeline.localeCompare(b.pipeline);
32
- if (p !== 0) return p;
33
- return a.modelId.localeCompare(b.modelId);
34
- });
35
- }
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __esm = (fn, res) => function __init() {
7
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ };
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
36
13
 
37
14
  // src/encoding.ts
38
15
  function encodeClientSecretBasic(clientId, clientSecret) {
@@ -40,24 +17,12 @@ function encodeClientSecretBasic(clientId, clientSecret) {
40
17
  const b64 = typeof Buffer !== "undefined" ? Buffer.from(raw, "utf8").toString("base64") : btoa(Array.from(new TextEncoder().encode(raw), (c) => String.fromCharCode(c)).join(""));
41
18
  return `Basic ${b64}`;
42
19
  }
20
+ var init_encoding = __esm({
21
+ "src/encoding.ts"() {
22
+ }
23
+ });
43
24
 
44
25
  // src/errors.ts
45
- var PmtHouseError = class extends Error {
46
- status;
47
- code;
48
- details;
49
- constructor(message, {
50
- status = 500,
51
- code = "pymthouse_error",
52
- details
53
- } = {}) {
54
- super(message);
55
- this.name = "PmtHouseError";
56
- this.status = status;
57
- this.code = code;
58
- this.details = details;
59
- }
60
- };
61
26
  function toPmtHouseError(error, fallbackMessage) {
62
27
  if (error instanceof PmtHouseError) {
63
28
  return error;
@@ -73,17 +38,68 @@ function toPmtHouseError(error, fallbackMessage) {
73
38
  status: 500
74
39
  });
75
40
  }
41
+ var PmtHouseError;
42
+ var init_errors = __esm({
43
+ "src/errors.ts"() {
44
+ PmtHouseError = class extends Error {
45
+ status;
46
+ code;
47
+ details;
48
+ constructor(message, {
49
+ status = 500,
50
+ code = "pymthouse_error",
51
+ details
52
+ } = {}) {
53
+ super(message);
54
+ this.name = "PmtHouseError";
55
+ this.status = status;
56
+ this.code = code;
57
+ this.details = details;
58
+ }
59
+ };
60
+ }
61
+ });
76
62
 
77
63
  // src/string-utils.ts
78
64
  function stripTrailingSlashes(value) {
79
65
  let end = value.length;
80
- while (end > 0 && value.charCodeAt(end - 1) === 47) {
66
+ while (end > 0 && (value.codePointAt(end - 1) ?? 0) === 47) {
81
67
  end--;
82
68
  }
83
69
  return value.slice(0, end);
84
70
  }
85
-
86
- // src/discovery.ts
71
+ function endsWithIgnoreCase(value, suffix) {
72
+ if (suffix.length > value.length) {
73
+ return false;
74
+ }
75
+ const start = value.length - suffix.length;
76
+ for (let i = 0; i < suffix.length; i++) {
77
+ const a = value.codePointAt(start + i) ?? 0;
78
+ const b = suffix.codePointAt(i) ?? 0;
79
+ if (a !== b && (a | 32) !== (b | 32)) {
80
+ return false;
81
+ }
82
+ }
83
+ return true;
84
+ }
85
+ function stripSuffixIgnoreCase(value, suffix) {
86
+ return endsWithIgnoreCase(value, suffix) ? value.slice(0, value.length - suffix.length) : value;
87
+ }
88
+ function stripOidcPathSuffix(issuerUrl) {
89
+ let base = stripTrailingSlashes(issuerUrl.trim());
90
+ base = stripSuffixIgnoreCase(base, "/oidc");
91
+ return stripTrailingSlashes(base);
92
+ }
93
+ function stripIssuerOriginFromOidcUrl(issuerUrl) {
94
+ let base = stripTrailingSlashes(issuerUrl.trim());
95
+ base = stripSuffixIgnoreCase(base, "/api/v1/oidc");
96
+ base = stripSuffixIgnoreCase(base, "/oidc");
97
+ return stripTrailingSlashes(base);
98
+ }
99
+ var init_string_utils = __esm({
100
+ "src/string-utils.ts"() {
101
+ }
102
+ });
87
103
  function authorizationServerToOidcDocument(as) {
88
104
  const tokenEndpoint = as.token_endpoint;
89
105
  const jwksUri = as.jwks_uri;
@@ -102,8 +118,6 @@ function authorizationServerToOidcDocument(as) {
102
118
  device_authorization_endpoint: as.device_authorization_endpoint
103
119
  };
104
120
  }
105
- var CACHE_TTL_MS = 5 * 60 * 1e3;
106
- var discoveryCache = /* @__PURE__ */ new Map();
107
121
  function normalizedIssuerKey(issuerUrl) {
108
122
  return stripTrailingSlashes(issuerUrl);
109
123
  }
@@ -179,6 +193,909 @@ function mapDiscoveryNetworkError(error) {
179
193
  code: "oidc_discovery_failed"
180
194
  });
181
195
  }
196
+ var CACHE_TTL_MS, discoveryCache;
197
+ var init_discovery = __esm({
198
+ "src/discovery.ts"() {
199
+ init_errors();
200
+ init_string_utils();
201
+ CACHE_TTL_MS = 5 * 60 * 1e3;
202
+ discoveryCache = /* @__PURE__ */ new Map();
203
+ }
204
+ });
205
+
206
+ // src/signer/fetch-json.ts
207
+ function oauthFailureDescription(parsed, failureLabel, status) {
208
+ if (typeof parsed.error_description === "string") {
209
+ return parsed.error_description;
210
+ }
211
+ if (typeof parsed.error === "string") {
212
+ return parsed.error;
213
+ }
214
+ return `${failureLabel} (${status})`;
215
+ }
216
+ async function readJsonObjectFromResponse(response, options) {
217
+ const text = await response.text();
218
+ let parsed;
219
+ try {
220
+ parsed = text ? JSON.parse(text) : {};
221
+ } catch {
222
+ throw new PmtHouseError(options.invalidJsonMessage, {
223
+ status: 502,
224
+ code: options.invalidJsonCode,
225
+ details: { status: response.status }
226
+ });
227
+ }
228
+ if (!response.ok) {
229
+ const description = oauthFailureDescription(parsed, options.failureLabel, response.status);
230
+ throw new PmtHouseError(description, {
231
+ status: response.status,
232
+ code: typeof parsed.error === "string" ? parsed.error : options.defaultErrorCode,
233
+ details: parsed
234
+ });
235
+ }
236
+ return parsed;
237
+ }
238
+ var init_fetch_json = __esm({
239
+ "src/signer/fetch-json.ts"() {
240
+ init_errors();
241
+ }
242
+ });
243
+
244
+ // src/signer/handler-errors.ts
245
+ function signerHandlerErrorResponse(error) {
246
+ if (error instanceof PmtHouseError) {
247
+ return new Response(
248
+ JSON.stringify({
249
+ error: error.code,
250
+ error_description: error.message,
251
+ details: error.details
252
+ }),
253
+ {
254
+ status: error.status,
255
+ headers: { "Content-Type": "application/json" }
256
+ }
257
+ );
258
+ }
259
+ const message = error instanceof Error ? error.message : "Internal error";
260
+ return new Response(JSON.stringify({ error: "internal_error", error_description: message }), {
261
+ status: 500,
262
+ headers: { "Content-Type": "application/json" }
263
+ });
264
+ }
265
+ var init_handler_errors = __esm({
266
+ "src/signer/handler-errors.ts"() {
267
+ init_errors();
268
+ }
269
+ });
270
+
271
+ // src/signer/json-fields.ts
272
+ function readStringField(body, key, errorCode, messagePrefix = "Response") {
273
+ const value = body[key];
274
+ if (typeof value !== "string" || !value.trim()) {
275
+ throw new PmtHouseError(`${messagePrefix} missing ${key}`, {
276
+ status: 502,
277
+ code: errorCode
278
+ });
279
+ }
280
+ return value.trim();
281
+ }
282
+ function readExpiresIn(body, errorCode) {
283
+ const expiresIn = body.expires_in;
284
+ if (typeof expiresIn !== "number" || !Number.isFinite(expiresIn) || expiresIn <= 0) {
285
+ throw new PmtHouseError("Response missing expires_in", {
286
+ status: 502,
287
+ code: errorCode
288
+ });
289
+ }
290
+ return Math.floor(expiresIn);
291
+ }
292
+ var init_json_fields = __esm({
293
+ "src/signer/json-fields.ts"() {
294
+ init_errors();
295
+ }
296
+ });
297
+
298
+ // src/signer/mint-token.ts
299
+ function parseMintUserSignerTokenResponse(body, ttlRefreshRatio = DEFAULT_TTL_REFRESH_RATIO) {
300
+ const accessToken = readStringField(body, "access_token", TOKEN_RESPONSE_ERROR, "Token response");
301
+ const expiresIn = readExpiresIn(body, TOKEN_RESPONSE_ERROR);
302
+ const balanceUsdMicros = readStringField(
303
+ body,
304
+ "balanceUsdMicros",
305
+ TOKEN_RESPONSE_ERROR,
306
+ "Token response"
307
+ );
308
+ const lifetimeGrantedUsdMicros = readStringField(
309
+ body,
310
+ "lifetimeGrantedUsdMicros",
311
+ TOKEN_RESPONSE_ERROR,
312
+ "Token response"
313
+ );
314
+ const now = Date.now();
315
+ const expiresAt = now + expiresIn * 1e3;
316
+ const refreshAt = now + Math.floor(expiresIn * 1e3 * ttlRefreshRatio);
317
+ return {
318
+ jwt: accessToken,
319
+ expiresAt,
320
+ refreshAt,
321
+ balanceUsdMicros,
322
+ lifetimeGrantedUsdMicros
323
+ };
324
+ }
325
+ var LIVEPEER_REMOTE_SIGNER_AUDIENCE, DEFAULT_TTL_REFRESH_RATIO, TOKEN_RESPONSE_ERROR;
326
+ var init_mint_token = __esm({
327
+ "src/signer/mint-token.ts"() {
328
+ init_json_fields();
329
+ LIVEPEER_REMOTE_SIGNER_AUDIENCE = "livepeer-remote-signer";
330
+ DEFAULT_TTL_REFRESH_RATIO = 0.8;
331
+ TOKEN_RESPONSE_ERROR = "invalid_token_response";
332
+ }
333
+ });
334
+
335
+ // src/signer/device-exchange.ts
336
+ function extractSignerAccessTokenFromExchangeBody(body) {
337
+ const tokenObj = body.token;
338
+ if (tokenObj !== null && typeof tokenObj === "object" && !Array.isArray(tokenObj)) {
339
+ const nested = tokenObj;
340
+ for (const key of ["accessToken", "access_token"]) {
341
+ const value = nested[key];
342
+ if (typeof value === "string" && value.trim()) {
343
+ return value.trim();
344
+ }
345
+ }
346
+ }
347
+ for (const key of ["accessToken", "access_token"]) {
348
+ const value = body[key];
349
+ if (typeof value === "string" && value.trim()) {
350
+ return value.trim();
351
+ }
352
+ }
353
+ throw new PmtHouseError("Device exchange response missing signer access token", {
354
+ status: 502,
355
+ code: "invalid_exchange_response"
356
+ });
357
+ }
358
+ function normalizeDeviceExchangeResponse(minted, options) {
359
+ const scope = minted.scope.trim() || "sign:job";
360
+ const body = {
361
+ access_token: minted.access_token,
362
+ token_type: "Bearer",
363
+ expires_in: minted.expires_in,
364
+ scope,
365
+ balanceUsdMicros: minted.balanceUsdMicros,
366
+ lifetimeGrantedUsdMicros: minted.lifetimeGrantedUsdMicros,
367
+ token: {
368
+ accessToken: minted.access_token,
369
+ access_token: minted.access_token,
370
+ expiresIn: minted.expires_in,
371
+ expires_in: minted.expires_in,
372
+ scope,
373
+ balanceUsdMicros: minted.balanceUsdMicros,
374
+ lifetimeGrantedUsdMicros: minted.lifetimeGrantedUsdMicros
375
+ }
376
+ };
377
+ const signerUrl = options?.signerUrl?.trim();
378
+ if (signerUrl) {
379
+ body.signerUrl = signerUrl;
380
+ }
381
+ return body;
382
+ }
383
+ async function mintSignerTokenFromDeviceToken(options) {
384
+ const fetchImpl = options.fetch ?? fetch;
385
+ const issuerUrl = stripTrailingSlashes(options.issuerUrl);
386
+ const as = await loadAuthorizationServer(issuerUrl, fetchImpl, {
387
+ allowInsecureHttp: options.allowInsecureHttp
388
+ });
389
+ const tokenEndpoint = as.token_endpoint;
390
+ if (!tokenEndpoint) {
391
+ throw new PmtHouseError("OIDC discovery document is missing token_endpoint", {
392
+ status: 500,
393
+ code: "oidc_discovery_invalid"
394
+ });
395
+ }
396
+ const audience = options.audience?.trim() || LIVEPEER_REMOTE_SIGNER_AUDIENCE;
397
+ const params = new URLSearchParams({
398
+ grant_type: TOKEN_EXCHANGE_GRANT,
399
+ subject_token: options.deviceToken,
400
+ subject_token_type: SUBJECT_ACCESS_TOKEN_TYPE,
401
+ audience,
402
+ resource: audience
403
+ });
404
+ if (options.scope?.trim()) {
405
+ params.set("scope", options.scope.trim());
406
+ }
407
+ const response = await fetchImpl(tokenEndpoint, {
408
+ method: "POST",
409
+ headers: {
410
+ Authorization: encodeClientSecretBasic(options.m2mClientId, options.m2mClientSecret),
411
+ "Content-Type": "application/x-www-form-urlencoded",
412
+ Accept: "application/json"
413
+ },
414
+ body: params.toString(),
415
+ cache: "no-store"
416
+ });
417
+ const parsed = await readJsonObjectFromResponse(response, {
418
+ invalidJsonMessage: "Token endpoint returned invalid JSON",
419
+ invalidJsonCode: "invalid_token_response",
420
+ failureLabel: "Signer JWT exchange failed",
421
+ defaultErrorCode: "token_exchange_failed"
422
+ });
423
+ const cached = parseMintUserSignerTokenResponse(parsed);
424
+ return {
425
+ access_token: cached.jwt,
426
+ expires_in: readExpiresIn(parsed, EXCHANGE_RESPONSE_ERROR),
427
+ scope: readStringField(parsed, "scope", EXCHANGE_RESPONSE_ERROR),
428
+ balanceUsdMicros: cached.balanceUsdMicros,
429
+ lifetimeGrantedUsdMicros: cached.lifetimeGrantedUsdMicros
430
+ };
431
+ }
432
+ var TOKEN_EXCHANGE_GRANT, SUBJECT_ACCESS_TOKEN_TYPE, EXCHANGE_RESPONSE_ERROR;
433
+ var init_device_exchange = __esm({
434
+ "src/signer/device-exchange.ts"() {
435
+ init_discovery();
436
+ init_encoding();
437
+ init_errors();
438
+ init_string_utils();
439
+ init_fetch_json();
440
+ init_json_fields();
441
+ init_mint_token();
442
+ TOKEN_EXCHANGE_GRANT = "urn:ietf:params:oauth:grant-type:token-exchange";
443
+ SUBJECT_ACCESS_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token";
444
+ EXCHANGE_RESPONSE_ERROR = "invalid_exchange_response";
445
+ }
446
+ });
447
+
448
+ // src/signer/api-key-exchange.ts
449
+ var api_key_exchange_exports = {};
450
+ __export(api_key_exchange_exports, {
451
+ createApiKeyExchangeHandler: () => createApiKeyExchangeHandler,
452
+ exchangeApiKeyForSigner: () => exchangeApiKeyForSigner,
453
+ mintSignerSessionFromApiKey: () => mintSignerSessionFromApiKey,
454
+ mintUserAccessTokenFromApiKey: () => mintUserAccessTokenFromApiKey,
455
+ parseApiKeyExchangeRequestBody: () => parseApiKeyExchangeRequestBody
456
+ });
457
+ async function parseApiKeyExchangeRequestBody(request) {
458
+ let body;
459
+ try {
460
+ body = await request.json();
461
+ } catch {
462
+ throw new PmtHouseError("Request body must be JSON", {
463
+ status: 400,
464
+ code: "invalid_request"
465
+ });
466
+ }
467
+ if (body === null || typeof body !== "object" || Array.isArray(body)) {
468
+ throw new PmtHouseError("Request body must be a JSON object", {
469
+ status: 400,
470
+ code: "invalid_request"
471
+ });
472
+ }
473
+ const record = body;
474
+ const apiKeyRaw = record.apiKey;
475
+ if (typeof apiKeyRaw !== "string" || !apiKeyRaw.trim()) {
476
+ throw new PmtHouseError("Request body must include apiKey", {
477
+ status: 400,
478
+ code: "invalid_request"
479
+ });
480
+ }
481
+ const scope = typeof record.scope === "string" && record.scope.trim() ? record.scope.trim() : void 0;
482
+ const clientId = typeof record.clientId === "string" && record.clientId.trim() ? record.clientId.trim() : void 0;
483
+ return { apiKey: apiKeyRaw.trim(), scope, clientId };
484
+ }
485
+ async function mintUserAccessTokenFromApiKey(input) {
486
+ const fetchImpl = input.fetch ?? fetch;
487
+ const issuerOrigin = stripIssuerOriginFromOidcUrl(input.issuerUrl);
488
+ const url = `${issuerOrigin}/api/v1/apps/${encodeURIComponent(input.publicClientId)}/auth/api-key/token`;
489
+ const response = await fetchImpl(url, {
490
+ method: "POST",
491
+ headers: {
492
+ Authorization: `Bearer ${input.apiKey}`,
493
+ "Content-Type": "application/json",
494
+ Accept: "application/json"
495
+ },
496
+ body: JSON.stringify(input.scope ? { scope: input.scope } : {}),
497
+ cache: "no-store"
498
+ });
499
+ const parsed = await readJsonObjectFromResponse(response, {
500
+ invalidJsonMessage: "API key token exchange returned invalid JSON",
501
+ invalidJsonCode: "invalid_token_response",
502
+ failureLabel: "API key token exchange failed",
503
+ defaultErrorCode: "api_key_token_exchange_failed"
504
+ });
505
+ const accessToken = parsed.access_token;
506
+ if (typeof accessToken !== "string" || !accessToken.trim()) {
507
+ throw new PmtHouseError("API key token exchange missing access_token", {
508
+ status: 502,
509
+ code: EXCHANGE_RESPONSE_ERROR2
510
+ });
511
+ }
512
+ const expiresIn = typeof parsed.expires_in === "number" && Number.isFinite(parsed.expires_in) ? parsed.expires_in : 900;
513
+ const scope = typeof parsed.scope === "string" && parsed.scope.trim() ? parsed.scope.trim() : input.scope?.trim() || "sign:job";
514
+ return {
515
+ access_token: accessToken.trim(),
516
+ expires_in: expiresIn,
517
+ scope
518
+ };
519
+ }
520
+ async function mintSignerSessionFromApiKey(input) {
521
+ const userToken = await mintUserAccessTokenFromApiKey({
522
+ issuerUrl: input.issuerUrl,
523
+ publicClientId: input.publicClientId,
524
+ apiKey: input.apiKey,
525
+ scope: input.scope,
526
+ fetch: input.fetch
527
+ });
528
+ return mintSignerTokenFromDeviceToken({
529
+ issuerUrl: input.issuerUrl,
530
+ m2mClientId: input.m2mClientId,
531
+ m2mClientSecret: input.m2mClientSecret,
532
+ deviceToken: userToken.access_token,
533
+ scope: userToken.scope,
534
+ audience: input.audience,
535
+ fetch: input.fetch,
536
+ allowInsecureHttp: input.allowInsecureHttp
537
+ });
538
+ }
539
+ async function exchangeApiKeyForSigner(options) {
540
+ const fetchImpl = options.fetch ?? fetch;
541
+ const url = `${stripTrailingSlashes(options.facadeUrl)}/api/pymthouse/keys/exchange`;
542
+ const body = { apiKey: options.apiKey };
543
+ if (options.scope?.trim()) {
544
+ body.scope = options.scope.trim();
545
+ }
546
+ if (options.clientId?.trim()) {
547
+ body.clientId = options.clientId.trim();
548
+ }
549
+ const response = await fetchImpl(url, {
550
+ method: "POST",
551
+ headers: {
552
+ "Content-Type": "application/json",
553
+ Accept: "application/json"
554
+ },
555
+ body: JSON.stringify(body),
556
+ cache: "no-store"
557
+ });
558
+ const parsed = await readJsonObjectFromResponse(response, {
559
+ invalidJsonMessage: "API key exchange returned invalid JSON",
560
+ invalidJsonCode: EXCHANGE_RESPONSE_ERROR2,
561
+ failureLabel: "API key exchange failed",
562
+ defaultErrorCode: "api_key_exchange_failed"
563
+ });
564
+ const accessToken = extractSignerAccessTokenFromExchangeBody(parsed);
565
+ const signerUrlRaw = parsed.signerUrl ?? parsed.signer_url;
566
+ const signerUrl = typeof signerUrlRaw === "string" && signerUrlRaw.trim() ? signerUrlRaw.trim() : void 0;
567
+ return normalizeDeviceExchangeResponse(
568
+ {
569
+ access_token: accessToken,
570
+ expires_in: typeof parsed.expires_in === "number" && Number.isFinite(parsed.expires_in) ? parsed.expires_in : 3600,
571
+ scope: typeof parsed.scope === "string" && parsed.scope.trim() ? parsed.scope.trim() : "sign:job",
572
+ balanceUsdMicros: typeof parsed.balanceUsdMicros === "string" ? parsed.balanceUsdMicros : "0",
573
+ lifetimeGrantedUsdMicros: typeof parsed.lifetimeGrantedUsdMicros === "string" ? parsed.lifetimeGrantedUsdMicros : "0"
574
+ },
575
+ { signerUrl }
576
+ );
577
+ }
578
+ function createApiKeyExchangeHandler(config) {
579
+ const publicClientId = config.publicClientId.trim();
580
+ return async function apiKeyExchangeHandler(request) {
581
+ try {
582
+ if (request.method !== "POST") {
583
+ return new Response(JSON.stringify({ error: "method_not_allowed" }), {
584
+ status: 405,
585
+ headers: { "Content-Type": "application/json" }
586
+ });
587
+ }
588
+ const parsed = await parseApiKeyExchangeRequestBody(request);
589
+ const effectiveClientId = parsed.clientId?.trim() || publicClientId;
590
+ if (effectiveClientId !== publicClientId) {
591
+ throw new PmtHouseError("clientId does not match configured public client", {
592
+ status: 400,
593
+ code: "invalid_request"
594
+ });
595
+ }
596
+ const minted = await mintSignerSessionFromApiKey({
597
+ issuerUrl: config.issuerUrl,
598
+ publicClientId,
599
+ m2mClientId: config.m2mClientId,
600
+ m2mClientSecret: config.m2mClientSecret,
601
+ apiKey: parsed.apiKey,
602
+ scope: parsed.scope,
603
+ audience: config.audience,
604
+ fetch: config.fetch,
605
+ allowInsecureHttp: config.allowInsecureHttp
606
+ });
607
+ const signerUrlValue = typeof config.signerUrl === "string" && config.signerUrl.trim() ? config.signerUrl.trim() : void 0;
608
+ const body = normalizeDeviceExchangeResponse(minted, { signerUrl: signerUrlValue });
609
+ return new Response(JSON.stringify(body), {
610
+ status: 200,
611
+ headers: {
612
+ "Content-Type": "application/json",
613
+ "Cache-Control": "no-store"
614
+ }
615
+ });
616
+ } catch (error) {
617
+ return signerHandlerErrorResponse(error);
618
+ }
619
+ };
620
+ }
621
+ var EXCHANGE_RESPONSE_ERROR2;
622
+ var init_api_key_exchange = __esm({
623
+ "src/signer/api-key-exchange.ts"() {
624
+ init_string_utils();
625
+ init_errors();
626
+ init_fetch_json();
627
+ init_handler_errors();
628
+ init_device_exchange();
629
+ EXCHANGE_RESPONSE_ERROR2 = "invalid_exchange_response";
630
+ }
631
+ });
632
+
633
+ // src/plan-pricing.ts
634
+ var NETWORK_USD_PER_MICRO = 1e-6;
635
+ var RETAIL_RATE_DECIMALS = 9;
636
+ function trimFixedDecimalZeros(fixed) {
637
+ const dotIndex = fixed.indexOf(".");
638
+ if (dotIndex === -1) {
639
+ return fixed;
640
+ }
641
+ let end = fixed.length;
642
+ while (end > dotIndex + 1 && fixed[end - 1] === "0") {
643
+ end -= 1;
644
+ }
645
+ if (end === dotIndex + 1) {
646
+ end = dotIndex;
647
+ }
648
+ const trimmed = fixed.slice(0, end);
649
+ return trimmed.length > 0 ? trimmed : "0";
650
+ }
651
+ function defaultRetailRateUsd() {
652
+ return formatRetailRateUsd(NETWORK_USD_PER_MICRO);
653
+ }
654
+ function formatRetailRateUsd(value) {
655
+ if (!Number.isFinite(value) || value < 0) {
656
+ return defaultRetailRateUsd();
657
+ }
658
+ return trimFixedDecimalZeros(value.toFixed(RETAIL_RATE_DECIMALS));
659
+ }
660
+ function parseRetailRateUsd(raw) {
661
+ if (raw === null || raw === void 0) {
662
+ return null;
663
+ }
664
+ const trimmed = String(raw).trim();
665
+ if (!trimmed) {
666
+ return null;
667
+ }
668
+ const n = Number(trimmed);
669
+ if (!Number.isFinite(n) || n < 0) {
670
+ return null;
671
+ }
672
+ return formatRetailRateUsd(n);
673
+ }
674
+ function markupPercentToRetailRateUsd(markupPercent) {
675
+ const pct = Number.isFinite(markupPercent) ? Math.max(0, markupPercent) : 0;
676
+ return formatRetailRateUsd(NETWORK_USD_PER_MICRO * (1 + pct / 100));
677
+ }
678
+ function retailRateUsdToMarkupPercent(raw) {
679
+ const rate = parseRetailRateUsd(raw);
680
+ if (!rate) {
681
+ return "";
682
+ }
683
+ const n = Number(rate);
684
+ if (!Number.isFinite(n) || n <= NETWORK_USD_PER_MICRO) {
685
+ return n === NETWORK_USD_PER_MICRO ? "0" : "";
686
+ }
687
+ const pct = (n / NETWORK_USD_PER_MICRO - 1) * 100;
688
+ if (!Number.isFinite(pct) || pct <= 0) {
689
+ return "";
690
+ }
691
+ return pct % 1 === 0 ? String(Math.round(pct)) : pct.toFixed(1);
692
+ }
693
+ function retailRateUsdPerMillion(raw) {
694
+ const rate = parseRetailRateUsd(raw);
695
+ if (!rate) {
696
+ return "";
697
+ }
698
+ const perM = Number(rate) * 1e6;
699
+ if (!Number.isFinite(perM)) {
700
+ return "";
701
+ }
702
+ return perM.toFixed(2);
703
+ }
704
+ function parseMarkupPercentInput(raw) {
705
+ const trimmed = raw.trim();
706
+ if (!trimmed) {
707
+ return null;
708
+ }
709
+ const n = Number(trimmed);
710
+ if (!Number.isFinite(n) || n < 0) {
711
+ return null;
712
+ }
713
+ return n;
714
+ }
715
+ function applyRetailRateToNetworkMicros(networkFeeUsdMicros, retailRateUsd) {
716
+ const networkPerMicro = NETWORK_USD_PER_MICRO;
717
+ const retail = Number(retailRateUsd);
718
+ if (!Number.isFinite(retail) || retail <= 0) {
719
+ return networkFeeUsdMicros;
720
+ }
721
+ const ratio = retail / networkPerMicro;
722
+ if (!Number.isFinite(ratio) || ratio <= 0) {
723
+ return networkFeeUsdMicros;
724
+ }
725
+ return networkFeeUsdMicros * BigInt(Math.round(ratio * 1e6)) / 1000000n;
726
+ }
727
+
728
+ // src/ingest.ts
729
+ init_encoding();
730
+ init_errors();
731
+ init_string_utils();
732
+ function signerSnapshotToIngestPayload(input) {
733
+ return {
734
+ requestId: input.snapshot.requestId,
735
+ externalUserId: input.externalUserId,
736
+ networkFeeUsdMicros: input.snapshot.computedFeeUsdMicros.toString(),
737
+ feeWei: input.snapshot.computedFeeWei,
738
+ pixels: input.snapshot.pixels,
739
+ pipeline: input.snapshot.pipeline,
740
+ modelId: input.snapshot.modelId,
741
+ gatewayRequestId: input.gatewayRequestId,
742
+ ethUsdPrice: input.snapshot.ethUsdPrice,
743
+ ethUsdRoundId: input.snapshot.ethUsdRoundId,
744
+ ethUsdObservedAt: input.snapshot.ethUsdObservedAt
745
+ };
746
+ }
747
+ function ingestUrl(issuerUrl, publicClientId) {
748
+ const origin = new URL(stripTrailingSlashes(issuerUrl)).origin;
749
+ return `${origin}/api/v1/apps/${encodeURIComponent(publicClientId)}/usage/signed-tickets`;
750
+ }
751
+ async function readJsonResponse(response) {
752
+ const text = await response.text();
753
+ if (!text.trim()) {
754
+ return {};
755
+ }
756
+ try {
757
+ const parsed = JSON.parse(text);
758
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
759
+ } catch {
760
+ return {};
761
+ }
762
+ }
763
+ async function ingestSignedTicket(options) {
764
+ const fetchImpl = options.fetch ?? fetch;
765
+ const url = ingestUrl(options.issuerUrl, options.publicClientId);
766
+ const response = await fetchImpl(url, {
767
+ method: "POST",
768
+ headers: {
769
+ Authorization: encodeClientSecretBasic(options.m2mClientId, options.m2mClientSecret),
770
+ "Content-Type": "application/json",
771
+ Accept: "application/json"
772
+ },
773
+ body: JSON.stringify(options.ticket),
774
+ cache: "no-store"
775
+ });
776
+ const body = await readJsonResponse(response);
777
+ if (!response.ok) {
778
+ const message = typeof body.error === "string" ? body.error : `Signed-ticket ingest failed (${response.status})`;
779
+ throw new PmtHouseError(message, {
780
+ status: response.status,
781
+ code: "ingest_failed",
782
+ details: body
783
+ });
784
+ }
785
+ return {
786
+ ingested: Boolean(body.ingested),
787
+ duplicate: Boolean(body.duplicate),
788
+ source: body.source === "openmeter" ? "openmeter" : "disabled"
789
+ };
790
+ }
791
+ async function ingestSignedTicketsBatch(options) {
792
+ const fetchImpl = options.fetch ?? fetch;
793
+ const url = ingestUrl(options.issuerUrl, options.publicClientId);
794
+ const response = await fetchImpl(url, {
795
+ method: "POST",
796
+ headers: {
797
+ Authorization: encodeClientSecretBasic(options.m2mClientId, options.m2mClientSecret),
798
+ "Content-Type": "application/json",
799
+ Accept: "application/json"
800
+ },
801
+ body: JSON.stringify({ tickets: options.tickets }),
802
+ cache: "no-store"
803
+ });
804
+ const body = await readJsonResponse(response);
805
+ if (!response.ok) {
806
+ const message = typeof body.error === "string" ? body.error : `Signed-ticket batch ingest failed (${response.status})`;
807
+ throw new PmtHouseError(message, {
808
+ status: response.status,
809
+ code: "ingest_failed",
810
+ details: body
811
+ });
812
+ }
813
+ const rawResults = Array.isArray(body.results) ? body.results : [];
814
+ return {
815
+ results: rawResults.map((entry) => {
816
+ const row = entry ?? {};
817
+ return {
818
+ requestId: typeof row.requestId === "string" ? row.requestId : void 0,
819
+ ok: row.ok === true,
820
+ ingested: Boolean(row.ingested),
821
+ duplicate: Boolean(row.duplicate),
822
+ source: row.source === "openmeter" ? "openmeter" : "disabled"
823
+ };
824
+ })
825
+ };
826
+ }
827
+
828
+ // src/usage.ts
829
+ function parseSafeBigInt(value, fallback = 0n) {
830
+ try {
831
+ return BigInt(value);
832
+ } catch {
833
+ return fallback;
834
+ }
835
+ }
836
+ function getUtcCalendarMonthIsoBounds(now = /* @__PURE__ */ new Date()) {
837
+ const y = now.getUTCFullYear();
838
+ const m = now.getUTCMonth();
839
+ const start = new Date(Date.UTC(y, m, 1, 0, 0, 0, 0));
840
+ const end = new Date(Date.UTC(y, m + 1, 0, 23, 59, 59, 999));
841
+ return { startDate: start.toISOString(), endDate: end.toISOString() };
842
+ }
843
+ function parseUsageDateParam(raw) {
844
+ if (raw == null) return null;
845
+ const trimmed = raw.trim();
846
+ if (!trimmed) return null;
847
+ const t = Date.parse(trimmed);
848
+ if (Number.isNaN(t)) return null;
849
+ return trimmed;
850
+ }
851
+ function aggregateUsageByExternalUserId(byUser, externalUserId) {
852
+ const rows = byUser?.filter((row) => row.externalUserId === externalUserId) ?? [];
853
+ if (rows.length === 0) {
854
+ return {
855
+ externalUserId,
856
+ requestCount: 0,
857
+ feeWei: "0"
858
+ };
859
+ }
860
+ let feeWei = 0n;
861
+ let requestCount = 0;
862
+ for (const row of rows) {
863
+ if (row.feeWei) {
864
+ feeWei += BigInt(row.feeWei);
865
+ }
866
+ requestCount += row.requestCount;
867
+ }
868
+ return {
869
+ externalUserId,
870
+ requestCount,
871
+ feeWei: feeWei.toString()
872
+ };
873
+ }
874
+ function summarizeUsageForExternalUser(usage, externalUserId) {
875
+ return aggregateUsageByExternalUserId(usage.byUser, externalUserId);
876
+ }
877
+ function listUsageByPipelineModel(usage) {
878
+ const rows = usage.byPipelineModel ?? [];
879
+ return [...rows].sort((a, b) => {
880
+ const p = a.pipeline.localeCompare(b.pipeline);
881
+ if (p !== 0) return p;
882
+ return a.modelId.localeCompare(b.modelId);
883
+ });
884
+ }
885
+ function getEndUserIdsForExternalUser(usage, externalUserId) {
886
+ const userIds = /* @__PURE__ */ new Set();
887
+ for (const row of usage.byUser ?? []) {
888
+ if (row.externalUserId === externalUserId && row.endUserId !== "unknown") {
889
+ userIds.add(row.endUserId);
890
+ }
891
+ }
892
+ return [...userIds];
893
+ }
894
+ var getUsageRecordUserIdsForExternalUser = getEndUserIdsForExternalUser;
895
+ function summarizeUsageFiatForExternalUser(usageByUser, externalUserId) {
896
+ const rows = usageByUser.byUser ?? [];
897
+ let requestCount = 0;
898
+ let networkFeeUsdMicros = 0n;
899
+ let ownerChargeUsdMicros = 0n;
900
+ let endUserBillableUsdMicros = 0n;
901
+ let currency = "USD";
902
+ for (const row of rows) {
903
+ if (row.externalUserId !== externalUserId) continue;
904
+ requestCount += row.requestCount;
905
+ if (row.currency) currency = row.currency;
906
+ if (row.networkFeeUsdMicros) {
907
+ networkFeeUsdMicros += BigInt(row.networkFeeUsdMicros);
908
+ }
909
+ if (row.ownerChargeUsdMicros) {
910
+ ownerChargeUsdMicros += BigInt(row.ownerChargeUsdMicros);
911
+ }
912
+ if (row.endUserBillableUsdMicros) {
913
+ endUserBillableUsdMicros += BigInt(row.endUserBillableUsdMicros);
914
+ }
915
+ }
916
+ return {
917
+ externalUserId,
918
+ requestCount,
919
+ currency,
920
+ networkFeeUsdMicros: networkFeeUsdMicros.toString(),
921
+ ownerChargeUsdMicros: ownerChargeUsdMicros.toString(),
922
+ endUserBillableUsdMicros: endUserBillableUsdMicros.toString()
923
+ };
924
+ }
925
+ function mergeUsageByPipelineModel(usagePipelineModels) {
926
+ let responses;
927
+ if (Array.isArray(usagePipelineModels)) {
928
+ responses = usagePipelineModels;
929
+ } else if (usagePipelineModels) {
930
+ responses = [usagePipelineModels];
931
+ } else {
932
+ responses = [];
933
+ }
934
+ const byKey = /* @__PURE__ */ new Map();
935
+ for (const response of responses) {
936
+ for (const row of response.byPipelineModel ?? []) {
937
+ const { pipeline, modelId } = row;
938
+ if (!pipeline || !modelId) continue;
939
+ const key = JSON.stringify([pipeline, modelId]);
940
+ const existing = byKey.get(key);
941
+ const rowCurrency = row.currency ?? "USD";
942
+ const networkFee = row.networkFeeUsdMicros ?? "0";
943
+ const ownerCharge = row.ownerChargeUsdMicros ?? "0";
944
+ const endUserBillable = row.endUserBillableUsdMicros ?? "0";
945
+ if (!existing) {
946
+ byKey.set(key, {
947
+ pipeline,
948
+ modelId,
949
+ requestCount: row.requestCount,
950
+ currency: rowCurrency,
951
+ networkFeeUsdMicros: networkFee,
952
+ ownerChargeUsdMicros: ownerCharge,
953
+ endUserBillableUsdMicros: endUserBillable
954
+ });
955
+ continue;
956
+ }
957
+ byKey.set(key, {
958
+ ...existing,
959
+ requestCount: existing.requestCount + row.requestCount,
960
+ networkFeeUsdMicros: (parseSafeBigInt(existing.networkFeeUsdMicros) + parseSafeBigInt(networkFee)).toString(),
961
+ ownerChargeUsdMicros: (parseSafeBigInt(existing.ownerChargeUsdMicros) + parseSafeBigInt(ownerCharge)).toString(),
962
+ endUserBillableUsdMicros: (parseSafeBigInt(existing.endUserBillableUsdMicros) + parseSafeBigInt(endUserBillable)).toString()
963
+ });
964
+ }
965
+ }
966
+ return [...byKey.values()].sort((a, b) => {
967
+ if (a.pipeline === b.pipeline) return a.modelId.localeCompare(b.modelId);
968
+ return a.pipeline.localeCompare(b.pipeline);
969
+ });
970
+ }
971
+ function buildMeScopeUsagePayload(usageByUser, externalUserId, usagePipelineModel, usageDaily) {
972
+ const summary = summarizeUsageFiatForExternalUser(usageByUser, externalUserId);
973
+ const pipelineModels = mergeUsageByPipelineModel(usagePipelineModel);
974
+ const dailyByPipeline = usageDaily?.byDailyPipeline ?? [];
975
+ return {
976
+ clientId: usageByUser.clientId,
977
+ period: usageByUser.period,
978
+ currentUser: {
979
+ externalUserId: summary.externalUserId,
980
+ requestCount: summary.requestCount,
981
+ currency: summary.currency,
982
+ networkFeeUsdMicros: summary.networkFeeUsdMicros,
983
+ ownerChargeUsdMicros: summary.ownerChargeUsdMicros,
984
+ endUserBillableUsdMicros: summary.endUserBillableUsdMicros,
985
+ pipelineModels,
986
+ dailyByPipeline
987
+ }
988
+ };
989
+ }
990
+ var DEFAULT_MAX_END_USER_IDS = 25;
991
+
992
+ // src/client.ts
993
+ init_encoding();
994
+ init_discovery();
995
+ init_errors();
996
+ function parseAppManifestResponse(json) {
997
+ if (!json || typeof json !== "object" || Array.isArray(json)) {
998
+ return { capabilities: [], excludedCapabilities: [] };
999
+ }
1000
+ const record = json;
1001
+ const capabilities = parseCapabilityArray(record.capabilities);
1002
+ const excludedCapabilities = parseCapabilityArray(record.excludedCapabilities);
1003
+ const manifestVersion = typeof record.manifestVersion === "string" && record.manifestVersion.trim() ? record.manifestVersion.trim() : void 0;
1004
+ return { capabilities, excludedCapabilities, manifestVersion };
1005
+ }
1006
+ function parseCapabilityArray(raw) {
1007
+ if (!Array.isArray(raw)) return [];
1008
+ return raw.filter(
1009
+ (c) => !!c && typeof c === "object" && typeof c.pipeline === "string" && typeof c.modelId === "string"
1010
+ );
1011
+ }
1012
+ function sortedCaps(caps) {
1013
+ return [...caps].sort((a, b) => {
1014
+ const p = a.pipeline.localeCompare(b.pipeline);
1015
+ return p === 0 ? a.modelId.localeCompare(b.modelId) : p;
1016
+ });
1017
+ }
1018
+ function computeManifestRevision(data) {
1019
+ if (data == null) {
1020
+ return "unavailable";
1021
+ }
1022
+ if (data.manifestVersion?.trim()) {
1023
+ return data.manifestVersion.trim();
1024
+ }
1025
+ const caps = sortedCaps(data.capabilities ?? []);
1026
+ const excl = sortedCaps(data.excludedCapabilities ?? []);
1027
+ if (caps.length === 0 && excl.length === 0) {
1028
+ return "empty";
1029
+ }
1030
+ return createHash("sha256").update(JSON.stringify({ capabilities: caps, excludedCapabilities: excl })).digest("hex").slice(0, 24);
1031
+ }
1032
+
1033
+ // src/client.ts
1034
+ init_string_utils();
1035
+
1036
+ // src/tokens.ts
1037
+ var SIGNER_SESSION_TTL_MS = 90 * 24 * 60 * 60 * 1e3;
1038
+ var SIGNER_SESSION_EXPIRES_IN_SEC = Math.floor(SIGNER_SESSION_TTL_MS / 1e3);
1039
+ var SIGN_JOB_SCOPE = "sign:job";
1040
+ var PYMTHOUSE_SIGNER_SESSION_TTL_MS = SIGNER_SESSION_TTL_MS;
1041
+ function computeSignerSessionExpiry(input) {
1042
+ const createdAt = input instanceof Date ? input : new Date(input);
1043
+ return new Date(createdAt.getTime() + SIGNER_SESSION_TTL_MS);
1044
+ }
1045
+ var computePymthouseExpiry = computeSignerSessionExpiry;
1046
+ function isLikelyOidcJwt(rawToken) {
1047
+ const t = rawToken.trim();
1048
+ return t.startsWith("eyJ") && t.split(".").length >= 3;
1049
+ }
1050
+ function isOpaqueSignerSessionToken(rawToken) {
1051
+ const t = rawToken.trim();
1052
+ return t.length > 0 && !isLikelyOidcJwt(t);
1053
+ }
1054
+ function base64UrlPayloadToUtf8(payloadB64) {
1055
+ const normalized = payloadB64.replaceAll("-", "+").replaceAll("_", "/");
1056
+ const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=");
1057
+ if (typeof Buffer !== "undefined") {
1058
+ return Buffer.from(padded, "base64").toString("utf8");
1059
+ }
1060
+ return atob(padded);
1061
+ }
1062
+ function decodeJwtExp(rawToken) {
1063
+ try {
1064
+ const parts = rawToken.split(".");
1065
+ if (parts.length < 2) return null;
1066
+ const payloadJson = base64UrlPayloadToUtf8(parts[1]);
1067
+ const payload = JSON.parse(payloadJson);
1068
+ if (typeof payload.exp !== "number" || !Number.isFinite(payload.exp)) return null;
1069
+ const expMs = Math.floor(payload.exp * 1e3);
1070
+ if (expMs <= 0) return null;
1071
+ return new Date(expMs);
1072
+ } catch {
1073
+ return null;
1074
+ }
1075
+ }
1076
+ function parseSignerSessionExchange(res) {
1077
+ const accessToken = typeof res.access_token === "string" ? res.access_token.trim() : "";
1078
+ if (!accessToken) {
1079
+ throw new Error("PymtHouse signer session exchange returned no access_token");
1080
+ }
1081
+ if (isLikelyOidcJwt(accessToken)) {
1082
+ throw new Error(
1083
+ "PymtHouse signer session exchange returned a JWT; expected opaque signer session token"
1084
+ );
1085
+ }
1086
+ const tokenType = typeof res.token_type === "string" && res.token_type.trim() ? res.token_type.trim() : "Bearer";
1087
+ const expiresIn = typeof res.expires_in === "number" && Number.isFinite(res.expires_in) && res.expires_in > 0 ? Math.floor(res.expires_in) : SIGNER_SESSION_EXPIRES_IN_SEC;
1088
+ const scope = typeof res.scope === "string" && res.scope.trim() ? res.scope.trim() : SIGN_JOB_SCOPE;
1089
+ return {
1090
+ accessToken,
1091
+ tokenType,
1092
+ expiresIn,
1093
+ scope
1094
+ };
1095
+ }
1096
+
1097
+ // src/oauth-map.ts
1098
+ init_errors();
182
1099
  var ACCEPTED_ISSUED_TOKEN_TYPES = /* @__PURE__ */ new Set([
183
1100
  "urn:ietf:params:oauth:token-type:access_token",
184
1101
  "urn:pmth:token-type:remote-signer-session"
@@ -272,8 +1189,8 @@ function m2mClient(clientId) {
272
1189
  }
273
1190
 
274
1191
  // src/client.ts
275
- var TOKEN_EXCHANGE_GRANT = "urn:ietf:params:oauth:grant-type:token-exchange";
276
- var SUBJECT_ACCESS_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token";
1192
+ var TOKEN_EXCHANGE_GRANT2 = "urn:ietf:params:oauth:grant-type:token-exchange";
1193
+ var SUBJECT_ACCESS_TOKEN_TYPE2 = "urn:ietf:params:oauth:token-type:access_token";
277
1194
  var REQUESTED_ACCESS_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token";
278
1195
  var DEVICE_RESOURCE_PREFIX = "urn:pmth:device_code:";
279
1196
  function normalizeUserCode(value) {
@@ -400,6 +1317,50 @@ var PmtHouseClient = class {
400
1317
  cache: "no-store"
401
1318
  });
402
1319
  }
1320
+ /**
1321
+ * Exchange a long-lived dashboard API key (`pmth_*`) for a short-lived user JWT.
1322
+ */
1323
+ async exchangeApiKeyForUserAccessToken(input) {
1324
+ const url = `${this.getAppsBaseUrl()}/auth/api-key/token`;
1325
+ return this.requestJson(url, {
1326
+ method: "POST",
1327
+ headers: {
1328
+ Authorization: `Bearer ${input.apiKey.trim()}`,
1329
+ "Content-Type": "application/json",
1330
+ Accept: "application/json"
1331
+ },
1332
+ body: JSON.stringify(input.scope ? { scope: input.scope } : {}),
1333
+ cache: "no-store"
1334
+ });
1335
+ }
1336
+ /**
1337
+ * Exchange a dashboard API key for a signer session via a trusted facade (recommended)
1338
+ * or directly when M2M credentials are available on this client.
1339
+ */
1340
+ async exchangeApiKeyForSignerSession(input) {
1341
+ if (input.facadeUrl?.trim()) {
1342
+ const { exchangeApiKeyForSigner: exchangeApiKeyForSigner2 } = await Promise.resolve().then(() => (init_api_key_exchange(), api_key_exchange_exports));
1343
+ const exchanged = await exchangeApiKeyForSigner2({
1344
+ facadeUrl: input.facadeUrl.trim(),
1345
+ apiKey: input.apiKey,
1346
+ scope: input.scope,
1347
+ clientId: this.publicClientId,
1348
+ fetch: this.fetchImpl
1349
+ });
1350
+ return {
1351
+ access_token: exchanged.access_token,
1352
+ token_type: exchanged.token_type,
1353
+ expires_in: exchanged.expires_in,
1354
+ scope: exchanged.scope,
1355
+ issued_token_type: "urn:ietf:params:oauth:token-type:access_token"
1356
+ };
1357
+ }
1358
+ const userToken = await this.exchangeApiKeyForUserAccessToken({
1359
+ apiKey: input.apiKey,
1360
+ scope: input.scope
1361
+ });
1362
+ return this.exchangeForSignerSession({ userJwt: userToken.access_token });
1363
+ }
403
1364
  async completeDeviceApproval(input) {
404
1365
  const as = await loadAuthorizationServer(this.issuerUrl, this.fetchImpl, {
405
1366
  allowInsecureHttp: this.allowInsecureHttp
@@ -408,14 +1369,14 @@ var PmtHouseClient = class {
408
1369
  const clientAuth = this.m2mClientAuth();
409
1370
  const params = new URLSearchParams();
410
1371
  params.set("subject_token", input.userJwt);
411
- params.set("subject_token_type", SUBJECT_ACCESS_TOKEN_TYPE);
1372
+ params.set("subject_token_type", SUBJECT_ACCESS_TOKEN_TYPE2);
412
1373
  params.set("resource", buildDeviceCodeResource(input.userCode));
413
1374
  try {
414
1375
  const response = await genericTokenEndpointRequest(
415
1376
  as,
416
1377
  client,
417
1378
  clientAuth,
418
- TOKEN_EXCHANGE_GRANT,
1379
+ TOKEN_EXCHANGE_GRANT2,
419
1380
  params,
420
1381
  this.tokenEndpointFetchOptions()
421
1382
  );
@@ -463,7 +1424,7 @@ var PmtHouseClient = class {
463
1424
  const clientAuth = this.m2mClientAuth();
464
1425
  const params = new URLSearchParams();
465
1426
  params.set("subject_token", input.userJwt);
466
- params.set("subject_token_type", SUBJECT_ACCESS_TOKEN_TYPE);
1427
+ params.set("subject_token_type", SUBJECT_ACCESS_TOKEN_TYPE2);
467
1428
  params.set("requested_token_type", REQUESTED_ACCESS_TOKEN_TYPE);
468
1429
  const resourceCandidate = typeof input.resource === "string" && input.resource.trim() !== "" ? input.resource.trim() : this.issuerUrl;
469
1430
  params.set("resource", stripTrailingSlashes(resourceCandidate));
@@ -472,7 +1433,7 @@ var PmtHouseClient = class {
472
1433
  as,
473
1434
  client,
474
1435
  clientAuth,
475
- TOKEN_EXCHANGE_GRANT,
1436
+ TOKEN_EXCHANGE_GRANT2,
476
1437
  params,
477
1438
  this.tokenEndpointFetchOptions()
478
1439
  );
@@ -528,12 +1489,267 @@ var PmtHouseClient = class {
528
1489
  if (input.groupBy) url.searchParams.set("groupBy", input.groupBy);
529
1490
  if (input.userId) url.searchParams.set("userId", input.userId);
530
1491
  if (input.gatewayRequestId) url.searchParams.set("gatewayRequestId", input.gatewayRequestId);
1492
+ if (input.includeRetail) url.searchParams.set("include", "retail");
531
1493
  return this.requestJson(url.toString(), {
532
1494
  method: "GET",
533
1495
  headers: this.builderHeaders(),
534
1496
  cache: "no-store"
535
1497
  });
536
1498
  }
1499
+ /**
1500
+ * Session-scoped usage for one `externalUserId`: user rollup plus merged pipeline/model breakdown.
1501
+ */
1502
+ async ingestSignedTicket(ticket) {
1503
+ return ingestSignedTicket({
1504
+ issuerUrl: this.issuerUrl,
1505
+ publicClientId: this.publicClientId,
1506
+ m2mClientId: this.m2mClientId,
1507
+ m2mClientSecret: this.m2mClientSecret,
1508
+ ticket,
1509
+ fetch: this.fetchImpl
1510
+ });
1511
+ }
1512
+ async ingestSignedTickets(tickets) {
1513
+ return ingestSignedTicketsBatch({
1514
+ issuerUrl: this.issuerUrl,
1515
+ publicClientId: this.publicClientId,
1516
+ m2mClientId: this.m2mClientId,
1517
+ m2mClientSecret: this.m2mClientSecret,
1518
+ tickets,
1519
+ fetch: this.fetchImpl
1520
+ });
1521
+ }
1522
+ async getSignerRouting() {
1523
+ return this.requestJson(
1524
+ `${this.getAppsBaseUrl()}/signer/routing`,
1525
+ {
1526
+ method: "GET",
1527
+ headers: this.builderHeaders(),
1528
+ cache: "no-store"
1529
+ }
1530
+ );
1531
+ }
1532
+ async listBillingProducts() {
1533
+ const url = `${this.getAppsBaseUrl()}/plans?apiVersion=2`;
1534
+ const body = await this.requestJson(
1535
+ url,
1536
+ {
1537
+ method: "GET",
1538
+ headers: this.builderHeaders(),
1539
+ cache: "no-store"
1540
+ }
1541
+ );
1542
+ return {
1543
+ apiVersion: body.apiVersion ?? 2,
1544
+ products: body.products ?? body.plans ?? []
1545
+ };
1546
+ }
1547
+ async syncBillingProduct(planId) {
1548
+ return this.requestJson(
1549
+ `${this.getAppsBaseUrl()}/plans/${encodeURIComponent(planId)}/sync`,
1550
+ {
1551
+ method: "POST",
1552
+ headers: this.builderHeaders(),
1553
+ cache: "no-store"
1554
+ }
1555
+ );
1556
+ }
1557
+ async getUsageBalance(externalUserId) {
1558
+ const url = new URL(`${this.getAppsBaseUrl()}/usage/balance`);
1559
+ url.searchParams.set("externalUserId", externalUserId);
1560
+ return this.requestJson(url.toString(), {
1561
+ method: "GET",
1562
+ headers: this.builderHeaders(),
1563
+ cache: "no-store"
1564
+ });
1565
+ }
1566
+ async getUserAllowances(externalUserId) {
1567
+ return this.requestJson(
1568
+ `${this.getAppsBaseUrl()}/users/${encodeURIComponent(externalUserId)}/allowances`,
1569
+ {
1570
+ method: "GET",
1571
+ headers: this.builderHeaders(),
1572
+ cache: "no-store"
1573
+ }
1574
+ );
1575
+ }
1576
+ async grantUserAllowance(externalUserId, input) {
1577
+ return this.requestJson(
1578
+ `${this.getAppsBaseUrl()}/users/${encodeURIComponent(externalUserId)}/allowances`,
1579
+ {
1580
+ method: "POST",
1581
+ headers: this.builderHeaders(),
1582
+ body: JSON.stringify(input),
1583
+ cache: "no-store"
1584
+ }
1585
+ );
1586
+ }
1587
+ /**
1588
+ * @deprecated Removed from PymtHouse — use {@link getUsageBalance} or {@link getUserAllowances}.
1589
+ */
1590
+ async getUserCredits(externalUserId) {
1591
+ return this.getUsageBalance(externalUserId);
1592
+ }
1593
+ /**
1594
+ * @deprecated Removed from PymtHouse — use {@link grantUserAllowance} (`POST .../allowances`).
1595
+ */
1596
+ async grantUserCredits(externalUserId, input) {
1597
+ const result = await this.grantUserAllowance(externalUserId, {
1598
+ amountUsdMicros: input.amountUsdMicros,
1599
+ source: input.source ?? "manual",
1600
+ featureKey: input.featureKey
1601
+ });
1602
+ const flat = result;
1603
+ const nested = result.allowances;
1604
+ return {
1605
+ externalUserId: result.externalUserId,
1606
+ balanceUsdMicros: flat.balanceUsdMicros ?? nested?.balanceUsdMicros ?? "0",
1607
+ consumedUsdMicros: flat.consumedUsdMicros ?? nested?.consumedUsdMicros ?? "0",
1608
+ lifetimeGrantedUsdMicros: flat.lifetimeGrantedUsdMicros ?? nested?.lifetimeGrantedUsdMicros ?? "0",
1609
+ hasAccess: flat.hasAccess ?? nested?.hasAccess ?? false,
1610
+ remainingUsdMicros: flat.balanceUsdMicros ?? nested?.balanceUsdMicros,
1611
+ grantedUsdMicros: flat.grantedUsdMicros,
1612
+ featureKey: flat.featureKey
1613
+ };
1614
+ }
1615
+ async getUserSubscription(externalUserId) {
1616
+ return this.requestJson(
1617
+ `${this.getAppsBaseUrl()}/users/${encodeURIComponent(externalUserId)}/subscription`,
1618
+ {
1619
+ method: "GET",
1620
+ headers: this.builderHeaders(),
1621
+ cache: "no-store"
1622
+ }
1623
+ );
1624
+ }
1625
+ async fetchUsageForExternalUser(input) {
1626
+ const usageByUser = await this.getUsage({
1627
+ startDate: input.startDate,
1628
+ endDate: input.endDate,
1629
+ groupBy: "user"
1630
+ });
1631
+ const userIds = getEndUserIdsForExternalUser(usageByUser, input.externalUserId);
1632
+ const cap = input.maxEndUserIds ?? DEFAULT_MAX_END_USER_IDS;
1633
+ const cappedUserIds = userIds.slice(0, cap);
1634
+ const usagePipelineModels = await Promise.all(
1635
+ cappedUserIds.map(
1636
+ (userId) => this.getUsage({
1637
+ startDate: input.startDate,
1638
+ endDate: input.endDate,
1639
+ groupBy: "pipeline_model",
1640
+ userId
1641
+ })
1642
+ )
1643
+ );
1644
+ const usageDaily = await this.getUsage({
1645
+ startDate: input.startDate,
1646
+ endDate: input.endDate,
1647
+ groupBy: "daily_pipeline",
1648
+ userId: input.externalUserId
1649
+ });
1650
+ return buildMeScopeUsagePayload(
1651
+ usageByUser,
1652
+ input.externalUserId,
1653
+ usagePipelineModels,
1654
+ usageDaily
1655
+ );
1656
+ }
1657
+ async getAppManifest(opts) {
1658
+ const url = `${this.getAppsBaseUrl()}/manifest`;
1659
+ const headers = {
1660
+ ...this.builderHeadersRecord()
1661
+ };
1662
+ if (opts?.ifNoneMatch) {
1663
+ headers["If-None-Match"] = opts.ifNoneMatch;
1664
+ }
1665
+ this.logger?.debug?.("PmtHouse request", { method: "GET", url });
1666
+ const response = await this.fetchImpl(url, {
1667
+ method: "GET",
1668
+ headers,
1669
+ signal: opts?.signal,
1670
+ cache: "no-store"
1671
+ });
1672
+ const etag = response.headers.get("etag")?.trim() ?? null;
1673
+ if (response.status === 304) {
1674
+ return {
1675
+ manifest: null,
1676
+ etag: etag ?? opts?.ifNoneMatch ?? null,
1677
+ notModified: true
1678
+ };
1679
+ }
1680
+ const raw = await response.text();
1681
+ const ct = response.headers.get("content-type") ?? "";
1682
+ const looksJson = ct.includes("application/json") || ct.includes("json");
1683
+ const parsed = raw && looksJson ? this.safeParseJson(raw) : null;
1684
+ if (!response.ok) {
1685
+ const details = parsed ?? {};
1686
+ let description;
1687
+ if (typeof details.error_description === "string") {
1688
+ description = details.error_description;
1689
+ } else if (typeof details.error === "string") {
1690
+ description = details.error;
1691
+ } else {
1692
+ description = `Request failed (${response.status})`;
1693
+ }
1694
+ throw new PmtHouseError(description, {
1695
+ status: response.status,
1696
+ code: typeof details.error === "string" ? details.error : "pymthouse_http_error",
1697
+ details
1698
+ });
1699
+ }
1700
+ if (!looksJson || parsed === null) {
1701
+ throw new PmtHouseError("Expected JSON response from Builder manifest endpoint", {
1702
+ status: 502,
1703
+ code: "invalid_response",
1704
+ details: { contentType: ct, preview: raw.slice(0, 200) }
1705
+ });
1706
+ }
1707
+ return {
1708
+ manifest: parseAppManifestResponse(parsed),
1709
+ etag,
1710
+ notModified: false
1711
+ };
1712
+ }
1713
+ /**
1714
+ * Upsert an external user, mint a short-lived JWT, and exchange for an opaque signer session.
1715
+ */
1716
+ async mintSignerSessionForExternalUser(input) {
1717
+ await this.upsertAppUser({
1718
+ externalUserId: input.externalUserId,
1719
+ email: input.email,
1720
+ status: "active"
1721
+ });
1722
+ const exchange = await this.mintUserSignerSessionToken({
1723
+ externalUserId: input.externalUserId,
1724
+ scope: input.scope ?? SIGN_JOB_SCOPE,
1725
+ resource: this.issuerUrl
1726
+ });
1727
+ return parseSignerSessionExchange(exchange);
1728
+ }
1729
+ /**
1730
+ * Approve a pending RFC 8628 device code for an external user (Option B).
1731
+ */
1732
+ async approveDeviceLogin(input) {
1733
+ if (input.publicClientId && input.publicClientId !== this.publicClientId) {
1734
+ throw new PmtHouseError(
1735
+ "publicClientId does not match configured public client id",
1736
+ { status: 400, code: "invalid_client" }
1737
+ );
1738
+ }
1739
+ await this.upsertAppUser({
1740
+ externalUserId: input.externalUserId,
1741
+ email: input.email,
1742
+ status: "active"
1743
+ });
1744
+ const userToken = await this.mintUserAccessToken({
1745
+ externalUserId: input.externalUserId,
1746
+ scope: SIGN_JOB_SCOPE
1747
+ });
1748
+ await this.completeDeviceApproval({
1749
+ userJwt: userToken.access_token,
1750
+ userCode: input.userCode
1751
+ });
1752
+ }
537
1753
  tokenEndpointFetchOptions() {
538
1754
  const o = {
539
1755
  [customFetch]: this.fetchImpl
@@ -550,6 +1766,9 @@ var PmtHouseClient = class {
550
1766
  return new URL(this.issuerUrl).origin;
551
1767
  }
552
1768
  builderHeaders() {
1769
+ return this.builderHeadersRecord();
1770
+ }
1771
+ builderHeadersRecord() {
553
1772
  return {
554
1773
  Authorization: encodeClientSecretBasic(this.m2mClientId, this.m2mClientSecret),
555
1774
  "Content-Type": "application/json",
@@ -573,7 +1792,14 @@ var PmtHouseClient = class {
573
1792
  const parsed = raw && looksJson ? this.safeParseJson(raw) : raw ? null : null;
574
1793
  if (!response.ok) {
575
1794
  const details = parsed ?? {};
576
- const description = typeof details.error_description === "string" ? details.error_description : typeof details.error === "string" ? details.error : `Request failed (${response.status})`;
1795
+ let description;
1796
+ if (typeof details.error_description === "string") {
1797
+ description = details.error_description;
1798
+ } else if (typeof details.error === "string") {
1799
+ description = details.error;
1800
+ } else {
1801
+ description = `Request failed (${response.status})`;
1802
+ }
577
1803
  throw new PmtHouseError(description, {
578
1804
  status: response.status,
579
1805
  code: typeof details.error === "string" ? details.error : "pymthouse_http_error",
@@ -616,55 +1842,58 @@ var PmtHouseClient = class {
616
1842
  }
617
1843
  };
618
1844
 
619
- // src/env.ts
620
- function assertEnvModuleServerOnly() {
621
- if (typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined") {
622
- throw new Error(
623
- "@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."
624
- );
1845
+ // src/index.ts
1846
+ init_errors();
1847
+
1848
+ // src/config.ts
1849
+ init_string_utils();
1850
+ var PYMTHOUSE_NOT_CONFIGURED_MESSAGE = "PymtHouse is not configured. Set PYMTHOUSE_ISSUER_URL, PYMTHOUSE_PUBLIC_CLIENT_ID, PYMTHOUSE_M2M_CLIENT_ID, and PYMTHOUSE_M2M_CLIENT_SECRET, then restart.";
1851
+ function trimEnv(name) {
1852
+ const value = process.env[name];
1853
+ if (!value) return null;
1854
+ const trimmed = value.trim();
1855
+ return trimmed || null;
1856
+ }
1857
+ function readPymthouseEnv() {
1858
+ const issuerUrl = trimEnv("PYMTHOUSE_ISSUER_URL");
1859
+ const publicClientId = trimEnv("PYMTHOUSE_PUBLIC_CLIENT_ID");
1860
+ const m2mClientId = trimEnv("PYMTHOUSE_M2M_CLIENT_ID");
1861
+ const m2mClientSecret = trimEnv("PYMTHOUSE_M2M_CLIENT_SECRET");
1862
+ if (!issuerUrl || !publicClientId || !m2mClientId || !m2mClientSecret) {
1863
+ return null;
625
1864
  }
1865
+ return {
1866
+ issuerUrl: stripTrailingSlashes(issuerUrl),
1867
+ publicClientId,
1868
+ m2mClientId,
1869
+ m2mClientSecret
1870
+ };
626
1871
  }
627
- assertEnvModuleServerOnly();
628
- var cachedClient = null;
629
- function requiredEnv(name) {
630
- const value = process.env[name];
631
- if (value && value.trim()) {
632
- return value.trim();
1872
+ function getPymthouseIssuerUrlFromEnv() {
1873
+ const raw = trimEnv("PYMTHOUSE_ISSUER_URL");
1874
+ if (!raw) return null;
1875
+ try {
1876
+ return stripTrailingSlashes(new URL(raw).href);
1877
+ } catch {
1878
+ return null;
633
1879
  }
634
- throw new PmtHouseError(`Missing required environment variable: ${name}`, {
635
- status: 500,
636
- code: "missing_env"
637
- });
638
1880
  }
639
- function getPymthouseBaseUrl() {
640
- const issuerUrl = requiredEnv("PYMTHOUSE_ISSUER_URL");
641
- return new URL(stripTrailingSlashes(issuerUrl)).origin;
642
- }
643
- function createPmtHouseClientFromEnv() {
644
- if (cachedClient) {
645
- return cachedClient;
646
- }
647
- const issuerUrl = requiredEnv("PYMTHOUSE_ISSUER_URL");
648
- cachedClient = new PmtHouseClient({
649
- issuerUrl,
650
- publicClientId: requiredEnv("PYMTHOUSE_PUBLIC_CLIENT_ID"),
651
- m2mClientId: requiredEnv("PYMTHOUSE_M2M_CLIENT_ID"),
652
- m2mClientSecret: requiredEnv("PYMTHOUSE_M2M_CLIENT_SECRET"),
653
- allowInsecureHttp: issuerUrl.startsWith("http:"),
654
- logger: {
655
- debug: (message, details) => {
656
- if (process.env.NODE_ENV !== "production") {
657
- console.debug(`[pymthouse] ${message}`, details ?? {});
658
- }
659
- },
660
- warn: (message, details) => {
661
- console.warn(`[pymthouse] ${message}`, details ?? {});
662
- }
663
- }
664
- });
665
- return cachedClient;
1881
+ function getPymthousePublicClientIdFromEnv() {
1882
+ return trimEnv("PYMTHOUSE_PUBLIC_CLIENT_ID");
1883
+ }
1884
+ function isPymthouseConfigured() {
1885
+ return readPymthouseEnv() !== null;
666
1886
  }
1887
+ function getBuilderApiV1BaseFromIssuerUrl(issuerUrl) {
1888
+ return stripOidcPathSuffix(issuerUrl);
1889
+ }
1890
+ function getPymthouseIssuerOrigin(issuerUrl) {
1891
+ return new URL(stripTrailingSlashes(issuerUrl.trim())).origin;
1892
+ }
1893
+
1894
+ // src/index.ts
1895
+ init_discovery();
667
1896
 
668
- export { PmtHouseClient, PmtHouseError, aggregateUsageByExternalUserId, authorizationServerToOidcDocument, buildDeviceCodeResource, clearDiscoveryCache, createPmtHouseClientFromEnv, fetchDiscoveryDocument, getPymthouseBaseUrl, listUsageByPipelineModel, loadAuthorizationServer, normalizeUserCode, summarizeUsageForExternalUser, toPmtHouseError };
1897
+ export { DEFAULT_MAX_END_USER_IDS, NETWORK_USD_PER_MICRO, PYMTHOUSE_NOT_CONFIGURED_MESSAGE, PYMTHOUSE_SIGNER_SESSION_TTL_MS, PmtHouseClient, PmtHouseError, SIGNER_SESSION_EXPIRES_IN_SEC, SIGNER_SESSION_TTL_MS, SIGN_JOB_SCOPE, aggregateUsageByExternalUserId, applyRetailRateToNetworkMicros, authorizationServerToOidcDocument, buildDeviceCodeResource, buildMeScopeUsagePayload, clearDiscoveryCache, computeManifestRevision, computePymthouseExpiry, computeSignerSessionExpiry, decodeJwtExp, defaultRetailRateUsd, fetchDiscoveryDocument, getBuilderApiV1BaseFromIssuerUrl, getEndUserIdsForExternalUser, getPymthouseIssuerOrigin, getPymthouseIssuerUrlFromEnv, getPymthousePublicClientIdFromEnv, getUsageRecordUserIdsForExternalUser, getUtcCalendarMonthIsoBounds, ingestSignedTicket, ingestSignedTicketsBatch, isLikelyOidcJwt, isOpaqueSignerSessionToken, isPymthouseConfigured, listUsageByPipelineModel, loadAuthorizationServer, markupPercentToRetailRateUsd, mergeUsageByPipelineModel, normalizeUserCode, parseAppManifestResponse, parseMarkupPercentInput, parseRetailRateUsd, parseSignerSessionExchange, parseUsageDateParam, readPymthouseEnv, retailRateUsdPerMillion, retailRateUsdToMarkupPercent, signerSnapshotToIngestPayload, summarizeUsageFiatForExternalUser, summarizeUsageForExternalUser, toPmtHouseError };
669
1898
  //# sourceMappingURL=index.js.map
670
1899
  //# sourceMappingURL=index.js.map