@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.
@@ -8722,6 +8722,8 @@ __export(index_exports, {
8722
8722
  UploadJob: () => UploadJob,
8723
8723
  UserReview: () => UserReview,
8724
8724
  VideoMode: () => VideoMode,
8725
+ VlmAbilityModeError: () => VlmAbilityModeError,
8726
+ VlmAbilityModeErrorReason: () => VlmAbilityModeErrorReason,
8725
8727
  VlmAbilityStatus: () => VlmAbilityStatus,
8726
8728
  WebrtcBase: () => WebrtcBase,
8727
8729
  WebrtcWhep: () => WebrtcWhep,
@@ -9044,14 +9046,25 @@ if ("document" in globalThis && "implementation" in globalThis.document) {
9044
9046
  }
9045
9047
 
9046
9048
  // endpoint.ts
9049
+ var ACCOUNT_CLAIM = "https://ident.eyepop.ai/account-uuid";
9050
+ var NOT_EXCHANGEABLE = ["API key or user login required", "API key authentication required for token refresh"];
9051
+ function carriesAccount(token) {
9052
+ try {
9053
+ const payload = (token.split(".")[1] ?? "").replace(/-/g, "+").replace(/_/g, "/");
9054
+ return Boolean(JSON.parse(atob(payload))?.[ACCOUNT_CLAIM]);
9055
+ } catch {
9056
+ return false;
9057
+ }
9058
+ }
9047
9059
  var Endpoint = class {
9048
9060
  constructor(options) {
9061
+ this._exchanging = null;
9049
9062
  this._options = options;
9050
9063
  this._token = null;
9051
9064
  this._expire_token_time = null;
9052
9065
  this._client = null;
9053
9066
  this._statusRetryHandlers = /* @__PURE__ */ new Map();
9054
- this._statusRetryHandlers.set(401, this.statusHandler401);
9067
+ this._statusRetryHandlers.set(401, (statusCode, failedToken) => this.statusHandler401(statusCode, failedToken));
9055
9068
  this._limit = new Semaphore(this._options.jobQueueLength ?? 1024);
9056
9069
  this._state = "Idle" /* Idle */;
9057
9070
  this._message = null;
@@ -9067,7 +9080,7 @@ var Endpoint = class {
9067
9080
  }
9068
9081
  setStatusRetryHandlers(statusRetryHandlers = void 0) {
9069
9082
  this._statusRetryHandlers = new Map(statusRetryHandlers);
9070
- this._statusRetryHandlers.set(401, this.statusHandler401);
9083
+ this._statusRetryHandlers.set(401, (statusCode, failedToken) => this.statusHandler401(statusCode, failedToken));
9071
9084
  }
9072
9085
  eyepopUrl() {
9073
9086
  if (this._options.eyepopUrl) {
@@ -9164,12 +9177,15 @@ var Endpoint = class {
9164
9177
  validUntil: this._expire_token_time * 1e3
9165
9178
  };
9166
9179
  }
9167
- statusHandler401(_statusCode) {
9168
- this._token = null;
9169
- this._expire_token_time = null;
9180
+ statusHandler401(_statusCode, failedToken = this._token) {
9181
+ if (this._token === failedToken) {
9182
+ this._token = null;
9183
+ this._expire_token_time = null;
9184
+ }
9170
9185
  }
9171
9186
  async fetchWithRetry(fetcher, retries = 1) {
9172
9187
  while (true) {
9188
+ const sentToken = this._token ?? void 0;
9173
9189
  try {
9174
9190
  const response = await fetcher();
9175
9191
  if (response.ok) {
@@ -9177,7 +9193,7 @@ var Endpoint = class {
9177
9193
  } else {
9178
9194
  const handler = this._statusRetryHandlers.get(response.status);
9179
9195
  if (handler != null) {
9180
- handler(response.status);
9196
+ handler(response.status, sentToken);
9181
9197
  if (retries--) {
9182
9198
  this._requestLogger.info("received %d response, retry", response.status);
9183
9199
  } else {
@@ -9211,54 +9227,75 @@ var Endpoint = class {
9211
9227
  }
9212
9228
  return `Bearer ${await this.currentAccessToken()}`;
9213
9229
  }
9230
+ async exchange(client, credential, accountUuid) {
9231
+ const query = accountUuid ? `?account_uuid=${encodeURIComponent(accountUuid)}` : "";
9232
+ return await client.fetch(`${this.eyepopUrl()}/v1/auth/authenticate${query}`, {
9233
+ method: "POST",
9234
+ headers: { Authorization: `Bearer ${credential}` }
9235
+ });
9236
+ }
9237
+ async storeExchangedToken(response, now) {
9238
+ if (response.status != 200) {
9239
+ const message = await response.text();
9240
+ return Promise.reject(`Unexpected status ${response.status}: ${message}`);
9241
+ }
9242
+ const token = await response.json();
9243
+ this._token = token.access_token;
9244
+ this._expire_token_time = now + token.expires_in - 60;
9245
+ }
9246
+ /**
9247
+ * A token that already carries an account, or that the compute API refuses to exchange
9248
+ * (see NOT_EXCHANGEABLE), is used as is. Any other refusal is raised.
9249
+ */
9250
+ async exchangeLoginToken(client, loginToken, validUntil, now) {
9251
+ if (!carriesAccount(loginToken)) {
9252
+ const response = await this.exchange(client, loginToken, this._options.accountId);
9253
+ const refusal = response.status == 401 ? await response.clone().text() : "";
9254
+ if (!NOT_EXCHANGEABLE.some((message) => refusal.includes(message))) {
9255
+ await this.storeExchangedToken(response, now);
9256
+ return;
9257
+ }
9258
+ }
9259
+ this._token = loginToken;
9260
+ this._expire_token_time = validUntil;
9261
+ }
9214
9262
  async currentAccessToken() {
9263
+ if (!this._token || this._expire_token_time < Date.now() / 1e3) {
9264
+ this._exchanging ?? (this._exchanging = this.authenticate().finally(() => this._exchanging = null));
9265
+ await this._exchanging;
9266
+ }
9267
+ this._logger.debug("using access token, valid for at least %d seconds", this._expire_token_time - Date.now() / 1e3);
9268
+ return this._token;
9269
+ }
9270
+ async authenticate() {
9215
9271
  const now = Date.now() / 1e3;
9216
- if (!this._token || this._expire_token_time < now) {
9217
- this.updateState("Authenticating" /* Authenticating */);
9218
- try {
9219
- const auth = resolveAuth(this._options);
9220
- if (!this._client) {
9221
- return Promise.reject("endpoint not connected");
9222
- } else if (auth?.isLocal !== void 0) {
9223
- this._token = "implicit";
9224
- this._expire_token_time = Number.MAX_VALUE;
9225
- } else if (auth?.session !== void 0) {
9226
- return Promise.reject("temporary access token expired");
9227
- } else if (auth?.oAuth2 !== void 0 && this._options.eyepopUrl) {
9228
- const session = await authenticateBrowserSession(auth.oAuth2, this._options);
9229
- this._token = session.accessToken;
9230
- this._expire_token_time = session.validUntil / 1e3;
9231
- } else if (accessTokenCredential(auth) !== void 0) {
9232
- this._token = accessTokenCredential(auth);
9233
- this._expire_token_time = Number.MAX_VALUE;
9234
- } else if (this._options.eyepopUrl) {
9235
- const credential = apiKeyCredential(auth);
9236
- if (credential === void 0) {
9237
- return Promise.reject("no valid auth option");
9238
- }
9239
- const headers = { Authorization: `Bearer ${credential}` };
9240
- const post_url = `${this.eyepopUrl()}/v1/auth/authenticate`;
9241
- const response = await this._client.fetch(post_url, {
9242
- method: "POST",
9243
- headers
9244
- });
9245
- if (response.status != 200) {
9246
- const message = await response.text();
9247
- return Promise.reject(`Unexpected status ${response.status}: ${message}`);
9248
- }
9249
- const data = await response.json();
9250
- const token = data;
9251
- this._token = token.access_token;
9252
- this._expire_token_time = now + token.expires_in - 60;
9253
- } else {
9272
+ this.updateState("Authenticating" /* Authenticating */);
9273
+ try {
9274
+ const auth = resolveAuth(this._options);
9275
+ if (!this._client) {
9276
+ return Promise.reject("endpoint not connected");
9277
+ } else if (auth?.isLocal !== void 0) {
9278
+ this._token = "implicit";
9279
+ this._expire_token_time = Number.MAX_VALUE;
9280
+ } else if (auth?.session !== void 0) {
9281
+ return Promise.reject("temporary access token expired");
9282
+ } else if (auth?.oAuth2 !== void 0 && this._options.eyepopUrl) {
9283
+ const login = await authenticateBrowserSession(auth.oAuth2, this._options);
9284
+ await this.exchangeLoginToken(this._client, login.accessToken, login.validUntil / 1e3, now);
9285
+ } else if (accessTokenCredential(auth) !== void 0) {
9286
+ await this.exchangeLoginToken(this._client, accessTokenCredential(auth), Number.MAX_VALUE, now);
9287
+ } else if (this._options.eyepopUrl) {
9288
+ const credential = apiKeyCredential(auth);
9289
+ if (credential === void 0) {
9254
9290
  return Promise.reject("no valid auth option");
9255
9291
  }
9256
- } finally {
9257
- this.updateState();
9292
+ await this.storeExchangedToken(await this.exchange(this._client, credential), now);
9293
+ } else {
9294
+ return Promise.reject("no valid auth option");
9258
9295
  }
9296
+ } finally {
9297
+ this.updateState();
9259
9298
  }
9260
- this._logger.debug("using access token, valid for at least %d seconds", this._expire_token_time - now);
9261
- return this._token;
9262
9299
  }
9263
9300
  };
9264
9301
 
@@ -11808,10 +11845,22 @@ var PipelineJob = class {
11808
11845
  this.openSource = null;
11809
11846
  // Stops the job's source on the pipeline, once it was sent.
11810
11847
  this.stopOnPipeline = null;
11848
+ this._milliComputeUnits = void 0;
11811
11849
  }
11812
11850
  get cancelled() {
11813
11851
  return this._cancelled;
11814
11852
  }
11853
+ /** The source's milli compute units: the latest total of its `usage` messages and its `end`. */
11854
+ get milliComputeUnits() {
11855
+ return this._milliComputeUnits;
11856
+ }
11857
+ /** Keep a usage's or an end's milliComputeUnits: each carries the source's total so far. */
11858
+ noteMilliComputeUnits(message) {
11859
+ const value = message["milliComputeUnits"];
11860
+ if (Number.isInteger(value) && value >= 0) {
11861
+ this._milliComputeUnits = value;
11862
+ }
11863
+ }
11815
11864
  command(id) {
11816
11865
  const command = { type: "setSource", id, ...this.source };
11817
11866
  if (this.params.componentParams !== void 0) {
@@ -12389,7 +12438,11 @@ var PipelineBackend = class {
12389
12438
  case "prediction":
12390
12439
  job.predicted(message["prediction"]);
12391
12440
  break;
12441
+ case "usage":
12442
+ job.noteMilliComputeUnits(message);
12443
+ break;
12392
12444
  case "end":
12445
+ job.noteMilliComputeUnits(message);
12393
12446
  if (message["reason"] === "eos") {
12394
12447
  return null;
12395
12448
  }
@@ -12953,6 +13006,7 @@ var InferAssetJob = class {
12953
13006
  constructor(assetUrl, inferRequest, vlmFetch, requestLogger) {
12954
13007
  this._response = null;
12955
13008
  this._runInfo = null;
13009
+ this._milliComputeUnits = void 0;
12956
13010
  this._controller = new AbortController();
12957
13011
  this._vlmFetch = vlmFetch;
12958
13012
  this._assetUrl = assetUrl;
@@ -12989,6 +13043,13 @@ var InferAssetJob = class {
12989
13043
  }
12990
13044
  return iterator();
12991
13045
  }
13046
+ /**
13047
+ * The milli compute units vlm-server billed for the inference, 0 on a cache hit; undefined before it answered
13048
+ * or when it reported none.
13049
+ */
13050
+ get milliComputeUnits() {
13051
+ return this._milliComputeUnits;
13052
+ }
12992
13053
  start(done) {
12993
13054
  this._response = new Promise(async (resolve, reject) => {
12994
13055
  try {
@@ -13013,6 +13074,8 @@ var InferAssetJob = class {
13013
13074
  const runInfo = response["run_info"];
13014
13075
  if (predictions) {
13015
13076
  this._runInfo = runInfo;
13077
+ const used = response["milli_compute_units"] ?? response["compute_units"];
13078
+ this._milliComputeUnits = typeof used === "number" ? used : void 0;
13016
13079
  resolve(predictions);
13017
13080
  break;
13018
13081
  } else {
@@ -13129,6 +13192,70 @@ function omit_nulls(_, value) {
13129
13192
  }
13130
13193
  return value;
13131
13194
  }
13195
+ var TASK_EXCLUDED_FIELDS = ["text_prompt", "transform_into", "response_schema"];
13196
+ var VlmAbilityModeErrorReason = /* @__PURE__ */ ((VlmAbilityModeErrorReason2) => {
13197
+ VlmAbilityModeErrorReason2["missing_mode"] = "missing_mode";
13198
+ VlmAbilityModeErrorReason2["unknown_task"] = "unknown_task";
13199
+ VlmAbilityModeErrorReason2["task_excludes_free_form"] = "task_excludes_free_form";
13200
+ VlmAbilityModeErrorReason2["task_without_prompts"] = "task_without_prompts";
13201
+ VlmAbilityModeErrorReason2["prompts_without_task"] = "prompts_without_task";
13202
+ VlmAbilityModeErrorReason2["empty_prompts"] = "empty_prompts";
13203
+ VlmAbilityModeErrorReason2["invalid_prompt"] = "invalid_prompt";
13204
+ return VlmAbilityModeErrorReason2;
13205
+ })(VlmAbilityModeErrorReason || {});
13206
+ var VlmAbilityModeError = class extends Error {
13207
+ constructor(reason, message, fields = []) {
13208
+ super(message);
13209
+ this.name = "VlmAbilityModeError";
13210
+ this.reason = reason;
13211
+ this.fields = fields;
13212
+ }
13213
+ };
13214
+ function isSet(value) {
13215
+ return value !== void 0 && value !== null;
13216
+ }
13217
+ function validateVlmAbilityMode(ability, partial) {
13218
+ const kind = partial ? "VLM ability update" : "VLM ability";
13219
+ const taskSet = isSet(ability.task);
13220
+ const promptsSet = isSet(ability.prompts);
13221
+ if (taskSet && !Object.values(AutoAnnotateTask).includes(ability.task)) {
13222
+ throw new VlmAbilityModeError("unknown_task" /* unknown_task */, `${kind}: unknown task '${String(ability.task)}', expected one of ${Object.values(AutoAnnotateTask).join(", ")}`, [
13223
+ "task"
13224
+ ]);
13225
+ }
13226
+ if (taskSet || promptsSet) {
13227
+ const conflicting = TASK_EXCLUDED_FIELDS.filter((field) => isSet(ability[field]));
13228
+ if (conflicting.length > 0) {
13229
+ throw new VlmAbilityModeError(
13230
+ "task_excludes_free_form" /* task_excludes_free_form */,
13231
+ `${kind}: a task derives the prompt and output schema, so ${conflicting.join(", ")} must be unset with task and prompts`,
13232
+ [...conflicting]
13233
+ );
13234
+ }
13235
+ }
13236
+ if (promptsSet) {
13237
+ if (!Array.isArray(ability.prompts) || ability.prompts.length === 0) {
13238
+ throw new VlmAbilityModeError("empty_prompts" /* empty_prompts */, `${kind}: prompts must be a non-empty list of {label, prompt}`, ["prompts"]);
13239
+ }
13240
+ ability.prompts.forEach((entry, i) => {
13241
+ if (!entry || typeof entry.label !== "string" || entry.label.length === 0 || typeof entry.prompt !== "string" || entry.prompt.length === 0) {
13242
+ throw new VlmAbilityModeError("invalid_prompt" /* invalid_prompt */, `${kind}: prompts[${i}] needs a non-empty label and prompt`, [`prompts[${i}]`]);
13243
+ }
13244
+ });
13245
+ }
13246
+ if (partial) {
13247
+ return;
13248
+ }
13249
+ if (taskSet && !promptsSet) {
13250
+ throw new VlmAbilityModeError("task_without_prompts" /* task_without_prompts */, `${kind}: task '${ability.task}' requires a non-empty prompts list of {label, prompt}`, ["prompts"]);
13251
+ }
13252
+ if (!taskSet && promptsSet) {
13253
+ 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"]);
13254
+ }
13255
+ if (!taskSet && !(typeof ability.text_prompt === "string" && ability.text_prompt.length > 0)) {
13256
+ throw new VlmAbilityModeError("missing_mode" /* missing_mode */, `${kind}: set either text_prompt (free-form) or task with non-empty prompts`, ["text_prompt", "task"]);
13257
+ }
13258
+ }
13132
13259
  var DataEndpoint = class extends Endpoint {
13133
13260
  constructor(options) {
13134
13261
  super(options);
@@ -13920,6 +14047,7 @@ var DataEndpoint = class extends Endpoint {
13920
14047
  });
13921
14048
  }
13922
14049
  async createVlmAbility(create, vlm_ability_group_uuid) {
14050
+ validateVlmAbilityMode(create, false);
13923
14051
  const groupQuery = vlm_ability_group_uuid ? `&vlm_ability_group_uuid=${vlm_ability_group_uuid}` : "";
13924
14052
  return this.request(`/vlm_abilities?account_uuid=${this._accountId}${groupQuery}`, {
13925
14053
  method: "POST",
@@ -13938,6 +14066,7 @@ var DataEndpoint = class extends Endpoint {
13938
14066
  });
13939
14067
  }
13940
14068
  async updateVlmAbility(vlm_ability_uuid, update) {
14069
+ validateVlmAbilityMode(update, true);
13941
14070
  return this.request(`/vlm_abilities/${vlm_ability_uuid}`, {
13942
14071
  method: "PATCH",
13943
14072
  body: JSON.stringify(update),
@@ -14536,17 +14665,17 @@ var EyePop;
14536
14665
  if (typeof auth.oAuth2 === "boolean") {
14537
14666
  const oAuth2 = opts.eyepopUrl && opts.eyepopUrl.match(/https:(.*).staging.eyepop.xyz/i) ? {
14538
14667
  oAuth2: {
14539
- domain: "dev-eyepop.us.auth0.com",
14668
+ domain: "auth0.eyepop.xyz",
14540
14669
  clientId: "jktx3YO2UnbkNPvr05PQWf26t1kNTJyg",
14541
14670
  audience: "https://dev-app.eyepop.ai",
14542
- scope: "admin:clouds access:inference-api access:datasets"
14671
+ scope: "access:inference-api access:datasets"
14543
14672
  }
14544
14673
  } : {
14545
14674
  oAuth2: {
14546
- domain: "eyepop.us.auth0.com",
14675
+ domain: "auth0.eyepop.ai",
14547
14676
  clientId: "Lb9ubA9Hf3jlaqWLUx8XgA0zvotgViCl",
14548
14677
  audience: "https://api.eyepop.ai",
14549
- scope: "admin:clouds access:inference-api access:datasets"
14678
+ scope: "access:inference-api access:datasets"
14550
14679
  }
14551
14680
  };
14552
14681
  if (opts.oAuth2 !== void 0) {
@@ -14631,6 +14760,8 @@ var index_default = EyePop;
14631
14760
  UploadJob,
14632
14761
  UserReview,
14633
14762
  VideoMode,
14763
+ VlmAbilityModeError,
14764
+ VlmAbilityModeErrorReason,
14634
14765
  VlmAbilityStatus,
14635
14766
  WebrtcBase,
14636
14767
  WebrtcWhep,