@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/env.cjs CHANGED
@@ -1,8 +1,17 @@
1
1
  'use strict';
2
2
 
3
3
  var oauth4webapi = require('oauth4webapi');
4
+ require('crypto');
4
5
 
5
- // src/client.ts
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
+ };
6
15
 
7
16
  // src/encoding.ts
8
17
  function encodeClientSecretBasic(clientId, clientSecret) {
@@ -10,35 +19,69 @@ function encodeClientSecretBasic(clientId, clientSecret) {
10
19
  const b64 = typeof Buffer !== "undefined" ? Buffer.from(raw, "utf8").toString("base64") : btoa(Array.from(new TextEncoder().encode(raw), (c) => String.fromCharCode(c)).join(""));
11
20
  return `Basic ${b64}`;
12
21
  }
22
+ var init_encoding = __esm({
23
+ "src/encoding.ts"() {
24
+ }
25
+ });
13
26
 
14
27
  // src/errors.ts
15
- var PmtHouseError = class extends Error {
16
- status;
17
- code;
18
- details;
19
- constructor(message, {
20
- status = 500,
21
- code = "pymthouse_error",
22
- details
23
- } = {}) {
24
- super(message);
25
- this.name = "PmtHouseError";
26
- this.status = status;
27
- this.code = code;
28
- this.details = details;
28
+ var PmtHouseError;
29
+ var init_errors = __esm({
30
+ "src/errors.ts"() {
31
+ PmtHouseError = class extends Error {
32
+ status;
33
+ code;
34
+ details;
35
+ constructor(message, {
36
+ status = 500,
37
+ code = "pymthouse_error",
38
+ details
39
+ } = {}) {
40
+ super(message);
41
+ this.name = "PmtHouseError";
42
+ this.status = status;
43
+ this.code = code;
44
+ this.details = details;
45
+ }
46
+ };
29
47
  }
30
- };
48
+ });
31
49
 
32
50
  // src/string-utils.ts
33
51
  function stripTrailingSlashes(value) {
34
52
  let end = value.length;
35
- while (end > 0 && value.charCodeAt(end - 1) === 47) {
53
+ while (end > 0 && (value.codePointAt(end - 1) ?? 0) === 47) {
36
54
  end--;
37
55
  }
38
56
  return value.slice(0, end);
39
57
  }
40
-
41
- // src/discovery.ts
58
+ function endsWithIgnoreCase(value, suffix) {
59
+ if (suffix.length > value.length) {
60
+ return false;
61
+ }
62
+ const start = value.length - suffix.length;
63
+ for (let i = 0; i < suffix.length; i++) {
64
+ const a = value.codePointAt(start + i) ?? 0;
65
+ const b = suffix.codePointAt(i) ?? 0;
66
+ if (a !== b && (a | 32) !== (b | 32)) {
67
+ return false;
68
+ }
69
+ }
70
+ return true;
71
+ }
72
+ function stripSuffixIgnoreCase(value, suffix) {
73
+ return endsWithIgnoreCase(value, suffix) ? value.slice(0, value.length - suffix.length) : value;
74
+ }
75
+ function stripIssuerOriginFromOidcUrl(issuerUrl) {
76
+ let base = stripTrailingSlashes(issuerUrl.trim());
77
+ base = stripSuffixIgnoreCase(base, "/api/v1/oidc");
78
+ base = stripSuffixIgnoreCase(base, "/oidc");
79
+ return stripTrailingSlashes(base);
80
+ }
81
+ var init_string_utils = __esm({
82
+ "src/string-utils.ts"() {
83
+ }
84
+ });
42
85
  function authorizationServerToOidcDocument(as) {
43
86
  const tokenEndpoint = as.token_endpoint;
44
87
  const jwksUri = as.jwks_uri;
@@ -57,8 +100,6 @@ function authorizationServerToOidcDocument(as) {
57
100
  device_authorization_endpoint: as.device_authorization_endpoint
58
101
  };
59
102
  }
60
- var CACHE_TTL_MS = 5 * 60 * 1e3;
61
- var discoveryCache = /* @__PURE__ */ new Map();
62
103
  function normalizedIssuerKey(issuerUrl) {
63
104
  return stripTrailingSlashes(issuerUrl);
64
105
  }
@@ -123,6 +164,612 @@ function mapDiscoveryNetworkError(error) {
123
164
  code: "oidc_discovery_failed"
124
165
  });
125
166
  }
167
+ var CACHE_TTL_MS, discoveryCache;
168
+ var init_discovery = __esm({
169
+ "src/discovery.ts"() {
170
+ init_errors();
171
+ init_string_utils();
172
+ CACHE_TTL_MS = 5 * 60 * 1e3;
173
+ discoveryCache = /* @__PURE__ */ new Map();
174
+ }
175
+ });
176
+
177
+ // src/signer/fetch-json.ts
178
+ function oauthFailureDescription(parsed, failureLabel, status) {
179
+ if (typeof parsed.error_description === "string") {
180
+ return parsed.error_description;
181
+ }
182
+ if (typeof parsed.error === "string") {
183
+ return parsed.error;
184
+ }
185
+ return `${failureLabel} (${status})`;
186
+ }
187
+ async function readJsonObjectFromResponse(response, options) {
188
+ const text = await response.text();
189
+ let parsed;
190
+ try {
191
+ parsed = text ? JSON.parse(text) : {};
192
+ } catch {
193
+ throw new PmtHouseError(options.invalidJsonMessage, {
194
+ status: 502,
195
+ code: options.invalidJsonCode,
196
+ details: { status: response.status }
197
+ });
198
+ }
199
+ if (!response.ok) {
200
+ const description = oauthFailureDescription(parsed, options.failureLabel, response.status);
201
+ throw new PmtHouseError(description, {
202
+ status: response.status,
203
+ code: typeof parsed.error === "string" ? parsed.error : options.defaultErrorCode,
204
+ details: parsed
205
+ });
206
+ }
207
+ return parsed;
208
+ }
209
+ var init_fetch_json = __esm({
210
+ "src/signer/fetch-json.ts"() {
211
+ init_errors();
212
+ }
213
+ });
214
+
215
+ // src/signer/handler-errors.ts
216
+ function signerHandlerErrorResponse(error) {
217
+ if (error instanceof PmtHouseError) {
218
+ return new Response(
219
+ JSON.stringify({
220
+ error: error.code,
221
+ error_description: error.message,
222
+ details: error.details
223
+ }),
224
+ {
225
+ status: error.status,
226
+ headers: { "Content-Type": "application/json" }
227
+ }
228
+ );
229
+ }
230
+ const message = error instanceof Error ? error.message : "Internal error";
231
+ return new Response(JSON.stringify({ error: "internal_error", error_description: message }), {
232
+ status: 500,
233
+ headers: { "Content-Type": "application/json" }
234
+ });
235
+ }
236
+ var init_handler_errors = __esm({
237
+ "src/signer/handler-errors.ts"() {
238
+ init_errors();
239
+ }
240
+ });
241
+
242
+ // src/signer/json-fields.ts
243
+ function readStringField(body, key, errorCode, messagePrefix = "Response") {
244
+ const value = body[key];
245
+ if (typeof value !== "string" || !value.trim()) {
246
+ throw new PmtHouseError(`${messagePrefix} missing ${key}`, {
247
+ status: 502,
248
+ code: errorCode
249
+ });
250
+ }
251
+ return value.trim();
252
+ }
253
+ function readExpiresIn(body, errorCode) {
254
+ const expiresIn = body.expires_in;
255
+ if (typeof expiresIn !== "number" || !Number.isFinite(expiresIn) || expiresIn <= 0) {
256
+ throw new PmtHouseError("Response missing expires_in", {
257
+ status: 502,
258
+ code: errorCode
259
+ });
260
+ }
261
+ return Math.floor(expiresIn);
262
+ }
263
+ var init_json_fields = __esm({
264
+ "src/signer/json-fields.ts"() {
265
+ init_errors();
266
+ }
267
+ });
268
+
269
+ // src/signer/mint-token.ts
270
+ function parseMintUserSignerTokenResponse(body, ttlRefreshRatio = DEFAULT_TTL_REFRESH_RATIO) {
271
+ const accessToken = readStringField(body, "access_token", TOKEN_RESPONSE_ERROR, "Token response");
272
+ const expiresIn = readExpiresIn(body, TOKEN_RESPONSE_ERROR);
273
+ const balanceUsdMicros = readStringField(
274
+ body,
275
+ "balanceUsdMicros",
276
+ TOKEN_RESPONSE_ERROR,
277
+ "Token response"
278
+ );
279
+ const lifetimeGrantedUsdMicros = readStringField(
280
+ body,
281
+ "lifetimeGrantedUsdMicros",
282
+ TOKEN_RESPONSE_ERROR,
283
+ "Token response"
284
+ );
285
+ const now = Date.now();
286
+ const expiresAt = now + expiresIn * 1e3;
287
+ const refreshAt = now + Math.floor(expiresIn * 1e3 * ttlRefreshRatio);
288
+ return {
289
+ jwt: accessToken,
290
+ expiresAt,
291
+ refreshAt,
292
+ balanceUsdMicros,
293
+ lifetimeGrantedUsdMicros
294
+ };
295
+ }
296
+ var LIVEPEER_REMOTE_SIGNER_AUDIENCE, DEFAULT_TTL_REFRESH_RATIO, TOKEN_RESPONSE_ERROR;
297
+ var init_mint_token = __esm({
298
+ "src/signer/mint-token.ts"() {
299
+ init_json_fields();
300
+ LIVEPEER_REMOTE_SIGNER_AUDIENCE = "livepeer-remote-signer";
301
+ DEFAULT_TTL_REFRESH_RATIO = 0.8;
302
+ TOKEN_RESPONSE_ERROR = "invalid_token_response";
303
+ }
304
+ });
305
+
306
+ // src/signer/device-exchange.ts
307
+ function extractSignerAccessTokenFromExchangeBody(body) {
308
+ const tokenObj = body.token;
309
+ if (tokenObj !== null && typeof tokenObj === "object" && !Array.isArray(tokenObj)) {
310
+ const nested = tokenObj;
311
+ for (const key of ["accessToken", "access_token"]) {
312
+ const value = nested[key];
313
+ if (typeof value === "string" && value.trim()) {
314
+ return value.trim();
315
+ }
316
+ }
317
+ }
318
+ for (const key of ["accessToken", "access_token"]) {
319
+ const value = body[key];
320
+ if (typeof value === "string" && value.trim()) {
321
+ return value.trim();
322
+ }
323
+ }
324
+ throw new PmtHouseError("Device exchange response missing signer access token", {
325
+ status: 502,
326
+ code: "invalid_exchange_response"
327
+ });
328
+ }
329
+ function normalizeDeviceExchangeResponse(minted, options) {
330
+ const scope = minted.scope.trim() || "sign:job";
331
+ const body = {
332
+ access_token: minted.access_token,
333
+ token_type: "Bearer",
334
+ expires_in: minted.expires_in,
335
+ scope,
336
+ balanceUsdMicros: minted.balanceUsdMicros,
337
+ lifetimeGrantedUsdMicros: minted.lifetimeGrantedUsdMicros,
338
+ token: {
339
+ accessToken: minted.access_token,
340
+ access_token: minted.access_token,
341
+ expiresIn: minted.expires_in,
342
+ expires_in: minted.expires_in,
343
+ scope,
344
+ balanceUsdMicros: minted.balanceUsdMicros,
345
+ lifetimeGrantedUsdMicros: minted.lifetimeGrantedUsdMicros
346
+ }
347
+ };
348
+ const signerUrl = options?.signerUrl?.trim();
349
+ if (signerUrl) {
350
+ body.signerUrl = signerUrl;
351
+ }
352
+ return body;
353
+ }
354
+ async function mintSignerTokenFromDeviceToken(options) {
355
+ const fetchImpl = options.fetch ?? fetch;
356
+ const issuerUrl = stripTrailingSlashes(options.issuerUrl);
357
+ const as = await loadAuthorizationServer(issuerUrl, fetchImpl, {
358
+ allowInsecureHttp: options.allowInsecureHttp
359
+ });
360
+ const tokenEndpoint = as.token_endpoint;
361
+ if (!tokenEndpoint) {
362
+ throw new PmtHouseError("OIDC discovery document is missing token_endpoint", {
363
+ status: 500,
364
+ code: "oidc_discovery_invalid"
365
+ });
366
+ }
367
+ const audience = options.audience?.trim() || LIVEPEER_REMOTE_SIGNER_AUDIENCE;
368
+ const params = new URLSearchParams({
369
+ grant_type: TOKEN_EXCHANGE_GRANT,
370
+ subject_token: options.deviceToken,
371
+ subject_token_type: SUBJECT_ACCESS_TOKEN_TYPE,
372
+ audience,
373
+ resource: audience
374
+ });
375
+ if (options.scope?.trim()) {
376
+ params.set("scope", options.scope.trim());
377
+ }
378
+ const response = await fetchImpl(tokenEndpoint, {
379
+ method: "POST",
380
+ headers: {
381
+ Authorization: encodeClientSecretBasic(options.m2mClientId, options.m2mClientSecret),
382
+ "Content-Type": "application/x-www-form-urlencoded",
383
+ Accept: "application/json"
384
+ },
385
+ body: params.toString(),
386
+ cache: "no-store"
387
+ });
388
+ const parsed = await readJsonObjectFromResponse(response, {
389
+ invalidJsonMessage: "Token endpoint returned invalid JSON",
390
+ invalidJsonCode: "invalid_token_response",
391
+ failureLabel: "Signer JWT exchange failed",
392
+ defaultErrorCode: "token_exchange_failed"
393
+ });
394
+ const cached = parseMintUserSignerTokenResponse(parsed);
395
+ return {
396
+ access_token: cached.jwt,
397
+ expires_in: readExpiresIn(parsed, EXCHANGE_RESPONSE_ERROR),
398
+ scope: readStringField(parsed, "scope", EXCHANGE_RESPONSE_ERROR),
399
+ balanceUsdMicros: cached.balanceUsdMicros,
400
+ lifetimeGrantedUsdMicros: cached.lifetimeGrantedUsdMicros
401
+ };
402
+ }
403
+ var TOKEN_EXCHANGE_GRANT, SUBJECT_ACCESS_TOKEN_TYPE, EXCHANGE_RESPONSE_ERROR;
404
+ var init_device_exchange = __esm({
405
+ "src/signer/device-exchange.ts"() {
406
+ init_discovery();
407
+ init_encoding();
408
+ init_errors();
409
+ init_string_utils();
410
+ init_fetch_json();
411
+ init_json_fields();
412
+ init_mint_token();
413
+ TOKEN_EXCHANGE_GRANT = "urn:ietf:params:oauth:grant-type:token-exchange";
414
+ SUBJECT_ACCESS_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token";
415
+ EXCHANGE_RESPONSE_ERROR = "invalid_exchange_response";
416
+ }
417
+ });
418
+
419
+ // src/signer/api-key-exchange.ts
420
+ var api_key_exchange_exports = {};
421
+ __export(api_key_exchange_exports, {
422
+ createApiKeyExchangeHandler: () => createApiKeyExchangeHandler,
423
+ exchangeApiKeyForSigner: () => exchangeApiKeyForSigner,
424
+ mintSignerSessionFromApiKey: () => mintSignerSessionFromApiKey,
425
+ mintUserAccessTokenFromApiKey: () => mintUserAccessTokenFromApiKey,
426
+ parseApiKeyExchangeRequestBody: () => parseApiKeyExchangeRequestBody
427
+ });
428
+ async function parseApiKeyExchangeRequestBody(request) {
429
+ let body;
430
+ try {
431
+ body = await request.json();
432
+ } catch {
433
+ throw new PmtHouseError("Request body must be JSON", {
434
+ status: 400,
435
+ code: "invalid_request"
436
+ });
437
+ }
438
+ if (body === null || typeof body !== "object" || Array.isArray(body)) {
439
+ throw new PmtHouseError("Request body must be a JSON object", {
440
+ status: 400,
441
+ code: "invalid_request"
442
+ });
443
+ }
444
+ const record = body;
445
+ const apiKeyRaw = record.apiKey;
446
+ if (typeof apiKeyRaw !== "string" || !apiKeyRaw.trim()) {
447
+ throw new PmtHouseError("Request body must include apiKey", {
448
+ status: 400,
449
+ code: "invalid_request"
450
+ });
451
+ }
452
+ const scope = typeof record.scope === "string" && record.scope.trim() ? record.scope.trim() : void 0;
453
+ const clientId = typeof record.clientId === "string" && record.clientId.trim() ? record.clientId.trim() : void 0;
454
+ return { apiKey: apiKeyRaw.trim(), scope, clientId };
455
+ }
456
+ async function mintUserAccessTokenFromApiKey(input) {
457
+ const fetchImpl = input.fetch ?? fetch;
458
+ const issuerOrigin = stripIssuerOriginFromOidcUrl(input.issuerUrl);
459
+ const url = `${issuerOrigin}/api/v1/apps/${encodeURIComponent(input.publicClientId)}/auth/api-key/token`;
460
+ const response = await fetchImpl(url, {
461
+ method: "POST",
462
+ headers: {
463
+ Authorization: `Bearer ${input.apiKey}`,
464
+ "Content-Type": "application/json",
465
+ Accept: "application/json"
466
+ },
467
+ body: JSON.stringify(input.scope ? { scope: input.scope } : {}),
468
+ cache: "no-store"
469
+ });
470
+ const parsed = await readJsonObjectFromResponse(response, {
471
+ invalidJsonMessage: "API key token exchange returned invalid JSON",
472
+ invalidJsonCode: "invalid_token_response",
473
+ failureLabel: "API key token exchange failed",
474
+ defaultErrorCode: "api_key_token_exchange_failed"
475
+ });
476
+ const accessToken = parsed.access_token;
477
+ if (typeof accessToken !== "string" || !accessToken.trim()) {
478
+ throw new PmtHouseError("API key token exchange missing access_token", {
479
+ status: 502,
480
+ code: EXCHANGE_RESPONSE_ERROR2
481
+ });
482
+ }
483
+ const expiresIn = typeof parsed.expires_in === "number" && Number.isFinite(parsed.expires_in) ? parsed.expires_in : 900;
484
+ const scope = typeof parsed.scope === "string" && parsed.scope.trim() ? parsed.scope.trim() : input.scope?.trim() || "sign:job";
485
+ return {
486
+ access_token: accessToken.trim(),
487
+ expires_in: expiresIn,
488
+ scope
489
+ };
490
+ }
491
+ async function mintSignerSessionFromApiKey(input) {
492
+ const userToken = await mintUserAccessTokenFromApiKey({
493
+ issuerUrl: input.issuerUrl,
494
+ publicClientId: input.publicClientId,
495
+ apiKey: input.apiKey,
496
+ scope: input.scope,
497
+ fetch: input.fetch
498
+ });
499
+ return mintSignerTokenFromDeviceToken({
500
+ issuerUrl: input.issuerUrl,
501
+ m2mClientId: input.m2mClientId,
502
+ m2mClientSecret: input.m2mClientSecret,
503
+ deviceToken: userToken.access_token,
504
+ scope: userToken.scope,
505
+ audience: input.audience,
506
+ fetch: input.fetch,
507
+ allowInsecureHttp: input.allowInsecureHttp
508
+ });
509
+ }
510
+ async function exchangeApiKeyForSigner(options) {
511
+ const fetchImpl = options.fetch ?? fetch;
512
+ const url = `${stripTrailingSlashes(options.facadeUrl)}/api/pymthouse/keys/exchange`;
513
+ const body = { apiKey: options.apiKey };
514
+ if (options.scope?.trim()) {
515
+ body.scope = options.scope.trim();
516
+ }
517
+ if (options.clientId?.trim()) {
518
+ body.clientId = options.clientId.trim();
519
+ }
520
+ const response = await fetchImpl(url, {
521
+ method: "POST",
522
+ headers: {
523
+ "Content-Type": "application/json",
524
+ Accept: "application/json"
525
+ },
526
+ body: JSON.stringify(body),
527
+ cache: "no-store"
528
+ });
529
+ const parsed = await readJsonObjectFromResponse(response, {
530
+ invalidJsonMessage: "API key exchange returned invalid JSON",
531
+ invalidJsonCode: EXCHANGE_RESPONSE_ERROR2,
532
+ failureLabel: "API key exchange failed",
533
+ defaultErrorCode: "api_key_exchange_failed"
534
+ });
535
+ const accessToken = extractSignerAccessTokenFromExchangeBody(parsed);
536
+ const signerUrlRaw = parsed.signerUrl ?? parsed.signer_url;
537
+ const signerUrl = typeof signerUrlRaw === "string" && signerUrlRaw.trim() ? signerUrlRaw.trim() : void 0;
538
+ return normalizeDeviceExchangeResponse(
539
+ {
540
+ access_token: accessToken,
541
+ expires_in: typeof parsed.expires_in === "number" && Number.isFinite(parsed.expires_in) ? parsed.expires_in : 3600,
542
+ scope: typeof parsed.scope === "string" && parsed.scope.trim() ? parsed.scope.trim() : "sign:job",
543
+ balanceUsdMicros: typeof parsed.balanceUsdMicros === "string" ? parsed.balanceUsdMicros : "0",
544
+ lifetimeGrantedUsdMicros: typeof parsed.lifetimeGrantedUsdMicros === "string" ? parsed.lifetimeGrantedUsdMicros : "0"
545
+ },
546
+ { signerUrl }
547
+ );
548
+ }
549
+ function createApiKeyExchangeHandler(config) {
550
+ const publicClientId = config.publicClientId.trim();
551
+ return async function apiKeyExchangeHandler(request) {
552
+ try {
553
+ if (request.method !== "POST") {
554
+ return new Response(JSON.stringify({ error: "method_not_allowed" }), {
555
+ status: 405,
556
+ headers: { "Content-Type": "application/json" }
557
+ });
558
+ }
559
+ const parsed = await parseApiKeyExchangeRequestBody(request);
560
+ const effectiveClientId = parsed.clientId?.trim() || publicClientId;
561
+ if (effectiveClientId !== publicClientId) {
562
+ throw new PmtHouseError("clientId does not match configured public client", {
563
+ status: 400,
564
+ code: "invalid_request"
565
+ });
566
+ }
567
+ const minted = await mintSignerSessionFromApiKey({
568
+ issuerUrl: config.issuerUrl,
569
+ publicClientId,
570
+ m2mClientId: config.m2mClientId,
571
+ m2mClientSecret: config.m2mClientSecret,
572
+ apiKey: parsed.apiKey,
573
+ scope: parsed.scope,
574
+ audience: config.audience,
575
+ fetch: config.fetch,
576
+ allowInsecureHttp: config.allowInsecureHttp
577
+ });
578
+ const signerUrlValue = typeof config.signerUrl === "string" && config.signerUrl.trim() ? config.signerUrl.trim() : void 0;
579
+ const body = normalizeDeviceExchangeResponse(minted, { signerUrl: signerUrlValue });
580
+ return new Response(JSON.stringify(body), {
581
+ status: 200,
582
+ headers: {
583
+ "Content-Type": "application/json",
584
+ "Cache-Control": "no-store"
585
+ }
586
+ });
587
+ } catch (error) {
588
+ return signerHandlerErrorResponse(error);
589
+ }
590
+ };
591
+ }
592
+ var EXCHANGE_RESPONSE_ERROR2;
593
+ var init_api_key_exchange = __esm({
594
+ "src/signer/api-key-exchange.ts"() {
595
+ init_string_utils();
596
+ init_errors();
597
+ init_fetch_json();
598
+ init_handler_errors();
599
+ init_device_exchange();
600
+ EXCHANGE_RESPONSE_ERROR2 = "invalid_exchange_response";
601
+ }
602
+ });
603
+
604
+ // src/client.ts
605
+ init_encoding();
606
+ init_discovery();
607
+ init_errors();
608
+ function parseAppManifestResponse(json) {
609
+ if (!json || typeof json !== "object" || Array.isArray(json)) {
610
+ return { capabilities: [], excludedCapabilities: [] };
611
+ }
612
+ const record = json;
613
+ const capabilities = parseCapabilityArray(record.capabilities);
614
+ const excludedCapabilities = parseCapabilityArray(record.excludedCapabilities);
615
+ const manifestVersion = typeof record.manifestVersion === "string" && record.manifestVersion.trim() ? record.manifestVersion.trim() : void 0;
616
+ return { capabilities, excludedCapabilities, manifestVersion };
617
+ }
618
+ function parseCapabilityArray(raw) {
619
+ if (!Array.isArray(raw)) return [];
620
+ return raw.filter(
621
+ (c) => !!c && typeof c === "object" && typeof c.pipeline === "string" && typeof c.modelId === "string"
622
+ );
623
+ }
624
+
625
+ // src/client.ts
626
+ init_string_utils();
627
+
628
+ // src/tokens.ts
629
+ var SIGNER_SESSION_TTL_MS = 90 * 24 * 60 * 60 * 1e3;
630
+ var SIGNER_SESSION_EXPIRES_IN_SEC = Math.floor(SIGNER_SESSION_TTL_MS / 1e3);
631
+ var SIGN_JOB_SCOPE = "sign:job";
632
+ function isLikelyOidcJwt(rawToken) {
633
+ const t = rawToken.trim();
634
+ return t.startsWith("eyJ") && t.split(".").length >= 3;
635
+ }
636
+ function parseSignerSessionExchange(res) {
637
+ const accessToken = typeof res.access_token === "string" ? res.access_token.trim() : "";
638
+ if (!accessToken) {
639
+ throw new Error("PymtHouse signer session exchange returned no access_token");
640
+ }
641
+ if (isLikelyOidcJwt(accessToken)) {
642
+ throw new Error(
643
+ "PymtHouse signer session exchange returned a JWT; expected opaque signer session token"
644
+ );
645
+ }
646
+ const tokenType = typeof res.token_type === "string" && res.token_type.trim() ? res.token_type.trim() : "Bearer";
647
+ 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;
648
+ const scope = typeof res.scope === "string" && res.scope.trim() ? res.scope.trim() : SIGN_JOB_SCOPE;
649
+ return {
650
+ accessToken,
651
+ tokenType,
652
+ expiresIn,
653
+ scope
654
+ };
655
+ }
656
+
657
+ // src/usage.ts
658
+ function parseSafeBigInt(value, fallback = 0n) {
659
+ try {
660
+ return BigInt(value);
661
+ } catch {
662
+ return fallback;
663
+ }
664
+ }
665
+ function getEndUserIdsForExternalUser(usage, externalUserId) {
666
+ const userIds = /* @__PURE__ */ new Set();
667
+ for (const row of usage.byUser ?? []) {
668
+ if (row.externalUserId === externalUserId && row.endUserId !== "unknown") {
669
+ userIds.add(row.endUserId);
670
+ }
671
+ }
672
+ return [...userIds];
673
+ }
674
+ function summarizeUsageFiatForExternalUser(usageByUser, externalUserId) {
675
+ const rows = usageByUser.byUser ?? [];
676
+ let requestCount = 0;
677
+ let networkFeeUsdMicros = 0n;
678
+ let ownerChargeUsdMicros = 0n;
679
+ let endUserBillableUsdMicros = 0n;
680
+ let currency = "USD";
681
+ for (const row of rows) {
682
+ if (row.externalUserId !== externalUserId) continue;
683
+ requestCount += row.requestCount;
684
+ if (row.currency) currency = row.currency;
685
+ if (row.networkFeeUsdMicros) {
686
+ networkFeeUsdMicros += BigInt(row.networkFeeUsdMicros);
687
+ }
688
+ if (row.ownerChargeUsdMicros) {
689
+ ownerChargeUsdMicros += BigInt(row.ownerChargeUsdMicros);
690
+ }
691
+ if (row.endUserBillableUsdMicros) {
692
+ endUserBillableUsdMicros += BigInt(row.endUserBillableUsdMicros);
693
+ }
694
+ }
695
+ return {
696
+ externalUserId,
697
+ requestCount,
698
+ currency,
699
+ networkFeeUsdMicros: networkFeeUsdMicros.toString(),
700
+ ownerChargeUsdMicros: ownerChargeUsdMicros.toString(),
701
+ endUserBillableUsdMicros: endUserBillableUsdMicros.toString()
702
+ };
703
+ }
704
+ function mergeUsageByPipelineModel(usagePipelineModels) {
705
+ let responses;
706
+ if (Array.isArray(usagePipelineModels)) {
707
+ responses = usagePipelineModels;
708
+ } else if (usagePipelineModels) {
709
+ responses = [usagePipelineModels];
710
+ } else {
711
+ responses = [];
712
+ }
713
+ const byKey = /* @__PURE__ */ new Map();
714
+ for (const response of responses) {
715
+ for (const row of response.byPipelineModel ?? []) {
716
+ const { pipeline, modelId } = row;
717
+ if (!pipeline || !modelId) continue;
718
+ const key = JSON.stringify([pipeline, modelId]);
719
+ const existing = byKey.get(key);
720
+ const rowCurrency = row.currency ?? "USD";
721
+ const networkFee = row.networkFeeUsdMicros ?? "0";
722
+ const ownerCharge = row.ownerChargeUsdMicros ?? "0";
723
+ const endUserBillable = row.endUserBillableUsdMicros ?? "0";
724
+ if (!existing) {
725
+ byKey.set(key, {
726
+ pipeline,
727
+ modelId,
728
+ requestCount: row.requestCount,
729
+ currency: rowCurrency,
730
+ networkFeeUsdMicros: networkFee,
731
+ ownerChargeUsdMicros: ownerCharge,
732
+ endUserBillableUsdMicros: endUserBillable
733
+ });
734
+ continue;
735
+ }
736
+ byKey.set(key, {
737
+ ...existing,
738
+ requestCount: existing.requestCount + row.requestCount,
739
+ networkFeeUsdMicros: (parseSafeBigInt(existing.networkFeeUsdMicros) + parseSafeBigInt(networkFee)).toString(),
740
+ ownerChargeUsdMicros: (parseSafeBigInt(existing.ownerChargeUsdMicros) + parseSafeBigInt(ownerCharge)).toString(),
741
+ endUserBillableUsdMicros: (parseSafeBigInt(existing.endUserBillableUsdMicros) + parseSafeBigInt(endUserBillable)).toString()
742
+ });
743
+ }
744
+ }
745
+ return [...byKey.values()].sort((a, b) => {
746
+ if (a.pipeline === b.pipeline) return a.modelId.localeCompare(b.modelId);
747
+ return a.pipeline.localeCompare(b.pipeline);
748
+ });
749
+ }
750
+ function buildMeScopeUsagePayload(usageByUser, externalUserId, usagePipelineModel, usageDaily) {
751
+ const summary = summarizeUsageFiatForExternalUser(usageByUser, externalUserId);
752
+ const pipelineModels = mergeUsageByPipelineModel(usagePipelineModel);
753
+ const dailyByPipeline = usageDaily?.byDailyPipeline ?? [];
754
+ return {
755
+ clientId: usageByUser.clientId,
756
+ period: usageByUser.period,
757
+ currentUser: {
758
+ externalUserId: summary.externalUserId,
759
+ requestCount: summary.requestCount,
760
+ currency: summary.currency,
761
+ networkFeeUsdMicros: summary.networkFeeUsdMicros,
762
+ ownerChargeUsdMicros: summary.ownerChargeUsdMicros,
763
+ endUserBillableUsdMicros: summary.endUserBillableUsdMicros,
764
+ pipelineModels,
765
+ dailyByPipeline
766
+ }
767
+ };
768
+ }
769
+ var DEFAULT_MAX_END_USER_IDS = 25;
770
+
771
+ // src/oauth-map.ts
772
+ init_errors();
126
773
  var ACCEPTED_ISSUED_TOKEN_TYPES = /* @__PURE__ */ new Set([
127
774
  "urn:ietf:params:oauth:token-type:access_token",
128
775
  "urn:pmth:token-type:remote-signer-session"
@@ -215,9 +862,94 @@ function m2mClient(clientId) {
215
862
  return { client_id: clientId };
216
863
  }
217
864
 
865
+ // src/ingest.ts
866
+ init_encoding();
867
+ init_errors();
868
+ init_string_utils();
869
+ function ingestUrl(issuerUrl, publicClientId) {
870
+ const origin = new URL(stripTrailingSlashes(issuerUrl)).origin;
871
+ return `${origin}/api/v1/apps/${encodeURIComponent(publicClientId)}/usage/signed-tickets`;
872
+ }
873
+ async function readJsonResponse(response) {
874
+ const text = await response.text();
875
+ if (!text.trim()) {
876
+ return {};
877
+ }
878
+ try {
879
+ const parsed = JSON.parse(text);
880
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
881
+ } catch {
882
+ return {};
883
+ }
884
+ }
885
+ async function ingestSignedTicket(options) {
886
+ const fetchImpl = options.fetch ?? fetch;
887
+ const url = ingestUrl(options.issuerUrl, options.publicClientId);
888
+ const response = await fetchImpl(url, {
889
+ method: "POST",
890
+ headers: {
891
+ Authorization: encodeClientSecretBasic(options.m2mClientId, options.m2mClientSecret),
892
+ "Content-Type": "application/json",
893
+ Accept: "application/json"
894
+ },
895
+ body: JSON.stringify(options.ticket),
896
+ cache: "no-store"
897
+ });
898
+ const body = await readJsonResponse(response);
899
+ if (!response.ok) {
900
+ const message = typeof body.error === "string" ? body.error : `Signed-ticket ingest failed (${response.status})`;
901
+ throw new PmtHouseError(message, {
902
+ status: response.status,
903
+ code: "ingest_failed",
904
+ details: body
905
+ });
906
+ }
907
+ return {
908
+ ingested: Boolean(body.ingested),
909
+ duplicate: Boolean(body.duplicate),
910
+ source: body.source === "openmeter" ? "openmeter" : "disabled"
911
+ };
912
+ }
913
+ async function ingestSignedTicketsBatch(options) {
914
+ const fetchImpl = options.fetch ?? fetch;
915
+ const url = ingestUrl(options.issuerUrl, options.publicClientId);
916
+ const response = await fetchImpl(url, {
917
+ method: "POST",
918
+ headers: {
919
+ Authorization: encodeClientSecretBasic(options.m2mClientId, options.m2mClientSecret),
920
+ "Content-Type": "application/json",
921
+ Accept: "application/json"
922
+ },
923
+ body: JSON.stringify({ tickets: options.tickets }),
924
+ cache: "no-store"
925
+ });
926
+ const body = await readJsonResponse(response);
927
+ if (!response.ok) {
928
+ const message = typeof body.error === "string" ? body.error : `Signed-ticket batch ingest failed (${response.status})`;
929
+ throw new PmtHouseError(message, {
930
+ status: response.status,
931
+ code: "ingest_failed",
932
+ details: body
933
+ });
934
+ }
935
+ const rawResults = Array.isArray(body.results) ? body.results : [];
936
+ return {
937
+ results: rawResults.map((entry) => {
938
+ const row = entry ?? {};
939
+ return {
940
+ requestId: typeof row.requestId === "string" ? row.requestId : void 0,
941
+ ok: row.ok === true,
942
+ ingested: Boolean(row.ingested),
943
+ duplicate: Boolean(row.duplicate),
944
+ source: row.source === "openmeter" ? "openmeter" : "disabled"
945
+ };
946
+ })
947
+ };
948
+ }
949
+
218
950
  // src/client.ts
219
- var TOKEN_EXCHANGE_GRANT = "urn:ietf:params:oauth:grant-type:token-exchange";
220
- var SUBJECT_ACCESS_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token";
951
+ var TOKEN_EXCHANGE_GRANT2 = "urn:ietf:params:oauth:grant-type:token-exchange";
952
+ var SUBJECT_ACCESS_TOKEN_TYPE2 = "urn:ietf:params:oauth:token-type:access_token";
221
953
  var REQUESTED_ACCESS_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token";
222
954
  var DEVICE_RESOURCE_PREFIX = "urn:pmth:device_code:";
223
955
  function normalizeUserCode(value) {
@@ -344,6 +1076,50 @@ var PmtHouseClient = class {
344
1076
  cache: "no-store"
345
1077
  });
346
1078
  }
1079
+ /**
1080
+ * Exchange a long-lived dashboard API key (`pmth_*`) for a short-lived user JWT.
1081
+ */
1082
+ async exchangeApiKeyForUserAccessToken(input) {
1083
+ const url = `${this.getAppsBaseUrl()}/auth/api-key/token`;
1084
+ return this.requestJson(url, {
1085
+ method: "POST",
1086
+ headers: {
1087
+ Authorization: `Bearer ${input.apiKey.trim()}`,
1088
+ "Content-Type": "application/json",
1089
+ Accept: "application/json"
1090
+ },
1091
+ body: JSON.stringify(input.scope ? { scope: input.scope } : {}),
1092
+ cache: "no-store"
1093
+ });
1094
+ }
1095
+ /**
1096
+ * Exchange a dashboard API key for a signer session via a trusted facade (recommended)
1097
+ * or directly when M2M credentials are available on this client.
1098
+ */
1099
+ async exchangeApiKeyForSignerSession(input) {
1100
+ if (input.facadeUrl?.trim()) {
1101
+ const { exchangeApiKeyForSigner: exchangeApiKeyForSigner2 } = await Promise.resolve().then(() => (init_api_key_exchange(), api_key_exchange_exports));
1102
+ const exchanged = await exchangeApiKeyForSigner2({
1103
+ facadeUrl: input.facadeUrl.trim(),
1104
+ apiKey: input.apiKey,
1105
+ scope: input.scope,
1106
+ clientId: this.publicClientId,
1107
+ fetch: this.fetchImpl
1108
+ });
1109
+ return {
1110
+ access_token: exchanged.access_token,
1111
+ token_type: exchanged.token_type,
1112
+ expires_in: exchanged.expires_in,
1113
+ scope: exchanged.scope,
1114
+ issued_token_type: "urn:ietf:params:oauth:token-type:access_token"
1115
+ };
1116
+ }
1117
+ const userToken = await this.exchangeApiKeyForUserAccessToken({
1118
+ apiKey: input.apiKey,
1119
+ scope: input.scope
1120
+ });
1121
+ return this.exchangeForSignerSession({ userJwt: userToken.access_token });
1122
+ }
347
1123
  async completeDeviceApproval(input) {
348
1124
  const as = await loadAuthorizationServer(this.issuerUrl, this.fetchImpl, {
349
1125
  allowInsecureHttp: this.allowInsecureHttp
@@ -352,14 +1128,14 @@ var PmtHouseClient = class {
352
1128
  const clientAuth = this.m2mClientAuth();
353
1129
  const params = new URLSearchParams();
354
1130
  params.set("subject_token", input.userJwt);
355
- params.set("subject_token_type", SUBJECT_ACCESS_TOKEN_TYPE);
1131
+ params.set("subject_token_type", SUBJECT_ACCESS_TOKEN_TYPE2);
356
1132
  params.set("resource", buildDeviceCodeResource(input.userCode));
357
1133
  try {
358
1134
  const response = await oauth4webapi.genericTokenEndpointRequest(
359
1135
  as,
360
1136
  client,
361
1137
  clientAuth,
362
- TOKEN_EXCHANGE_GRANT,
1138
+ TOKEN_EXCHANGE_GRANT2,
363
1139
  params,
364
1140
  this.tokenEndpointFetchOptions()
365
1141
  );
@@ -407,7 +1183,7 @@ var PmtHouseClient = class {
407
1183
  const clientAuth = this.m2mClientAuth();
408
1184
  const params = new URLSearchParams();
409
1185
  params.set("subject_token", input.userJwt);
410
- params.set("subject_token_type", SUBJECT_ACCESS_TOKEN_TYPE);
1186
+ params.set("subject_token_type", SUBJECT_ACCESS_TOKEN_TYPE2);
411
1187
  params.set("requested_token_type", REQUESTED_ACCESS_TOKEN_TYPE);
412
1188
  const resourceCandidate = typeof input.resource === "string" && input.resource.trim() !== "" ? input.resource.trim() : this.issuerUrl;
413
1189
  params.set("resource", stripTrailingSlashes(resourceCandidate));
@@ -416,7 +1192,7 @@ var PmtHouseClient = class {
416
1192
  as,
417
1193
  client,
418
1194
  clientAuth,
419
- TOKEN_EXCHANGE_GRANT,
1195
+ TOKEN_EXCHANGE_GRANT2,
420
1196
  params,
421
1197
  this.tokenEndpointFetchOptions()
422
1198
  );
@@ -472,12 +1248,267 @@ var PmtHouseClient = class {
472
1248
  if (input.groupBy) url.searchParams.set("groupBy", input.groupBy);
473
1249
  if (input.userId) url.searchParams.set("userId", input.userId);
474
1250
  if (input.gatewayRequestId) url.searchParams.set("gatewayRequestId", input.gatewayRequestId);
1251
+ if (input.includeRetail) url.searchParams.set("include", "retail");
1252
+ return this.requestJson(url.toString(), {
1253
+ method: "GET",
1254
+ headers: this.builderHeaders(),
1255
+ cache: "no-store"
1256
+ });
1257
+ }
1258
+ /**
1259
+ * Session-scoped usage for one `externalUserId`: user rollup plus merged pipeline/model breakdown.
1260
+ */
1261
+ async ingestSignedTicket(ticket) {
1262
+ return ingestSignedTicket({
1263
+ issuerUrl: this.issuerUrl,
1264
+ publicClientId: this.publicClientId,
1265
+ m2mClientId: this.m2mClientId,
1266
+ m2mClientSecret: this.m2mClientSecret,
1267
+ ticket,
1268
+ fetch: this.fetchImpl
1269
+ });
1270
+ }
1271
+ async ingestSignedTickets(tickets) {
1272
+ return ingestSignedTicketsBatch({
1273
+ issuerUrl: this.issuerUrl,
1274
+ publicClientId: this.publicClientId,
1275
+ m2mClientId: this.m2mClientId,
1276
+ m2mClientSecret: this.m2mClientSecret,
1277
+ tickets,
1278
+ fetch: this.fetchImpl
1279
+ });
1280
+ }
1281
+ async getSignerRouting() {
1282
+ return this.requestJson(
1283
+ `${this.getAppsBaseUrl()}/signer/routing`,
1284
+ {
1285
+ method: "GET",
1286
+ headers: this.builderHeaders(),
1287
+ cache: "no-store"
1288
+ }
1289
+ );
1290
+ }
1291
+ async listBillingProducts() {
1292
+ const url = `${this.getAppsBaseUrl()}/plans?apiVersion=2`;
1293
+ const body = await this.requestJson(
1294
+ url,
1295
+ {
1296
+ method: "GET",
1297
+ headers: this.builderHeaders(),
1298
+ cache: "no-store"
1299
+ }
1300
+ );
1301
+ return {
1302
+ apiVersion: body.apiVersion ?? 2,
1303
+ products: body.products ?? body.plans ?? []
1304
+ };
1305
+ }
1306
+ async syncBillingProduct(planId) {
1307
+ return this.requestJson(
1308
+ `${this.getAppsBaseUrl()}/plans/${encodeURIComponent(planId)}/sync`,
1309
+ {
1310
+ method: "POST",
1311
+ headers: this.builderHeaders(),
1312
+ cache: "no-store"
1313
+ }
1314
+ );
1315
+ }
1316
+ async getUsageBalance(externalUserId) {
1317
+ const url = new URL(`${this.getAppsBaseUrl()}/usage/balance`);
1318
+ url.searchParams.set("externalUserId", externalUserId);
475
1319
  return this.requestJson(url.toString(), {
476
1320
  method: "GET",
477
1321
  headers: this.builderHeaders(),
478
1322
  cache: "no-store"
479
1323
  });
480
1324
  }
1325
+ async getUserAllowances(externalUserId) {
1326
+ return this.requestJson(
1327
+ `${this.getAppsBaseUrl()}/users/${encodeURIComponent(externalUserId)}/allowances`,
1328
+ {
1329
+ method: "GET",
1330
+ headers: this.builderHeaders(),
1331
+ cache: "no-store"
1332
+ }
1333
+ );
1334
+ }
1335
+ async grantUserAllowance(externalUserId, input) {
1336
+ return this.requestJson(
1337
+ `${this.getAppsBaseUrl()}/users/${encodeURIComponent(externalUserId)}/allowances`,
1338
+ {
1339
+ method: "POST",
1340
+ headers: this.builderHeaders(),
1341
+ body: JSON.stringify(input),
1342
+ cache: "no-store"
1343
+ }
1344
+ );
1345
+ }
1346
+ /**
1347
+ * @deprecated Removed from PymtHouse — use {@link getUsageBalance} or {@link getUserAllowances}.
1348
+ */
1349
+ async getUserCredits(externalUserId) {
1350
+ return this.getUsageBalance(externalUserId);
1351
+ }
1352
+ /**
1353
+ * @deprecated Removed from PymtHouse — use {@link grantUserAllowance} (`POST .../allowances`).
1354
+ */
1355
+ async grantUserCredits(externalUserId, input) {
1356
+ const result = await this.grantUserAllowance(externalUserId, {
1357
+ amountUsdMicros: input.amountUsdMicros,
1358
+ source: input.source ?? "manual",
1359
+ featureKey: input.featureKey
1360
+ });
1361
+ const flat = result;
1362
+ const nested = result.allowances;
1363
+ return {
1364
+ externalUserId: result.externalUserId,
1365
+ balanceUsdMicros: flat.balanceUsdMicros ?? nested?.balanceUsdMicros ?? "0",
1366
+ consumedUsdMicros: flat.consumedUsdMicros ?? nested?.consumedUsdMicros ?? "0",
1367
+ lifetimeGrantedUsdMicros: flat.lifetimeGrantedUsdMicros ?? nested?.lifetimeGrantedUsdMicros ?? "0",
1368
+ hasAccess: flat.hasAccess ?? nested?.hasAccess ?? false,
1369
+ remainingUsdMicros: flat.balanceUsdMicros ?? nested?.balanceUsdMicros,
1370
+ grantedUsdMicros: flat.grantedUsdMicros,
1371
+ featureKey: flat.featureKey
1372
+ };
1373
+ }
1374
+ async getUserSubscription(externalUserId) {
1375
+ return this.requestJson(
1376
+ `${this.getAppsBaseUrl()}/users/${encodeURIComponent(externalUserId)}/subscription`,
1377
+ {
1378
+ method: "GET",
1379
+ headers: this.builderHeaders(),
1380
+ cache: "no-store"
1381
+ }
1382
+ );
1383
+ }
1384
+ async fetchUsageForExternalUser(input) {
1385
+ const usageByUser = await this.getUsage({
1386
+ startDate: input.startDate,
1387
+ endDate: input.endDate,
1388
+ groupBy: "user"
1389
+ });
1390
+ const userIds = getEndUserIdsForExternalUser(usageByUser, input.externalUserId);
1391
+ const cap = input.maxEndUserIds ?? DEFAULT_MAX_END_USER_IDS;
1392
+ const cappedUserIds = userIds.slice(0, cap);
1393
+ const usagePipelineModels = await Promise.all(
1394
+ cappedUserIds.map(
1395
+ (userId) => this.getUsage({
1396
+ startDate: input.startDate,
1397
+ endDate: input.endDate,
1398
+ groupBy: "pipeline_model",
1399
+ userId
1400
+ })
1401
+ )
1402
+ );
1403
+ const usageDaily = await this.getUsage({
1404
+ startDate: input.startDate,
1405
+ endDate: input.endDate,
1406
+ groupBy: "daily_pipeline",
1407
+ userId: input.externalUserId
1408
+ });
1409
+ return buildMeScopeUsagePayload(
1410
+ usageByUser,
1411
+ input.externalUserId,
1412
+ usagePipelineModels,
1413
+ usageDaily
1414
+ );
1415
+ }
1416
+ async getAppManifest(opts) {
1417
+ const url = `${this.getAppsBaseUrl()}/manifest`;
1418
+ const headers = {
1419
+ ...this.builderHeadersRecord()
1420
+ };
1421
+ if (opts?.ifNoneMatch) {
1422
+ headers["If-None-Match"] = opts.ifNoneMatch;
1423
+ }
1424
+ this.logger?.debug?.("PmtHouse request", { method: "GET", url });
1425
+ const response = await this.fetchImpl(url, {
1426
+ method: "GET",
1427
+ headers,
1428
+ signal: opts?.signal,
1429
+ cache: "no-store"
1430
+ });
1431
+ const etag = response.headers.get("etag")?.trim() ?? null;
1432
+ if (response.status === 304) {
1433
+ return {
1434
+ manifest: null,
1435
+ etag: etag ?? opts?.ifNoneMatch ?? null,
1436
+ notModified: true
1437
+ };
1438
+ }
1439
+ const raw = await response.text();
1440
+ const ct = response.headers.get("content-type") ?? "";
1441
+ const looksJson = ct.includes("application/json") || ct.includes("json");
1442
+ const parsed = raw && looksJson ? this.safeParseJson(raw) : null;
1443
+ if (!response.ok) {
1444
+ const details = parsed ?? {};
1445
+ let description;
1446
+ if (typeof details.error_description === "string") {
1447
+ description = details.error_description;
1448
+ } else if (typeof details.error === "string") {
1449
+ description = details.error;
1450
+ } else {
1451
+ description = `Request failed (${response.status})`;
1452
+ }
1453
+ throw new PmtHouseError(description, {
1454
+ status: response.status,
1455
+ code: typeof details.error === "string" ? details.error : "pymthouse_http_error",
1456
+ details
1457
+ });
1458
+ }
1459
+ if (!looksJson || parsed === null) {
1460
+ throw new PmtHouseError("Expected JSON response from Builder manifest endpoint", {
1461
+ status: 502,
1462
+ code: "invalid_response",
1463
+ details: { contentType: ct, preview: raw.slice(0, 200) }
1464
+ });
1465
+ }
1466
+ return {
1467
+ manifest: parseAppManifestResponse(parsed),
1468
+ etag,
1469
+ notModified: false
1470
+ };
1471
+ }
1472
+ /**
1473
+ * Upsert an external user, mint a short-lived JWT, and exchange for an opaque signer session.
1474
+ */
1475
+ async mintSignerSessionForExternalUser(input) {
1476
+ await this.upsertAppUser({
1477
+ externalUserId: input.externalUserId,
1478
+ email: input.email,
1479
+ status: "active"
1480
+ });
1481
+ const exchange = await this.mintUserSignerSessionToken({
1482
+ externalUserId: input.externalUserId,
1483
+ scope: input.scope ?? SIGN_JOB_SCOPE,
1484
+ resource: this.issuerUrl
1485
+ });
1486
+ return parseSignerSessionExchange(exchange);
1487
+ }
1488
+ /**
1489
+ * Approve a pending RFC 8628 device code for an external user (Option B).
1490
+ */
1491
+ async approveDeviceLogin(input) {
1492
+ if (input.publicClientId && input.publicClientId !== this.publicClientId) {
1493
+ throw new PmtHouseError(
1494
+ "publicClientId does not match configured public client id",
1495
+ { status: 400, code: "invalid_client" }
1496
+ );
1497
+ }
1498
+ await this.upsertAppUser({
1499
+ externalUserId: input.externalUserId,
1500
+ email: input.email,
1501
+ status: "active"
1502
+ });
1503
+ const userToken = await this.mintUserAccessToken({
1504
+ externalUserId: input.externalUserId,
1505
+ scope: SIGN_JOB_SCOPE
1506
+ });
1507
+ await this.completeDeviceApproval({
1508
+ userJwt: userToken.access_token,
1509
+ userCode: input.userCode
1510
+ });
1511
+ }
481
1512
  tokenEndpointFetchOptions() {
482
1513
  const o = {
483
1514
  [oauth4webapi.customFetch]: this.fetchImpl
@@ -494,6 +1525,9 @@ var PmtHouseClient = class {
494
1525
  return new URL(this.issuerUrl).origin;
495
1526
  }
496
1527
  builderHeaders() {
1528
+ return this.builderHeadersRecord();
1529
+ }
1530
+ builderHeadersRecord() {
497
1531
  return {
498
1532
  Authorization: encodeClientSecretBasic(this.m2mClientId, this.m2mClientSecret),
499
1533
  "Content-Type": "application/json",
@@ -517,7 +1551,14 @@ var PmtHouseClient = class {
517
1551
  const parsed = raw && looksJson ? this.safeParseJson(raw) : raw ? null : null;
518
1552
  if (!response.ok) {
519
1553
  const details = parsed ?? {};
520
- const description = typeof details.error_description === "string" ? details.error_description : typeof details.error === "string" ? details.error : `Request failed (${response.status})`;
1554
+ let description;
1555
+ if (typeof details.error_description === "string") {
1556
+ description = details.error_description;
1557
+ } else if (typeof details.error === "string") {
1558
+ description = details.error;
1559
+ } else {
1560
+ description = `Request failed (${response.status})`;
1561
+ }
521
1562
  throw new PmtHouseError(description, {
522
1563
  status: response.status,
523
1564
  code: typeof details.error === "string" ? details.error : "pymthouse_http_error",
@@ -561,6 +1602,8 @@ var PmtHouseClient = class {
561
1602
  };
562
1603
 
563
1604
  // src/env.ts
1605
+ init_errors();
1606
+ init_string_utils();
564
1607
  function assertEnvModuleServerOnly() {
565
1608
  if (typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined") {
566
1609
  throw new Error(