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