skedyul 1.2.51 → 1.3.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.
@@ -1852,6 +1852,68 @@ var CoreApiService = class {
1852
1852
  };
1853
1853
  var coreApiService = new CoreApiService();
1854
1854
 
1855
+ // src/ratelimit/config-loader.ts
1856
+ import * as fs from "fs";
1857
+ import * as path from "path";
1858
+ var cachedRuntimeQueues = null;
1859
+ function getRuntimeConfigPath() {
1860
+ if (process.env.LAMBDA_TASK_ROOT) {
1861
+ return path.join(process.env.LAMBDA_TASK_ROOT, ".skedyul", "config.json");
1862
+ }
1863
+ return path.join(process.cwd(), ".skedyul", "config.json");
1864
+ }
1865
+ function loadRuntimeConfigFile() {
1866
+ const configPath = getRuntimeConfigPath();
1867
+ if (!fs.existsSync(configPath)) {
1868
+ return null;
1869
+ }
1870
+ try {
1871
+ const raw = fs.readFileSync(configPath, "utf-8");
1872
+ return JSON.parse(raw);
1873
+ } catch {
1874
+ return null;
1875
+ }
1876
+ }
1877
+ var registeredServerConfig = null;
1878
+ function registerQueueConfig(config) {
1879
+ registeredServerConfig = config;
1880
+ cachedRuntimeQueues = config.queues ?? null;
1881
+ }
1882
+ function getQueueDefinitions() {
1883
+ if (cachedRuntimeQueues) {
1884
+ return cachedRuntimeQueues;
1885
+ }
1886
+ if (registeredServerConfig?.queues) {
1887
+ cachedRuntimeQueues = stripNonSerializableQueues(registeredServerConfig.queues);
1888
+ return cachedRuntimeQueues;
1889
+ }
1890
+ const fileConfig = loadRuntimeConfigFile();
1891
+ if (fileConfig?.queues) {
1892
+ cachedRuntimeQueues = fileConfig.queues;
1893
+ return cachedRuntimeQueues;
1894
+ }
1895
+ return {};
1896
+ }
1897
+ function stripNonSerializableQueues(queues) {
1898
+ const result = {};
1899
+ for (const [name, config] of Object.entries(queues)) {
1900
+ const { shouldRetry: _shouldRetry, ...serializable } = config;
1901
+ result[name] = serializable;
1902
+ }
1903
+ return result;
1904
+ }
1905
+ function getQueueConfig(queueName) {
1906
+ return getQueueDefinitions()[queueName];
1907
+ }
1908
+ function getQueueConfigWithRetry(queueName) {
1909
+ const fromServer = registeredServerConfig?.queues?.[queueName];
1910
+ if (fromServer) {
1911
+ return fromServer;
1912
+ }
1913
+ const serializable = getQueueConfig(queueName);
1914
+ return serializable;
1915
+ }
1916
+
1855
1917
  // src/server/utils/schema.ts
1856
1918
  import * as z8 from "zod";
1857
1919
  function normalizeBilling(billing) {
@@ -3173,6 +3235,35 @@ var report = {
3173
3235
  }
3174
3236
  };
3175
3237
 
3238
+ // src/ratelimit/context.ts
3239
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
3240
+ var executionContextStorage = new AsyncLocalStorage2();
3241
+ var activeOperationStorage = new AsyncLocalStorage2();
3242
+ function runWithRateLimitExecutionContext(ctx, fn) {
3243
+ return executionContextStorage.run(ctx, fn);
3244
+ }
3245
+ function getRateLimitExecutionContext() {
3246
+ return executionContextStorage.getStore();
3247
+ }
3248
+ function runWithActiveQueuedOperation(operation, fn) {
3249
+ return activeOperationStorage.run(operation, fn);
3250
+ }
3251
+ function getActiveQueuedOperation() {
3252
+ return activeOperationStorage.getStore();
3253
+ }
3254
+ function setActiveQueuedOperationLease(lease) {
3255
+ const op = activeOperationStorage.getStore();
3256
+ if (op) {
3257
+ op.lease = lease;
3258
+ }
3259
+ }
3260
+ function updateActiveQueuedOperationAttempt(attempt) {
3261
+ const op = activeOperationStorage.getStore();
3262
+ if (op) {
3263
+ op.attempt = attempt;
3264
+ }
3265
+ }
3266
+
3176
3267
  // src/errors.ts
