@whittlelabs/sifter 0.30.10 → 0.30.12

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.
Files changed (3) hide show
  1. package/bin.js +234 -116
  2. package/bin.js.map +4 -4
  3. package/package.json +1 -1
package/bin.js CHANGED
@@ -11277,6 +11277,167 @@ var require_rate_limited = __commonJS({
11277
11277
  }
11278
11278
  });
11279
11279
 
11280
+ // ../../packages/keep/dist/exchange.js
11281
+ var require_exchange = __commonJS({
11282
+ "../../packages/keep/dist/exchange.js"(exports2) {
11283
+ "use strict";
11284
+ Object.defineProperty(exports2, "__esModule", { value: true });
11285
+ exports2.KeepUnavailableError = void 0;
11286
+ exports2.exchangeCredentials = exchangeCredentials;
11287
+ exports2.hostOf = hostOf;
11288
+ var rate_limited_1 = require_rate_limited();
11289
+ var KeepUnavailableError = class extends Error {
11290
+ code = "SERVICE_UNAVAILABLE";
11291
+ /** Host the exchange was sent to, so a log line names the service that is down. */
11292
+ host;
11293
+ /** The status whatever answered sent, or null when nothing answered at all. */
11294
+ statusCode;
11295
+ constructor(message, detail) {
11296
+ super(message, detail.cause !== void 0 ? { cause: detail.cause } : void 0);
11297
+ this.name = "KeepUnavailableError";
11298
+ this.host = detail.host;
11299
+ this.statusCode = detail.statusCode;
11300
+ }
11301
+ };
11302
+ exports2.KeepUnavailableError = KeepUnavailableError;
11303
+ async function exchangeCredentials(request) {
11304
+ const { manager, url } = request;
11305
+ const host = hostOf(url);
11306
+ let response;
11307
+ try {
11308
+ response = await fetch(url, {
11309
+ method: "POST",
11310
+ headers: { "Content-Type": "application/json" },
11311
+ body: JSON.stringify({
11312
+ clientId: request.clientId,
11313
+ clientSecret: request.clientSecret
11314
+ })
11315
+ });
11316
+ } catch (err) {
11317
+ throw new KeepUnavailableError(`[${manager}] could not reach Keep at ${host}: ${describeTransportError(err)}`, { host, statusCode: null, cause: err });
11318
+ }
11319
+ request.onResponse?.(response);
11320
+ const text = await response.text().catch(() => "");
11321
+ let json;
11322
+ try {
11323
+ json = JSON.parse(text);
11324
+ } catch {
11325
+ json = null;
11326
+ }
11327
+ if (response.status >= 500) {
11328
+ const reason = json?.error?.message ?? `${request.exchangeName} failed with status ${response.status}`;
11329
+ throw new KeepUnavailableError(`[${manager}] Keep at ${host} is unavailable (${response.status}): ${reason}`, { host, statusCode: response.status });
11330
+ }
11331
+ if (response.status === 429) {
11332
+ throw request.rateLimit.close({
11333
+ manager,
11334
+ host,
11335
+ reason: json?.error?.message ?? "Too Many Requests",
11336
+ retryAfterMs: (0, rate_limited_1.retryAfterMsOf)(json, response.headers)
11337
+ });
11338
+ }
11339
+ if (json === null) {
11340
+ throw new Error(`[${manager}] Keep at ${host} answered ${response.status} with a body that is not JSON`);
11341
+ }
11342
+ if (!response.ok || !json.success) {
11343
+ const msg = json.error?.message ?? `${request.exchangeName} failed with status ${response.status}`;
11344
+ if (response.status === 401 && request.refused) {
11345
+ throw request.refused(msg);
11346
+ }
11347
+ throw new Error(`[${manager}] ${msg}`);
11348
+ }
11349
+ return json.data;
11350
+ }
11351
+ function hostOf(url) {
11352
+ try {
11353
+ return new URL(url).host;
11354
+ } catch {
11355
+ return url;
11356
+ }
11357
+ }
11358
+ function describeTransportError(err) {
11359
+ if (err instanceof Error) {
11360
+ const cause = err.cause;
11361
+ if (cause instanceof Error && cause.message && cause.message !== err.message) {
11362
+ return `${err.message}: ${cause.message}`;
11363
+ }
11364
+ return err.message;
11365
+ }
11366
+ return String(err);
11367
+ }
11368
+ }
11369
+ });
11370
+
11371
+ // ../../packages/keep/dist/wait-for-token.js
11372
+ var require_wait_for_token = __commonJS({
11373
+ "../../packages/keep/dist/wait-for-token.js"(exports2) {
11374
+ "use strict";
11375
+ Object.defineProperty(exports2, "__esModule", { value: true });
11376
+ exports2.waitForKeepToken = waitForKeepToken;
11377
+ var WAIT_FOR_TOKEN_MAX_MS = 20 * 6e4;
11378
+ var INITIAL_DELAY_MS = 1e3;
11379
+ var MAX_DELAY_MS = 3e4;
11380
+ async function waitForKeepToken(getToken, options) {
11381
+ const logger = options.logger ?? console;
11382
+ const initialDelay = options.initialDelayMs ?? INITIAL_DELAY_MS;
11383
+ const maxDelay = options.maxDelayMs ?? MAX_DELAY_MS;
11384
+ const started = Date.now();
11385
+ const deadline = started + (options.maxWaitMs ?? WAIT_FOR_TOKEN_MAX_MS);
11386
+ const tag = `[${options.label}]`;
11387
+ let delay = initialDelay;
11388
+ let waiting = null;
11389
+ for (; ; ) {
11390
+ let pause;
11391
+ try {
11392
+ const token = await getToken();
11393
+ if (waiting !== null) {
11394
+ logger.info(`${tag} Keep issued a token after ${formatDuration(Date.now() - started)} of waiting`);
11395
+ }
11396
+ return token;
11397
+ } catch (err) {
11398
+ if (isRateLimited(err)) {
11399
+ pause = Math.max(0, err.retryAfterMs);
11400
+ delay = initialDelay;
11401
+ if (waiting !== "rate-limited") {
11402
+ waiting = "rate-limited";
11403
+ logger.warn(`${tag} Keep is rate limiting this client's token exchange (${err.message}). The credentials are not in question; waiting ${formatDuration(pause)} to ask again.`);
11404
+ }
11405
+ } else if (isUnavailable(err)) {
11406
+ pause = delay;
11407
+ delay = Math.min(delay * 2, maxDelay);
11408
+ if (waiting !== "unavailable") {
11409
+ waiting = "unavailable";
11410
+ logger.warn(`${tag} Keep is unavailable (${err.message}); retrying with backoff.`);
11411
+ }
11412
+ } else {
11413
+ throw err;
11414
+ }
11415
+ if (Date.now() + pause > deadline) {
11416
+ logger.error(`${tag} giving up on a Keep token after ${formatDuration(Date.now() - started)}: ${err.message}`);
11417
+ throw err;
11418
+ }
11419
+ }
11420
+ await new Promise((resolve) => setTimeout(resolve, pause));
11421
+ }
11422
+ }
11423
+ function isRateLimited(err) {
11424
+ const e = err;
11425
+ return e?.code === "RATE_LIMITED" && typeof e.retryAfterMs === "number";
11426
+ }
11427
+ function isUnavailable(err) {
11428
+ return err?.code === "SERVICE_UNAVAILABLE";
11429
+ }
11430
+ function formatDuration(ms) {
11431
+ const s = Math.round(ms / 1e3);
11432
+ if (s < 60)
11433
+ return `${s}s`;
11434
+ const m = Math.floor(s / 60);
11435
+ const rest = s % 60;
11436
+ return rest === 0 ? `${m}m` : `${m}m${rest}s`;
11437
+ }
11438
+ }
11439
+ });
11440
+
11280
11441
  // ../../packages/keep/dist/service-token.js
