@eyepop.ai/eyepop 3.16.0 → 3.17.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.
@@ -8632,6 +8632,283 @@ 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
+ function errorInfoMessage(errorInfo) {
8685
+ return [errorInfo.code, errorInfo.message, errorInfo.details].filter((part) => typeof part == "string" && part.length > 0).join(": ");
8686
+ }
8687
+ function pipelineErrorMessage(session) {
8688
+ if (session.pipeline_error) {
8689
+ const message = errorInfoMessage(session.pipeline_error);
8690
+ return message ? `Pipeline failed for compute session ${session.session_uuid}: ${message}` : `Pipeline failed for compute session ${session.session_uuid}`;
8691
+ }
8692
+ if (session.session_status == "pipeline_error" /* PIPELINE_ERROR */) {
8693
+ return `Pipeline failed for compute session ${session.session_uuid}`;
8694
+ }
8695
+ return null;
8696
+ }
8697
+ async function responseErrorMessage(response) {
8698
+ const contentType = response.headers.get("content-type") || "";
8699
+ if (contentType.includes("application/json")) {
8700
+ try {
8701
+ const parsed = normalizedJsonValue(await response.clone().json());
8702
+ const message2 = normalizedErrorMessage(parsed);
8703
+ if (message2) {
8704
+ return message2;
8705
+ }
8706
+ } catch (_) {
8707
+ }
8708
+ }
8709
+ const body = await response.text();
8710
+ const message = normalizedErrorMessage(body);
8711
+ return message || body;
8712
+ }
8713
+ function normalizedJsonValue(value) {
8714
+ if (typeof value == "string") {
8715
+ try {
8716
+ const parsed = JSON.parse(value);
8717
+ return normalizedJsonValue(parsed);
8718
+ } catch (_) {
8719
+ const unescaped = value.replace(/\\"/g, '"');
8720
+ if (unescaped != value) {
8721
+ try {
8722
+ const parsed = JSON.parse(unescaped);
8723
+ return normalizedJsonValue(parsed);
8724
+ } catch (_2) {
8725
+ }
8726
+ }
8727
+ return value;
8728
+ }
8729
+ }
8730
+ return value;
8731
+ }
8732
+ async function responseJson(response) {
8733
+ return normalizedJsonValue(await response.json());
8734
+ }
8735
+ function normalizedErrorMessage(value) {
8736
+ value = normalizedJsonValue(value);
8737
+ if (typeof value == "string") {
8738
+ return value;
8739
+ }
8740
+ if (!value || typeof value != "object") {
8741
+ return null;
8742
+ }
8743
+ const record = value;
8744
+ const normalizedError = normalizedJsonValue(record["error"]);
8745
+ const errorMessage = normalizedErrorMessage(normalizedError) || (typeof normalizedError == "string" ? normalizedError : null);
8746
+ const message = [record["code"], errorMessage, record["message"], record["details"]].filter((part) => typeof part == "string" && part.length > 0).join(": ");
8747
+ if (message) {
8748
+ return message;
8749
+ }
8750
+ if (record["pipeline_error"]) {
8751
+ return normalizedErrorMessage(record["pipeline_error"]);
8752
+ }
8753
+ if (normalizedError && typeof normalizedError == "object") {
8754
+ return normalizedErrorMessage(normalizedError);
8755
+ }
8756
+ try {
8757
+ return JSON.stringify(value);
8758
+ } catch (_) {
8759
+ return null;
8760
+ }
8761
+ }
8762
+ var ComputeSessionClient = class {
8763
+ constructor(options) {
8764
+ this.options = options;
8765
+ }
8766
+ async resolve() {
8767
+ const session = this.options.sessionUuid ? await this.getPermanentComputeSession(this.options.sessionUuid) : await this.getOnDemandComputeSession();
8768
+ const pipelineFailure = pipelineErrorMessage(session);
8769
+ if (pipelineFailure) {
8770
+ throw new Error(pipelineFailure);
8771
+ }
8772
+ const accessToken = this.sessionAccessToken(session);
8773
+ await this.waitUntilReady(session, accessToken);
8774
+ const pipelineId = pipelineIdFromSession(session);
8775
+ if (this.options.pop && !pipelineId) {
8776
+ throw new Error("Compute session did not provide a pipeline for the requested pop");
8777
+ }
8778
+ return {
8779
+ session,
8780
+ pipelineId,
8781
+ accessToken,
8782
+ accessTokenValidUntil: accessTokenValidUntil(session)
8783
+ };
8784
+ }
8785
+ async getOnDemandComputeSession() {
8786
+ let session = null;
8787
+ const sessionsUrl = `${this.options.computeUrl}/v1/sessions`;
8788
+ const headers = {
8789
+ Authorization: await this.options.authorizationHeader()
8790
+ };
8791
+ let sessions;
8792
+ if (!this.options.pop) {
8793
+ this.options.requestLogger?.debug("fetching sessions from: %s", sessionsUrl);
8794
+ const response = await this.options.httpClient.fetch(sessionsUrl, {
8795
+ headers
8796
+ });
8797
+ if (response.status == 404) {
8798
+ sessions = [];
8799
+ } else if (response.status != 200) {
8800
+ const message = await responseErrorMessage(response);
8801
+ throw new Error(`Unexpected status ${response.status} fetching compute sessions: ${message}`);
8802
+ } else {
8803
+ const response_content = await responseJson(response);
8804
+ sessions = response_content;
8805
+ }
8806
+ if (sessions.length > 0) {
8807
+ this.options.logger?.debug(`User has ${sessions.length} sessions, inspecting for usable session`);
8808
+ for (let s of sessions) {
8809
+ if (!SESSION_DEAD.has(s.session_status) && !s.persistent && (!this.options.sessionName || s.session_name === this.options.sessionName)) {
8810
+ session = s;
8811
+ this.options.logger?.debug(`Use active session ${s.session_uuid} with pipeline ${pipelineIdFromSession(s)}`);
8812
+ break;
8813
+ }
8814
+ }
8815
+ }
8816
+ }
8817
+ if (!session) {
8818
+ this.options.requestLogger?.debug("creating a new session at: %s", sessionsUrl);
8819
+ const createBody = sessionCreateBody(this.options);
8820
+ const createHeaders = {
8821
+ ...headers,
8822
+ ...createBody ? { "Content-Type": "application/json" } : {}
8823
+ };
8824
+ const request = {
8825
+ headers: createHeaders,
8826
+ method: "POST"
8827
+ };
8828
+ if (createBody) {
8829
+ request.body = createBody;
8830
+ }
8831
+ const query = "wait=true";
8832
+ const response = await this.options.httpClient.fetch(`${sessionsUrl}?${query}`, request);
8833
+ if (response.status != 200) {
8834
+ const message = await responseErrorMessage(response);
8835
+ throw new Error(`Unexpected status ${response.status} creating a compute session: ${message}`);
8836
+ } else {
8837
+ sessions = await responseJson(response);
8838
+ }
8839
+ if (sessions.length == 0) {
8840
+ throw new Error("Unexpected, no compute session after attempt to create one");
8841
+ }
8842
+ session = sessions[0] || null;
8843
+ }
8844
+ if (!session) {
8845
+ throw new Error("unexpected undefined session");
8846
+ }
8847
+ return session;
8848
+ }
8849
+ async getPermanentComputeSession(sessionUuid) {
8850
+ let session = null;
8851
+ const sessionUrl = `${this.options.computeUrl}/v1/sessions/${sessionUuid}`;
8852
+ this.options.requestLogger?.debug("fetching session from: %s", sessionUrl);
8853
+ const headers = {
8854
+ Authorization: await this.options.authorizationHeader()
8855
+ };
8856
+ let response = await this.options.httpClient.fetch(sessionUrl, {
8857
+ headers
8858
+ });
8859
+ if (response.status == 404) {
8860
+ throw new Error(`Unexpected status ${response.status} compute sessions ${sessionUuid} not found`);
8861
+ } else if (response.status != 200) {
8862
+ const message = await responseErrorMessage(response);
8863
+ throw new Error(`Unexpected status ${response.status} fetching compute sessions: ${message}`);
8864
+ } else {
8865
+ const response_content = await responseJson(response);
8866
+ session = response_content;
8867
+ }
8868
+ return session;
8869
+ }
8870
+ sessionAccessToken(session) {
8871
+ if (!session.access_token) {
8872
+ throw new Error(`Compute session ${session.session_uuid} did not provide an access token`);
8873
+ }
8874
+ return session.access_token;
8875
+ }
8876
+ async waitUntilReady(session, accessToken) {
8877
+ let isReady = false;
8878
+ const deadline = Date.now() + this.options.readyTimeoutMs;
8879
+ while (!isReady) {
8880
+ const health_url = `${session.session_endpoint}/health`;
8881
+ const response = await this.options.httpClient.fetch(health_url, {
8882
+ headers: {
8883
+ Authorization: `Bearer ${accessToken}`,
8884
+ Accept: "application/json"
8885
+ }
8886
+ });
8887
+ if (response.status == 200) {
8888
+ const contentType = response.headers.get("content-type") || "";
8889
+ const body = await response.text();
8890
+ try {
8891
+ isReady = sessionHealthReady(body, contentType);
8892
+ } catch (error) {
8893
+ this.options.logger?.debug(`session ${session.session_uuid} health check returned invalid JSON, wait and poll for update`);
8894
+ }
8895
+ if (!isReady && Date.now() > deadline) {
8896
+ throw new Error(`Created session ${session.session_uuid} did not become healthy after ${this.options.readyTimeoutMs / 1e3} seconds`);
8897
+ }
8898
+ if (!isReady) {
8899
+ this.options.logger?.debug(`session ${session.session_uuid} health check not ready, wait and poll for update`);
8900
+ await sleep(CHECK_FOR_IS_READY_INTERVAL);
8901
+ }
8902
+ } else if (Date.now() > deadline) {
8903
+ throw new Error(`Created session ${session.session_uuid} did not become healthy after ${this.options.readyTimeoutMs / 1e3} seconds`);
8904
+ } else {
8905
+ this.options.logger?.debug(`session ${session.session_uuid} health check status ${response.status}, wait and poll for update`);
8906
+ await sleep(CHECK_FOR_IS_READY_INTERVAL);
8907
+ }
8908
+ }
8909
+ }
8910
+ };
8911
+
8635
8912
  // types.ts
8636
8913
  var EndpointState = /* @__PURE__ */ ((EndpointState2) => {
8637
8914
  EndpointState2["Idle"] = "Idle";
@@ -8644,6 +8921,7 @@ var EndpointState = /* @__PURE__ */ ((EndpointState2) => {
8644
8921
  })(EndpointState || {});
8645
8922
 
8646
8923
  // streaming.ts
8924
+ import { v4 as uuidv4 } from "uuid";
8647
8925
  var Stream = class _Stream {
8648
8926
  constructor(iterator, controller) {
8649
8927
  this.iterator = iterator;
@@ -8824,6 +9102,76 @@ function readableStreamFromString(s) {
8824
9102
  }
8825
9103
  });
8826
9104
  }
9105
+ function randomMultipartBoundary() {
9106
+ return `----formdata-boundary-${uuidv4()}`;
9107
+ }
9108
+ function toPartStream(content) {
9109
+ if (content instanceof ReadableStream) {
9110
+ return content;
9111
+ }
9112
+ if (content instanceof Blob) {
9113
+ return content.stream();
9114
+ }
9115
+ const bytes = ArrayBuffer.isView(content) ? new Uint8Array(content.buffer, content.byteOffset, content.byteLength) : new Uint8Array(content);
9116
+ return new ReadableStream({
9117
+ start(controller) {
9118
+ controller.enqueue(bytes);
9119
+ controller.close();
9120
+ }
9121
+ });
9122
+ }
9123
+ async function cancelMultipartContent(content, reason) {
9124
+ if (content instanceof ReadableStream) {
9125
+ await content.cancel(reason);
9126
+ }
9127
+ }
9128
+ function encodeMultipartFormData(parts, boundary) {
9129
+ const encoder = new TextEncoder();
9130
+ let partIndex = 0;
9131
+ let reader = null;
9132
+ return new ReadableStream({
9133
+ async pull(controller) {
9134
+ if (reader) {
9135
+ const { done, value } = await reader.read();
9136
+ if (!done) {
9137
+ controller.enqueue(value);
9138
+ return;
9139
+ }
9140
+ reader = null;
9141
+ controller.enqueue(encoder.encode("\r\n"));
9142
+ partIndex++;
9143
+ }
9144
+ if (partIndex >= parts.length) {
9145
+ controller.enqueue(encoder.encode(`--${boundary}--\r
9146
+ `));
9147
+ controller.close();
9148
+ return;
9149
+ }
9150
+ const part = parts[partIndex];
9151
+ let header = `--${boundary}\r
9152
+ Content-Disposition: form-data; name="${part.name}"\r
9153
+ `;
9154
+ if (part.contentType) {
9155
+ header += `Content-Type: ${part.contentType}\r
9156
+ `;
9157
+ }
9158
+ header += "\r\n";
9159
+ controller.enqueue(encoder.encode(header));
9160
+ if (typeof part.content === "string") {
9161
+ controller.enqueue(encoder.encode(part.content));
9162
+ controller.enqueue(encoder.encode("\r\n"));
9163
+ partIndex++;
9164
+ } else {
9165
+ reader = toPartStream(part.content).getReader();
9166
+ }
9167
+ },
9168
+ async cancel(reason) {
9169
+ await reader?.cancel(reason);
9170
+ const firstUnstartedPart = reader ? partIndex + 1 : partIndex;
9171
+ await Promise.all(parts.slice(firstUnstartedPart).map((part) => cancelMultipartContent(part.content, reason)));
9172
+ }
9173
+ });
9174
+ }
8827
9175
 
8828
9176
  // worker/worker_types.ts
8829
9177
  var PredictionVersion = /* @__PURE__ */ ((PredictionVersion3) => {
@@ -8881,7 +9229,7 @@ var ContourType = /* @__PURE__ */ ((ContourType2) => {
8881
9229
  })(ContourType || {});
8882
9230
 
8883
9231
  // worker/webrtc_whip.ts
8884
- import { v4 as uuidv4 } from "uuid";
9232
+ import { v4 as uuidv42 } from "uuid";
8885
9233
 
8886
9234
  // worker/webrtc_base.ts
8887
9235
  import path from "path";
@@ -9156,7 +9504,7 @@ var WebrtcBase = class _WebrtcBase {
9156
9504
  // worker/webrtc_whip.ts
9157
9505
  var WebrtcWhip = class extends WebrtcBase {
9158
9506
  constructor(stream, getSession, client, is_pipeline_direct, requestLogger) {
9159
- super(getSession, client, uuidv4().toString(), "liveIngress/whip", is_pipeline_direct, requestLogger);
9507
+ super(getSession, client, uuidv42().toString(), "liveIngress/whip", is_pipeline_direct, requestLogger);
9160
9508
  this._stream = stream;
9161
9509
  }
9162
9510
  async start() {
@@ -9330,14 +9678,18 @@ var UploadJob = class extends AbstractJob {
9330
9678
  get [Symbol.toStringTag]() {
9331
9679
  return "uploadJob";
9332
9680
  }
9333
- constructor(stream, mimeType, size, videoMode, params, getSession, client, requestLogger) {
9681
+ constructor(stream, mimeType, size, videoMode, params, getSession, client, requestLogger, uploadBodyFactory) {
9334
9682
  super(params, getSession, client, requestLogger);
9335
9683
  this._uploadStream = stream;
9684
+ this._uploadBodyFactory = uploadBodyFactory;
9336
9685
  this._mimeType = mimeType;
9337
9686
  this._size = size;
9338
9687
  this._videoMode = videoMode;
9339
9688
  this._needsFullDuplex = !client.isFullDuplex() && mimeType.startsWith("video/") && videoMode == "stream" /* STREAM */;
9340
9689
  }
9690
+ async uploadBody() {
9691
+ return this._uploadBodyFactory ? await this._uploadBodyFactory() : this._uploadStream;
9692
+ }
9341
9693
  async onEvent(event) {
9342
9694
  if (event.type == "prepared") {
9343
9695
  if (!event.source_id) {
@@ -9379,7 +9731,7 @@ var UploadJob = class extends AbstractJob {
9379
9731
  const blob = new Blob([JSON.stringify(this._params.fps)], { type: "application/json" });
9380
9732
  formData.append("fps", blob);
9381
9733
  }
9382
- const fileBlob = await new Response(this._uploadStream).blob();
9734
+ const fileBlob = await new Response(await this.uploadBody()).blob();
9383
9735
  formData.append("file", fileBlob);
9384
9736
  const headers = {
9385
9737
  ...session.authenticationHeaders(),
@@ -9406,7 +9758,7 @@ var UploadJob = class extends AbstractJob {
9406
9758
  request = this._client.fetch(postUrl, {
9407
9759
  headers,
9408
9760
  method: "POST",
9409
- body: this._uploadStream,
9761
+ body: await this.uploadBody(),
9410
9762
  signal: this._controller.signal,
9411
9763
  // @ts-ignore
9412
9764
  duplex: "half",
@@ -9474,7 +9826,7 @@ var UploadJob = class extends AbstractJob {
9474
9826
  const blob = new Blob([JSON.stringify(this._params.fps)], { type: "application/json" });
9475
9827
  formData.append("fps", blob);
9476
9828
  }
9477
- const fileBlob = await new Response(this._uploadStream).blob();
9829
+ const fileBlob = await new Response(await this.uploadBody()).blob();
9478
9830
  formData.append("file", fileBlob);
9479
9831
  const headers = {
9480
9832
  ...session.authenticationHeaders(),
@@ -9501,7 +9853,7 @@ var UploadJob = class extends AbstractJob {
9501
9853
  response = await this._client.fetch(postUrl, {
9502
9854
  headers,
9503
9855
  method: "POST",
9504
- body: this._uploadStream,
9856
+ body: await this.uploadBody(),
9505
9857
  signal: this._controller.signal,
9506
9858
  // @ts-ignore
9507
9859
  duplex: "half"
@@ -9551,6 +9903,111 @@ var LoadFromJob = class extends AbstractJob {
9551
9903
  });
9552
9904
  }
9553
9905
  };
9906
+ var UploadGroupJob = class extends AbstractJob {
9907
+ get [Symbol.toStringTag]() {
9908
+ return "uploadGroupJob";
9909
+ }
9910
+ constructor(sources, params, getSession, client, requestLogger) {
9911
+ super(params, getSession, client, requestLogger);
9912
+ if (sources.length === 0) {
9913
+ throw new Error("upload group requires at least one source");
9914
+ }
9915
+ this._sources = sources;
9916
+ }
9917
+ async startJob() {
9918
+ const session = await this._getSession();
9919
+ if (!session.baseUrl) {
9920
+ return Promise.reject(new Error("session.baseUrl must not be null"));
9921
+ }
9922
+ const queryParams = new URLSearchParams({
9923
+ mode: "queue",
9924
+ processing: "sync"
9925
+ });
9926
+ if (this._version !== void 0) {
9927
+ queryParams.set("version", this._version.toString());
9928
+ }
9929
+ if (this._params.motionDetect) {
9930
+ for (const [key, value] of Object.entries(this._params.motionDetect)) {
9931
+ queryParams.set(key, value);
9932
+ }
9933
+ }
9934
+ const postUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?${queryParams.toString()}`;
9935
+ const parts = [];
9936
+ if (this._params.componentParams) {
9937
+ parts.push({ name: "params", contentType: "application/json", content: JSON.stringify(this._params.componentParams) });
9938
+ }
9939
+ if (this._params.roi) {
9940
+ parts.push({ name: "roi", contentType: "application/json", content: JSON.stringify(this._params.roi) });
9941
+ }
9942
+ if (this._params.fps) {
9943
+ parts.push({ name: "fps", contentType: "application/json", content: JSON.stringify(this._params.fps) });
9944
+ }
9945
+ for (const source of this._sources) {
9946
+ parts.push({ name: "file", contentType: source.mimeType, content: source.stream });
9947
+ }
9948
+ const boundary = randomMultipartBoundary();
9949
+ const headers = {
9950
+ ...session.authenticationHeaders(),
9951
+ Accept: "application/jsonl",
9952
+ "Content-Type": `multipart/form-data; boundary=${boundary}`
9953
+ };
9954
+ return await this._client.fetch(postUrl, {
9955
+ headers,
9956
+ method: "POST",
9957
+ body: encodeMultipartFormData(parts, boundary),
9958
+ signal: this._controller.signal,
9959
+ // @ts-ignore
9960
+ duplex: "half",
9961
+ eyepop: { responseStreaming: true }
9962
+ });
9963
+ }
9964
+ };
9965
+ var LoadFromGroupJob = class extends AbstractJob {
9966
+ get [Symbol.toStringTag]() {
9967
+ return "loadFromGroupJob";
9968
+ }
9969
+ constructor(urls, params, getSession, client, requestLogger) {
9970
+ super(params, getSession, client, requestLogger);
9971
+ if (urls.length === 0) {
9972
+ throw new Error("load from group requires at least one url");
9973
+ }
9974
+ this._urls = urls;
9975
+ }
9976
+ async startJob() {
9977
+ const session = await this._getSession();
9978
+ if (!session.baseUrl) {
9979
+ return Promise.reject(new Error("session.baseUrl must not be null"));
9980
+ }
9981
+ const body = {
9982
+ sourceType: "GROUP",
9983
+ sources: this._urls.map((url) => ({ sourceType: "URL", url })),
9984
+ version: this._version
9985
+ };
9986
+ if (this._params.componentParams) {
9987
+ body["params"] = this._params.componentParams;
9988
+ }
9989
+ if (this._params.roi) {
9990
+ body["roi"] = this._params.roi;
9991
+ }
9992
+ if (this._params.motionDetect) {
9993
+ Object.assign(body, this._params.motionDetect);
9994
+ }
9995
+ const headers = {
9996
+ ...session.authenticationHeaders(),
9997
+ Accept: "application/jsonl",
9998
+ "Content-Type": "application/json"
9999
+ };
10000
+ const patchUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=queue&processing=sync`;
10001
+ return await this._client.fetch(patchUrl, {
10002
+ headers,
10003
+ method: "PATCH",
10004
+ body: JSON.stringify(body),
10005
+ signal: this._controller.signal,
10006
+ // @ts-ignore
10007
+ eyepop: { responseStreaming: true }
10008
+ });
10009
+ }
10010
+ };
9554
10011
  var LoadFromAssetUuidJob = class extends AbstractJob {
9555
10012
  constructor(assetUuid, params, getSession, client, requestLogger) {
9556
10013
  super(params, getSession, client, requestLogger);
@@ -10152,21 +10609,6 @@ var TransientPopId = /* @__PURE__ */ ((TransientPopId2) => {
10152
10609
  })(TransientPopId || {});
10153
10610
 
10154
10611
  // worker/worker_endpoint.ts
10155
- var SessionStatus = /* @__PURE__ */ ((SessionStatus2) => {
10156
- SessionStatus2["UNKNOWN"] = "unknown";
10157
- SessionStatus2["PENDING"] = "pending";
10158
- SessionStatus2["RUNNING"] = "running";
10159
- SessionStatus2["STOPPED"] = "stopped";
10160
- SessionStatus2["FAILED"] = "failed";
10161
- SessionStatus2["ERROR"] = "error";
10162
- SessionStatus2["PIPELINE_CREATING"] = "pipeline_creating";
10163
- SessionStatus2["PIPELINE_OK"] = "pipeline_ok";
10164
- SessionStatus2["PIPELINE_CHECKING"] = "pipeline_checking";
10165
- SessionStatus2["PIPELINE_ERROR"] = "pipeline_error";
10166
- SessionStatus2["UPGRADING"] = "upgrading";
10167
- return SessionStatus2;
10168
- })(SessionStatus || {});
10169
- var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
10170
10612
  var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10171
10613
  constructor(options) {
10172
10614
  super(options);
@@ -10329,6 +10771,114 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10329
10771
  return Promise.reject("unknown source type");
10330
10772
  }
10331
10773
  }
10774
+ async uploadGroup(paths, params = {}) {
10775
+ if (paths.length === 0) {
10776
+ throw new Error("upload group requires at least one path");
10777
+ }
10778
+ if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10779
+ throw new Error("endpoint not connected, use connect()");
10780
+ }
10781
+ await this._limit.acquire();
10782
+ try {
10783
+ this.updateState();
10784
+ const sources = await Promise.all(
10785
+ paths.map(async (path2) => {
10786
+ let streamSource;
10787
+ if (this._options.platformSupport?.resolvePath) {
10788
+ streamSource = await this._options.platformSupport.resolvePath({ path: path2 }, this._logger);
10789
+ } else {
10790
+ streamSource = await resolvePath({ path: path2 }, this._logger);
10791
+ }
10792
+ return { stream: streamSource.stream, mimeType: streamSource.mimeType };
10793
+ })
10794
+ );
10795
+ const job = new UploadGroupJob(
10796
+ sources,
10797
+ params,
10798
+ async () => this.session(),
10799
+ this._client,
10800
+ this._requestLogger
10801
+ );
10802
+ return job.start(
10803
+ () => {
10804
+ this.jobDone(job);
10805
+ },
10806
+ (statusCode) => {
10807
+ this.jobStatus(job, statusCode);
10808
+ }
10809
+ );
10810
+ } catch (e) {
10811
+ this._limit.release();
10812
+ throw e;
10813
+ }
10814
+ }
10815
+ async uploadStreamGroup(streams, mimeTypes, params = {}) {
10816
+ if (streams.length === 0) {
10817
+ throw new Error("upload stream group requires at least one stream");
10818
+ }
10819
+ if (mimeTypes !== void 0 && mimeTypes.length !== streams.length) {
10820
+ throw new Error("mimeTypes must have the same length as streams");
10821
+ }
10822
+ if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10823
+ throw new Error("endpoint not connected, use connect()");
10824
+ }
10825
+ await this._limit.acquire();
10826
+ try {
10827
+ this.updateState();
10828
+ const sources = streams.map((stream, i) => ({
10829
+ stream,
10830
+ mimeType: mimeTypes?.[i] ?? null
10831
+ }));
10832
+ const job = new UploadGroupJob(
10833
+ sources,
10834
+ params,
10835
+ async () => this.session(),
10836
+ this._client,
10837
+ this._requestLogger
10838
+ );
10839
+ return job.start(
10840
+ () => {
10841
+ this.jobDone(job);
10842
+ },
10843
+ (statusCode) => {
10844
+ this.jobStatus(job, statusCode);
10845
+ }
10846
+ );
10847
+ } catch (e) {
10848
+ this._limit.release();
10849
+ throw e;
10850
+ }
10851
+ }
10852
+ async loadFromGroup(urls, params = {}) {
10853
+ if (urls.length === 0) {
10854
+ throw new Error("load from group requires at least one url");
10855
+ }
10856
+ if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10857
+ throw new Error("endpoint not connected, use connect()");
10858
+ }
10859
+ await this._limit.acquire();
10860
+ try {
10861
+ this.updateState();
10862
+ const job = new LoadFromGroupJob(
10863
+ urls,
10864
+ params,
10865
+ async () => this.session(),
10866
+ this._client,
10867
+ this._requestLogger
10868
+ );
10869
+ return job.start(
10870
+ () => {
10871
+ this.jobDone(job);
10872
+ },
10873
+ (statusCode) => {
10874
+ this.jobStatus(job, statusCode);
10875
+ }
10876
+ );
10877
+ } catch (e) {
10878
+ this._limit.release();
10879
+ throw e;
10880
+ }
10881
+ }
10332
10882
  async uploadFile(source, params) {
10333
10883
  await this.ensureTransientPipelineStarted();
10334
10884
  if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
@@ -10403,12 +10953,21 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10403
10953
  await this._limit.acquire();
10404
10954
  try {
10405
10955
  this.updateState();
10406
- let streamSource;
10407
- if (this._options.platformSupport?.resolvePath) {
10408
- streamSource = await this._options.platformSupport.resolvePath(source, this._logger);
10409
- } else {
10410
- streamSource = await resolvePath(source, this._logger);
10411
- }
10956
+ const resolveStreamSource = async () => {
10957
+ if (this._options.platformSupport?.resolvePath) {
10958
+ return await this._options.platformSupport.resolvePath(source, this._logger);
10959
+ }
10960
+ return await resolvePath(source, this._logger);
10961
+ };
10962
+ const streamSource = await resolveStreamSource();
10963
+ let firstStreamPending = true;
10964
+ const uploadBodyFactory = async () => {
10965
+ if (firstStreamPending) {
10966
+ firstStreamPending = false;
10967
+ return streamSource.stream;
10968
+ }
10969
+ return (await resolveStreamSource()).stream;
10970
+ };
10412
10971
  const job = new UploadJob(
10413
10972
  streamSource.stream,
10414
10973
  streamSource.mimeType,
@@ -10419,7 +10978,8 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10419
10978
  return this.session();
10420
10979
  },
10421
10980
  this._client,
10422
- this._requestLogger
10981
+ this._requestLogger,
10982
+ uploadBodyFactory
10423
10983
  );
10424
10984
  return job.start(
10425
10985
  () => {
@@ -10649,161 +11209,38 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10649
11209
  const seconds = this.options().sessionReadyTimeoutSeconds;
10650
11210
  return typeof seconds == "number" && Number.isFinite(seconds) && seconds > 0 ? seconds * 1e3 : _WorkerEndpoint.WAIT_FOR_IS_READY;
10651
11211
  }
10652
- sessionHealthReady(body, contentType) {
10653
- if (contentType.includes("application/json")) {
10654
- const health = JSON.parse(body);
10655
- if (health && typeof health == "object") {
10656
- if (health.message && !health.session_status) {
10657
- return true;
10658
- }
10659
- return health.session_status == "running" /* RUNNING */;
10660
- }
10661
- }
10662
- return contentType.includes("text/plain") && body.includes("I'm fine");
10663
- }
10664
11212
  async getComputeSession() {
10665
11213
  if (!this._client) {
10666
11214
  throw new Error("endpoint not initialized");
10667
11215
  }
10668
- let session;
10669
- const sessionUuid = this.options().sessionUuid;
10670
- if (sessionUuid) {
10671
- session = await this.getPermanentComputeSession(this._client, sessionUuid);
10672
- } else {
10673
- session = await this.getOnDemandComputeSession(this._client);
10674
- }
10675
- this.setComputeSessionAccessToken(session);
10676
- const headers = {
10677
- Authorization: await this.authorizationHeader()
10678
- };
10679
- let isReady = false;
10680
- const readyTimeoutMs = this.sessionReadyTimeoutMs();
10681
- const deadline = Date.now() + readyTimeoutMs;
10682
- while (!isReady) {
10683
- const health_url = `${session.session_endpoint}/health`;
10684
- const response = await this._client.fetch(health_url, {
10685
- headers: {
10686
- ...headers,
10687
- Accept: "application/json"
10688
- }
10689
- });
10690
- if (response.status == 200) {
10691
- const contentType = response.headers.get("content-type") || "";
10692
- const body = await response.text();
10693
- try {
10694
- isReady = this.sessionHealthReady(body, contentType);
10695
- } catch (error) {
10696
- this._logger.debug(`session ${session.session_uuid} health check returned invalid JSON, wait and poll for update`);
10697
- }
10698
- if (!isReady && Date.now() > deadline) {
10699
- throw new Error(`Created session ${session.session_uuid} did not become healthy after ${readyTimeoutMs / 1e3} seconds`);
10700
- }
10701
- if (!isReady) {
10702
- this._logger.debug(`session ${session.session_uuid} health check not ready, wait and poll for update`);
10703
- await sleep(_WorkerEndpoint.CHECK_FOR_IS_READY_INTERVAL);
10704
- }
10705
- } else if (Date.now() > deadline) {
10706
- throw new Error(`Created session ${session.session_uuid} did not become healthy after ${readyTimeoutMs / 1e3} seconds`);
10707
- } else {
10708
- this._logger.debug(`session ${session.session_uuid} health check status ${response.status}, wait and poll for update`);
10709
- await sleep(_WorkerEndpoint.CHECK_FOR_IS_READY_INTERVAL);
10710
- }
11216
+ let resolved;
11217
+ try {
11218
+ resolved = await new ComputeSessionClient({
11219
+ computeUrl: this.eyepopUrl(),
11220
+ httpClient: this._client,
11221
+ authorizationHeader: () => this.computeAuthorizationHeader(),
11222
+ sessionUuid: this.options().sessionUuid,
11223
+ sessionName: this.options().sessionName,
11224
+ pipelineImage: this.options().pipelineImage,
11225
+ pipelineVersion: this.options().pipelineVersion,
11226
+ pop: this.options().pop,
11227
+ readyTimeoutMs: this.sessionReadyTimeoutMs(),
11228
+ logger: this._logger,
11229
+ requestLogger: this._requestLogger
11230
+ }).resolve();
11231
+ } catch (error) {
11232
+ this.updateState("Error" /* Error */);
11233
+ throw error;
10711
11234
  }
10712
- this._baseUrl = session.session_endpoint;
11235
+ this._sessionAccessToken = resolved.accessToken;
11236
+ this._sessionAccessTokenValidUntil = resolved.accessTokenValidUntil;
11237
+ this._baseUrl = resolved.session.session_endpoint;
10713
11238
  this._pipeline = null;
10714
- this._pipelineId = this.pipelineIdFromSession(session);
11239
+ this._pipelineId = resolved.pipelineId;
10715
11240
  if (this.options().pop) {
10716
11241
  this.setTransientPipelinePop();
10717
11242
  }
10718
11243
  }
10719
- async getOnDemandComputeSession(httpClient) {
10720
- let session = null;
10721
- const sessionsUrl = `${this.eyepopUrl()}/v1/sessions`;
10722
- const headers = {
10723
- Authorization: await this.computeAuthorizationHeader()
10724
- };
10725
- let sessions;
10726
- if (!this.options().pop) {
10727
- this._requestLogger.debug("fetching sessions from: %s", sessionsUrl);
10728
- const response = await httpClient.fetch(sessionsUrl, {
10729
- headers
10730
- });
10731
- if (response.status == 404) {
10732
- sessions = [];
10733
- } else if (response.status != 200) {
10734
- this.updateState("Error" /* Error */);
10735
- const message = await response.text();
10736
- throw new Error(`Unexpected status ${response.status} fetching compute sessions: ${message}`);
10737
- } else {
10738
- const response_content = await response.json();
10739
- sessions = response_content;
10740
- }
10741
- if (sessions.length > 0) {
10742
- this._logger.debug(`User has ${sessions.length} sessions, inspecting for usable session`);
10743
- for (let s of sessions) {
10744
- if (!_WorkerEndpoint.SESSION_DEAD.has(s.session_status) && !s.persistent) {
10745
- session = s;
10746
- this._logger.debug(`Use active session ${s.session_uuid} with pipeline ${this.pipelineIdFromSession(s)}`);
10747
- break;
10748
- }
10749
- }
10750
- }
10751
- }
10752
- if (!session) {
10753
- this._requestLogger.debug("creating a new session at: %s", sessionsUrl);
10754
- const createBody = this.sessionCreateBody();
10755
- const createHeaders = {
10756
- ...headers,
10757
- ...createBody ? { "Content-Type": "application/json" } : {}
10758
- };
10759
- const request = {
10760
- headers: createHeaders,
10761
- method: "POST"
10762
- };
10763
- if (createBody) {
10764
- request.body = createBody;
10765
- }
10766
- const response = await httpClient.fetch(`${sessionsUrl}?wait=true`, request);
10767
- if (response.status != 200) {
10768
- this.updateState("Error" /* Error */);
10769
- const message = await response.text();
10770
- throw new Error(`Unexpected status ${response.status} creating a compute session: ${message}`);
10771
- } else {
10772
- sessions = await response.json();
10773
- }
10774
- if (sessions.length == 0) {
10775
- throw new Error("Unexpected, no compute session after attempt to create one");
10776
- }
10777
- session = sessions[0] || null;
10778
- }
10779
- if (!session) {
10780
- throw new Error("unexpected undefined session");
10781
- }
10782
- return session;
10783
- }
10784
- async getPermanentComputeSession(httpClient, sessionUuid) {
10785
- let session = null;
10786
- const sessionUrl = `${this.eyepopUrl()}/v1/sessions/${sessionUuid}`;
10787
- this._requestLogger.debug("fetching session from: %s", sessionUrl);
10788
- const headers = {
10789
- Authorization: await this.computeAuthorizationHeader()
10790
- };
10791
- let response = await httpClient.fetch(sessionUrl, {
10792
- headers
10793
- });
10794
- if (response.status == 404) {
10795
- this.updateState("Error" /* Error */);
10796
- throw new Error(`Unexpected status ${response.status} compute sessions ${sessionUuid} not found`);
10797
- } else if (response.status != 200) {
10798
- this.updateState("Error" /* Error */);
10799
- const message = await response.text();
10800
- throw new Error(`Unexpected status ${response.status} fetching compute sessions: ${message}`);
10801
- } else {
10802
- const response_content = await response.json();
10803
- session = response_content;
10804
- }
10805
- return session;
10806
- }
10807
11244
  async startTransientPipeline() {
10808
11245
  const client = this._client;
10809
11246
  const baseUrl = this._baseUrl;
@@ -10860,6 +11297,13 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10860
11297
  if (this._pipelineId) {
10861
11298
  return;
10862
11299
  }
11300
+ if (this.options().pop) {
11301
+ await this.reconnect();
11302
+ if (!this._pipelineId) {
11303
+ throw new Error("Compute session did not provide a pipeline for the requested pop");
11304
+ }
11305
+ return;
11306
+ }
10863
11307
  if (!this._pipelineCreatePromise) {
10864
11308
  this._pipelineCreatePromise = this.startTransientPipeline();
10865
11309
  }
@@ -10875,7 +11319,9 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10875
11319
  throw new Error("endpoint not initialized");
10876
11320
  }
10877
11321
  const previousPop = this.options().pop;
10878
- const previousPipeline = this._pipeline;
11322
+ const previousBaseUrl = this._baseUrl;
11323
+ const previousSessionAccessToken = this._sessionAccessToken;
11324
+ const previousSessionAccessTokenValidUntil = this._sessionAccessTokenValidUntil;
10879
11325
  if (this._pipelineId) {
10880
11326
  await this.deleteTransientPipeline(this._pipelineId);
10881
11327
  this._pipelineId = null;
@@ -10883,10 +11329,14 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10883
11329
  this._pipeline = null;
10884
11330
  try {
10885
11331
  this.options().pop = pop;
10886
- await this.ensureTransientPipelineStarted();
11332
+ await this.getComputeSession();
10887
11333
  } catch (error) {
10888
11334
  this.options().pop = previousPop;
10889
- this._pipeline = previousPipeline;
11335
+ this._pipeline = null;
11336
+ this._pipelineId = null;
11337
+ this._baseUrl = previousPop ? null : previousBaseUrl;
11338
+ this._sessionAccessToken = previousSessionAccessToken;
11339
+ this._sessionAccessTokenValidUntil = previousSessionAccessTokenValidUntil;
10890
11340
  throw error;
10891
11341
  }
10892
11342
  if (!this._pipelineId) {
@@ -10911,13 +11361,6 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10911
11361
  this._logger.info(`stopping pipeline failed, status ${response.status}: ${message}`);
10912
11362
  }
10913
11363
  }
10914
- pipelineIdFromSession(session) {
10915
- if (session.pipeline_uuid) {
10916
- return session.pipeline_uuid;
10917
- }
10918
- const pipeline = session.pipelines?.[0];
10919
- return pipeline?.id || pipeline?.pipeline_id || null;
10920
- }
10921
11364
  async authorizationHeader() {
10922
11365
  if (this._sessionAccessToken) {
10923
11366
  return `Bearer ${this._sessionAccessToken}`;
@@ -10933,20 +11376,6 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10933
11376
  }
10934
11377
  return super.authorizationHeader();
10935
11378
  }
10936
- setComputeSessionAccessToken(session) {
10937
- if (!session.access_token) {
10938
- throw new Error(`Compute session ${session.session_uuid} did not provide an access token`);
10939
- }
10940
- this._sessionAccessToken = session.access_token;
10941
- if (session.access_token_expires_at) {
10942
- const expiresAt = Date.parse(session.access_token_expires_at);
10943
- this._sessionAccessTokenValidUntil = Number.isNaN(expiresAt) ? null : expiresAt;
10944
- } else if (session.access_token_expires_in) {
10945
- this._sessionAccessTokenValidUntil = Date.now() + session.access_token_expires_in * 1e3;
10946
- } else {
10947
- this._sessionAccessTokenValidUntil = null;
10948
- }
10949
- }
10950
11379
  setTransientPipelinePop() {
10951
11380
  const pop = this.options().pop;
10952
11381
  if (pop && this._pipelineId) {
@@ -10958,26 +11387,8 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10958
11387
  };
10959
11388
  }
10960
11389
  }
10961
- sessionCreateBody() {
10962
- const body = {};
10963
- if (this.options().sessionName) {
10964
- body.session_name = this.options().sessionName;
10965
- }
10966
- if (this.options().pipelineImage) {
10967
- body.pipeline_image = this.options().pipelineImage;
10968
- }
10969
- if (this.options().pipelineVersion) {
10970
- body.pipeline_version = this.options().pipelineVersion;
10971
- }
10972
- if (this.options().pop) {
10973
- body.pop = this.options().pop;
10974
- }
10975
- return Object.keys(body).length > 0 ? JSON.stringify(body) : void 0;
10976
- }
10977
11390
  };
10978
- _WorkerEndpoint.SESSION_DEAD = /* @__PURE__ */ new Set(["error" /* ERROR */, "failed" /* FAILED */, "stopped" /* STOPPED */, "pipeline_error" /* PIPELINE_ERROR */, "unknown" /* UNKNOWN */]);
10979
11391
  _WorkerEndpoint.WAIT_FOR_IS_READY = 60 * 1e3;
10980
- _WorkerEndpoint.CHECK_FOR_IS_READY_INTERVAL = 250;
10981
11392
  var WorkerEndpoint = _WorkerEndpoint;
10982
11393
 
10983
11394
  // shims/websockets.ts
@@ -12468,6 +12879,7 @@ export {
12468
12879
  ForwardOperatorType,
12469
12880
  InferenceType,
12470
12881
  LoadFromAssetUuidJob,
12882
+ LoadFromGroupJob,
12471
12883
  LoadFromJob,
12472
12884
  LoadMediaStreamJob,
12473
12885
  ModelExportStatus,
@@ -12481,12 +12893,12 @@ export {
12481
12893
  PopComponentType,
12482
12894
  PredictionVersion,
12483
12895
  Semaphore,
12484
- SessionStatus,
12485
12896
  Stream,
12486
12897
  TaskMode,
12487
12898
  TaskType,
12488
12899
  TranscodeMode,
12489
12900
  TransientPopId,
12901
+ UploadGroupJob,
12490
12902
  UploadJob,
12491
12903
  UserReview,
12492
12904
  VideoMode,
@@ -12497,7 +12909,9 @@ export {
12497
12909
  WorkerEndpoint,
12498
12910
  WorkflowPhase,
12499
12911
  index_default as default,
12912
+ encodeMultipartFormData,
12500
12913
  getBaseUrl,
12914
+ randomMultipartBoundary,
12501
12915
  readableStreamFromString
12502
12916
  };
12503
12917
  /*! Bundled license information: