@eyepop.ai/eyepop 3.16.0 → 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.
@@ -8680,6 +8680,7 @@ __export(index_exports, {
8680
8680
  ForwardOperatorType: () => ForwardOperatorType,
8681
8681
  InferenceType: () => InferenceType,
8682
8682
  LoadFromAssetUuidJob: () => LoadFromAssetUuidJob,
8683
+ LoadFromGroupJob: () => LoadFromGroupJob,
8683
8684
  LoadFromJob: () => LoadFromJob,
8684
8685
  LoadMediaStreamJob: () => LoadMediaStreamJob,
8685
8686
  ModelExportStatus: () => ModelExportStatus,
@@ -8693,12 +8694,12 @@ __export(index_exports, {
8693
8694
  PopComponentType: () => PopComponentType,
8694
8695
  PredictionVersion: () => PredictionVersion,
8695
8696
  Semaphore: () => Semaphore,
8696
- SessionStatus: () => SessionStatus,
8697
8697
  Stream: () => Stream,
8698
8698
  TaskMode: () => TaskMode,
8699
8699
  TaskType: () => TaskType,
8700
8700
  TranscodeMode: () => TranscodeMode,
8701
8701
  TransientPopId: () => TransientPopId,
8702
+ UploadGroupJob: () => UploadGroupJob,
8702
8703
  UploadJob: () => UploadJob,
8703
8704
  UserReview: () => UserReview,
8704
8705
  VideoMode: () => VideoMode,
@@ -8709,11 +8710,211 @@ __export(index_exports, {
8709
8710
  WorkerEndpoint: () => WorkerEndpoint,
8710
8711
  WorkflowPhase: () => WorkflowPhase,
8711
8712
  default: () => index_default,
8713
+ encodeMultipartFormData: () => encodeMultipartFormData,
8712
8714
  getBaseUrl: () => getBaseUrl,
8715
+ randomMultipartBoundary: () => randomMultipartBoundary,
8713
8716
  readableStreamFromString: () => readableStreamFromString
8714
8717
  });
8715
8718
  module.exports = __toCommonJS(index_exports);
8716
8719
 
8720
+ // compute/compute_session.ts
8721
+ var SESSION_DEAD = /* @__PURE__ */ new Set(["error" /* ERROR */, "failed" /* FAILED */, "stopped" /* STOPPED */, "pipeline_error" /* PIPELINE_ERROR */, "unknown" /* UNKNOWN */]);
8722
+ var CHECK_FOR_IS_READY_INTERVAL = 250;
8723
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
8724
+ function pipelineIdFromSession(session) {
8725
+ if (session.pipeline_uuid) {
8726
+ return session.pipeline_uuid;
8727
+ }
8728
+ const pipeline = session.pipelines?.[0];
8729
+ return pipeline?.id || pipeline?.pipeline_id || null;
8730
+ }
8731
+ function sessionCreateBody(options) {
8732
+ const body = {};
8733
+ if (options.sessionName) {
8734
+ body.session_name = options.sessionName;
8735
+ }
8736
+ if (options.pipelineImage) {
8737
+ body.pipeline_image = options.pipelineImage;
8738
+ }
8739
+ if (options.pipelineVersion) {
8740
+ body.pipeline_version = options.pipelineVersion;
8741
+ }
8742
+ if (options.pop) {
8743
+ body.pop = options.pop;
8744
+ }
8745
+ return Object.keys(body).length > 0 ? JSON.stringify(body) : void 0;
8746
+ }
8747
+ function sessionHealthReady(body, contentType) {
8748
+ if (contentType.includes("application/json")) {
8749
+ const health = JSON.parse(body);
8750
+ if (health && typeof health == "object") {
8751
+ if (health.message && !health.session_status) {
8752
+ return true;
8753
+ }
8754
+ return health.session_status == "running" /* RUNNING */;
8755
+ }
8756
+ }
8757
+ return contentType.includes("text/plain") && body.includes("I'm fine");
8758
+ }
8759
+ function accessTokenValidUntil(session) {
8760
+ if (session.access_token_expires_at) {
8761
+ const expiresAt = Date.parse(session.access_token_expires_at);
8762
+ return Number.isNaN(expiresAt) ? null : expiresAt;
8763
+ }
8764
+ if (session.access_token_expires_in) {
8765
+ return Date.now() + session.access_token_expires_in * 1e3;
8766
+ }
8767
+ return null;
8768
+ }
8769
+ var ComputeSessionClient = class {
8770
+ constructor(options) {
8771
+ this.options = options;
8772
+ }
8773
+ async resolve() {
8774
+ const session = this.options.sessionUuid ? await this.getPermanentComputeSession(this.options.sessionUuid) : await this.getOnDemandComputeSession();
8775
+ const accessToken = this.sessionAccessToken(session);
8776
+ await this.waitUntilReady(session, accessToken);
8777
+ const pipelineId = pipelineIdFromSession(session);
8778
+ if (this.options.pop && !pipelineId) {
8779
+ throw new Error("Compute session did not provide a pipeline for the requested pop");
8780
+ }
8781
+ return {
8782
+ session,
8783
+ pipelineId,
8784
+ accessToken,
8785
+ accessTokenValidUntil: accessTokenValidUntil(session)
8786
+ };
8787
+ }
8788
+ async getOnDemandComputeSession() {
8789
+ let session = null;
8790
+ const sessionsUrl = `${this.options.computeUrl}/v1/sessions`;
8791
+ const headers = {
8792
+ Authorization: await this.options.authorizationHeader()
8793
+ };
8794
+ let sessions;
8795
+ if (!this.options.pop) {
8796
+ this.options.requestLogger?.debug("fetching sessions from: %s", sessionsUrl);
8797
+ const response = await this.options.httpClient.fetch(sessionsUrl, {
8798
+ headers
8799
+ });
8800
+ if (response.status == 404) {
8801
+ sessions = [];
8802
+ } else if (response.status != 200) {
8803
+ const message = await response.text();
8804
+ throw new Error(`Unexpected status ${response.status} fetching compute sessions: ${message}`);
8805
+ } else {
8806
+ const response_content = await response.json();
8807
+ sessions = response_content;
8808
+ }
8809
+ if (sessions.length > 0) {
8810
+ this.options.logger?.debug(`User has ${sessions.length} sessions, inspecting for usable session`);
8811
+ for (let s of sessions) {
8812
+ if (!SESSION_DEAD.has(s.session_status) && !s.persistent && (!this.options.sessionName || s.session_name === this.options.sessionName)) {
8813
+ session = s;
8814
+ this.options.logger?.debug(`Use active session ${s.session_uuid} with pipeline ${pipelineIdFromSession(s)}`);
8815
+ break;
8816
+ }
8817
+ }
8818
+ }
8819
+ }
8820
+ if (!session) {
8821
+ this.options.requestLogger?.debug("creating a new session at: %s", sessionsUrl);
8822
+ const createBody = sessionCreateBody(this.options);
8823
+ const createHeaders = {
8824
+ ...headers,
8825
+ ...createBody ? { "Content-Type": "application/json" } : {}
8826
+ };
8827
+ const request = {
8828
+ headers: createHeaders,
8829
+ method: "POST"
8830
+ };
8831
+ if (createBody) {
8832
+ request.body = createBody;
8833
+ }
8834
+ const query = "wait=true";
8835
+ const response = await this.options.httpClient.fetch(`${sessionsUrl}?${query}`, request);
8836
+ if (response.status != 200) {
8837
+ const message = await response.text();
8838
+ throw new Error(`Unexpected status ${response.status} creating a compute session: ${message}`);
8839
+ } else {
8840
+ sessions = await response.json();
8841
+ }
8842
+ if (sessions.length == 0) {
8843
+ throw new Error("Unexpected, no compute session after attempt to create one");
8844
+ }
8845
+ session = sessions[0] || null;
8846
+ }
8847
+ if (!session) {
8848
+ throw new Error("unexpected undefined session");
8849
+ }
8850
+ if (this.options.pop && !pipelineIdFromSession(session)) {
8851
+ throw new Error("Compute session did not provide a pipeline for the requested pop");
8852
+ }
8853
+ return session;
8854
+ }
8855
+ async getPermanentComputeSession(sessionUuid) {
8856
+ let session = null;
8857
+ const sessionUrl = `${this.options.computeUrl}/v1/sessions/${sessionUuid}`;
8858
+ this.options.requestLogger?.debug("fetching session from: %s", sessionUrl);
8859
+ const headers = {
8860
+ Authorization: await this.options.authorizationHeader()
8861
+ };
8862
+ let response = await this.options.httpClient.fetch(sessionUrl, {
8863
+ headers
8864
+ });
8865
+ if (response.status == 404) {
8866
+ throw new Error(`Unexpected status ${response.status} compute sessions ${sessionUuid} not found`);
8867
+ } else if (response.status != 200) {
8868
+ const message = await response.text();
8869
+ throw new Error(`Unexpected status ${response.status} fetching compute sessions: ${message}`);
8870
+ } else {
8871
+ const response_content = await response.json();
8872
+ session = response_content;
8873
+ }
8874
+ return session;
8875
+ }
8876
+ sessionAccessToken(session) {
8877
+ if (!session.access_token) {
8878
+ throw new Error(`Compute session ${session.session_uuid} did not provide an access token`);
8879
+ }
8880
+ return session.access_token;
8881
+ }
8882
+ async waitUntilReady(session, accessToken) {
8883
+ let isReady = false;
8884
+ const deadline = Date.now() + this.options.readyTimeoutMs;
8885
+ while (!isReady) {
8886
+ const health_url = `${session.session_endpoint}/health`;
8887
+ const response = await this.options.httpClient.fetch(health_url, {
8888
+ headers: {
8889
+ Authorization: `Bearer ${accessToken}`,
8890
+ Accept: "application/json"
8891
+ }
8892
+ });
8893
+ if (response.status == 200) {
8894
+ const contentType = response.headers.get("content-type") || "";
8895
+ const body = await response.text();
8896
+ try {
8897
+ isReady = sessionHealthReady(body, contentType);
8898
+ } catch (error) {
8899
+ this.options.logger?.debug(`session ${session.session_uuid} health check returned invalid JSON, wait and poll for update`);
8900
+ }
8901
+ if (!isReady && Date.now() > deadline) {
8902
+ throw new Error(`Created session ${session.session_uuid} did not become healthy after ${this.options.readyTimeoutMs / 1e3} seconds`);
8903
+ }
8904
+ if (!isReady) {
8905
+ this.options.logger?.debug(`session ${session.session_uuid} health check not ready, wait and poll for update`);
8906
+ await sleep(CHECK_FOR_IS_READY_INTERVAL);
8907
+ }
8908
+ } else if (Date.now() > deadline) {
8909
+ throw new Error(`Created session ${session.session_uuid} did not become healthy after ${this.options.readyTimeoutMs / 1e3} seconds`);
8910
+ } else {
8911
+ this.options.logger?.debug(`session ${session.session_uuid} health check status ${response.status}, wait and poll for update`);
8912
+ await sleep(CHECK_FOR_IS_READY_INTERVAL);
8913
+ }
8914
+ }
8915
+ }
8916
+ };
8917
+
8717
8918
  // types.ts
8718
8919
  var EndpointState = /* @__PURE__ */ ((EndpointState2) => {
8719
8920
  EndpointState2["Idle"] = "Idle";
@@ -8726,6 +8927,7 @@ var EndpointState = /* @__PURE__ */ ((EndpointState2) => {
8726
8927
  })(EndpointState || {});
8727
8928
 
8728
8929
  // streaming.ts
8930
+ var import_uuid = require("uuid");
8729
8931
  var Stream = class _Stream {
8730
8932
  constructor(iterator, controller) {
8731
8933
  this.iterator = iterator;
@@ -8906,6 +9108,76 @@ function readableStreamFromString(s) {
8906
9108
  }
8907
9109
  });
8908
9110
  }
9111
+ function randomMultipartBoundary() {
9112
+ return `----formdata-boundary-${(0, import_uuid.v4)()}`;
9113
+ }
9114
+ function toPartStream(content) {
9115
+ if (content instanceof ReadableStream) {
9116
+ return content;
9117
+ }
9118
+ if (content instanceof Blob) {
9119
+ return content.stream();
9120
+ }
9121
+ const bytes = ArrayBuffer.isView(content) ? new Uint8Array(content.buffer, content.byteOffset, content.byteLength) : new Uint8Array(content);
9122
+ return new ReadableStream({
9123
+ start(controller) {
9124
+ controller.enqueue(bytes);
9125
+ controller.close();
9126
+ }
9127
+ });
9128
+ }
9129
+ async function cancelMultipartContent(content, reason) {
9130
+ if (content instanceof ReadableStream) {
9131
+ await content.cancel(reason);
9132
+ }
9133
+ }
9134
+ function encodeMultipartFormData(parts, boundary) {
9135
+ const encoder = new TextEncoder();
9136
+ let partIndex = 0;
9137
+ let reader = null;
9138
+ return new ReadableStream({
9139
+ async pull(controller) {
9140
+ if (reader) {
9141
+ const { done, value } = await reader.read();
9142
+ if (!done) {
9143
+ controller.enqueue(value);
9144
+ return;
9145
+ }
9146
+ reader = null;
9147
+ controller.enqueue(encoder.encode("\r\n"));
9148
+ partIndex++;
9149
+ }
9150
+ if (partIndex >= parts.length) {
9151
+ controller.enqueue(encoder.encode(`--${boundary}--\r
9152
+ `));
9153
+ controller.close();
9154
+ return;
9155
+ }
9156
+ const part = parts[partIndex];
9157
+ let header = `--${boundary}\r
9158
+ Content-Disposition: form-data; name="${part.name}"\r
9159
+ `;
9160
+ if (part.contentType) {
9161
+ header += `Content-Type: ${part.contentType}\r
9162
+ `;
9163
+ }
9164
+ header += "\r\n";
9165
+ controller.enqueue(encoder.encode(header));
9166
+ if (typeof part.content === "string") {
9167
+ controller.enqueue(encoder.encode(part.content));
9168
+ controller.enqueue(encoder.encode("\r\n"));
9169
+ partIndex++;
9170
+ } else {
9171
+ reader = toPartStream(part.content).getReader();
9172
+ }
9173
+ },
9174
+ async cancel(reason) {
9175
+ await reader?.cancel(reason);
9176
+ const firstUnstartedPart = reader ? partIndex + 1 : partIndex;
9177
+ await Promise.all(parts.slice(firstUnstartedPart).map((part) => cancelMultipartContent(part.content, reason)));
9178
+ }
9179
+ });
9180
+ }
8909
9181
 
8910
9182
  // worker/worker_types.ts
8911
9183
  var PredictionVersion = /* @__PURE__ */ ((PredictionVersion3) => {
@@ -8963,7 +9235,7 @@ var ContourType = /* @__PURE__ */ ((ContourType2) => {
8963
9235
  })(ContourType || {});
8964
9236
 
8965
9237
  // worker/webrtc_whip.ts
8966
- var import_uuid = require("uuid");
9238
+ var import_uuid2 = require("uuid");
8967
9239
 
8968
9240
  // worker/webrtc_base.ts
8969
9241
  var import_path = __toESM(require("path"));
@@ -9238,7 +9510,7 @@ var WebrtcBase = class _WebrtcBase {
9238
9510
  // worker/webrtc_whip.ts
9239
9511
  var WebrtcWhip = class extends WebrtcBase {
9240
9512
  constructor(stream, getSession, client, is_pipeline_direct, requestLogger) {
9241
- super(getSession, client, (0, import_uuid.v4)().toString(), "liveIngress/whip", is_pipeline_direct, requestLogger);
9513
+ super(getSession, client, (0, import_uuid2.v4)().toString(), "liveIngress/whip", is_pipeline_direct, requestLogger);
9242
9514
  this._stream = stream;
9243
9515
  }
9244
9516
  async start() {
@@ -9633,6 +9905,111 @@ var LoadFromJob = class extends AbstractJob {
9633
9905
  });
9634
9906
  }
9635
9907
  };
9908
+ var UploadGroupJob = class extends AbstractJob {
9909
+ get [Symbol.toStringTag]() {
9910
+ return "uploadGroupJob";
9911
+ }
9912
+ constructor(sources, params, getSession, client, requestLogger) {
9913
+ super(params, getSession, client, requestLogger);
9914
+ if (sources.length === 0) {
9915
+ throw new Error("upload group requires at least one source");
9916
+ }
9917
+ this._sources = sources;
9918
+ }
9919
+ async startJob() {
9920
+ const session = await this._getSession();
9921
+ if (!session.baseUrl) {
9922
+ return Promise.reject(new Error("session.baseUrl must not be null"));
9923
+ }
9924
+ const queryParams = new URLSearchParams({
9925
+ mode: "queue",
9926
+ processing: "sync"
9927
+ });
9928
+ if (this._version !== void 0) {
9929
+ queryParams.set("version", this._version.toString());
9930
+ }
9931
+ if (this._params.motionDetect) {
9932
+ for (const [key, value] of Object.entries(this._params.motionDetect)) {
9933
+ queryParams.set(key, value);
9934
+ }
9935
+ }
9936
+ const postUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?${queryParams.toString()}`;
9937
+ const parts = [];
9938
+ if (this._params.componentParams) {
9939
+ parts.push({ name: "params", contentType: "application/json", content: JSON.stringify(this._params.componentParams) });
9940
+ }
9941
+ if (this._params.roi) {
9942
+ parts.push({ name: "roi", contentType: "application/json", content: JSON.stringify(this._params.roi) });
9943
+ }
9944
+ if (this._params.fps) {
9945
+ parts.push({ name: "fps", contentType: "application/json", content: JSON.stringify(this._params.fps) });
9946
+ }
9947
+ for (const source of this._sources) {
9948
+ parts.push({ name: "file", contentType: source.mimeType, content: source.stream });
9949
+ }
9950
+ const boundary = randomMultipartBoundary();
9951
+ const headers = {
9952
+ ...session.authenticationHeaders(),
9953
+ Accept: "application/jsonl",
9954
+ "Content-Type": `multipart/form-data; boundary=${boundary}`
9955
+ };
9956
+ return await this._client.fetch(postUrl, {
9957
+ headers,
9958
+ method: "POST",
9959
+ body: encodeMultipartFormData(parts, boundary),
9960
+ signal: this._controller.signal,
9961
+ // @ts-ignore
9962
+ duplex: "half",
9963
+ eyepop: { responseStreaming: true }
9964
+ });
9965
+ }
9966
+ };
9967
+ var LoadFromGroupJob = class extends AbstractJob {
9968
+ get [Symbol.toStringTag]() {
9969
+ return "loadFromGroupJob";
9970
+ }
9971
+ constructor(urls, params, getSession, client, requestLogger) {
9972
+ super(params, getSession, client, requestLogger);
9973
+ if (urls.length === 0) {
9974
+ throw new Error("load from group requires at least one url");
9975
+ }
9976
+ this._urls = urls;
9977
+ }
9978
+ async startJob() {
9979
+ const session = await this._getSession();
9980
+ if (!session.baseUrl) {
9981
+ return Promise.reject(new Error("session.baseUrl must not be null"));
9982
+ }
9983
+ const body = {
9984
+ sourceType: "GROUP",
9985
+ sources: this._urls.map((url) => ({ sourceType: "URL", url })),
9986
+ version: this._version
9987
+ };
9988
+ if (this._params.componentParams) {
9989
+ body["params"] = this._params.componentParams;
9990
+ }
9991
+ if (this._params.roi) {
9992
+ body["roi"] = this._params.roi;
9993
+ }
9994
+ if (this._params.motionDetect) {
9995
+ Object.assign(body, this._params.motionDetect);
9996
+ }
9997
+ const headers = {
9998
+ ...session.authenticationHeaders(),
9999
+ Accept: "application/jsonl",
10000
+ "Content-Type": "application/json"
10001
+ };
10002
+ const patchUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=queue&processing=sync`;
10003
+ return await this._client.fetch(patchUrl, {
10004
+ headers,
10005
+ method: "PATCH",
10006
+ body: JSON.stringify(body),
10007
+ signal: this._controller.signal,
10008
+ // @ts-ignore
10009
+ eyepop: { responseStreaming: true }
10010
+ });
10011
+ }
10012
+ };
9636
10013
  var LoadFromAssetUuidJob = class extends AbstractJob {
9637
10014
  constructor(assetUuid, params, getSession, client, requestLogger) {
9638
10015
  super(params, getSession, client, requestLogger);
@@ -10234,21 +10611,6 @@ var TransientPopId = /* @__PURE__ */ ((TransientPopId2) => {
10234
10611
  })(TransientPopId || {});
10235
10612
 
10236
10613
  // worker/worker_endpoint.ts
10237
- var SessionStatus = /* @__PURE__ */ ((SessionStatus2) => {
10238
- SessionStatus2["UNKNOWN"] = "unknown";
10239
- SessionStatus2["PENDING"] = "pending";
10240
- SessionStatus2["RUNNING"] = "running";
10241
- SessionStatus2["STOPPED"] = "stopped";
10242
- SessionStatus2["FAILED"] = "failed";
10243
- SessionStatus2["ERROR"] = "error";
10244
- SessionStatus2["PIPELINE_CREATING"] = "pipeline_creating";
10245
- SessionStatus2["PIPELINE_OK"] = "pipeline_ok";
10246
- SessionStatus2["PIPELINE_CHECKING"] = "pipeline_checking";
10247
- SessionStatus2["PIPELINE_ERROR"] = "pipeline_error";
10248
- SessionStatus2["UPGRADING"] = "upgrading";
10249
- return SessionStatus2;
10250
- })(SessionStatus || {});
10251
- var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
10252
10614
  var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10253
10615
  constructor(options) {
10254
10616
  super(options);
@@ -10411,6 +10773,114 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10411
10773
  return Promise.reject("unknown source type");
10412
10774
  }
10413
10775
  }
10776
+ async uploadGroup(paths, params = {}) {
10777
+ if (paths.length === 0) {
10778
+ throw new Error("upload group requires at least one path");
10779
+ }
10780
+ if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10781
+ throw new Error("endpoint not connected, use connect()");
10782
+ }
10783
+ await this._limit.acquire();
10784
+ try {
10785
+ this.updateState();
10786
+ const sources = await Promise.all(
10787
+ paths.map(async (path2) => {
10788
+ let streamSource;
10789
+ if (this._options.platformSupport?.resolvePath) {
10790
+ streamSource = await this._options.platformSupport.resolvePath({ path: path2 }, this._logger);
10791
+ } else {
10792
+ streamSource = await resolvePath({ path: path2 }, this._logger);
10793
+ }
10794
+ return { stream: streamSource.stream, mimeType: streamSource.mimeType };
10795
+ })
10796
+ );
10797
+ const job = new UploadGroupJob(
10798
+ sources,
10799
+ params,
10800
+ async () => this.session(),
10801
+ this._client,
10802
+ this._requestLogger
10803
+ );
10804
+ return job.start(
10805
+ () => {
10806
+ this.jobDone(job);
10807
+ },
10808
+ (statusCode) => {
10809
+ this.jobStatus(job, statusCode);
10810
+ }
10811
+ );
10812
+ } catch (e) {
10813
+ this._limit.release();
10814
+ throw e;
10815
+ }
10816
+ }
10817
+ async uploadStreamGroup(streams, mimeTypes, params = {}) {
10818
+ if (streams.length === 0) {
10819
+ throw new Error("upload stream group requires at least one stream");
10820
+ }
10821
+ if (mimeTypes !== void 0 && mimeTypes.length !== streams.length) {
10822
+ throw new Error("mimeTypes must have the same length as streams");
10823
+ }
10824
+ if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10825
+ throw new Error("endpoint not connected, use connect()");
10826
+ }
10827
+ await this._limit.acquire();
10828
+ try {
10829
+ this.updateState();
10830
+ const sources = streams.map((stream, i) => ({
10831
+ stream,
10832
+ mimeType: mimeTypes?.[i] ?? null
10833
+ }));
10834
+ const job = new UploadGroupJob(
10835
+ sources,
10836
+ params,
10837
+ async () => this.session(),
10838
+ this._client,
10839
+ this._requestLogger
10840
+ );
10841
+ return job.start(
10842
+ () => {
10843
+ this.jobDone(job);
10844
+ },
10845
+ (statusCode) => {
10846
+ this.jobStatus(job, statusCode);
10847
+ }
10848
+ );
10849
+ } catch (e) {
10850
+ this._limit.release();
10851
+ throw e;
10852
+ }
10853
+ }
10854
+ async loadFromGroup(urls, params = {}) {
10855
+ if (urls.length === 0) {
10856
+ throw new Error("load from group requires at least one url");
10857
+ }
10858
+ if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10859
+ throw new Error("endpoint not connected, use connect()");
10860
+ }
10861
+ await this._limit.acquire();
10862
+ try {
10863
+ this.updateState();
10864
+ const job = new LoadFromGroupJob(
10865
+ urls,
10866
+ params,
10867
+ async () => this.session(),
10868
+ this._client,
10869
+ this._requestLogger
10870
+ );
10871
+ return job.start(
10872
+ () => {
10873
+ this.jobDone(job);
10874
+ },
10875
+ (statusCode) => {
10876
+ this.jobStatus(job, statusCode);
10877
+ }
10878
+ );
10879
+ } catch (e) {
10880
+ this._limit.release();
10881
+ throw e;
10882
+ }
10883
+ }
10414
10884
  async uploadFile(source, params) {
10415
10885
  await this.ensureTransientPipelineStarted();
10416
10886
  if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
@@ -10731,161 +11201,38 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10731
11201
  const seconds = this.options().sessionReadyTimeoutSeconds;
10732
11202
  return typeof seconds == "number" && Number.isFinite(seconds) && seconds > 0 ? seconds * 1e3 : _WorkerEndpoint.WAIT_FOR_IS_READY;
10733
11203
  }
10734
- sessionHealthReady(body, contentType) {
10735
- if (contentType.includes("application/json")) {
10736
- const health = JSON.parse(body);
10737
- if (health && typeof health == "object") {
10738
- if (health.message && !health.session_status) {
10739
- return true;
10740
- }
10741
- return health.session_status == "running" /* RUNNING */;
10742
- }
10743
- }
10744
- return contentType.includes("text/plain") && body.includes("I'm fine");
10745
- }
10746
11204
  async getComputeSession() {
10747
11205
  if (!this._client) {
10748
11206
  throw new Error("endpoint not initialized");
10749
11207
  }
10750
- let session;
10751
- const sessionUuid = this.options().sessionUuid;
10752
- if (sessionUuid) {
10753
- session = await this.getPermanentComputeSession(this._client, sessionUuid);
10754
- } else {
10755
- session = await this.getOnDemandComputeSession(this._client);
10756
- }
10757
- this.setComputeSessionAccessToken(session);
10758
- const headers = {
10759
- Authorization: await this.authorizationHeader()
10760
- };
10761
- let isReady = false;
10762
- const readyTimeoutMs = this.sessionReadyTimeoutMs();
10763
- const deadline = Date.now() + readyTimeoutMs;
10764
- while (!isReady) {
10765
- const health_url = `${session.session_endpoint}/health`;
10766
- const response = await this._client.fetch(health_url, {
10767
- headers: {
10768
- ...headers,
10769
- Accept: "application/json"
10770
- }
10771
- });
10772
- if (response.status == 200) {
10773
- const contentType = response.headers.get("content-type") || "";
10774
- const body = await response.text();
10775
- try {
10776
- isReady = this.sessionHealthReady(body, contentType);
10777
- } catch (error) {
10778
- this._logger.debug(`session ${session.session_uuid} health check returned invalid JSON, wait and poll for update`);
10779
- }
10780
- if (!isReady && Date.now() > deadline) {
10781
- throw new Error(`Created session ${session.session_uuid} did not become healthy after ${readyTimeoutMs / 1e3} seconds`);
10782
- }
10783
- if (!isReady) {
10784
- this._logger.debug(`session ${session.session_uuid} health check not ready, wait and poll for update`);
10785
- await sleep(_WorkerEndpoint.CHECK_FOR_IS_READY_INTERVAL);
10786
- }
10787
- } else if (Date.now() > deadline) {
10788
- throw new Error(`Created session ${session.session_uuid} did not become healthy after ${readyTimeoutMs / 1e3} seconds`);
10789
- } else {
10790
- this._logger.debug(`session ${session.session_uuid} health check status ${response.status}, wait and poll for update`);
10791
- await sleep(_WorkerEndpoint.CHECK_FOR_IS_READY_INTERVAL);
10792
- }
11208
+ let resolved;
11209
+ try {
11210
+ resolved = await new ComputeSessionClient({
11211
+ computeUrl: this.eyepopUrl(),
11212
+ httpClient: this._client,
11213
+ authorizationHeader: () => this.computeAuthorizationHeader(),
11214
+ sessionUuid: this.options().sessionUuid,
11215
+ sessionName: this.options().sessionName,
11216
+ pipelineImage: this.options().pipelineImage,
11217
+ pipelineVersion: this.options().pipelineVersion,
11218
+ pop: this.options().pop,
11219
+ readyTimeoutMs: this.sessionReadyTimeoutMs(),
11220
+ logger: this._logger,
11221
+ requestLogger: this._requestLogger
11222
+ }).resolve();
11223
+ } catch (error) {
11224
+ this.updateState("Error" /* Error */);
11225
+ throw error;
10793
11226
  }
10794
- this._baseUrl = session.session_endpoint;
11227
+ this._sessionAccessToken = resolved.accessToken;
11228
+ this._sessionAccessTokenValidUntil = resolved.accessTokenValidUntil;
11229
+ this._baseUrl = resolved.session.session_endpoint;
10795
11230
  this._pipeline = null;
10796
- this._pipelineId = this.pipelineIdFromSession(session);
11231
+ this._pipelineId = resolved.pipelineId;
10797
11232
  if (this.options().pop) {
10798
11233
  this.setTransientPipelinePop();
10799
11234
  }
10800
11235
  }
10801
- async getOnDemandComputeSession(httpClient) {
10802
- let session = null;
10803
- const sessionsUrl = `${this.eyepopUrl()}/v1/sessions`;
10804
- const headers = {
10805
- Authorization: await this.computeAuthorizationHeader()
10806
- };
10807
- let sessions;
10808
- if (!this.options().pop) {
10809
- this._requestLogger.debug("fetching sessions from: %s", sessionsUrl);
10810
- const response = await httpClient.fetch(sessionsUrl, {
10811
- headers
10812
- });
10813
- if (response.status == 404) {
10814
- sessions = [];
10815
- } else if (response.status != 200) {
10816
- this.updateState("Error" /* Error */);
10817
- const message = await response.text();
10818
- throw new Error(`Unexpected status ${response.status} fetching compute sessions: ${message}`);
10819
- } else {
10820
- const response_content = await response.json();
10821
- sessions = response_content;
10822
- }
10823
- if (sessions.length > 0) {
10824
- this._logger.debug(`User has ${sessions.length} sessions, inspecting for usable session`);
10825
- for (let s of sessions) {
10826
- if (!_WorkerEndpoint.SESSION_DEAD.has(s.session_status) && !s.persistent) {
10827
- session = s;
10828
- this._logger.debug(`Use active session ${s.session_uuid} with pipeline ${this.pipelineIdFromSession(s)}`);
10829
- break;
10830
- }
10831
- }
10832
- }
10833
- }
10834
- if (!session) {
10835
- this._requestLogger.debug("creating a new session at: %s", sessionsUrl);
10836
- const createBody = this.sessionCreateBody();
10837
- const createHeaders = {
10838
- ...headers,
10839
- ...createBody ? { "Content-Type": "application/json" } : {}
10840
- };
10841
- const request = {
10842
- headers: createHeaders,
10843
- method: "POST"
10844
- };
10845
- if (createBody) {
10846
- request.body = createBody;
10847
- }
10848
- const response = await httpClient.fetch(`${sessionsUrl}?wait=true`, request);
10849
- if (response.status != 200) {
10850
- this.updateState("Error" /* Error */);
10851
- const message = await response.text();
10852
- throw new Error(`Unexpected status ${response.status} creating a compute session: ${message}`);
10853
- } else {
10854
- sessions = await response.json();
10855
- }
10856
- if (sessions.length == 0) {
10857
- throw new Error("Unexpected, no compute session after attempt to create one");
10858
- }
10859
- session = sessions[0] || null;
10860
- }
10861
- if (!session) {
10862
- throw new Error("unexpected undefined session");
10863
- }
10864
- return session;
10865
- }
10866
- async getPermanentComputeSession(httpClient, sessionUuid) {
10867
- let session = null;
10868
- const sessionUrl = `${this.eyepopUrl()}/v1/sessions/${sessionUuid}`;
10869
- this._requestLogger.debug("fetching session from: %s", sessionUrl);
10870
- const headers = {
10871
- Authorization: await this.computeAuthorizationHeader()
10872
- };
10873
- let response = await httpClient.fetch(sessionUrl, {
10874
- headers
10875
- });
10876
- if (response.status == 404) {
10877
- this.updateState("Error" /* Error */);
10878
- throw new Error(`Unexpected status ${response.status} compute sessions ${sessionUuid} not found`);
10879
- } else if (response.status != 200) {
10880
- this.updateState("Error" /* Error */);
10881
- const message = await response.text();
10882
- throw new Error(`Unexpected status ${response.status} fetching compute sessions: ${message}`);
10883
- } else {
10884
- const response_content = await response.json();
10885
- session = response_content;
10886
- }
10887
- return session;
10888
- }
10889
11236
  async startTransientPipeline() {
10890
11237
  const client = this._client;
10891
11238
  const baseUrl = this._baseUrl;
@@ -10942,6 +11289,13 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10942
11289
  if (this._pipelineId) {
10943
11290
  return;
10944
11291
  }
11292
+ if (this.options().pop) {
11293
+ await this.reconnect();
11294
+ if (!this._pipelineId) {
11295
+ throw new Error("Compute session did not provide a pipeline for the requested pop");
11296
+ }
11297
+ return;
11298
+ }
10945
11299
  if (!this._pipelineCreatePromise) {
10946
11300
  this._pipelineCreatePromise = this.startTransientPipeline();
10947
11301
  }
@@ -10957,7 +11311,9 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10957
11311
  throw new Error("endpoint not initialized");
10958
11312
  }
10959
11313
  const previousPop = this.options().pop;
10960
- const previousPipeline = this._pipeline;
11314
+ const previousBaseUrl = this._baseUrl;
11315
+ const previousSessionAccessToken = this._sessionAccessToken;
11316
+ const previousSessionAccessTokenValidUntil = this._sessionAccessTokenValidUntil;
10961
11317
  if (this._pipelineId) {
10962
11318
  await this.deleteTransientPipeline(this._pipelineId);
10963
11319
  this._pipelineId = null;
@@ -10965,10 +11321,14 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10965
11321
  this._pipeline = null;
10966
11322
  try {
10967
11323
  this.options().pop = pop;
10968
- await this.ensureTransientPipelineStarted();
11324
+ await this.getComputeSession();
10969
11325
  } catch (error) {
10970
11326
  this.options().pop = previousPop;
10971
- this._pipeline = previousPipeline;
11327
+ this._pipeline = null;
11328
+ this._pipelineId = null;
11329
+ this._baseUrl = previousPop ? null : previousBaseUrl;
11330
+ this._sessionAccessToken = previousSessionAccessToken;
11331
+ this._sessionAccessTokenValidUntil = previousSessionAccessTokenValidUntil;
10972
11332
  throw error;
10973
11333
  }
10974
11334
  if (!this._pipelineId) {
@@ -10993,13 +11353,6 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10993
11353
  this._logger.info(`stopping pipeline failed, status ${response.status}: ${message}`);
10994
11354
  }
10995
11355
  }
10996
- pipelineIdFromSession(session) {
10997
- if (session.pipeline_uuid) {
10998
- return session.pipeline_uuid;
10999
- }
11000
- const pipeline = session.pipelines?.[0];
11001
- return pipeline?.id || pipeline?.pipeline_id || null;
11002
- }
11003
11356
  async authorizationHeader() {
11004
11357
  if (this._sessionAccessToken) {
11005
11358
  return `Bearer ${this._sessionAccessToken}`;
@@ -11015,20 +11368,6 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
11015
11368
  }
11016
11369
  return super.authorizationHeader();
11017
11370
  }
11018
- setComputeSessionAccessToken(session) {
11019
- if (!session.access_token) {
11020
- throw new Error(`Compute session ${session.session_uuid} did not provide an access token`);
11021
- }
11022
- this._sessionAccessToken = session.access_token;
11023
- if (session.access_token_expires_at) {
11024
- const expiresAt = Date.parse(session.access_token_expires_at);
11025
- this._sessionAccessTokenValidUntil = Number.isNaN(expiresAt) ? null : expiresAt;
11026
- } else if (session.access_token_expires_in) {
11027
- this._sessionAccessTokenValidUntil = Date.now() + session.access_token_expires_in * 1e3;
11028
- } else {
11029
- this._sessionAccessTokenValidUntil = null;
11030
- }
11031
- }
11032
11371
  setTransientPipelinePop() {
11033
11372
  const pop = this.options().pop;
11034
11373
  if (pop && this._pipelineId) {
@@ -11040,26 +11379,8 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
11040
11379
  };
11041
11380
  }
11042
11381
  }
11043
- sessionCreateBody() {
11044
- const body = {};
11045
- if (this.options().sessionName) {
11046
- body.session_name = this.options().sessionName;
11047
- }
11048
- if (this.options().pipelineImage) {
11049
- body.pipeline_image = this.options().pipelineImage;
11050
- }
11051
- if (this.options().pipelineVersion) {
11052
- body.pipeline_version = this.options().pipelineVersion;
11053
- }
11054
- if (this.options().pop) {
11055
- body.pop = this.options().pop;
11056
- }
11057
- return Object.keys(body).length > 0 ? JSON.stringify(body) : void 0;
11058
- }
11059
11382
  };
11060
- _WorkerEndpoint.SESSION_DEAD = /* @__PURE__ */ new Set(["error" /* ERROR */, "failed" /* FAILED */, "stopped" /* STOPPED */, "pipeline_error" /* PIPELINE_ERROR */, "unknown" /* UNKNOWN */]);
11061
11383
  _WorkerEndpoint.WAIT_FOR_IS_READY = 60 * 1e3;
11062
- _WorkerEndpoint.CHECK_FOR_IS_READY_INTERVAL = 250;
11063
11384
  var WorkerEndpoint = _WorkerEndpoint;
11064
11385
 
11065
11386
  // shims/websockets.ts
@@ -12551,6 +12872,7 @@ var index_default = EyePop;
12551
12872
  ForwardOperatorType,
12552
12873
  InferenceType,
12553
12874
  LoadFromAssetUuidJob,
12875
+ LoadFromGroupJob,
12554
12876
  LoadFromJob,
12555
12877
  LoadMediaStreamJob,
12556
12878
  ModelExportStatus,
@@ -12564,12 +12886,12 @@ var index_default = EyePop;
12564
12886
  PopComponentType,
12565
12887
  PredictionVersion,
12566
12888
  Semaphore,
12567
- SessionStatus,
12568
12889
  Stream,
12569
12890
  TaskMode,
12570
12891
  TaskType,
12571
12892
  TranscodeMode,
12572
12893
  TransientPopId,
12894
+ UploadGroupJob,
12573
12895
  UploadJob,
12574
12896
  UserReview,
12575
12897
  VideoMode,
@@ -12579,7 +12901,9 @@ var index_default = EyePop;
12579
12901
  WebrtcWhip,
12580
12902
  WorkerEndpoint,
12581
12903
  WorkflowPhase,
12904
+ encodeMultipartFormData,
12582
12905
  getBaseUrl,
12906
+ randomMultipartBoundary,
12583
12907
  readableStreamFromString
12584
12908
  });
12585
12909
  /*! Bundled license information: