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