3177
3268
  var InstallError = class extends Error {
3178
3269
  // Optional: which field caused the error
@@ -3227,8 +3318,8 @@ var AppAuthInvalidError = class extends Error {
3227
3318
  };
3228
3319
 
3229
3320
  // src/server/context-logger.ts
3230
- import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
3231
- var logContextStorage = new AsyncLocalStorage2();
3321
+ import { AsyncLocalStorage as AsyncLocalStorage3 } from "async_hooks";
3322
+ var logContextStorage = new AsyncLocalStorage3();
3232
3323
  function runWithLogContext(context, fn) {
3233
3324
  return logContextStorage.run(context, fn);
3234
3325
  }
@@ -3441,9 +3532,17 @@ function createCallToolHandler(registry, state, onMaxRequests) {
3441
3532
  baseUrl: toolEnv.SKEDYUL_API_URL ?? "",
3442
3533
  apiToken: toolEnv.SKEDYUL_API_TOKEN ?? ""
3443
3534
  };
3535
+ const rateLimitContext = {
3536
+ app,
3537
+ appInstallationId: trigger === "provision" ? void 0 : rawContext.appInstallationId,
3538
+ invocation,
3539
+ isProvisionContext: trigger === "provision"
3540
+ };
3444
3541
  const functionResult = await runWithConfig(requestConfig, async () => {
3445
- return await runWithLogContext({ invocation }, async () => {
3446
- return await fn(inputs, executionContext);
3542
+ return await runWithRateLimitExecutionContext(rateLimitContext, async () => {
3543
+ return await runWithLogContext({ invocation }, async () => {
3544
+ return await fn(inputs, executionContext);
3545
+ });
3447
3546
  });
3448
3547
  });
3449
3548
  const billing = normalizeBilling(functionResult.billing);
@@ -3580,7 +3679,7 @@ function printStartupLog(config, tools, port) {
3580
3679
 
3581
3680
  // src/server/route-handlers/adapters.ts
3582
3681
  function fromLambdaEvent(event2) {
3583
- const path5 = event2.path || event2.rawPath || "/";
3682
+ const path6 = event2.path || event2.rawPath || "/";
3584
3683
  const method = event2.httpMethod || event2.requestContext?.http?.method || "POST";
3585
3684
  const forwardedProto = event2.headers?.["x-forwarded-proto"] ?? event2.headers?.["X-Forwarded-Proto"];
3586
3685
  const protocol = forwardedProto ?? "https";
@@ -3588,9 +3687,9 @@ function fromLambdaEvent(event2) {
3588
3687
  const queryString = event2.queryStringParameters ? "?" + new URLSearchParams(
3589
3688
  event2.queryStringParameters
3590
3689
  ).toString() : "";
3591
- const url = `${protocol}://${host}${path5}${queryString}`;
3690
+ const url = `${protocol}://${host}${path6}${queryString}`;
3592
3691
  return {
3593
- path: path5,
3692
+ path: path6,
3594
3693
  method,
3595
3694
  headers: event2.headers,
3596
3695
  query: event2.queryStringParameters ?? {},
@@ -3672,8 +3771,8 @@ function parseBodyByContentType(req) {
3672
3771
  }
3673
3772
 
3674
3773
  // src/server/route-handlers/handlers.ts
3675
- import * as fs from "fs";
3676
- import * as path from "path";
3774
+ import * as fs2 from "fs";
3775
+ import * as path2 from "path";
3677
3776
 
3678
3777
  // src/server/core-api-handler.ts
3679
3778
  async function handleCoreMethod(method, params) {
@@ -3841,7 +3940,8 @@ function serializeConfig(config) {
3841
3940
  type: w.type ?? "WEBHOOK"
3842
3941
  })) : [],
3843
3942
  provision: isProvisionConfig(config.provision) ? config.provision : void 0,
3844
- agents: config.agents
3943
+ agents: config.agents,
3944
+ queues: config.queues
3845
3945
  };
3846
3946
  }
3847
3947
  function isProvisionConfig(value) {
@@ -4137,7 +4237,7 @@ async function handleOAuthCallback(parsedBody, hooks) {
4137
4237
  }
4138
4238
 
4139
4239
  // src/server/handlers/webhook-handler.ts
4140
- function parseWebhookRequest(parsedBody, method, url, path5, headers, query, rawBody, appIdHeader, appVersionIdHeader) {
4240
+ function parseWebhookRequest(parsedBody, method, url, path6, headers, query, rawBody, appIdHeader, appVersionIdHeader) {
4141
4241
  const isEnvelope = typeof parsedBody === "object" && parsedBody !== null && "env" in parsedBody && "request" in parsedBody && "context" in parsedBody;
4142
4242
  if (isEnvelope) {
4143
4243
  const envelope = parsedBody;
@@ -4191,7 +4291,7 @@ function parseWebhookRequest(parsedBody, method, url, path5, headers, query, raw
4191
4291
  const webhookRequest = {
4192
4292
  method,
4193
4293
  url,
4194
- path: path5,
4294
+ path: path6,
4195
4295
  headers,
4196
4296
  query,
4197
4297
  body: parsedBody,
@@ -4249,7 +4349,7 @@ function isMethodAllowed(webhookRegistry, handle, method) {
4249
4349
 
4250
4350
  // src/server/route-handlers/handlers.ts
4251
4351
  function getConfigFilePath() {
4252
- return process.env.LAMBDA_TASK_ROOT ? path.join(process.env.LAMBDA_TASK_ROOT, ".skedyul", "config.json") : ".skedyul/config.json";
4352
+ return process.env.LAMBDA_TASK_ROOT ? path2.join(process.env.LAMBDA_TASK_ROOT, ".skedyul", "config.json") : ".skedyul/config.json";
4253
4353
  }
4254
4354
  function findToolInRegistry(registry, toolName) {
4255
4355
  for (const [key, t] of Object.entries(registry)) {
@@ -4288,8 +4388,8 @@ function handleHealthRoute(ctx) {
4288
4388
  function handleConfigRoute(ctx) {
4289
4389
  const configFilePath = getConfigFilePath();
4290
4390
  try {
4291
- if (fs.existsSync(configFilePath)) {
4292
- const fileConfig = JSON.parse(fs.readFileSync(configFilePath, "utf-8"));
4391
+ if (fs2.existsSync(configFilePath)) {
4392
+ const fileConfig = JSON.parse(fs2.readFileSync(configFilePath, "utf-8"));
4293
4393
  return { status: 200, body: fileConfig };
4294
4394
  }
4295
4395
  } catch (err) {
@@ -4974,6 +5074,7 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
4974
5074
  installContextLogger();
4975
5075
  function createSkedyulServer(config) {
4976
5076
  mergeRuntimeEnv();
5077
+ registerQueueConfig(config);
4977
5078
  const registry = config.tools;
4978
5079
  const webhookRegistry = config.webhooks;
4979
5080
  if (config.coreApi?.service) {
@@ -5242,8 +5343,8 @@ function defineNavigation(navigation) {
5242
5343
  }
5243
5344
 
5244
5345
  // src/config/loader.ts
5245
- import * as fs2 from "fs";
5246
- import * as path2 from "path";
5346
+ import * as fs3 from "fs";
5347
+ import * as path3 from "path";
5247
5348
  import * as os from "os";
5248
5349
  var CONFIG_FILE_NAMES = [
5249
5350
  "skedyul.config.ts",
@@ -5252,13 +5353,13 @@ var CONFIG_FILE_NAMES = [
5252
5353
  "skedyul.config.cjs"
5253
5354
  ];
5254
5355
  async function transpileTypeScript(filePath) {
5255
- const content = fs2.readFileSync(filePath, "utf-8");
5256
- const configDir = path2.dirname(path2.resolve(filePath));
5356
+ const content = fs3.readFileSync(filePath, "utf-8");
5357
+ const configDir = path3.dirname(path3.resolve(filePath));
5257
5358
  let transpiled = content.replace(/import\s+type\s+\{[^}]+\}\s+from\s+['"][^'"]+['"]\s*;?\n?/g, "").replace(/import\s+\{\s*defineConfig\s*\}\s+from\s+['"]skedyul['"]\s*;?\n?/g, "").replace(/:\s*SkedyulConfig/g, "").replace(/export\s+default\s+/, "module.exports = ").replace(/defineConfig\s*\(\s*\{/, "{").replace(/\}\s*\)\s*;?\s*$/, "}");
5258
5359
  transpiled = transpiled.replace(
5259
5360
  /import\s+(\w+)\s+from\s+['"](\.[^'"]+)['"]\s*(?:with\s*\{[^}]*\})?/g,
5260
5361
  (match, varName, relativePath) => {
5261
- const absolutePath = path2.resolve(configDir, relativePath);
5362
+ const absolutePath = path3.resolve(configDir, relativePath);
5262
5363
  return `const ${varName} = require('${absolutePath.replace(/\\/g, "/")}')`;
5263
5364
  }
5264
5365
  );
@@ -5266,8 +5367,8 @@ async function transpileTypeScript(filePath) {
5266
5367
  return transpiled;
5267
5368
  }
5268
5369
  async function loadConfig(configPath) {
5269
- const absolutePath = path2.resolve(configPath);
5270
- if (!fs2.existsSync(absolutePath)) {
5370
+ const absolutePath = path3.resolve(configPath);
5371
+ if (!fs3.existsSync(absolutePath)) {
5271
5372
  throw new Error(`Config file not found: ${absolutePath}`);
5272
5373
  }
5273
5374
  const isTypeScript = absolutePath.endsWith(".ts");
@@ -5276,8 +5377,8 @@ async function loadConfig(configPath) {
5276
5377
  if (isTypeScript) {
5277
5378
  const transpiled = await transpileTypeScript(absolutePath);
5278
5379
  const tempDir = os.tmpdir();
5279
- const tempFile = path2.join(tempDir, `skedyul-config-${Date.now()}.cjs`);
5280
- fs2.writeFileSync(tempFile, transpiled);
5380
+ const tempFile = path3.join(tempDir, `skedyul-config-${Date.now()}.cjs`);
5381
+ fs3.writeFileSync(tempFile, transpiled);
5281
5382
  moduleToLoad = tempFile;
5282
5383
  try {
5283
5384
  const module2 = __require(moduleToLoad);
@@ -5291,7 +5392,7 @@ async function loadConfig(configPath) {
5291
5392
  return config2;
5292
5393
  } finally {
5293
5394
  try {
5294
- fs2.unlinkSync(tempFile);
5395
+ fs3.unlinkSync(tempFile);
5295
5396
  } catch {
5296
5397
  }
5297
5398
  }
@@ -5326,13 +5427,13 @@ function validateConfig(config) {
5326
5427
  }
5327
5428
 
5328
5429
  // src/config/schema-loader.ts
5329
- import * as fs3 from "fs";
5330
- import * as path3 from "path";
5430
+ import * as fs4 from "fs";
5431
+ import * as path4 from "path";
5331
5432
  import * as os2 from "os";
5332
5433
 
5333
5434
  // src/config/resolver.ts
5334
- import * as fs4 from "fs";
5335
- import * as path4 from "path";
5435
+ import * as fs5 from "fs";
5436
+ import * as path5 from "path";
5336
5437
 
5337
5438
  // src/config/utils.ts
5338
5439
  function getAllEnvKeys(config) {
@@ -5349,6 +5450,553 @@ function getRequiredInstallEnvKeys(config) {
5349
5450
  return Object.entries(provision.env).filter(([, def]) => def.required).map(([key]) => key);
5350
5451
  }
5351
5452
 
5453
+ // src/ratelimit/errors.ts
5454
+ var QueueContextError = class extends Error {
5455
+ constructor(message) {
5456
+ super(message);
5457
+ this.code = "QUEUE_CONTEXT_ERROR";
5458
+ this.name = "QueueContextError";
5459
+ }
5460
+ };
5461
+ var QueueNotFoundError = class extends Error {
5462
+ constructor(queueName) {
5463
+ super(`Queue "${queueName}" is not defined in skedyul.config queues`);
5464
+ this.code = "QUEUE_NOT_FOUND";
5465
+ this.name = "QueueNotFoundError";
5466
+ }
5467
+ };
5468
+ var QueuedFetchExhaustedError = class extends Error {
5469
+ constructor(attempts, maxRetries, causeError) {
5470
+ super(
5471
+ `queuedFetch exhausted retries after ${attempts} attempts (maxRetries=${maxRetries})`
5472
+ );
5473
+ this.code = "QUEUED_FETCH_EXHAUSTED";
5474
+ this.name = "QueuedFetchExhaustedError";
5475
+ this.attempts = attempts;
5476
+ this.maxRetries = maxRetries;
5477
+ this.causeError = causeError;
5478
+ }
5479
+ };
5480
+ var RequeueOutsideContextError = class extends Error {
5481
+ constructor() {
5482
+ super("requeue() can only be called inside an active queuedFetch operation");
5483
+ this.code = "REQUEUE_OUTSIDE_CONTEXT";
5484
+ this.name = "RequeueOutsideContextError";
5485
+ }
5486
+ };
5487
+ var RateLimitBackendError = class extends Error {
5488
+ constructor(message, statusCode) {
5489
+ super(message);
5490
+ this.code = "RATE_LIMIT_BACKEND_ERROR";
5491
+ this.name = "RateLimitBackendError";
5492
+ this.statusCode = statusCode;
5493
+ }
5494
+ };
5495
+
5496
+ // src/ratelimit/resolve-queue-key.ts
5497
+ function resolveEndpointHandle(config, invocation) {
5498
+ if (config.endpoint) {
5499
+ return config.endpoint;
5500
+ }
5501
+ if (invocation?.toolHandle) {
5502
+ return invocation.toolHandle;
5503
+ }
5504
+ if (invocation?.serverHookHandle) {
5505
+ return invocation.serverHookHandle;
5506
+ }
5507
+ throw new QueueContextError(
5508
+ `Cannot resolve endpoint handle for queue scope "${config.scope}". Set endpoint in queue config or ensure invocation.toolHandle/serverHookHandle is available.`
5509
+ );
5510
+ }
5511
+ function appendSubKey(base, subKey) {
5512
+ return subKey ? `${base}:${subKey}` : base;
5513
+ }
5514
+ function resolveQueueKey(queueName, config, ctx, subKey) {
5515
+ const { app, appInstallationId, invocation, isProvisionContext: isProvisionContext2 } = ctx;
5516
+ switch (config.scope) {
5517
+ case "provision": {
5518
+ const base = `rl:pv:${app.versionId}:${queueName}`;
5519
+ return appendSubKey(base, subKey);
5520
+ }
5521
+ case "install": {
5522
+ if (!appInstallationId) {
5523
+ throw new QueueContextError(
5524
+ `Queue "${queueName}" with scope "install" requires appInstallationId in context`
5525
+ );
5526
+ }
5527
+ const base = `rl:in:${appInstallationId}:${queueName}`;
5528
+ return appendSubKey(base, subKey);
5529
+ }
5530
+ case "provision_endpoint": {
5531
+ if (!isProvisionContext2 && appInstallationId) {
5532
+ throw new QueueContextError(
5533
+ `Queue "${queueName}" with scope "provision_endpoint" requires provision context (no install)`
5534
+ );
5535
+ }
5536
+ const endpointHandle = resolveEndpointHandle(config, invocation);
5537
+ const base = `rl:pep:${app.versionId}:${endpointHandle}:${queueName}`;
5538
+ return appendSubKey(base, subKey);
5539
+ }
5540
+ case "install_endpoint": {
5541
+ if (!appInstallationId) {
5542
+ throw new QueueContextError(
5543
+ `Queue "${queueName}" with scope "install_endpoint" requires appInstallationId in context`
5544
+ );
5545
+ }
5546
+ const endpointHandle = resolveEndpointHandle(config, invocation);
5547
+ const base = `rl:iep:${appInstallationId}:${endpointHandle}:${queueName}`;
5548
+ return appendSubKey(base, subKey);
5549
+ }
5550
+ case "global": {
5551
+ const base = `rl:gl:${app.versionId}:${queueName}`;
5552
+ return appendSubKey(base, subKey);
5553
+ }
5554
+ default: {
5555
+ const _exhaustive = config.scope;
5556
+ throw new QueueContextError(`Unknown queue scope: ${String(_exhaustive)}`);
5557
+ }
5558
+ }
5559
+ }
5560
+ function toQueueLimits(config) {
5561
+ return {
5562
+ maxConcurrent: config.maxConcurrent,
5563
+ minTime: config.minTime,
5564
+ reservoir: config.reservoir,
5565
+ reservoirRefreshAmount: config.reservoirRefreshAmount,
5566
+ reservoirRefreshInterval: config.reservoirRefreshInterval
5567
+ };
5568
+ }
5569
+
5570
+ // src/ratelimit/backends/platform.ts
5571
+ function getApiBaseUrl() {
5572
+ const config = getConfig();
5573
+ if (config.baseUrl) {
5574
+ return config.baseUrl.replace(/\/+$/, "");
5575
+ }
5576
+ return (process.env.SKEDYUL_API_URL ?? process.env.SKEDYUL_NODE_URL ?? "").replace(/\/+$/, "");
5577
+ }
5578
+ function getApiToken() {
5579
+ const config = getConfig();
5580
+ return config.apiToken || process.env.SKEDYUL_API_TOKEN || "";
5581
+ }
5582
+ async function callRateLimitApi(path6, body) {
5583
+ const baseUrl = getApiBaseUrl();
5584
+ const token2 = getApiToken();
5585
+ if (!baseUrl || !token2) {
5586
+ throw new RateLimitBackendError(
5587
+ "SKEDYUL_API_URL and SKEDYUL_API_TOKEN are required for platform rate limiting"
5588
+ );
5589
+ }
5590
+ const response = await fetch(`${baseUrl}/api/internal/ratelimit/${path6}`, {
5591
+ method: "POST",
5592
+ headers: {
5593
+ Authorization: `Bearer ${token2}`,
5594
+ "Content-Type": "application/json"
5595
+ },
5596
+ body: JSON.stringify(body)
5597
+ });
5598
+ const payload = await response.json().catch(() => null);
5599
+ if (!response.ok || payload?.success === false) {
5600
+ const message = payload?.errors?.[0]?.message ?? `Rate limit API ${path6} failed with status ${response.status}`;
5601
+ throw new RateLimitBackendError(message, response.status);
5602
+ }
5603
+ if (!payload?.data) {
5604
+ throw new RateLimitBackendError(`Rate limit API ${path6} returned empty data`);
5605
+ }
5606
+ return payload.data;
5607
+ }
5608
+ var PlatformRateLimitBackend = class {
5609
+ async acquire(queueKey, limits, timeoutMs) {
5610
+ const data = await callRateLimitApi("acquire", {
5611
+ queueKey,
5612
+ limits,
5613
+ timeoutMs
5614
+ });
5615
+ return {
5616
+ leaseId: data.leaseId,
5617
+ acquiredAt: data.acquiredAt,
5618
+ queueKey: data.queueKey
5619
+ };
5620
+ }
5621
+ async release(lease) {
5622
+ await callRateLimitApi("release", {
5623
+ leaseId: lease.leaseId
5624
+ });
5625
+ }
5626
+ };
5627
+ var platformRateLimitBackend = new PlatformRateLimitBackend();
5628
+
5629
+ // src/ratelimit/backends/memory.ts
5630
+ var DEFAULT_MAX_CONCURRENT = 10;
5631
+ function getMaxConcurrent(limits) {
5632
+ return limits.maxConcurrent ?? DEFAULT_MAX_CONCURRENT;
5633
+ }
5634
+ function getMinTime(limits) {
5635
+ return limits.minTime ?? 0;
5636
+ }
5637
+ function generateLeaseId() {
5638
+ return `mem_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
5639
+ }
5640
+ function refillTokens(state, limits) {
5641
+ if (limits.reservoir === void 0) {
5642
+ return;
5643
+ }
5644
+ const capacity = limits.reservoir;
5645
+ const refreshAmount = limits.reservoirRefreshAmount ?? limits.reservoir;
5646
+ const refreshInterval = limits.reservoirRefreshInterval ?? 6e4;
5647
+ const now = Date.now();
5648
+ const elapsed = now - state.lastRefillAt;
5649
+ if (elapsed >= refreshInterval) {
5650
+ const intervals = Math.floor(elapsed / refreshInterval);
5651
+ state.tokens = Math.min(capacity, state.tokens + intervals * refreshAmount);
5652
+ state.lastRefillAt = now;
5653
+ }
5654
+ }
5655
+ function canAcquire(state, limits) {
5656
+ if (state.running >= getMaxConcurrent(limits)) {
5657
+ return false;
5658
+ }
5659
+ refillTokens(state, limits);
5660
+ if (limits.reservoir !== void 0 && state.tokens <= 0) {
5661
+ return false;
5662
+ }
5663
+ const minTime = getMinTime(limits);
5664
+ if (minTime > 0 && state.lastStartAt > 0) {
5665
+ if (Date.now() - state.lastStartAt < minTime) {
5666
+ return false;
5667
+ }
5668
+ }
5669
+ return true;
5670
+ }
5671
+ var MemoryRateLimitBackend = class {
5672
+ constructor() {
5673
+ this.queues = /* @__PURE__ */ new Map();
5674
+ this.leases = /* @__PURE__ */ new Map();
5675
+ }
5676
+ getState(queueKey) {
5677
+ let state = this.queues.get(queueKey);
5678
+ if (!state) {
5679
+ state = {
5680
+ running: 0,
5681
+ waiters: [],
5682
+ lastStartAt: 0,
5683
+ tokens: Number.POSITIVE_INFINITY,
5684
+ lastRefillAt: Date.now()
5685
+ };
5686
+ if (queueKey.includes(":")) {
5687
+ }
5688
+ this.queues.set(queueKey, state);
5689
+ }
5690
+ return state;
5691
+ }
5692
+ grant(state, queueKey, limits) {
5693
+ if (limits.reservoir !== void 0) {
5694
+ state.tokens -= 1;
5695
+ }
5696
+ state.running += 1;
5697
+ state.lastStartAt = Date.now();
5698
+ const leaseId = generateLeaseId();
5699
+ this.leases.set(leaseId, { queueKey, limits });
5700
+ return { leaseId, acquiredAt: Date.now(), queueKey };
5701
+ }
5702
+ drainWaiters(queueKey, state) {
5703
+ let progressed = true;
5704
+ while (progressed && state.waiters.length > 0) {
5705
+ progressed = false;
5706
+ for (let i = 0; i < state.waiters.length; i += 1) {
5707
+ const waiter = state.waiters[i];
5708
+ if (!waiter || !canAcquire(state, waiter.limits)) {
5709
+ continue;
5710
+ }
5711
+ state.waiters.splice(i, 1);
5712
+ if (waiter.timer) {
5713
+ clearTimeout(waiter.timer);
5714
+ }
5715
+ waiter.resolve(this.grant(state, queueKey, waiter.limits));
5716
+ progressed = true;
5717
+ break;
5718
+ }
5719
+ }
5720
+ }
5721
+ async acquire(queueKey, limits, timeoutMs = 12e4) {
5722
+ const state = this.getState(queueKey);
5723
+ if (limits.reservoir !== void 0 && state.tokens === Number.POSITIVE_INFINITY) {
5724
+ state.tokens = limits.reservoir;
5725
+ }
5726
+ if (canAcquire(state, limits)) {
5727
+ return this.grant(state, queueKey, limits);
5728
+ }
5729
+ return new Promise((resolve4, reject) => {
5730
+ const waiter = {
5731
+ resolve: resolve4,
5732
+ reject,
5733
+ timer: null,
5734
+ limits
5735
+ };
5736
+ if (timeoutMs > 0) {
5737
+ waiter.timer = setTimeout(() => {
5738
+ const idx = state.waiters.indexOf(waiter);
5739
+ if (idx >= 0) {
5740
+ state.waiters.splice(idx, 1);
5741
+ }
5742
+ reject(
5743
+ new Error(`Rate limit acquire timed out after ${timeoutMs}ms for ${queueKey}`)
5744
+ );
5745
+ }, timeoutMs);
5746
+ }
5747
+ state.waiters.push(waiter);
5748
+ const retry = () => {
5749
+ if (!state.waiters.includes(waiter)) {
5750
+ return;
5751
+ }
5752
+ if (canAcquire(state, limits)) {
5753
+ const idx = state.waiters.indexOf(waiter);
5754
+ if (idx >= 0) {
5755
+ state.waiters.splice(idx, 1);
5756
+ }
5757
+ if (waiter.timer) {
5758
+ clearTimeout(waiter.timer);
5759
+ }
5760
+ resolve4(this.grant(state, queueKey, limits));
5761
+ return;
5762
+ }
5763
+ setTimeout(retry, Math.max(getMinTime(limits), 10));
5764
+ };
5765
+ setTimeout(retry, Math.max(getMinTime(limits), 10));
5766
+ });
5767
+ }
5768
+ async release(lease) {
5769
+ const tracked = this.leases.get(lease.leaseId);
5770
+ this.leases.delete(lease.leaseId);
5771
+ const queueKey = tracked?.queueKey ?? lease.queueKey;
5772
+ const state = this.queues.get(queueKey);
5773
+ if (!state) {
5774
+ return;
5775
+ }
5776
+ state.running = Math.max(0, state.running - 1);
5777
+ this.drainWaiters(queueKey, state);
5778
+ }
5779
+ };
5780
+ var memoryRateLimitBackend = new MemoryRateLimitBackend();
5781
+
5782
+ // src/ratelimit/backends/index.ts
5783
+ var cachedBackend = null;
5784
+ function shouldForceMemoryBackend() {
5785
+ const flag = process.env.SKEDYUL_RATE_LIMIT_MEMORY;
5786
+ return flag === "1" || flag === "true";
5787
+ }
5788
+ function getRateLimitBackend() {
5789
+ if (cachedBackend) {
5790
+ return cachedBackend;
5791
+ }
5792
+ if (shouldForceMemoryBackend()) {
5793
+ cachedBackend = memoryRateLimitBackend;
5794
+ return cachedBackend;
5795
+ }
5796
+ cachedBackend = createResilientBackend();
5797
+ return cachedBackend;
5798
+ }
5799
+ function createResilientBackend() {
5800
+ return {
5801
+ async acquire(queueKey, limits, timeoutMs) {
5802
+ try {
5803
+ return await platformRateLimitBackend.acquire(queueKey, limits, timeoutMs);
5804
+ } catch (error) {
5805
+ if (shouldFallbackToMemory(error)) {
5806
+ return memoryRateLimitBackend.acquire(queueKey, limits, timeoutMs);
5807
+ }
5808
+ throw error;
5809
+ }
5810
+ },
5811
+ async release(lease) {
5812
+ if (lease.leaseId.startsWith("mem_")) {
5813
+ return memoryRateLimitBackend.release(lease);
5814
+ }
5815
+ try {
5816
+ await platformRateLimitBackend.release(lease);
5817
+ } catch (error) {
5818
+ if (shouldFallbackToMemory(error)) {
5819
+ return memoryRateLimitBackend.release(lease);
5820
+ }
5821
+ throw error;
5822
+ }
5823
+ }
5824
+ };
5825
+ }
5826
+ function shouldFallbackToMemory(error) {
5827
+ if (shouldForceMemoryBackend()) {
5828
+ return true;
5829
+ }
5830
+ if (error instanceof RateLimitBackendError) {
5831
+ if (error.statusCode === 404 || error.statusCode === 503) {
5832
+ return true;
5833
+ }
5834
+ }
5835
+ if (error instanceof TypeError) {
5836
+ return true;
5837
+ }
5838
+ const nodeEnv = process.env.NODE_ENV;
5839
+ return nodeEnv === "development" || nodeEnv === "test";
5840
+ }
5841
+
5842
+ // src/ratelimit/should-retry.ts
5843
+ var DEFAULT_RETRY_STATUS_CODES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
5844
+ function getErrorStatusCode(error) {
5845
+ if (typeof error !== "object" || error === null) {
5846
+ return void 0;
5847
+ }
5848
+ const record2 = error;
5849
+ if (typeof record2.statusCode === "number") {
5850
+ return record2.statusCode;
5851
+ }
5852
+ if (typeof record2.status === "number") {
5853
+ return record2.status;
5854
+ }
5855
+ return void 0;
5856
+ }
5857
+ function getErrorCode(error) {
5858
+ if (typeof error !== "object" || error === null) {
5859
+ return void 0;
5860
+ }
5861
+ const record2 = error;
5862
+ return typeof record2.code === "string" ? record2.code : void 0;
5863
+ }
5864
+ function defaultShouldRetry(error, _attempt) {
5865
+ const code = getErrorCode(error);
5866
+ if (code === "RATE_LIMITED" || code === "RATE_LIMIT_BACKEND_ERROR") {
5867
+ return true;
5868
+ }
5869
+ const status = getErrorStatusCode(error);
5870
+ if (status !== void 0 && DEFAULT_RETRY_STATUS_CODES.has(status)) {
5871
+ return true;
5872
+ }
5873
+ const message = error instanceof Error ? error.message : typeof error === "string" ? error : "";
5874
+ const lower = message.toLowerCase();
5875
+ if (lower.includes("rate limit") || lower.includes("too many requests") || lower.includes("econnreset") || lower.includes("etimedout")) {
5876
+ return true;
5877
+ }
5878
+ return false;
5879
+ }
5880
+ function sleep(ms) {
5881
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
5882
+ }
5883
+
5884
+ // src/ratelimit/queued-fetch.ts
5885
+ function normalizeQueueInput(input) {
5886
+ if (typeof input === "string") {
5887
+ return { name: input };
5888
+ }
5889
+ return { name: input.queue, subKey: input.key };
5890
+ }
5891
+ function resolveQueue(queueInput, ctxOverride) {
5892
+ const ctx = ctxOverride ?? getRateLimitExecutionContext();
5893
+ if (!ctx?.app?.versionId) {
5894
+ throw new QueueContextError(
5895
+ "Cannot resolve queue without app context. Ensure queuedFetch runs inside a tool/hook handler."
5896
+ );
5897
+ }
5898
+ const { name, subKey } = normalizeQueueInput(queueInput);
5899
+ const config = getQueueConfigWithRetry(name);
5900
+ if (!config) {
5901
+ throw new QueueNotFoundError(name);
5902
+ }
5903
+ const queueKey = resolveQueueKey(name, config, ctx, subKey);
5904
+ return {
5905
+ name,
5906
+ queueKey,
5907
+ config,
5908
+ limits: toQueueLimits(config)
5909
+ };
5910
+ }
5911
+ function createQueueHandle(queueInput) {
5912
+ return {
5913
+ run(fn) {
5914
+ return queuedFetch(queueInput, fn);
5915
+ }
5916
+ };
5917
+ }
5918
+ async function queuedFetch(queueInput, fnOrPromise) {
5919
+ const fn = typeof fnOrPromise === "function" ? fnOrPromise : () => fnOrPromise;
5920
+ const resolved = resolveQueue(queueInput);
5921
+ const maxRetries = resolved.config.maxRetries ?? 0;
5922
+ const operation = {
5923
+ queueInput,
5924
+ fn,
5925
+ attempt: 0,
5926
+ resolved,
5927
+ lease: null
5928
+ };
5929
+ return runWithActiveQueuedOperation(
5930
+ operation,
5931
+ () => executeWithRetries(operation, maxRetries)
5932
+ );
5933
+ }
5934
+ async function executeWithRetries(operation, maxRetries) {
5935
+ const backend = getRateLimitBackend();
5936
+ const timeoutMs = operation.resolved.config.timeout ?? 12e4;
5937
+ const retryDelayMs = operation.resolved.config.retryDelayMs ?? 1e3;
5938
+ const shouldRetryFn = getQueueConfigWithRetry(operation.resolved.name)?.shouldRetry ?? defaultShouldRetry;
5939
+ const lease = await backend.acquire(
5940
+ operation.resolved.queueKey,
5941
+ operation.resolved.limits,
5942
+ timeoutMs
5943
+ );
5944
+ operation.lease = lease;
5945
+ setActiveQueuedOperationLease(lease);
5946
+ try {
5947
+ return await operation.fn();
5948
+ } catch (error) {
5949
+ await backend.release(lease);
5950
+ operation.lease = null;
5951
+ setActiveQueuedOperationLease(null);
5952
+ if (shouldRetryFn(error, operation.attempt) && operation.attempt < maxRetries) {
5953
+ await sleep(retryDelayMs);
5954
+ operation.attempt += 1;
5955
+ updateActiveQueuedOperationAttempt(operation.attempt);
5956
+ return executeWithRetries(operation, maxRetries);
5957
+ }
5958
+ if (shouldRetryFn(error, operation.attempt) && operation.attempt >= maxRetries) {
5959
+ throw new QueuedFetchExhaustedError(
5960
+ operation.attempt + 1,
5961
+ maxRetries,
5962
+ error
5963
+ );
5964
+ }
5965
+ throw error;
5966
+ } finally {
5967
+ if (operation.lease) {
5968
+ await backend.release(operation.lease);
5969
+ operation.lease = null;
5970
+ setActiveQueuedOperationLease(null);
5971
+ }
5972
+ }
5973
+ }
5974
+ async function requeue() {
5975
+ const operation = getActiveQueuedOperation();
5976
+ if (!operation) {
5977
+ throw new RequeueOutsideContextError();
5978
+ }
5979
+ const maxRetries = operation.resolved.config.maxRetries ?? 0;
5980
+ const nextAttempt = operation.attempt + 1;
5981
+ if (nextAttempt > maxRetries) {
5982
+ throw new QueuedFetchExhaustedError(nextAttempt, maxRetries, void 0);
5983
+ }
5984
+ if (operation.lease) {
5985
+ const backend = getRateLimitBackend();
5986
+ await backend.release(operation.lease);
5987
+ operation.lease = null;
5988
+ setActiveQueuedOperationLease(null);
5989
+ }
5990
+ operation.attempt = nextAttempt;
5991
+ updateActiveQueuedOperationAttempt(nextAttempt);
5992
+ const retryDelayMs = operation.resolved.config.retryDelayMs ?? 1e3;
5993
+ await sleep(retryDelayMs);
5994
+ return executeWithRetries(operation, maxRetries);
5995
+ }
5996
+ async function queuedFetchResponse(queueInput, url, init) {
5997
+ return queuedFetch(queueInput, () => fetch(url, init));
5998
+ }
5999
+
5352
6000
  // src/triggers/types.ts
5353
6001
  import { z as z11 } from "zod/v4";
5354
6002
  var CRMDataSchema = z11.object({
@@ -5453,16 +6101,16 @@ function evaluateTemplate(template, context) {
5453
6101
  const templateRegex = /\{\{\s*([^}]+)\s*\}\}/g;
5454
6102
  const singleMatch = template.match(/^\{\{\s*([^}]+)\s*\}\}$/);
5455
6103
  if (singleMatch) {
5456
- const path5 = singleMatch[1].trim();
5457
- return resolvePath(context, path5);
6104
+ const path6 = singleMatch[1].trim();
6105
+ return resolvePath(context, path6);
5458
6106
  }
5459
- return template.replace(templateRegex, (_, path5) => {
5460
- const value = resolvePath(context, path5.trim());
6107
+ return template.replace(templateRegex, (_, path6) => {
6108
+ const value = resolvePath(context, path6.trim());
5461
6109
  return value === void 0 || value === null ? "" : String(value);
5462
6110
  });
5463
6111
  }
5464
- function resolvePath(obj, path5) {
5465
- const parts = path5.split(".");
6112
+ function resolvePath(obj, path6) {
6113
+ const parts = path6.split(".");
5466
6114
  let current = obj;
5467
6115
  for (const part of parts) {
5468
6116
  if (current === null || current === void 0) {
@@ -5483,14 +6131,14 @@ function evaluateCondition(condition, context) {
5483
6131
  const expression = match[1].trim();
5484
6132
  const eqMatch = expression.match(/^(.+?)\s*==\s*['"](.+)['"]$/);
5485
6133
  if (eqMatch) {
5486
- const [, path5, expectedValue] = eqMatch;
5487
- const actualValue = resolvePath(context, path5.trim());
6134
+ const [, path6, expectedValue] = eqMatch;
6135
+ const actualValue = resolvePath(context, path6.trim());
5488
6136
  return actualValue === expectedValue;
5489
6137
  }
5490
6138
  const neqMatch = expression.match(/^(.+?)\s*!=\s*['"](.+)['"]$/);
5491
6139
  if (neqMatch) {
5492
- const [, path5, expectedValue] = neqMatch;
5493
- const actualValue = resolvePath(context, path5.trim());
6140
+ const [, path6, expectedValue] = neqMatch;
6141
+ const actualValue = resolvePath(context, path6.trim());
5494
6142
  return actualValue !== expectedValue;
5495
6143
  }
5496
6144
  const value = resolvePath(context, expression);
@@ -6854,10 +7502,15 @@ export {
6854
7502
  ParticipantEventPayloadSchema,
6855
7503
  ParticipantKindSchema,
6856
7504
  ProvisionConfigSchema,
7505
+ QueueContextError,
7506
+ QueueNotFoundError,
7507
+ QueuedFetchExhaustedError,
7508
+ RateLimitBackendError,
6857
7509
  RelationshipCardinalitySchema,
6858
7510
  RelationshipDefinitionSchema,
6859
7511
  RelationshipExtensionSchema,
6860
7512
  RelationshipLinkSchema,
7513
+ RequeueOutsideContextError,
6861
7514
  ResolvedSkillSchema,
6862
7515
  ResolvedTriggerSchema,
6863
7516
  ResourceDependencySchema,
@@ -6935,6 +7588,7 @@ export {
6935
7588
  createListResponse,
6936
7589
  createNotFoundError,
6937
7590
  createPermissionError,
7591
+ createQueueHandle,
6938
7592
  createRateLimitError,
6939
7593
  createServerHookContext,
6940
7594
  createSuccessResponse,
@@ -6945,6 +7599,7 @@ export {
6945
7599
  createWorkflowStepContext,
6946
7600
  cron,
6947
7601
  src_default as default,
7602
+ defaultShouldRetry,
6948
7603
  defineAgent,
6949
7604
  defineChannel,
6950
7605
  defineConfig,
@@ -6967,6 +7622,7 @@ export {
6967
7622
  getConfig,
6968
7623
  getContextByHandle,
6969
7624
  getContextByModel,
7625
+ getRateLimitExecutionContext,
6970
7626
  getRequiredInstallEnvKeys,
6971
7627
  getRetryDelay,
6972
7628
  instance,
@@ -6986,10 +7642,16 @@ export {
6986
7642
  loadConfig,
6987
7643
  matchesTrigger,
6988
7644
  parseCRMSchema,
7645
+ queuedFetch,
7646
+ queuedFetchResponse,
7647
+ registerQueueConfig,
6989
7648
  report,
7649
+ requeue,
6990
7650
  resolveInputMappings,
7651
+ resolveQueue,
6991
7652
  resource,
6992
7653
  runWithConfig,
7654
+ runWithRateLimitExecutionContext,
6993
7655
  safeParseCRMSchema,
6994
7656
  safeParseConfig,
6995
7657
  server,