skedyul 1.2.51 → 1.3.1

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);
@@ -3487,6 +3586,7 @@ function createCallToolHandler(registry, state, onMaxRequests) {
3487
3586
  };
3488
3587
  }
3489
3588
  const errorMessage = error instanceof Error ? error.message : String(error ?? "");
3589
+ const errorCode = error && typeof error === "object" && "code" in error && typeof error.code === "string" ? error.code : "TOOL_EXECUTION_ERROR";
3490
3590
  return {
3491
3591
  output: null,
3492
3592
  billing: { credits: 0 },
@@ -3496,7 +3596,7 @@ function createCallToolHandler(registry, state, onMaxRequests) {
3496
3596
  toolName
3497
3597
  },
3498
3598
  error: {
3499
- code: "TOOL_EXECUTION_ERROR",
3599
+ code: errorCode,
3500
3600
  message: errorMessage
3501
3601
  }
3502
3602
  };
@@ -3580,7 +3680,7 @@ function printStartupLog(config, tools, port) {
3580
3680
 
3581
3681
  // src/server/route-handlers/adapters.ts
3582
3682
  function fromLambdaEvent(event2) {
3583
- const path5 = event2.path || event2.rawPath || "/";
3683
+ const path6 = event2.path || event2.rawPath || "/";
3584
3684
  const method = event2.httpMethod || event2.requestContext?.http?.method || "POST";
3585
3685
  const forwardedProto = event2.headers?.["x-forwarded-proto"] ?? event2.headers?.["X-Forwarded-Proto"];
3586
3686
  const protocol = forwardedProto ?? "https";
@@ -3588,9 +3688,9 @@ function fromLambdaEvent(event2) {
3588
3688
  const queryString = event2.queryStringParameters ? "?" + new URLSearchParams(
3589
3689
  event2.queryStringParameters
3590
3690
  ).toString() : "";
3591
- const url = `${protocol}://${host}${path5}${queryString}`;
3691
+ const url = `${protocol}://${host}${path6}${queryString}`;
3592
3692
  return {
3593
- path: path5,
3693
+ path: path6,
3594
3694
  method,
3595
3695
  headers: event2.headers,
3596
3696
  query: event2.queryStringParameters ?? {},
@@ -3672,8 +3772,8 @@ function parseBodyByContentType(req) {
3672
3772
  }
3673
3773
 
3674
3774
  // src/server/route-handlers/handlers.ts
3675
- import * as fs from "fs";
3676
- import * as path from "path";
3775
+ import * as fs2 from "fs";
3776
+ import * as path2 from "path";
3677
3777
 
3678
3778
  // src/server/core-api-handler.ts
3679
3779
  async function handleCoreMethod(method, params) {
@@ -3841,7 +3941,8 @@ function serializeConfig(config) {
3841
3941
  type: w.type ?? "WEBHOOK"
3842
3942
  })) : [],
3843
3943
  provision: isProvisionConfig(config.provision) ? config.provision : void 0,
3844
- agents: config.agents
3944
+ agents: config.agents,
3945
+ queues: config.queues
3845
3946
  };
3846
3947
  }
3847
3948
  function isProvisionConfig(value) {
@@ -4137,7 +4238,7 @@ async function handleOAuthCallback(parsedBody, hooks) {
4137
4238
  }
4138
4239
 
4139
4240
  // src/server/handlers/webhook-handler.ts
4140
- function parseWebhookRequest(parsedBody, method, url, path5, headers, query, rawBody, appIdHeader, appVersionIdHeader) {
4241
+ function parseWebhookRequest(parsedBody, method, url, path6, headers, query, rawBody, appIdHeader, appVersionIdHeader) {
4141
4242
  const isEnvelope = typeof parsedBody === "object" && parsedBody !== null && "env" in parsedBody && "request" in parsedBody && "context" in parsedBody;
4142
4243
  if (isEnvelope) {
4143
4244
  const envelope = parsedBody;
@@ -4191,7 +4292,7 @@ function parseWebhookRequest(parsedBody, method, url, path5, headers, query, raw
4191
4292
  const webhookRequest = {
4192
4293
  method,
4193
4294
  url,
4194
- path: path5,
4295
+ path: path6,
4195
4296
  headers,
4196
4297
  query,
4197
4298
  body: parsedBody,
@@ -4249,7 +4350,7 @@ function isMethodAllowed(webhookRegistry, handle, method) {
4249
4350
 
4250
4351
  // src/server/route-handlers/handlers.ts
4251
4352
  function getConfigFilePath() {
4252
- return process.env.LAMBDA_TASK_ROOT ? path.join(process.env.LAMBDA_TASK_ROOT, ".skedyul", "config.json") : ".skedyul/config.json";
4353
+ return process.env.LAMBDA_TASK_ROOT ? path2.join(process.env.LAMBDA_TASK_ROOT, ".skedyul", "config.json") : ".skedyul/config.json";
4253
4354
  }
4254
4355
  function findToolInRegistry(registry, toolName) {
4255
4356
  for (const [key, t] of Object.entries(registry)) {
@@ -4288,8 +4389,8 @@ function handleHealthRoute(ctx) {
4288
4389
  function handleConfigRoute(ctx) {
4289
4390
  const configFilePath = getConfigFilePath();
4290
4391
  try {
4291
- if (fs.existsSync(configFilePath)) {
4292
- const fileConfig = JSON.parse(fs.readFileSync(configFilePath, "utf-8"));
4392
+ if (fs2.existsSync(configFilePath)) {
4393
+ const fileConfig = JSON.parse(fs2.readFileSync(configFilePath, "utf-8"));
4293
4394
  return { status: 200, body: fileConfig };
4294
4395
  }
4295
4396
  } catch (err) {
@@ -4974,6 +5075,7 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
4974
5075
  installContextLogger();
4975
5076
  function createSkedyulServer(config) {
4976
5077
  mergeRuntimeEnv();
5078
+ registerQueueConfig(config);
4977
5079
  const registry = config.tools;
4978
5080
  const webhookRegistry = config.webhooks;
4979
5081
  if (config.coreApi?.service) {
@@ -5242,8 +5344,8 @@ function defineNavigation(navigation) {
5242
5344
  }
5243
5345
 
5244
5346
  // src/config/loader.ts
5245
- import * as fs2 from "fs";
5246
- import * as path2 from "path";
5347
+ import * as fs3 from "fs";
5348
+ import * as path3 from "path";
5247
5349
  import * as os from "os";
5248
5350
  var CONFIG_FILE_NAMES = [
5249
5351
  "skedyul.config.ts",
@@ -5252,13 +5354,13 @@ var CONFIG_FILE_NAMES = [
5252
5354
  "skedyul.config.cjs"
5253
5355
  ];
5254
5356
  async function transpileTypeScript(filePath) {
5255
- const content = fs2.readFileSync(filePath, "utf-8");
5256
- const configDir = path2.dirname(path2.resolve(filePath));
5357
+ const content = fs3.readFileSync(filePath, "utf-8");
5358
+ const configDir = path3.dirname(path3.resolve(filePath));
5257
5359
  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
5360
  transpiled = transpiled.replace(
5259
5361
  /import\s+(\w+)\s+from\s+['"](\.[^'"]+)['"]\s*(?:with\s*\{[^}]*\})?/g,
5260
5362
  (match, varName, relativePath) => {
5261
- const absolutePath = path2.resolve(configDir, relativePath);
5363
+ const absolutePath = path3.resolve(configDir, relativePath);
5262
5364
  return `const ${varName} = require('${absolutePath.replace(/\\/g, "/")}')`;
5263
5365
  }
5264
5366
  );
@@ -5266,8 +5368,8 @@ async function transpileTypeScript(filePath) {
5266
5368
  return transpiled;
5267
5369
  }
5268
5370
  async function loadConfig(configPath) {
5269
- const absolutePath = path2.resolve(configPath);
5270
- if (!fs2.existsSync(absolutePath)) {
5371
+ const absolutePath = path3.resolve(configPath);
5372
+ if (!fs3.existsSync(absolutePath)) {
5271
5373
  throw new Error(`Config file not found: ${absolutePath}`);
5272
5374
  }
5273
5375
  const isTypeScript = absolutePath.endsWith(".ts");
@@ -5276,8 +5378,8 @@ async function loadConfig(configPath) {
5276
5378
  if (isTypeScript) {
5277
5379
  const transpiled = await transpileTypeScript(absolutePath);
5278
5380
  const tempDir = os.tmpdir();
5279
- const tempFile = path2.join(tempDir, `skedyul-config-${Date.now()}.cjs`);
5280
- fs2.writeFileSync(tempFile, transpiled);
5381
+ const tempFile = path3.join(tempDir, `skedyul-config-${Date.now()}.cjs`);
5382
+ fs3.writeFileSync(tempFile, transpiled);
5281
5383
  moduleToLoad = tempFile;
5282
5384
  try {
5283
5385
  const module2 = __require(moduleToLoad);
@@ -5291,7 +5393,7 @@ async function loadConfig(configPath) {
5291
5393
  return config2;
5292
5394
  } finally {
5293
5395
  try {
5294
- fs2.unlinkSync(tempFile);
5396
+ fs3.unlinkSync(tempFile);
5295
5397
  } catch {
5296
5398
  }
5297
5399
  }
@@ -5326,13 +5428,13 @@ function validateConfig(config) {
5326
5428
  }
5327
5429
 
5328
5430
  // src/config/schema-loader.ts
5329
- import * as fs3 from "fs";
5330
- import * as path3 from "path";
5431
+ import * as fs4 from "fs";
5432
+ import * as path4 from "path";
5331
5433
  import * as os2 from "os";
5332
5434
 
5333
5435
  // src/config/resolver.ts
5334
- import * as fs4 from "fs";
5335
- import * as path4 from "path";
5436
+ import * as fs5 from "fs";
5437
+ import * as path5 from "path";
5336
5438
 
5337
5439
  // src/config/utils.ts
5338
5440
  function getAllEnvKeys(config) {
@@ -5349,6 +5451,553 @@ function getRequiredInstallEnvKeys(config) {
5349
5451
  return Object.entries(provision.env).filter(([, def]) => def.required).map(([key]) => key);
5350
5452
  }
5351
5453
 
5454
+ // src/ratelimit/errors.ts
5455
+ var QueueContextError = class extends Error {
5456
+ constructor(message) {
5457
+ super(message);
5458
+ this.code = "QUEUE_CONTEXT_ERROR";
5459
+ this.name = "QueueContextError";
5460
+ }
5461
+ };
5462
+ var QueueNotFoundError = class extends Error {
5463
+ constructor(queueName) {
5464
+ super(`Queue "${queueName}" is not defined in skedyul.config queues`);
5465
+ this.code = "QUEUE_NOT_FOUND";
5466
+ this.name = "QueueNotFoundError";
5467
+ }
5468
+ };
5469
+ var QueuedFetchExhaustedError = class extends Error {
5470
+ constructor(attempts, maxRetries, causeError) {
5471
+ super(
5472
+ `queuedFetch exhausted retries after ${attempts} attempts (maxRetries=${maxRetries})`
5473
+ );
5474
+ this.code = "QUEUED_FETCH_EXHAUSTED";
5475
+ this.name = "QueuedFetchExhaustedError";
5476
+ this.attempts = attempts;
5477
+ this.maxRetries = maxRetries;
5478
+ this.causeError = causeError;
5479
+ }
5480
+ };
5481
+ var RequeueOutsideContextError = class extends Error {
5482
+ constructor() {
5483
+ super("requeue() can only be called inside an active queuedFetch operation");
5484
+ this.code = "REQUEUE_OUTSIDE_CONTEXT";
5485
+ this.name = "RequeueOutsideContextError";
5486
+ }
5487
+ };
5488
+ var RateLimitBackendError = class extends Error {
5489
+ constructor(message, statusCode) {
5490
+ super(message);
5491
+ this.code = "RATE_LIMIT_BACKEND_ERROR";
5492
+ this.name = "RateLimitBackendError";
5493
+ this.statusCode = statusCode;
5494
+ }
5495
+ };
5496
+
5497
+ // src/ratelimit/resolve-queue-key.ts
5498
+ function resolveEndpointHandle(config, invocation) {
5499
+ if (config.endpoint) {
5500
+ return config.endpoint;
5501
+ }
5502
+ if (invocation?.toolHandle) {
5503
+ return invocation.toolHandle;
5504
+ }
5505
+ if (invocation?.serverHookHandle) {
5506
+ return invocation.serverHookHandle;
5507
+ }
5508
+ throw new QueueContextError(
5509
+ `Cannot resolve endpoint handle for queue scope "${config.scope}". Set endpoint in queue config or ensure invocation.toolHandle/serverHookHandle is available.`
5510
+ );
5511
+ }
5512
+ function appendSubKey(base, subKey) {
5513
+ return subKey ? `${base}:${subKey}` : base;
5514
+ }
5515
+ function resolveQueueKey(queueName, config, ctx, subKey) {
5516
+ const { app, appInstallationId, invocation, isProvisionContext: isProvisionContext2 } = ctx;
5517
+ switch (config.scope) {
5518
+ case "provision": {
5519
+ const base = `rl:pv:${app.versionId}:${queueName}`;
5520
+ return appendSubKey(base, subKey);
5521
+ }
5522
+ case "install": {
5523
+ if (!appInstallationId) {
5524
+ throw new QueueContextError(
5525
+ `Queue "${queueName}" with scope "install" requires appInstallationId in context`
5526
+ );
5527
+ }
5528
+ const base = `rl:in:${appInstallationId}:${queueName}`;
5529
+ return appendSubKey(base, subKey);
5530
+ }
5531
+ case "provision_endpoint": {
5532
+ if (!isProvisionContext2 && appInstallationId) {
5533
+ throw new QueueContextError(
5534
+ `Queue "${queueName}" with scope "provision_endpoint" requires provision context (no install)`
5535
+ );
5536
+ }
5537
+ const endpointHandle = resolveEndpointHandle(config, invocation);
5538
+ const base = `rl:pep:${app.versionId}:${endpointHandle}:${queueName}`;
5539
+ return appendSubKey(base, subKey);
5540
+ }
5541
+ case "install_endpoint": {
5542
+ if (!appInstallationId) {
5543
+ throw new QueueContextError(
5544
+ `Queue "${queueName}" with scope "install_endpoint" requires appInstallationId in context`
5545
+ );
5546
+ }
5547
+ const endpointHandle = resolveEndpointHandle(config, invocation);
5548
+ const base = `rl:iep:${appInstallationId}:${endpointHandle}:${queueName}`;
5549
+ return appendSubKey(base, subKey);
5550
+ }
5551
+ case "global": {
5552
+ const base = `rl:gl:${app.versionId}:${queueName}`;
5553
+ return appendSubKey(base, subKey);
5554
+ }
5555
+ default: {
5556
+ const _exhaustive = config.scope;
5557
+ throw new QueueContextError(`Unknown queue scope: ${String(_exhaustive)}`);
5558
+ }
5559
+ }
5560
+ }
5561
+ function toQueueLimits(config) {
5562
+ return {
5563
+ maxConcurrent: config.maxConcurrent,
5564
+ minTime: config.minTime,
5565
+ reservoir: config.reservoir,
5566
+ reservoirRefreshAmount: config.reservoirRefreshAmount,
5567
+ reservoirRefreshInterval: config.reservoirRefreshInterval
5568
+ };
5569
+ }
5570
+
5571
+ // src/ratelimit/backends/platform.ts
5572
+ function getApiBaseUrl() {
5573
+ const config = getConfig();
5574
+ if (config.baseUrl) {
5575
+ return config.baseUrl.replace(/\/+$/, "");
5576
+ }
5577
+ return (process.env.SKEDYUL_API_URL ?? process.env.SKEDYUL_NODE_URL ?? "").replace(/\/+$/, "");
5578
+ }
5579
+ function getApiToken() {
5580
+ const config = getConfig();
5581
+ return config.apiToken || process.env.SKEDYUL_API_TOKEN || "";
5582
+ }
5583
+ async function callRateLimitApi(path6, body) {
5584
+ const baseUrl = getApiBaseUrl();
5585
+ const token2 = getApiToken();
5586
+ if (!baseUrl || !token2) {
5587
+ throw new RateLimitBackendError(
5588
+ "SKEDYUL_API_URL and SKEDYUL_API_TOKEN are required for platform queue coordination"
5589
+ );
5590
+ }
5591
+ const response = await fetch(`${baseUrl}/api/internal/ratelimit/${path6}`, {
5592
+ method: "POST",
5593
+ headers: {
5594
+ Authorization: `Bearer ${token2}`,
5595
+ "Content-Type": "application/json"
5596
+ },
5597
+ body: JSON.stringify(body)
5598
+ });
5599
+ const payload = await response.json().catch(() => null);
5600
+ if (!response.ok || payload?.success === false) {
5601
+ const message = payload?.errors?.[0]?.message ?? `Queue coordination API ${path6} failed with status ${response.status}`;
5602
+ throw new RateLimitBackendError(message, response.status);
5603
+ }
5604
+ if (!payload?.data) {
5605
+ throw new RateLimitBackendError(`Queue coordination API ${path6} returned empty data`);
5606
+ }
5607
+ return payload.data;
5608
+ }
5609
+ var PlatformRateLimitBackend = class {
5610
+ async acquire(queueKey, limits, timeoutMs) {
5611
+ const data = await callRateLimitApi("acquire", {
5612
+ queueKey,
5613
+ limits,
5614
+ timeoutMs
5615
+ });
5616
+ return {
5617
+ leaseId: data.leaseId,
5618
+ acquiredAt: data.acquiredAt,
5619
+ queueKey: data.queueKey
5620
+ };
5621
+ }
5622
+ async release(lease) {
5623
+ await callRateLimitApi("release", {
5624
+ leaseId: lease.leaseId
5625
+ });
5626
+ }
5627
+ };
5628
+ var platformRateLimitBackend = new PlatformRateLimitBackend();
5629
+
5630
+ // src/ratelimit/backends/memory.ts
5631
+ var DEFAULT_MAX_CONCURRENT = 10;
5632
+ function getMaxConcurrent(limits) {
5633
+ return limits.maxConcurrent ?? DEFAULT_MAX_CONCURRENT;
5634
+ }
5635
+ function getMinTime(limits) {
5636
+ return limits.minTime ?? 0;
5637
+ }
5638
+ function generateLeaseId() {
5639
+ return `mem_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
5640
+ }
5641
+ function refillTokens(state, limits) {
5642
+ if (limits.reservoir === void 0) {
5643
+ return;
5644
+ }
5645
+ const capacity = limits.reservoir;
5646
+ const refreshAmount = limits.reservoirRefreshAmount ?? limits.reservoir;
5647
+ const refreshInterval = limits.reservoirRefreshInterval ?? 6e4;
5648
+ const now = Date.now();
5649
+ const elapsed = now - state.lastRefillAt;
5650
+ if (elapsed >= refreshInterval) {
5651
+ const intervals = Math.floor(elapsed / refreshInterval);
5652
+ state.tokens = Math.min(capacity, state.tokens + intervals * refreshAmount);
5653
+ state.lastRefillAt = now;
5654
+ }
5655
+ }
5656
+ function canAcquire(state, limits) {
5657
+ if (state.running >= getMaxConcurrent(limits)) {
5658
+ return false;
5659
+ }
5660
+ refillTokens(state, limits);
5661
+ if (limits.reservoir !== void 0 && state.tokens <= 0) {
5662
+ return false;
5663
+ }
5664
+ const minTime = getMinTime(limits);
5665
+ if (minTime > 0 && state.lastStartAt > 0) {
5666
+ if (Date.now() - state.lastStartAt < minTime) {
5667
+ return false;
5668
+ }
5669
+ }
5670
+ return true;
5671
+ }
5672
+ var MemoryRateLimitBackend = class {
5673
+ constructor() {
5674
+ this.queues = /* @__PURE__ */ new Map();
5675
+ this.leases = /* @__PURE__ */ new Map();
5676
+ }
5677
+ getState(queueKey) {
5678
+ let state = this.queues.get(queueKey);
5679
+ if (!state) {
5680
+ state = {
5681
+ running: 0,
5682
+ waiters: [],
5683
+ lastStartAt: 0,
5684
+ tokens: Number.POSITIVE_INFINITY,
5685
+ lastRefillAt: Date.now()
5686
+ };
5687
+ if (queueKey.includes(":")) {
5688
+ }
5689
+ this.queues.set(queueKey, state);
5690
+ }
5691
+ return state;
5692
+ }
5693
+ grant(state, queueKey, limits) {
5694
+ if (limits.reservoir !== void 0) {
5695
+ state.tokens -= 1;
5696
+ }
5697
+ state.running += 1;
5698
+ state.lastStartAt = Date.now();
5699
+ const leaseId = generateLeaseId();
5700
+ this.leases.set(leaseId, { queueKey, limits });
5701
+ return { leaseId, acquiredAt: Date.now(), queueKey };
5702
+ }
5703
+ drainWaiters(queueKey, state) {
5704
+ let progressed = true;
5705
+ while (progressed && state.waiters.length > 0) {
5706
+ progressed = false;
5707
+ for (let i = 0; i < state.waiters.length; i += 1) {
5708
+ const waiter = state.waiters[i];
5709
+ if (!waiter || !canAcquire(state, waiter.limits)) {
5710
+ continue;
5711
+ }
5712
+ state.waiters.splice(i, 1);
5713
+ if (waiter.timer) {
5714
+ clearTimeout(waiter.timer);
5715
+ }
5716
+ waiter.resolve(this.grant(state, queueKey, waiter.limits));
5717
+ progressed = true;
5718
+ break;
5719
+ }
5720
+ }
5721
+ }
5722
+ async acquire(queueKey, limits, timeoutMs = 12e4) {
5723
+ const state = this.getState(queueKey);
5724
+ if (limits.reservoir !== void 0 && state.tokens === Number.POSITIVE_INFINITY) {
5725
+ state.tokens = limits.reservoir;
5726
+ }
5727
+ if (canAcquire(state, limits)) {
5728
+ return this.grant(state, queueKey, limits);
5729
+ }
5730
+ return new Promise((resolve4, reject) => {
5731
+ const waiter = {
5732
+ resolve: resolve4,
5733
+ reject,
5734
+ timer: null,
5735
+ limits
5736
+ };
5737
+ if (timeoutMs > 0) {
5738
+ waiter.timer = setTimeout(() => {
5739
+ const idx = state.waiters.indexOf(waiter);
5740
+ if (idx >= 0) {
5741
+ state.waiters.splice(idx, 1);
5742
+ }
5743
+ reject(
5744
+ new Error(`Queue slot acquire timed out after ${timeoutMs}ms for ${queueKey}`)
5745
+ );
5746
+ }, timeoutMs);
5747
+ }
5748
+ state.waiters.push(waiter);
5749
+ const retry = () => {
5750
+ if (!state.waiters.includes(waiter)) {
5751
+ return;
5752
+ }
5753
+ if (canAcquire(state, limits)) {
5754
+ const idx = state.waiters.indexOf(waiter);
5755
+ if (idx >= 0) {
5756
+ state.waiters.splice(idx, 1);
5757
+ }
5758
+ if (waiter.timer) {
5759
+ clearTimeout(waiter.timer);
5760
+ }
5761
+ resolve4(this.grant(state, queueKey, limits));
5762
+ return;
5763
+ }
5764
+ setTimeout(retry, Math.max(getMinTime(limits), 10));
5765
+ };
5766
+ setTimeout(retry, Math.max(getMinTime(limits), 10));
5767
+ });
5768
+ }
5769
+ async release(lease) {
5770
+ const tracked = this.leases.get(lease.leaseId);
5771
+ this.leases.delete(lease.leaseId);
5772
+ const queueKey = tracked?.queueKey ?? lease.queueKey;
5773
+ const state = this.queues.get(queueKey);
5774
+ if (!state) {
5775
+ return;
5776
+ }
5777
+ state.running = Math.max(0, state.running - 1);
5778
+ this.drainWaiters(queueKey, state);
5779
+ }
5780
+ };
5781
+ var memoryRateLimitBackend = new MemoryRateLimitBackend();
5782
+
5783
+ // src/ratelimit/backends/index.ts
5784
+ var cachedBackend = null;
5785
+ function shouldForceMemoryBackend() {
5786
+ const flag = process.env.SKEDYUL_RATE_LIMIT_MEMORY;
5787
+ return flag === "1" || flag === "true";
5788
+ }
5789
+ function getRateLimitBackend() {
5790
+ if (cachedBackend) {
5791
+ return cachedBackend;
5792
+ }
5793
+ if (shouldForceMemoryBackend()) {
5794
+ cachedBackend = memoryRateLimitBackend;
5795
+ return cachedBackend;
5796
+ }
5797
+ cachedBackend = createResilientBackend();
5798
+ return cachedBackend;
5799
+ }
5800
+ function createResilientBackend() {
5801
+ return {
5802
+ async acquire(queueKey, limits, timeoutMs) {
5803
+ try {
5804
+ return await platformRateLimitBackend.acquire(queueKey, limits, timeoutMs);
5805
+ } catch (error) {
5806
+ if (shouldFallbackToMemory(error)) {
5807
+ return memoryRateLimitBackend.acquire(queueKey, limits, timeoutMs);
5808
+ }
5809
+ throw error;
5810
+ }
5811
+ },
5812
+ async release(lease) {
5813
+ if (lease.leaseId.startsWith("mem_")) {
5814
+ return memoryRateLimitBackend.release(lease);
5815
+ }
5816
+ try {
5817
+ await platformRateLimitBackend.release(lease);
5818
+ } catch (error) {
5819
+ if (shouldFallbackToMemory(error)) {
5820
+ return memoryRateLimitBackend.release(lease);
5821
+ }
5822
+ throw error;
5823
+ }
5824
+ }
5825
+ };
5826
+ }
5827
+ function shouldFallbackToMemory(error) {
5828
+ if (shouldForceMemoryBackend()) {
5829
+ return true;
5830
+ }
5831
+ if (error instanceof RateLimitBackendError) {
5832
+ if (error.statusCode === 404 || error.statusCode === 503) {
5833
+ return true;
5834
+ }
5835
+ }
5836
+ if (error instanceof TypeError) {
5837
+ return true;
5838
+ }
5839
+ const nodeEnv = process.env.NODE_ENV;
5840
+ return nodeEnv === "development" || nodeEnv === "test";
5841
+ }
5842
+
5843
+ // src/ratelimit/should-retry.ts
5844
+ var DEFAULT_RETRY_STATUS_CODES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
5845
+ function getErrorStatusCode(error) {
5846
+ if (typeof error !== "object" || error === null) {
5847
+ return void 0;
5848
+ }
5849
+ const record2 = error;
5850
+ if (typeof record2.statusCode === "number") {
5851
+ return record2.statusCode;
5852
+ }
5853
+ if (typeof record2.status === "number") {
5854
+ return record2.status;
5855
+ }
5856
+ return void 0;
5857
+ }
5858
+ function getErrorCode(error) {
5859
+ if (typeof error !== "object" || error === null) {
5860
+ return void 0;
5861
+ }
5862
+ const record2 = error;
5863
+ return typeof record2.code === "string" ? record2.code : void 0;
5864
+ }
5865
+ function defaultShouldRetry(error, _attempt) {
5866
+ const code = getErrorCode(error);
5867
+ if (code === "RATE_LIMITED" || code === "RATE_LIMIT_BACKEND_ERROR") {
5868
+ return true;
5869
+ }
5870
+ const status = getErrorStatusCode(error);
5871
+ if (status !== void 0 && DEFAULT_RETRY_STATUS_CODES.has(status)) {
5872
+ return true;
5873
+ }
5874
+ const message = error instanceof Error ? error.message : typeof error === "string" ? error : "";
5875
+ const lower = message.toLowerCase();
5876
+ if (lower.includes("rate limit") || lower.includes("too many requests") || lower.includes("econnreset") || lower.includes("etimedout")) {
5877
+ return true;
5878
+ }
5879
+ return false;
5880
+ }
5881
+ function sleep(ms) {
5882
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
5883
+ }
5884
+
5885
+ // src/ratelimit/queued-fetch.ts
5886
+ function normalizeQueueInput(input) {
5887
+ if (typeof input === "string") {
5888
+ return { name: input };
5889
+ }
5890
+ return { name: input.queue, subKey: input.key };
5891
+ }
5892
+ function resolveQueue(queueInput, ctxOverride) {
5893
+ const ctx = ctxOverride ?? getRateLimitExecutionContext();
5894
+ if (!ctx?.app?.versionId) {
5895
+ throw new QueueContextError(
5896
+ "Cannot resolve queue without app context. Ensure queuedFetch runs inside a tool/hook handler."
5897
+ );
5898
+ }
5899
+ const { name, subKey } = normalizeQueueInput(queueInput);
5900
+ const config = getQueueConfigWithRetry(name);
5901
+ if (!config) {
5902
+ throw new QueueNotFoundError(name);
5903
+ }
5904
+ const queueKey = resolveQueueKey(name, config, ctx, subKey);
5905
+ return {
5906
+ name,
5907
+ queueKey,
5908
+ config,
5909
+ limits: toQueueLimits(config)
5910
+ };
5911
+ }
5912
+ function createQueueHandle(queueInput) {
5913
+ return {
5914
+ run(fn) {
5915
+ return queuedFetch(queueInput, fn);
5916
+ }
5917
+ };
5918
+ }
5919
+ async function queuedFetch(queueInput, fnOrPromise) {
5920
+ const fn = typeof fnOrPromise === "function" ? fnOrPromise : () => fnOrPromise;
5921
+ const resolved = resolveQueue(queueInput);
5922
+ const maxRetries = resolved.config.maxRetries ?? 0;
5923
+ const operation = {
5924
+ queueInput,
5925
+ fn,
5926
+ attempt: 0,
5927
+ resolved,
5928
+ lease: null
5929
+ };
5930
+ return runWithActiveQueuedOperation(
5931
+ operation,
5932
+ () => executeWithRetries(operation, maxRetries)
5933
+ );
5934
+ }
5935
+ async function executeWithRetries(operation, maxRetries) {
5936
+ const backend = getRateLimitBackend();
5937
+ const timeoutMs = operation.resolved.config.timeout ?? 12e4;
5938
+ const retryDelayMs = operation.resolved.config.retryDelayMs ?? 1e3;
5939
+ const shouldRetryFn = getQueueConfigWithRetry(operation.resolved.name)?.shouldRetry ?? defaultShouldRetry;
5940
+ const lease = await backend.acquire(
5941
+ operation.resolved.queueKey,
5942
+ operation.resolved.limits,
5943
+ timeoutMs
5944
+ );
5945
+ operation.lease = lease;
5946
+ setActiveQueuedOperationLease(lease);
5947
+ try {
5948
+ return await operation.fn();
5949
+ } catch (error) {
5950
+ await backend.release(lease);
5951
+ operation.lease = null;
5952
+ setActiveQueuedOperationLease(null);
5953
+ if (shouldRetryFn(error, operation.attempt) && operation.attempt < maxRetries) {
5954
+ await sleep(retryDelayMs);
5955
+ operation.attempt += 1;
5956
+ updateActiveQueuedOperationAttempt(operation.attempt);
5957
+ return executeWithRetries(operation, maxRetries);
5958
+ }
5959
+ if (shouldRetryFn(error, operation.attempt) && operation.attempt >= maxRetries) {
5960
+ throw new QueuedFetchExhaustedError(
5961
+ operation.attempt + 1,
5962
+ maxRetries,
5963
+ error
5964
+ );
5965
+ }
5966
+ throw error;
5967
+ } finally {
5968
+ if (operation.lease) {
5969
+ await backend.release(operation.lease);
5970
+ operation.lease = null;
5971
+ setActiveQueuedOperationLease(null);
5972
+ }
5973
+ }
5974
+ }
5975
+ async function requeue() {
5976
+ const operation = getActiveQueuedOperation();
5977
+ if (!operation) {
5978
+ throw new RequeueOutsideContextError();
5979
+ }
5980
+ const maxRetries = operation.resolved.config.maxRetries ?? 0;
5981
+ const nextAttempt = operation.attempt + 1;
5982
+ if (nextAttempt > maxRetries) {
5983
+ throw new QueuedFetchExhaustedError(nextAttempt, maxRetries, void 0);
5984
+ }
5985
+ if (operation.lease) {
5986
+ const backend = getRateLimitBackend();
5987
+ await backend.release(operation.lease);
5988
+ operation.lease = null;
5989
+ setActiveQueuedOperationLease(null);
5990
+ }
5991
+ operation.attempt = nextAttempt;
5992
+ updateActiveQueuedOperationAttempt(nextAttempt);
5993
+ const retryDelayMs = operation.resolved.config.retryDelayMs ?? 1e3;
5994
+ await sleep(retryDelayMs);
5995
+ return executeWithRetries(operation, maxRetries);
5996
+ }
5997
+ async function queuedFetchResponse(queueInput, url, init) {
5998
+ return queuedFetch(queueInput, () => fetch(url, init));
5999
+ }
6000
+
5352
6001
  // src/triggers/types.ts
5353
6002
  import { z as z11 } from "zod/v4";
5354
6003
  var CRMDataSchema = z11.object({
@@ -5453,16 +6102,16 @@ function evaluateTemplate(template, context) {
5453
6102
  const templateRegex = /\{\{\s*([^}]+)\s*\}\}/g;
5454
6103
  const singleMatch = template.match(/^\{\{\s*([^}]+)\s*\}\}$/);
5455
6104
  if (singleMatch) {
5456
- const path5 = singleMatch[1].trim();
5457
- return resolvePath(context, path5);
6105
+ const path6 = singleMatch[1].trim();
6106
+ return resolvePath(context, path6);
5458
6107
  }
5459
- return template.replace(templateRegex, (_, path5) => {
5460
- const value = resolvePath(context, path5.trim());
6108
+ return template.replace(templateRegex, (_, path6) => {
6109
+ const value = resolvePath(context, path6.trim());
5461
6110
  return value === void 0 || value === null ? "" : String(value);
5462
6111
  });
5463
6112
  }
5464
- function resolvePath(obj, path5) {
5465
- const parts = path5.split(".");
6113
+ function resolvePath(obj, path6) {
6114
+ const parts = path6.split(".");
5466
6115
  let current = obj;
5467
6116
  for (const part of parts) {
5468
6117
  if (current === null || current === void 0) {
@@ -5483,14 +6132,14 @@ function evaluateCondition(condition, context) {
5483
6132
  const expression = match[1].trim();
5484
6133
  const eqMatch = expression.match(/^(.+?)\s*==\s*['"](.+)['"]$/);
5485
6134
  if (eqMatch) {
5486
- const [, path5, expectedValue] = eqMatch;
5487
- const actualValue = resolvePath(context, path5.trim());
6135
+ const [, path6, expectedValue] = eqMatch;
6136
+ const actualValue = resolvePath(context, path6.trim());
5488
6137
  return actualValue === expectedValue;
5489
6138
  }
5490
6139
  const neqMatch = expression.match(/^(.+?)\s*!=\s*['"](.+)['"]$/);
5491
6140
  if (neqMatch) {
5492
- const [, path5, expectedValue] = neqMatch;
5493
- const actualValue = resolvePath(context, path5.trim());
6141
+ const [, path6, expectedValue] = neqMatch;
6142
+ const actualValue = resolvePath(context, path6.trim());
5494
6143
  return actualValue !== expectedValue;
5495
6144
  }
5496
6145
  const value = resolvePath(context, expression);
@@ -6499,6 +7148,9 @@ function isTimeInWindowSlot(date, window) {
6499
7148
  const tzInfo = getTimezoneInfo(date, tz);
6500
7149
  return allowedDays.includes(tzInfo.day) && tzInfo.totalMinutes >= windowStartMinutes && tzInfo.totalMinutes < windowEndMinutes;
6501
7150
  }
7151
+ function getLocalDateKey(date, timezone) {
7152
+ return date.toLocaleDateString("en-CA", { timeZone: timezone });
7153
+ }
6502
7154
  function calculateWaitTime(step, now) {
6503
7155
  const nowTime = now.getTime();
6504
7156
  switch (step.mode) {
@@ -6567,7 +7219,7 @@ function calculateWaitTime(step, now) {
6567
7219
  }
6568
7220
  }
6569
7221
  let earliestScheduledTime = null;
6570
- let earliestWaitTime = Infinity;
7222
+ let earliestWaitFromNow = Infinity;
6571
7223
  for (const window of step.windows) {
6572
7224
  if (!window || window.startTime === void 0 || window.endTime === void 0) {
6573
7225
  continue;
@@ -6585,12 +7237,10 @@ function calculateWaitTime(step, now) {
6585
7237
  const currentHour = targetTzInfo.hour;
6586
7238
  const currentMinute = targetTzInfo.minute;
6587
7239
  const currentTotalMinutes = targetTzInfo.totalMinutes;
6588
- let windowWaitTime;
6589
- let windowScheduledTime;
7240
+ let msUntilWindowStart;
6590
7241
  if (allowedDays.includes(currentDay) && currentTotalMinutes < windowStartMinutes) {
6591
7242
  const minutesUntilWindow = windowStartMinutes - currentTotalMinutes;
6592
- windowWaitTime = minutesUntilWindow * 60 * 1e3;
6593
- windowScheduledTime = new Date(targetDate.getTime() + windowWaitTime);
7243
+ msUntilWindowStart = minutesUntilWindow * 60 * 1e3;
6594
7244
  } else {
6595
7245
  let daysToAdd = 7;
6596
7246
  for (let i = 1; i <= 7; i++) {
@@ -6606,17 +7256,20 @@ function calculateWaitTime(step, now) {
6606
7256
  const daysMs = daysToAdd * millisecondsPerDay;
6607
7257
  const hoursAdjustment = (windowStartHour - currentHour) * millisecondsPerHour;
6608
7258
  const minutesAdjustment = (windowStartMinute - currentMinute) * millisecondsPerMinute;
6609
- windowWaitTime = daysMs + hoursAdjustment + minutesAdjustment;
6610
- windowScheduledTime = new Date(targetDate.getTime() + windowWaitTime);
7259
+ msUntilWindowStart = daysMs + hoursAdjustment + minutesAdjustment;
6611
7260
  }
6612
- if (windowWaitTime < earliestWaitTime) {
6613
- earliestWaitTime = windowWaitTime;
7261
+ const windowStartTimeMs = targetDate.getTime() + msUntilWindowStart;
7262
+ const sameLocalDayAsNow = getLocalDateKey(now, tz) === getLocalDateKey(targetDate, tz);
7263
+ const windowScheduledTime = sameLocalDayAsNow ? new Date(windowStartTimeMs + relativeDelay) : new Date(windowStartTimeMs);
7264
+ const waitFromNow = windowScheduledTime.getTime() - nowTime;
7265
+ if (waitFromNow < earliestWaitFromNow) {
7266
+ earliestWaitFromNow = waitFromNow;
6614
7267
  earliestScheduledTime = windowScheduledTime;
6615
7268
  }
6616
7269
  }
6617
7270
  if (earliestScheduledTime) {
6618
7271
  return {
6619
- waitTime: relativeDelay + earliestWaitTime,
7272
+ waitTime: Math.max(0, earliestScheduledTime.getTime() - nowTime),
6620
7273
  scheduledAt: earliestScheduledTime
6621
7274
  };
6622
7275
  }
@@ -6854,10 +7507,15 @@ export {
6854
7507
  ParticipantEventPayloadSchema,
6855
7508
  ParticipantKindSchema,
6856
7509
  ProvisionConfigSchema,
7510
+ QueueContextError,
7511
+ QueueNotFoundError,
7512
+ QueuedFetchExhaustedError,
7513
+ RateLimitBackendError,
6857
7514
  RelationshipCardinalitySchema,
6858
7515
  RelationshipDefinitionSchema,
6859
7516
  RelationshipExtensionSchema,
6860
7517
  RelationshipLinkSchema,
7518
+ RequeueOutsideContextError,
6861
7519
  ResolvedSkillSchema,
6862
7520
  ResolvedTriggerSchema,
6863
7521
  ResourceDependencySchema,
@@ -6935,6 +7593,7 @@ export {
6935
7593
  createListResponse,
6936
7594
  createNotFoundError,
6937
7595
  createPermissionError,
7596
+ createQueueHandle,
6938
7597
  createRateLimitError,
6939
7598
  createServerHookContext,
6940
7599
  createSuccessResponse,
@@ -6945,6 +7604,7 @@ export {
6945
7604
  createWorkflowStepContext,
6946
7605
  cron,
6947
7606
  src_default as default,
7607
+ defaultShouldRetry,
6948
7608
  defineAgent,
6949
7609
  defineChannel,
6950
7610
  defineConfig,
@@ -6967,6 +7627,7 @@ export {
6967
7627
  getConfig,
6968
7628
  getContextByHandle,
6969
7629
  getContextByModel,
7630
+ getRateLimitExecutionContext,
6970
7631
  getRequiredInstallEnvKeys,
6971
7632
  getRetryDelay,
6972
7633
  instance,
@@ -6986,10 +7647,16 @@ export {
6986
7647
  loadConfig,
6987
7648
  matchesTrigger,
6988
7649
  parseCRMSchema,
7650
+ queuedFetch,
7651
+ queuedFetchResponse,
7652
+ registerQueueConfig,
6989
7653
  report,
7654
+ requeue,
6990
7655
  resolveInputMappings,
7656
+ resolveQueue,
6991
7657
  resource,
6992
7658
  runWithConfig,
7659
+ runWithRateLimitExecutionContext,
6993
7660
  safeParseCRMSchema,
6994
7661
  safeParseConfig,
6995
7662
  server,