@eyepop.ai/eyepop 3.15.1 → 3.17.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.
@@ -8632,6 +8632,204 @@ var require_mime_types = __commonJS({
8632
8632
  }
8633
8633
  });
8634
8634
 
8635
+ // compute/compute_session.ts
8636
+ var SESSION_DEAD = /* @__PURE__ */ new Set(["error" /* ERROR */, "failed" /* FAILED */, "stopped" /* STOPPED */, "pipeline_error" /* PIPELINE_ERROR */, "unknown" /* UNKNOWN */]);
8637
+ var CHECK_FOR_IS_READY_INTERVAL = 250;
8638
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
8639
+ function pipelineIdFromSession(session) {
8640
+ if (session.pipeline_uuid) {
8641
+ return session.pipeline_uuid;
8642
+ }
8643
+ const pipeline = session.pipelines?.[0];
8644
+ return pipeline?.id || pipeline?.pipeline_id || null;
8645
+ }
8646
+ function sessionCreateBody(options) {
8647
+ const body = {};
8648
+ if (options.sessionName) {
8649
+ body.session_name = options.sessionName;
8650
+ }
8651
+ if (options.pipelineImage) {
8652
+ body.pipeline_image = options.pipelineImage;
8653
+ }
8654
+ if (options.pipelineVersion) {
8655
+ body.pipeline_version = options.pipelineVersion;
8656
+ }
8657
+ if (options.pop) {
8658
+ body.pop = options.pop;
8659
+ }
8660
+ return Object.keys(body).length > 0 ? JSON.stringify(body) : void 0;
8661
+ }
8662
+ function sessionHealthReady(body, contentType) {
8663
+ if (contentType.includes("application/json")) {
8664
+ const health = JSON.parse(body);
8665
+ if (health && typeof health == "object") {
8666
+ if (health.message && !health.session_status) {
8667
+ return true;
8668
+ }
8669
+ return health.session_status == "running" /* RUNNING */;
8670
+ }
8671
+ }
8672
+ return contentType.includes("text/plain") && body.includes("I'm fine");
8673
+ }
8674
+ function accessTokenValidUntil(session) {
8675
+ if (session.access_token_expires_at) {
8676
+ const expiresAt = Date.parse(session.access_token_expires_at);
8677
+ return Number.isNaN(expiresAt) ? null : expiresAt;
8678
+ }
8679
+ if (session.access_token_expires_in) {
8680
+ return Date.now() + session.access_token_expires_in * 1e3;
8681
+ }
8682
+ return null;
8683
+ }
8684
+ var ComputeSessionClient = class {
8685
+ constructor(options) {
8686
+ this.options = options;
8687
+ }
8688
+ async resolve() {
8689
+ const session = this.options.sessionUuid ? await this.getPermanentComputeSession(this.options.sessionUuid) : await this.getOnDemandComputeSession();
8690
+ const accessToken = this.sessionAccessToken(session);
8691
+ await this.waitUntilReady(session, accessToken);
8692
+ const pipelineId = pipelineIdFromSession(session);
8693
+ if (this.options.pop && !pipelineId) {
8694
+ throw new Error("Compute session did not provide a pipeline for the requested pop");
8695
+ }
8696
+ return {
8697
+ session,
8698
+ pipelineId,
8699
+ accessToken,
8700
+ accessTokenValidUntil: accessTokenValidUntil(session)
8701
+ };
8702
+ }
8703
+ async getOnDemandComputeSession() {
8704
+ let session = null;
8705
+ const sessionsUrl = `${this.options.computeUrl}/v1/sessions`;
8706
+ const headers = {
8707
+ Authorization: await this.options.authorizationHeader()
8708
+ };
8709
+ let sessions;
8710
+ if (!this.options.pop) {
8711
+ this.options.requestLogger?.debug("fetching sessions from: %s", sessionsUrl);
8712
+ const response = await this.options.httpClient.fetch(sessionsUrl, {
8713
+ headers
8714
+ });
8715
+ if (response.status == 404) {
8716
+ sessions = [];
8717
+ } else if (response.status != 200) {
8718
+ const message = await response.text();
8719
+ throw new Error(`Unexpected status ${response.status} fetching compute sessions: ${message}`);
8720
+ } else {
8721
+ const response_content = await response.json();
8722
+ sessions = response_content;
8723
+ }
8724
+ if (sessions.length > 0) {
8725
+ this.options.logger?.debug(`User has ${sessions.length} sessions, inspecting for usable session`);
8726
+ for (let s of sessions) {
8727
+ if (!SESSION_DEAD.has(s.session_status) && !s.persistent && (!this.options.sessionName || s.session_name === this.options.sessionName)) {
8728
+ session = s;
8729
+ this.options.logger?.debug(`Use active session ${s.session_uuid} with pipeline ${pipelineIdFromSession(s)}`);
8730
+ break;
8731
+ }
8732
+ }
8733
+ }
8734
+ }
8735
+ if (!session) {
8736
+ this.options.requestLogger?.debug("creating a new session at: %s", sessionsUrl);
8737
+ const createBody = sessionCreateBody(this.options);
8738
+ const createHeaders = {
8739
+ ...headers,
8740
+ ...createBody ? { "Content-Type": "application/json" } : {}
8741
+ };
8742
+ const request = {
8743
+ headers: createHeaders,
8744
+ method: "POST"
8745
+ };
8746
+ if (createBody) {
8747
+ request.body = createBody;
8748
+ }
8749
+ const query = "wait=true";
8750
+ const response = await this.options.httpClient.fetch(`${sessionsUrl}?${query}`, request);
8751
+ if (response.status != 200) {
8752
+ const message = await response.text();
8753
+ throw new Error(`Unexpected status ${response.status} creating a compute session: ${message}`);
8754
+ } else {
8755
+ sessions = await response.json();
8756
+ }
8757
+ if (sessions.length == 0) {
8758
+ throw new Error("Unexpected, no compute session after attempt to create one");
8759
+ }
8760
+ session = sessions[0] || null;
8761
+ }
8762
+ if (!session) {
8763
+ throw new Error("unexpected undefined session");
8764
+ }
8765
+ if (this.options.pop && !pipelineIdFromSession(session)) {
8766
+ throw new Error("Compute session did not provide a pipeline for the requested pop");
8767
+ }
8768
+ return session;
8769
+ }
8770
+ async getPermanentComputeSession(sessionUuid) {
8771
+ let session = null;
8772
+ const sessionUrl = `${this.options.computeUrl}/v1/sessions/${sessionUuid}`;
8773
+ this.options.requestLogger?.debug("fetching session from: %s", sessionUrl);
8774
+ const headers = {
8775
+ Authorization: await this.options.authorizationHeader()
8776
+ };
8777
+ let response = await this.options.httpClient.fetch(sessionUrl, {
8778
+ headers
8779
+ });
8780
+ if (response.status == 404) {
8781
+ throw new Error(`Unexpected status ${response.status} compute sessions ${sessionUuid} not found`);
8782
+ } else if (response.status != 200) {
8783
+ const message = await response.text();
8784
+ throw new Error(`Unexpected status ${response.status} fetching compute sessions: ${message}`);
8785
+ } else {
8786
+ const response_content = await response.json();
8787
+ session = response_content;
8788
+ }
8789
+ return session;
8790
+ }
8791
+ sessionAccessToken(session) {
8792
+ if (!session.access_token) {
8793
+ throw new Error(`Compute session ${session.session_uuid} did not provide an access token`);
8794
+ }
8795
+ return session.access_token;
8796
+ }
8797
+ async waitUntilReady(session, accessToken) {
8798
+ let isReady = false;
8799
+ const deadline = Date.now() + this.options.readyTimeoutMs;
8800
+ while (!isReady) {
8801
+ const health_url = `${session.session_endpoint}/health`;
8802
+ const response = await this.options.httpClient.fetch(health_url, {
8803
+ headers: {
8804
+ Authorization: `Bearer ${accessToken}`,
8805
+ Accept: "application/json"
8806
+ }
8807
+ });
8808
+ if (response.status == 200) {
8809
+ const contentType = response.headers.get("content-type") || "";
8810
+ const body = await response.text();
8811
+ try {
8812
+ isReady = sessionHealthReady(body, contentType);
8813
+ } catch (error) {
8814
+ this.options.logger?.debug(`session ${session.session_uuid} health check returned invalid JSON, wait and poll for update`);
8815
+ }
8816
+ if (!isReady && Date.now() > deadline) {
8817
+ throw new Error(`Created session ${session.session_uuid} did not become healthy after ${this.options.readyTimeoutMs / 1e3} seconds`);
8818
+ }
8819
+ if (!isReady) {
8820
+ this.options.logger?.debug(`session ${session.session_uuid} health check not ready, wait and poll for update`);
8821
+ await sleep(CHECK_FOR_IS_READY_INTERVAL);
8822
+ }
8823
+ } else if (Date.now() > deadline) {
8824
+ throw new Error(`Created session ${session.session_uuid} did not become healthy after ${this.options.readyTimeoutMs / 1e3} seconds`);
8825
+ } else {
8826
+ this.options.logger?.debug(`session ${session.session_uuid} health check status ${response.status}, wait and poll for update`);
8827
+ await sleep(CHECK_FOR_IS_READY_INTERVAL);
8828
+ }
8829
+ }
8830
+ }
8831
+ };
8832
+
8635
8833
  // types.ts