11281
11442
  var require_service_token = __commonJS({
11282
11443
  "../../packages/keep/dist/service-token.js"(exports2) {
@@ -11284,6 +11445,8 @@ var require_service_token = __commonJS({
11284
11445
  Object.defineProperty(exports2, "__esModule", { value: true });
11285
11446
  exports2.ServiceTokenManager = void 0;
11286
11447
  var rate_limited_1 = require_rate_limited();
11448
+ var exchange_1 = require_exchange();
11449
+ var wait_for_token_1 = require_wait_for_token();
11287
11450
  var ServiceTokenManager = class {
11288
11451
  token = null;
11289
11452
  expiresAt = 0;
@@ -11308,6 +11471,14 @@ var require_service_token = __commonJS({
11308
11471
  /**
11309
11472
  * Returns a valid service token, refreshing if needed.
11310
11473
  * Returns null if Keep credentials are not configured (graceful degradation for local dev).
11474
+ *
11475
+ * Throws `KeepUnavailableError` when Keep did not answer or answered 5xx,
11476
+ * `KeepRateLimitedError` when Keep's exchange limiter locked this client out
11477
+ * (and, until the time Keep named, without asking again), and a plain `Error`
11478
+ * for any other refusal, a 401 above all. A refresh that fails for either
11479
+ * of the first two reasons while the cached token has not yet expired
11480
+ * answers with the cached token instead: the refresh starts 30s early
11481
+ * precisely so a failed one costs nothing.
11311
11482
  */
11312
11483
  async getToken() {
11313
11484
  if (!this.enabled)
@@ -11318,52 +11489,51 @@ var require_service_token = __commonJS({
11318
11489
  if (this.pendingExchange) {
11319
11490
  return this.pendingExchange;
11320
11491
  }
11321
- this.rateLimit.check();
11322
- this.pendingExchange = this.exchange();
11492
+ this.pendingExchange = this.refresh();
11323
11493
  try {
11324
11494
  return await this.pendingExchange;
11325
11495
  } finally {
11326
11496
  this.pendingExchange = null;
11327
11497
  }
11328
11498
  }
11499
+ /**
11500
+ * `getToken`, for a service that cannot start without a token: waits out a
11501
+ * rate limit (for the time Keep named) and an outage (with backoff), bounded
11502
+ * by `maxWaitMs` (20 minutes by default, one full lockout and change), and
11503
+ * throws a refusal at once. See `waitForKeepToken`.
11504
+ */
11505
+ waitForToken(options) {
11506
+ return (0, wait_for_token_1.waitForKeepToken)(() => this.getToken(), options);
11507
+ }
11508
+ async refresh() {
11509
+ try {
11510
+ this.rateLimit.check();
11511
+ return await this.exchange();
11512
+ } catch (err) {
11513
+ const code = err?.code;
11514
+ if (this.token && Date.now() < this.expiresAt - STALE_MARGIN_MS && (code === "SERVICE_UNAVAILABLE" || code === "RATE_LIMITED")) {
11515
+ return this.token;
11516
+ }
11517
+ throw err;
11518
+ }
11519
+ }
11329
11520
  async exchange() {
11330
- const url = `${this.keepApiUrl}/api/service-tokens`;
11331
- const response = await fetch(url, {
11332
- method: "POST",
11333
- headers: { "Content-Type": "application/json" },
11334
- body: JSON.stringify({
11335
- clientId: this.clientId,
11336
- clientSecret: this.clientSecret
11337
- })
11521
+ const data = await (0, exchange_1.exchangeCredentials)({
11522
+ manager: "ServiceTokenManager",
11523
+ url: `${this.keepApiUrl}/api/service-tokens`,
11524
+ clientId: this.clientId,
11525
+ clientSecret: this.clientSecret,
11526
+ rateLimit: this.rateLimit,
11527
+ onResponse: this.onResponse,
11528
+ exchangeName: "Token exchange"
11338
11529
  });
11339
- this.onResponse?.(response);
11340
- if (response.status === 429) {
11341
- const body = await response.json().catch(() => null);
11342
- throw this.rateLimit.close({
11343
- manager: "ServiceTokenManager",
11344
- host: hostOf(url),
11345
- reason: body?.error?.message ?? "Too Many Requests",
11346
- retryAfterMs: (0, rate_limited_1.retryAfterMsOf)(body, response.headers)
11347
- });
11348
- }
11349
- const json = await response.json();
11350
- if (!response.ok || !json.success) {
11351
- const msg = json.error?.message ?? `Token exchange failed with status ${response.status}`;
11352
- throw new Error(`[ServiceTokenManager] ${msg}`);
11353
- }
11354
- this.token = json.data.token;
11355
- this.expiresAt = Date.now() + json.data.expiresIn * 1e3;
11530
+ this.token = data.token;
11531
+ this.expiresAt = Date.now() + data.expiresIn * 1e3;
11356
11532
  return this.token;
11357
11533
  }
11358
11534
  };
11359
11535
  exports2.ServiceTokenManager = ServiceTokenManager;
11360
- function hostOf(url) {
11361
- try {
11362
- return new URL(url).host;
11363
- } catch {
11364
- return url;
11365
- }
11366
- }
11536
+ var STALE_MARGIN_MS = 5e3;
11367
11537
  }
11368
11538
  });
11369
11539
 
@@ -11372,8 +11542,9 @@ var require_agent_token = __commonJS({
11372
11542
  "../../packages/keep/dist/agent-token.js"(exports2) {
11373
11543
  "use strict";
11374
11544
  Object.defineProperty(exports2, "__esModule", { value: true });
11375
- exports2.AgentTokenManager = exports2.KeepUnavailableError = exports2.AgentCredentialRevokedError = void 0;
11545
+ exports2.AgentTokenManager = exports2.AgentCredentialRevokedError = void 0;
11376
11546
  var rate_limited_1 = require_rate_limited();
11547
+ var exchange_1 = require_exchange();
11377
11548
  var AgentCredentialRevokedError2 = class extends Error {
11378
11549
  code = "PAIRING_REVOKED";
11379
11550
  constructor(message) {
@@ -11382,20 +11553,6 @@ var require_agent_token = __commonJS({
11382
11553
  }
11383
11554
  };
11384
11555
  exports2.AgentCredentialRevokedError = AgentCredentialRevokedError2;
11385
- var KeepUnavailableError = class extends Error {
11386
- code = "SERVICE_UNAVAILABLE";
11387
- /** Host the exchange was sent to, so a log line names the service that is down. */
11388
- host;
11389
- /** The status whatever answered sent, or null when nothing answered at all. */
11390
- statusCode;
11391
- constructor(message, detail) {
11392
- super(message, detail.cause !== void 0 ? { cause: detail.cause } : void 0);
11393
- this.name = "KeepUnavailableError";
11394
- this.host = detail.host;
11395
- this.statusCode = detail.statusCode;
11396
- }
11397
- };
11398
- exports2.KeepUnavailableError = KeepUnavailableError;
11399
11556
  var AgentTokenManager = class {
11400
11557
  token = null;
11401
11558
  expiresAt = 0;
@@ -11432,74 +11589,22 @@ var require_agent_token = __commonJS({
11432
11589
  }
11433
11590
  }
11434
11591
  async exchange() {
11435
- const url = `${this.keepApiUrl}/api/agent-tokens`;
11436
- const host = hostOf(url);
11437
- let response;
11438
- try {
11439
- response = await fetch(url, {
11440
- method: "POST",
11441
- headers: { "Content-Type": "application/json" },
11442
- body: JSON.stringify({
11443
- clientId: this.clientId,
11444
- clientSecret: this.clientSecret
11445
- })
11446
- });
11447
- } catch (err) {
11448
- throw new KeepUnavailableError(`[AgentTokenManager] could not reach Keep at ${host}: ${describeTransportError(err)}`, { host, statusCode: null, cause: err });
11449
- }
11450
- this.onResponse?.(response);
11451
- const text = await response.text().catch(() => "");
11452
- let json;
11453
- try {
11454
- json = JSON.parse(text);
11455
- } catch {
11456
- json = null;
11457
- }
11458
- if (response.status >= 500) {
11459
- const reason = json?.error?.message ?? `Agent token exchange failed with status ${response.status}`;
11460
- throw new KeepUnavailableError(`[AgentTokenManager] Keep at ${host} is unavailable (${response.status}): ${reason}`, { host, statusCode: response.status });
11461
- }
11462
- if (response.status === 429) {
11463
- throw this.rateLimit.close({
11464
- manager: "AgentTokenManager",
11465
- host,
11466
- reason: json?.error?.message ?? "Too Many Requests",
11467
- retryAfterMs: (0, rate_limited_1.retryAfterMsOf)(json, response.headers)
11468
- });
11469
- }
11470
- if (json === null) {
11471
- throw new Error(`[AgentTokenManager] Keep at ${host} answered ${response.status} with a body that is not JSON`);
11472
- }
11473
- if (!response.ok || !json.success) {
11474
- const msg = json.error?.message ?? `Agent token exchange failed with status ${response.status}`;
11475
- if (response.status === 401) {
11476
- throw new AgentCredentialRevokedError2(`[AgentTokenManager] Keep refused this agent's credentials (401): the pairing was revoked or the secret rotated. Re-pair to continue.`);
11477
- }
11478
- throw new Error(`[AgentTokenManager] ${msg}`);
11479
- }
11480
- this.token = json.data.token;
11481
- this.expiresAt = Date.now() + json.data.expiresIn * 1e3;
11592
+ const data = await (0, exchange_1.exchangeCredentials)({
11593
+ manager: "AgentTokenManager",
11594
+ url: `${this.keepApiUrl}/api/agent-tokens`,
11595
+ clientId: this.clientId,
11596
+ clientSecret: this.clientSecret,
11597
+ rateLimit: this.rateLimit,
11598
+ onResponse: this.onResponse,
11599
+ exchangeName: "Agent token exchange",
11600
+ refused: () => new AgentCredentialRevokedError2(`[AgentTokenManager] Keep refused this agent's credentials (401): the pairing was revoked or the secret rotated. Re-pair to continue.`)
11601
+ });
11602
+ this.token = data.token;
11603
+ this.expiresAt = Date.now() + data.expiresIn * 1e3;
11482
11604
  return this.token;
11483
11605
  }
11484
11606
  };
11485
11607
  exports2.AgentTokenManager = AgentTokenManager;
11486
- function hostOf(url) {
11487
- try {
11488
- return new URL(url).host;
11489
- } catch {
11490
- return url;
11491
- }
11492
- }
11493
- function describeTransportError(err) {
11494
- if (err instanceof Error) {
11495
- const cause = err.cause;
11496
- if (cause instanceof Error && cause.message && cause.message !== err.message) {
11497
- return `${err.message}: ${cause.message}`;
11498
- }
11499
- return err.message;
11500
- }
11501
- return String(err);
11502
- }
11503
11608
  }
11504
11609
  });
11505
11610
 
@@ -11508,7 +11613,7 @@ var require_dist2 = __commonJS({
11508
11613
  "../../packages/keep/dist/index.js"(exports2) {
11509
11614
  "use strict";
11510
11615
  Object.defineProperty(exports2, "__esModule", { value: true });
11511
- exports2.KeepRateLimitedError = exports2.KeepUnavailableError = exports2.AgentCredentialRevokedError = exports2.AgentTokenManager = exports2.ServiceTokenManager = exports2.KeepClient = void 0;
11616
+ exports2.waitForKeepToken = exports2.KeepRateLimitedError = exports2.KeepUnavailableError = exports2.AgentCredentialRevokedError = exports2.AgentTokenManager = exports2.ServiceTokenManager = exports2.KeepClient = void 0;
11512
11617
  var client_1 = require_client();
11513
11618
  Object.defineProperty(exports2, "KeepClient", { enumerable: true, get: function() {
11514
11619
  return client_1.KeepClient;
@@ -11524,13 +11629,18 @@ var require_dist2 = __commonJS({
11524
11629
  Object.defineProperty(exports2, "AgentCredentialRevokedError", { enumerable: true, get: function() {
11525
11630
  return agent_token_1.AgentCredentialRevokedError;
11526
11631
  } });
11632
+ var exchange_1 = require_exchange();
11527
11633
  Object.defineProperty(exports2, "KeepUnavailableError", { enumerable: true, get: function() {
11528
- return agent_token_1.KeepUnavailableError;
11634
+ return exchange_1.KeepUnavailableError;
11529
11635
  } });
11530
11636
  var rate_limited_1 = require_rate_limited();
11531
11637
  Object.defineProperty(exports2, "KeepRateLimitedError", { enumerable: true, get: function() {
11532
11638
  return rate_limited_1.KeepRateLimitedError;
11533
11639
  } });
11640
+ var wait_for_token_1 = require_wait_for_token();
11641
+ Object.defineProperty(exports2, "waitForKeepToken", { enumerable: true, get: function() {
11642
+ return wait_for_token_1.waitForKeepToken;
11643
+ } });
11534
11644
  }
11535
11645
  });
11536
11646
 
@@ -20331,6 +20441,14 @@ var require_client2 = __commonJS({
20331
20441
  async cancelJob(jobId) {
20332
20442
  return this.request("PUT", `/api/jobs/${jobId}`);
20333
20443
  }
20444
+ /**
20445
+ * Permanently delete every job filed under `clientRefPrefix` (which must end
20446
+ * with a UUID and a colon), inputs and outputs included. Operator only:
20447
+ * the caller needs `jobs:job-purges:write` at '*'. Idempotent.
20448
+ */
20449
+ async purgeJobs(clientRefPrefix) {
20450
+ return this.request("POST", "/api/job-purges", { clientRefPrefix });
20451
+ }
20334
20452
  // ── Outputs ──────────────────────────────────────────────────────
20335
20453
  async listOutputs(jobId) {
20336
20454
  const all = [];
@@ -26751,7 +26869,7 @@ var import_path = require("path");
26751
26869
  var import_promises = require("fs/promises");
26752
26870
  var import_yaml = __toESM(require_dist3());
26753
26871
  var import_shuttle = __toESM(require_dist5());
26754
- var buildVersion = true ? "0.30.10" : pkg.version;
26872
+ var buildVersion = true ? "0.30.12" : pkg.version;
26755
26873
  var sifterBrand = {
26756
26874
  product: {
26757
26875
  id: "sifter",