@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.
@@ -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,290 @@ __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
+ function errorInfoMessage(errorInfo) {
8770
+ return [errorInfo.code, errorInfo.message, errorInfo.details].filter((part) => typeof part == "string" && part.length > 0).join(": ");
8771
+ }
8772
+ function pipelineErrorMessage(session) {
8773
+ if (session.pipeline_error) {
8774
+ const message = errorInfoMessage(session.pipeline_error);
8775
+ return message ? `Pipeline failed for compute session ${session.session_uuid}: ${message}` : `Pipeline failed for compute session ${session.session_uuid}`;
8776
+ }
8777
+ if (session.session_status == "pipeline_error" /* PIPELINE_ERROR */) {
8778
+ return `Pipeline failed for compute session ${session.session_uuid}`;
8779
+ }
8780
+ return null;
8781
+ }
8782
+ async function responseErrorMessage(response) {
8783
+ const contentType = response.headers.get("content-type") || "";
8784
+ if (contentType.includes("application/json")) {
8785
+ try {
8786
+ const parsed = normalizedJsonValue(await response.clone().json());
8787
+ const message2 = normalizedErrorMessage(parsed);
8788
+ if (message2) {
8789
+ return message2;
8790
+ }
8791
+ } catch (_) {
8792
+ }
8793
+ }
8794
+ const body = await response.text();
8795
+ const message = normalizedErrorMessage(body);
8796
+ return message || body;
8797
+ }
8798
+ function normalizedJsonValue(value) {
8799
+ if (typeof value == "string") {
8800
+ try {
8801
+ const parsed = JSON.parse(value);
8802
+ return normalizedJsonValue(parsed);
8803
+ } catch (_) {
8804
+ const unescaped = value.replace(/\\"/g, '"');
8805
+ if (unescaped != value) {
8806
+ try {
8807
+ const parsed = JSON.parse(unescaped);
8808
+ return normalizedJsonValue(parsed);
8809
+ } catch (_2) {
8810
+ }
8811
+ }
8812
+ return value;
8813
+ }
8814
+ }
8815
+ return value;
8816
+ }
8817
+ async function responseJson(response) {
8818
+ return normalizedJsonValue(await response.json());
8819
+ }
8820
+ function normalizedErrorMessage(value) {
8821
+ value = normalizedJsonValue(value);
8822
+ if (typeof value == "string") {
8823
+ return value;
8824
+ }
8825
+ if (!value || typeof value != "object") {
8826
+ return null;
8827
+ }
8828
+ const record = value;
8829
+ const normalizedError = normalizedJsonValue(record["error"]);
8830
+ const errorMessage = normalizedErrorMessage(normalizedError) || (typeof normalizedError == "string" ? normalizedError : null);
8831
+ const message = [record["code"], errorMessage, record["message"], record["details"]].filter((part) => typeof part == "string" && part.length > 0).join(": ");
8832
+ if (message) {
8833
+ return message;
8834
+ }
8835
+ if (record["pipeline_error"]) {
8836
+ return normalizedErrorMessage(record["pipeline_error"]);
8837
+ }
8838
+ if (normalizedError && typeof normalizedError == "object") {
8839
+ return normalizedErrorMessage(normalizedError);
8840
+ }
8841
+ try {
8842
+ return JSON.stringify(value);
8843
+ } catch (_) {
8844
+ return null;
8845
+ }
8846
+ }
8847
+ var ComputeSessionClient = class {
8848
+ constructor(options) {
8849
+ this.options = options;
8850
+ }
8851
+ async resolve() {
8852
+ const session = this.options.sessionUuid ? await this.getPermanentComputeSession(this.options.sessionUuid) : await this.getOnDemandComputeSession();
8853
+ const pipelineFailure = pipelineErrorMessage(session);
8854
+ if (pipelineFailure) {
8855
+ throw new Error(pipelineFailure);
8856
+ }
8857
+ const accessToken = this.sessionAccessToken(session);
8858
+ await this.waitUntilReady(session, accessToken);
8859
+ const pipelineId = pipelineIdFromSession(session);
8860
+ if (this.options.pop && !pipelineId) {
8861
+ throw new Error("Compute session did not provide a pipeline for the requested pop");
8862
+ }
8863
+ return {
8864
+ session,
8865
+ pipelineId,
8866
+ accessToken,
8867
+ accessTokenValidUntil: accessTokenValidUntil(session)
8868
+ };
8869
+ }
8870
+ async getOnDemandComputeSession() {
8871
+ let session = null;
8872
+ const sessionsUrl = `${this.options.computeUrl}/v1/sessions`;
8873
+ const headers = {
8874
+ Authorization: await this.options.authorizationHeader()
8875
+ };
8876
+ let sessions;
8877
+ if (!this.options.pop) {
8878
+ this.options.requestLogger?.debug("fetching sessions from: %s", sessionsUrl);
8879
+ const response = await this.options.httpClient.fetch(sessionsUrl, {
8880
+ headers
8881
+ });
8882
+ if (response.status == 404) {
8883
+ sessions = [];
8884
+ } else if (response.status != 200) {
8885
+ const message = await responseErrorMessage(response);
8886
+ throw new Error(`Unexpected status ${response.status} fetching compute sessions: ${message}`);
8887
+ } else {
8888
+ const response_content = await responseJson(response);
8889
+ sessions = response_content;
8890
+ }
8891
+ if (sessions.length > 0) {
8892
+ this.options.logger?.debug(`User has ${sessions.length} sessions, inspecting for usable session`);
8893
+ for (let s of sessions) {
8894
+ if (!SESSION_DEAD.has(s.session_status) && !s.persistent && (!this.options.sessionName || s.session_name === this.options.sessionName)) {
8895
+ session = s;
8896
+ this.options.logger?.debug(`Use active session ${s.session_uuid} with pipeline ${pipelineIdFromSession(s)}`);
8897
+ break;
8898
+ }
8899
+ }
8900
+ }
8901
+ }
8902
+ if (!session) {
8903
+ this.options.requestLogger?.debug("creating a new session at: %s", sessionsUrl);
8904
+ const createBody = sessionCreateBody(this.options);
8905
+ const createHeaders = {
8906
+ ...headers,
8907
+ ...createBody ? { "Content-Type": "application/json" } : {}
8908
+ };
8909
+ const request = {
8910
+ headers: createHeaders,
8911
+ method: "POST"
8912
+ };
8913
+ if (createBody) {
8914
+ request.body = createBody;
8915
+ }
8916
+ const query = "wait=true";
8917
+ const response = await this.options.httpClient.fetch(`${sessionsUrl}?${query}`, request);
8918
+ if (response.status != 200) {
8919
+ const message = await responseErrorMessage(response);
8920
+ throw new Error(`Unexpected status ${response.status} creating a compute session: ${message}`);
8921
+ } else {
8922
+ sessions = await responseJson(response);
8923
+ }
8924
+ if (sessions.length == 0) {
8925
+ throw new Error("Unexpected, no compute session after attempt to create one");
8926
+ }
8927
+ session = sessions[0] || null;
8928
+ }
8929
+ if (!session) {
8930
+ throw new Error("unexpected undefined session");
8931
+ }
8932
+ return session;
8933
+ }
8934
+ async getPermanentComputeSession(sessionUuid) {
8935
+ let session = null;
8936
+ const sessionUrl = `${this.options.computeUrl}/v1/sessions/${sessionUuid}`;
8937
+ this.options.requestLogger?.debug("fetching session from: %s", sessionUrl);
8938
+ const headers = {
8939
+ Authorization: await this.options.authorizationHeader()
8940
+ };
8941
+ let response = await this.options.httpClient.fetch(sessionUrl, {
8942
+ headers
8943
+ });
8944
+ if (response.status == 404) {
8945
+ throw new Error(`Unexpected status ${response.status} compute sessions ${sessionUuid} not found`);
8946
+ } else if (response.status != 200) {
8947
+ const message = await responseErrorMessage(response);
8948
+ throw new Error(`Unexpected status ${response.status} fetching compute sessions: ${message}`);
8949
+ } else {
8950
+ const response_content = await responseJson(response);
8951
+ session = response_content;
8952
+ }
8953
+ return session;
8954
+ }
8955
+ sessionAccessToken(session) {
8956
+ if (!session.access_token) {
8957
+ throw new Error(`Compute session ${session.session_uuid} did not provide an access token`);
8958
+ }
8959
+ return session.access_token;
8960
+ }
8961
+ async waitUntilReady(session, accessToken) {
8962
+ let isReady = false;
8963
+ const deadline = Date.now() + this.options.readyTimeoutMs;
8964
+ while (!isReady) {
8965
+ const health_url = `${session.session_endpoint}/health`;
8966
+ const response = await this.options.httpClient.fetch(health_url, {
8967
+ headers: {
8968
+ Authorization: `Bearer ${accessToken}`,
8969
+ Accept: "application/json"
8970
+ }
8971
+ });
8972
+ if (response.status == 200) {
8973
+ const contentType = response.headers.get("content-type") || "";
8974
+ const body = await response.text();
8975
+ try {
8976
+ isReady = sessionHealthReady(body, contentType);
8977
+ } catch (error) {
8978
+ this.options.logger?.debug(`session ${session.session_uuid} health check returned invalid JSON, wait and poll for update`);
8979
+ }
8980
+ if (!isReady && Date.now() > deadline) {
8981
+ throw new Error(`Created session ${session.session_uuid} did not become healthy after ${this.options.readyTimeoutMs / 1e3} seconds`);
8982
+ }
8983
+ if (!isReady) {
8984
+ this.options.logger?.debug(`session ${session.session_uuid} health check not ready, wait and poll for update`);
8985
+ await sleep(CHECK_FOR_IS_READY_INTERVAL);
8986
+ }
8987
+ } else if (Date.now() > deadline) {
8988
+ throw new Error(`Created session ${session.session_uuid} did not become healthy after ${this.options.readyTimeoutMs / 1e3} seconds`);
8989
+ } else {
8990
+ this.options.logger?.debug(`session ${session.session_uuid} health check status ${response.status}, wait and poll for update`);
8991
+ await sleep(CHECK_FOR_IS_READY_INTERVAL);
8992
+ }
8993
+ }
8994
+ }
8995
+ };
8996
+
8717
8997
  // types.ts
8718
8998
  var EndpointState = /* @__PURE__ */ ((EndpointState2) => {
8719
8999
  EndpointState2["Idle"] = "Idle";
@@ -8726,6 +9006,7 @@ var EndpointState = /* @__PURE__ */ ((EndpointState2) => {
8726
9006
  })(EndpointState || {});
8727
9007
 
8728
9008
  // streaming.ts
9009
+ var import_uuid = require("uuid");
8729
9010
  var Stream = class _Stream {
8730
9011
  constructor(iterator, controller) {
8731
9012
  this.iterator = iterator;
@@ -8906,6 +9187,76 @@ function readableStreamFromString(s) {
8906
9187
  }
8907
9188
  });
8908
9189
  }
9190
+ function randomMultipartBoundary() {
9191
+ return `----formdata-boundary-${(0, import_uuid.v4)()}`;
9192
+ }
9193
+ function toPartStream(content) {
9194
+ if (content instanceof ReadableStream) {
9195
+ return content;
9196
+ }
9197
+ if (content instanceof Blob) {
9198
+ return content.stream();
9199
+ }
9200
+ const bytes = ArrayBuffer.isView(content) ? new Uint8Array(content.buffer, content.byteOffset, content.byteLength) : new Uint8Array(content);
9201
+ return new ReadableStream({
9202
+ start(controller) {
9203
+ controller.enqueue(bytes);
9204
+ controller.close();
9205
+ }
9206
+ });
9207
+ }
9208
+ async function cancelMultipartContent(content, reason) {
9209
+ if (content instanceof ReadableStream) {
9210
+ await content.cancel(reason);
9211
+ }
9212
+ }
9213
+ function encodeMultipartFormData(parts, boundary) {
9214
+ const encoder = new TextEncoder();
9215
+ let partIndex = 0;
9216
+ let reader = null;
9217
+ return new ReadableStream({
9218
+ async pull(controller) {
9219
+ if (reader) {
9220
+ const { done, value } = await reader.read();
9221
+ if (!done) {
9222
+ controller.enqueue(value);
9223
+ return;
9224
+ }
9225
+ reader = null;
9226
+ controller.enqueue(encoder.encode("\r\n"));
9227
+ partIndex++;
9228
+ }
9229
+ if (partIndex >= parts.length) {
9230
+ controller.enqueue(encoder.encode(`--${boundary}--\r
9231
+ `));
9232
+ controller.close();
9233
+ return;
9234
+ }
9235
+ const part = parts[partIndex];
9236
+ let header = `--${boundary}\r
9237
+ Content-Disposition: form-data; name="${part.name}"\r
9238
+ `;
9239
+ if (part.contentType) {
9240
+ header += `Content-Type: ${part.contentType}\r
9241
+ `;
9242
+ }
9243
+ header += "\r\n";
9244
+ controller.enqueue(encoder.encode(header));
9245
+ if (typeof part.content === "string") {
9246
+ controller.enqueue(encoder.encode(part.content));
9247
+ controller.enqueue(encoder.encode("\r\n"));
9248
+ partIndex++;
9249
+ } else {
9250
+ reader = toPartStream(part.content).getReader();
9251
+ }
9252
+ },
9253
+ async cancel(reason) {
9254
+ await reader?.cancel(reason);
9255
+ const firstUnstartedPart = reader ? partIndex + 1 : partIndex;
9256
+ await Promise.all(parts.slice(firstUnstartedPart).map((part) => cancelMultipartContent(part.content, reason)));
9257
+ }
9258
+ });
9259
+ }
8909
9260
 
8910
9261
  // worker/worker_types.ts
8911
9262
  var PredictionVersion = /* @__PURE__ */ ((PredictionVersion3) => {
@@ -8963,7 +9314,7 @@ var ContourType = /* @__PURE__ */ ((ContourType2) => {
8963
9314
  })(ContourType || {});
8964
9315
 
8965
9316
  // worker/webrtc_whip.ts
8966
- var import_uuid = require("uuid");
9317
+ var import_uuid2 = require("uuid");
8967
9318
 
8968
9319
  // worker/webrtc_base.ts
8969
9320
  var import_path = __toESM(require("path"));
@@ -9238,7 +9589,7 @@ var WebrtcBase = class _WebrtcBase {
9238
9589
  // worker/webrtc_whip.ts
9239
9590
  var WebrtcWhip = class extends WebrtcBase {
9240
9591
  constructor(stream, getSession, client, is_pipeline_direct, requestLogger) {
9241
- super(getSession, client, (0, import_uuid.v4)().toString(), "liveIngress/whip", is_pipeline_direct, requestLogger);
9592
+ super(getSession, client, (0, import_uuid2.v4)().toString(), "liveIngress/whip", is_pipeline_direct, requestLogger);
9242
9593
  this._stream = stream;
9243
9594
  }
9244
9595
  async start() {
@@ -9412,14 +9763,18 @@ var UploadJob = class extends AbstractJob {
9412
9763
  get [Symbol.toStringTag]() {
9413
9764
  return "uploadJob";
9414
9765
  }
9415
- constructor(stream, mimeType, size, videoMode, params, getSession, client, requestLogger) {
9766
+ constructor(stream, mimeType, size, videoMode, params, getSession, client, requestLogger, uploadBodyFactory) {
9416
9767
  super(params, getSession, client, requestLogger);
9417
9768
  this._uploadStream = stream;
9769
+ this._uploadBodyFactory = uploadBodyFactory;
9418
9770
  this._mimeType = mimeType;
9419
9771
  this._size = size;
9420
9772
  this._videoMode = videoMode;
9421
9773
  this._needsFullDuplex = !client.isFullDuplex() && mimeType.startsWith("video/") && videoMode == "stream" /* STREAM */;
9422
9774
  }
9775
+ async uploadBody() {
9776
+ return this._uploadBodyFactory ? await this._uploadBodyFactory() : this._uploadStream;
9777
+ }
9423
9778
  async onEvent(event) {
9424
9779
  if (event.type == "prepared") {
9425
9780
  if (!event.source_id) {
@@ -9461,7 +9816,7 @@ var UploadJob = class extends AbstractJob {
9461
9816
  const blob = new Blob([JSON.stringify(this._params.fps)], { type: "application/json" });
9462
9817
  formData.append("fps", blob);
9463
9818
  }
9464
- const fileBlob = await new Response(this._uploadStream).blob();
9819
+ const fileBlob = await new Response(await this.uploadBody()).blob();
9465
9820
  formData.append("file", fileBlob);
9466
9821
  const headers = {
9467
9822
  ...session.authenticationHeaders(),
@@ -9488,7 +9843,7 @@ var UploadJob = class extends AbstractJob {
9488
9843
  request = this._client.fetch(postUrl, {
9489
9844
  headers,
9490
9845
  method: "POST",
9491
- body: this._uploadStream,
9846
+ body: await this.uploadBody(),
9492
9847
  signal: this._controller.signal,
9493
9848
  // @ts-ignore
9494
9849
  duplex: "half",
@@ -9556,7 +9911,7 @@ var UploadJob = class extends AbstractJob {
9556
9911
  const blob = new Blob([JSON.stringify(this._params.fps)], { type: "application/json" });
9557
9912
  formData.append("fps", blob);
9558
9913
  }
9559
- const fileBlob = await new Response(this._uploadStream).blob();
9914
+ const fileBlob = await new Response(await this.uploadBody()).blob();
9560
9915
  formData.append("file", fileBlob);
9561
9916
  const headers = {
9562
9917
  ...session.authenticationHeaders(),
@@ -9583,7 +9938,7 @@ var UploadJob = class extends AbstractJob {
9583
9938
  response = await this._client.fetch(postUrl, {
9584
9939
  headers,
9585
9940
  method: "POST",
9586
- body: this._uploadStream,
9941
+ body: await this.uploadBody(),
9587
9942
  signal: this._controller.signal,
9588
9943
  // @ts-ignore
9589
9944
  duplex: "half"
@@ -9633,6 +9988,111 @@ var LoadFromJob = class extends AbstractJob {
9633
9988
  });
9634
9989
  }
9635
9990
  };
9991
+ var UploadGroupJob = class extends AbstractJob {
9992
+ get [Symbol.toStringTag]() {
9993
+ return "uploadGroupJob";
9994
+ }
9995
+ constructor(sources, params, getSession, client, requestLogger) {
9996
+ super(params, getSession, client, requestLogger);
9997
+ if (sources.length === 0) {
9998
+ throw new Error("upload group requires at least one source");
9999
+ }
10000
+ this._sources = sources;
10001
+ }
10002
+ async startJob() {
10003
+ const session = await this._getSession();
10004
+ if (!session.baseUrl) {
10005
+ return Promise.reject(new Error("session.baseUrl must not be null"));
10006
+ }
10007
+ const queryParams = new URLSearchParams({
10008
+ mode: "queue",
10009
+ processing: "sync"
10010
+ });
10011
+ if (this._version !== void 0) {
10012
+ queryParams.set("version", this._version.toString());
10013
+ }
10014
+ if (this._params.motionDetect) {
10015
+ for (const [key, value] of Object.entries(this._params.motionDetect)) {
10016
+ queryParams.set(key, value);
10017
+ }
10018
+ }
10019
+ const postUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?${queryParams.toString()}`;
10020
+ const parts = [];
10021
+ if (this._params.componentParams) {
10022
+ parts.push({ name: "params", contentType: "application/json", content: JSON.stringify(this._params.componentParams) });
10023
+ }
10024
+ if (this._params.roi) {
10025
+ parts.push({ name: "roi", contentType: "application/json", content: JSON.stringify(this._params.roi) });
10026
+ }
10027
+ if (this._params.fps) {
10028
+ parts.push({ name: "fps", contentType: "application/json", content: JSON.stringify(this._params.fps) });
10029
+ }
10030
+ for (const source of this._sources) {
10031
+ parts.push({ name: "file", contentType: source.mimeType, content: source.stream });
10032
+ }
10033
+ const boundary = randomMultipartBoundary();
10034
+ const headers = {
10035
+ ...session.authenticationHeaders(),
10036
+ Accept: "application/jsonl",
10037
+ "Content-Type": `multipart/form-data; boundary=${boundary}`
10038
+ };
10039
+ return await this._client.fetch(postUrl, {
10040
+ headers,
10041
+ method: "POST",
10042
+ body: encodeMultipartFormData(parts, boundary),
10043
+ signal: this._controller.signal,
10044
+ // @ts-ignore
10045
+ duplex: "half",
10046
+ eyepop: { responseStreaming: true }
10047
+ });
10048
+ }
10049
+ };
10050
+ var LoadFromGroupJob = class extends AbstractJob {
10051
+ get [Symbol.toStringTag]() {
10052
+ return "loadFromGroupJob";
10053
+ }
10054
+ constructor(urls, params, getSession, client, requestLogger) {
10055
+ super(params, getSession, client, requestLogger);
10056
+ if (urls.length === 0) {
10057
+ throw new Error("load from group requires at least one url");
10058
+ }
10059
+ this._urls = urls;
10060
+ }
10061
+ async startJob() {
10062
+ const session = await this._getSession();
10063
+ if (!session.baseUrl) {
10064
+ return Promise.reject(new Error("session.baseUrl must not be null"));
10065
+ }
10066
+ const body = {
10067
+ sourceType: "GROUP",
10068
+ sources: this._urls.map((url) => ({ sourceType: "URL", url })),
10069
+ version: this._version
10070
+ };
10071
+ if (this._params.componentParams) {
10072
+ body["params"] = this._params.componentParams;
10073
+ }
10074
+ if (this._params.roi) {
10075
+ body["roi"] = this._params.roi;
10076
+ }
10077
+ if (this._params.motionDetect) {
10078
+ Object.assign(body, this._params.motionDetect);
10079
+ }
10080
+ const headers = {
10081
+ ...session.authenticationHeaders(),
10082
+ Accept: "application/jsonl",
10083
+ "Content-Type": "application/json"
10084
+ };
10085
+ const patchUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=queue&processing=sync`;
10086
+ return await this._client.fetch(patchUrl, {
10087
+ headers,
10088
+ method: "PATCH",
10089
+ body: JSON.stringify(body),
10090
+ signal: this._controller.signal,
10091
+ // @ts-ignore
10092
+ eyepop: { responseStreaming: true }
10093
+ });
10094
+ }
10095
+ };
9636
10096
  var LoadFromAssetUuidJob = class extends AbstractJob {
9637
10097
  constructor(assetUuid, params, getSession, client, requestLogger) {
9638
10098
  super(params, getSession, client, requestLogger);
@@ -10234,21 +10694,6 @@ var TransientPopId = /* @__PURE__ */ ((TransientPopId2) => {
10234
10694
  })(TransientPopId || {});
10235
10695
 
10236
10696
  // 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
10697
  var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10253
10698
  constructor(options) {
10254
10699
  super(options);
@@ -10411,6 +10856,114 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10411
10856
  return Promise.reject("unknown source type");
10412
10857
  }
10413
10858
  }
10859
+ async uploadGroup(paths, params = {}) {
10860
+ if (paths.length === 0) {
10861
+ throw new Error("upload group requires at least one path");
10862
+ }
10863
+ if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10864
+ throw new Error("endpoint not connected, use connect()");
10865
+ }
10866
+ await this._limit.acquire();
10867
+ try {
10868
+ this.updateState();
10869
+ const sources = await Promise.all(
10870
+ paths.map(async (path2) => {
10871
+ let streamSource;
10872
+ if (this._options.platformSupport?.resolvePath) {
10873
+ streamSource = await this._options.platformSupport.resolvePath({ path: path2 }, this._logger);
10874
+ } else {
10875
+ streamSource = await resolvePath({ path: path2 }, this._logger);
10876
+ }
10877
+ return { stream: streamSource.stream, mimeType: streamSource.mimeType };
10878
+ })
10879
+ );
10880
+ const job = new UploadGroupJob(
10881
+ sources,
10882
+ params,
10883
+ async () => this.session(),
10884
+ this._client,
10885
+ this._requestLogger
10886
+ );
10887
+ return job.start(
10888
+ () => {
10889
+ this.jobDone(job);
10890
+ },
10891
+ (statusCode) => {
10892
+ this.jobStatus(job, statusCode);
10893
+ }
10894
+ );
10895
+ } catch (e) {
10896
+ this._limit.release();
10897
+ throw e;
10898
+ }
10899
+ }
10900
+ async uploadStreamGroup(streams, mimeTypes, params = {}) {
10901
+ if (streams.length === 0) {
10902
+ throw new Error("upload stream group requires at least one stream");
10903
+ }
10904
+ if (mimeTypes !== void 0 && mimeTypes.length !== streams.length) {
10905
+ throw new Error("mimeTypes must have the same length as streams");
10906
+ }
10907
+ if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10908
+ throw new Error("endpoint not connected, use connect()");
10909
+ }
10910
+ await this._limit.acquire();
10911
+ try {
10912
+ this.updateState();
10913
+ const sources = streams.map((stream, i) => ({
10914
+ stream,
10915
+ mimeType: mimeTypes?.[i] ?? null
10916
+ }));
10917
+ const job = new UploadGroupJob(
10918
+ sources,
10919
+ params,
10920
+ async () => this.session(),
10921
+ this._client,
10922
+ this._requestLogger
10923
+ );
10924
+ return job.start(
10925
+ () => {
10926
+ this.jobDone(job);
10927
+ },
10928
+ (statusCode) => {
10929
+ this.jobStatus(job, statusCode);
10930
+ }
10931
+ );
10932
+ } catch (e) {
10933
+ this._limit.release();
10934
+ throw e;
10935
+ }
10936
+ }
10937
+ async loadFromGroup(urls, params = {}) {
10938
+ if (urls.length === 0) {
10939
+ throw new Error("load from group requires at least one url");
10940
+ }
10941
+ if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10942
+ throw new Error("endpoint not connected, use connect()");
10943
+ }
10944
+ await this._limit.acquire();
10945
+ try {
10946
+ this.updateState();
10947
+ const job = new LoadFromGroupJob(
10948
+ urls,
10949
+ params,
10950
+ async () => this.session(),
10951
+ this._client,
10952
+ this._requestLogger
10953
+ );
10954
+ return job.start(
10955
+ () => {
10956
+ this.jobDone(job);
10957
+ },
10958
+ (statusCode) => {
10959
+ this.jobStatus(job, statusCode);
10960
+ }
10961
+ );
10962
+ } catch (e) {
10963
+ this._limit.release();
10964
+ throw e;
10965
+ }
10966
+ }
10414
10967
  async uploadFile(source, params) {
10415
10968
  await this.ensureTransientPipelineStarted();
10416
10969
  if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
@@ -10485,12 +11038,21 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10485
11038
  await this._limit.acquire();
10486
11039
  try {
10487
11040
  this.updateState();
10488
- let streamSource;
10489
- if (this._options.platformSupport?.resolvePath) {
10490
- streamSource = await this._options.platformSupport.resolvePath(source, this._logger);
10491
- } else {
10492
- streamSource = await resolvePath(source, this._logger);
10493
- }
11041
+ const resolveStreamSource = async () => {
11042
+ if (this._options.platformSupport?.resolvePath) {
11043
+ return await this._options.platformSupport.resolvePath(source, this._logger);
11044
+ }
11045
+ return await resolvePath(source, this._logger);
11046
+ };
11047
+ const streamSource = await resolveStreamSource();
11048
+ let firstStreamPending = true;
11049
+ const uploadBodyFactory = async () => {
11050
+ if (firstStreamPending) {
11051
+ firstStreamPending = false;
11052
+ return streamSource.stream;
11053
+ }
11054
+ return (await resolveStreamSource()).stream;
11055
+ };
10494
11056
  const job = new UploadJob(
10495
11057
  streamSource.stream,
10496
11058
  streamSource.mimeType,
@@ -10501,7 +11063,8 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10501
11063
  return this.session();
10502
11064
  },
10503
11065
  this._client,
10504
- this._requestLogger
11066
+ this._requestLogger,
11067
+ uploadBodyFactory
10505
11068
  );
10506
11069
  return job.start(
10507
11070
  () => {
@@ -10731,161 +11294,38 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10731
11294
  const seconds = this.options().sessionReadyTimeoutSeconds;
10732
11295
  return typeof seconds == "number" && Number.isFinite(seconds) && seconds > 0 ? seconds * 1e3 : _WorkerEndpoint.WAIT_FOR_IS_READY;
10733
11296
  }
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
11297
  async getComputeSession() {
10747
11298
  if (!this._client) {
10748
11299
  throw new Error("endpoint not initialized");
10749
11300
  }
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
- }
11301
+ let resolved;
11302
+ try {
11303
+ resolved = await new ComputeSessionClient({
11304
+ computeUrl: this.eyepopUrl(),
11305
+ httpClient: this._client,
11306
+ authorizationHeader: () => this.computeAuthorizationHeader(),
11307
+ sessionUuid: this.options().sessionUuid,
11308
+ sessionName: this.options().sessionName,
11309
+ pipelineImage: this.options().pipelineImage,
11310
+ pipelineVersion: this.options().pipelineVersion,
11311
+ pop: this.options().pop,
11312
+ readyTimeoutMs: this.sessionReadyTimeoutMs(),
11313
+ logger: this._logger,
11314
+ requestLogger: this._requestLogger
11315
+ }).resolve();
11316
+ } catch (error) {
11317
+ this.updateState("Error" /* Error */);
11318
+ throw error;
10793
11319
  }
10794
- this._baseUrl = session.session_endpoint;
11320
+ this._sessionAccessToken = resolved.accessToken;
11321
+ this._sessionAccessTokenValidUntil = resolved.accessTokenValidUntil;
11322
+ this._baseUrl = resolved.session.session_endpoint;
10795
11323
  this._pipeline = null;
10796
- this._pipelineId = this.pipelineIdFromSession(session);
11324
+ this._pipelineId = resolved.pipelineId;
10797
11325
  if (this.options().pop) {
10798
11326
  this.setTransientPipelinePop();
10799
11327
  }
10800
11328
  }
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
11329
  async startTransientPipeline() {
10890
11330
  const client = this._client;
10891
11331
  const baseUrl = this._baseUrl;
@@ -10942,6 +11382,13 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10942
11382
  if (this._pipelineId) {
10943
11383
  return;
10944
11384
  }
11385
+ if (this.options().pop) {
11386
+ await this.reconnect();
11387
+ if (!this._pipelineId) {
11388
+ throw new Error("Compute session did not provide a pipeline for the requested pop");
11389
+ }
11390
+ return;
11391
+ }
10945
11392
  if (!this._pipelineCreatePromise) {
10946
11393
  this._pipelineCreatePromise = this.startTransientPipeline();
10947
11394
  }
@@ -10957,7 +11404,9 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10957
11404
  throw new Error("endpoint not initialized");
10958
11405
  }
10959
11406
  const previousPop = this.options().pop;
10960
- const previousPipeline = this._pipeline;
11407
+ const previousBaseUrl = this._baseUrl;
11408
+ const previousSessionAccessToken = this._sessionAccessToken;
11409
+ const previousSessionAccessTokenValidUntil = this._sessionAccessTokenValidUntil;
10961
11410
  if (this._pipelineId) {
10962
11411
  await this.deleteTransientPipeline(this._pipelineId);
10963
11412
  this._pipelineId = null;
@@ -10965,10 +11414,14 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10965
11414
  this._pipeline = null;
10966
11415
  try {
10967
11416
  this.options().pop = pop;
10968
- await this.ensureTransientPipelineStarted();
11417
+ await this.getComputeSession();
10969
11418
  } catch (error) {
10970
11419
  this.options().pop = previousPop;
10971
- this._pipeline = previousPipeline;
11420
+ this._pipeline = null;
11421
+ this._pipelineId = null;
11422
+ this._baseUrl = previousPop ? null : previousBaseUrl;
11423
+ this._sessionAccessToken = previousSessionAccessToken;
11424
+ this._sessionAccessTokenValidUntil = previousSessionAccessTokenValidUntil;
10972
11425
  throw error;
10973
11426
  }
10974
11427
  if (!this._pipelineId) {
@@ -10993,13 +11446,6 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10993
11446
  this._logger.info(`stopping pipeline failed, status ${response.status}: ${message}`);
10994
11447
  }
10995
11448
  }
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
11449
  async authorizationHeader() {
11004
11450
  if (this._sessionAccessToken) {
11005
11451
  return `Bearer ${this._sessionAccessToken}`;
@@ -11015,20 +11461,6 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
11015
11461
  }
11016
11462
  return super.authorizationHeader();
11017
11463
  }
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
11464
  setTransientPipelinePop() {
11033
11465
  const pop = this.options().pop;
11034
11466
  if (pop && this._pipelineId) {
@@ -11040,26 +11472,8 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
11040
11472
  };
11041
11473
  }
11042
11474
  }
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
11475
  };
11060
- _WorkerEndpoint.SESSION_DEAD = /* @__PURE__ */ new Set(["error" /* ERROR */, "failed" /* FAILED */, "stopped" /* STOPPED */, "pipeline_error" /* PIPELINE_ERROR */, "unknown" /* UNKNOWN */]);
11061
11476
  _WorkerEndpoint.WAIT_FOR_IS_READY = 60 * 1e3;
11062
- _WorkerEndpoint.CHECK_FOR_IS_READY_INTERVAL = 250;
11063
11477
  var WorkerEndpoint = _WorkerEndpoint;
11064
11478
 
11065
11479
  // shims/websockets.ts
@@ -12551,6 +12965,7 @@ var index_default = EyePop;
12551
12965
  ForwardOperatorType,
12552
12966
  InferenceType,
12553
12967
  LoadFromAssetUuidJob,
12968
+ LoadFromGroupJob,
12554
12969
  LoadFromJob,
12555
12970
  LoadMediaStreamJob,
12556
12971
  ModelExportStatus,
@@ -12564,12 +12979,12 @@ var index_default = EyePop;
12564
12979
  PopComponentType,
12565
12980
  PredictionVersion,
12566
12981
  Semaphore,
12567
- SessionStatus,
12568
12982
  Stream,
12569
12983
  TaskMode,
12570
12984
  TaskType,
12571
12985
  TranscodeMode,
12572
12986
  TransientPopId,
12987
+ UploadGroupJob,
12573
12988
  UploadJob,
12574
12989
  UserReview,
12575
12990
  VideoMode,
@@ -12579,7 +12994,9 @@ var index_default = EyePop;
12579
12994
  WebrtcWhip,
12580
12995
  WorkerEndpoint,
12581
12996
  WorkflowPhase,
12997
+ encodeMultipartFormData,
12582
12998
  getBaseUrl,
12999
+ randomMultipartBoundary,
12583
13000
  readableStreamFromString
12584
13001
  });
12585
13002
  /*! Bundled license information: