@pymthouse/builder-sdk 0.1.0 → 0.3.1

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 (77) hide show
  1. package/README.md +66 -0
  2. package/dist/{client-BroVFyIy.d.ts → client-BHfjDvIe.d.ts} +49 -1
  3. package/dist/{client-BhC1YhB1.d.cts → client-CvhJEhjV.d.cts} +49 -1
  4. package/dist/config.cjs +59 -3
  5. package/dist/config.cjs.map +1 -1
  6. package/dist/config.d.cts +8 -1
  7. package/dist/config.d.ts +8 -1
  8. package/dist/config.js +57 -4
  9. package/dist/config.js.map +1 -1
  10. package/dist/device-initiate.cjs +1 -1
  11. package/dist/device-initiate.cjs.map +1 -1
  12. package/dist/device-initiate.js +1 -1
  13. package/dist/device-initiate.js.map +1 -1
  14. package/dist/device.cjs +1 -1
  15. package/dist/device.cjs.map +1 -1
  16. package/dist/device.d.cts +1 -1
  17. package/dist/device.d.ts +1 -1
  18. package/dist/device.js +1 -1
  19. package/dist/device.js.map +1 -1
  20. package/dist/env.cjs +794 -36
  21. package/dist/env.cjs.map +1 -1
  22. package/dist/env.d.cts +2 -2
  23. package/dist/env.d.ts +2 -2
  24. package/dist/env.js +794 -36
  25. package/dist/env.js.map +1 -1
  26. package/dist/gateway/client/index.cjs +492 -0
  27. package/dist/gateway/client/index.cjs.map +1 -0
  28. package/dist/gateway/client/index.d.cts +63 -0
  29. package/dist/gateway/client/index.d.ts +63 -0
  30. package/dist/gateway/client/index.js +489 -0
  31. package/dist/gateway/client/index.js.map +1 -0
  32. package/dist/gateway/index.cjs +16 -0
  33. package/dist/gateway/index.cjs.map +1 -0
  34. package/dist/gateway/index.d.cts +52 -0
  35. package/dist/gateway/index.d.ts +52 -0
  36. package/dist/gateway/index.js +10 -0
  37. package/dist/gateway/index.js.map +1 -0
  38. package/dist/gateway/server/index.cjs +1248 -0
  39. package/dist/gateway/server/index.cjs.map +1 -0
  40. package/dist/gateway/server/index.d.cts +31 -0
  41. package/dist/gateway/server/index.d.ts +31 -0
  42. package/dist/gateway/server/index.js +1233 -0
  43. package/dist/gateway/server/index.js.map +1 -0
  44. package/dist/index.cjs +1075 -186
  45. package/dist/index.cjs.map +1 -1
  46. package/dist/index.d.cts +6 -4
  47. package/dist/index.d.ts +6 -4
  48. package/dist/index.js +1042 -163
  49. package/dist/index.js.map +1 -1
  50. package/dist/ingest-B3Yi8Tb1.d.cts +271 -0
  51. package/dist/ingest-DoKJTWU9.d.ts +271 -0
  52. package/dist/plan-pricing.cjs +108 -0
  53. package/dist/plan-pricing.cjs.map +1 -0
  54. package/dist/plan-pricing.d.cts +15 -0
  55. package/dist/plan-pricing.d.ts +15 -0
  56. package/dist/plan-pricing.js +98 -0
  57. package/dist/plan-pricing.js.map +1 -0
  58. package/dist/signer/server.cjs +1366 -0
  59. package/dist/signer/server.cjs.map +1 -0
  60. package/dist/signer/server.d.cts +73 -0
  61. package/dist/signer/server.d.ts +73 -0
  62. package/dist/signer/server.js +1331 -0
  63. package/dist/signer/server.js.map +1 -0
  64. package/dist/tokens.d.cts +1 -1
  65. package/dist/tokens.d.ts +1 -1
  66. package/dist/types-_R1AwEZp.d.cts +343 -0
  67. package/dist/types-_R1AwEZp.d.ts +343 -0
  68. package/dist/verify.cjs +1 -1
  69. package/dist/verify.cjs.map +1 -1
  70. package/dist/verify.d.cts +1 -1
  71. package/dist/verify.d.ts +1 -1
  72. package/dist/verify.js +1 -1
  73. package/dist/verify.js.map +1 -1
  74. package/gateway/proto/lp_rpc.proto +542 -0
  75. package/package.json +42 -1
  76. package/dist/types-rKzVXvMu.d.cts +0 -196
  77. package/dist/types-rKzVXvMu.d.ts +0 -196