8636
8834
  var EndpointState = /* @__PURE__ */ ((EndpointState2) => {
8637
8835
  EndpointState2["Idle"] = "Idle";
@@ -8644,6 +8842,7 @@ var EndpointState = /* @__PURE__ */ ((EndpointState2) => {
8644
8842
  })(EndpointState || {});
8645
8843
 
8646
8844
  // streaming.ts
8845
+ import { v4 as uuidv4 } from "uuid";
8647
8846
  var Stream = class _Stream {
8648
8847
  constructor(iterator, controller) {
8649
8848
  this.iterator = iterator;
@@ -8824,6 +9023,76 @@ function readableStreamFromString(s) {
8824
9023
  }
8825
9024
  });
8826
9025
  }
9026
+ function randomMultipartBoundary() {
9027
+ return `----formdata-boundary-${uuidv4()}`;
9028
+ }
9029
+ function toPartStream(content) {
9030
+ if (content instanceof ReadableStream) {
9031
+ return content;
9032
+ }
9033
+ if (content instanceof Blob) {
9034
+ return content.stream();
9035
+ }
9036
+ const bytes = ArrayBuffer.isView(content) ? new Uint8Array(content.buffer, content.byteOffset, content.byteLength) : new Uint8Array(content);
9037
+ return new ReadableStream({
9038
+ start(controller) {
9039
+ controller.enqueue(bytes);
9040
+ controller.close();
9041
+ }
9042
+ });
9043
+ }
9044
+ async function cancelMultipartContent(content, reason) {
9045
+ if (content instanceof ReadableStream) {
9046
+ await content.cancel(reason);
9047
+ }
9048
+ }
9049
+ function encodeMultipartFormData(parts, boundary) {
9050
+ const encoder = new TextEncoder();
9051
+ let partIndex = 0;
9052
+ let reader = null;
9053
+ return new ReadableStream({
9054
+ async pull(controller) {
9055
+ if (reader) {
9056
+ const { done, value } = await reader.read();
9057
+ if (!done) {
9058
+ controller.enqueue(value);
9059
+ return;
9060
+ }
9061
+ reader = null;
9062
+ controller.enqueue(encoder.encode("\r\n"));
9063
+ partIndex++;
9064
+ }
9065
+ if (partIndex >= parts.length) {
9066
+ controller.enqueue(encoder.encode(`--${boundary}--\r
9067
+ `));
9068
+ controller.close();
9069
+ return;
9070
+ }
9071
+ const part = parts[partIndex];
9072
+ let header = `--${boundary}\r
9073
+ Content-Disposition: form-data; name="${part.name}"\r
9074
+ `;
9075
+ if (part.contentType) {
9076
+ header += `Content-Type: ${part.contentType}\r
9077
+ `;
9078
+ }
9079
+ header += "\r\n";
9080
+ controller.enqueue(encoder.encode(header));
9081
+ if (typeof part.content === "string") {
9082
+ controller.enqueue(encoder.encode(part.content));
9083
+ controller.enqueue(encoder.encode("\r\n"));
9084
+ partIndex++;
9085
+ } else {
9086
+ reader = toPartStream(part.content).getReader();
9087
+ }
9088
+ },
9089
+ async cancel(reason) {
9090
+ await reader?.cancel(reason);
9091
+ const firstUnstartedPart = reader ? partIndex + 1 : partIndex;
9092
+ await Promise.all(parts.slice(firstUnstartedPart).map((part) => cancelMultipartContent(part.content, reason)));
9093
+ }
9094
+ });
9095
+ }
8827
9096
 
8828
9097
  // worker/worker_types.ts
8829
9098
  var PredictionVersion = /* @__PURE__ */ ((PredictionVersion3) => {
@@ -8881,7 +9150,7 @@ var ContourType = /* @__PURE__ */ ((ContourType2) => {
8881
9150
  })(ContourType || {});
8882
9151
 
8883
9152
  // worker/webrtc_whip.ts
8884
- import { v4 as uuidv4 } from "uuid";
9153
+ import { v4 as uuidv42 } from "uuid";
8885
9154
 
8886
9155
  // worker/webrtc_base.ts
8887
9156
  import path from "path";
@@ -9156,7 +9425,7 @@ var WebrtcBase = class _WebrtcBase {
9156
9425
  // worker/webrtc_whip.ts
9157
9426
  var WebrtcWhip = class extends WebrtcBase {
9158
9427
  constructor(stream, getSession, client, is_pipeline_direct, requestLogger) {
9159
- super(getSession, client, uuidv4().toString(), "liveIngress/whip", is_pipeline_direct, requestLogger);
9428
+ super(getSession, client, uuidv42().toString(), "liveIngress/whip", is_pipeline_direct, requestLogger);
9160
9429
  this._stream = stream;
9161
9430
  }
9162
9431
  async start() {
@@ -9551,6 +9820,111 @@ var LoadFromJob = class extends AbstractJob {
9551
9820
  });
9552
9821
  }
9553
9822
  };
9823
+ var UploadGroupJob = class extends AbstractJob {
9824
+ get [Symbol.toStringTag]() {
9825
+ return "uploadGroupJob";
9826
+ }
9827
+ constructor(sources, params, getSession, client, requestLogger) {
9828
+ super(params, getSession, client, requestLogger);
9829
+ if (sources.length === 0) {
9830
+ throw new Error("upload group requires at least one source");
9831
+ }
9832
+ this._sources = sources;
9833
+ }
9834
+ async startJob() {
9835
+ const session = await this._getSession();
9836
+ if (!session.baseUrl) {
9837
+ return Promise.reject(new Error("session.baseUrl must not be null"));
9838
+ }
9839
+ const queryParams = new URLSearchParams({
9840
+ mode: "queue",
9841
+ processing: "sync"
9842
+ });
9843
+ if (this._version !== void 0) {
9844
+ queryParams.set("version", this._version.toString());
9845
+ }
9846
+ if (this._params.motionDetect) {
9847
+ for (const [key, value] of Object.entries(this._params.motionDetect)) {
9848
+ queryParams.set(key, value);
9849
+ }
9850
+ }
9851
+ const postUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?${queryParams.toString()}`;
9852
+ const parts = [];
9853
+ if (this._params.componentParams) {
9854
+ parts.push({ name: "params", contentType: "application/json", content: JSON.stringify(this._params.componentParams) });
9855
+ }
9856
+ if (this._params.roi) {
9857
+ parts.push({ name: "roi", contentType: "application/json", content: JSON.stringify(this._params.roi) });
9858
+ }
9859
+ if (this._params.fps) {
9860
+ parts.push({ name: "fps", contentType: "application/json", content: JSON.stringify(this._params.fps) });
9861
+ }
9862
+ for (const source of this._sources) {
9863
+ parts.push({ name: "file", contentType: source.mimeType, content: source.stream });
9864
+ }
9865
+ const boundary = randomMultipartBoundary();
9866
+ const headers = {
9867
+ ...session.authenticationHeaders(),
9868
+ Accept: "application/jsonl",
9869
+ "Content-Type": `multipart/form-data; boundary=${boundary}`
9870
+ };
9871
+ return await this._client.fetch(postUrl, {
9872
+ headers,
9873
+ method: "POST",
9874
+ body: encodeMultipartFormData(parts, boundary),
9875
+ signal: this._controller.signal,
9876
+ // @ts-ignore
9877
+ duplex: "half",
9878
+ eyepop: { responseStreaming: true }
9879
+ });
9880
+ }
9881
+ };
9882
+ var LoadFromGroupJob = class extends AbstractJob {
9883
+ get [Symbol.toStringTag]() {
9884
+ return "loadFromGroupJob";
9885
+ }
9886
+ constructor(urls, params, getSession, client, requestLogger) {
9887
+ super(params, getSession, client, requestLogger);
9888
+ if (urls.length === 0) {
9889
+ throw new Error("load from group requires at least one url");
9890
+ }
9891
+ this._urls = urls;
9892
+ }
9893
+ async startJob() {
9894
+ const session = await this._getSession();
9895
+ if (!session.baseUrl) {
9896
+ return Promise.reject(new Error("session.baseUrl must not be null"));
9897
+ }
9898
+ const body = {
9899
+ sourceType: "GROUP",
9900
+ sources: this._urls.map((url) => ({ sourceType: "URL", url })),
9901
+ version: this._version
9902
+ };
9903
+ if (this._params.componentParams) {
9904
+ body["params"] = this._params.componentParams;
9905
+ }
9906
+ if (this._params.roi) {
9907
+ body["roi"] = this._params.roi;
9908
+ }
9909
+ if (this._params.motionDetect) {
9910
+ Object.assign(body, this._params.motionDetect);
9911
+ }
9912
+ const headers = {
9913
+ ...session.authenticationHeaders(),
9914
+ Accept: "application/jsonl",
9915
+ "Content-Type": "application/json"
9916
+ };
9917
+ const patchUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=queue&processing=sync`;
9918
+ return await this._client.fetch(patchUrl, {
9919
+ headers,
9920
+ method: "PATCH",
9921
+ body: JSON.stringify(body),
9922
+ signal: this._controller.signal,
9923
+ // @ts-ignore
9924
+ eyepop: { responseStreaming: true }
9925
+ });
9926
+ }
9927
+ };
9554
9928
  var LoadFromAssetUuidJob = class extends AbstractJob {
9555
9929
  constructor(assetUuid, params, getSession, client, requestLogger) {
9556
9930
  super(params, getSession, client, requestLogger);
@@ -9717,8 +10091,9 @@ if ("document" in globalThis && "implementation" in globalThis.document) {
9717
10091
  };
9718
10092
  } else {
9719
10093
  class NodeHttpClient {
9720
- constructor(agent, logger) {
10094
+ constructor(agent, fetchFn, logger) {
9721
10095
  this._agent = agent;
10096
+ this._fetch = fetchFn;
9722
10097
  this._logger = logger;
9723
10098
  }
9724
10099
  async fetch(input, init) {
@@ -9726,7 +10101,7 @@ if ("document" in globalThis && "implementation" in globalThis.document) {
9726
10101
  init.dispatcher = this._agent;
9727
10102
  const logLine = logLineFromRequest(input, init);
9728
10103
  this._logger.debug(`Node fetch() BEFORE ${logLine}`);
9729
- const response = await fetch(input, init);
10104
+ const response = await this._fetch(input, init);
9730
10105
  this._logger.debug(`AFTER ${logLine} -> ${response.status}`);
9731
10106
  return response;
9732
10107
  }
@@ -9746,7 +10121,7 @@ if ("document" in globalThis && "implementation" in globalThis.document) {
9746
10121
  pipelining: 0
9747
10122
  });
9748
10123
  logger.debug("Using node fetch with undici agent as http client");
9749
- return new NodeHttpClient(agent, logger);
10124
+ return new NodeHttpClient(agent, undici.fetch, logger);
9750
10125
  };
9751
10126
  }
9752
10127
 
@@ -10151,20 +10526,6 @@ var TransientPopId = /* @__PURE__ */ ((TransientPopId2) => {
10151
10526
  })(TransientPopId || {});
10152
10527
 
10153
10528
  // worker/worker_endpoint.ts
10154
- var SessionStatus = /* @__PURE__ */ ((SessionStatus2) => {
10155
- SessionStatus2["UNKNOWN"] = "unknown";
10156
- SessionStatus2["PENDING"] = "pending";
10157
- SessionStatus2["RUNNING"] = "running";
10158
- SessionStatus2["STOPPED"] = "stopped";
10159
- SessionStatus2["FAILED"] = "failed";
10160
- SessionStatus2["ERROR"] = "error";
10161
- SessionStatus2["PIPELINE_CREATING"] = "pipeline_creating";
10162
- SessionStatus2["PIPELINE_OK"] = "pipeline_ok";
10163
- SessionStatus2["PIPELINE_CHECKING"] = "pipeline_checking";
10164
- SessionStatus2["PIPELINE_ERROR"] = "pipeline_error";
10165
- return SessionStatus2;
10166
- })(SessionStatus || {});
10167
- var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
10168
10529
  var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10169
10530
  constructor(options) {
10170
10531
  super(options);
@@ -10172,7 +10533,10 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10172
10533
  this._baseUrl = null;
10173
10534
  this._pipelineId = null;
10174
10535
  this._popName = null;
10536
+ this._sessionAccessToken = null;
10537
+ this._sessionAccessTokenValidUntil = null;
10175
10538
  this._pipeline = null;
10539
+ this._pipelineCreatePromise = null;
10176
10540
  }
10177
10541
  options() {
10178
10542
  return this._options;
@@ -10199,20 +10563,29 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10199
10563
  this._baseUrl = null;
10200
10564
  }
10201
10565
  async session() {
10202
- const session = await super.session();
10203
- if (this._baseUrl == null || this._pipelineId == null) {
10566
+ let session = null;
10567
+ if (this._baseUrl == null) {
10204
10568
  await this.reconnect();
10205
- if (this._baseUrl == null || this._pipelineId == null) {
10569
+ if (this._baseUrl == null) {
10206
10570
  return Promise.reject("endpoint not connected");
10207
10571
  }
10208
10572
  }
10573
+ if (this._sessionAccessToken) {
10574
+ session = {
10575
+ eyepopUrl: this.options().eyepopUrl,
10576
+ accessToken: this._sessionAccessToken,
10577
+ validUntil: this._sessionAccessTokenValidUntil || Number.MAX_VALUE
10578
+ };
10579
+ } else {
10580
+ session = await super.session();
10581
+ }
10209
10582
  return {
10210
10583
  eyepopUrl: session.eyepopUrl,
10211
10584
  accessToken: session.accessToken,
10212
10585
  validUntil: session.validUntil,
10213
10586
  popId: this.options().popId,
10214
10587
  baseUrl: this._baseUrl,
10215
- pipelineId: this._pipelineId,
10588
+ pipelineId: this._pipelineId || void 0,
10216
10589
  authenticationHeaders: () => this._authenticationHeaders(session)
10217
10590
  };
10218
10591
  }
@@ -10222,11 +10595,14 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10222
10595
  pop() {
10223
10596
  if (this._pipeline) {
10224
10597
  return this._pipeline.pop || null;
10225
- } else {
10226
- return null;
10227
10598
  }
10599
+ return this.options().pop || null;
10228
10600
  }
10229
10601
  async changePop(pop) {
10602
+ if (this.options().popId == "transient" /* Transient */) {
10603
+ await this.changeTransientPop(pop);
10604
+ return;
10605
+ }
10230
10606
  const client = this._client;
10231
10607
  const baseUrl = this._baseUrl;
10232
10608
  if (!baseUrl || !client) {
@@ -10249,6 +10625,7 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10249
10625
  const message = await response.text();
10250
10626
  throw new Error(`Unexpected status ${response.status}: ${message}`);
10251
10627
  }
10628
+ this.options().pop = pop;
10252
10629
  if (this._pipeline) {
10253
10630
  this._pipeline.pop = pop;
10254
10631
  } else {
@@ -10263,6 +10640,10 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10263
10640
  _authenticationHeaders(session) {
10264
10641
  return this._options.auth.isLocal !== void 0 ? {} : { Authorization: `Bearer ${session.accessToken}` };
10265
10642
  }
10643
+ /**
10644
+ * @deprecated Pop ids are only retained for backwards compatibility.
10645
+ * Configure transient sessions with `WorkerOptions.pop`.
10646
+ */
10266
10647
  popId() {
10267
10648
  return this.options().popId || null;
10268
10649
  }
@@ -10307,7 +10688,116 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10307
10688
  return Promise.reject("unknown source type");
10308
10689
  }
10309
10690
  }
10691
+ async uploadGroup(paths, params = {}) {
10692
+ if (paths.length === 0) {
10693
+ throw new Error("upload group requires at least one path");
10694
+ }
10695
+ if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10696
+ throw new Error("endpoint not connected, use connect()");
10697
+ }
10698
+ await this._limit.acquire();
10699
+ try {
10700
+ this.updateState();
10701
+ const sources = await Promise.all(
10702
+ paths.map(async (path2) => {
10703
+ let streamSource;
10704
+ if (this._options.platformSupport?.resolvePath) {
10705
+ streamSource = await this._options.platformSupport.resolvePath({ path: path2 }, this._logger);
10706
+ } else {
10707
+ streamSource = await resolvePath({ path: path2 }, this._logger);
10708
+ }
10709
+ return { stream: streamSource.stream, mimeType: streamSource.mimeType };
10710
+ })
10711
+ );
10712
+ const job = new UploadGroupJob(
10713
+ sources,
10714
+ params,
10715
+ async () => this.session(),
10716
+ this._client,
10717
+ this._requestLogger
10718
+ );
10719
+ return job.start(
10720
+ () => {
10721
+ this.jobDone(job);
10722
+ },
10723
+ (statusCode) => {
10724
+ this.jobStatus(job, statusCode);
10725
+ }
10726
+ );
10727
+ } catch (e) {
10728
+ this._limit.release();
10729
+ throw e;
10730
+ }
10731
+ }
10732
+ async uploadStreamGroup(streams, mimeTypes, params = {}) {
10733
+ if (streams.length === 0) {
10734
+ throw new Error("upload stream group requires at least one stream");
10735
+ }
10736
+ if (mimeTypes !== void 0 && mimeTypes.length !== streams.length) {
10737
+ throw new Error("mimeTypes must have the same length as streams");
10738
+ }
10739
+ if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10740
+ throw new Error("endpoint not connected, use connect()");
10741
+ }
10742
+ await this._limit.acquire();
10743
+ try {
10744
+ this.updateState();
10745
+ const sources = streams.map((stream, i) => ({
10746
+ stream,
10747
+ mimeType: mimeTypes?.[i] ?? null
10748
+ }));
10749
+ const job = new UploadGroupJob(
10750
+ sources,
10751
+ params,
10752
+ async () => this.session(),
10753
+ this._client,
10754
+ this._requestLogger
10755
+ );
10756
+ return job.start(
10757
+ () => {
10758
+ this.jobDone(job);
10759
+ },
10760
+ (statusCode) => {
10761
+ this.jobStatus(job, statusCode);
10762
+ }
10763
+ );
10764
+ } catch (e) {
10765
+ this._limit.release();
10766
+ throw e;
10767
+ }
10768
+ }
10769
+ async loadFromGroup(urls, params = {}) {
10770
+ if (urls.length === 0) {
10771
+ throw new Error("load from group requires at least one url");
10772
+ }
10773
+ if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10774
+ throw new Error("endpoint not connected, use connect()");
10775
+ }
10776
+ await this._limit.acquire();
10777
+ try {
10778
+ this.updateState();
10779
+ const job = new LoadFromGroupJob(
10780
+ urls,
10781
+ params,
10782
+ async () => this.session(),
10783
+ this._client,
10784
+ this._requestLogger
10785
+ );
10786
+ return job.start(
10787
+ () => {
10788
+ this.jobDone(job);
10789
+ },
10790
+ (statusCode) => {
10791
+ this.jobStatus(job, statusCode);
10792
+ }
10793
+ );
10794
+ } catch (e) {
10795
+ this._limit.release();
10796
+ throw e;
10797
+ }
10798
+ }
10310
10799
  async uploadFile(source, params) {
10800
+ await this.ensureTransientPipelineStarted();
10311
10801
  if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10312
10802
  return Promise.reject("endpoint not connected, use connect()");
10313
10803
  }
@@ -10340,6 +10830,7 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10340
10830
  }
10341
10831
  }
10342
10832
  async uploadStream(source, params) {
10833
+ await this.ensureTransientPipelineStarted();
10343
10834
  if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10344
10835
  return Promise.reject("endpoint not connected, use connect()");
10345
10836
  }
@@ -10372,6 +10863,7 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10372
10863
  }
10373
10864
  }
10374
10865
  async uploadPath(source, params) {
10866
+ await this.ensureTransientPipelineStarted();
10375
10867
  if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10376
10868
  throw new Error("endpoint not connected, use connect()");
10377
10869
  }
@@ -10418,6 +10910,7 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10418
10910
  params
10419
10911
  );
10420
10912
  }
10913
+ await this.ensureTransientPipelineStarted();
10421
10914
  if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10422
10915
  throw new Error("endpoint not connected, use connect()");
10423
10916
  }
@@ -10447,6 +10940,7 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10447
10940
  }
10448
10941
  }
10449
10942
  async loadFromAssetUuid(source, params) {
10943
+ await this.ensureTransientPipelineStarted();
10450
10944
  if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10451
10945
  throw new Error("endpoint not connected, use connect()");
10452
10946
  }
@@ -10476,6 +10970,7 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10476
10970
  }
10477
10971
  }
10478
10972
  async loadMediaStream(source, params) {
10973
+ await this.ensureTransientPipelineStarted();
10479
10974
  if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10480
10975
  throw new Error("endpoint not connected, use connect()");
10481
10976
  }
@@ -10518,7 +11013,9 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10518
11013
  async reconnect() {
10519
11014
  if (this.options().popId == "transient" /* Transient */) {
10520
11015
  await this.getComputeSession();
10521
- this._pipelineId = await this.startTransientPipeline();
11016
+ if (this.options().pop) {
11017
+ this.setTransientPipelinePop();
11018
+ }
10522
11019
  } else if (this.options().sessionUuid) {
10523
11020
  await this.getComputeSession();
10524
11021
  } else {
@@ -10615,120 +11112,41 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10615
11112
  }
10616
11113
  }
