@eyepop.ai/eyepop 3.25.4 → 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.
@@ -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, (statusCode) => this.statusHandler401(statusCode));
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, (statusCode) => this.statusHandler401(statusCode));
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 = null;
9053
- this._expire_token_time = null;
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
- 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 {
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
- } finally {
9141
- this.updateState();
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
 
@@ -11692,10 +11726,22 @@ var PipelineJob = class {
11692
11726
  this.openSource = null;
11693
11727
  // Stops the job's source on the pipeline, once it was sent.
11694
11728
  this.stopOnPipeline = null;
11729
+ this._milliComputeUnits = void 0;
11695
11730
  }
11696
11731
  get cancelled() {
11697
11732
  return this._cancelled;
11698
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
+ }
11699
11745
  command(id) {
11700
11746
  const command = { type: "setSource", id, ...this.source };
11701
11747
  if (this.params.componentParams !== void 0) {
@@ -12273,7 +12319,11 @@ var PipelineBackend = class {
12273
12319
  case "prediction":
12274
12320
  job.predicted(message["prediction"]);
12275
12321
  break;
12322
+ case "usage":
12323
+ job.noteMilliComputeUnits(message);
12324
+ break;
12276
12325
  case "end":
12326
+ job.noteMilliComputeUnits(message);
12277
12327
  if (message["reason"] === "eos") {
12278
12328
  return null;
12279
12329
  }
@@ -12837,6 +12887,7 @@ var InferAssetJob = class {
12837
12887
  constructor(assetUrl, inferRequest, vlmFetch, requestLogger) {
12838
12888
  this._response = null;
12839
12889
  this._runInfo = null;
12890
+ this._milliComputeUnits = void 0;
12840
12891
  this._controller = new AbortController();
12841
12892
  this._vlmFetch = vlmFetch;
12842
12893
  this._assetUrl = assetUrl;
@@ -12873,6 +12924,13 @@ var InferAssetJob = class {
12873
12924
  }
12874
12925
  return iterator();
12875
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
+ }
12876
12934
  start(done) {
12877
12935
  this._response = new Promise(async (resolve, reject) => {
12878
12936
  try {
@@ -12897,6 +12955,8 @@ var InferAssetJob = class {
12897
12955
  const runInfo = response["run_info"];
12898
12956
  if (predictions) {
12899
12957
  this._runInfo = runInfo;
12958
+ const used = response["milli_compute_units"] ?? response["compute_units"];
12959
+ this._milliComputeUnits = typeof used === "number" ? used : void 0;
12900
12960
  resolve(predictions);
12901
12961
  break;
12902
12962
  } else {