@eyepop.ai/eyepop 3.22.0 → 3.25.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.
@@ -8666,6 +8666,667 @@ function accessTokenCredential(auth) {
8666
8666
  return nonEmptyCredential(auth.accessToken);
8667
8667
  }
8668
8668
 
8669
+ // types.ts
8670
+ var EndpointState = /* @__PURE__ */ ((EndpointState2) => {
8671
+ EndpointState2["Idle"] = "Idle";
8672
+ EndpointState2["Busy"] = "Busy";
8673
+ EndpointState2["Authenticating"] = "Authenticating";
8674
+ EndpointState2["FetchConfig"] = "FetchConfig";
8675
+ EndpointState2["Error"] = "Error";
8676
+ EndpointState2["NotAvailable"] = "NotAvailable";
8677
+ return EndpointState2;
8678
+ })(EndpointState || {});
8679
+
8680
+ // shims/http_client.ts
8681
+ var createHttpClient;
8682
+ function logLineFromRequest(input, init) {
8683
+ const url = input instanceof Request ? input.url : input;
8684
+ const method = input instanceof Request ? input.method : init?.method || (init?.body !== void 0 ? "POST" : "GET");
8685
+ return `${method} ${url}`;
8686
+ }
8687
+ if ("document" in globalThis && "implementation" in globalThis.document) {
8688
+ class BrowserHttpClient {
8689
+ constructor(logger) {
8690
+ this._logger = logger;
8691
+ }
8692
+ async fetch(input, init) {
8693
+ const logLine = logLineFromRequest(input, init);
8694
+ this._logger.debug(`Browser fetch() BEFORE ${logLine}`);
8695
+ const response = await fetch(input, init);
8696
+ this._logger.debug(`AFTER ${logLine} -> ${response.status}`);
8697
+ return response;
8698
+ }
8699
+ async close() {
8700
+ }
8701
+ isFullDuplex() {
8702
+ return false;
8703
+ }
8704
+ }
8705
+ createHttpClient = async (logger) => {
8706
+ return new BrowserHttpClient(logger);
8707
+ };
8708
+ } else {
8709
+ class NodeHttpClient {
8710
+ constructor(agent, fetchFn, logger) {
8711
+ this._agent = agent;
8712
+ this._fetch = fetchFn;
8713
+ this._logger = logger;
8714
+ }
8715
+ async fetch(input, init) {
8716
+ init || (init = {});
8717
+ init.dispatcher = this._agent;
8718
+ const logLine = logLineFromRequest(input, init);
8719
+ this._logger.debug(`Node fetch() BEFORE ${logLine}`);
8720
+ const response = await this._fetch(input, init);
8721
+ this._logger.debug(`AFTER ${logLine} -> ${response.status}`);
8722
+ return response;
8723
+ }
8724
+ async close() {
8725
+ return this._agent.close();
8726
+ }
8727
+ isFullDuplex() {
8728
+ return false;
8729
+ }
8730
+ }
8731
+ createHttpClient = async (logger) => {
8732
+ const undici = __require("undici");
8733
+ const agent = new undici.Agent({
8734
+ keepAliveTimeout: 1e4,
8735
+ connections: 5,
8736
+ // two parallel streaming requests to the same host seem to deadlock with pipelining > 0
8737
+ pipelining: 0
8738
+ });
8739
+ logger.debug("Using node fetch with undici agent as http client");
8740
+ return new NodeHttpClient(agent, undici.fetch, logger);
8741
+ };
8742
+ }
8743
+
8744
+ // semaphore.ts
8745
+ var Semaphore = class {
8746
+ /**
8747
+ * Creates a semaphore.
8748
+ * @param permits The number of permits, i.e. strands of execution being allowed
8749
+ * to run in parallel.
8750
+ * This number can be initialized with a negative integer.
8751
+ */
8752
+ constructor(permits) {
8753
+ this.promiseResolverQueue = [];
8754
+ this.permits = permits;
8755
+ }
8756
+ /**
8757
+ * Returns the number of available permits.
8758
+ * @returns The number of available permits.
8759
+ */
8760
+ getPermits() {
8761
+ return this.permits;
8762
+ }
8763
+ /**
8764
+ * Returns a promise used to wait for a permit to become available. This method should be awaited on.
8765
+ * @returns A promise that gets resolved when execution is allowed to proceed.
8766
+ */
8767
+ async wait() {
8768
+ if (this.permits > 0) {
8769
+ this.permits -= 1;
8770
+ return Promise.resolve(true);
8771
+ }
8772
+ return new Promise((resolver) => this.promiseResolverQueue.push(resolver));
8773
+ }
8774
+ /**
8775
+ * Alias for {@linkcode Semaphore.wait}.
8776
+ * @returns A promise that gets resolved when execution is allowed to proceed.
8777
+ */
8778
+ async acquire() {
8779
+ return this.wait();
8780
+ }
8781
+ /**
8782
+ * Same as {@linkcode Semaphore.wait} except the promise returned gets resolved with false if no
8783
+ * permit becomes available in time.
8784
+ * @param milliseconds The time spent waiting before the wait is aborted. This is a lower bound,
8785
+ * you shouldn't rely on it being precise.
8786
+ * @returns A promise that gets resolved to true when execution is allowed to proceed or
8787
+ * false if the time given elapses before a permit becomes available.
8788
+ */
8789
+ async waitFor(milliseconds) {
8790
+ if (this.permits > 0) {
8791
+ this.permits -= 1;
8792
+ return Promise.resolve(true);
8793
+ }
8794
+ let resolver = (_) => void 0;
8795
+ const promise = new Promise((r) => {
8796
+ resolver = r;
8797
+ });
8798
+ this.promiseResolverQueue.push(resolver);
8799
+ setTimeout(() => {
8800
+ const index = this.promiseResolverQueue.indexOf(resolver);
8801
+ if (index !== -1) {
8802
+ this.promiseResolverQueue.splice(index, 1);
8803
+ } else {
8804
+ console.warn(`Semaphore.waitFor couldn't find its promise resolver in the queue`);
8805
+ }
8806
+ resolver(false);
8807
+ }, milliseconds);
8808
+ return promise;
8809
+ }
8810
+ /**
8811
+ * Synchronous function that tries to acquire a permit and returns true if successful, false otherwise.
8812
+ * @returns Whether a permit could be acquired.
8813
+ */
8814
+ tryAcquire() {
8815
+ if (this.permits > 0) {
8816
+ this.permits -= 1;
8817
+ return true;
8818
+ }
8819
+ return false;
8820
+ }
8821
+ /**
8822
+ * Acquires all permits that are currently available and returns the number of acquired permits.
8823
+ * @returns Number of acquired permits.
8824
+ */
8825
+ drainPermits() {
8826
+ if (this.permits > 0) {
8827
+ const permitCount = this.permits;
8828
+ this.permits = 0;
8829
+ return permitCount;
8830
+ }
8831
+ return 0;
8832
+ }
8833
+ /**
8834
+ * Increases the number of permits by one. If there are other functions waiting, one of them will
8835
+ * continue to execute in a future iteration of the event loop.
8836
+ */
8837
+ signal() {
8838
+ this.permits += 1;
8839
+ if (this.permits > 1 && this.promiseResolverQueue.length > 0) {
8840
+ console.warn("Semaphore.permits should never be > 0 when there is someone waiting.");
8841
+ } else if (this.permits === 1 && this.promiseResolverQueue.length > 0) {
8842
+ this.permits -= 1;
8843
+ const nextResolver = this.promiseResolverQueue.shift();
8844
+ if (nextResolver) {
8845
+ nextResolver(true);
8846
+ }
8847
+ }
8848
+ }
8849
+ /**
8850
+ * Alias for {@linkcode Semaphore.signal}.
8851
+ */
8852
+ release() {
8853
+ this.signal();
8854
+ }
8855
+ /**
8856
+ * Schedules func to be called once a permit becomes available.
8857
+ * Returns a promise that resolves to the return value of func.
8858
+ * @typeparam T The return type of func.
8859
+ * @param func The function to be executed.
8860
+ * @return A promise that gets resolved with the return value of the function.
8861
+ */
8862
+ async execute(func) {
8863
+ await this.wait();
8864
+ try {
8865
+ return await func();
8866
+ } finally {
8867
+ this.signal();
8868
+ }
8869
+ }
8870
+ };
8871
+
8872
+ // endpoint.ts
8873
+ import { pino } from "pino";
8874
+
8875
+ // shims/browser_session.ts
8876
+ import { createAuth0Client } from "@auth0/auth0-spa-js";
8877
+ var authenticateBrowserSession;
8878
+ if ("document" in globalThis && "implementation" in globalThis.document) {
8879
+ authenticateBrowserSession = async (auth0, options) => {
8880
+ if (options.eyepopUrl == null) {
8881
+ return Promise.reject("options.eyepopUrl cannot be null");
8882
+ }
8883
+ const auth0ClientOptions = {
8884
+ clientId: auth0.clientId,
8885
+ domain: auth0.domain,
8886
+ authorizationParams: {
8887
+ scope: auth0.scope,
8888
+ audience: auth0.audience
8889
+ }
8890
+ };
8891
+ const auth0Client = await createAuth0Client(auth0ClientOptions);
8892
+ if (!await auth0Client.isAuthenticated()) {
8893
+ await auth0Client.loginWithPopup();
8894
+ }
8895
+ const accessToken = await auth0Client.getTokenSilently();
8896
+ if (!accessToken) {
8897
+ return Promise.reject("auth0 login failed");
8898
+ }
8899
+ let session;
8900
+ const workerOptions = options;
8901
+ if (typeof workerOptions.popId != "undefined") {
8902
+ const workerSession = {
8903
+ eyepopUrl: options.eyepopUrl,
8904
+ accessToken,
8905
+ validUntil: Date.now() + 60 * 60 * 1e3,
8906
+ popId: workerOptions.popId,
8907
+ baseUrl: void 0,
8908
+ pipelineId: void 0,
8909
+ isPipelineMode: workerOptions.isPipelineMode,
8910
+ authenticationHeaders: () => {
8911
+ return { Authorization: `Bearer ${accessToken}` };
8912
+ }
8913
+ };
8914
+ session = workerSession;
8915
+ } else {
8916
+ session = {
8917
+ eyepopUrl: options.eyepopUrl,
8918
+ accessToken,
8919
+ validUntil: Date.now() + 60 * 60 * 1e3
8920
+ };
8921
+ }
8922
+ return session;
8923
+ };
8924
+ } else {
8925
+ authenticateBrowserSession = (_auth0, _options) => {
8926
+ return Promise.reject("auth0 login not supported server-side");
8927
+ };
8928
+ }
8929
+
8930
+ // endpoint.ts
8931
+ var Endpoint = class {
8932
+ constructor(options) {
8933
+ this._options = options;
8934
+ this._token = null;
8935
+ this._expire_token_time = null;
8936
+ this._client = null;
8937
+ this._statusRetryHandlers = /* @__PURE__ */ new Map();
8938
+ this._statusRetryHandlers.set(401, this.statusHandler401);
8939
+ this._limit = new Semaphore(this._options.jobQueueLength ?? 1024);
8940
+ this._state = "Idle" /* Idle */;
8941
+ this._message = null;
8942
+ this._stateChangeHandler = null;
8943
+ let rootLogger;
8944
+ if (options.logger) {
8945
+ rootLogger = options.logger;
8946
+ } else {
8947
+ rootLogger = pino();
8948
+ }
8949
+ this._logger = rootLogger.child({ module: "eyepop" });
8950
+ this._requestLogger = this._logger.child({ module: "requests" });
8951
+ }
8952
+ setStatusRetryHandlers(statusRetryHandlers = void 0) {
8953
+ this._statusRetryHandlers = new Map(statusRetryHandlers);
8954
+ this._statusRetryHandlers.set(401, this.statusHandler401);
8955
+ }
8956
+ eyepopUrl() {
8957
+ if (this._options.eyepopUrl) {
8958
+ return this._options.eyepopUrl.replace(/\/+$/, "");
8959
+ } else {
8960
+ return "https://compute.eyepop.ai";
8961
+ }
8962
+ }
8963
+ // TODO: deprecated Web API endpoint needed for POP_ID support
8964
+ eyepopWebApiUrl() {
8965
+ if (this._options.eyepopUrl) {
8966
+ return this._options.eyepopUrl.replace(/\/+$/, "").replace(/compute\./, "web-api.");
8967
+ } else {
8968
+ return "https://api.eyepop.ai";
8969
+ }
8970
+ }
8971
+ pendingJobs() {
8972
+ return (this._options.jobQueueLength ?? 1024) - this._limit.getPermits();
8973
+ }
8974
+ state() {
8975
+ return this._state;
8976
+ }
8977
+ lastMessage() {
8978
+ return this._message;
8979
+ }
8980
+ onStateChanged(handler) {
8981
+ this._stateChangeHandler = handler;
8982
+ return this;
8983
+ }
8984
+ updateState(newState = void 0, message = void 0) {
8985
+ const fromState = this._state;
8986
+ if (newState == void 0) {
8987
+ if (this._limit.getPermits() == this._options.jobQueueLength) {
8988
+ newState = "Idle" /* Idle */;
8989
+ } else {
8990
+ newState = "Busy" /* Busy */;
8991
+ }
8992
+ }
8993
+ this._state = newState;
8994
+ this._message = message ? message : null;
8995
+ if (this._stateChangeHandler) {
8996
+ this._stateChangeHandler(fromState, this._state);
8997
+ }
8998
+ }
8999
+ async connect() {
9000
+ if (this._client) {
9001
+ this._logger.warn("endpoint already connected");
9002
+ return this;
9003
+ }
9004
+ if (!this._options.eyepopUrl) {
9005
+ return Promise.reject("option eyepopUrl or environment variable EYEPOP_URL is required");
9006
+ }
9007
+ const auth = resolveAuth(this._options);
9008
+ if (auth === void 0) {
9009
+ return Promise.reject("cannot connect without defined auth option");
9010
+ }
9011
+ if (auth.session !== void 0) {
9012
+ this._token = auth.session.accessToken;
9013
+ this._expire_token_time = auth.session.validUntil / 1e3;
9014
+ } else if (auth.oAuth2 !== void 0) {
9015
+ } else if (accessTokenCredential(auth) !== void 0) {
9016
+ } else if (apiKeyCredential(auth) !== void 0) {
9017
+ } else if (auth.isLocal !== void 0) {
9018
+ } else {
9019
+ return Promise.reject("option apiKey/accessToken or environment variable EYEPOP_API_KEY is required");
9020
+ }
9021
+ if (this._options.platformSupport?.createHttpClient !== void 0) {
9022
+ this._client = await this._options.platformSupport.createHttpClient(this._requestLogger);
9023
+ } else {
9024
+ this._client = await createHttpClient(this._requestLogger);
9025
+ }
9026
+ return await this.reconnect();
9027
+ }
9028
+ async disconnect(wait = true) {
9029
+ if (wait && this._limit) {
9030
+ for (let j = 0; j < this._options.jobQueueLength; j++) {
9031
+ await this._limit.acquire();
9032
+ }
9033
+ }
9034
+ this._limit = new Semaphore(this._options.jobQueueLength ?? 1024);
9035
+ if (this._client) {
9036
+ await this._client.close();
9037
+ this._client = null;
9038
+ }
9039
+ }
9040
+ async session() {
9041
+ await this.currentAccessToken();
9042
+ if (this._token == null || this._expire_token_time == null) {
9043
+ return Promise.reject("endpoint not connected");
9044
+ }
9045
+ return {
9046
+ eyepopUrl: this._options.eyepopUrl,
9047
+ accessToken: this._token,
9048
+ validUntil: this._expire_token_time * 1e3
9049
+ };
9050
+ }
9051
+ statusHandler401(_statusCode) {
9052
+ this._token = null;
9053
+ this._expire_token_time = null;
9054
+ }
9055
+ async fetchWithRetry(fetcher, retries = 1) {
9056
+ while (true) {
9057
+ try {
9058
+ const response = await fetcher();
9059
+ if (response.ok) {
9060
+ return response;
9061
+ } else {
9062
+ const handler = this._statusRetryHandlers.get(response.status);
9063
+ if (handler != null) {
9064
+ handler(response.status);
9065
+ if (retries--) {
9066
+ this._requestLogger.info("received %d response, retry", response.status);
9067
+ } else {
9068
+ return response;
9069
+ }
9070
+ } else {
9071
+ return response;
9072
+ }
9073
+ }
9074
+ } catch (error) {
9075
+ const handler = this._statusRetryHandlers.get(404);
9076
+ if (handler != null) {
9077
+ handler(error);
9078
+ if (retries--) {
9079
+ this._requestLogger.info("error %s, retry", error);
9080
+ } else {
9081
+ return Promise.reject(`error ${error}`);
9082
+ }
9083
+ } else {
9084
+ return Promise.reject(`error ${error}`);
9085
+ }
9086
+ }
9087
+ }
9088
+ }
9089
+ async reconnect() {
9090
+ throw new Error("reconnect() not implemented");
9091
+ }
9092
+ async authorizationHeader() {
9093
+ if (resolveAuth(this._options)?.isLocal !== void 0) {
9094
+ return `Implicit`;
9095
+ }
9096
+ return `Bearer ${await this.currentAccessToken()}`;
9097
+ }
9098
+ async currentAccessToken() {
9099
+ const now = Date.now() / 1e3;
9100
+ if (!this._token || this._expire_token_time < now) {
9101
+ this.updateState("Authenticating" /* Authenticating */);
9102
+ try {
9103
+ const auth = resolveAuth(this._options);
9104
+ if (!this._client) {
9105
+ return Promise.reject("endpoint not connected");
9106
+ } else if (auth?.isLocal !== void 0) {
9107
+ this._token = "implicit";
9108
+ this._expire_token_time = Number.MAX_VALUE;
9109
+ } else if (auth?.session !== void 0) {
9110
+ return Promise.reject("temporary access token expired");
9111
+ } else if (auth?.oAuth2 !== void 0 && this._options.eyepopUrl) {
9112
+ const session = await authenticateBrowserSession(auth.oAuth2, this._options);
9113
+ this._token = session.accessToken;
9114
+ this._expire_token_time = session.validUntil / 1e3;
9115
+ } else if (accessTokenCredential(auth) !== void 0) {
9116
+ this._token = accessTokenCredential(auth);
9117
+ this._expire_token_time = Number.MAX_VALUE;
9118
+ } else if (this._options.eyepopUrl) {
9119
+ const credential = apiKeyCredential(auth);
9120
+ if (credential === void 0) {
9121
+ return Promise.reject("no valid auth option");
9122
+ }
9123
+ const headers = { Authorization: `Bearer ${credential}` };
9124
+ const post_url = `${this.eyepopUrl()}/v1/auth/authenticate`;
9125
+ const response = await this._client.fetch(post_url, {
9126
+ method: "POST",
9127
+ headers
9128
+ });
9129
+ if (response.status != 200) {
9130
+ const message = await response.text();
9131
+ return Promise.reject(`Unexpected status ${response.status}: ${message}`);
9132
+ }
9133
+ const data = await response.json();
9134
+ const token = data;
9135
+ this._token = token.access_token;
9136
+ this._expire_token_time = now + token.expires_in - 60;
9137
+ } else {
9138
+ return Promise.reject("no valid auth option");
9139
+ }
9140
+ } finally {
9141
+ this.updateState();
9142
+ }
9143
+ }
9144
+ this._logger.debug("using access token, valid for at least %d seconds", this._expire_token_time - now);
9145
+ return this._token;
9146
+ }
9147
+ };
9148
+
9149
+ // camera.ts
9150
+ var QUATERNION_TOLERANCE = 1e-3;
9151
+ function isFinite_(value) {
9152
+ return value === void 0 || Number.isFinite(value);
9153
+ }
9154
+ function validateCamera(camera) {
9155
+ if (camera.intrinsics !== void 0 && camera.hfovDegrees !== void 0) {
9156
+ throw new Error("camera.intrinsics and camera.hfovDegrees are alternatives, supply one");
9157
+ }
9158
+ if (camera.intrinsics === void 0 && camera.hfovDegrees === void 0) {
9159
+ throw new Error("camera requires either camera.intrinsics or camera.hfovDegrees");
9160
+ }
9161
+ if (camera.hfovDegrees !== void 0) {
9162
+ if (!(camera.hfovDegrees > 0 && camera.hfovDegrees < 180)) {
9163
+ throw new Error("camera.hfovDegrees must be a horizontal field of view in (0, 180) degrees");
9164
+ }
9165
+ } else {
9166
+ const intrinsics = camera.intrinsics;
9167
+ for (const [name, focal] of [
9168
+ ["fx", intrinsics.fx],
9169
+ ["fy", intrinsics.fy]
9170
+ ]) {
9171
+ if (!(focal > 0) || !Number.isFinite(focal)) {
9172
+ throw new Error(`camera.intrinsics.${name} must be a positive, finite focal length`);
9173
+ }
9174
+ }
9175
+ for (const [name, principal, extent] of [
9176
+ ["cx", intrinsics.cx, "width"],
9177
+ ["cy", intrinsics.cy, "height"]
9178
+ ]) {
9179
+ if (!(principal >= 0 && principal <= 1)) {
9180
+ throw new Error(`camera.intrinsics.${name} must be within [0, 1] of the frame ${extent}`);
9181
+ }
9182
+ }
9183
+ }
9184
+ if (camera.distortion !== void 0) {
9185
+ for (const name of ["k1", "k2", "p1", "p2", "k3"]) {
9186
+ if (!isFinite_(camera.distortion[name])) {
9187
+ throw new Error(`camera.distortion.${name} must be a finite number`);
9188
+ }
9189
+ }
9190
+ }
9191
+ if (camera.extrinsics !== void 0) {
9192
+ const translation = camera.extrinsics.translation;
9193
+ if (translation !== void 0) {
9194
+ for (const axis of ["x", "y", "z"]) {
9195
+ if (!Number.isFinite(translation[axis])) {
9196
+ throw new Error(`camera.extrinsics.translation.${axis} must be a finite number`);
9197
+ }
9198
+ }
9199
+ }
9200
+ const rotation = camera.extrinsics.rotation;
9201
+ if (rotation !== void 0) {
9202
+ const norm = Math.sqrt(rotation.w ** 2 + rotation.x ** 2 + rotation.y ** 2 + rotation.z ** 2);
9203
+ if (!(Math.abs(norm - 1) <= QUATERNION_TOLERANCE)) {
9204
+ throw new Error("camera.extrinsics.rotation must be a unit quaternion");
9205
+ }
9206
+ }
9207
+ }
9208
+ }
9209
+
9210
+ // worker/worker_types.ts
9211
+ var PredictionVersion = /* @__PURE__ */ ((PredictionVersion3) => {
9212
+ PredictionVersion3[PredictionVersion3["V1"] = 1] = "V1";
9213
+ PredictionVersion3[PredictionVersion3["V2"] = 2] = "V2";
9214
+ PredictionVersion3[PredictionVersion3["V3"] = 3] = "V3";
9215
+ return PredictionVersion3;
9216
+ })(PredictionVersion || {});
9217
+ var DEFAULT_PREDICTION_VERSION = 2 /* V2 */;
9218
+ var VideoMode = /* @__PURE__ */ ((VideoMode2) => {
9219
+ VideoMode2["STREAM"] = "stream";
9220
+ VideoMode2["BUFFER"] = "buffer";
9221
+ return VideoMode2;
9222
+ })(VideoMode || {});
9223
+ var PopComponentType = /* @__PURE__ */ ((PopComponentType2) => {
9224
+ PopComponentType2["FORWARD"] = "forward";
9225
+ PopComponentType2["INFERENCE"] = "inference";
9226
+ PopComponentType2["TRACKING"] = "tracking";
9227
+ PopComponentType2["CONTOUR_FINDER"] = "contour_finder";
9228
+ PopComponentType2["COMPONENT_FINDER"] = "component_finder";
9229
+ return PopComponentType2;
9230
+ })(PopComponentType || {});
9231
+ var ForwardOperatorType = /* @__PURE__ */ ((ForwardOperatorType2) => {
9232
+ ForwardOperatorType2["FULL"] = "full";
9233
+ ForwardOperatorType2["CROP"] = "crop";
9234
+ ForwardOperatorType2["CROP_WITH_FULL_FALLBACK"] = "crop_with_full_fallback";
9235
+ ForwardOperatorType2["SELECT_CROP"] = "select_crop";
9236
+ ForwardOperatorType2["SELECT_FULL"] = "select_full";
9237
+ return ForwardOperatorType2;
9238
+ })(ForwardOperatorType || {});
9239
+ var SelectMode = /* @__PURE__ */ ((SelectMode2) => {
9240
+ SelectMode2["MOST_RELEVANT"] = "most_relevant";
9241
+ return SelectMode2;
9242
+ })(SelectMode || {});
9243
+ function isSelectOperator(operator) {
9244
+ return operator?.type === "select_crop" /* SELECT_CROP */ || operator?.type === "select_full" /* SELECT_FULL */;
9245
+ }
9246
+ var InferenceType = /* @__PURE__ */ ((InferenceType2) => {
9247
+ InferenceType2["IMAGE_CLASSIFICATION"] = "image_classification";
9248
+ InferenceType2["OBJECT_DETECTION"] = "object_detection";
9249
+ InferenceType2["KEY_POINTS"] = "key_points";
9250
+ InferenceType2["OCR"] = "ocr";
9251
+ InferenceType2["MESH"] = "mesh";
9252
+ InferenceType2["FEATURE_VECTOR"] = "feature_vector";
9253
+ InferenceType2["SEMANTIC_SEGMENTATION"] = "semantic_segmentation";
9254
+ InferenceType2["SEGMENTATION"] = "segmentation";
9255
+ return InferenceType2;
9256
+ })(InferenceType || {});
9257
+ var MotionModel = /* @__PURE__ */ ((MotionModel2) => {
9258
+ MotionModel2["RANDOM_WALK"] = "random_walk";
9259
+ MotionModel2["CONSTANT_VELOCITY"] = "constant_velocity";
9260
+ MotionModel2["CONSTANT_ACCELERATION"] = "constant_acceleration";
9261
+ return MotionModel2;
9262
+ })(MotionModel || {});
9263
+ var ContourType = /* @__PURE__ */ ((ContourType2) => {
9264
+ ContourType2["ALL_PIXELS"] = "all_pixels";
9265
+ ContourType2["POLYGON"] = "polygon";
9266
+ ContourType2["CONVEX_HULL"] = "convex_hull";
9267
+ ContourType2["HOUGH_CIRCLES"] = "hough_circles";
9268
+ ContourType2["CIRCLE"] = "circle";
9269
+ ContourType2["TRIANGLE"] = "triangle";
9270
+ ContourType2["RECTANGLE"] = "rectangle";
9271
+ return ContourType2;
9272
+ })(ContourType || {});
9273
+ function validatePop(pop) {
9274
+ if (pop.depthMap !== void 0) {
9275
+ if (pop.depthMap.ability === void 0 === (pop.depthMap.abilityUuid === void 0)) {
9276
+ throw new Error("depthMap requires exactly one of ability or abilityUuid");
9277
+ }
9278
+ }
9279
+ if (pop.defaults?.camera !== void 0) {
9280
+ validateCamera(pop.defaults.camera);
9281
+ }
9282
+ validateForwards(pop.components);
9283
+ }
9284
+ function popSelects(pop) {
9285
+ const selects = (components) => (components ?? []).some((component) => isSelectOperator(component.forward?.operator) || selects(component.forward?.targets));
9286
+ return selects(pop?.components);
9287
+ }
9288
+ function validateForwards(components) {
9289
+ for (const component of components ?? []) {
9290
+ const operator = component.forward?.operator;
9291
+ if (operator !== void 0) {
9292
+ validateSelectOperator(operator);
9293
+ }
9294
+ validateForwards(component.forward?.targets);
9295
+ }
9296
+ }
9297
+ function validateSelectOperator(operator) {
9298
+ if (!isSelectOperator(operator)) {
9299
+ if (operator.select !== void 0) {
9300
+ throw new Error("select is only valid with the select_crop or select_full operator");
9301
+ }
9302
+ return;
9303
+ }
9304
+ const select = operator.select;
9305
+ if (select === void 0) {
9306
+ throw new Error(`${operator.type} requires a select block`);
9307
+ }
9308
+ if (select.mode !== void 0 && select.mode !== "most_relevant" /* MOST_RELEVANT */) {
9309
+ throw new Error(`select mode ${select.mode} is not supported, the only mode is ${"most_relevant" /* MOST_RELEVANT */}`);
9310
+ }
9311
+ if (select.relevancyModel !== void 0 && select.relevancyModelUuid !== void 0) {
9312
+ throw new Error("select can only have one of relevancyModelUuid or relevancyModel");
9313
+ }
9314
+ if (select.minTrackLengthSeconds !== void 0 && select.minTrackLengthSeconds < 0) {
9315
+ throw new Error("select minTrackLengthSeconds cannot be negative");
9316
+ }
9317
+ if (select.intervalSeconds !== void 0 && select.intervalSeconds <= 0) {
9318
+ throw new Error("select intervalSeconds must be positive");
9319
+ }
9320
+ if (operator.crop !== void 0) {
9321
+ if (operator.type !== "select_crop" /* SELECT_CROP */) {
9322
+ throw new Error(`crop options are only valid with select_crop, not ${operator.type}`);
9323
+ }
9324
+ if (operator.crop.maxItems !== void 0) {
9325
+ throw new Error("select_crop forwards one detection per selection, maxItems does not apply");
9326
+ }
9327
+ }
9328
+ }
9329
+
8669
9330
  // compute/compute_session.ts