10617
11114
  }
11115
+ sessionReadyTimeoutMs() {
11116
+ const seconds = this.options().sessionReadyTimeoutSeconds;
11117
+ return typeof seconds == "number" && Number.isFinite(seconds) && seconds > 0 ? seconds * 1e3 : _WorkerEndpoint.WAIT_FOR_IS_READY;
11118
+ }
10618
11119
  async getComputeSession() {
10619
11120
  if (!this._client) {
10620
11121
  throw new Error("endpoint not initialized");
10621
11122
  }
10622
- let session;
10623
- let headers;
10624
- const sessionUuid = this.options().sessionUuid;
10625
- if (sessionUuid) {
10626
- [session, headers] = await this.getPermanentComputeSession(this._client, sessionUuid);
10627
- } else {
10628
- [session, headers] = await this.getOnDemandComputeSession(this._client);
10629
- }
10630
- let isReady = false;
10631
- const deadline = Date.now() + _WorkerEndpoint.WAIT_FOR_IS_READY;
10632
- while (!isReady) {
10633
- const health_url = `${session.session_endpoint}/health`;
10634
- const response = await this._client.fetch(health_url, {
10635
- headers
10636
- });
10637
- if (response.status < 300) {
10638
- isReady = true;
10639
- } else if (Date.now() > deadline) {
10640
- throw new Error(`Created session ${session.session_uuid} did not become healthy after ${_WorkerEndpoint.WAIT_FOR_IS_READY / 1e3} seconds`);
10641
- } else {
10642
- this._logger.debug(`session ${session.session_uuid} health check status ${response.status}, wait and poll for update`);
10643
- await sleep(_WorkerEndpoint.CHECK_FOR_IS_READY_INTERVAL);
10644
- }
10645
- }
10646
- this._baseUrl = session.session_endpoint;
10647
- this._pipeline = null;
10648
- if (session.pipeline_uuid) {
10649
- this._pipelineId = session.pipeline_uuid;
10650
- } else if (session.pipelines) {
10651
- this._pipelineId = session.pipelines[0].pipeline_id;
10652
- } else {
10653
- this._pipelineId = null;
10654
- }
10655
- }
10656
- async getOnDemandComputeSession(httpClient) {
10657
- let session = null;
10658
- const sessionsUrl = `${this.eyepopUrl()}/v1/sessions`;
10659
- this._requestLogger.debug("fetching sessions from: %s", sessionsUrl);
10660
- const headers = {
10661
- Authorization: await this.authorizationHeader()
10662
- };
10663
- let response = await httpClient.fetch(sessionsUrl, {
10664
- headers
10665
- });
10666
- let sessions;
10667
- if (response.status == 404) {
10668
- sessions = [];
10669
- } else if (response.status != 200) {
11123
+ let resolved;
11124
+ try {
11125
+ resolved = await new ComputeSessionClient({
11126
+ computeUrl: this.eyepopUrl(),
11127
+ httpClient: this._client,
11128
+ authorizationHeader: () => this.computeAuthorizationHeader(),
11129
+ sessionUuid: this.options().sessionUuid,
11130
+ sessionName: this.options().sessionName,
11131
+ pipelineImage: this.options().pipelineImage,
11132
+ pipelineVersion: this.options().pipelineVersion,
11133
+ pop: this.options().pop,
11134
+ readyTimeoutMs: this.sessionReadyTimeoutMs(),
11135
+ logger: this._logger,
11136
+ requestLogger: this._requestLogger
11137
+ }).resolve();
11138
+ } catch (error) {
10670
11139
  this.updateState("Error" /* Error */);
10671
- const message = await response.text();
10672
- throw new Error(`Unexpected status ${response.status} fetching compute sessions: ${message}`);
10673
- } else {
10674
- const response_content = await response.json();
10675
- sessions = response_content;
10676
- }
10677
- if (sessions.length > 0) {
10678
- this._logger.debug(`User has ${sessions.length} sessions, inspecting for usable session`);
10679
- for (let s of sessions) {
10680
- if (!_WorkerEndpoint.SESSION_DEAD.has(s.session_status) && !s.persistent) {
10681
- session = s;
10682
- this._logger.debug(`Use active session ${s.session_uuid} with pipeline ${s.pipeline_uuid}`);
10683
- break;
10684
- }
10685
- }
11140
+ throw error;
10686
11141
  }
10687
- if (!session) {
10688
- this._requestLogger.debug("creating a new session at: %s", sessionsUrl);
10689
- response = await httpClient.fetch(`${sessionsUrl}?wait=true`, {
10690
- headers,
10691
- method: "POST"
10692
- });
10693
- if (response.status != 200) {
10694
- this.updateState("Error" /* Error */);
10695
- const message = await response.text();
10696
- throw new Error(`Unexpected status ${response.status} creating a compute session: ${message}`);
10697
- } else {
10698
- sessions = await response.json();
10699
- }
10700
- if (sessions.length == 0) {
10701
- throw new Error("Unexpected, no compute session after attempt to create one");
10702
- }
10703
- session = sessions[0] || null;
10704
- }
10705
- if (!session) {
10706
- throw new Error("unexpected undefined session");
10707
- }
10708
- return [session, headers];
10709
- }
10710
- async getPermanentComputeSession(httpClient, sessionUuid) {
10711
- let session = null;
10712
- const sessionUrl = `${this.eyepopUrl()}/v1/sessions/${sessionUuid}`;
10713
- this._requestLogger.debug("fetching session from: %s", sessionUrl);
10714
- const headers = {
10715
- Authorization: await this.authorizationHeader()
10716
- };
10717
- let response = await httpClient.fetch(sessionUrl, {
10718
- headers
10719
- });
10720
- if (response.status == 404) {
10721
- this.updateState("Error" /* Error */);
10722
- throw new Error(`Unexpected status ${response.status} compute sessions ${sessionUuid} not found`);
10723
- } else if (response.status != 200) {
10724
- this.updateState("Error" /* Error */);
10725
- const message = await response.text();
10726
- throw new Error(`Unexpected status ${response.status} fetching compute sessions: ${message}`);
10727
- } else {
10728
- const response_content = await response.json();
10729
- session = response_content;
11142
+ this._sessionAccessToken = resolved.accessToken;
11143
+ this._sessionAccessTokenValidUntil = resolved.accessTokenValidUntil;
11144
+ this._baseUrl = resolved.session.session_endpoint;
11145
+ this._pipeline = null;
11146
+ this._pipelineId = resolved.pipelineId;
11147
+ if (this.options().pop) {
11148
+ this.setTransientPipelinePop();
10730
11149
  }
10731
- return [session, headers];
10732
11150
  }
10733
11151
  async startTransientPipeline() {
10734
11152
  const client = this._client;
@@ -10737,12 +11155,12 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10737
11155
  throw new Error("endpoint not initialized");
10738
11156
  }
10739
11157
  let pop;
10740
- if (this._pipeline && this._pipeline.pop) {
11158
+ if (this.options().pop) {
11159
+ pop = this.options().pop;
11160
+ } else if (this._pipeline && this._pipeline.pop) {
10741
11161
  pop = this._pipeline.pop;
10742
11162
  } else {
10743
- pop = {
10744
- components: []
10745
- };
11163
+ throw new Error("Cannot start a transient pipeline without a pop");
10746
11164
  }
10747
11165
  const body = {
10748
11166
  pop,
@@ -10754,8 +11172,9 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10754
11172
  videoOutput: "no_output"
10755
11173
  };
10756
11174
  const post_url = `${baseUrl.replace(/\/+$/, "")}/pipelines`;
10757
- let response = await this.fetchWithRetry(async () => {
11175
+ const response = await this.fetchWithRetry(async () => {
10758
11176
  let headers = {
11177
+ Accept: "application/json",
10759
11178
  "Content-Type": "application/json",
10760
11179
  Authorization: await this.authorizationHeader()
10761
11180
  };
@@ -10767,15 +11186,116 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10767
11186
  });
10768
11187
  if (response.status != 200) {
10769
11188
  const message = await response.text();
10770
- throw new Error(`Unexpected status ${response.status}: ${message}`);
11189
+ throw new Error(`Unexpected status ${response.status} starting transient pipeline: ${message.slice(0, 300)}`);
10771
11190
  }
10772
11191
  const result = await response.json();
10773
11192
  return result["id"];
10774
11193
  }
11194
+ async ensureTransientPipelineStarted() {
11195
+ if (this.options().popId != "transient" /* Transient */ || this._pipelineId) {
11196
+ return;
11197
+ }
11198
+ if (!this.options().pop) {
11199
+ return;
11200
+ }
11201
+ if (!this._baseUrl) {
11202
+ await this.reconnect();
11203
+ }
11204
+ if (this._pipelineId) {
11205
+ return;
11206
+ }
11207
+ if (this.options().pop) {
11208
+ await this.reconnect();
11209
+ if (!this._pipelineId) {
11210
+ throw new Error("Compute session did not provide a pipeline for the requested pop");
11211
+ }
11212
+ return;
11213
+ }
11214
+ if (!this._pipelineCreatePromise) {
11215
+ this._pipelineCreatePromise = this.startTransientPipeline();
11216
+ }
11217
+ try {
11218
+ this._pipelineId = await this._pipelineCreatePromise;
11219
+ this.setTransientPipelinePop();
11220
+ } finally {
11221
+ this._pipelineCreatePromise = null;
11222
+ }
11223
+ }
11224
+ async changeTransientPop(pop) {
11225
+ if (!this._client) {
11226
+ throw new Error("endpoint not initialized");
11227
+ }
11228
+ const previousPop = this.options().pop;
11229
+ const previousBaseUrl = this._baseUrl;
11230
+ const previousSessionAccessToken = this._sessionAccessToken;
11231
+ const previousSessionAccessTokenValidUntil = this._sessionAccessTokenValidUntil;
11232
+ if (this._pipelineId) {
11233
+ await this.deleteTransientPipeline(this._pipelineId);
11234
+ this._pipelineId = null;
11235
+ }
11236
+ this._pipeline = null;
11237
+ try {
11238
+ this.options().pop = pop;
11239
+ await this.getComputeSession();
11240
+ } catch (error) {
11241
+ this.options().pop = previousPop;
11242
+ this._pipeline = null;
11243
+ this._pipelineId = null;
11244
+ this._baseUrl = previousPop ? null : previousBaseUrl;
11245
+ this._sessionAccessToken = previousSessionAccessToken;
11246
+ this._sessionAccessTokenValidUntil = previousSessionAccessTokenValidUntil;
11247
+ throw error;
11248
+ }
11249
+ if (!this._pipelineId) {
11250
+ throw new Error("Compute session did not provide a pipeline for the requested pop");
11251
+ }
11252
+ this.setTransientPipelinePop();
11253
+ }
11254
+ async deleteTransientPipeline(pipelineId) {
11255
+ if (!this._client || !this._baseUrl) {
11256
+ return;
11257
+ }
11258
+ const deleteUrl = `${this._baseUrl}/pipelines/${pipelineId}`;
11259
+ const headers = {
11260
+ Authorization: await this.authorizationHeader()
11261
+ };
11262
+ let response = await this._client.fetch(deleteUrl, {
11263
+ method: "DELETE",
11264
+ headers
11265
+ });
11266
+ if (response.status >= 300) {
11267
+ const message = await response.text();
11268
+ this._logger.info(`stopping pipeline failed, status ${response.status}: ${message}`);
11269
+ }
11270
+ }
11271
+ async authorizationHeader() {
11272
+ if (this._sessionAccessToken) {
11273
+ return `Bearer ${this._sessionAccessToken}`;
11274
+ }
11275
+ return super.authorizationHeader();
11276
+ }
11277
+ async computeAuthorizationHeader() {
11278
+ if (this.options().auth.isLocal !== void 0) {
11279
+ return "Implicit";
11280
+ }
11281
+ if (this.options().auth.apiKey !== void 0) {
11282
+ return `Bearer ${this.options().auth.apiKey}`;
11283
+ }
11284
+ return super.authorizationHeader();
11285
+ }
11286
+ setTransientPipelinePop() {
11287
+ const pop = this.options().pop;
11288
+ if (pop && this._pipelineId) {
11289
+ this._pipeline = {
11290
+ id: this._pipelineId,
11291
+ state: "idle",
11292
+ startTime: (/* @__PURE__ */ new Date()).toString(),
11293
+ pop
11294
+ };
11295
+ }
11296
+ }
10775
11297
  };
10776
- _WorkerEndpoint.SESSION_DEAD = /* @__PURE__ */ new Set(["error" /* ERROR */, "failed" /* FAILED */, "stopped" /* STOPPED */, "pipeline_error" /* PIPELINE_ERROR */, "unknown" /* UNKNOWN */]);
10777
11298
  _WorkerEndpoint.WAIT_FOR_IS_READY = 60 * 1e3;
10778
- _WorkerEndpoint.CHECK_FOR_IS_READY_INTERVAL = 250;
10779
11299
  var WorkerEndpoint = _WorkerEndpoint;
10780
11300
 
10781
11301
  // shims/websockets.ts
@@ -12149,6 +12669,15 @@ var EyePop;
12149
12669
  if (opts.sessionUuid === void 0) {
12150
12670
  opts.sessionUuid = readEnv("EYEPOP_SESSION_UUID");
12151
12671
  }
12672
+ if (opts.sessionName === void 0) {
12673
+ opts.sessionName = readEnv("EYEPOP_SESSION_NAME");
12674
+ }
12675
+ if (opts.pipelineImage === void 0) {
12676
+ opts.pipelineImage = readEnv("EYEPOP_PIPELINE_IMAGE");
12677
+ }
12678
+ if (opts.pipelineVersion === void 0) {
12679
+ opts.pipelineVersion = readEnv("EYEPOP_PIPELINE_VERSION");
12680
+ }
12152
12681
  if (opts.popId === void 0 && opts.sessionUuid === void 0) {
12153
12682
  opts.popId = readEnv("EYEPOP_POP_ID") || "transient" /* Transient */;
12154
12683
  }
@@ -12257,6 +12786,7 @@ export {
12257
12786
  ForwardOperatorType,
12258
12787
  InferenceType,
12259
12788
  LoadFromAssetUuidJob,
12789
+ LoadFromGroupJob,
12260
12790
  LoadFromJob,
12261
12791
  LoadMediaStreamJob,
12262
12792
  ModelExportStatus,
@@ -12270,12 +12800,12 @@ export {
12270
12800
  PopComponentType,
12271
12801
  PredictionVersion,
12272
12802
  Semaphore,
12273
- SessionStatus,
12274
12803
  Stream,
12275
12804
  TaskMode,
12276
12805
  TaskType,
12277
12806
  TranscodeMode,
12278
12807
  TransientPopId,
12808
+ UploadGroupJob,
12279
12809
  UploadJob,
12280
12810
  UserReview,
12281
12811
  VideoMode,
@@ -12286,7 +12816,9 @@ export {
12286
12816
  WorkerEndpoint,
12287
12817
  WorkflowPhase,
12288
12818
  index_default as default,
12819
+ encodeMultipartFormData,
12289
12820
  getBaseUrl,
12821
+ randomMultipartBoundary,
12290
12822
  readableStreamFromString
12291
12823
  };
12292
12824
  /*! Bundled license information: