@eyepop.ai/eyepop 3.26.0-rc.1 → 3.26.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.
@@ -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 NOT_EXCHANGEABLE = ["API key or user login required", "API key authentication required for token refresh"];
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 = 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,75 @@ 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 to exchange
9130
+ * (see NOT_EXCHANGEABLE), 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
+ const refusal = response.status == 401 ? await response.clone().text() : "";
9136
+ if (!NOT_EXCHANGEABLE.some((message) => refusal.includes(message))) {
9137
+ await this.storeExchangedToken(response, now);
9138
+ return;
9139
+ }
9140
+ }
9141
+ this._token = loginToken;
9142
+ this._expire_token_time = validUntil;
9143
+ }
9098
9144
  async currentAccessToken() {
9145
+ if (!this._token || this._expire_token_time < Date.now() / 1e3) {
9146
+ this._exchanging ?? (this._exchanging = this.authenticate().finally(() => this._exchanging = null));
9147
+ await this._exchanging;
9148
+ }
9149
+ this._logger.debug("using access token, valid for at least %d seconds", this._expire_token_time - Date.now() / 1e3);
9150
+ return this._token;
9151
+ }
9152
+ async authenticate() {
9099
9153
  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 {
9154
+ this.updateState("Authenticating" /* Authenticating */);
9155
+ try {
9156
+ const auth = resolveAuth(this._options);
9157
+ if (!this._client) {
9158
+ return Promise.reject("endpoint not connected");
9159
+ } else if (auth?.isLocal !== void 0) {
9160
+ this._token = "implicit";
9161
+ this._expire_token_time = Number.MAX_VALUE;
9162
+ } else if (auth?.session !== void 0) {
9163
+ return Promise.reject("temporary access token expired");
9164
+ } else if (auth?.oAuth2 !== void 0 && this._options.eyepopUrl) {
9165
+ const login = await authenticateBrowserSession(auth.oAuth2, this._options);
9166
+ await this.exchangeLoginToken(this._client, login.accessToken, login.validUntil / 1e3, now);
9167
+ } else if (accessTokenCredential(auth) !== void 0) {
9168
+ await this.exchangeLoginToken(this._client, accessTokenCredential(auth), Number.MAX_VALUE, now);
9169
+ } else if (this._options.eyepopUrl) {
9170
+ const credential = apiKeyCredential(auth);
9171
+ if (credential === void 0) {
9138
9172
  return Promise.reject("no valid auth option");
9139
9173
  }
9140
- } finally {
9141
- this.updateState();
9174
+ await this.storeExchangedToken(await this.exchange(this._client, credential), now);
9175
+ } else {
9176
+ return Promise.reject("no valid auth option");
9142
9177
  }
9178
+ } finally {
9179
+ this.updateState();
9143
9180
  }
9144
- this._logger.debug("using access token, valid for at least %d seconds", this._expire_token_time - now);
9145
- return this._token;
9146
9181
  }
9147
9182
  };
9148
9183
 
@@ -11692,10 +11727,22 @@ var PipelineJob = class {
11692
11727
  this.openSource = null;
11693
11728
  // Stops the job's source on the pipeline, once it was sent.
11694
11729
  this.stopOnPipeline = null;
11730
+ this._milliComputeUnits = void 0;
11695
11731
  }
11696
11732
  get cancelled() {
11697
11733
  return this._cancelled;
11698
11734
  }
11735
+ /** The source's milli compute units: the latest total of its `usage` messages and its `end`. */
11736
+ get milliComputeUnits() {
11737
+ return this._milliComputeUnits;
11738
+ }
11739
+ /** Keep a usage's or an end's milliComputeUnits: each carries the source's total so far. */
11740
+ noteMilliComputeUnits(message) {
11741
+ const value = message["milliComputeUnits"];
11742
+ if (Number.isInteger(value) && value >= 0) {
11743
+ this._milliComputeUnits = value;
11744
+ }
11745
+ }
11699
11746
  command(id) {
11700
11747
  const command = { type: "setSource", id, ...this.source };
11701
11748
  if (this.params.componentParams !== void 0) {
@@ -12273,7 +12320,11 @@ var PipelineBackend = class {
12273
12320
  case "prediction":
12274
12321
  job.predicted(message["prediction"]);
12275
12322
  break;
12323
+ case "usage":
12324
+ job.noteMilliComputeUnits(message);
12325
+ break;
12276
12326
  case "end":
12327
+ job.noteMilliComputeUnits(message);
12277
12328
  if (message["reason"] === "eos") {
12278
12329
  return null;
12279
12330
  }
@@ -12837,6 +12888,7 @@ var InferAssetJob = class {
12837
12888
  constructor(assetUrl, inferRequest, vlmFetch, requestLogger) {
12838
12889
  this._response = null;
12839
12890
  this._runInfo = null;
12891
+ this._milliComputeUnits = void 0;
12840
12892
  this._controller = new AbortController();
12841
12893
  this._vlmFetch = vlmFetch;
12842
12894
  this._assetUrl = assetUrl;
@@ -12873,6 +12925,13 @@ var InferAssetJob = class {
12873
12925
  }
12874
12926
  return iterator();
12875
12927
  }
12928
+ /**
12929
+ * The milli compute units vlm-server billed for the inference, 0 on a cache hit; undefined before it answered
12930
+ * or when it reported none.
12931
+ */
12932
+ get milliComputeUnits() {
12933
+ return this._milliComputeUnits;
12934
+ }
12876
12935
  start(done) {
12877
12936
  this._response = new Promise(async (resolve, reject) => {
12878
12937
  try {
@@ -12897,6 +12956,8 @@ var InferAssetJob = class {
12897
12956
  const runInfo = response["run_info"];
12898
12957
  if (predictions) {
12899
12958
  this._runInfo = runInfo;
12959
+ const used = response["milli_compute_units"] ?? response["compute_units"];
12960
+ this._milliComputeUnits = typeof used === "number" ? used : void 0;
12900
12961
  resolve(predictions);
12901
12962
  break;
12902
12963
  } else {
@@ -13013,6 +13074,70 @@ function omit_nulls(_, value) {
13013
13074
  }
13014
13075
  return value;
13015
13076
  }
13077
+ var TASK_EXCLUDED_FIELDS = ["text_prompt", "transform_into", "response_schema"];
13078
+ var VlmAbilityModeErrorReason = /* @__PURE__ */ ((VlmAbilityModeErrorReason2) => {
13079
+ VlmAbilityModeErrorReason2["missing_mode"] = "missing_mode";
13080
+ VlmAbilityModeErrorReason2["unknown_task"] = "unknown_task";
13081
+ VlmAbilityModeErrorReason2["task_excludes_free_form"] = "task_excludes_free_form";
13082
+ VlmAbilityModeErrorReason2["task_without_prompts"] = "task_without_prompts";
13083
+ VlmAbilityModeErrorReason2["prompts_without_task"] = "prompts_without_task";
13084
+ VlmAbilityModeErrorReason2["empty_prompts"] = "empty_prompts";
13085
+ VlmAbilityModeErrorReason2["invalid_prompt"] = "invalid_prompt";
13086
+ return VlmAbilityModeErrorReason2;
13087
+ })(VlmAbilityModeErrorReason || {});
13088
+ var VlmAbilityModeError = class extends Error {
13089
+ constructor(reason, message, fields = []) {
13090
+ super(message);
13091
+ this.name = "VlmAbilityModeError";
13092
+ this.reason = reason;
13093
+ this.fields = fields;
13094
+ }
13095
+ };
13096
+ function isSet(value) {
13097
+ return value !== void 0 && value !== null;
13098
+ }
13099
+ function validateVlmAbilityMode(ability, partial) {
13100
+ const kind = partial ? "VLM ability update" : "VLM ability";
13101
+ const taskSet = isSet(ability.task);
13102
+ const promptsSet = isSet(ability.prompts);
13103
+ if (taskSet && !Object.values(AutoAnnotateTask).includes(ability.task)) {
13104
+ throw new VlmAbilityModeError("unknown_task" /* unknown_task */, `${kind}: unknown task '${String(ability.task)}', expected one of ${Object.values(AutoAnnotateTask).join(", ")}`, [
13105
+ "task"
13106
+ ]);
13107
+ }
13108
+ if (taskSet || promptsSet) {
13109
+ const conflicting = TASK_EXCLUDED_FIELDS.filter((field) => isSet(ability[field]));
13110
+ if (conflicting.length > 0) {
13111
+ throw new VlmAbilityModeError(
13112
+ "task_excludes_free_form" /* task_excludes_free_form */,
13113
+ `${kind}: a task derives the prompt and output schema, so ${conflicting.join(", ")} must be unset with task and prompts`,
13114
+ [...conflicting]
13115
+ );
13116
+ }
13117
+ }
13118
+ if (promptsSet) {
13119
+ if (!Array.isArray(ability.prompts) || ability.prompts.length === 0) {
13120
+ throw new VlmAbilityModeError("empty_prompts" /* empty_prompts */, `${kind}: prompts must be a non-empty list of {label, prompt}`, ["prompts"]);
13121
+ }
13122
+ ability.prompts.forEach((entry, i) => {
13123
+ if (!entry || typeof entry.label !== "string" || entry.label.length === 0 || typeof entry.prompt !== "string" || entry.prompt.length === 0) {
13124
+ throw new VlmAbilityModeError("invalid_prompt" /* invalid_prompt */, `${kind}: prompts[${i}] needs a non-empty label and prompt`, [`prompts[${i}]`]);
13125
+ }
13126
+ });
13127
+ }
13128
+ if (partial) {
13129
+ return;
13130
+ }
13131
+ if (taskSet && !promptsSet) {
13132
+ throw new VlmAbilityModeError("task_without_prompts" /* task_without_prompts */, `${kind}: task '${ability.task}' requires a non-empty prompts list of {label, prompt}`, ["prompts"]);
13133
+ }
13134
+ if (!taskSet && promptsSet) {
13135
+ throw new VlmAbilityModeError("prompts_without_task" /* prompts_without_task */, `${kind}: prompts are only used with a task; set task, or use text_prompt for a free-form ability`, ["prompts"]);
13136
+ }
13137
+ if (!taskSet && !(typeof ability.text_prompt === "string" && ability.text_prompt.length > 0)) {
13138
+ throw new VlmAbilityModeError("missing_mode" /* missing_mode */, `${kind}: set either text_prompt (free-form) or task with non-empty prompts`, ["text_prompt", "task"]);
13139
+ }
13140
+ }
13016
13141
  var DataEndpoint = class extends Endpoint {
13017
13142
  constructor(options) {
13018
13143
  super(options);
@@ -13804,6 +13929,7 @@ var DataEndpoint = class extends Endpoint {
13804
13929
  });
13805
13930
  }
13806
13931
  async createVlmAbility(create, vlm_ability_group_uuid) {
13932
+ validateVlmAbilityMode(create, false);
13807
13933
  const groupQuery = vlm_ability_group_uuid ? `&vlm_ability_group_uuid=${vlm_ability_group_uuid}` : "";
13808
13934
  return this.request(`/vlm_abilities?account_uuid=${this._accountId}${groupQuery}`, {
13809
13935
  method: "POST",
@@ -13822,6 +13948,7 @@ var DataEndpoint = class extends Endpoint {
13822
13948
  });
13823
13949
  }
13824
13950
  async updateVlmAbility(vlm_ability_uuid, update) {
13951
+ validateVlmAbilityMode(update, true);
13825
13952
  return this.request(`/vlm_abilities/${vlm_ability_uuid}`, {
13826
13953
  method: "PATCH",
13827
13954
  body: JSON.stringify(update),
@@ -14420,17 +14547,17 @@ var EyePop;
14420
14547
  if (typeof auth.oAuth2 === "boolean") {
14421
14548
  const oAuth2 = opts.eyepopUrl && opts.eyepopUrl.match(/https:(.*).staging.eyepop.xyz/i) ? {
14422
14549
  oAuth2: {
14423
- domain: "dev-eyepop.us.auth0.com",
14550
+ domain: "auth0.eyepop.xyz",
14424
14551
  clientId: "jktx3YO2UnbkNPvr05PQWf26t1kNTJyg",
14425
14552
  audience: "https://dev-app.eyepop.ai",
14426
- scope: "admin:clouds access:inference-api access:datasets"
14553
+ scope: "access:inference-api access:datasets"
14427
14554
  }
14428
14555
  } : {
14429
14556
  oAuth2: {
14430
- domain: "eyepop.us.auth0.com",
14557
+ domain: "auth0.eyepop.ai",
14431
14558
  clientId: "Lb9ubA9Hf3jlaqWLUx8XgA0zvotgViCl",
14432
14559
  audience: "https://api.eyepop.ai",
14433
- scope: "admin:clouds access:inference-api access:datasets"
14560
+ scope: "access:inference-api access:datasets"
14434
14561
  }
14435
14562
  };
14436
14563
  if (opts.oAuth2 !== void 0) {
@@ -14514,6 +14641,8 @@ export {
14514
14641
  UploadJob,
14515
14642
  UserReview,
14516
14643
  VideoMode,
14644
+ VlmAbilityModeError,
14645
+ VlmAbilityModeErrorReason,
14517
14646
  VlmAbilityStatus,
14518
14647
  WebrtcBase,
14519
14648
  WebrtcWhep,