@@ -0,0 +1,1248 @@
1
+ 'use strict';
2
+
3
+ var http = require('http');
4
+ var https = require('https');
5
+ var module$1 = require('module');
6
+ var path = require('path');
7
+ var url = require('url');
8
+ var tls = require('tls');
9
+ var crypto = require('crypto');
10
+
11
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
12
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
13
+
14
+ var http__default = /*#__PURE__*/_interopDefault(http);
15
+ var https__default = /*#__PURE__*/_interopDefault(https);
16
+ var path__default = /*#__PURE__*/_interopDefault(path);
17
+ var tls__default = /*#__PURE__*/_interopDefault(tls);
18
+
19
+ // src/string-utils.ts
20
+ function stripTrailingSlashes(value) {
21
+ let end = value.length;
22
+ while (end > 0 && (value.codePointAt(end - 1) ?? 0) === 47) {
23
+ end--;
24
+ }
25
+ return value.slice(0, end);
26
+ }
27
+ function endsWithIgnoreCase(value, suffix) {
28
+ if (suffix.length > value.length) {
29
+ return false;
30
+ }
31
+ const start = value.length - suffix.length;
32
+ for (let i = 0; i < suffix.length; i++) {
33
+ const a = value.codePointAt(start + i) ?? 0;
34
+ const b = suffix.codePointAt(i) ?? 0;
35
+ if (a !== b && (a | 32) !== (b | 32)) {
36
+ return false;
37
+ }
38
+ }
39
+ return true;
40
+ }
41
+ function stripSuffixIgnoreCase(value, suffix) {
42
+ return endsWithIgnoreCase(value, suffix) ? value.slice(0, value.length - suffix.length) : value;
43
+ }
44
+ function stripIssuerOriginFromOidcUrl(issuerUrl) {
45
+ let base = stripTrailingSlashes(issuerUrl.trim());
46
+ base = stripSuffixIgnoreCase(base, "/api/v1/oidc");
47
+ base = stripSuffixIgnoreCase(base, "/oidc");
48
+ return stripTrailingSlashes(base);
49
+ }
50
+
51
+ // src/gateway/server/config.ts
52
+ function readGatewayConfigFromEnv(env = process.env) {
53
+ const enabled = env.GATEWAY_ENABLED === "1" || env.NEXT_PUBLIC_GATEWAY_ENABLED === "1";
54
+ if (!enabled) {
55
+ return null;
56
+ }
57
+ const issuerUrl = env.PYMTHOUSE_ISSUER_URL?.trim();
58
+ const signerUrl = env.PYMTHOUSE_SIGNER_URL?.trim() || env.SIGNER_PUBLIC_URL?.trim() || (issuerUrl ? `${stripIssuerOriginFromOidcUrl(issuerUrl)}/api/signer` : "");
59
+ if (!signerUrl) {
60
+ return null;
61
+ }
62
+ const discoveryUrl = env.LIVEPEER_DISCOVERY_SERVICE_URL?.trim() || env.GATEWAY_DISCOVERY_URL?.trim() || void 0;
63
+ const discoveryTimeoutMs = Number(env.GATEWAY_DISCOVERY_TIMEOUT_MS ?? "60000");
64
+ const paymentIntervalMs = Number(env.GATEWAY_PAYMENT_INTERVAL_MS ?? "2000");
65
+ return {
66
+ enabled: true,
67
+ signerUrl,
68
+ discoveryUrl,
69
+ discoveryTimeoutMs: Number.isFinite(discoveryTimeoutMs) ? discoveryTimeoutMs : 6e4,
70
+ useTofu: env.GATEWAY_USE_TOFU !== "0",
71
+ paymentIntervalMs: Number.isFinite(paymentIntervalMs) ? paymentIntervalMs : 2e3
72
+ };
73
+ }
74
+
75
+ // src/gateway/server/auth.ts
76
+ function isAsciiWhitespace(code) {
77
+ return code <= 32;
78
+ }
79
+ function bearerTokenStart(header) {
80
+ const prefix = "bearer";
81
+ if (header.length < prefix.length) {
82
+ return -1;
83
+ }
84
+ for (let i = 0; i < prefix.length; i++) {
85
+ if (((header.codePointAt(i) ?? 0) | 32) !== prefix.codePointAt(i)) {
86
+ return -1;
87
+ }
88
+ }
89
+ let start = prefix.length;
90
+ while (start < header.length && isAsciiWhitespace(header.codePointAt(start) ?? 0)) {
91
+ start++;
92
+ }
93
+ return start < header.length ? start : -1;
94
+ }
95
+ function extractBearerToken(request) {
96
+ const header = request.headers.get("Authorization")?.trim();
97
+ if (!header) {
98
+ return null;
99
+ }
100
+ const start = bearerTokenStart(header);
101
+ if (start < 0) {
102
+ return null;
103
+ }
104
+ let end = header.length;
105
+ while (end > start && isAsciiWhitespace(header.codePointAt(end - 1) ?? 0)) {
106
+ end--;
107
+ }
108
+ return header.slice(start, end);
109
+ }
110
+ function unauthorizedResponse(message = "unauthorized") {
111
+ return Response.json({ error: message }, { status: 401 });
112
+ }
113
+ function forbiddenResponse(message = "forbidden") {
114
+ return Response.json({ error: message }, { status: 403 });
115
+ }
116
+ function disabledResponse() {
117
+ return Response.json(
118
+ {
119
+ error: "gateway_disabled",
120
+ error_description: "Set GATEWAY_ENABLED=1 to enable the browser gateway relay"
121
+ },
122
+ { status: 503 }
123
+ );
124
+ }
125
+
126
+ // src/gateway/types.ts
127
+ var LIVE_VIDEO_TO_VIDEO_CAPABILITY_ID = 35;
128
+ var DEFAULT_TRICKLE_MIME_TYPE = "video/mp2t";
129
+ var DEFAULT_DISCOVERY_TIMEOUT_MS = 6e4;
130
+ var TRICKLE_SEQ_LATEST = -1;
131
+ var TRICKLE_SEQ_CURRENT = -2;
132
+
133
+ // src/gateway/server/capabilities.ts
134
+ function modelCapabilityQuery(modelId) {
135
+ return `live-video-to-video/${modelId.trim()}`;
136
+ }
137
+ function buildLv2vCapabilitiesMessage(modelId) {
138
+ const capId = LIVE_VIDEO_TO_VIDEO_CAPABILITY_ID;
139
+ return {
140
+ capacities: { [capId]: 1 },
141
+ constraints: {
142
+ PerCapability: {
143
+ [capId]: {
144
+ models: {
145
+ [modelId.trim()]: {}
146
+ }
147
+ }
148
+ }
149
+ }
150
+ };
151
+ }
152
+ function capabilityForDiscoveryUrl(url, modelId) {
153
+ if (!url.includes("/v1/discovery/raw")) {
154
+ return modelCapabilityQuery(modelId);
155
+ }
156
+ if (!modelId.includes("/")) {
157
+ return modelId;
158
+ }
159
+ const segment = modelId.split("/").pop();
160
+ return segment ?? modelId;
161
+ }
162
+ function appendCapabilityQuery(url, modelId) {
163
+ const parsed = new URL(url);
164
+ parsed.searchParams.append("caps", capabilityForDiscoveryUrl(url, modelId));
165
+ return parsed.toString();
166
+ }
167
+ var insecureHttpsAgent = new https__default.default.Agent({
168
+ rejectUnauthorized: false
169
+ });
170
+ function encodeRequestBody(raw) {
171
+ if (raw === void 0) {
172
+ return void 0;
173
+ }
174
+ if (raw instanceof Buffer) {
175
+ return raw;
176
+ }
177
+ return Buffer.from(raw);
178
+ }
179
+ async function insecureFetch(url, init = {}) {
180
+ const parsed = new URL(url.includes("://") ? url : `https://${url}`);
181
+ const isHttps = parsed.protocol === "https:";
182
+ const lib = isHttps ? https__default.default : http__default.default;
183
+ const timeoutMs = init.timeoutMs ?? 6e4;
184
+ const body = encodeRequestBody(init.body);
185
+ const defaultMethod = body === void 0 ? "GET" : "POST";
186
+ return new Promise((resolve, reject) => {
187
+ const headers = { ...init.headers };
188
+ if (body !== void 0 && !headers["Content-Length"]) {
189
+ headers["Content-Length"] = String(body.length);
190
+ }
191
+ const req = lib.request(
192
+ parsed,
193
+ {
194
+ method: init.method ?? defaultMethod,
195
+ headers,
196
+ agent: isHttps ? insecureHttpsAgent : void 0,
197
+ rejectUnauthorized: false
198
+ },
199
+ (res) => {
200
+ const chunks = [];
201
+ res.on("data", (chunk) => chunks.push(chunk));
202
+ res.on("end", () => {
203
+ const merged = Buffer.concat(chunks);
204
+ const responseHeaders = new Headers();
205
+ for (const [key, value] of Object.entries(res.headers)) {
206
+ if (value === void 0) {
207
+ continue;
208
+ }
209
+ if (Array.isArray(value)) {
210
+ for (const item of value) {
211
+ responseHeaders.append(key, item);
212
+ }
213
+ } else {
214
+ responseHeaders.set(key, value);
215
+ }
216
+ }
217
+ resolve(
218
+ new Response(merged, {
219
+ status: res.statusCode ?? 0,
220
+ headers: responseHeaders
221
+ })
222
+ );
223
+ });
224
+ }
225
+ );
226
+ const timer = setTimeout(() => {
227
+ req.destroy(new Error(`Request timed out after ${timeoutMs}ms`));
228
+ }, timeoutMs);
229
+ if (init.signal) {
230
+ init.signal.addEventListener("abort", () => {
231
+ req.destroy(new Error("aborted"));
232
+ });
233
+ }
234
+ req.on("error", (err) => {
235
+ clearTimeout(timer);
236
+ reject(err);
237
+ });
238
+ req.on("close", () => clearTimeout(timer));
239
+ if (body !== void 0) {
240
+ req.write(body);
241
+ }
242
+ req.end();
243
+ });
244
+ }
245
+ async function readJsonResponse(response) {
246
+ const text = await response.text();
247
+ if (!text.trim()) {
248
+ return {};
249
+ }
250
+ return JSON.parse(text);
251
+ }
252
+ function httpOrigin(url) {
253
+ const parsed = new URL(url.includes("://") ? url : `https://${url}`);
254
+ return `${parsed.protocol}//${parsed.host}`;
255
+ }
256
+
257
+ // src/gateway/server/discovery.ts
258
+ var DISCOVERY_SERVICE_RAW_PATH = "/v1/discovery/raw";
259
+ function isDiscoveryServiceEndpoint(url) {
260
+ return url.includes(DISCOVERY_SERVICE_RAW_PATH);
261
+ }
262
+ function normalizeDiscoveryServiceUrl(url) {
263
+ const parsed = new URL(url.trim());
264
+ let path2 = parsed.pathname.replace(/\/$/, "");
265
+ if (!path2.endsWith(DISCOVERY_SERVICE_RAW_PATH)) {
266
+ if (path2.endsWith("/v1/discovery") || !path2) {
267
+ path2 = DISCOVERY_SERVICE_RAW_PATH;
268
+ }
269
+ }
270
+ parsed.pathname = path2;
271
+ if (!parsed.searchParams.has("serviceType")) {
272
+ parsed.searchParams.set("serviceType", "legacy");
273
+ }
274
+ return parsed.toString();
275
+ }
276
+ function resolveDiscoveryEndpoint(input) {
277
+ if (input.orchestratorUrl?.trim()) {
278
+ const list = input.orchestratorUrl.split(",").map((s) => s.trim()).filter(Boolean);
279
+ if (list.length > 0) {
280
+ return { url: "", headers: input.signerHeaders, discoveryService: false };
281
+ }
282
+ }
283
+ if (input.discoveryUrl?.trim()) {
284
+ let url = input.discoveryUrl.trim();
285
+ const discoveryService = isDiscoveryServiceEndpoint(url);
286
+ if (discoveryService) {
287
+ url = normalizeDiscoveryServiceUrl(url);
288
+ }
289
+ url = appendCapabilityQuery(url, input.modelId);
290
+ return {
291
+ url,
292
+ headers: input.signerHeaders,
293
+ discoveryService
294
+ };
295
+ }
296
+ if (input.signerUrl?.trim()) {
297
+ const url = appendCapabilityQuery(
298
+ `${httpOrigin(input.signerUrl)}/discover-orchestrators`,
299
+ input.modelId
300
+ );
301
+ return { url, headers: input.signerHeaders, discoveryService: false };
302
+ }
303
+ throw new Error("discovery requires orchestratorUrl, discoveryUrl, or signerUrl");
304
+ }
305
+ function pickDiscoveryAddress(record) {
306
+ if (typeof record.address === "string") {
307
+ return record.address;
308
+ }
309
+ if (typeof record.url === "string") {
310
+ return record.url;
311
+ }
312
+ return void 0;
313
+ }
314
+ function parseDiscoveryList(data) {
315
+ if (!Array.isArray(data)) {
316
+ throw new TypeError(`Discovery response must be a JSON list, got ${typeof data}`);
317
+ }
318
+ const urls = [];
319
+ for (const item of data) {
320
+ if (!item || typeof item !== "object") {
321
+ continue;
322
+ }
323
+ const record = item;
324
+ const trimmed = pickDiscoveryAddress(record)?.trim();
325
+ if (trimmed) {
326
+ urls.push(trimmed);
327
+ }
328
+ }
329
+ return urls;
330
+ }
331
+ async function discoverOrchestrators(input) {
332
+ if (input.orchestratorUrl?.trim()) {
333
+ return input.orchestratorUrl.split(",").map((s) => s.trim()).filter(Boolean);
334
+ }
335
+ const { url, headers } = resolveDiscoveryEndpoint(input);
336
+ const response = await insecureFetch(url, {
337
+ method: "GET",
338
+ headers: {
339
+ Accept: "application/json",
340
+ ...headers
341
+ },
342
+ timeoutMs: input.discoveryTimeoutMs ?? DEFAULT_DISCOVERY_TIMEOUT_MS
343
+ });
344
+ if (!response.ok) {
345
+ const body = await response.text();
346
+ throw new Error(`Discovery failed HTTP ${response.status}: ${body.slice(0, 500)}`);
347
+ }
348
+ const data = await readJsonResponse(response);
349
+ return parseDiscoveryList(data);
350
+ }
351
+ var cachedRoot = null;
352
+ function protoPath() {
353
+ const here = path__default.default.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))));
354
+ return path__default.default.resolve(here, "../../../gateway/proto/lp_rpc.proto");
355
+ }
356
+ function requireGrpcPeer(label, load) {
357
+ try {
358
+ return load();
359
+ } catch {
360
+ throw new Error(
361
+ `${label} is required for @pymthouse/builder-sdk/gateway/server. Install peer dependencies: @grpc/grpc-js @grpc/proto-loader`
362
+ );
363
+ }
364
+ }
365
+ function loadProtoRoot() {
366
+ const grpc = requireGrpcPeer(
367
+ "@grpc/grpc-js",
368
+ () => module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))("@grpc/grpc-js")
369
+ );
370
+ if (cachedRoot) {
371
+ return { grpc, root: cachedRoot };
372
+ }
373
+ const protoLoader = requireGrpcPeer(
374
+ "@grpc/proto-loader",
375
+ () => module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))("@grpc/proto-loader")
376
+ );
377
+ const packageDefinition = protoLoader.loadSync(protoPath(), {
378
+ keepCase: true,
379
+ longs: String,
380
+ enums: String,
381
+ defaults: true,
382
+ oneofs: true
383
+ });
384
+ cachedRoot = grpc.loadPackageDefinition(packageDefinition);
385
+ return { grpc, root: cachedRoot };
386
+ }
387
+ function loadOrchestratorGrpc() {
388
+ const { grpc, root } = loadProtoRoot();
389
+ return { grpc, Orchestrator: root.net.Orchestrator };
390
+ }
391
+ function encodeCapabilitiesBase64(modelId) {
392
+ const { root } = loadProtoRoot();
393
+ const bytes = root.net.Capabilities.serialize(buildLv2vCapabilitiesMessage(modelId));
394
+ return Buffer.from(bytes).toString("base64");
395
+ }
396
+
397
+ // src/gateway/server/signer-material.ts
398
+ var signerMaterialCache = /* @__PURE__ */ new Map();
399
+ function cacheKey(signerUrl, headers) {
400
+ const headerPart = headers ? JSON.stringify(Object.entries(headers).sort(([a], [b]) => a.localeCompare(b))) : "";
401
+ return `${httpOrigin(signerUrl)}|${headerPart}`;
402
+ }
403
+ async function getSignerMaterial(signerUrl, signerHeaders) {
404
+ if (!signerUrl.trim()) {
405
+ return { address: "", sig: "" };
406
+ }
407
+ const key = cacheKey(signerUrl, signerHeaders);
408
+ const cached = signerMaterialCache.get(key);
409
+ if (cached) {
410
+ return cached;
411
+ }
412
+ const url = `${httpOrigin(signerUrl)}/sign-orchestrator-info`;
413
+ const response = await insecureFetch(url, {
414
+ method: "POST",
415
+ headers: {
416
+ Accept: "application/json",
417
+ "Content-Type": "application/json",
418
+ ...signerHeaders
419
+ },
420
+ body: Buffer.from("{}"),
421
+ timeoutMs: 5e3
422
+ });
423
+ if (!response.ok) {
424
+ const body = await response.text();
425
+ throw new Error(`Signer error HTTP ${response.status}: ${body.slice(0, 500)}`);
426
+ }
427
+ const data = await readJsonResponse(response);
428
+ const address = data.address?.trim() ?? "";
429
+ const sig = data.signature?.trim() ?? "";
430
+ if (!address || !sig) {
431
+ throw new Error("Signer response missing address or signature");
432
+ }
433
+ const material = { address, sig };
434
+ signerMaterialCache.set(key, material);
435
+ return material;
436
+ }
437
+ function hexToBytes(hex) {
438
+ let value = hex.trim();
439
+ if (value.startsWith("0x") || value.startsWith("0X")) {
440
+ value = value.slice(2);
441
+ }
442
+ if (value.length % 2 === 1) {
443
+ value = `0${value}`;
444
+ }
445
+ return Buffer.from(value, "hex");
446
+ }
447
+ function signerMaterialToGrpcFields(material) {
448
+ return {
449
+ address: hexToBytes(material.address),
450
+ sig: hexToBytes(material.sig)
451
+ };
452
+ }
453
+ var tofuCache = /* @__PURE__ */ new Map();
454
+ function splitHostPort(target) {
455
+ const trimmed = target.trim();
456
+ if (trimmed.startsWith("[")) {
457
+ const host2 = trimmed.slice(1, trimmed.indexOf("]"));
458
+ const port = Number(trimmed.slice(trimmed.indexOf("]") + 2));
459
+ return { host: host2, port };
460
+ }
461
+ const [host, portRaw] = trimmed.split(":");
462
+ return { host, port: Number(portRaw) };
463
+ }
464
+ function isIpAddress(host) {
465
+ return /^\d{1,3}(\.\d{1,3}){3}$/.test(host) || host.includes(":");
466
+ }
467
+ function authorityFromSan(san) {
468
+ for (const entry of san) {
469
+ if (entry.typ === "DNS" && entry.val) {
470
+ return entry.val;
471
+ }
472
+ }
473
+ for (const entry of san) {
474
+ if (entry.typ === "IP" && entry.val) {
475
+ return entry.val;
476
+ }
477
+ }
478
+ return void 0;
479
+ }
480
+ function authorityFromSubject(cn) {
481
+ if (typeof cn === "string") {
482
+ return cn;
483
+ }
484
+ if (Array.isArray(cn) && cn.length > 0) {
485
+ return cn[0] ?? "";
486
+ }
487
+ return "";
488
+ }
489
+ function pickCertAuthority(cert) {
490
+ const san = cert.subjectaltname?.split(", ").map((entry) => {
491
+ const [typ, val] = entry.split(":");
492
+ return { typ, val };
493
+ });
494
+ if (san) {
495
+ const fromSan = authorityFromSan(san);
496
+ if (fromSan) {
497
+ return fromSan;
498
+ }
499
+ }
500
+ return authorityFromSubject(cert.subject?.CN);
501
+ }
502
+ async function fetchTofuRootCert(target) {
503
+ const { host, port } = splitHostPort(target);
504
+ return new Promise((resolve, reject) => {
505
+ const servername = isIpAddress(host) ? void 0 : host;
506
+ const socket = tls__default.default.connect(
507
+ {
508
+ host,
509
+ port,
510
+ servername,
511
+ rejectUnauthorized: false,
512
+ ALPNProtocols: ["h2"]
513
+ },
514
+ () => {
515
+ const peer = socket.getPeerCertificate();
516
+ socket.end();
517
+ if (!peer?.raw) {
518
+ reject(new Error("No peer certificate"));
519
+ return;
520
+ }
521
+ const pem = `-----BEGIN CERTIFICATE-----
522
+ ${peer.raw.toString("base64").match(/.{1,64}/g)?.join("\n")}
523
+ -----END CERTIFICATE-----
524
+ `;
525
+ const authority = pickCertAuthority(peer) || host;
526
+ resolve({ rootPem: Buffer.from(pem), authority });
527
+ }
528
+ );
529
+ socket.on("error", reject);
530
+ socket.setTimeout(5e3, () => {
531
+ socket.destroy(new Error("TLS probe timeout"));
532
+ });
533
+ });
534
+ }
535
+ function parseGrpcTarget(orchUrl) {
536
+ const url = orchUrl.includes("://") ? orchUrl : `https://${orchUrl}`;
537
+ const parsed = new URL(url);
538
+ if (parsed.protocol !== "https:") {
539
+ throw new Error(`Only https orchestrator URLs are supported (got ${parsed.protocol})`);
540
+ }
541
+ return parsed.host;
542
+ }
543
+ async function trustOnFirstUse(target) {
544
+ const cached = tofuCache.get(target);
545
+ if (cached) {
546
+ return cached;
547
+ }
548
+ const material = await fetchTofuRootCert(target);
549
+ tofuCache.set(target, material);
550
+ return material;
551
+ }
552
+ function evictTofuCache(target) {
553
+ tofuCache.delete(target);
554
+ }
555
+ function isCertVerifyError(message) {
556
+ return message.includes("CERTIFICATE_VERIFY_FAILED");
557
+ }
558
+
559
+ // src/gateway/server/orch-grpc.ts
560
+ async function getOrchestratorInfo(input) {
561
+ const useTofu = input.useTofu !== false;
562
+ const target = parseGrpcTarget(input.orchUrl);
563
+ const signer = await getSignerMaterial(input.signerUrl, input.signerHeaders);
564
+ const { address, sig } = signerMaterialToGrpcFields(signer);
565
+ const request = {
566
+ address,
567
+ sig,
568
+ capabilities: buildLv2vCapabilitiesMessage(input.modelId),
569
+ ignoreCapacityCheck: true
570
+ };
571
+ const maxAttempts = useTofu ? 2 : 1;
572
+ let lastError = null;
573
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
574
+ try {
575
+ return await callGetOrchestrator(target, request, useTofu);
576
+ } catch (err) {
577
+ lastError = err instanceof Error ? err : new Error(String(err));
578
+ if (useTofu && attempt === 0 && isCertVerifyError(lastError.message)) {
579
+ evictTofuCache(target);
580
+ continue;
581
+ }
582
+ throw lastError;
583
+ }
584
+ }
585
+ throw lastError ?? new Error("GetOrchestrator failed");
586
+ }
587
+ function callGetOrchestrator(target, request, useTofu) {
588
+ return new Promise((resolve, reject) => {
589
+ void (async () => {
590
+ try {
591
+ const { grpc, Orchestrator } = loadOrchestratorGrpc();
592
+ let credentials;
593
+ let options = {};
594
+ if (useTofu) {
595
+ const { rootPem, authority } = await trustOnFirstUse(target);
596
+ credentials = grpc.credentials.createSsl(rootPem);
597
+ options = {
598
+ "grpc.ssl_target_name_override": authority,
599
+ "grpc.default_authority": authority
600
+ };
601
+ } else {
602
+ credentials = grpc.credentials.createSsl();
603
+ }
604
+ const client = new Orchestrator(target, credentials, options);
605
+ client.GetOrchestrator(request, (err, response) => {
606
+ if (err) {
607
+ reject(err);
608
+ return;
609
+ }
610
+ resolve(response);
611
+ });
612
+ } catch (e) {
613
+ reject(e);
614
+ }
615
+ })();
616
+ });
617
+ }
618
+ function serializeOrchestratorInfo(info) {
619
+ if (typeof info.SerializeToString === "function") {
620
+ return Buffer.from(info.SerializeToString());
621
+ }
622
+ const { root } = loadProtoRoot();
623
+ return Buffer.from(root.net.OrchestratorInfo.serialize({ ...info }));
624
+ }
625
+
626
+ // src/gateway/server/payment-session.ts
627
+ var PaymentSession = class {
628
+ constructor(signerUrl, orchestratorInfo, signerHeaders, modelId, useTofu) {
629
+ this.signerUrl = signerUrl;
630
+ this.signerHeaders = signerHeaders;
631
+ this.modelId = modelId;
632
+ this.useTofu = useTofu;
633
+ this.orchestratorInfo = orchestratorInfo;
634
+ }
635
+ signerUrl;
636
+ signerHeaders;
637
+ modelId;
638
+ useTofu;
639
+ manifestId = null;
640
+ state = null;
641
+ orchestratorInfo;
642
+ setManifestId(manifestId) {
643
+ this.manifestId = manifestId.trim();
644
+ }
645
+ get transcoderUrl() {
646
+ const url = this.orchestratorInfo.transcoder?.trim();
647
+ if (!url) {
648
+ throw new Error("OrchestratorInfo missing transcoder URL");
649
+ }
650
+ return url;
651
+ }
652
+ async getPaymentHeaders() {
653
+ if (!this.signerUrl.trim()) {
654
+ return { payment: "", segCreds: "" };
655
+ }
656
+ let attempts = 0;
657
+ while (true) {
658
+ try {
659
+ return await this.requestPayment();
660
+ } catch (err) {
661
+ if (attempts >= 3 || !(err instanceof SignerRefreshRequired)) {
662
+ throw err;
663
+ }
664
+ this.orchestratorInfo = await getOrchestratorInfo({
665
+ orchUrl: this.transcoderUrl,
666
+ signerUrl: this.signerUrl,
667
+ signerHeaders: this.signerHeaders,
668
+ modelId: this.modelId,
669
+ useTofu: this.useTofu
670
+ });
671
+ attempts += 1;
672
+ }
673
+ }
674
+ }
675
+ async sendPayment(orchestratorUrl) {
676
+ const headers = await this.getPaymentHeaders();
677
+ const target = orchestratorUrl?.trim() || this.transcoderUrl;
678
+ const url = `${httpOrigin(target)}/payment`;
679
+ const response = await insecureFetch(url, {
680
+ method: "POST",
681
+ headers: {
682
+ "Livepeer-Payment": headers.payment,
683
+ "Livepeer-Segment": headers.segCreds
684
+ },
685
+ body: Buffer.alloc(0),
686
+ timeoutMs: 5e3
687
+ });
688
+ if (!response.ok) {
689
+ const body = await response.text();
690
+ throw new Error(`Payment POST failed HTTP ${response.status}: ${body.slice(0, 300)}`);
691
+ }
692
+ }
693
+ async requestPayment() {
694
+ const url = `${httpOrigin(this.signerUrl)}/generate-live-payment`;
695
+ const orchB64 = serializeOrchestratorInfo(this.orchestratorInfo).toString("base64");
696
+ const capsB64 = encodeCapabilitiesBase64(this.modelId);
697
+ const payload = {
698
+ orchestrator: orchB64,
699
+ type: "lv2v",
700
+ capabilities: capsB64
701
+ };
702
+ if (this.manifestId) {
703
+ payload.ManifestID = this.manifestId;
704
+ }
705
+ if (this.state) {
706
+ payload.state = this.state;
707
+ }
708
+ const response = await insecureFetch(url, {
709
+ method: "POST",
710
+ headers: {
711
+ Accept: "application/json",
712
+ "Content-Type": "application/json",
713
+ ...this.signerHeaders
714
+ },
715
+ body: Buffer.from(JSON.stringify(payload)),
716
+ timeoutMs: 1e4
717
+ });
718
+ if (response.status === 480) {
719
+ const orchHeader = response.headers.get("Livepeer-Orchestrator-URL")?.trim();
720
+ throw new SignerRefreshRequired(orchHeader ?? "");
721
+ }
722
+ if (!response.ok) {
723
+ const body = await response.text();
724
+ throw new Error(`generate-live-payment HTTP ${response.status}: ${body.slice(0, 500)}`);
725
+ }
726
+ const data = await readJsonResponse(response);
727
+ const payment = data.payment ?? "";
728
+ const segCreds = data.segCreds ?? "";
729
+ if (!payment) {
730
+ throw new Error("generate-live-payment missing payment field");
731
+ }
732
+ if (!data.state || typeof data.state !== "object") {
733
+ throw new Error("generate-live-payment missing state object");
734
+ }
735
+ this.state = data.state;
736
+ return { payment, segCreds };
737
+ }
738
+ };
739
+ var SignerRefreshRequired = class extends Error {
740
+ constructor(orchestratorUrl) {
741
+ super("Signer refresh required (HTTP 480)");
742
+ this.orchestratorUrl = orchestratorUrl;
743
+ this.name = "SignerRefreshRequired";
744
+ }
745
+ orchestratorUrl;
746
+ };
747
+
748
+ // src/gateway/server/lv2v.ts
749
+ async function startLv2vSession(input) {
750
+ const orchList = await discoverOrchestrators({
751
+ orchestratorUrl: input.request.orchestratorUrl,
752
+ discoveryUrl: input.request.discoveryUrl ?? input.discoveryUrl,
753
+ signerUrl: input.signerUrl,
754
+ signerHeaders: input.signerHeaders,
755
+ modelId: input.request.modelId,
756
+ discoveryTimeoutMs: input.discoveryTimeoutMs
757
+ });
758
+ if (orchList.length === 0) {
759
+ throw new Error("No orchestrators discovered");
760
+ }
761
+ const rejections = [];
762
+ for (const orchUrl of orchList) {
763
+ try {
764
+ return await startLv2vOnOrchestrator(orchUrl, input);
765
+ } catch (err) {
766
+ rejections.push({
767
+ url: orchUrl,
768
+ reason: err instanceof Error ? err.message : String(err)
769
+ });
770
+ }
771
+ }
772
+ throw new Error(
773
+ `All orchestrators failed (${rejections.length} tried): ${rejections.map((r) => `${r.url}: ${r.reason}`).join("; ")}`
774
+ );
775
+ }
776
+ async function startLv2vOnOrchestrator(orchUrl, input) {
777
+ const info = await getOrchestratorInfo({
778
+ orchUrl,
779
+ signerUrl: input.signerUrl,
780
+ signerHeaders: input.signerHeaders,
781
+ modelId: input.request.modelId,
782
+ useTofu: input.useTofu
783
+ });
784
+ const paymentSession = new PaymentSession(
785
+ input.signerUrl,
786
+ info,
787
+ input.signerHeaders,
788
+ input.request.modelId,
789
+ input.useTofu !== false
790
+ );
791
+ const paymentHeaders = await paymentSession.getPaymentHeaders();
792
+ const transcoder = paymentSession.transcoderUrl;
793
+ const url = `${httpOrigin(transcoder)}/live-video-to-video`;
794
+ const body = {
795
+ model_id: input.request.modelId
796
+ };
797
+ if (input.request.params) {
798
+ body.params = input.request.params;
799
+ }
800
+ if (input.request.streamId) {
801
+ body.stream_id = input.request.streamId;
802
+ }
803
+ if (input.request.requestId) {
804
+ body.gateway_request_id = input.request.requestId;
805
+ }
806
+ const response = await insecureFetch(url, {
807
+ method: "POST",
808
+ headers: {
809
+ Accept: "application/json",
810
+ "Content-Type": "application/json",
811
+ "Livepeer-Payment": paymentHeaders.payment,
812
+ "Livepeer-Segment": paymentHeaders.segCreds
813
+ },
814
+ body: Buffer.from(JSON.stringify(body)),
815
+ timeoutMs: 1e4
816
+ });
817
+ if (!response.ok) {
818
+ const text = await response.text();
819
+ throw new Error(`live-video-to-video HTTP ${response.status}: ${text.slice(0, 500)}`);
820
+ }
821
+ const data = await readJsonResponse(response);
822
+ const manifestId = data.manifest_id?.trim();
823
+ const publishUrl = data.publish_url?.trim();
824
+ const subscribeUrl = data.subscribe_url?.trim();
825
+ if (!manifestId) {
826
+ throw new Error("live-video-to-video response missing manifest_id");
827
+ }
828
+ if (!publishUrl) {
829
+ throw new Error("live-video-to-video response missing publish_url");
830
+ }
831
+ if (!subscribeUrl) {
832
+ throw new Error("live-video-to-video response missing subscribe_url");
833
+ }
834
+ paymentSession.setManifestId(manifestId);
835
+ return {
836
+ manifestId,
837
+ publishUrl,
838
+ subscribeUrl,
839
+ controlUrl: data.control_url?.trim(),
840
+ eventsUrl: data.events_url?.trim(),
841
+ mimeType: DEFAULT_TRICKLE_MIME_TYPE,
842
+ paymentSession,
843
+ orchestratorUrl: orchUrl
844
+ };
845
+ }
846
+ var SESSIONS_GLOBAL_KEY = /* @__PURE__ */ Symbol.for("@pymthouse/builder-sdk/gateway-sessions");
847
+ function sessionsMap() {
848
+ const globalStore = globalThis;
849
+ globalStore[SESSIONS_GLOBAL_KEY] ??= /* @__PURE__ */ new Map();
850
+ return globalStore[SESSIONS_GLOBAL_KEY];
851
+ }
852
+ function hashBearerToken(token) {
853
+ return crypto.createHash("sha256").update(token.trim()).digest("hex");
854
+ }
855
+ function createSessionId() {
856
+ return crypto.randomUUID();
857
+ }
858
+ function putSession(record) {
859
+ sessionsMap().set(record.id, record);
860
+ }
861
+ function getSession(sessionId) {
862
+ return sessionsMap().get(sessionId);
863
+ }
864
+ function deleteSession(sessionId) {
865
+ return sessionsMap().delete(sessionId);
866
+ }
867
+ function assertSessionOwner(record, ownerTokenHash) {
868
+ if (record.ownerTokenHash !== ownerTokenHash) {
869
+ throw new Error("Forbidden: session does not belong to this bearer token");
870
+ }
871
+ }
872
+ function closeSessionRecord(record) {
873
+ if (record.closed) {
874
+ return;
875
+ }
876
+ record.closed = true;
877
+ if (record.paymentInterval) {
878
+ clearInterval(record.paymentInterval);
879
+ record.paymentInterval = void 0;
880
+ }
881
+ }
882
+
883
+ // src/gateway/server/trickle-relay.ts
884
+ function parseTrickleIntHeader(headers, name) {
885
+ const raw = headers.get(name);
886
+ if (raw === null) {
887
+ return null;
888
+ }
889
+ const parsed = Number.parseInt(raw, 10);
890
+ return Number.isFinite(parsed) ? parsed : null;
891
+ }
892
+ function publishBaseUrl(record) {
893
+ return record.publishUrl.replace(/\/$/, "");
894
+ }
895
+ function subscribeBaseUrl(record) {
896
+ return record.subscribeUrl.replace(/\/$/, "");
897
+ }
898
+ async function ensureTrickleChannel(record) {
899
+ if (record.trickleCreated) {
900
+ return;
901
+ }
902
+ const response = await insecureFetch(record.publishUrl, {
903
+ method: "POST",
904
+ headers: {
905
+ "Expect-Content": record.mimeType
906
+ },
907
+ body: Buffer.alloc(0),
908
+ timeoutMs: 1e4
909
+ });
910
+ if (!response.ok) {
911
+ const body = await response.text();
912
+ throw new Error(`Trickle create failed HTTP ${response.status}: ${body.slice(0, 300)}`);
913
+ }
914
+ record.trickleCreated = true;
915
+ }
916
+ async function resolveTricklePublishSeq(record) {
917
+ await ensureTrickleChannel(record);
918
+ const url = `${publishBaseUrl(record)}/next`;
919
+ const response = await insecureFetch(url, {
920
+ method: "GET",
921
+ timeoutMs: 1e4
922
+ });
923
+ if (!response.ok) {
924
+ return 0;
925
+ }
926
+ const latest = response.headers.get("Lp-Trickle-Latest");
927
+ if (latest !== null) {
928
+ const parsed = Number.parseInt(latest, 10);
929
+ if (Number.isFinite(parsed)) {
930
+ return parsed;
931
+ }
932
+ }
933
+ return 0;
934
+ }
935
+ async function prepareTricklePublish(record) {
936
+ await ensureTrickleChannel(record);
937
+ if (record.publishSeq < 0) {
938
+ record.publishSeq = await resolveTricklePublishSeq(record);
939
+ }
940
+ }
941
+ async function publishTrickleSegment(record, seq, bytes, contentType) {
942
+ await prepareTricklePublish(record);
943
+ const url = `${publishBaseUrl(record)}/${seq}`;
944
+ const headers = {
945
+ "Content-Type": contentType
946
+ };
947
+ if (!record.trickleResetSent) {
948
+ headers["Lp-Trickle-Reset"] = "1";
949
+ record.trickleResetSent = true;
950
+ }
951
+ const response = await insecureFetch(url, {
952
+ method: "POST",
953
+ headers,
954
+ body: bytes,
955
+ timeoutMs: 12e4
956
+ });
957
+ if (!response.ok) {
958
+ const body = await response.text();
959
+ throw new Error(`Trickle publish failed HTTP ${response.status}: ${body.slice(0, 300)}`);
960
+ }
961
+ record.publishSeq = Math.max(record.publishSeq, seq + 1);
962
+ }
963
+ async function resolveTrickleSubscribeSeq(record) {
964
+ const url = `${subscribeBaseUrl(record)}/next`;
965
+ const response = await insecureFetch(url, {
966
+ method: "GET",
967
+ timeoutMs: 1e4
968
+ });
969
+ if (!response.ok) {
970
+ return TRICKLE_SEQ_LATEST;
971
+ }
972
+ const fromHeader = parseTrickleIntHeader(response.headers, "Lp-Trickle-Latest");
973
+ if (fromHeader !== null) {
974
+ return fromHeader;
975
+ }
976
+ const body = (await response.text()).trim();
977
+ const fromBody = Number.parseInt(body, 10);
978
+ return Number.isFinite(fromBody) ? fromBody : TRICKLE_SEQ_LATEST;
979
+ }
980
+ async function subscribeTrickleSegment(record, seq) {
981
+ const url = `${subscribeBaseUrl(record)}/${seq}`;
982
+ const response = await insecureFetch(url, {
983
+ method: "GET",
984
+ headers: record.trickleCreated ? { Connection: "close" } : void 0,
985
+ timeoutMs: 3e4
986
+ });
987
+ if (response.status === 404) {
988
+ const latest = await resolveTrickleSubscribeSeq(record);
989
+ return { wait: true, latestSeq: latest };
990
+ }
991
+ if (response.status === 470) {
992
+ const latestHeader = response.headers.get("Lp-Trickle-Latest");
993
+ const parsedLatest = latestHeader ? Number.parseInt(latestHeader, 10) : Number.NaN;
994
+ const latest = Number.isFinite(parsedLatest) ? parsedLatest : await resolveTrickleSubscribeSeq(record);
995
+ if (latest < seq) {
996
+ return { wait: true, latestSeq: latest };
997
+ }
998
+ if (latest === seq) {
999
+ return { wait: true, latestSeq: latest };
1000
+ }
1001
+ return subscribeTrickleSegment(record, latest);
1002
+ }
1003
+ if (!response.ok) {
1004
+ const body = await response.text();
1005
+ throw new Error(`Trickle subscribe failed HTTP ${response.status}: ${body.slice(0, 300)}`);
1006
+ }
1007
+ const data = response.body;
1008
+ if (!data) {
1009
+ throw new Error("Trickle subscribe response missing body stream");
1010
+ }
1011
+ const contentType = response.headers.get("Content-Type") ?? record.mimeType;
1012
+ const segmentSeq = parseTrickleIntHeader(response.headers, "Lp-Trickle-Seq") ?? Math.max(seq, 0);
1013
+ const latestSeq = parseTrickleIntHeader(response.headers, "Lp-Trickle-Latest") ?? segmentSeq;
1014
+ return { data, contentType, segmentSeq, nextSeq: segmentSeq + 1, latestSeq };
1015
+ }
1016
+ async function closeTricklePublish(record) {
1017
+ if (!record.trickleCreated) {
1018
+ return;
1019
+ }
1020
+ await insecureFetch(record.publishUrl, {
1021
+ method: "DELETE",
1022
+ timeoutMs: 5e3
1023
+ }).catch(() => void 0);
1024
+ record.trickleCreated = false;
1025
+ }
1026
+
1027
+ // src/gateway/server/handlers.ts
1028
+ function signerHeadersFromBearer(token) {
1029
+ return { Authorization: `Bearer ${token}` };
1030
+ }
1031
+ function startPaymentLoop(config, record) {
1032
+ if (!record || record.paymentInterval) {
1033
+ return;
1034
+ }
1035
+ record.paymentInterval = setInterval(() => {
1036
+ void record.paymentSession.sendPayment(record.orchestratorUrl).catch(() => void 0);
1037
+ }, config.paymentIntervalMs);
1038
+ }
1039
+ async function parseStartGatewaySessionRequest(request, config) {
1040
+ try {
1041
+ const parsed = await request.json();
1042
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1043
+ return Response.json({ error: "invalid_request" }, { status: 400 });
1044
+ }
1045
+ const record = parsed;
1046
+ const modelId = typeof record.modelId === "string" ? record.modelId.trim() : "";
1047
+ if (!modelId) {
1048
+ return Response.json({ error: "modelId is required" }, { status: 400 });
1049
+ }
1050
+ return {
1051
+ modelId,
1052
+ orchestratorUrl: typeof record.orchestratorUrl === "string" ? record.orchestratorUrl : void 0,
1053
+ discoveryUrl: typeof record.discoveryUrl === "string" ? record.discoveryUrl : config.discoveryUrl,
1054
+ params: record.params && typeof record.params === "object" && !Array.isArray(record.params) ? record.params : void 0,
1055
+ streamId: typeof record.streamId === "string" ? record.streamId : void 0,
1056
+ requestId: typeof record.requestId === "string" ? record.requestId : void 0
1057
+ };
1058
+ } catch {
1059
+ return Response.json({ error: "invalid_json" }, { status: 400 });
1060
+ }
1061
+ }
1062
+ async function startGatewaySessionRecord(config, token, body) {
1063
+ try {
1064
+ const job = await startLv2vSession({
1065
+ request: body,
1066
+ signerUrl: config.signerUrl,
1067
+ signerHeaders: signerHeadersFromBearer(token),
1068
+ discoveryUrl: config.discoveryUrl,
1069
+ discoveryTimeoutMs: config.discoveryTimeoutMs,
1070
+ useTofu: config.useTofu
1071
+ });
1072
+ const sessionId = createSessionId();
1073
+ const ownerTokenHash = hashBearerToken(token);
1074
+ const sessionRecord = {
1075
+ id: sessionId,
1076
+ ownerTokenHash,
1077
+ manifestId: job.manifestId,
1078
+ publishUrl: job.publishUrl,
1079
+ subscribeUrl: job.subscribeUrl,
1080
+ controlUrl: job.controlUrl,
1081
+ mimeType: job.mimeType,
1082
+ paymentSession: job.paymentSession,
1083
+ orchestratorUrl: job.orchestratorUrl,
1084
+ publishSeq: -1,
1085
+ subscribeSeq: TRICKLE_SEQ_CURRENT,
1086
+ trickleCreated: false,
1087
+ trickleResetSent: false,
1088
+ closed: false
1089
+ };
1090
+ putSession(sessionRecord);
1091
+ startPaymentLoop(config, sessionRecord);
1092
+ let publishSeq = 0;
1093
+ try {
1094
+ await prepareTricklePublish(sessionRecord);
1095
+ publishSeq = Math.max(0, sessionRecord.publishSeq);
1096
+ } catch (prepareErr) {
1097
+ const message = prepareErr instanceof Error ? prepareErr.message : String(prepareErr);
1098
+ return Response.json({ error: "trickle_prepare_failed", message }, { status: 502 });
1099
+ }
1100
+ return Response.json({
1101
+ sessionId,
1102
+ manifestId: job.manifestId,
1103
+ mimeType: job.mimeType,
1104
+ publishSeq,
1105
+ subscribeSeq: sessionRecord.subscribeSeq
1106
+ });
1107
+ } catch (err) {
1108
+ const message = err instanceof Error ? err.message : String(err);
1109
+ return Response.json({ error: "start_session_failed", message }, { status: 502 });
1110
+ }
1111
+ }
1112
+ function createGatewayStartSessionHandler(config) {
1113
+ return async (request) => {
1114
+ if (!config) {
1115
+ return disabledResponse();
1116
+ }
1117
+ const token = extractBearerToken(request);
1118
+ if (!token) {
1119
+ return unauthorizedResponse();
1120
+ }
1121
+ const body = await parseStartGatewaySessionRequest(request, config);
1122
+ if (body instanceof Response) {
1123
+ return body;
1124
+ }
1125
+ return startGatewaySessionRecord(config, token, body);
1126
+ };
1127
+ }
1128
+ function createGatewayPublishSegmentHandler(config) {
1129
+ return async (request, context) => {
1130
+ if (!config) {
1131
+ return disabledResponse();
1132
+ }
1133
+ const token = extractBearerToken(request);
1134
+ if (!token) {
1135
+ return unauthorizedResponse();
1136
+ }
1137
+ const { id, seq: seqRaw } = await context.params;
1138
+ const seq = Number.parseInt(seqRaw, 10);
1139
+ if (!Number.isFinite(seq)) {
1140
+ return Response.json({ error: "invalid_seq" }, { status: 400 });
1141
+ }
1142
+ const record = getSession(id);
1143
+ if (!record || record.closed) {
1144
+ return Response.json({ error: "session_not_found" }, { status: 404 });
1145
+ }
1146
+ try {
1147
+ assertSessionOwner(record, hashBearerToken(token));
1148
+ } catch {
1149
+ return forbiddenResponse();
1150
+ }
1151
+ const bytes = Buffer.from(await request.arrayBuffer());
1152
+ const contentType = request.headers.get("Content-Type")?.trim() || record.mimeType;
1153
+ try {
1154
+ await publishTrickleSegment(record, seq, bytes, contentType);
1155
+ record.publishSeq = Math.max(record.publishSeq, seq);
1156
+ return Response.json({ seq, ok: true });
1157
+ } catch (err) {
1158
+ const message = err instanceof Error ? err.message : String(err);
1159
+ return Response.json({ error: "publish_failed", message }, { status: 502 });
1160
+ }
1161
+ };
1162
+ }
1163
+ function createGatewaySubscribeSegmentHandler(config) {
1164
+ return async (request, context) => {
1165
+ if (!config) {
1166
+ return disabledResponse();
1167
+ }
1168
+ const token = extractBearerToken(request);
1169
+ if (!token) {
1170
+ return unauthorizedResponse();
1171
+ }
1172
+ const { id } = await context.params;
1173
+ const record = getSession(id);
1174
+ if (!record || record.closed) {
1175
+ return Response.json({ error: "session_not_found" }, { status: 404 });
1176
+ }
1177
+ try {
1178
+ assertSessionOwner(record, hashBearerToken(token));
1179
+ } catch {
1180
+ return forbiddenResponse();
1181
+ }
1182
+ const url = new URL(request.url);
1183
+ const seqParam = url.searchParams.get("seq");
1184
+ const seq = seqParam === null ? record.subscribeSeq : Number.parseInt(seqParam, 10);
1185
+ try {
1186
+ const segment = await subscribeTrickleSegment(record, seq);
1187
+ if (!segment) {
1188
+ return new Response(null, { status: 204 });
1189
+ }
1190
+ if ("wait" in segment) {
1191
+ return new Response(null, {
1192
+ status: 204,
1193
+ headers: {
1194
+ "X-Gateway-Latest-Seq": String(segment.latestSeq),
1195
+ "X-Gateway-Wait": "1"
1196
+ }
1197
+ });
1198
+ }
1199
+ record.subscribeSeq = segment.nextSeq;
1200
+ return new Response(segment.data, {
1201
+ status: 200,
1202
+ headers: {
1203
+ "Content-Type": segment.contentType,
1204
+ "X-Gateway-Segment-Seq": String(segment.segmentSeq),
1205
+ "X-Gateway-Next-Seq": String(segment.nextSeq),
1206
+ "X-Gateway-Latest-Seq": String(segment.latestSeq)
1207
+ }
1208
+ });
1209
+ } catch (err) {
1210
+ const message = err instanceof Error ? err.message : String(err);
1211
+ return Response.json({ error: "subscribe_failed", message }, { status: 502 });
1212
+ }
1213
+ };
1214
+ }
1215
+ function createGatewayStopSessionHandler(config) {
1216
+ return async (request, context) => {
1217
+ if (!config) {
1218
+ return disabledResponse();
1219
+ }
1220
+ const token = extractBearerToken(request);
1221
+ if (!token) {
1222
+ return unauthorizedResponse();
1223
+ }
1224
+ const { id } = await context.params;
1225
+ const record = getSession(id);
1226
+ if (!record) {
1227
+ return new Response(null, { status: 204 });
1228
+ }
1229
+ try {
1230
+ assertSessionOwner(record, hashBearerToken(token));
1231
+ } catch {
1232
+ return forbiddenResponse();
1233
+ }
1234
+ closeSessionRecord(record);
1235
+ await closeTricklePublish(record).catch(() => void 0);
1236
+ deleteSession(id);
1237
+ return new Response(null, { status: 204 });
1238
+ };
1239
+ }
1240
+
1241
+ exports.createGatewayPublishSegmentHandler = createGatewayPublishSegmentHandler;
1242
+ exports.createGatewayStartSessionHandler = createGatewayStartSessionHandler;
1243
+ exports.createGatewayStopSessionHandler = createGatewayStopSessionHandler;
1244
+ exports.createGatewaySubscribeSegmentHandler = createGatewaySubscribeSegmentHandler;
1245
+ exports.hashBearerToken = hashBearerToken;
1246
+ exports.readGatewayConfigFromEnv = readGatewayConfigFromEnv;
1247
+ //# sourceMappingURL=index.cjs.map
1248
+ //# sourceMappingURL=index.cjs.map