@eyepop.ai/eyepop 3.25.3 → 3.25.5
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.
- package/dist/eyepop.index.d.mts +83 -9
- package/dist/eyepop.index.d.ts +83 -9
- package/dist/eyepop.index.js +213 -86
- package/dist/eyepop.index.js.map +1 -1
- package/dist/eyepop.index.mjs +212 -86
- package/dist/eyepop.index.mjs.map +1 -1
- package/dist/eyepop.min.js +1 -1
- package/package.json +1 -1
package/dist/eyepop.index.mjs
CHANGED
|
@@ -8928,14 +8928,25 @@ if ("document" in globalThis && "implementation" in globalThis.document) {
|
|
|
8928
8928
|
}
|
|
8929
8929
|
|
|
8930
8930
|
// endpoint.ts
|
|
8931
|
+
var ACCOUNT_CLAIM = "https://ident.eyepop.ai/account-uuid";
|
|
8932
|
+
var M2M_REFUSAL = "API key or user login required";
|
|
8933
|
+
function carriesAccount(token) {
|
|
8934
|
+
try {
|
|
8935
|
+
const payload = (token.split(".")[1] ?? "").replace(/-/g, "+").replace(/_/g, "/");
|
|
8936
|
+
return Boolean(JSON.parse(atob(payload))?.[ACCOUNT_CLAIM]);
|
|
8937
|
+
} catch {
|
|
8938
|
+
return false;
|
|
8939
|
+
}
|
|
8940
|
+
}
|
|
8931
8941
|
var Endpoint = class {
|
|
8932
8942
|
constructor(options) {
|
|
8943
|
+
this._exchanging = null;
|
|
8933
8944
|
this._options = options;
|
|
8934
8945
|
this._token = null;
|
|
8935
8946
|
this._expire_token_time = null;
|
|
8936
8947
|
this._client = null;
|
|
8937
8948
|
this._statusRetryHandlers = /* @__PURE__ */ new Map();
|
|
8938
|
-
this._statusRetryHandlers.set(401, this.statusHandler401);
|
|
8949
|
+
this._statusRetryHandlers.set(401, (statusCode, failedToken) => this.statusHandler401(statusCode, failedToken));
|
|
8939
8950
|
this._limit = new Semaphore(this._options.jobQueueLength ?? 1024);
|
|
8940
8951
|
this._state = "Idle" /* Idle */;
|
|
8941
8952
|
this._message = null;
|
|
@@ -8951,7 +8962,7 @@ var Endpoint = class {
|
|
|
8951
8962
|
}
|
|
8952
8963
|
setStatusRetryHandlers(statusRetryHandlers = void 0) {
|
|
8953
8964
|
this._statusRetryHandlers = new Map(statusRetryHandlers);
|
|
8954
|
-
this._statusRetryHandlers.set(401, this.statusHandler401);
|
|
8965
|
+
this._statusRetryHandlers.set(401, (statusCode, failedToken) => this.statusHandler401(statusCode, failedToken));
|
|
8955
8966
|
}
|
|
8956
8967
|
eyepopUrl() {
|
|
8957
8968
|
if (this._options.eyepopUrl) {
|
|
@@ -9048,12 +9059,15 @@ var Endpoint = class {
|
|
|
9048
9059
|
validUntil: this._expire_token_time * 1e3
|
|
9049
9060
|
};
|
|
9050
9061
|
}
|
|
9051
|
-
statusHandler401(_statusCode) {
|
|
9052
|
-
this._token
|
|
9053
|
-
|
|
9062
|
+
statusHandler401(_statusCode, failedToken = this._token) {
|
|
9063
|
+
if (this._token === failedToken) {
|
|
9064
|
+
this._token = null;
|
|
9065
|
+
this._expire_token_time = null;
|
|
9066
|
+
}
|
|
9054
9067
|
}
|
|
9055
9068
|
async fetchWithRetry(fetcher, retries = 1) {
|
|
9056
9069
|
while (true) {
|
|
9070
|
+
const sentToken = this._token ?? void 0;
|
|
9057
9071
|
try {
|
|
9058
9072
|
const response = await fetcher();
|
|
9059
9073
|
if (response.ok) {
|
|
@@ -9061,7 +9075,7 @@ var Endpoint = class {
|
|
|
9061
9075
|
} else {
|
|
9062
9076
|
const handler = this._statusRetryHandlers.get(response.status);
|
|
9063
9077
|
if (handler != null) {
|
|
9064
|
-
handler(response.status);
|
|
9078
|
+
handler(response.status, sentToken);
|
|
9065
9079
|
if (retries--) {
|
|
9066
9080
|
this._requestLogger.info("received %d response, retry", response.status);
|
|
9067
9081
|
} else {
|
|
@@ -9095,54 +9109,74 @@ var Endpoint = class {
|
|
|
9095
9109
|
}
|
|
9096
9110
|
return `Bearer ${await this.currentAccessToken()}`;
|
|
9097
9111
|
}
|
|
9112
|
+
async exchange(client, credential, accountUuid) {
|
|
9113
|
+
const query = accountUuid ? `?account_uuid=${encodeURIComponent(accountUuid)}` : "";
|
|
9114
|
+
return await client.fetch(`${this.eyepopUrl()}/v1/auth/authenticate${query}`, {
|
|
9115
|
+
method: "POST",
|
|
9116
|
+
headers: { Authorization: `Bearer ${credential}` }
|
|
9117
|
+
});
|
|
9118
|
+
}
|
|
9119
|
+
async storeExchangedToken(response, now) {
|
|
9120
|
+
if (response.status != 200) {
|
|
9121
|
+
const message = await response.text();
|
|
9122
|
+
return Promise.reject(`Unexpected status ${response.status}: ${message}`);
|
|
9123
|
+
}
|
|
9124
|
+
const token = await response.json();
|
|
9125
|
+
this._token = token.access_token;
|
|
9126
|
+
this._expire_token_time = now + token.expires_in - 60;
|
|
9127
|
+
}
|
|
9128
|
+
/**
|
|
9129
|
+
* A token that already carries an account, or that the compute API refuses as an exchanged
|
|
9130
|
+
* token ("API key or user login required"), is used as is. Any other refusal is raised.
|
|
9131
|
+
*/
|
|
9132
|
+
async exchangeLoginToken(client, loginToken, validUntil, now) {
|
|
9133
|
+
if (!carriesAccount(loginToken)) {
|
|
9134
|
+
const response = await this.exchange(client, loginToken, this._options.accountId);
|
|
9135
|
+
if (response.status != 401 || !(await response.clone().text()).includes(M2M_REFUSAL)) {
|
|
9136
|
+
await this.storeExchangedToken(response, now);
|
|
9137
|
+
return;
|
|
9138
|
+
}
|
|
9139
|
+
}
|
|
9140
|
+
this._token = loginToken;
|
|
9141
|
+
this._expire_token_time = validUntil;
|
|
9142
|
+
}
|
|
9098
9143
|
async currentAccessToken() {
|
|
9144
|
+
if (!this._token || this._expire_token_time < Date.now() / 1e3) {
|
|
9145
|
+
this._exchanging ?? (this._exchanging = this.authenticate().finally(() => this._exchanging = null));
|
|
9146
|
+
await this._exchanging;
|
|
9147
|
+
}
|
|
9148
|
+
this._logger.debug("using access token, valid for at least %d seconds", this._expire_token_time - Date.now() / 1e3);
|
|
9149
|
+
return this._token;
|
|
9150
|
+
}
|
|
9151
|
+
async authenticate() {
|
|
9099
9152
|
const now = Date.now() / 1e3;
|
|
9100
|
-
|
|
9101
|
-
|
|
9102
|
-
|
|
9103
|
-
|
|
9104
|
-
|
|
9105
|
-
|
|
9106
|
-
|
|
9107
|
-
|
|
9108
|
-
|
|
9109
|
-
|
|
9110
|
-
|
|
9111
|
-
|
|
9112
|
-
|
|
9113
|
-
|
|
9114
|
-
|
|
9115
|
-
|
|
9116
|
-
|
|
9117
|
-
|
|
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 {
|
|
9153
|
+
this.updateState("Authenticating" /* Authenticating */);
|
|
9154
|
+
try {
|
|
9155
|
+
const auth = resolveAuth(this._options);
|
|
9156
|
+
if (!this._client) {
|
|
9157
|
+
return Promise.reject("endpoint not connected");
|
|
9158
|
+
} else if (auth?.isLocal !== void 0) {
|
|
9159
|
+
this._token = "implicit";
|
|
9160
|
+
this._expire_token_time = Number.MAX_VALUE;
|
|
9161
|
+
} else if (auth?.session !== void 0) {
|
|
9162
|
+
return Promise.reject("temporary access token expired");
|
|
9163
|
+
} else if (auth?.oAuth2 !== void 0 && this._options.eyepopUrl) {
|
|
9164
|
+
const login = await authenticateBrowserSession(auth.oAuth2, this._options);
|
|
9165
|
+
await this.exchangeLoginToken(this._client, login.accessToken, login.validUntil / 1e3, now);
|
|
9166
|
+
} else if (accessTokenCredential(auth) !== void 0) {
|
|
9167
|
+
await this.exchangeLoginToken(this._client, accessTokenCredential(auth), Number.MAX_VALUE, now);
|
|
9168
|
+
} else if (this._options.eyepopUrl) {
|
|
9169
|
+
const credential = apiKeyCredential(auth);
|
|
9170
|
+
if (credential === void 0) {
|
|
9138
9171
|
return Promise.reject("no valid auth option");
|
|
9139
9172
|
}
|
|
9140
|
-
|
|
9141
|
-
|
|
9173
|
+
await this.storeExchangedToken(await this.exchange(this._client, credential), now);
|
|
9174
|
+
} else {
|
|
9175
|
+
return Promise.reject("no valid auth option");
|
|
9142
9176
|
}
|
|
9177
|
+
} finally {
|
|
9178
|
+
this.updateState();
|
|
9143
9179
|
}
|
|
9144
|
-
this._logger.debug("using access token, valid for at least %d seconds", this._expire_token_time - now);
|
|
9145
|
-
return this._token;
|
|
9146
9180
|
}
|
|
9147
9181
|
};
|
|
9148
9182
|
|
|
@@ -11497,6 +11531,8 @@ if (isBrowser) {
|
|
|
11497
11531
|
var PIPELINE_PATH = "/api/v1/pipeline";
|
|
11498
11532
|
var SUBPROTOCOL = "eyepop.v1";
|
|
11499
11533
|
var MAX_OPEN_SOURCES = 256;
|
|
11534
|
+
var PIPELINE_SOCKETS = 4;
|
|
11535
|
+
var EXTRA_SOCKET_RETRY_MS = 10 * 1e3;
|
|
11500
11536
|
var COMBINED_UPLOAD_MAX_BYTES = 8 * 1024 * 1024;
|
|
11501
11537
|
var CHUNK = 1;
|
|
11502
11538
|
var COMBINED = 2;
|
|
@@ -11616,6 +11652,8 @@ var PipelineSocket = class {
|
|
|
11616
11652
|
// The ids of sources sent on this socket that have neither ended nor been refused, as the
|
|
11617
11653
|
// pipeline API counts them, cancelled ones included.
|
|
11618
11654
|
this.held = /* @__PURE__ */ new Set();
|
|
11655
|
+
// The held sources that are live. A live source never ends, so nothing queued behind it plays.
|
|
11656
|
+
this.lives = /* @__PURE__ */ new Set();
|
|
11619
11657
|
// The held sources the pipeline API took: only these end, so only these make room.
|
|
11620
11658
|
this.accepted = /* @__PURE__ */ new Set();
|
|
11621
11659
|
// The ends on this socket so far.
|
|
@@ -11624,6 +11662,7 @@ var PipelineSocket = class {
|
|
|
11624
11662
|
// the next send waits. Null once none is held.
|
|
11625
11663
|
this.cap = null;
|
|
11626
11664
|
this._roomWaiters = [];
|
|
11665
|
+
this.heartbeat = null;
|
|
11627
11666
|
this.closed = new Promise((resolve) => this._markClosed = resolve);
|
|
11628
11667
|
}
|
|
11629
11668
|
/** A source ended or the socket closed: wake the send waiting for room. */
|
|
@@ -11657,6 +11696,10 @@ var PipelineSocket = class {
|
|
|
11657
11696
|
return !this._closed && this.ws.readyState === 1;
|
|
11658
11697
|
}
|
|
11659
11698
|
markClosed() {
|
|
11699
|
+
if (this.heartbeat !== null) {
|
|
11700
|
+
clearInterval(this.heartbeat);
|
|
11701
|
+
this.heartbeat = null;
|
|
11702
|
+
}
|
|
11660
11703
|
this._closed = true;
|
|
11661
11704
|
this._markClosed();
|
|
11662
11705
|
this.freed();
|
|
@@ -11683,10 +11726,22 @@ var PipelineJob = class {
|
|
|
11683
11726
|
this.openSource = null;
|
|
11684
11727
|
// Stops the job's source on the pipeline, once it was sent.
|
|
11685
11728
|
this.stopOnPipeline = null;
|
|
11729
|
+
this._milliComputeUnits = void 0;
|
|
11686
11730
|
}
|
|
11687
11731
|
get cancelled() {
|
|
11688
11732
|
return this._cancelled;
|
|
11689
11733
|
}
|
|
11734
|
+
/** The source's milli compute units: the latest total of its `usage` messages and its `end`. */
|
|
11735
|
+
get milliComputeUnits() {
|
|
11736
|
+
return this._milliComputeUnits;
|
|
11737
|
+
}
|
|
11738
|
+
/** Keep a usage's or an end's milliComputeUnits: each carries the source's total so far. */
|
|
11739
|
+
noteMilliComputeUnits(message) {
|
|
11740
|
+
const value = message["milliComputeUnits"];
|
|
11741
|
+
if (Number.isInteger(value) && value >= 0) {
|
|
11742
|
+
this._milliComputeUnits = value;
|
|
11743
|
+
}
|
|
11744
|
+
}
|
|
11690
11745
|
command(id) {
|
|
11691
11746
|
const command = { type: "setSource", id, ...this.source };
|
|
11692
11747
|
if (this.params.componentParams !== void 0) {
|
|
@@ -11745,14 +11800,14 @@ var PipelineBackend = class {
|
|
|
11745
11800
|
constructor(ctx) {
|
|
11746
11801
|
this.ctx = ctx;
|
|
11747
11802
|
this._pipelineApiUrl = null;
|
|
11748
|
-
this.
|
|
11803
|
+
this._sockets = [];
|
|
11749
11804
|
this._connecting = null;
|
|
11750
11805
|
// Every job's setSource and every changePop goes out through this chain, in the order submitted.
|
|
11751
11806
|
this._sendChain = Promise.resolve();
|
|
11752
11807
|
this._disconnecting = false;
|
|
11753
11808
|
this._nextId = 0;
|
|
11754
11809
|
this._warned = /* @__PURE__ */ new Set();
|
|
11755
|
-
this.
|
|
11810
|
+
this._noExtraSocketUntil = 0;
|
|
11756
11811
|
// Reconnects at once since a source last played.
|
|
11757
11812
|
this._reconnects = 0;
|
|
11758
11813
|
this._currentPop = ctx.options.pop ?? null;
|
|
@@ -11766,13 +11821,9 @@ var PipelineBackend = class {
|
|
|
11766
11821
|
async disconnect(closeEndpoint) {
|
|
11767
11822
|
await closeEndpoint();
|
|
11768
11823
|
this._disconnecting = true;
|
|
11769
|
-
|
|
11770
|
-
|
|
11771
|
-
|
|
11772
|
-
}
|
|
11773
|
-
const socket = this._socket;
|
|
11774
|
-
this._socket = null;
|
|
11775
|
-
if (socket !== null) {
|
|
11824
|
+
const sockets = this._sockets;
|
|
11825
|
+
this._sockets = [];
|
|
11826
|
+
for (const socket of sockets) {
|
|
11776
11827
|
socket.ws.close(1e3);
|
|
11777
11828
|
await socket.closed;
|
|
11778
11829
|
}
|
|
@@ -11790,21 +11841,55 @@ var PipelineBackend = class {
|
|
|
11790
11841
|
this._sendChain = result.catch(() => void 0);
|
|
11791
11842
|
return result;
|
|
11792
11843
|
}
|
|
11793
|
-
|
|
11844
|
+
openSockets() {
|
|
11845
|
+
return this._sockets.filter((socket) => socket.open);
|
|
11846
|
+
}
|
|
11847
|
+
/** An open socket, or a new one with the last pop set on it. */
|
|
11794
11848
|
async ensureSocket() {
|
|
11795
|
-
|
|
11796
|
-
|
|
11849
|
+
const open = this.openSockets()[0];
|
|
11850
|
+
if (open !== void 0) {
|
|
11851
|
+
return open;
|
|
11797
11852
|
}
|
|
11798
11853
|
if (this._connecting === null) {
|
|
11799
|
-
this._connecting = this.openSocket().finally(() => this._connecting = null);
|
|
11854
|
+
this._connecting = this.openSocket(true).finally(() => this._connecting = null);
|
|
11800
11855
|
}
|
|
11801
11856
|
return await this._connecting;
|
|
11802
11857
|
}
|
|
11803
|
-
|
|
11858
|
+
/**
|
|
11859
|
+
* The socket for the next job; the caller runs it in submission order. A live job goes on a socket
|
|
11860
|
+
* of its own, an idle one or a new one, which `pipelineSockets` does not count. Any other job skips
|
|
11861
|
+
* the sockets with a live source: a new one while every other socket has a source and fewer than
|
|
11862
|
+
* `pipelineSockets` are open, else the one with room and the fewest sources.
|
|
11863
|
+
*/
|
|
11864
|
+
async socketForJob(job) {
|
|
11865
|
+
if (job.live) {
|
|
11866
|
+
return this.openSockets().find((socket) => socket.held.size === 0) ?? await this.openSocket(true);
|
|
11867
|
+
}
|
|
11868
|
+
const sockets = this.openSockets().filter((socket) => socket.lives.size === 0);
|
|
11869
|
+
if (sockets.length === 0) {
|
|
11870
|
+
return await this.openSocket(true);
|
|
11871
|
+
}
|
|
11872
|
+
if (sockets.length < (this.ctx.options.pipelineSockets ?? PIPELINE_SOCKETS) && sockets.every((socket) => socket.held.size > 0) && Date.now() >= this._noExtraSocketUntil) {
|
|
11873
|
+
try {
|
|
11874
|
+
return await this.openSocket(false);
|
|
11875
|
+
} catch (error) {
|
|
11876
|
+
this._noExtraSocketUntil = Date.now() + EXTRA_SOCKET_RETRY_MS;
|
|
11877
|
+
this.ctx.requestLogger.info("could not open another pipeline API socket: %s", error);
|
|
11878
|
+
}
|
|
11879
|
+
}
|
|
11880
|
+
const rank = (socket) => (socket.hasRoom ? 0 : MAX_OPEN_SOURCES + 1) + socket.held.size;
|
|
11881
|
+
return sockets.reduce((best, socket) => rank(socket) < rank(best) ? socket : best);
|
|
11882
|
+
}
|
|
11883
|
+
/** A new socket with the last pop set on it; `patient` waits through 503 with Retry-After. */
|
|
11884
|
+
async openSocket(patient) {
|
|
11804
11885
|
if (this.ctx.client() === null) {
|
|
11805
11886
|
throw new PipelineSocketClosedError(void 0, "the endpoint is not connected");
|
|
11806
11887
|
}
|
|
11807
|
-
const ws = await this.upgrade(await this.socketUrl());
|
|
11888
|
+
const ws = await this.upgrade(await this.socketUrl(), patient);
|
|
11889
|
+
if (this._disconnecting) {
|
|
11890
|
+
ws.close(1e3);
|
|
11891
|
+
throw new PipelineSocketClosedError(void 0, "the endpoint disconnected");
|
|
11892
|
+
}
|
|
11808
11893
|
const socket = new PipelineSocket(ws);
|
|
11809
11894
|
ws.onmessage = (event) => {
|
|
11810
11895
|
if (typeof event.data === "string") {
|
|
@@ -11818,31 +11903,29 @@ var PipelineBackend = class {
|
|
|
11818
11903
|
ws.onerror = () => {
|
|
11819
11904
|
};
|
|
11820
11905
|
ws.onclose = (event) => this.closed(socket, event.code, event.reason);
|
|
11821
|
-
this.
|
|
11822
|
-
this.startHeartbeat(
|
|
11906
|
+
this._sockets.push(socket);
|
|
11907
|
+
this.startHeartbeat(socket);
|
|
11823
11908
|
if (this._currentPop !== null) {
|
|
11824
11909
|
try {
|
|
11825
11910
|
await this.setPopOn(socket, this._currentPop);
|
|
11826
11911
|
} catch (error) {
|
|
11827
|
-
this.
|
|
11912
|
+
this._sockets = this._sockets.filter((other) => other !== socket);
|
|
11828
11913
|
ws.close(1e3);
|
|
11829
11914
|
throw error;
|
|
11830
11915
|
}
|
|
11831
11916
|
}
|
|
11832
11917
|
return socket;
|
|
11833
11918
|
}
|
|
11834
|
-
startHeartbeat(
|
|
11835
|
-
|
|
11836
|
-
clearInterval(this._heartbeat);
|
|
11837
|
-
}
|
|
11919
|
+
startHeartbeat(socket) {
|
|
11920
|
+
const ws = socket.ws;
|
|
11838
11921
|
const ping = ws.ping;
|
|
11839
11922
|
if (typeof ping === "function") {
|
|
11840
|
-
|
|
11923
|
+
socket.heartbeat = setInterval(() => {
|
|
11841
11924
|
if (ws.readyState === 1) {
|
|
11842
11925
|
ping.call(ws);
|
|
11843
11926
|
}
|
|
11844
11927
|
}, HEARTBEAT_MS);
|
|
11845
|
-
|
|
11928
|
+
socket.heartbeat.unref?.();
|
|
11846
11929
|
}
|
|
11847
11930
|
}
|
|
11848
11931
|
async socketUrl() {
|
|
@@ -11880,7 +11963,7 @@ var PipelineBackend = class {
|
|
|
11880
11963
|
return (await this.ctx.credentialHeader()).replace(/^Bearer /, "");
|
|
11881
11964
|
}
|
|
11882
11965
|
/** Open the socket, waiting through 503 with Retry-After and exchanging the API key again after a 401. */
|
|
11883
|
-
async upgrade(url) {
|
|
11966
|
+
async upgrade(url, patient) {
|
|
11884
11967
|
const deadline = Date.now() + MAX_RETRY_MS;
|
|
11885
11968
|
let rejected = false;
|
|
11886
11969
|
while (true) {
|
|
@@ -11898,7 +11981,7 @@ var PipelineBackend = class {
|
|
|
11898
11981
|
continue;
|
|
11899
11982
|
}
|
|
11900
11983
|
const wait = error.status === 503 && error.retryAfter !== void 0 ? Math.max(error.retryAfter, 1) * 1e3 : void 0;
|
|
11901
|
-
if (wait === void 0 || Date.now() + wait > deadline) {
|
|
11984
|
+
if (!patient || wait === void 0 || Date.now() + wait > deadline) {
|
|
11902
11985
|
throw error;
|
|
11903
11986
|
}
|
|
11904
11987
|
this.ctx.requestLogger.info("the pipeline API answered 503, retrying in %d ms", wait);
|
|
@@ -11908,9 +11991,7 @@ var PipelineBackend = class {
|
|
|
11908
11991
|
}
|
|
11909
11992
|
closed(socket, code, reason) {
|
|
11910
11993
|
socket.markClosed();
|
|
11911
|
-
|
|
11912
|
-
this._socket = null;
|
|
11913
|
-
}
|
|
11994
|
+
this._sockets = this._sockets.filter((other) => other !== socket);
|
|
11914
11995
|
const error = new PipelineSocketClosedError(code, reason);
|
|
11915
11996
|
socket.sources.forEach((source) => source.close(error));
|
|
11916
11997
|
socket.commands.forEach((command) => command.reject(error));
|
|
@@ -11936,12 +12017,14 @@ var PipelineBackend = class {
|
|
|
11936
12017
|
const source = typeof id === "string" ? socket.sources.get(id) : void 0;
|
|
11937
12018
|
if (type === "end") {
|
|
11938
12019
|
socket.held.delete(id);
|
|
12020
|
+
socket.lives.delete(id);
|
|
11939
12021
|
socket.accepted.delete(id);
|
|
11940
12022
|
socket.freed();
|
|
11941
12023
|
} else if (type === "result" && socket.held.has(id)) {
|
|
11942
12024
|
socket.accepted.add(id);
|
|
11943
12025
|
} else if (type === "error" && message["error"] !== "unknown_command") {
|
|
11944
12026
|
socket.held.delete(id);
|
|
12027
|
+
socket.lives.delete(id);
|
|
11945
12028
|
socket.wake();
|
|
11946
12029
|
}
|
|
11947
12030
|
if (type === "send" || type === "credit") {
|
|
@@ -11983,6 +12066,10 @@ var PipelineBackend = class {
|
|
|
11983
12066
|
command(socket, message) {
|
|
11984
12067
|
const id = this.newId(message["type"]);
|
|
11985
12068
|
return new Promise((resolve, reject) => {
|
|
12069
|
+
if (!socket.open) {
|
|
12070
|
+
reject(new PipelineSocketClosedError(void 0, "the socket closed"));
|
|
12071
|
+
return;
|
|
12072
|
+
}
|
|
11986
12073
|
socket.commands.set(id, { resolve, reject });
|
|
11987
12074
|
socket.sendJson({ ...message, id });
|
|
11988
12075
|
});
|
|
@@ -11991,15 +12078,28 @@ var PipelineBackend = class {
|
|
|
11991
12078
|
pop() {
|
|
11992
12079
|
return this._currentPop;
|
|
11993
12080
|
}
|
|
11994
|
-
/**
|
|
12081
|
+
/**
|
|
12082
|
+
* Set the pop on every open socket's pipeline; a pop the first refuses rejects, and the last pop it
|
|
12083
|
+
* took stays. shortcut: a refusal from a later socket, which the first took, leaves the earlier ones
|
|
12084
|
+
* on the new pop; a refusal is the pop's own fault, so every socket answers it alike.
|
|
12085
|
+
*/
|
|
11995
12086
|
async changePop(pop) {
|
|
11996
12087
|
if (this.ctx.client() === null) {
|
|
11997
12088
|
this._currentPop = pop;
|
|
11998
12089
|
return;
|
|
11999
12090
|
}
|
|
12000
12091
|
await this.inOrder(async () => {
|
|
12001
|
-
|
|
12002
|
-
|
|
12092
|
+
const sockets = this.openSockets();
|
|
12093
|
+
if (sockets.length > 0) {
|
|
12094
|
+
for (const socket of sockets) {
|
|
12095
|
+
try {
|
|
12096
|
+
await this.setPopOn(socket, pop);
|
|
12097
|
+
} catch (error) {
|
|
12098
|
+
if (!(error instanceof PipelineSocketClosedError)) {
|
|
12099
|
+
throw error;
|
|
12100
|
+
}
|
|
12101
|
+
}
|
|
12102
|
+
}
|
|
12003
12103
|
this._currentPop = pop;
|
|
12004
12104
|
return;
|
|
12005
12105
|
}
|
|
@@ -12160,22 +12260,30 @@ var PipelineBackend = class {
|
|
|
12160
12260
|
}
|
|
12161
12261
|
/** Send the job's setSource; the caller runs it in submission order. */
|
|
12162
12262
|
async setSource(job) {
|
|
12163
|
-
if (job.cancelled) {
|
|
12164
|
-
return null;
|
|
12165
|
-
}
|
|
12166
|
-
let socket = await this.ensureSocket();
|
|
12167
|
-
await socket.room(job);
|
|
12168
12263
|
if (job.cancelled) {
|
|
12169
12264
|
return null;
|
|
12170
12265
|
}
|
|
12171
12266
|
const combined = await combinedMedia(job);
|
|
12172
|
-
socket
|
|
12267
|
+
let socket;
|
|
12268
|
+
while (true) {
|
|
12269
|
+
socket = await this.socketForJob(job);
|
|
12270
|
+
await socket.room(job);
|
|
12271
|
+
if (job.cancelled) {
|
|
12272
|
+
return null;
|
|
12273
|
+
}
|
|
12274
|
+
if (socket.open) {
|
|
12275
|
+
break;
|
|
12276
|
+
}
|
|
12277
|
+
}
|
|
12173
12278
|
const source = new OpenSource(this.newId("source"));
|
|
12174
12279
|
if (combined === null) {
|
|
12175
12280
|
uploadKeys(job).forEach((key) => source.uploads.set(key, new Credit()));
|
|
12176
12281
|
}
|
|
12177
12282
|
socket.sources.set(source.id, source);
|
|
12178
12283
|
socket.held.add(source.id);
|
|
12284
|
+
if (job.live) {
|
|
12285
|
+
socket.lives.add(source.id);
|
|
12286
|
+
}
|
|
12179
12287
|
source.endedBefore = socket.ended;
|
|
12180
12288
|
job.openSource = source;
|
|
12181
12289
|
job.stopOnPipeline = () => {
|
|
@@ -12211,7 +12319,11 @@ var PipelineBackend = class {
|
|
|
12211
12319
|
case "prediction":
|
|
12212
12320
|
job.predicted(message["prediction"]);
|
|
12213
12321
|
break;
|
|
12322
|
+
case "usage":
|
|
12323
|
+
job.noteMilliComputeUnits(message);
|
|
12324
|
+
break;
|
|
12214
12325
|
case "end":
|
|
12326
|
+
job.noteMilliComputeUnits(message);
|
|
12215
12327
|
if (message["reason"] === "eos") {
|
|
12216
12328
|
return null;
|
|
12217
12329
|
}
|
|
@@ -12451,6 +12563,9 @@ var WorkerEndpoint = class extends Endpoint {
|
|
|
12451
12563
|
if (options.jobQueueLength > MAX_OPEN_SOURCES) {
|
|
12452
12564
|
throw new PipelineConfigurationError("job_queue_length", `jobQueueLength ${options.jobQueueLength} exceeds ${MAX_OPEN_SOURCES}, the open sources a pipeline API socket takes`);
|
|
12453
12565
|
}
|
|
12566
|
+
if (options.pipelineSockets !== void 0 && !(Number.isInteger(options.pipelineSockets) && options.pipelineSockets > 0)) {
|
|
12567
|
+
throw new PipelineConfigurationError("pipeline_sockets", `pipelineSockets ${options.pipelineSockets} is not a positive whole number`);
|
|
12568
|
+
}
|
|
12454
12569
|
}
|
|
12455
12570
|
super(options);
|
|
12456
12571
|
const context = {
|
|
@@ -12772,6 +12887,7 @@ var InferAssetJob = class {
|
|
|
12772
12887
|
constructor(assetUrl, inferRequest, vlmFetch, requestLogger) {
|
|
12773
12888
|
this._response = null;
|
|
12774
12889
|
this._runInfo = null;
|
|
12890
|
+
this._milliComputeUnits = void 0;
|
|
12775
12891
|
this._controller = new AbortController();
|
|
12776
12892
|
this._vlmFetch = vlmFetch;
|
|
12777
12893
|
this._assetUrl = assetUrl;
|
|
@@ -12808,6 +12924,13 @@ var InferAssetJob = class {
|
|
|
12808
12924
|
}
|
|
12809
12925
|
return iterator();
|
|
12810
12926
|
}
|
|
12927
|
+
/**
|
|
12928
|
+
* The milli compute units vlm-server billed for the inference, 0 on a cache hit; undefined before it answered
|
|
12929
|
+
* or when it reported none.
|
|
12930
|
+
*/
|
|
12931
|
+
get milliComputeUnits() {
|
|
12932
|
+
return this._milliComputeUnits;
|
|
12933
|
+
}
|
|
12811
12934
|
start(done) {
|
|
12812
12935
|
this._response = new Promise(async (resolve, reject) => {
|
|
12813
12936
|
try {
|
|
@@ -12832,6 +12955,8 @@ var InferAssetJob = class {
|
|
|
12832
12955
|
const runInfo = response["run_info"];
|
|
12833
12956
|
if (predictions) {
|
|
12834
12957
|
this._runInfo = runInfo;
|
|
12958
|
+
const used = response["milli_compute_units"] ?? response["compute_units"];
|
|
12959
|
+
this._milliComputeUnits = typeof used === "number" ? used : void 0;
|
|
12835
12960
|
resolve(predictions);
|
|
12836
12961
|
break;
|
|
12837
12962
|
} else {
|
|
@@ -14425,6 +14550,7 @@ export {
|
|
|
14425
14550
|
ModelType,
|
|
14426
14551
|
MotionModel,
|
|
14427
14552
|
PIPELINE_PATH,
|
|
14553
|
+
PIPELINE_SOCKETS,
|
|
14428
14554
|
PipelineBackend,
|
|
14429
14555
|
PipelineCommandError,
|
|
14430
14556
|
PipelineConfigurationError,
|