8670
9331
  var SESSION_DEAD = /* @__PURE__ */ new Set(["error" /* ERROR */, "failed" /* FAILED */, "stopped" /* STOPPED */, "pipeline_error" /* PIPELINE_ERROR */, "unknown" /* UNKNOWN */]);
8671
9332
  var CHECK_FOR_IS_READY_INTERVAL = 250;
@@ -8982,17 +9643,6 @@ var ComputeSessionClient = class {
8982
9643
  }
8983
9644
  };
8984
9645
 
8985
- // types.ts
8986
- var EndpointState = /* @__PURE__ */ ((EndpointState2) => {
8987
- EndpointState2["Idle"] = "Idle";
8988
- EndpointState2["Busy"] = "Busy";
8989
- EndpointState2["Authenticating"] = "Authenticating";
8990
- EndpointState2["FetchConfig"] = "FetchConfig";
8991
- EndpointState2["Error"] = "Error";
8992
- EndpointState2["NotAvailable"] = "NotAvailable";
8993
- return EndpointState2;
8994
- })(EndpointState || {});
8995
-
8996
9646
  // streaming.ts
8997
9647
  import { v4 as uuidv4 } from "uuid";
8998
9648
  var Stream = class _Stream {
@@ -9209,166 +9859,41 @@ function encodeMultipartFormData(parts, boundary) {
9209
9859
  if (!done) {
9210
9860
  controller.enqueue(value);
9211
9861
  return;
9212
- }
9213
- reader = null;
9214
- controller.enqueue(encoder.encode("\r\n"));
9215
- partIndex++;
9216
- }
9217
- if (partIndex >= parts.length) {
9218
- controller.enqueue(encoder.encode(`--${boundary}--\r
9219
- `));
9220
- controller.close();
9221
- return;
9222
- }
9223
- const part = parts[partIndex];
9224
- let header = `--${boundary}\r
9225
- Content-Disposition: form-data; name="${part.name}"\r
9226
- `;
9227
- if (part.contentType) {
9228
- header += `Content-Type: ${part.contentType}\r
9229
- `;
9230
- }
9231
- header += "\r\n";
9232
- controller.enqueue(encoder.encode(header));
9233
- if (typeof part.content === "string") {
9234
- controller.enqueue(encoder.encode(part.content));
9235
- controller.enqueue(encoder.encode("\r\n"));
9236
- partIndex++;
9237
- } else {
9238
- reader = toPartStream(part.content).getReader();
9239
- }
9240
- },
9241
- async cancel(reason) {
9242
- await reader?.cancel(reason);
9243
- const firstUnstartedPart = reader ? partIndex + 1 : partIndex;
9244
- await Promise.all(parts.slice(firstUnstartedPart).map((part) => cancelMultipartContent(part.content, reason)));
9245
- }
9246
- });
9247
- }
9248
-
9249
- // camera.ts
9250
- var QUATERNION_TOLERANCE = 1e-3;
9251
- function isFinite_(value) {
9252
- return value === void 0 || Number.isFinite(value);
9253
- }
9254
- function validateCamera(camera) {
9255
- if (camera.intrinsics !== void 0 && camera.hfovDegrees !== void 0) {
9256
- throw new Error("camera.intrinsics and camera.hfovDegrees are alternatives, supply one");
9257
- }
9258
- if (camera.intrinsics === void 0 && camera.hfovDegrees === void 0) {
9259
- throw new Error("camera requires either camera.intrinsics or camera.hfovDegrees");
9260
- }
9261
- if (camera.hfovDegrees !== void 0) {
9262
- if (!(camera.hfovDegrees > 0 && camera.hfovDegrees < 180)) {
9263
- throw new Error("camera.hfovDegrees must be a horizontal field of view in (0, 180) degrees");
9264
- }
9265
- } else {
9266
- const intrinsics = camera.intrinsics;
9267
- for (const [name, focal] of [
9268
- ["fx", intrinsics.fx],
9269
- ["fy", intrinsics.fy]
9270
- ]) {
9271
- if (!(focal > 0) || !Number.isFinite(focal)) {
9272
- throw new Error(`camera.intrinsics.${name} must be a positive, finite focal length`);
9273
- }
9274
- }
9275
- for (const [name, principal, extent] of [
9276
- ["cx", intrinsics.cx, "width"],
9277
- ["cy", intrinsics.cy, "height"]
9278
- ]) {
9279
- if (!(principal >= 0 && principal <= 1)) {
9280
- throw new Error(`camera.intrinsics.${name} must be within [0, 1] of the frame ${extent}`);
9281
- }
9282
- }
9283
- }
9284
- if (camera.distortion !== void 0) {
9285
- for (const name of ["k1", "k2", "p1", "p2", "k3"]) {
9286
- if (!isFinite_(camera.distortion[name])) {
9287
- throw new Error(`camera.distortion.${name} must be a finite number`);
9288
- }
9289
- }
9290
- }
9291
- if (camera.extrinsics !== void 0) {
9292
- const translation = camera.extrinsics.translation;
9293
- if (translation !== void 0) {
9294
- for (const axis of ["x", "y", "z"]) {
9295
- if (!Number.isFinite(translation[axis])) {
9296
- throw new Error(`camera.extrinsics.translation.${axis} must be a finite number`);
9297
- }
9298
- }
9299
- }
9300
- const rotation = camera.extrinsics.rotation;
9301
- if (rotation !== void 0) {
9302
- const norm = Math.sqrt(rotation.w ** 2 + rotation.x ** 2 + rotation.y ** 2 + rotation.z ** 2);
9303
- if (!(Math.abs(norm - 1) <= QUATERNION_TOLERANCE)) {
9304
- throw new Error("camera.extrinsics.rotation must be a unit quaternion");
9305
- }
9306
- }
9307
- }
9308
- }
9309
-
9310
- // worker/worker_types.ts
9311
- var PredictionVersion = /* @__PURE__ */ ((PredictionVersion3) => {
9312
- PredictionVersion3[PredictionVersion3["V1"] = 1] = "V1";
9313
- PredictionVersion3[PredictionVersion3["V2"] = 2] = "V2";
9314
- return PredictionVersion3;
9315
- })(PredictionVersion || {});
9316
- var DEFAULT_PREDICTION_VERSION = 2 /* V2 */;
9317
- var VideoMode = /* @__PURE__ */ ((VideoMode2) => {
9318
- VideoMode2["STREAM"] = "stream";
9319
- VideoMode2["BUFFER"] = "buffer";
9320
- return VideoMode2;
9321
- })(VideoMode || {});
9322
- var PopComponentType = /* @__PURE__ */ ((PopComponentType2) => {
9323
- PopComponentType2["FORWARD"] = "forward";
9324
- PopComponentType2["INFERENCE"] = "inference";
9325
- PopComponentType2["TRACKING"] = "tracking";
9326
- PopComponentType2["CONTOUR_FINDER"] = "contour_finder";
9327
- PopComponentType2["COMPONENT_FINDER"] = "component_finder";
9328
- return PopComponentType2;
9329
- })(PopComponentType || {});
9330
- var ForwardOperatorType = /* @__PURE__ */ ((ForwardOperatorType2) => {
9331
- ForwardOperatorType2["FULL"] = "full";
9332
- ForwardOperatorType2["CROP"] = "crop";
9333
- ForwardOperatorType2["CROP_WITH_FULL_FALLBACK"] = "crop_with_full_fallback";
9334
- return ForwardOperatorType2;
9335
- })(ForwardOperatorType || {});
9336
- var InferenceType = /* @__PURE__ */ ((InferenceType2) => {
9337
- InferenceType2["IMAGE_CLASSIFICATION"] = "image_classification";
9338
- InferenceType2["OBJECT_DETECTION"] = "object_detection";
9339
- InferenceType2["KEY_POINTS"] = "key_points";
9340
- InferenceType2["OCR"] = "ocr";
9341
- InferenceType2["MESH"] = "mesh";
9342
- InferenceType2["FEATURE_VECTOR"] = "feature_vector";
9343
- InferenceType2["SEMANTIC_SEGMENTATION"] = "semantic_segmentation";
9344
- InferenceType2["SEGMENTATION"] = "segmentation";
9345
- return InferenceType2;
9346
- })(InferenceType || {});
9347
- var MotionModel = /* @__PURE__ */ ((MotionModel2) => {
9348
- MotionModel2["RANDOM_WALK"] = "random_walk";
9349
- MotionModel2["CONSTANT_VELOCITY"] = "constant_velocity";
9350
- MotionModel2["CONSTANT_ACCELERATION"] = "constant_acceleration";
9351
- return MotionModel2;
9352
- })(MotionModel || {});
9353
- var ContourType = /* @__PURE__ */ ((ContourType2) => {
9354
- ContourType2["ALL_PIXELS"] = "all_pixels";
9355
- ContourType2["POLYGON"] = "polygon";
9356
- ContourType2["CONVEX_HULL"] = "convex_hull";
9357
- ContourType2["HOUGH_CIRCLES"] = "hough_circles";
9358
- ContourType2["CIRCLE"] = "circle";
9359
- ContourType2["TRIANGLE"] = "triangle";
9360
- ContourType2["RECTANGLE"] = "rectangle";
9361
- return ContourType2;
9362
- })(ContourType || {});
9363
- function validatePop(pop) {
9364
- if (pop.depthMap !== void 0) {
9365
- if (pop.depthMap.ability === void 0 === (pop.depthMap.abilityUuid === void 0)) {
9366
- throw new Error("depthMap requires exactly one of ability or abilityUuid");
9862
+ }
9863
+ reader = null;
9864
+ controller.enqueue(encoder.encode("\r\n"));
9865
+ partIndex++;
9866
+ }
9867
+ if (partIndex >= parts.length) {
9868
+ controller.enqueue(encoder.encode(`--${boundary}--\r
9869
+ `));
9870
+ controller.close();
9871
+ return;
9872
+ }
9873
+ const part = parts[partIndex];
9874
+ let header = `--${boundary}\r
9875
+ Content-Disposition: form-data; name="${part.name}"\r
9876
+ `;
9877
+ if (part.contentType) {
9878
+ header += `Content-Type: ${part.contentType}\r
9879
+ `;
9880
+ }
9881
+ header += "\r\n";
9882
+ controller.enqueue(encoder.encode(header));
9883
+ if (typeof part.content === "string") {
9884
+ controller.enqueue(encoder.encode(part.content));
9885
+ controller.enqueue(encoder.encode("\r\n"));
9886
+ partIndex++;
9887
+ } else {
9888
+ reader = toPartStream(part.content).getReader();
9889
+ }
9890
+ },
9891
+ async cancel(reason) {
9892
+ await reader?.cancel(reason);
9893
+ const firstUnstartedPart = reader ? partIndex + 1 : partIndex;
9894
+ await Promise.all(parts.slice(firstUnstartedPart).map((part) => cancelMultipartContent(part.content, reason)));
9367
9895
  }
9368
- }
9369
- if (pop.defaults?.camera !== void 0) {
9370
- validateCamera(pop.defaults.camera);
9371
- }
9896
+ });
9372
9897
  }
9373
9898
 
9374
9899
  // worker/webrtc_whip.ts
@@ -9821,8 +10346,8 @@ var UploadJob = class extends AbstractJob {
9821
10346
  get [Symbol.toStringTag]() {
9822
10347
  return "uploadJob";
9823
10348
  }
9824
- constructor(stream, mimeType, size, videoMode, params, getSession, client, requestLogger, uploadBodyFactory) {
9825
- super(params, getSession, client, requestLogger);
10349
+ constructor(stream, mimeType, size, videoMode, params, getSession, client, requestLogger, uploadBodyFactory, version = DEFAULT_PREDICTION_VERSION) {
10350
+ super(params, getSession, client, requestLogger, version);
9826
10351
  this._uploadStream = stream;
9827
10352
  this._uploadBodyFactory = uploadBodyFactory;
9828
10353
  this._mimeType = mimeType;
@@ -10015,8 +10540,8 @@ var UploadJob = class extends AbstractJob {
10015
10540
  }
10016
10541
  };
10017
10542
  var LoadFromJob = class extends AbstractJob {
10018
- constructor(location, params, getSession, client, requestLogger) {
10019
- super(params, getSession, client, requestLogger);
10543
+ constructor(location, params, getSession, client, requestLogger, version = DEFAULT_PREDICTION_VERSION) {
10544
+ super(params, getSession, client, requestLogger, version);
10020
10545
  this._location = location;
10021
10546
  }
10022
10547
  get [Symbol.toStringTag]() {
@@ -10062,8 +10587,8 @@ var UploadGroupJob = class extends AbstractJob {
10062
10587
  get [Symbol.toStringTag]() {
10063
10588
  return "uploadGroupJob";
10064
10589
  }
10065
- constructor(sources, params, getSession, client, requestLogger) {
10066
- super(params, getSession, client, requestLogger);
10590
+ constructor(sources, params, getSession, client, requestLogger, version = DEFAULT_PREDICTION_VERSION) {
10591
+ super(params, getSession, client, requestLogger, version);
10067
10592
  if (sources.length === 0) {
10068
10593
  throw new Error("upload group requires at least one source");
10069
10594
  }
@@ -10124,8 +10649,8 @@ var LoadFromGroupJob = class extends AbstractJob {
10124
10649
  get [Symbol.toStringTag]() {
10125
10650
  return "loadFromGroupJob";
10126
10651
  }
10127
- constructor(urls, params, getSession, client, requestLogger) {
10128
- super(params, getSession, client, requestLogger);
10652
+ constructor(urls, params, getSession, client, requestLogger, version = DEFAULT_PREDICTION_VERSION) {
10653
+ super(params, getSession, client, requestLogger, version);
10129
10654
  if (urls.length === 0) {
10130
10655
  throw new Error("load from group requires at least one url");
10131
10656
  }
@@ -10170,8 +10695,8 @@ var LoadFromGroupJob = class extends AbstractJob {
10170
10695
  }
10171
10696
  };
10172
10697
  var LoadFromAssetUuidJob = class extends AbstractJob {
10173
- constructor(assetUuid, params, getSession, client, requestLogger) {
10174
- super(params, getSession, client, requestLogger);
10698
+ constructor(assetUuid, params, getSession, client, requestLogger, version = DEFAULT_PREDICTION_VERSION) {
10699
+ super(params, getSession, client, requestLogger, version);
10175
10700
  this._assetUuid = assetUuid;
10176
10701
  }
10177
10702
  get [Symbol.toStringTag]() {
@@ -10211,1378 +10736,1755 @@ var LoadFromAssetUuidJob = class extends AbstractJob {
10211
10736
  }
10212
10737
  };
10213
10738
  var LoadMediaStreamJob = class extends AbstractJob {
10214
- constructor(mediaStream, params, getSession, client, requestLogger) {
10215
- super(params, getSession, client, requestLogger);
10739
+ constructor(mediaStream, params, getSession, client, requestLogger, version = DEFAULT_PREDICTION_VERSION) {
10740
+ super(params, getSession, client, requestLogger, version);
10216
10741
  this._mediaStream = mediaStream;
10217
10742
  }
10218
10743
  get [Symbol.toStringTag]() {
10219
10744
  return "loadLiveIngressJob";
10220
10745
  }
10221
- async startJob() {
10222
- const session = await this._getSession();
10223
- if (!session.baseUrl) {
10224
- return Promise.reject("session.baseUrl must not ne null");
10225
- }
10226
- const whip = new WebrtcWhip(
10227
- this._mediaStream,
10228
- async () => {
10229
- return session;
10230
- },
10231
- this._client,
10232
- true,
10233
- this._requestLogger
10234
- );
10235
- whip.start().then((_) => {
10236
- this._requestLogger.debug("direct whip for ingressId=%s successful", whip.ingressId());
10237
- }).catch((reason) => {
10238
- this._controller.abort(reason);
10239
- });
10240
- const body = {
10241
- sourceType: "WHIP",
10242
- whipIngressId: whip.ingressId(),
10243
- params: this._params.componentParams,
10244
- roi: this._params.roi,
10245
- fps: this._params.fps,
10246
- camera: this._params.camera,
10247
- version: this._version
10248
- };
10249
- if (this._params.motionDetect) {
10250
- Object.assign(body, this._params.motionDetect);
10251
- }
10252
- const headers = {
10253
- ...session.authenticationHeaders(),
10254
- Accept: "application/jsonl",
10255
- "Content-Type": "application/json"
10256
- };
10257
- const patchUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=preempt&processing=sync`;
10258
- return await this._client.fetch(patchUrl, {
10259
- headers,
10260
- method: "PATCH",
10261
- body: JSON.stringify(body),
10262
- signal: this._controller.signal,
10263
- // @ts-ignore
10264
- eyepop: { responseStreaming: true }
10265
- });
10266
- }
10267
- };
10268
-
10269
- // shims/local_file.ts
10270
- var resolvePath;
10271
- if ("document" in globalThis && "implementation" in globalThis.document) {
10272
- resolvePath = async (_source, _logger) => {
10273
- throw new DOMException("resolving a path to a file is not supported in browser");
10274
- };
10275
- } else {
10276
- let iteratorToStream = function(iterator) {
10277
- return new ReadableStream({
10278
- async pull(controller) {
10279
- const { value, done } = await iterator.next();
10280
- if (done) {
10281
- controller.close();
10282
- } else {
10283
- controller.enqueue(new Uint8Array(value));
10284
- }
10285
- }
10286
- });
10287
- };
10288
- iteratorToStream2 = iteratorToStream;
10289
- async function* nodeStreamToIterator(stream) {
10290
- for await (const chunk of stream) {
10291
- yield chunk;
10292
- }
10293
- }
10294
- resolvePath = async (source, _) => {
10295
- const mime = require_mime_types();
10296
- const filehandle = __require("fs/promises");
10297
- const fd = await filehandle.open(source.path, "r");
10298
- const stream = fd.createReadStream();
10299
- const iterator = nodeStreamToIterator(stream);
10300
- const webStream = iteratorToStream(iterator);
10301
- const mimeType = source.mimeType ? source.mimeType : mime.lookup(source.path) || "image/*";
10302
- return {
10303
- stream: webStream,
10304
- mimeType
10305
- };
10306
- };
10307
- }
10308
- var iteratorToStream2;
10309
-
10310
- // shims/http_client.ts
10311
- var createHttpClient;
10312
- function logLineFromRequest(input, init) {
10313
- const url = input instanceof Request ? input.url : input;
10314
- const method = input instanceof Request ? input.method : init?.method || (init?.body !== void 0 ? "POST" : "GET");
10315
- return `${method} ${url}`;
10316
- }
10317
- if ("document" in globalThis && "implementation" in globalThis.document) {
10318
- class BrowserHttpClient {
10319
- constructor(logger) {
10320
- this._logger = logger;
10321
- }
10322
- async fetch(input, init) {
10323
- const logLine = logLineFromRequest(input, init);
10324
- this._logger.debug(`Browser fetch() BEFORE ${logLine}`);
10325
- const response = await fetch(input, init);
10326
- this._logger.debug(`AFTER ${logLine} -> ${response.status}`);
10327
- return response;
10328
- }
10329
- async close() {
10330
- }
10331
- isFullDuplex() {
10332
- return false;
10333
- }
10334
- }
10335
- createHttpClient = async (logger) => {
10336
- return new BrowserHttpClient(logger);
10337
- };
10338
- } else {
10339
- class NodeHttpClient {
10340
- constructor(agent, fetchFn, logger) {
10341
- this._agent = agent;
10342
- this._fetch = fetchFn;
10343
- this._logger = logger;
10344
- }
10345
- async fetch(input, init) {
10346
- init || (init = {});
10347
- init.dispatcher = this._agent;
10348
- const logLine = logLineFromRequest(input, init);
10349
- this._logger.debug(`Node fetch() BEFORE ${logLine}`);
10350
- const response = await this._fetch(input, init);
10351
- this._logger.debug(`AFTER ${logLine} -> ${response.status}`);
10352
- return response;
10353
- }
10354
- async close() {
10355
- return this._agent.close();
10356
- }
10357
- isFullDuplex() {
10358
- return false;
10359
- }
10360
- }
10361
- createHttpClient = async (logger) => {
10362
- const undici = __require("undici");
10363
- const agent = new undici.Agent({
10364
- keepAliveTimeout: 1e4,
10365
- connections: 5,
10366
- // two parallel streaming requests to the same host seem to deadlock with pipelining > 0
10367
- pipelining: 0
10368
- });
10369
- logger.debug("Using node fetch with undici agent as http client");
10370
- return new NodeHttpClient(agent, undici.fetch, logger);
10371
- };
10372
- }
10373
-
10374
- // semaphore.ts
10375
- var Semaphore = class {
10376
- /**
10377
- * Creates a semaphore.
10378
- * @param permits The number of permits, i.e. strands of execution being allowed
10379
- * to run in parallel.
10380
- * This number can be initialized with a negative integer.
10381
- */
10382
- constructor(permits) {
10383
- this.promiseResolverQueue = [];
10384
- this.permits = permits;
10385
- }
10386
- /**
10387
- * Returns the number of available permits.
10388
- * @returns The number of available permits.
10389
- */
10390
- getPermits() {
10391
- return this.permits;
10392
- }
10393
- /**
10394
- * Returns a promise used to wait for a permit to become available. This method should be awaited on.
10395
- * @returns A promise that gets resolved when execution is allowed to proceed.
10396
- */
10397
- async wait() {
10398
- if (this.permits > 0) {
10399
- this.permits -= 1;
10400
- return Promise.resolve(true);
10401
- }
10402
- return new Promise((resolver) => this.promiseResolverQueue.push(resolver));
10403
- }
10404
- /**
10405
- * Alias for {@linkcode Semaphore.wait}.
10406
- * @returns A promise that gets resolved when execution is allowed to proceed.
10407
- */
10408
- async acquire() {
10409
- return this.wait();
10410
- }
10411
- /**
10412
- * Same as {@linkcode Semaphore.wait} except the promise returned gets resolved with false if no
10413
- * permit becomes available in time.
10414
- * @param milliseconds The time spent waiting before the wait is aborted. This is a lower bound,
10415
- * you shouldn't rely on it being precise.
10416
- * @returns A promise that gets resolved to true when execution is allowed to proceed or
10417
- * false if the time given elapses before a permit becomes available.
10418
- */
10419
- async waitFor(milliseconds) {
10420
- if (this.permits > 0) {
10421
- this.permits -= 1;
10422
- return Promise.resolve(true);
10423
- }
10424
- let resolver = (_) => void 0;
10425
- const promise = new Promise((r) => {
10426
- resolver = r;
10427
- });
10428
- this.promiseResolverQueue.push(resolver);
10429
- setTimeout(() => {
10430
- const index = this.promiseResolverQueue.indexOf(resolver);
10431
- if (index !== -1) {
10432
- this.promiseResolverQueue.splice(index, 1);
10433
- } else {
10434
- console.warn(`Semaphore.waitFor couldn't find its promise resolver in the queue`);
10435
- }
10436
- resolver(false);
10437
- }, milliseconds);
10438
- return promise;
10439
- }
10440
- /**
10441
- * Synchronous function that tries to acquire a permit and returns true if successful, false otherwise.
10442
- * @returns Whether a permit could be acquired.
10443
- */
10444
- tryAcquire() {
10445
- if (this.permits > 0) {
10446
- this.permits -= 1;
10447
- return true;
10746
+ async startJob() {
10747
+ const session = await this._getSession();
10748
+ if (!session.baseUrl) {
10749
+ return Promise.reject("session.baseUrl must not ne null");
10448
10750
  }
10449
- return false;
10450
- }
10451
- /**
10452
- * Acquires all permits that are currently available and returns the number of acquired permits.
10453
- * @returns Number of acquired permits.
10454
- */
10455
- drainPermits() {
10456
- if (this.permits > 0) {
10457
- const permitCount = this.permits;
10458
- this.permits = 0;
10459
- return permitCount;
10751
+ const whip = new WebrtcWhip(
10752
+ this._mediaStream,
10753
+ async () => {
10754
+ return session;
10755
+ },
10756
+ this._client,
10757
+ true,
10758
+ this._requestLogger
10759
+ );
10760
+ whip.start().then((_) => {
10761
+ this._requestLogger.debug("direct whip for ingressId=%s successful", whip.ingressId());
10762
+ }).catch((reason) => {
10763
+ this._controller.abort(reason);
10764
+ });
10765
+ const body = {
10766
+ sourceType: "WHIP",
10767
+ whipIngressId: whip.ingressId(),
10768
+ params: this._params.componentParams,
10769
+ roi: this._params.roi,
10770
+ fps: this._params.fps,
10771
+ camera: this._params.camera,
10772
+ version: this._version
10773
+ };
10774
+ if (this._params.motionDetect) {
10775
+ Object.assign(body, this._params.motionDetect);
10460
10776
  }
10461
- return 0;
10777
+ const headers = {
10778
+ ...session.authenticationHeaders(),
10779
+ Accept: "application/jsonl",
10780
+ "Content-Type": "application/json"
10781
+ };
10782
+ const patchUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?mode=preempt&processing=sync`;
10783
+ return await this._client.fetch(patchUrl, {
10784
+ headers,
10785
+ method: "PATCH",
10786
+ body: JSON.stringify(body),
10787
+ signal: this._controller.signal,
10788
+ // @ts-ignore
10789
+ eyepop: { responseStreaming: true }
10790
+ });
10462
10791
  }
10463
- /**
10464
- * Increases the number of permits by one. If there are other functions waiting, one of them will
10465
- * continue to execute in a future iteration of the event loop.
10466
- */
10467
- signal() {
10468
- this.permits += 1;
10469
- if (this.permits > 1 && this.promiseResolverQueue.length > 0) {
10470
- console.warn("Semaphore.permits should never be > 0 when there is someone waiting.");
10471
- } else if (this.permits === 1 && this.promiseResolverQueue.length > 0) {
10472
- this.permits -= 1;
10473
- const nextResolver = this.promiseResolverQueue.shift();
10474
- if (nextResolver) {
10475
- nextResolver(true);
10792
+ };
10793
+
10794
+ // shims/local_file.ts
10795
+ var resolvePath;
10796
+ if ("document" in globalThis && "implementation" in globalThis.document) {
10797
+ resolvePath = async (_source, _logger) => {
10798
+ throw new DOMException("resolving a path to a file is not supported in browser");
10799
+ };
10800
+ } else {
10801
+ let iteratorToStream = function(iterator) {
10802
+ return new ReadableStream({
10803
+ async pull(controller) {
10804
+ const { value, done } = await iterator.next();
10805
+ if (done) {
10806
+ controller.close();
10807
+ } else {
10808
+ controller.enqueue(new Uint8Array(value));
10809
+ }
10476
10810
  }
10811
+ });
10812
+ };
10813
+ iteratorToStream2 = iteratorToStream;
10814
+ async function* nodeStreamToIterator(stream) {
10815
+ for await (const chunk of stream) {
10816
+ yield chunk;
10477
10817
  }
10478
10818
  }
10479
- /**
10480
- * Alias for {@linkcode Semaphore.signal}.
10481
- */
10482
- release() {
10483
- this.signal();
10819
+ resolvePath = async (source, _) => {
10820
+ const mime = require_mime_types();
10821
+ const filehandle = __require("fs/promises");
10822
+ const fd = await filehandle.open(source.path, "r");
10823
+ const size = (await fd.stat()).size;
10824
+ const stream = fd.createReadStream();
10825
+ const iterator = nodeStreamToIterator(stream);
10826
+ const webStream = iteratorToStream(iterator);
10827
+ const mimeType = source.mimeType ? source.mimeType : mime.lookup(source.path) || "image/*";
10828
+ return {
10829
+ stream: webStream,
10830
+ mimeType,
10831
+ size
10832
+ };
10833
+ };
10834
+ }
10835
+ var iteratorToStream2;
10836
+
10837
+ // worker/worker_options.ts
10838
+ var TransientPopId = /* @__PURE__ */ ((TransientPopId2) => {
10839
+ TransientPopId2["Transient"] = "transient";
10840
+ return TransientPopId2;
10841
+ })(TransientPopId || {});
10842
+
10843
+ // worker/session_backend.ts
10844
+ var _SessionBackend = class _SessionBackend {
10845
+ constructor(ctx) {
10846
+ this.ctx = ctx;
10847
+ this._baseUrl = null;
10848
+ this._pipelineId = null;
10849
+ this._popName = null;
10850
+ this._sessionAccessToken = null;
10851
+ this._sessionAccessTokenValidUntil = null;
10852
+ // The account the connected compute session runs under, as compute-api reports it.
10853
+ this._accountId = void 0;
10854
+ this._pipeline = null;
10855
+ this._pipelineCreatePromise = null;
10484
10856
  }
10485
- /**
10486
- * Schedules func to be called once a permit becomes available.
10487
- * Returns a promise that resolves to the return value of func.
10488
- * @typeparam T The return type of func.
10489
- * @param func The function to be executed.
10490
- * @return A promise that gets resolved with the return value of the function.
10491
- */
10492
- async execute(func) {
10493
- await this.wait();
10494
- try {
10495
- return await func();
10496
- } finally {
10497
- this.signal();
10857
+ async disconnect(closeEndpoint) {
10858
+ await this.deleteOwnPipeline();
10859
+ await closeEndpoint();
10860
+ }
10861
+ async deleteOwnPipeline() {
10862
+ const client = this.ctx.client();
10863
+ if (this.ctx.options.popId == "transient" /* Transient */ && this._pipelineId && client) {
10864
+ const deleteUrl = `${this._baseUrl}/pipelines/${this._pipelineId}`;
10865
+ const headers = {
10866
+ Authorization: await this.authorizationHeader()
10867
+ };
10868
+ let response = await client.fetch(deleteUrl, {
10869
+ method: "DELETE",
10870
+ headers
10871
+ });
10872
+ if (response?.status != 204) {
10873
+ const message = await response?.text();
10874
+ this.ctx.logger.info(`stopping pipeline failed, status ${response?.status}: ${message}`);
10875
+ }
10498
10876
  }
10499
10877
  }
10500
- };
10501
-
10502
- // endpoint.ts
10503
- import { pino } from "pino";
10504
-
10505
- // shims/browser_session.ts
10506
- import { createAuth0Client } from "@auth0/auth0-spa-js";
10507
- var authenticateBrowserSession;
10508
- if ("document" in globalThis && "implementation" in globalThis.document) {
10509
- authenticateBrowserSession = async (auth0, options) => {
10510
- if (options.eyepopUrl == null) {
10511
- return Promise.reject("options.eyepopUrl cannot be null");
10878
+ onStatus(statusCode) {
10879
+ if (statusCode == 404) {
10880
+ this._pipelineId = null;
10881
+ this._baseUrl = null;
10512
10882
  }
10513
- const auth0ClientOptions = {
10514
- clientId: auth0.clientId,
10515
- domain: auth0.domain,
10516
- authorizationParams: {
10517
- scope: auth0.scope,
10518
- audience: auth0.audience
10883
+ }
10884
+ async session() {
10885
+ let session = null;
10886
+ if (this._baseUrl == null) {
10887
+ await this.reconnect();
10888
+ if (this._baseUrl == null) {
10889
+ return Promise.reject("endpoint not connected");
10519
10890
  }
10520
- };
10521
- const auth0Client = await createAuth0Client(auth0ClientOptions);
10522
- if (!await auth0Client.isAuthenticated()) {
10523
- await auth0Client.loginWithPopup();
10524
- }
10525
- const accessToken = await auth0Client.getTokenSilently();
10526
- if (!accessToken) {
10527
- return Promise.reject("auth0 login failed");
10528
10891
  }
10529
- let session;
10530
- const workerOptions = options;
10531
- if (typeof workerOptions.popId != "undefined") {
10532
- const workerSession = {
10533
- eyepopUrl: options.eyepopUrl,
10534
- accessToken,
10535
- validUntil: Date.now() + 60 * 60 * 1e3,
10536
- popId: workerOptions.popId,
10537
- baseUrl: void 0,
10538
- pipelineId: void 0,
10539
- authenticationHeaders: () => {
10540
- return { Authorization: `Bearer ${accessToken}` };
10541
- }
10542
- };
10543
- session = workerSession;
10544
- } else {
10892
+ if (this._sessionAccessToken) {
10545
10893
  session = {
10546
- eyepopUrl: options.eyepopUrl,
10547
- accessToken,
10548
- validUntil: Date.now() + 60 * 60 * 1e3
10894
+ eyepopUrl: this.ctx.options.eyepopUrl,
10895
+ accessToken: this._sessionAccessToken,
10896
+ validUntil: this._sessionAccessTokenValidUntil || Number.MAX_VALUE
10549
10897
  };
10550
- }
10551
- return session;
10552
- };
10553
- } else {
10554
- authenticateBrowserSession = (_auth0, _options) => {
10555
- return Promise.reject("auth0 login not supported server-side");
10556
- };
10557
- }
10558
-
10559
- // endpoint.ts
10560
- var Endpoint = class {
10561
- constructor(options) {
10562
- this._options = options;
10563
- this._token = null;
10564
- this._expire_token_time = null;
10565
- this._client = null;
10566
- this._statusRetryHandlers = /* @__PURE__ */ new Map();
10567
- this._statusRetryHandlers.set(401, this.statusHandler401);
10568
- this._limit = new Semaphore(this._options.jobQueueLength ?? 1024);
10569
- this._state = "Idle" /* Idle */;
10570
- this._message = null;
10571
- this._stateChangeHandler = null;
10572
- let rootLogger;
10573
- if (options.logger) {
10574
- rootLogger = options.logger;
10575
10898
  } else {
10576
- rootLogger = pino();
10899
+ session = await this.ctx.credentialSession();
10900
+ }
10901
+ return {
10902
+ eyepopUrl: session.eyepopUrl,
10903
+ accessToken: session.accessToken,
10904
+ validUntil: session.validUntil,
10905
+ popId: this.ctx.options.popId,
10906
+ accountId: this._accountId ?? this.ctx.options.accountId,
10907
+ baseUrl: this._baseUrl,
10908
+ pipelineId: this._pipelineId || void 0,
10909
+ isPipelineMode: false,
10910
+ authenticationHeaders: () => this._authenticationHeaders(session)
10911
+ };
10912
+ }
10913
+ popName() {
10914
+ return this._popName;
10915
+ }
10916
+ pop() {
10917
+ if (this._pipeline) {
10918
+ return this._pipeline.pop || null;
10577
10919
  }
10578
- this._logger = rootLogger.child({ module: "eyepop" });
10579
- this._requestLogger = this._logger.child({ module: "requests" });
10920
+ return this.ctx.options.pop || null;
10580
10921
  }
10581
- setStatusRetryHandlers(statusRetryHandlers = void 0) {
10582
- this._statusRetryHandlers = new Map(statusRetryHandlers);
10583
- this._statusRetryHandlers.set(401, this.statusHandler401);
10922
+ /**
10923
+ * A select forward's results come only at V3. Only a Pop that selects asks
10924
+ * for it, so a worker too old to know V3 - which also rejects that Pop - is
10925
+ * never sent a version it would not accept.
10926
+ */
10927
+ predictionVersion() {
10928
+ return popSelects(this.pop()) ? 3 /* V3 */ : DEFAULT_PREDICTION_VERSION;
10584
10929
  }
10585
- eyepopUrl() {
10586
- if (this._options.eyepopUrl) {
10587
- return this._options.eyepopUrl.replace(/\/+$/, "");
10588
- } else {
10589
- return "https://compute.eyepop.ai";
10930
+ async changePop(pop) {
10931
+ if (this.ctx.options.popId == "transient" /* Transient */) {
10932
+ await this.changeTransientPop(pop);
10933
+ return;
10590
10934
  }
10591
- }
10592
- // TODO: deprecated Web API endpoint needed for POP_ID support
10593
- eyepopWebApiUrl() {
10594
- if (this._options.eyepopUrl) {
10595
- return this._options.eyepopUrl.replace(/\/+$/, "").replace(/compute\./, "web-api.");
10935
+ const client = this.ctx.client();
10936
+ const baseUrl = this._baseUrl;
10937
+ if (!baseUrl || !client) {
10938
+ return Promise.reject("endpoint not connected, use connect()");
10939
+ }
10940
+ let response = await this.ctx.fetchWithRetry(async () => {
10941
+ const session = await this.session();
10942
+ let headers = {
10943
+ "Content-Type": "application/json",
10944
+ ...this._authenticationHeaders(session)
10945
+ };
10946
+ const patch_url = `${session.baseUrl}/pipelines/${session.pipelineId}/pop`;
10947
+ return await client.fetch(patch_url, {
10948
+ method: "PATCH",
10949
+ body: JSON.stringify(pop),
10950
+ headers
10951
+ });
10952
+ });
10953
+ if (response.status != 204) {
10954
+ const message = await response.text();
10955
+ throw new Error(`Unexpected status ${response.status}: ${message}`);
10956
+ }
10957
+ this.ctx.options.pop = pop;
10958
+ if (this._pipeline) {
10959
+ this._pipeline.pop = pop;
10596
10960
  } else {
10597
- return "https://api.eyepop.ai";
10961
+ this._pipeline = {
10962
+ id: this._pipelineId || "unknown",
10963
+ state: "idle",
10964
+ startTime: (/* @__PURE__ */ new Date()).toString(),
10965
+ pop
10966
+ };
10598
10967
  }
10599
10968
  }
10600
- pendingJobs() {
10601
- return (this._options.jobQueueLength ?? 1024) - this._limit.getPermits();
10602
- }
10603
- state() {
10604
- return this._state;
10969
+ _authenticationHeaders(session) {
10970
+ return resolveAuth(this.ctx.options)?.isLocal !== void 0 ? {} : { Authorization: `Bearer ${session.accessToken}` };
10605
10971
  }
10606
- lastMessage() {
10607
- return this._message;
10972
+ async submit(work) {
10973
+ switch (work.kind) {
10974
+ case "source":
10975
+ return await this.process(work.source, work.params);
10976
+ case "uploadGroup":
10977
+ return await this.uploadGroup(work.paths, work.params);
10978
+ case "uploadStreamGroup":
10979
+ return await this.uploadStreamGroup(work.streams, work.mimeTypes, work.params);
10980
+ case "loadFromGroup":
10981
+ return await this.loadFromGroup(work.urls, work.params);
10982
+ }
10608
10983
  }
10609
- onStateChanged(handler) {
10610
- this._stateChangeHandler = handler;
10611
- return this;
10984
+ async process(source, params) {
10985
+ if (source.file !== void 0) {
10986
+ return this.uploadFile(source, params);
10987
+ } else if (source.stream !== void 0) {
10988
+ return this.uploadStream(source, params);
10989
+ } else if (source.path !== void 0) {
10990
+ return this.uploadPath(source, params);
10991
+ } else if (source.url !== void 0) {
10992
+ return this.loadFrom(source, params);
10993
+ } else if (source.assetUuid !== void 0) {
10994
+ return this.loadFromAssetUuid(source, params);
10995
+ } else if (source.mediaStream !== void 0) {
10996
+ return this.loadMediaStream(source, params);
10997
+ } else {
10998
+ return Promise.reject("unknown source type");
10999
+ }
10612
11000
  }
10613
- updateState(newState = void 0, message = void 0) {
10614
- const fromState = this._state;
10615
- if (newState == void 0) {
10616
- if (this._limit.getPermits() == this._options.jobQueueLength) {
10617
- newState = "Idle" /* Idle */;
10618
- } else {
10619
- newState = "Busy" /* Busy */;
11001
+ start(job) {
11002
+ return job.start(
11003
+ () => {
11004
+ this.ctx.jobDone();
11005
+ },
11006
+ (statusCode) => {
11007
+ if (statusCode == 404) {
11008
+ this.onStatus(statusCode);
11009
+ } else if (statusCode == 401) {
11010
+ this.ctx.forgetCredential();
11011
+ }
10620
11012
  }
11013
+ );
11014
+ }
11015
+ async uploadGroup(paths, params) {
11016
+ const client = this.ctx.client();
11017
+ const limit = this.ctx.limit();
11018
+ if (!this._baseUrl || !this._pipelineId || !client) {
11019
+ throw new Error("endpoint not connected, use connect()");
10621
11020
  }
10622
- this._state = newState;
10623
- this._message = message ? message : null;
10624
- if (this._stateChangeHandler) {
10625
- this._stateChangeHandler(fromState, this._state);
11021
+ await limit.acquire();
11022
+ try {
11023
+ this.ctx.updateState();
11024
+ const sources = await Promise.all(
11025
+ paths.map(async (path2) => {
11026
+ const streamSource = await this.resolvePath({ path: path2 });
11027
+ return { stream: streamSource.stream, mimeType: streamSource.mimeType };
11028
+ })
11029
+ );
11030
+ return this.start(new UploadGroupJob(sources, params, async () => this.session(), client, this.ctx.requestLogger, this.predictionVersion()));
11031
+ } catch (e) {
11032
+ limit.release();
11033
+ throw e;
10626
11034
  }
10627
11035
  }
10628
- async connect() {
10629
- if (this._client) {
10630
- this._logger.warn("endpoint already connected");
10631
- return this;
11036
+ async uploadStreamGroup(streams, mimeTypes, params) {
11037
+ const client = this.ctx.client();
11038
+ const limit = this.ctx.limit();
11039
+ if (!this._baseUrl || !this._pipelineId || !client) {
11040
+ throw new Error("endpoint not connected, use connect()");
10632
11041
  }
10633
- if (!this._options.eyepopUrl) {
10634
- return Promise.reject("option eyepopUrl or environment variable EYEPOP_URL is required");
11042
+ await limit.acquire();
11043
+ try {
11044
+ this.ctx.updateState();
11045
+ const sources = streams.map((stream, i) => ({
11046
+ stream,
11047
+ mimeType: mimeTypes?.[i] ?? null
11048
+ }));
11049
+ return this.start(new UploadGroupJob(sources, params, async () => this.session(), client, this.ctx.requestLogger, this.predictionVersion()));
11050
+ } catch (e) {
11051
+ limit.release();
11052
+ throw e;
10635
11053
  }
10636
- const auth = resolveAuth(this._options);
10637
- if (auth === void 0) {
10638
- return Promise.reject("cannot connect without defined auth option");
11054
+ }
11055
+ async loadFromGroup(urls, params) {
11056
+ const client = this.ctx.client();
11057
+ const limit = this.ctx.limit();
11058
+ if (!this._baseUrl || !this._pipelineId || !client) {
11059
+ throw new Error("endpoint not connected, use connect()");
10639
11060
  }
10640
- if (auth.session !== void 0) {
10641
- this._token = auth.session.accessToken;
10642
- this._expire_token_time = auth.session.validUntil / 1e3;
10643
- } else if (auth.oAuth2 !== void 0) {
10644
- } else if (accessTokenCredential(auth) !== void 0) {
10645
- } else if (apiKeyCredential(auth) !== void 0) {
10646
- } else if (auth.isLocal !== void 0) {
10647
- } else {
10648
- return Promise.reject("option apiKey/accessToken or environment variable EYEPOP_API_KEY is required");
11061
+ await limit.acquire();
11062
+ try {
11063
+ this.ctx.updateState();
11064
+ return this.start(new LoadFromGroupJob(urls, params, async () => this.session(), client, this.ctx.requestLogger, this.predictionVersion()));
11065
+ } catch (e) {
11066
+ limit.release();
11067
+ throw e;
10649
11068
  }
10650
- if (this._options.platformSupport?.createHttpClient !== void 0) {
10651
- this._client = await this._options.platformSupport.createHttpClient(this._requestLogger);
10652
- } else {
10653
- this._client = await createHttpClient(this._requestLogger);
11069
+ }
11070
+ async resolvePath(source) {
11071
+ if (this.ctx.options.platformSupport?.resolvePath) {
11072
+ return await this.ctx.options.platformSupport.resolvePath(source, this.ctx.logger);
10654
11073
  }
10655
- return await this.reconnect();
11074
+ return await resolvePath(source, this.ctx.logger);
10656
11075
  }
10657
- async disconnect(wait = true) {
10658
- if (wait && this._limit) {
10659
- for (let j = 0; j < this._options.jobQueueLength; j++) {
10660
- await this._limit.acquire();
11076
+ /** Start a job once the transient pipeline runs and a queue permit is held; the permit is released if it cannot start. */
11077
+ async startConnected(makeJob, rejectsWithText = false) {
11078
+ await this.ensureTransientPipelineStarted();
11079
+ const client = this.ctx.client();
11080
+ const limit = this.ctx.limit();
11081
+ if (!this._baseUrl || !this._pipelineId || !client) {
11082
+ if (rejectsWithText) {
11083
+ return Promise.reject("endpoint not connected, use connect()");
10661
11084
  }
11085
+ throw new Error("endpoint not connected, use connect()");
10662
11086
  }
10663
- this._limit = new Semaphore(this._options.jobQueueLength ?? 1024);
10664
- if (this._client) {
10665
- await this._client.close();
10666
- this._client = null;
11087
+ await limit.acquire();
11088
+ try {
11089
+ this.ctx.updateState();
11090
+ return this.start(await makeJob(client));
11091
+ } catch (e) {
11092
+ limit.release();
11093
+ throw e;
10667
11094
  }
10668
11095
  }
10669
- async session() {
10670
- await this.currentAccessToken();
10671
- if (this._token == null || this._expire_token_time == null) {
10672
- return Promise.reject("endpoint not connected");
10673
- }
10674
- return {
10675
- eyepopUrl: this._options.eyepopUrl,
10676
- accessToken: this._token,
10677
- validUntil: this._expire_token_time * 1e3
10678
- };
11096
+ async uploadFile(source, params) {
11097
+ return await this.startConnected(
11098
+ async (client) => new UploadJob(
11099
+ source.file,
11100
+ source.file.type,
11101
+ source.file.size,
11102
+ source.videoMode,
11103
+ params,
11104
+ async () => this.session(),
11105
+ client,
11106
+ this.ctx.requestLogger,
11107
+ void 0,
11108
+ this.predictionVersion()
11109
+ ),
11110
+ true
11111
+ );
10679
11112
  }
10680
- statusHandler401(_statusCode) {
10681
- this._token = null;
10682
- this._expire_token_time = null;
11113
+ async uploadStream(source, params) {
11114
+ return await this.startConnected(
11115
+ async (client) => new UploadJob(source.stream, source.mimeType, source.size, source.videoMode, params, async () => this.session(), client, this.ctx.requestLogger, void 0, this.predictionVersion()),
11116
+ true
11117
+ );
10683
11118
  }
10684
- async fetchWithRetry(fetcher, retries = 1) {
10685
- while (true) {
10686
- try {
10687
- const response = await fetcher();
10688
- if (response.ok) {
10689
- return response;
10690
- } else {
10691
- const handler = this._statusRetryHandlers.get(response.status);
10692
- if (handler != null) {
10693
- handler(response.status);
10694
- if (retries--) {
10695
- this._requestLogger.info("received %d response, retry", response.status);
10696
- } else {
10697
- return response;
10698
- }
10699
- } else {
10700
- return response;
10701
- }
11119
+ async uploadPath(source, params) {
11120
+ return await this.startConnected(async (client) => {
11121
+ const streamSource = await this.resolvePath(source);
11122
+ let firstStreamPending = true;
11123
+ const uploadBodyFactory = async () => {
11124
+ if (firstStreamPending) {
11125
+ firstStreamPending = false;
11126
+ return streamSource.stream;
10702
11127
  }
10703
- } catch (error) {
10704
- const handler = this._statusRetryHandlers.get(404);
10705
- if (handler != null) {
10706
- handler(error);
10707
- if (retries--) {
10708
- this._requestLogger.info("error %s, retry", error);
10709
- } else {
10710
- return Promise.reject(`error ${error}`);
10711
- }
10712
- } else {
10713
- return Promise.reject(`error ${error}`);
11128
+ return (await this.resolvePath(source)).stream;
11129
+ };
11130
+ return new UploadJob(
11131
+ streamSource.stream,
11132
+ streamSource.mimeType,
11133
+ streamSource.size,
11134
+ source.videoMode,
11135
+ params,
11136
+ async () => this.session(),
11137
+ client,
11138
+ this.ctx.requestLogger,
11139
+ uploadBodyFactory,
11140
+ this.predictionVersion()
11141
+ );
11142
+ });
11143
+ }
11144
+ async loadFrom(source, params) {
11145
+ if (source.url.startsWith("file://")) {
11146
+ return await this.uploadPath({ path: source.url.substring("file://".length) }, params);
11147
+ }
11148
+ return await this.startConnected(async (client) => new LoadFromJob(source.url, params, async () => this.session(), client, this.ctx.requestLogger, this.predictionVersion()));
11149
+ }
11150
+ async loadFromAssetUuid(source, params) {
11151
+ return await this.startConnected(async (client) => new LoadFromAssetUuidJob(source.assetUuid, params, async () => this.session(), client, this.ctx.requestLogger, this.predictionVersion()));
11152
+ }
11153
+ async loadMediaStream(source, params) {
11154
+ return await this.startConnected(async (client) => new LoadMediaStreamJob(source.mediaStream, params, async () => this.session(), client, this.ctx.requestLogger, this.predictionVersion()));
11155
+ }
11156
+ async reconnect() {
11157
+ if (this.ctx.options.popId == "transient" /* Transient */) {
11158
+ await this.getComputeSession();
11159
+ if (this.ctx.options.pop) {
11160
+ this.setTransientPipelinePop();
11161
+ }
11162
+ } else if (this.ctx.options.sessionUuid) {
11163
+ await this.getComputeSession();
11164
+ } else {
11165
+ await this.startWebApiSessionWithPopId();
11166
+ }
11167
+ this.ctx.updateState();
11168
+ }
11169
+ async startWebApiSessionWithPopId() {
11170
+ const client = this.ctx.client();
11171
+ if (!client) {
11172
+ throw new Error("endpoint not initialized");
11173
+ }
11174
+ const config_url = `${this.ctx.eyepopWebApiUrl()}/pops/${this.ctx.options.popId}/config?auto_start=false`;
11175
+ const headers = {
11176
+ Authorization: await this.authorizationHeader()
11177
+ };
11178
+ this.ctx.updateState("FetchConfig" /* FetchConfig */);
11179
+ let response = await client.fetch(config_url, { headers });
11180
+ if (response.status == 401) {
11181
+ this.ctx.requestLogger.debug("GET %s: 401, about to retry with fresh access token", config_url);
11182
+ this.ctx.forgetCredential();
11183
+ const headers2 = {
11184
+ Authorization: await this.authorizationHeader()
11185
+ };
11186
+ this.ctx.updateState("FetchConfig" /* FetchConfig */);
11187
+ response = await client.fetch(config_url, {
11188
+ headers: headers2
11189
+ });
11190
+ }
11191
+ if (response.status != 200) {
11192
+ const message = await response.text();
11193
+ if (response.status == 429) {
11194
+ this.ctx.updateState("NotAvailable" /* NotAvailable */, message);
11195
+ } else {
11196
+ this.ctx.updateState("Error" /* Error */, message);
11197
+ }
11198
+ throw new Error(`Unexpected status ${response.status}: ${message}`);
11199
+ }
11200
+ let config = await response.json();
11201
+ if (!config.base_url && this.ctx.options.autoStart) {
11202
+ const auto_start_config_url = `${this.ctx.eyepopWebApiUrl()}/pops/${this.ctx.options.popId}/config?auto_start=true`;
11203
+ this.ctx.requestLogger.debug("pop was not running, trying to autostart with: %s", auto_start_config_url);
11204
+ const headers2 = {
11205
+ Authorization: await this.authorizationHeader()
11206
+ };
11207
+ response = await client.fetch(auto_start_config_url, {
11208
+ headers: headers2
11209
+ });
11210
+ if (response.status != 200) {
11211
+ this.ctx.updateState("Error" /* Error */);
11212
+ const message = await response.text();
11213
+ throw new Error(`Unexpected status ${response.status}: ${message}`);
11214
+ }
11215
+ config = await response.json();
11216
+ }
11217
+ const baseUrl = new URL(config.base_url, this.ctx.eyepopUrl());
11218
+ this._baseUrl = baseUrl.toString();
11219
+ if (this._baseUrl) {
11220
+ this._baseUrl = this._baseUrl.replace(/\/+$/, "");
11221
+ this._pipelineId = config.pipeline_id;
11222
+ this._popName = config.name;
11223
+ if (!this._pipelineId || !this._baseUrl) {
11224
+ throw new Error("Pop not started");
11225
+ }
11226
+ const get_url = `${this._baseUrl}/pipelines/${this._pipelineId}`;
11227
+ const headers2 = {
11228
+ Authorization: await this.authorizationHeader()
11229
+ };
11230
+ let response2 = await client.fetch(get_url, {
11231
+ method: "GET",
11232
+ headers: headers2
11233
+ });
11234
+ if (response2.status > 200) {
11235
+ const message = await response2.text();
11236
+ throw new Error(`Unexpected status ${response2.status}: ${message}`);
11237
+ }
11238
+ this._pipeline = await response2.json();
11239
+ if (this.ctx.options.stopJobs) {
11240
+ const body = { sourceType: "NONE" };
11241
+ const headers3 = {
11242
+ Authorization: await this.authorizationHeader(),
11243
+ "Content-Type": "application/json"
11244
+ };
11245
+ const stop_url = `${this._baseUrl}/pipelines/${this._pipeline.id}/source?mode=preempt&processing=sync`;
11246
+ let response3 = await client.fetch(stop_url, {
11247
+ method: "PATCH",
11248
+ headers: headers3,
11249
+ body: JSON.stringify(body)
11250
+ });
11251
+ if (response3.status >= 300) {
11252
+ const message = await response3.text();
11253
+ throw new Error(`Unexpected status ${response3.status}: ${message}`);
10714
11254
  }
10715
11255
  }
10716
11256
  }
10717
11257
  }
10718
- async reconnect() {
10719
- throw new Error("reconnect() not implemented");
11258
+ sessionReadyTimeoutMs() {
11259
+ const seconds = this.ctx.options.sessionReadyTimeoutSeconds;
11260
+ return typeof seconds == "number" && Number.isFinite(seconds) && seconds > 0 ? seconds * 1e3 : _SessionBackend.WAIT_FOR_IS_READY;
10720
11261
  }
10721
- async authorizationHeader() {
10722
- if (resolveAuth(this._options)?.isLocal !== void 0) {
10723
- return `Implicit`;
11262
+ async getComputeSession() {
11263
+ const client = this.ctx.client();
11264
+ if (!client) {
11265
+ throw new Error("endpoint not initialized");
10724
11266
  }
10725
- return `Bearer ${await this.currentAccessToken()}`;
10726
- }
10727
- async currentAccessToken() {
10728
- const now = Date.now() / 1e3;
10729
- if (!this._token || this._expire_token_time < now) {
10730
- this.updateState("Authenticating" /* Authenticating */);
10731
- try {
10732
- const auth = resolveAuth(this._options);
10733
- if (!this._client) {
10734
- return Promise.reject("endpoint not connected");
10735
- } else if (auth?.isLocal !== void 0) {
10736
- this._token = "implicit";
10737
- this._expire_token_time = Number.MAX_VALUE;
10738
- } else if (auth?.session !== void 0) {
10739
- return Promise.reject("temporary access token expired");
10740
- } else if (auth?.oAuth2 !== void 0 && this._options.eyepopUrl) {
10741
- const session = await authenticateBrowserSession(auth.oAuth2, this._options);
10742
- this._token = session.accessToken;
10743
- this._expire_token_time = session.validUntil / 1e3;
10744
- } else if (accessTokenCredential(auth) !== void 0) {
10745
- this._token = accessTokenCredential(auth);
10746
- this._expire_token_time = Number.MAX_VALUE;
10747
- } else if (this._options.eyepopUrl) {
10748
- const credential = apiKeyCredential(auth);
10749
- if (credential === void 0) {
10750
- return Promise.reject("no valid auth option");
10751
- }
10752
- const headers = { Authorization: `Bearer ${credential}` };
10753
- const post_url = `${this.eyepopUrl()}/v1/auth/authenticate`;
10754
- const response = await this._client.fetch(post_url, {
10755
- method: "POST",
10756
- headers
10757
- });
10758
- if (response.status != 200) {
10759
- const message = await response.text();
10760
- return Promise.reject(`Unexpected status ${response.status}: ${message}`);
10761
- }
10762
- const data = await response.json();
10763
- const token = data;
10764
- this._token = token.access_token;
10765
- this._expire_token_time = now + token.expires_in - 60;
10766
- } else {
10767
- return Promise.reject("no valid auth option");
10768
- }
10769
- } finally {
10770
- this.updateState();
10771
- }
11267
+ let resolved;
11268
+ try {
11269
+ resolved = await new ComputeSessionClient({
11270
+ computeUrl: this.ctx.eyepopUrl(),
11271
+ httpClient: client,
11272
+ authorizationHeader: () => this.computeAuthorizationHeader(),
11273
+ sessionUuid: this.ctx.options.sessionUuid,
11274
+ sessionName: this.ctx.options.sessionName,
11275
+ accountId: this.ctx.options.accountId,
11276
+ pipelineImage: this.ctx.options.pipelineImage,
11277
+ pipelineVersion: this.ctx.options.pipelineVersion,
11278
+ pop: this.ctx.options.pop,
11279
+ readyTimeoutMs: this.sessionReadyTimeoutMs(),
11280
+ logger: this.ctx.logger,
11281
+ requestLogger: this.ctx.requestLogger
11282
+ }).resolve();
11283
+ } catch (error) {
11284
+ this.ctx.updateState("Error" /* Error */);
11285
+ throw error;
10772
11286
  }
10773
- this._logger.debug("using access token, valid for at least %d seconds", this._expire_token_time - now);
10774
- return this._token;
10775
- }
10776
- };
10777
-
10778
- // worker/worker_options.ts
10779
- var TransientPopId = /* @__PURE__ */ ((TransientPopId2) => {
10780
- TransientPopId2["Transient"] = "transient";
10781
- return TransientPopId2;
10782
- })(TransientPopId || {});
10783
-
10784
- // worker/worker_endpoint.ts
10785
- var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10786
- constructor(options) {
10787
- super(options);
10788
- this.setStatusRetryHandlers(/* @__PURE__ */ new Map([[404, (statusCode) => this.statusHandler404(statusCode)]]));
10789
- this._baseUrl = null;
10790
- this._pipelineId = null;
10791
- this._popName = null;
10792
- this._sessionAccessToken = null;
10793
- this._sessionAccessTokenValidUntil = null;
10794
- this._accountId = void 0;
11287
+ this._sessionAccessToken = resolved.accessToken;
11288
+ this._sessionAccessTokenValidUntil = resolved.accessTokenValidUntil;
11289
+ this._baseUrl = resolved.session.session_endpoint;
11290
+ this._accountId = resolved.session.account_uuid || void 0;
10795
11291
  this._pipeline = null;
10796
- this._pipelineCreatePromise = null;
10797
- }
10798
- options() {
10799
- return this._options;
11292
+ this._pipelineId = resolved.pipelineId;
11293
+ if (this.ctx.options.pop) {
11294
+ this.setTransientPipelinePop();
11295
+ }
10800
11296
  }
10801
- async disconnect(wait = true) {
10802
- if (this.options().popId == "transient" /* Transient */ && this._pipelineId) {
10803
- const deleteUrl = `${this._baseUrl}/pipelines/${this._pipelineId}`;
10804
- const headers = {
11297
+ async startTransientPipeline() {
11298
+ const client = this.ctx.client();
11299
+ const baseUrl = this._baseUrl;
11300
+ if (client == null || baseUrl == null) {
11301
+ throw new Error("endpoint not initialized");
11302
+ }
11303
+ let pop;
11304
+ if (this.ctx.options.pop) {
11305
+ pop = this.ctx.options.pop;
11306
+ } else if (this._pipeline && this._pipeline.pop) {
11307
+ pop = this._pipeline.pop;
11308
+ } else {
11309
+ throw new Error("Cannot start a transient pipeline without a pop");
11310
+ }
11311
+ const body = {
11312
+ pop,
11313
+ source: {
11314
+ sourceType: "NONE"
11315
+ },
11316
+ idleTimeoutSeconds: 30,
11317
+ logging: ["out_meta"],
11318
+ videoOutput: "no_output"
11319
+ };
11320
+ const post_url = `${baseUrl.replace(/\/+$/, "")}/pipelines`;
11321
+ const response = await this.ctx.fetchWithRetry(async () => {
11322
+ let headers = {
11323
+ Accept: "application/json",
11324
+ "Content-Type": "application/json",
10805
11325
  Authorization: await this.authorizationHeader()
10806
11326
  };
10807
- let response = await this._client?.fetch(deleteUrl, {
10808
- method: "DELETE",
11327
+ return client.fetch(post_url, {
11328
+ method: "POST",
11329
+ body: JSON.stringify(body),
10809
11330
  headers
10810
11331
  });
10811
- if (response?.status != 204) {
10812
- const message = await response?.text();
10813
- this._logger.info(`stopping pipeline failed, status ${response?.status}: ${message}`);
10814
- }
11332
+ });
11333
+ if (response.status != 200) {
11334
+ const message = await response.text();
11335
+ throw new Error(`Unexpected status ${response.status} starting transient pipeline: ${message.slice(0, 300)}`);
10815
11336
  }
10816
- return super.disconnect(wait);
10817
- }
10818
- statusHandler404(_) {
10819
- this._pipelineId = null;
10820
- this._baseUrl = null;
11337
+ const result = await response.json();
11338
+ return result["id"];
10821
11339
  }
10822
- async session() {
10823
- let session = null;
10824
- if (this._baseUrl == null) {
11340
+ async ensureTransientPipelineStarted() {
11341
+ if (this.ctx.options.popId != "transient" /* Transient */ || this._pipelineId) {
11342
+ return;
11343
+ }
11344
+ if (!this.ctx.options.pop) {
11345
+ return;
11346
+ }
11347
+ if (!this._baseUrl) {
10825
11348
  await this.reconnect();
10826
- if (this._baseUrl == null) {
10827
- return Promise.reject("endpoint not connected");
11349
+ }
11350
+ if (this._pipelineId) {
11351
+ return;
11352
+ }
11353
+ if (this.ctx.options.pop) {
11354
+ await this.reconnect();
11355
+ if (!this._pipelineId) {
11356
+ throw new Error("Compute session did not provide a pipeline for the requested pop");
10828
11357
  }
11358
+ return;
10829
11359
  }
10830
- if (this._sessionAccessToken) {
10831
- session = {
10832
- eyepopUrl: this.options().eyepopUrl,
10833
- accessToken: this._sessionAccessToken,
10834
- validUntil: this._sessionAccessTokenValidUntil || Number.MAX_VALUE
10835
- };
10836
- } else {
10837
- session = await super.session();
11360
+ if (!this._pipelineCreatePromise) {
11361
+ this._pipelineCreatePromise = this.startTransientPipeline();
11362
+ }
11363
+ try {
11364
+ this._pipelineId = await this._pipelineCreatePromise;
11365
+ this.setTransientPipelinePop();
11366
+ } finally {
11367
+ this._pipelineCreatePromise = null;
10838
11368
  }
10839
- return {
10840
- eyepopUrl: session.eyepopUrl,
10841
- accessToken: session.accessToken,
10842
- validUntil: session.validUntil,
10843
- popId: this.options().popId,
10844
- accountId: this._accountId ?? this.options().accountId,
10845
- baseUrl: this._baseUrl,
10846
- pipelineId: this._pipelineId || void 0,
10847
- authenticationHeaders: () => this._authenticationHeaders(session)
10848
- };
10849
11369
  }
10850
- popName() {
10851
- return this._popName;
11370
+ async changeTransientPop(pop) {
11371
+ if (!this.ctx.client()) {
11372
+ throw new Error("endpoint not initialized");
11373
+ }
11374
+ const previousPop = this.ctx.options.pop;
11375
+ const previousBaseUrl = this._baseUrl;
11376
+ const previousSessionAccessToken = this._sessionAccessToken;
11377
+ const previousSessionAccessTokenValidUntil = this._sessionAccessTokenValidUntil;
11378
+ if (this._pipelineId) {
11379
+ await this.deleteTransientPipeline(this._pipelineId);
11380
+ this._pipelineId = null;
11381
+ }
11382
+ this._pipeline = null;
11383
+ try {
11384
+ this.ctx.options.pop = pop;
11385
+ await this.getComputeSession();
11386
+ } catch (error) {
11387
+ this.ctx.options.pop = previousPop;
11388
+ this._pipeline = null;
11389
+ this._pipelineId = null;
11390
+ this._baseUrl = previousPop ? null : previousBaseUrl;
11391
+ this._sessionAccessToken = previousSessionAccessToken;
11392
+ this._sessionAccessTokenValidUntil = previousSessionAccessTokenValidUntil;
11393
+ throw error;
11394
+ }
11395
+ if (!this._pipelineId) {
11396
+ throw new Error("Compute session did not provide a pipeline for the requested pop");
11397
+ }
11398
+ this.setTransientPipelinePop();
10852
11399
  }
10853
- pop() {
10854
- if (this._pipeline) {
10855
- return this._pipeline.pop || null;
11400
+ async deleteTransientPipeline(pipelineId) {
11401
+ const client = this.ctx.client();
11402
+ if (!client || !this._baseUrl) {
11403
+ return;
11404
+ }
11405
+ const deleteUrl = `${this._baseUrl}/pipelines/${pipelineId}`;
11406
+ const headers = {
11407
+ Authorization: await this.authorizationHeader()
11408
+ };
11409
+ let response = await client.fetch(deleteUrl, {
11410
+ method: "DELETE",
11411
+ headers
11412
+ });
11413
+ if (response.status >= 300) {
11414
+ const message = await response.text();
11415
+ this.ctx.logger.info(`stopping pipeline failed, status ${response.status}: ${message}`);
10856
11416
  }
10857
- return this.options().pop || null;
10858
11417
  }
10859
- async changePop(pop) {
10860
- validatePop(pop);
10861
- if (this.options().popId == "transient" /* Transient */) {
10862
- await this.changeTransientPop(pop);
10863
- return;
11418
+ async authorizationHeader() {
11419
+ if (this._sessionAccessToken) {
11420
+ return `Bearer ${this._sessionAccessToken}`;
10864
11421
  }
10865
- const client = this._client;
10866
- const baseUrl = this._baseUrl;
10867
- if (!baseUrl || !client) {
10868
- return Promise.reject("endpoint not connected, use connect()");
11422
+ return this.ctx.credentialHeader();
11423
+ }
11424
+ async computeAuthorizationHeader() {
11425
+ const auth = resolveAuth(this.ctx.options);
11426
+ if (auth?.isLocal !== void 0) {
11427
+ return "Implicit";
10869
11428
  }
10870
- let response = await this.fetchWithRetry(async () => {
10871
- const session = await this.session();
10872
- let headers = {
10873
- "Content-Type": "application/json",
10874
- ...this._authenticationHeaders(session)
10875
- };
10876
- const patch_url = `${session.baseUrl}/pipelines/${session.pipelineId}/pop`;
10877
- return await client.fetch(patch_url, {
10878
- method: "PATCH",
10879
- body: JSON.stringify(pop),
10880
- headers
10881
- });
10882
- });
10883
- if (response.status != 204) {
10884
- const message = await response.text();
10885
- throw new Error(`Unexpected status ${response.status}: ${message}`);
11429
+ const credential = apiKeyCredential(auth) ?? accessTokenCredential(auth);
11430
+ if (credential !== void 0) {
11431
+ return `Bearer ${credential}`;
10886
11432
  }
10887
- this.options().pop = pop;
10888
- if (this._pipeline) {
10889
- this._pipeline.pop = pop;
10890
- } else {
11433
+ return this.ctx.credentialHeader();
11434
+ }
11435
+ setTransientPipelinePop() {
11436
+ const pop = this.ctx.options.pop;
11437
+ if (pop && this._pipelineId) {
10891
11438
  this._pipeline = {
10892
- id: this._pipelineId || "unknown",
11439
+ id: this._pipelineId,
10893
11440
  state: "idle",
10894
11441
  startTime: (/* @__PURE__ */ new Date()).toString(),
10895
11442
  pop
10896
11443
  };
10897
11444
  }
10898
11445
  }
10899
- _authenticationHeaders(session) {
10900
- return resolveAuth(this._options)?.isLocal !== void 0 ? {} : { Authorization: `Bearer ${session.accessToken}` };
11446
+ };
11447
+ _SessionBackend.WAIT_FOR_IS_READY = 60 * 1e3;
11448
+ var SessionBackend = _SessionBackend;
11449
+
11450
+ // shims/websockets.ts
11451
+ var createWebSocket;
11452
+ var UpgradeError = class extends Error {
11453
+ constructor(message, status, retryAfter) {
11454
+ super(message);
11455
+ this.name = "UpgradeError";
11456
+ this.status = status;
11457
+ this.retryAfter = retryAfter;
10901
11458
  }
10902
- /**
10903
- * @deprecated Pop ids are only retained for backwards compatibility.
10904
- * Configure transient sessions with `WorkerOptions.pop`.
10905
- */
10906
- popId() {
10907
- return this.options().popId || null;
11459
+ };
11460
+ var openAuthorizedWebSocket;
11461
+ var isBrowser = "document" in globalThis && "implementation" in globalThis.document;
11462
+ if (isBrowser) {
11463
+ createWebSocket = (url, protocols) => {
11464
+ return new WebSocket(url, protocols);
11465
+ };
11466
+ openAuthorizedWebSocket = (url, protocol, token) => {
11467
+ const protocols = token === void 0 ? [protocol] : [protocol, `eyepop.bearer.${token}`];
11468
+ const ws = new WebSocket(url, protocols);
11469
+ ws.binaryType = "arraybuffer";
11470
+ return new Promise((resolve, reject) => {
11471
+ ws.onopen = () => resolve(ws);
11472
+ ws.onerror = () => reject(new UpgradeError(`could not open ${url}`));
11473
+ });
11474
+ };
11475
+ } else {
11476
+ createWebSocket = (url, protocols) => {
11477
+ const ws = __require("ws");
11478
+ return new ws.WebSocket(url, protocols);
11479
+ };
11480
+ openAuthorizedWebSocket = (url, protocol, token) => {
11481
+ const ws = __require("ws");
11482
+ const headers = token === void 0 ? {} : { Authorization: `Bearer ${token}` };
11483
+ const socket = new ws.WebSocket(url, [protocol], { headers, maxPayload: 0 });
11484
+ socket.binaryType = "arraybuffer";
11485
+ return new Promise((resolve, reject) => {
11486
+ socket.once("open", () => resolve(socket));
11487
+ socket.once("unexpected-response", (_request, response) => {
11488
+ const retryAfter = Number.parseFloat(response.headers?.["retry-after"]);
11489
+ reject(new UpgradeError(`the pipeline API answered the upgrade ${response.statusCode}`, response.statusCode, Number.isFinite(retryAfter) ? retryAfter : void 0));
11490
+ response.resume();
11491
+ socket.terminate();
11492
+ });
11493
+ socket.once("error", (error) => reject(new UpgradeError(`could not open ${url}: ${error.message}`)));
11494
+ });
11495
+ };
11496
+ }
11497
+
11498
+ // worker/pipeline_backend.ts
11499
+ var PIPELINE_PATH = "/api/v1/pipeline";
11500
+ var SUBPROTOCOL = "eyepop.v1";
11501
+ var MAX_OPEN_SOURCES = 256;
11502
+ var COMBINED_UPLOAD_MAX_BYTES = 8 * 1024 * 1024;
11503
+ var CHUNK = 1;
11504
+ var COMBINED = 2;
11505
+ var CHUNK_BYTES = 64 * 1024;
11506
+ var HEARTBEAT_MS = 20 * 1e3;
11507
+ var MAX_RETRY_MS = 30 * 1e3;
11508
+ var RECONNECT_AT_ONCE = /* @__PURE__ */ new Set(["1008 credential_expired", "1011 overloaded", "1001 shutting_down"]);
11509
+ var MAX_RECONNECT_DELAY_MS = 30 * 1e3;
11510
+ var PipelineConfigurationError = class extends Error {
11511
+ constructor(problem, message, options = []) {
11512
+ super(message);
11513
+ this.name = "PipelineConfigurationError";
11514
+ this.problem = problem;
11515
+ this.options = options;
10908
11516
  }
10909
- async process(param1, param2) {
10910
- if (param1.source !== void 0) {
10911
- return await this.processV2(param1);
10912
- } else {
10913
- return await this.processV1(param1, param2);
10914
- }
11517
+ };
11518
+ var PipelineSocketClosedError = class extends Error {
11519
+ constructor(code, reason) {
11520
+ super(`the pipeline API closed the socket: ${code ?? ""} ${reason}`.trim());
11521
+ this.name = "PipelineSocketClosedError";
11522
+ this.code = code;
11523
+ this.reason = reason;
10915
11524
  }
10916
- async processV1(source, params) {
10917
- if (source.file !== void 0) {
10918
- return this.uploadFile(source, { componentParams: params });
10919
- } else if (source.stream !== void 0) {
10920
- return this.uploadStream(source, { componentParams: params });
10921
- } else if (source.path !== void 0) {
10922
- return this.uploadPath(source, { componentParams: params });
10923
- } else if (source.url !== void 0) {
10924
- return this.loadFrom(source, { componentParams: params });
10925
- } else if (source.assetUuid !== void 0) {
10926
- return this.loadFromAssetUuid(source, { componentParams: params });
10927
- } else if (source.mediaStream !== void 0) {
10928
- return this.loadMediaStream(source, { componentParams: params });
10929
- } else {
10930
- return Promise.reject("unknown source type");
10931
- }
11525
+ };
11526
+ var PipelineCommandError = class extends Error {
11527
+ constructor(error, detail = "") {
11528
+ super(`the pipeline API refused the command: ${error} ${detail}`.trim());
11529
+ this.name = "PipelineCommandError";
11530
+ this.error = error;
11531
+ this.detail = detail;
10932
11532
  }
10933
- /**
10934
- * Reject a source's calibration before any work is started for it.
10935
- *
10936
- * Called by every entry point that takes ProcessParams rather than at the
10937
- * one boundary they share, because they have none: the group methods build
10938
- * their jobs directly instead of going through processV2. It runs before
10939
- * the concurrency limit is acquired, so a request that can never be sent
10940
- * does not hold a slot.
10941
- */
10942
- validateProcessParams(params) {
10943
- if (params.camera !== void 0) {
10944
- validateCamera(params.camera);
10945
- }
11533
+ };
11534
+ var PipelineSourceError = class extends Error {
11535
+ constructor(sourceId, reason, detail = "") {
11536
+ super(`Error in source ${sourceId}: ${reason} ${detail}`.trim());
11537
+ this.name = "PipelineSourceError";
11538
+ this.sourceId = sourceId;
11539
+ this.reason = reason;
11540
+ this.detail = detail;
10946
11541
  }
10947
- async processV2(request) {
10948
- this.validateProcessParams(request);
10949
- if (request.source.file !== void 0) {
10950
- return this.uploadFile(request.source, request);
10951
- } else if (request.source.stream !== void 0) {
10952
- return this.uploadStream(request.source, request);
10953
- } else if (request.source.path !== void 0) {
10954
- return this.uploadPath(request.source, request);
10955
- } else if (request.source.url !== void 0) {
10956
- return this.loadFrom(request.source, request);
10957
- } else if (request.source.assetUuid !== void 0) {
10958
- return this.loadFromAssetUuid(request.source, request);
10959
- } else if (request.source.mediaStream !== void 0) {
10960
- return this.loadMediaStream(request.source, request);
11542
+ };
11543
+ var CANCELLED = /* @__PURE__ */ Symbol("cancelled");
11544
+ var Inbox = class {
11545
+ constructor() {
11546
+ this._items = [];
11547
+ this._waiter = null;
11548
+ }
11549
+ push(item) {
11550
+ const waiter = this._waiter;
11551
+ if (waiter) {
11552
+ this._waiter = null;
11553
+ waiter(item);
10961
11554
  } else {
10962
- return Promise.reject("unknown source type");
11555
+ this._items.push(item);
10963
11556
  }
10964
11557
  }
10965
- async uploadGroup(paths, params = {}) {
10966
- if (paths.length === 0) {
10967
- throw new Error("upload group requires at least one path");
11558
+ next() {
11559
+ const item = this._items.shift();
11560
+ if (item !== void 0) {
11561
+ return Promise.resolve(item);
10968
11562
  }
10969
- this.validateProcessParams(params);
10970
- if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10971
- throw new Error("endpoint not connected, use connect()");
11563
+ return new Promise((resolve) => this._waiter = resolve);
11564
+ }
11565
+ };
11566
+ var UploadEnded = class extends Error {
11567
+ };
11568
+ var Credit = class {
11569
+ constructor() {
11570
+ this._credit = 0;
11571
+ this._ended = false;
11572
+ this._waiter = null;
11573
+ }
11574
+ grant(n) {
11575
+ this._credit += n;
11576
+ this.wake();
11577
+ }
11578
+ end() {
11579
+ this._ended = true;
11580
+ this.wake();
11581
+ }
11582
+ async take(most) {
11583
+ while (this._credit <= 0 && !this._ended) {
11584
+ await new Promise((resolve) => this._waiter = resolve);
10972
11585
  }
10973
- await this._limit.acquire();
10974
- try {
10975
- this.updateState();
10976
- const sources = await Promise.all(
10977
- paths.map(async (path2) => {
10978
- let streamSource;
10979
- if (this._options.platformSupport?.resolvePath) {
10980
- streamSource = await this._options.platformSupport.resolvePath({ path: path2 }, this._logger);
10981
- } else {
10982
- streamSource = await resolvePath({ path: path2 }, this._logger);
10983
- }
10984
- return { stream: streamSource.stream, mimeType: streamSource.mimeType };
10985
- })
10986
- );
10987
- const job = new UploadGroupJob(sources, params, async () => this.session(), this._client, this._requestLogger);
10988
- return job.start(
10989
- () => {
10990
- this.jobDone(job);
10991
- },
10992
- (statusCode) => {
10993
- this.jobStatus(job, statusCode);
10994
- }
10995
- );
10996
- } catch (e) {
10997
- this._limit.release();
10998
- throw e;
11586
+ if (this._ended) {
11587
+ throw new UploadEnded();
10999
11588
  }
11589
+ const n = Math.min(most, this._credit);
11590
+ this._credit -= n;
11591
+ return n;
11000
11592
  }
11001
- async uploadStreamGroup(streams, mimeTypes, params = {}) {
11002
- if (streams.length === 0) {
11003
- throw new Error("upload stream group requires at least one stream");
11593
+ wake() {
11594
+ const waiter = this._waiter;
11595
+ this._waiter = null;
11596
+ waiter?.();
11597
+ }
11598
+ };
11599
+ var OpenSource = class {
11600
+ constructor(id) {
11601
+ this.id = id;
11602
+ this.inbox = new Inbox();
11603
+ this.uploads = /* @__PURE__ */ new Map();
11604
+ }
11605
+ close(error) {
11606
+ this.uploads.forEach((upload) => upload.end());
11607
+ this.inbox.push(error);
11608
+ }
11609
+ };
11610
+ var PipelineSocket = class {
11611
+ constructor(ws) {
11612
+ this.ws = ws;
11613
+ this.sources = /* @__PURE__ */ new Map();
11614
+ this.commands = /* @__PURE__ */ new Map();
11615
+ this._closed = false;
11616
+ this.closed = new Promise((resolve) => this._markClosed = resolve);
11617
+ }
11618
+ get open() {
11619
+ return !this._closed && this.ws.readyState === 1;
11620
+ }
11621
+ markClosed() {
11622
+ this._closed = true;
11623
+ this._markClosed();
11624
+ }
11625
+ sendJson(message) {
11626
+ this.ws.send(JSON.stringify(message));
11627
+ }
11628
+ sendBytes(data) {
11629
+ this.ws.send(data);
11630
+ }
11631
+ };
11632
+ var PipelineJob = class {
11633
+ constructor(source, media, live, params, mediaStream = void 0) {
11634
+ this.source = source;
11635
+ this.media = media;
11636
+ this.live = live;
11637
+ this.params = params;
11638
+ this.mediaStream = mediaStream;
11639
+ this._results = new Inbox();
11640
+ this._consumed = false;
11641
+ this._cancelled = false;
11642
+ this.openSource = null;
11643
+ // Stops the job's source on the pipeline, once it was sent.
11644
+ this.stopOnPipeline = null;
11645
+ }
11646
+ get cancelled() {
11647
+ return this._cancelled;
11648
+ }
11649
+ command(id) {
11650
+ const command = { type: "setSource", id, ...this.source };
11651
+ if (this.params.componentParams !== void 0) {
11652
+ command["params"] = this.params.componentParams;
11004
11653
  }
11005
- if (mimeTypes !== void 0 && mimeTypes.length !== streams.length) {
11006
- throw new Error("mimeTypes must have the same length as streams");
11654
+ if (this.params.roi !== void 0) {
11655
+ command["roi"] = this.params.roi;
11007
11656
  }
11008
- this.validateProcessParams(params);
11009
- if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
11010
- throw new Error("endpoint not connected, use connect()");
11657
+ if (this.params.camera !== void 0) {
11658
+ command["camera"] = this.params.camera;
11011
11659
  }
11012
- await this._limit.acquire();
11013
- try {
11014
- this.updateState();
11015
- const sources = streams.map((stream, i) => ({
11016
- stream,
11017
- mimeType: mimeTypes?.[i] ?? null
11018
- }));
11019
- const job = new UploadGroupJob(sources, params, async () => this.session(), this._client, this._requestLogger);
11020
- return job.start(
11021
- () => {
11022
- this.jobDone(job);
11023
- },
11024
- (statusCode) => {
11025
- this.jobStatus(job, statusCode);
11026
- }
11027
- );
11028
- } catch (e) {
11029
- this._limit.release();
11030
- throw e;
11660
+ if (this.params.fps !== void 0) {
11661
+ command["fps"] = this.params.fps;
11031
11662
  }
11663
+ return command;
11032
11664
  }
11033
- async loadFromGroup(urls, params = {}) {
11034
- if (urls.length === 0) {
11035
- throw new Error("load from group requires at least one url");
11036
- }
11037
- this.validateProcessParams(params);
11038
- if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
11039
- throw new Error("endpoint not connected, use connect()");
11040
- }
11041
- await this._limit.acquire();
11042
- try {
11043
- this.updateState();
11044
- const job = new LoadFromGroupJob(urls, params, async () => this.session(), this._client, this._requestLogger);
11045
- return job.start(
11046
- () => {
11047
- this.jobDone(job);
11048
- },
11049
- (statusCode) => {
11050
- this.jobStatus(job, statusCode);
11051
- }
11052
- );
11053
- } catch (e) {
11054
- this._limit.release();
11055
- throw e;
11665
+ predicted(prediction) {
11666
+ if (!this._cancelled) {
11667
+ this._results.push(prediction);
11056
11668
  }
11057
11669
  }
11058
- async uploadFile(source, params) {
11059
- await this.ensureTransientPipelineStarted();
11060
- if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
11061
- return Promise.reject("endpoint not connected, use connect()");
11062
- }
11063
- await this._limit.acquire();
11064
- try {
11065
- this.updateState();
11066
- const job = new UploadJob(
11067
- source.file,
11068
- source.file.type,
11069
- source.file.size,
11070
- source.videoMode,
11071
- params,
11072
- async () => {
11073
- return this.session();
11074
- },
11075
- this._client,
11076
- this._requestLogger
11077
- );
11078
- return job.start(
11079
- () => {
11080
- this.jobDone(job);
11081
- },
11082
- (statusCode) => {
11083
- this.jobStatus(job, statusCode);
11084
- }
11085
- );
11086
- } catch (e) {
11087
- this._limit.release();
11088
- throw e;
11670
+ finish(error = null) {
11671
+ if (error !== null) {
11672
+ this._results.push(error);
11089
11673
  }
11674
+ this._results.push(null);
11090
11675
  }
11091
- async uploadStream(source, params) {
11092
- await this.ensureTransientPipelineStarted();
11093
- if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
11094
- return Promise.reject("endpoint not connected, use connect()");
11095
- }
11096
- await this._limit.acquire();
11097
- try {
11098
- this.updateState();
11099
- const job = new UploadJob(
11100
- source.stream,
11101
- source.mimeType,
11102
- source.size,
11103
- source.videoMode,
11104
- params,
11105
- async () => {
11106
- return this.session();
11107
- },
11108
- this._client,
11109
- this._requestLogger
11110
- );
11111
- return job.start(
11112
- () => {
11113
- this.jobDone(job);
11114
- },
11115
- (statusCode) => {
11116
- this.jobStatus(job, statusCode);
11117
- }
11118
- );
11119
- } catch (e) {
11120
- this._limit.release();
11121
- throw e;
11676
+ cancel() {
11677
+ if (this._cancelled) {
11678
+ return;
11122
11679
  }
11680
+ this._cancelled = true;
11681
+ this._results.push(null);
11682
+ this.openSource?.inbox.push(CANCELLED);
11683
+ this.stopOnPipeline?.();
11123
11684
  }
11124
- async uploadPath(source, params) {
11125
- await this.ensureTransientPipelineStarted();
11126
- if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
11127
- throw new Error("endpoint not connected, use connect()");
11685
+ async *[Symbol.asyncIterator]() {
11686
+ if (this._consumed) {
11687
+ throw new Error("Cannot iterate over a consumed stream");
11128
11688
  }
11129
- await this._limit.acquire();
11130
- try {
11131
- this.updateState();
11132
- const resolveStreamSource = async () => {
11133
- if (this._options.platformSupport?.resolvePath) {
11134
- return await this._options.platformSupport.resolvePath(source, this._logger);
11135
- }
11136
- return await resolvePath(source, this._logger);
11137
- };
11138
- const streamSource = await resolveStreamSource();
11139
- let firstStreamPending = true;
11140
- const uploadBodyFactory = async () => {
11141
- if (firstStreamPending) {
11142
- firstStreamPending = false;
11143
- return streamSource.stream;
11144
- }
11145
- return (await resolveStreamSource()).stream;
11146
- };
11147
- const job = new UploadJob(
11148
- streamSource.stream,
11149
- streamSource.mimeType,
11150
- streamSource.size,
11151
- source.videoMode,
11152
- params,
11153
- async () => {
11154
- return this.session();
11155
- },
11156
- this._client,
11157
- this._requestLogger,
11158
- uploadBodyFactory
11159
- );
11160
- return job.start(
11161
- () => {
11162
- this.jobDone(job);
11163
- },
11164
- (statusCode) => {
11165
- this.jobStatus(job, statusCode);
11166
- }
11167
- );
11168
- } catch (e) {
11169
- this._limit.release();
11170
- throw e;
11689
+ this._consumed = true;
11690
+ while (true) {
11691
+ const item = await this._results.next();
11692
+ if (item === null) {
11693
+ return;
11694
+ }
11695
+ if (item instanceof Error) {
11696
+ throw item;
11697
+ }
11698
+ yield item;
11171
11699
  }
11172
11700
  }
11173
- async loadFrom(source, params) {
11174
- if (source.url.startsWith("file://")) {
11175
- return await this.uploadPath(
11176
- {
11177
- path: source.url.substring("file://".length)
11178
- },
11179
- params
11180
- );
11701
+ };
11702
+ var PipelineBackend = class {
11703
+ constructor(ctx) {
11704
+ this.ctx = ctx;
11705
+ this._pipelineApiUrl = null;
11706
+ this._socket = null;
11707
+ this._connecting = null;
11708
+ // Every job's setSource and every changePop goes out through this chain, in the order submitted.
11709
+ this._sendChain = Promise.resolve();
11710
+ this._disconnecting = false;
11711
+ this._nextId = 0;
11712
+ this._warned = /* @__PURE__ */ new Set();
11713
+ this._heartbeat = null;
11714
+ // Reconnects at once since a source last played.
11715
+ this._reconnects = 0;
11716
+ this._currentPop = ctx.options.pop ?? null;
11717
+ }
11718
+ // Connection
11719
+ async reconnect() {
11720
+ this._disconnecting = false;
11721
+ await this.inOrder(() => this.ensureSocket());
11722
+ this.ctx.updateState();
11723
+ }
11724
+ async disconnect(closeEndpoint) {
11725
+ await closeEndpoint();
11726
+ this._disconnecting = true;
11727
+ if (this._heartbeat !== null) {
11728
+ clearInterval(this._heartbeat);
11729
+ this._heartbeat = null;
11181
11730
  }
11182
- await this.ensureTransientPipelineStarted();
11183
- if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
11184
- throw new Error("endpoint not connected, use connect()");
11731
+ const socket = this._socket;
11732
+ this._socket = null;
11733
+ if (socket !== null) {
11734
+ socket.ws.close(1e3);
11735
+ await socket.closed;
11185
11736
  }
11186
- await this._limit.acquire();
11187
- try {
11188
- this.updateState();
11189
- const job = new LoadFromJob(
11190
- source.url,
11191
- params,
11192
- async () => {
11193
- return this.session();
11194
- },
11195
- this._client,
11196
- this._requestLogger
11197
- );
11198
- return job.start(
11199
- () => {
11200
- this.jobDone(job);
11201
- },
11202
- (statusCode) => {
11203
- this.jobStatus(job, statusCode);
11204
- }
11205
- );
11206
- } catch (e) {
11207
- this._limit.release();
11208
- throw e;
11737
+ }
11738
+ onStatus(_statusCode) {
11739
+ }
11740
+ popName() {
11741
+ return null;
11742
+ }
11743
+ async authorizationHeader() {
11744
+ return this.ctx.credentialHeader();
11745
+ }
11746
+ inOrder(work) {
11747
+ const result = this._sendChain.then(work);
11748
+ this._sendChain = result.catch(() => void 0);
11749
+ return result;
11750
+ }
11751
+ /** The open socket, or a new one with the last pop set on it. */
11752
+ async ensureSocket() {
11753
+ if (this._socket !== null && this._socket.open) {
11754
+ return this._socket;
11209
11755
  }
11756
+ if (this._connecting === null) {
11757
+ this._connecting = this.openSocket().finally(() => this._connecting = null);
11758
+ }
11759
+ return await this._connecting;
11210
11760
  }
11211
- async loadFromAssetUuid(source, params) {
11212
- await this.ensureTransientPipelineStarted();
11213
- if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
11214
- throw new Error("endpoint not connected, use connect()");
11761
+ async openSocket() {
11762
+ if (this.ctx.client() === null) {
11763
+ throw new PipelineSocketClosedError(void 0, "the endpoint is not connected");
11215
11764
  }
11216
- await this._limit.acquire();
11217
- try {
11218
- this.updateState();
11219
- const job = new LoadFromAssetUuidJob(
11220
- source.assetUuid,
11221
- params,
11222
- async () => {
11223
- return this.session();
11224
- },
11225
- this._client,
11226
- this._requestLogger
11227
- );
11228
- return job.start(
11229
- () => {
11230
- this.jobDone(job);
11231
- },
11232
- (statusCode) => {
11233
- this.jobStatus(job, statusCode);
11765
+ const ws = await this.upgrade(await this.socketUrl());
11766
+ const socket = new PipelineSocket(ws);
11767
+ ws.onmessage = (event) => {
11768
+ if (typeof event.data === "string") {
11769
+ try {
11770
+ this.dispatch(socket, JSON.parse(event.data));
11771
+ } catch (error) {
11772
+ this.ctx.logger.warn("unreadable message from the pipeline API: %s", error);
11234
11773
  }
11235
- );
11236
- } catch (e) {
11237
- this._limit.release();
11238
- throw e;
11774
+ }
11775
+ };
11776
+ ws.onerror = () => {
11777
+ };
11778
+ ws.onclose = (event) => this.closed(socket, event.code, event.reason);
11779
+ this._socket = socket;
11780
+ this.startHeartbeat(ws);
11781
+ if (this._currentPop !== null) {
11782
+ try {
11783
+ await this.setPopOn(socket, this._currentPop);
11784
+ } catch (error) {
11785
+ this._socket = null;
11786
+ ws.close(1e3);
11787
+ throw error;
11788
+ }
11239
11789
  }
11790
+ return socket;
11240
11791
  }
11241
- async loadMediaStream(source, params) {
11242
- await this.ensureTransientPipelineStarted();
11243
- if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
11244
- throw new Error("endpoint not connected, use connect()");
11792
+ startHeartbeat(ws) {
11793
+ if (this._heartbeat !== null) {
11794
+ clearInterval(this._heartbeat);
11245
11795
  }
11246
- await this._limit.acquire();
11247
- try {
11248
- this.updateState();
11249
- const job = new LoadMediaStreamJob(
11250
- source.mediaStream,
11251
- params,
11252
- async () => {
11253
- return this.session();
11254
- },
11255
- this._client,
11256
- this._requestLogger
11257
- );
11258
- return job.start(
11259
- () => {
11260
- this.jobDone(job);
11261
- },
11262
- (statusCode) => {
11263
- this.jobStatus(job, statusCode);
11796
+ const ping = ws.ping;
11797
+ if (typeof ping === "function") {
11798
+ this._heartbeat = setInterval(() => {
11799
+ if (ws.readyState === 1) {
11800
+ ping.call(ws);
11264
11801
  }
11265
- );
11266
- } catch (e) {
11267
- this._limit.release();
11268
- throw e;
11802
+ }, HEARTBEAT_MS);
11803
+ this._heartbeat.unref?.();
11269
11804
  }
11270
11805
  }
11271
- jobDone(_) {
11272
- this._limit?.release();
11273
- this.updateState();
11806
+ async socketUrl() {
11807
+ if (this._pipelineApiUrl === null) {
11808
+ const base = this.ctx.options.isLocalMode ? this.ctx.eyepopUrl() : new URL(await this.configuredPipelineApiUrl(), this.ctx.eyepopUrl() + "/").toString();
11809
+ this._pipelineApiUrl = checkedUrl(base);
11810
+ }
11811
+ return "ws" + this._pipelineApiUrl.slice("http".length) + PIPELINE_PATH;
11274
11812
  }
11275
- jobStatus(_, statusCode) {
11276
- if (statusCode == 404) {
11277
- this.statusHandler404(statusCode);
11278
- } else if (statusCode == 401) {
11279
- this.statusHandler401(statusCode);
11813
+ async configuredPipelineApiUrl() {
11814
+ const client = this.ctx.client();
11815
+ if (client === null) {
11816
+ throw new Error("endpoint not connected");
11817
+ }
11818
+ let configUrl = `${this.ctx.eyepopUrl()}/v1/configs`;
11819
+ const accountId = this.ctx.options.accountId;
11820
+ if (accountId) {
11821
+ configUrl += `?account_uuid=${encodeURIComponent(accountId)}`;
11822
+ }
11823
+ const response = await this.ctx.fetchWithRetry(async () => client.fetch(configUrl, { headers: { Authorization: await this.ctx.credentialHeader() } }));
11824
+ if (!response.ok) {
11825
+ throw new Error(`Unexpected status ${response.status} for GET ${configUrl}: ${await response.text()}`);
11826
+ }
11827
+ const config = await response.json();
11828
+ const url = config?.["pipeline_api_url"];
11829
+ if (!url) {
11830
+ throw new PipelineConfigurationError("no_pipeline_api_url", `${configUrl} has no pipeline_api_url: this compute API serves no pipeline API`);
11831
+ }
11832
+ return url;
11833
+ }
11834
+ async token() {
11835
+ if (resolveAuth(this.ctx.options)?.isLocal !== void 0) {
11836
+ return void 0;
11280
11837
  }
11838
+ return (await this.ctx.credentialHeader()).replace(/^Bearer /, "");
11281
11839
  }
11282
- async reconnect() {
11283
- if (this.options().popId == "transient" /* Transient */) {
11284
- await this.getComputeSession();
11285
- if (this.options().pop) {
11286
- this.setTransientPipelinePop();
11840
+ /** Open the socket, waiting through 503 with Retry-After and exchanging the API key again after a 401. */
11841
+ async upgrade(url) {
11842
+ const deadline = Date.now() + MAX_RETRY_MS;
11843
+ let rejected = false;
11844
+ while (true) {
11845
+ try {
11846
+ this.ctx.requestLogger.debug("before upgrade %s", url);
11847
+ return await openAuthorizedWebSocket(url, SUBPROTOCOL, await this.token());
11848
+ } catch (error) {
11849
+ if (!(error instanceof UpgradeError)) {
11850
+ throw error;
11851
+ }
11852
+ if (error.status === 401 && !rejected) {
11853
+ this.ctx.requestLogger.debug("the pipeline API answered 401, authenticating again");
11854
+ rejected = true;
11855
+ this.ctx.forgetCredential();
11856
+ continue;
11857
+ }
11858
+ const wait = error.status === 503 && error.retryAfter !== void 0 ? Math.max(error.retryAfter, 1) * 1e3 : void 0;
11859
+ if (wait === void 0 || Date.now() + wait > deadline) {
11860
+ throw error;
11861
+ }
11862
+ this.ctx.requestLogger.info("the pipeline API answered 503, retrying in %d ms", wait);
11863
+ await new Promise((resolve) => setTimeout(resolve, wait));
11287
11864
  }
11288
- } else if (this.options().sessionUuid) {
11289
- await this.getComputeSession();
11290
- } else {
11291
- await this.startWebApiSessionWithPopId();
11292
11865
  }
11293
- this.updateState();
11294
- return Promise.resolve(this);
11295
11866
  }
11296
- async startWebApiSessionWithPopId() {
11297
- if (!this._client) {
11298
- throw new Error("endpoint not initialized");
11867
+ closed(socket, code, reason) {
11868
+ socket.markClosed();
11869
+ if (this._socket === socket) {
11870
+ this._socket = null;
11299
11871
  }
11300
- const config_url = `${this.eyepopWebApiUrl()}/pops/${this.options().popId}/config?auto_start=false`;
11301
- const headers = {
11302
- Authorization: await this.authorizationHeader()
11303
- };
11304
- this.updateState("FetchConfig" /* FetchConfig */);
11305
- let response = await this._client.fetch(config_url, { headers });
11306
- if (response.status == 401) {
11307
- this._requestLogger.debug("GET %s: 401, about to retry with fresh access token", config_url);
11308
- this.statusHandler401(response.status);
11309
- const headers2 = {
11310
- Authorization: await this.authorizationHeader()
11311
- };
11312
- this.updateState("FetchConfig" /* FetchConfig */);
11313
- response = await this._client.fetch(config_url, {
11314
- headers: headers2
11315
- });
11872
+ const error = new PipelineSocketClosedError(code, reason);
11873
+ socket.sources.forEach((source) => source.close(error));
11874
+ socket.commands.forEach((command) => command.reject(error));
11875
+ socket.commands.clear();
11876
+ if (this._disconnecting) {
11877
+ return;
11316
11878
  }
11317
- if (response.status != 200) {
11318
- const message = await response.text();
11319
- if (response.status == 429) {
11320
- this.updateState("NotAvailable" /* NotAvailable */, message);
11321
- } else {
11322
- this.updateState("Error" /* Error */, message);
11879
+ this.ctx.requestLogger.debug("the pipeline API closed the socket: %s %s", code, reason);
11880
+ if (RECONNECT_AT_ONCE.has(`${code} ${reason}`)) {
11881
+ if (reason === "credential_expired") {
11882
+ this.ctx.forgetCredential();
11323
11883
  }
11324
- throw new Error(`Unexpected status ${response.status}: ${message}`);
11325
- }
11326
- let config = await response.json();
11327
- if (!config.base_url && this.options().autoStart) {
11328
- const auto_start_config_url = `${this.eyepopWebApiUrl()}/pops/${this.options().popId}/config?auto_start=true`;
11329
- this._requestLogger.debug("pop was not running, trying to autostart with: %s", auto_start_config_url);
11330
- const headers2 = {
11331
- Authorization: await this.authorizationHeader()
11332
- };
11333
- response = await this._client.fetch(auto_start_config_url, {
11334
- headers: headers2
11884
+ const delay = this._reconnects === 0 ? 0 : Math.min(1e3 * 2 ** (this._reconnects - 1), MAX_RECONNECT_DELAY_MS);
11885
+ this._reconnects += 1;
11886
+ new Promise((resolve) => setTimeout(resolve, delay)).then(() => this._disconnecting ? void 0 : this.inOrder(() => this.ensureSocket())).catch((error2) => {
11887
+ this.ctx.requestLogger.info("could not reopen the pipeline API socket: %s", error2);
11335
11888
  });
11336
- if (response.status != 200) {
11337
- this.updateState("Error" /* Error */);
11338
- const message = await response.text();
11339
- throw new Error(`Unexpected status ${response.status}: ${message}`);
11889
+ }
11890
+ }
11891
+ dispatch(socket, message) {
11892
+ const type = message["type"];
11893
+ const id = message["id"];
11894
+ const source = typeof id === "string" ? socket.sources.get(id) : void 0;
11895
+ if (type === "send" || type === "credit") {
11896
+ const upload = source?.uploads.get(message["member"] ?? null);
11897
+ upload?.grant(Number(type === "send" ? message["credit"] : message["bytes"]));
11898
+ return;
11899
+ }
11900
+ const command = typeof id === "string" ? socket.commands.get(id) : void 0;
11901
+ if (command !== void 0) {
11902
+ socket.commands.delete(id);
11903
+ if (type === "error") {
11904
+ command.reject(new PipelineCommandError(message["error"] ?? "", message["message"] ?? ""));
11905
+ } else {
11906
+ command.resolve(message);
11340
11907
  }
11341
- config = await response.json();
11908
+ return;
11342
11909
  }
11343
- const baseUrl = new URL(config.base_url, this.eyepopUrl());
11344
- this._baseUrl = baseUrl.toString();
11345
- if (this._baseUrl) {
11346
- this._baseUrl = this._baseUrl.replace(/\/+$/, "");
11347
- this._pipelineId = config.pipeline_id;
11348
- this._popName = config.name;
11349
- if (!this._pipelineId || !this._baseUrl) {
11350
- throw new Error("Pop not started");
11910
+ if (source !== void 0) {
11911
+ if (type === "start") {
11912
+ this._reconnects = 0;
11351
11913
  }
11352
- const get_url = `${this._baseUrl}/pipelines/${this._pipelineId}`;
11353
- const headers2 = {
11354
- Authorization: await this.authorizationHeader()
11355
- };
11356
- let response2 = await this._client.fetch(get_url, {
11357
- method: "GET",
11358
- headers: headers2
11359
- });
11360
- if (response2.status > 200) {
11361
- const message = await response2.text();
11362
- throw new Error(`Unexpected status ${response2.status}: ${message}`);
11914
+ if (type === "end") {
11915
+ source.uploads.forEach((upload) => upload.end());
11363
11916
  }
11364
- this._pipeline = await response2.json();
11365
- if (this.options().stopJobs) {
11366
- const body = { sourceType: "NONE" };
11367
- const headers3 = {
11368
- Authorization: await this.authorizationHeader(),
11369
- "Content-Type": "application/json"
11370
- };
11371
- const stop_url = `${this._baseUrl}/pipelines/${this._pipeline.id}/source?mode=preempt&processing=sync`;
11372
- let response3 = await this._client.fetch(stop_url, {
11373
- method: "PATCH",
11374
- headers: headers3,
11375
- body: JSON.stringify(body)
11376
- });
11377
- if (response3.status >= 300) {
11378
- const message = await response3.text();
11379
- throw new Error(`Unexpected status ${response3.status}: ${message}`);
11380
- }
11917
+ source.inbox.push(message);
11918
+ } else if (type === "error") {
11919
+ if (id === void 0) {
11920
+ this.ctx.logger.warn("the pipeline API refused a message: %s %s", message["error"], message["message"]);
11921
+ } else {
11922
+ this.ctx.requestLogger.debug("the pipeline API refused a command for %s: %s %s", id, message["error"], message["message"]);
11381
11923
  }
11382
11924
  }
11383
11925
  }
11384
- sessionReadyTimeoutMs() {
11385
- const seconds = this.options().sessionReadyTimeoutSeconds;
11386
- return typeof seconds == "number" && Number.isFinite(seconds) && seconds > 0 ? seconds * 1e3 : _WorkerEndpoint.WAIT_FOR_IS_READY;
11926
+ newId(prefix) {
11927
+ this._nextId += 1;
11928
+ return `${prefix}-${this._nextId}`;
11387
11929
  }
11388
- async getComputeSession() {
11389
- if (!this._client) {
11390
- throw new Error("endpoint not initialized");
11930
+ /** Send a command that is answered by `result` or `error` under its id. */
11931
+ command(socket, message) {
11932
+ const id = this.newId(message["type"]);
11933
+ return new Promise((resolve, reject) => {
11934
+ socket.commands.set(id, { resolve, reject });
11935
+ socket.sendJson({ ...message, id });
11936
+ });
11937
+ }
11938
+ // Pop
11939
+ pop() {
11940
+ return this._currentPop;
11941
+ }
11942
+ /** Set the pop on the socket's pipeline; a pop it refuses rejects, and the last pop it took stays. */
11943
+ async changePop(pop) {
11944
+ if (this.ctx.client() === null) {
11945
+ this._currentPop = pop;
11946
+ return;
11391
11947
  }
11392
- let resolved;
11393
- try {
11394
- resolved = await new ComputeSessionClient({
11395
- computeUrl: this.eyepopUrl(),
11396
- httpClient: this._client,
11397
- authorizationHeader: () => this.computeAuthorizationHeader(),
11398
- sessionUuid: this.options().sessionUuid,
11399
- sessionName: this.options().sessionName,
11400
- accountId: this.options().accountId,
11401
- pipelineImage: this.options().pipelineImage,
11402
- pipelineVersion: this.options().pipelineVersion,
11403
- pop: this.options().pop,
11404
- readyTimeoutMs: this.sessionReadyTimeoutMs(),
11405
- logger: this._logger,
11406
- requestLogger: this._requestLogger
11407
- }).resolve();
11408
- } catch (error) {
11409
- this.updateState("Error" /* Error */);
11410
- throw error;
11948
+ await this.inOrder(async () => {
11949
+ if (this._socket !== null && this._socket.open) {
11950
+ await this.setPopOn(this._socket, pop);
11951
+ this._currentPop = pop;
11952
+ return;
11953
+ }
11954
+ const previous = this._currentPop;
11955
+ this._currentPop = pop;
11956
+ try {
11957
+ await this.ensureSocket();
11958
+ } catch (error) {
11959
+ this._currentPop = previous;
11960
+ throw error;
11961
+ }
11962
+ });
11963
+ }
11964
+ async setPopOn(socket, pop) {
11965
+ await this.command(socket, { type: "setPop", pop });
11966
+ }
11967
+ // Jobs
11968
+ async submit(work) {
11969
+ if (this.ctx.client() === null) {
11970
+ throw new Error("endpoint not connected, use connect()");
11411
11971
  }
11412
- this._sessionAccessToken = resolved.accessToken;
11413
- this._sessionAccessTokenValidUntil = resolved.accessTokenValidUntil;
11414
- this._baseUrl = resolved.session.session_endpoint;
11415
- this._accountId = resolved.session.account_uuid || void 0;
11416
- this._pipeline = null;
11417
- this._pipelineId = resolved.pipelineId;
11418
- if (this.options().pop) {
11419
- this.setTransientPipelinePop();
11972
+ const sourceOptions = work.kind === "source" ? work.source : void 0;
11973
+ this.notSent({ motionDetect: work.params.motionDetect, videoMode: sourceOptions?.videoMode, rtspForceNonCompliantUrl: work.params.rtspForceNonCompliantUrl });
11974
+ const job = await this.jobOf(work);
11975
+ const limit = this.ctx.limit();
11976
+ await limit.acquire();
11977
+ this.ctx.updateState();
11978
+ this.run(job).then(
11979
+ () => job.finish(),
11980
+ (error) => job.finish(error instanceof Error ? error : new Error(String(error)))
11981
+ ).finally(() => this.ctx.jobDone());
11982
+ return job;
11983
+ }
11984
+ notSent(options) {
11985
+ for (const [name, value] of Object.entries(options)) {
11986
+ if (value === void 0 || this._warned.has(name)) {
11987
+ continue;
11988
+ }
11989
+ this._warned.add(name);
11990
+ const ticket = name === "rtspForceNonCompliantUrl" ? "" : " (AWSU-317)";
11991
+ this.ctx.logger.warn(`${name} is not sent to the pipeline API and has no effect in pipeline mode${ticket}`);
11992
+ }
11993
+ }
11994
+ async jobOf(work) {
11995
+ switch (work.kind) {
11996
+ case "source":
11997
+ return await this.sourceJob(work.source, work.params);
11998
+ case "uploadGroup": {
11999
+ const media = await Promise.all(work.paths.map((path2) => this.pathMedia({ path: path2 })));
12000
+ return new PipelineJob(uploadGroup(media), media, false, work.params);
12001
+ }
12002
+ case "uploadStreamGroup": {
12003
+ const media = work.streams.map((stream, i) => bodyMedia(stream, work.mimeTypes?.[i] ?? "application/octet-stream", void 0));
12004
+ return new PipelineJob(uploadGroup(media), media, false, work.params);
12005
+ }
12006
+ case "loadFromGroup":
12007
+ return new PipelineJob({ group: work.urls.map((url) => ({ url })) }, [], false, work.params);
11420
12008
  }
11421
12009
  }
11422
- async startTransientPipeline() {
11423
- const client = this._client;
11424
- const baseUrl = this._baseUrl;
11425
- if (client == null || baseUrl == null) {
11426
- throw new Error("endpoint not initialized");
11427
- }
11428
- let pop;
11429
- if (this.options().pop) {
11430
- pop = this.options().pop;
11431
- } else if (this._pipeline && this._pipeline.pop) {
11432
- pop = this._pipeline.pop;
11433
- } else {
11434
- throw new Error("Cannot start a transient pipeline without a pop");
12010
+ async sourceJob(source, params) {
12011
+ if (source.file !== void 0) {
12012
+ const file = source.file;
12013
+ const media = bodyMedia(file, file.type || "application/octet-stream", file.size);
12014
+ return new PipelineJob({ upload: { contentType: media.contentType } }, [media], false, params);
12015
+ } else if (source.stream !== void 0) {
12016
+ const stream = source;
12017
+ const media = bodyMedia(stream.stream, stream.mimeType, stream.size ?? knownSize(stream.stream));
12018
+ return new PipelineJob({ upload: { contentType: media.contentType } }, [media], false, params);
12019
+ } else if (source.path !== void 0) {
12020
+ const media = await this.pathMedia(source);
12021
+ return new PipelineJob({ upload: { contentType: media.contentType } }, [media], false, params);
12022
+ } else if (source.url !== void 0) {
12023
+ const url = source.url;
12024
+ if (url.startsWith("file://")) {
12025
+ return await this.sourceJob({ path: url.substring("file://".length) }, params);
12026
+ }
12027
+ return new PipelineJob({ url }, [], /^(rtsp|rtmp):\/\//.test(url), params);
12028
+ } else if (source.assetUuid !== void 0) {
12029
+ return new PipelineJob({ assetUuid: source.assetUuid }, [], false, params);
12030
+ } else if (source.mediaStream !== void 0) {
12031
+ const ingressId = this.newId("whip");
12032
+ return new PipelineJob({ whip: { ingressId } }, [], true, params, source.mediaStream);
11435
12033
  }
11436
- const body = {
11437
- pop,
11438
- source: {
11439
- sourceType: "NONE"
11440
- },
11441
- idleTimeoutSeconds: 30,
11442
- logging: ["out_meta"],
11443
- videoOutput: "no_output"
11444
- };
11445
- const post_url = `${baseUrl.replace(/\/+$/, "")}/pipelines`;
11446
- const response = await this.fetchWithRetry(async () => {
11447
- let headers = {
11448
- Accept: "application/json",
11449
- "Content-Type": "application/json",
11450
- Authorization: await this.authorizationHeader()
11451
- };
11452
- return client.fetch(post_url, {
11453
- method: "POST",
11454
- body: JSON.stringify(body),
11455
- headers
11456
- });
11457
- });
11458
- if (response.status != 200) {
11459
- const message = await response.text();
11460
- throw new Error(`Unexpected status ${response.status} starting transient pipeline: ${message.slice(0, 300)}`);
12034
+ throw new Error("unknown source type");
12035
+ }
12036
+ async pathMedia(source) {
12037
+ const options = this.ctx.options;
12038
+ const resolve = async () => options.platformSupport?.resolvePath ? await options.platformSupport.resolvePath(source, this.ctx.logger) : await resolvePath(source, this.ctx.logger);
12039
+ const first = await resolve();
12040
+ let firstPending = true;
12041
+ return {
12042
+ contentType: first.mimeType,
12043
+ size: first.size,
12044
+ open: async () => {
12045
+ if (firstPending) {
12046
+ firstPending = false;
12047
+ return first.stream;
12048
+ }
12049
+ return (await resolve()).stream;
12050
+ }
12051
+ };
12052
+ }
12053
+ /** Run the job's source to its end; a live source refused overloaded goes once more on a new socket. */
12054
+ async run(job) {
12055
+ let retried = false;
12056
+ while (true) {
12057
+ const sent = await this.inOrder(() => this.setSource(job));
12058
+ if (sent === null) {
12059
+ return;
12060
+ }
12061
+ const { socket, source } = sent;
12062
+ const uploading = source.uploads.size > 0 ? this.upload(socket, source, job) : Promise.resolve();
12063
+ let whip = null;
12064
+ let overloaded;
12065
+ try {
12066
+ overloaded = await this.follow(job, source, () => {
12067
+ if (job.mediaStream !== void 0) {
12068
+ const fail = (error) => {
12069
+ job.stopOnPipeline?.();
12070
+ source.inbox.push(error instanceof Error ? error : new Error(String(error)));
12071
+ };
12072
+ whip = new SocketWhip(job.mediaStream, job.source["whip"]["ingressId"], (message) => this.command(socket, message), fail, this.ctx.requestLogger);
12073
+ whip.start().catch(fail);
12074
+ }
12075
+ });
12076
+ } finally {
12077
+ job.openSource = null;
12078
+ job.stopOnPipeline = null;
12079
+ socket.sources.delete(source.id);
12080
+ source.uploads.forEach((upload) => upload.end());
12081
+ await uploading;
12082
+ await whip?.close();
12083
+ }
12084
+ if (!overloaded) {
12085
+ return;
12086
+ }
12087
+ if (retried || !job.live) {
12088
+ throw new PipelineCommandError("overloaded", "the pipeline API has no capacity for another live source");
12089
+ }
12090
+ retried = true;
12091
+ await socket.closed;
11461
12092
  }
11462
- const result = await response.json();
11463
- return result["id"];
11464
12093
  }
11465
- async ensureTransientPipelineStarted() {
11466
- if (this.options().popId != "transient" /* Transient */ || this._pipelineId) {
11467
- return;
12094
+ /** Send the job's setSource; the caller runs it in submission order. */
12095
+ async setSource(job) {
12096
+ if (job.cancelled) {
12097
+ return null;
11468
12098
  }
11469
- if (!this.options().pop) {
11470
- return;
12099
+ const combined = await combinedMedia(job);
12100
+ const socket = await this.ensureSocket();
12101
+ const source = new OpenSource(this.newId("source"));
12102
+ if (combined === null) {
12103
+ uploadKeys(job).forEach((key) => source.uploads.set(key, new Credit()));
12104
+ }
12105
+ socket.sources.set(source.id, source);
12106
+ job.openSource = source;
12107
+ job.stopOnPipeline = () => {
12108
+ if (socket.open) {
12109
+ socket.sendJson({ type: "stopSource", id: source.id });
12110
+ }
12111
+ };
12112
+ const command = job.command(source.id);
12113
+ if (combined === null) {
12114
+ socket.sendJson(command);
12115
+ } else {
12116
+ socket.sendBytes(combinedMessage(command, combined));
11471
12117
  }
11472
- if (!this._baseUrl) {
11473
- await this.reconnect();
12118
+ return { socket, source };
12119
+ }
12120
+ /** Hand the source's predictions to the job until it ends; true when the pipeline API was overloaded. */
12121
+ async follow(job, source, onResult) {
12122
+ while (true) {
12123
+ if (job.cancelled) {
12124
+ return false;
12125
+ }
12126
+ const message = await source.inbox.next();
12127
+ if (message === CANCELLED) {
12128
+ return false;
12129
+ }
12130
+ if (message instanceof Error) {
12131
+ throw message;
12132
+ }
12133
+ switch (message["type"]) {
12134
+ case "result":
12135
+ onResult();
12136
+ break;
12137
+ case "prediction":
12138
+ job.predicted(message["prediction"]);
12139
+ break;
12140
+ case "end":
12141
+ if (message["reason"] === "eos") {
12142
+ return false;
12143
+ }
12144
+ throw new PipelineSourceError(source.id, message["reason"] ?? "", message["message"] ?? "");
12145
+ case "error":
12146
+ if (message["error"] === "overloaded") {
12147
+ return true;
12148
+ }
12149
+ throw new PipelineCommandError(message["error"] ?? "", message["message"] ?? "");
12150
+ case "dropped":
12151
+ this.ctx.requestLogger.debug("the pipeline API dropped %s predictions of a live source", message["count"]);
12152
+ break;
12153
+ }
11474
12154
  }
11475
- if (this._pipelineId) {
11476
- return;
12155
+ }
12156
+ /** Send each upload member's bytes in chunks within its credit, then its uploaded. */
12157
+ async upload(socket, source, job) {
12158
+ const keys = uploadKeys(job);
12159
+ try {
12160
+ for (let i = 0; i < keys.length; i++) {
12161
+ const key = keys[i];
12162
+ const credit = source.uploads.get(key);
12163
+ for await (let piece of pieces(await job.media[i].open())) {
12164
+ while (piece.length > 0) {
12165
+ const n = await credit.take(Math.min(piece.length, CHUNK_BYTES));
12166
+ const chunk = new Uint8Array(n + 1);
12167
+ chunk[0] = CHUNK;
12168
+ chunk.set(piece.subarray(0, n), 1);
12169
+ socket.sendBytes(chunk);
12170
+ piece = piece.subarray(n);
12171
+ }
12172
+ }
12173
+ socket.sendJson(key === null ? { type: "uploaded", id: source.id } : { type: "uploaded", id: source.id, member: key });
12174
+ }
12175
+ } catch (error) {
12176
+ if (!(error instanceof UploadEnded)) {
12177
+ if (socket.open) {
12178
+ source.inbox.push(error instanceof Error ? error : new Error(String(error)));
12179
+ }
12180
+ }
11477
12181
  }
11478
- if (this.options().pop) {
11479
- await this.reconnect();
11480
- if (!this._pipelineId) {
11481
- throw new Error("Compute session did not provide a pipeline for the requested pop");
12182
+ }
12183
+ async session() {
12184
+ const token = await this.token();
12185
+ const validUntil = token === void 0 ? Number.MAX_VALUE : Math.min((await this.ctx.credentialSession()).validUntil, Number.MAX_VALUE);
12186
+ return {
12187
+ eyepopUrl: this.ctx.eyepopUrl(),
12188
+ accessToken: token ?? "",
12189
+ validUntil,
12190
+ popId: "transient",
12191
+ accountId: this.ctx.options.accountId,
12192
+ baseUrl: this._pipelineApiUrl ?? void 0,
12193
+ pipelineId: void 0,
12194
+ isPipelineMode: true,
12195
+ authenticationHeaders: () => token === void 0 ? {} : { Authorization: `Bearer ${token}` }
12196
+ };
12197
+ }
12198
+ };
12199
+ var SocketWhip = class {
12200
+ constructor(_stream, _ingressId, _command, _fail, _logger) {
12201
+ this._stream = _stream;
12202
+ this._ingressId = _ingressId;
12203
+ this._command = _command;
12204
+ this._fail = _fail;
12205
+ this._logger = _logger;
12206
+ this._pc = null;
12207
+ this._answered = false;
12208
+ this._queued = [];
12209
+ this._offerData = null;
12210
+ }
12211
+ async start() {
12212
+ const servers = await this._command({ type: "whipIceServers", ingressId: this._ingressId });
12213
+ const iceServers = servers["iceServers"] ?? [];
12214
+ this._logger.debug("whip %s: ice servers %j", this._ingressId, iceServers.map((server) => server.split(";")[0]));
12215
+ const pc = new RTCPeerConnection({ iceServers: WebrtcBase.linkToIceServers(iceServers.join(", ") || null) });
12216
+ this._pc = pc;
12217
+ pc.oniceconnectionstatechange = () => {
12218
+ this._logger.debug("whip %s: ice connection %s", this._ingressId, pc.iceConnectionState);
12219
+ if (pc.iceConnectionState === "failed" && this._pc === pc) {
12220
+ this._fail(
12221
+ new Error(
12222
+ `the WebRTC connection of whip ingress ${this._ingressId} failed: no candidate pair connected, so the pipeline API is likely reachable through no STUN or TURN server (ice servers: ${iceServers.length})`
12223
+ )
12224
+ );
12225
+ }
12226
+ };
12227
+ pc.onicecandidate = (evt) => {
12228
+ if (evt.candidate !== null) {
12229
+ this.candidate(evt.candidate).catch((error) => this._logger.debug("whipIce failed: %s", error));
11482
12230
  }
12231
+ };
12232
+ this._stream.getTracks().forEach((track) => pc.addTrack(track, this._stream));
12233
+ const offer = await pc.createOffer();
12234
+ if (!offer.sdp) {
12235
+ throw new Error("the local offer has no sdp");
12236
+ }
12237
+ this._offerData = WebrtcBase.parseOffer(offer.sdp);
12238
+ await pc.setLocalDescription(offer);
12239
+ const answer = await this._command({ type: "whipOffer", ingressId: this._ingressId, sdp: offer.sdp });
12240
+ await pc.setRemoteDescription({ type: "answer", sdp: answer["sdp"] });
12241
+ this._answered = true;
12242
+ const queued = this._queued;
12243
+ this._queued = [];
12244
+ if (queued.length > 0) {
12245
+ await this.sendCandidates(queued);
12246
+ }
12247
+ }
12248
+ async candidate(candidate) {
12249
+ if (!this._answered) {
12250
+ this._queued.push(candidate);
11483
12251
  return;
11484
12252
  }
11485
- if (!this._pipelineCreatePromise) {
11486
- this._pipelineCreatePromise = this.startTransientPipeline();
12253
+ await this.sendCandidates([candidate]);
12254
+ }
12255
+ async sendCandidates(candidates) {
12256
+ if (this._offerData === null) {
12257
+ return;
11487
12258
  }
12259
+ await this._command({ type: "whipIce", ingressId: this._ingressId, fragment: WebrtcBase.generateSdpFragment(this._offerData, candidates) });
12260
+ }
12261
+ async close() {
12262
+ this._stream.getTracks().forEach((track) => track.stop());
12263
+ const pc = this._pc;
12264
+ this._pc = null;
12265
+ pc?.close();
12266
+ }
12267
+ };
12268
+ function checkedUrl(url) {
12269
+ const parsed = new URL(url);
12270
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
12271
+ throw new PipelineConfigurationError("insecure_url", `the pipeline API url ${url} must be an http or https url`);
12272
+ }
12273
+ if (parsed.protocol === "http:" && !isLoopback(parsed.hostname)) {
12274
+ throw new PipelineConfigurationError("insecure_url", `the pipeline API url ${url} must use https unless its host is a loopback address`);
12275
+ }
12276
+ return url.replace(/\/+$/, "");
12277
+ }
12278
+ function isLoopback(host) {
12279
+ return host === "localhost" || host === "[::1]" || host === "::1" || /^127\.\d+\.\d+\.\d+$/.test(host);
12280
+ }
12281
+ function knownSize(body) {
12282
+ if (typeof Blob !== "undefined" && body instanceof Blob) {
12283
+ return body.size;
12284
+ }
12285
+ if (ArrayBuffer.isView(body) || body instanceof ArrayBuffer) {
12286
+ return body.byteLength;
12287
+ }
12288
+ return void 0;
12289
+ }
12290
+ function bodyMedia(body, contentType, size) {
12291
+ return { open: async () => body, contentType, size: size ?? knownSize(body) };
12292
+ }
12293
+ function uploadGroup(media) {
12294
+ return { group: media.map((m) => ({ upload: { contentType: m.contentType } })) };
12295
+ }
12296
+ function uploadKeys(job) {
12297
+ if (job.source["upload"] !== void 0) {
12298
+ return [null];
12299
+ }
12300
+ const group = job.source["group"] ?? [];
12301
+ return group.flatMap((member, i) => member["upload"] !== void 0 ? [i] : []);
12302
+ }
12303
+ async function combinedMedia(job) {
12304
+ if (job.media.length === 0 || job.live || job.media.some((m) => m.size === void 0)) {
12305
+ return null;
12306
+ }
12307
+ if (job.media.reduce((total, m) => total + (m.size ?? 0), 0) > COMBINED_UPLOAD_MAX_BYTES) {
12308
+ return null;
12309
+ }
12310
+ const bytes = [];
12311
+ for (const media of job.media) {
12312
+ bytes.push(await readAll(await media.open()));
12313
+ }
12314
+ return bytes;
12315
+ }
12316
+ async function readAll(body) {
12317
+ const parts = [];
12318
+ for await (const piece of pieces(body)) {
12319
+ parts.push(piece);
12320
+ }
12321
+ return concat(parts);
12322
+ }
12323
+ function concat(parts) {
12324
+ const all = new Uint8Array(parts.reduce((total, p) => total + p.length, 0));
12325
+ let offset = 0;
12326
+ for (const part of parts) {
12327
+ all.set(part, offset);
12328
+ offset += part.length;
12329
+ }
12330
+ return all;
12331
+ }
12332
+ function combinedMessage(command, media) {
12333
+ if (command["group"] !== void 0) {
12334
+ let i = 0;
12335
+ command = {
12336
+ ...command,
12337
+ group: command["group"].map((member) => member["upload"] !== void 0 ? { upload: { ...member["upload"], bytes: media[i++].length } } : member)
12338
+ };
12339
+ }
12340
+ const header = new TextEncoder().encode(JSON.stringify(command));
12341
+ const prefix = new Uint8Array(5);
12342
+ prefix[0] = COMBINED;
12343
+ new DataView(prefix.buffer).setUint32(1, header.length, false);
12344
+ return concat([prefix, header, ...media]);
12345
+ }
12346
+ async function* pieces(body) {
12347
+ if (typeof Blob !== "undefined" && body instanceof Blob) {
12348
+ yield* pieces(body.stream());
12349
+ } else if (body instanceof ArrayBuffer) {
12350
+ yield new Uint8Array(body);
12351
+ } else if (ArrayBuffer.isView(body)) {
12352
+ yield new Uint8Array(body.buffer, body.byteOffset, body.byteLength);
12353
+ } else {
12354
+ const reader = body.getReader();
11488
12355
  try {
11489
- this._pipelineId = await this._pipelineCreatePromise;
11490
- this.setTransientPipelinePop();
12356
+ while (true) {
12357
+ const { value, done } = await reader.read();
12358
+ if (done) {
12359
+ return;
12360
+ }
12361
+ if (value !== void 0 && value.length > 0) {
12362
+ yield value;
12363
+ }
12364
+ }
11491
12365
  } finally {
11492
- this._pipelineCreatePromise = null;
12366
+ reader.releaseLock();
11493
12367
  }
11494
12368
  }
11495
- async changeTransientPop(pop) {
11496
- if (!this._client) {
11497
- throw new Error("endpoint not initialized");
11498
- }
11499
- const previousPop = this.options().pop;
11500
- const previousBaseUrl = this._baseUrl;
11501
- const previousSessionAccessToken = this._sessionAccessToken;
11502
- const previousSessionAccessTokenValidUntil = this._sessionAccessTokenValidUntil;
11503
- if (this._pipelineId) {
11504
- await this.deleteTransientPipeline(this._pipelineId);
11505
- this._pipelineId = null;
11506
- }
11507
- this._pipeline = null;
11508
- try {
11509
- this.options().pop = pop;
11510
- await this.getComputeSession();
11511
- } catch (error) {
11512
- this.options().pop = previousPop;
11513
- this._pipeline = null;
11514
- this._pipelineId = null;
11515
- this._baseUrl = previousPop ? null : previousBaseUrl;
11516
- this._sessionAccessToken = previousSessionAccessToken;
11517
- this._sessionAccessTokenValidUntil = previousSessionAccessTokenValidUntil;
11518
- throw error;
11519
- }
11520
- if (!this._pipelineId) {
11521
- throw new Error("Compute session did not provide a pipeline for the requested pop");
12369
+ }
12370
+
12371
+ // worker/worker_endpoint.ts
12372
+ var WorkerEndpoint = class extends Endpoint {
12373
+ constructor(options) {
12374
+ if (options.isPipelineMode) {
12375
+ options = { ...options, jobQueueLength: options.jobQueueLength || MAX_OPEN_SOURCES };
12376
+ if (options.jobQueueLength > MAX_OPEN_SOURCES) {
12377
+ throw new PipelineConfigurationError("job_queue_length", `jobQueueLength ${options.jobQueueLength} exceeds ${MAX_OPEN_SOURCES}, the open sources a pipeline API socket takes`);
12378
+ }
11522
12379
  }
11523
- this.setTransientPipelinePop();
12380
+ super(options);
12381
+ const context = {
12382
+ options,
12383
+ logger: this._logger,
12384
+ requestLogger: this._requestLogger,
12385
+ client: () => this._client,
12386
+ limit: () => this._limit,
12387
+ jobDone: () => {
12388
+ this._limit.release();
12389
+ this.updateState();
12390
+ },
12391
+ updateState: (state, message) => this.updateState(state, message),
12392
+ eyepopUrl: () => this.eyepopUrl(),
12393
+ eyepopWebApiUrl: () => this.eyepopWebApiUrl(),
12394
+ credentialHeader: () => super.authorizationHeader(),
12395
+ forgetCredential: () => this.statusHandler401(401),
12396
+ fetchWithRetry: (fetcher, retries) => this.fetchWithRetry(fetcher, retries),
12397
+ credentialSession: () => super.session()
12398
+ };
12399
+ this._backend = options.isPipelineMode ? new PipelineBackend(context) : new SessionBackend(context);
12400
+ this.setStatusRetryHandlers(/* @__PURE__ */ new Map([[404, (statusCode) => this._backend.onStatus(statusCode)]]));
11524
12401
  }
11525
- async deleteTransientPipeline(pipelineId) {
11526
- if (!this._client || !this._baseUrl) {
11527
- return;
12402
+ options() {
12403
+ return this._options;
12404
+ }
12405
+ /** What runs this endpoint's jobs. */
12406
+ backend() {
12407
+ return this._backend;
12408
+ }
12409
+ async disconnect(wait = true) {
12410
+ await this._backend.disconnect(() => super.disconnect(wait));
12411
+ }
12412
+ async session() {
12413
+ return await this._backend.session();
12414
+ }
12415
+ popName() {
12416
+ return this._backend.popName();
12417
+ }
12418
+ pop() {
12419
+ return this._backend.pop();
12420
+ }
12421
+ async changePop(pop) {
12422
+ validatePop(pop);
12423
+ await this._backend.changePop(pop);
12424
+ }
12425
+ /**
12426
+ * @deprecated Pop ids are only retained for backwards compatibility.
12427
+ * Configure transient sessions with `WorkerOptions.pop`.
12428
+ */
12429
+ popId() {
12430
+ return this.options().popId || null;
12431
+ }
12432
+ async process(param1, param2) {
12433
+ if (param1.source !== void 0) {
12434
+ const request = param1;
12435
+ this.validateProcessParams(request);
12436
+ return await this.submit({ kind: "source", source: request.source, params: request });
12437
+ } else {
12438
+ return await this.submit({ kind: "source", source: param1, params: { componentParams: param2 } });
11528
12439
  }
11529
- const deleteUrl = `${this._baseUrl}/pipelines/${pipelineId}`;
11530
- const headers = {
11531
- Authorization: await this.authorizationHeader()
11532
- };
11533
- let response = await this._client.fetch(deleteUrl, {
11534
- method: "DELETE",
11535
- headers
11536
- });
11537
- if (response.status >= 300) {
11538
- const message = await response.text();
11539
- this._logger.info(`stopping pipeline failed, status ${response.status}: ${message}`);
12440
+ }
12441
+ /**
12442
+ * Reject a source's calibration before any work is started for it.
12443
+ *
12444
+ * Called by every entry point that takes ProcessParams, before the
12445
+ * concurrency limit is acquired, so a request that can never be sent
12446
+ * does not hold a slot.
12447
+ */
12448
+ validateProcessParams(params) {
12449
+ if (params.camera !== void 0) {
12450
+ validateCamera(params.camera);
11540
12451
  }
11541
12452
  }
11542
- async authorizationHeader() {
11543
- if (this._sessionAccessToken) {
11544
- return `Bearer ${this._sessionAccessToken}`;
12453
+ async uploadGroup(paths, params = {}) {
12454
+ if (paths.length === 0) {
12455
+ throw new Error("upload group requires at least one path");
11545
12456
  }
11546
- return super.authorizationHeader();
12457
+ this.validateProcessParams(params);
12458
+ return await this.submit({ kind: "uploadGroup", paths, params });
11547
12459
  }
11548
- async computeAuthorizationHeader() {
11549
- const auth = resolveAuth(this.options());
11550
- if (auth?.isLocal !== void 0) {
11551
- return "Implicit";
12460
+ async uploadStreamGroup(streams, mimeTypes, params = {}) {
12461
+ if (streams.length === 0) {
12462
+ throw new Error("upload stream group requires at least one stream");
11552
12463
  }
11553
- const credential = apiKeyCredential(auth) ?? accessTokenCredential(auth);
11554
- if (credential !== void 0) {
11555
- return `Bearer ${credential}`;
12464
+ if (mimeTypes !== void 0 && mimeTypes.length !== streams.length) {
12465
+ throw new Error("mimeTypes must have the same length as streams");
11556
12466
  }
11557
- return super.authorizationHeader();
12467
+ this.validateProcessParams(params);
12468
+ return await this.submit({ kind: "uploadStreamGroup", streams, mimeTypes, params });
11558
12469
  }
11559
- setTransientPipelinePop() {
11560
- const pop = this.options().pop;
11561
- if (pop && this._pipelineId) {
11562
- this._pipeline = {
11563
- id: this._pipelineId,
11564
- state: "idle",
11565
- startTime: (/* @__PURE__ */ new Date()).toString(),
11566
- pop
11567
- };
12470
+ async loadFromGroup(urls, params = {}) {
12471
+ if (urls.length === 0) {
12472
+ throw new Error("load from group requires at least one url");
11568
12473
  }
12474
+ this.validateProcessParams(params);
12475
+ return await this.submit({ kind: "loadFromGroup", urls, params });
12476
+ }
12477
+ async submit(work) {
12478
+ return await this._backend.submit(work);
12479
+ }
12480
+ async reconnect() {
12481
+ await this._backend.reconnect();
12482
+ return this;
12483
+ }
12484
+ async authorizationHeader() {
12485
+ return await this._backend.authorizationHeader();
11569
12486
  }
11570
12487
  };
11571
- _WorkerEndpoint.WAIT_FOR_IS_READY = 60 * 1e3;
11572
- var WorkerEndpoint = _WorkerEndpoint;
11573
-
11574
- // shims/websockets.ts
11575
- var createWebSocket;
11576
- if ("document" in globalThis && "implementation" in globalThis.document) {
11577
- createWebSocket = (url, protocols) => {
11578
- return new WebSocket(url, protocols);
11579
- };
11580
- } else {
11581
- createWebSocket = (url, protocols) => {
11582
- const ws = __require("ws");
11583
- return new ws.WebSocket(url, protocols);
11584
- };
11585
- }
11586
12488
 
11587
12489
  // data/data_endpoint.ts
11588
12490
  import urljoin from "url-join";
@@ -13250,6 +14152,13 @@ var EyePop;
13250
14152
  if (opts.isLocalMode === void 0) {
13251
14153
  opts.isLocalMode = stringToBooleanSafe(readEnv("EYEPOP_LOCAL_MODE"));
13252
14154
  }
14155
+ if (opts.isPipelineMode === void 0) {
14156
+ const shared = resolveAuth(opts)?.session;
14157
+ opts.isPipelineMode = shared?.isPipelineMode ?? stringToBooleanSafe(readEnv("EYEPOP_PIPELINE_MODE"));
14158
+ }
14159
+ if (opts.isPipelineMode) {
14160
+ return pipelineEndpoint(opts);
14161
+ }
13253
14162
  if (opts.isLocalMode) {
13254
14163
  opts.eyepopUrl = "http://127.0.0.1:8080";
13255
14164
  }
@@ -13301,6 +14210,38 @@ var EyePop;
13301
14210
  return new WorkerEndpoint(opts);
13302
14211
  }
13303
14212
  EyePop2.workerEndpoint = workerEndpoint;
14213
+ function pipelineEndpoint(opts) {
14214
+ const popId = opts.popId ?? readEnv("EYEPOP_POP_ID");
14215
+ const sessionOnly = {
14216
+ popId: popId === "transient" /* Transient */ ? void 0 : popId,
14217
+ sessionUuid: opts.sessionUuid ?? readEnv("EYEPOP_SESSION_UUID"),
14218
+ sessionName: opts.sessionName,
14219
+ pipelineImage: opts.pipelineImage,
14220
+ pipelineVersion: opts.pipelineVersion
14221
+ };
14222
+ const named = Object.entries(sessionOnly).filter(([, value]) => value).map(([name]) => name);
14223
+ if (named.length > 0) {
14224
+ throw new PipelineConfigurationError("session_options", `pipeline mode runs no worker session; it takes no ${named.join(", ")}`, named);
14225
+ }
14226
+ if (opts.isLocalMode && !opts.eyepopUrl) {
14227
+ opts.eyepopUrl = readEnv("EYEPOP_URL") || "http://127.0.0.1:9002";
14228
+ }
14229
+ if (!opts.jobQueueLength) {
14230
+ opts.jobQueueLength = MAX_OPEN_SOURCES;
14231
+ }
14232
+ _fill_default_options(opts);
14233
+ if (resolveAuth(opts) === void 0) {
14234
+ if (opts.isLocalMode) {
14235
+ opts.auth = { isLocal: true };
14236
+ } else {
14237
+ throw new Error("apiKey/accessToken/session/oAuth2 option or EYEPOP_API_KEY environment variable is required");
14238
+ }
14239
+ }
14240
+ if (opts.accountId === void 0) {
14241
+ opts.accountId = resolveAuth(opts)?.session?.accountId ?? readAccountUuidEnv();
14242
+ }
14243
+ return new WorkerEndpoint(opts);
14244
+ }
13304
14245
  function dataEndpoint(opts = {}) {
13305
14246
  _fill_default_options(opts);
13306
14247
  const auth = resolveAuth(opts);
@@ -13377,6 +14318,7 @@ export {
13377
14318
  AssetUrlType,
13378
14319
  AutoAnnotateStatus,
13379
14320
  AutoAnnotateTask,
14321
+ COMBINED_UPLOAD_MAX_BYTES,
13380
14322
  ChangeType,
13381
14323
  ComputeAccountMismatchError,
13382
14324
  ComputeApiError,
@@ -13398,6 +14340,7 @@ export {
13398
14340
  LoadFromGroupJob,
13399
14341
  LoadFromJob,
13400
14342
  LoadMediaStreamJob,
14343
+ MAX_OPEN_SOURCES,
13401
14344
  ModelExportStatus,
13402
14345
  ModelFormat,
13403
14346
  ModelPrecisionType,
@@ -13406,11 +14349,21 @@ export {
13406
14349
  ModelTrainingStage,
13407
14350
  ModelType,
13408
14351
  MotionModel,
14352
+ PIPELINE_PATH,
14353
+ PipelineBackend,
14354
+ PipelineCommandError,
14355
+ PipelineConfigurationError,
14356
+ PipelineJob,
14357
+ PipelineSocketClosedError,
14358
+ PipelineSourceError,
13409
14359
  PointCloud,
13410
14360
  PopComponentType,
13411
14361
  PredictionVersion,
13412
14362
  QUATERNION_TOLERANCE,
14363
+ SUBPROTOCOL,
14364
+ SelectMode,
13413
14365
  Semaphore,
14366
+ SessionBackend,
13414
14367
  Stream,
13415
14368
  TaskMode,
13416
14369
  TaskType,
@@ -13436,6 +14389,8 @@ export {
13436
14389
  index_default as default,
13437
14390
  encodeMultipartFormData,
13438
14391
  getBaseUrl,
14392
+ isSelectOperator,
14393
+ popSelects,
13439
14394
  randomMultipartBoundary,
13440
14395
  readableStreamFromString,
13441
14396
  resolveAuth,