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.
package/dist/index.js CHANGED
@@ -169,10 +169,15 @@ __export(index_exports, {
169
169
  ParticipantEventPayloadSchema: () => ParticipantEventPayloadSchema,
170
170
  ParticipantKindSchema: () => ParticipantKindSchema,
171
171
  ProvisionConfigSchema: () => ProvisionConfigSchema,
172
+ QueueContextError: () => QueueContextError,
173
+ QueueNotFoundError: () => QueueNotFoundError,
174
+ QueuedFetchExhaustedError: () => QueuedFetchExhaustedError,
175
+ RateLimitBackendError: () => RateLimitBackendError,
172
176
  RelationshipCardinalitySchema: () => RelationshipCardinalitySchema,
173
177
  RelationshipDefinitionSchema: () => RelationshipDefinitionSchema,
174
178
  RelationshipExtensionSchema: () => RelationshipExtensionSchema,
175
179
  RelationshipLinkSchema: () => RelationshipLinkSchema,
180
+ RequeueOutsideContextError: () => RequeueOutsideContextError,
176
181
  ResolvedSkillSchema: () => ResolvedSkillSchema,
177
182
  ResolvedTriggerSchema: () => ResolvedTriggerSchema,
178
183
  ResourceDependencySchema: () => ResourceDependencySchema,
@@ -250,6 +255,7 @@ __export(index_exports, {
250
255
  createListResponse: () => createListResponse,
251
256
  createNotFoundError: () => createNotFoundError,
252
257
  createPermissionError: () => createPermissionError,
258
+ createQueueHandle: () => createQueueHandle,
253
259
  createRateLimitError: () => createRateLimitError,
254
260
  createServerHookContext: () => createServerHookContext,
255
261
  createSuccessResponse: () => createSuccessResponse,
@@ -260,6 +266,7 @@ __export(index_exports, {
260
266
  createWorkflowStepContext: () => createWorkflowStepContext,
261
267
  cron: () => cron,
262
268
  default: () => index_default,
269
+ defaultShouldRetry: () => defaultShouldRetry,
263
270
  defineAgent: () => defineAgent,
264
271
  defineChannel: () => defineChannel,
265
272
  defineConfig: () => defineConfig,
@@ -282,6 +289,7 @@ __export(index_exports, {
282
289
  getConfig: () => getConfig,
283
290
  getContextByHandle: () => getContextByHandle,
284
291
  getContextByModel: () => getContextByModel,
292
+ getRateLimitExecutionContext: () => getRateLimitExecutionContext,
285
293
  getRequiredInstallEnvKeys: () => getRequiredInstallEnvKeys,
286
294
  getRetryDelay: () => getRetryDelay,
287
295
  instance: () => instance,
@@ -301,10 +309,16 @@ __export(index_exports, {
301
309
  loadConfig: () => loadConfig,
302
310
  matchesTrigger: () => matchesTrigger,
303
311
  parseCRMSchema: () => parseCRMSchema,
312
+ queuedFetch: () => queuedFetch,
313
+ queuedFetchResponse: () => queuedFetchResponse,
314
+ registerQueueConfig: () => registerQueueConfig,
304
315
  report: () => report,
316
+ requeue: () => requeue,
305
317
  resolveInputMappings: () => resolveInputMappings,
318
+ resolveQueue: () => resolveQueue,
306
319
  resource: () => resource,
307
320
  runWithConfig: () => runWithConfig,
321
+ runWithRateLimitExecutionContext: () => runWithRateLimitExecutionContext,
308
322
  safeParseCRMSchema: () => safeParseCRMSchema,
309
323
  safeParseConfig: () => safeParseConfig,
310
324
  server: () => server,
@@ -2163,6 +2177,68 @@ var CoreApiService = class {
2163
2177
  };
2164
2178
  var coreApiService = new CoreApiService();
2165
2179
 
2180
+ // src/ratelimit/config-loader.ts
2181
+ var fs = __toESM(require("fs"));
2182
+ var path = __toESM(require("path"));
2183
+ var cachedRuntimeQueues = null;
2184
+ function getRuntimeConfigPath() {
2185
+ if (process.env.LAMBDA_TASK_ROOT) {
2186
+ return path.join(process.env.LAMBDA_TASK_ROOT, ".skedyul", "config.json");
2187
+ }
2188
+ return path.join(process.cwd(), ".skedyul", "config.json");
2189
+ }
2190
+ function loadRuntimeConfigFile() {
2191
+ const configPath = getRuntimeConfigPath();
2192
+ if (!fs.existsSync(configPath)) {
2193
+ return null;
2194
+ }
2195
+ try {
2196
+ const raw = fs.readFileSync(configPath, "utf-8");
2197
+ return JSON.parse(raw);
2198
+ } catch {
2199
+ return null;
2200
+ }
2201
+ }
2202
+ var registeredServerConfig = null;
2203
+ function registerQueueConfig(config) {
2204
+ registeredServerConfig = config;
2205
+ cachedRuntimeQueues = config.queues ?? null;
2206
+ }
2207
+ function getQueueDefinitions() {
2208
+ if (cachedRuntimeQueues) {
2209
+ return cachedRuntimeQueues;
2210
+ }
2211
+ if (registeredServerConfig?.queues) {
2212
+ cachedRuntimeQueues = stripNonSerializableQueues(registeredServerConfig.queues);
2213
+ return cachedRuntimeQueues;
2214
+ }
2215
+ const fileConfig = loadRuntimeConfigFile();
2216
+ if (fileConfig?.queues) {
2217
+ cachedRuntimeQueues = fileConfig.queues;
2218
+ return cachedRuntimeQueues;
2219
+ }
2220
+ return {};
2221
+ }
2222
+ function stripNonSerializableQueues(queues) {
2223
+ const result = {};
2224
+ for (const [name, config] of Object.entries(queues)) {
2225
+ const { shouldRetry: _shouldRetry, ...serializable } = config;
2226
+ result[name] = serializable;
2227
+ }
2228
+ return result;
2229
+ }
2230
+ function getQueueConfig(queueName) {
2231
+ return getQueueDefinitions()[queueName];
2232
+ }
2233
+ function getQueueConfigWithRetry(queueName) {
2234
+ const fromServer = registeredServerConfig?.queues?.[queueName];
2235
+ if (fromServer) {
2236
+ return fromServer;
2237
+ }
2238
+ const serializable = getQueueConfig(queueName);
2239
+ return serializable;
2240
+ }
2241
+
2166
2242
  // src/server/utils/schema.ts
2167
2243
  var z8 = __toESM(require("zod"));
2168
2244
  function normalizeBilling(billing) {
@@ -3484,6 +3560,35 @@ var report = {
3484
3560
  }
3485
3561
  };
3486
3562
 
3563
+ // src/ratelimit/context.ts
3564
+ var import_async_hooks2 = require("async_hooks");
3565
+ var executionContextStorage = new import_async_hooks2.AsyncLocalStorage();
3566
+ var activeOperationStorage = new import_async_hooks2.AsyncLocalStorage();
3567
+ function runWithRateLimitExecutionContext(ctx, fn) {
3568
+ return executionContextStorage.run(ctx, fn);
3569
+ }
3570
+ function getRateLimitExecutionContext() {
3571
+ return executionContextStorage.getStore();
3572
+ }
3573
+ function runWithActiveQueuedOperation(operation, fn) {
3574
+ return activeOperationStorage.run(operation, fn);
3575
+ }
3576
+ function getActiveQueuedOperation() {
3577
+ return activeOperationStorage.getStore();
3578
+ }
3579
+ function setActiveQueuedOperationLease(lease) {
3580
+ const op = activeOperationStorage.getStore();
3581
+ if (op) {
3582
+ op.lease = lease;
3583
+ }
3584
+ }
3585
+ function updateActiveQueuedOperationAttempt(attempt) {
3586
+ const op = activeOperationStorage.getStore();
3587
+ if (op) {
3588
+ op.attempt = attempt;
3589
+ }
3590
+ }
3591
+
3487
3592
  // src/errors.ts
3488
3593
  var InstallError = class extends Error {
3489
3594
  // Optional: which field caused the error
@@ -3538,8 +3643,8 @@ var AppAuthInvalidError = class extends Error {
3538
3643
  };
3539
3644
 
3540
3645
  // src/server/context-logger.ts
3541
- var import_async_hooks2 = require("async_hooks");
3542
- var logContextStorage = new import_async_hooks2.AsyncLocalStorage();
3646
+ var import_async_hooks3 = require("async_hooks");
3647
+ var logContextStorage = new import_async_hooks3.AsyncLocalStorage();
3543
3648
  function runWithLogContext(context, fn) {
3544
3649
  return logContextStorage.run(context, fn);
3545
3650
  }
@@ -3752,9 +3857,17 @@ function createCallToolHandler(registry, state, onMaxRequests) {
3752
3857
  baseUrl: toolEnv.SKEDYUL_API_URL ?? "",
3753
3858
  apiToken: toolEnv.SKEDYUL_API_TOKEN ?? ""
3754
3859
  };
3860
+ const rateLimitContext = {
3861
+ app,
3862
+ appInstallationId: trigger === "provision" ? void 0 : rawContext.appInstallationId,
3863
+ invocation,
3864
+ isProvisionContext: trigger === "provision"
3865
+ };
3755
3866
  const functionResult = await runWithConfig(requestConfig, async () => {
3756
- return await runWithLogContext({ invocation }, async () => {
3757
- return await fn(inputs, executionContext);
3867
+ return await runWithRateLimitExecutionContext(rateLimitContext, async () => {
3868
+ return await runWithLogContext({ invocation }, async () => {
3869
+ return await fn(inputs, executionContext);
3870
+ });
3758
3871
  });
3759
3872
  });
3760
3873
  const billing = normalizeBilling(functionResult.billing);
@@ -3891,7 +4004,7 @@ function printStartupLog(config, tools, port) {
3891
4004
 
3892
4005
  // src/server/route-handlers/adapters.ts
3893
4006
  function fromLambdaEvent(event2) {
3894
- const path5 = event2.path || event2.rawPath || "/";
4007
+ const path6 = event2.path || event2.rawPath || "/";
3895
4008
  const method = event2.httpMethod || event2.requestContext?.http?.method || "POST";
3896
4009
  const forwardedProto = event2.headers?.["x-forwarded-proto"] ?? event2.headers?.["X-Forwarded-Proto"];
3897
4010
  const protocol = forwardedProto ?? "https";
@@ -3899,9 +4012,9 @@ function fromLambdaEvent(event2) {
3899
4012
  const queryString = event2.queryStringParameters ? "?" + new URLSearchParams(
3900
4013
  event2.queryStringParameters
3901
4014
  ).toString() : "";
3902
- const url = `${protocol}://${host}${path5}${queryString}`;
4015
+ const url = `${protocol}://${host}${path6}${queryString}`;
3903
4016
  return {
3904
- path: path5,
4017
+ path: path6,
3905
4018
  method,
3906
4019
  headers: event2.headers,
3907
4020
  query: event2.queryStringParameters ?? {},
@@ -3983,8 +4096,8 @@ function parseBodyByContentType(req) {
3983
4096
  }
3984
4097
 
3985
4098
  // src/server/route-handlers/handlers.ts
3986
- var fs = __toESM(require("fs"));
3987
- var path = __toESM(require("path"));
4099
+ var fs2 = __toESM(require("fs"));
4100
+ var path2 = __toESM(require("path"));
3988
4101
 
3989
4102
  // src/server/core-api-handler.ts
3990
4103
  async function handleCoreMethod(method, params) {
@@ -4152,7 +4265,8 @@ function serializeConfig(config) {
4152
4265
  type: w.type ?? "WEBHOOK"
4153
4266
  })) : [],
4154
4267
  provision: isProvisionConfig(config.provision) ? config.provision : void 0,
4155
- agents: config.agents
4268
+ agents: config.agents,
4269
+ queues: config.queues
4156
4270
  };
4157
4271
  }
4158
4272
  function isProvisionConfig(value) {
@@ -4448,7 +4562,7 @@ async function handleOAuthCallback(parsedBody, hooks) {
4448
4562
  }
4449
4563
 
4450
4564
  // src/server/handlers/webhook-handler.ts
4451
- function parseWebhookRequest(parsedBody, method, url, path5, headers, query, rawBody, appIdHeader, appVersionIdHeader) {
4565
+ function parseWebhookRequest(parsedBody, method, url, path6, headers, query, rawBody, appIdHeader, appVersionIdHeader) {
4452
4566
  const isEnvelope = typeof parsedBody === "object" && parsedBody !== null && "env" in parsedBody && "request" in parsedBody && "context" in parsedBody;
4453
4567
  if (isEnvelope) {
4454
4568
  const envelope = parsedBody;
@@ -4502,7 +4616,7 @@ function parseWebhookRequest(parsedBody, method, url, path5, headers, query, raw
4502
4616
  const webhookRequest = {
4503
4617
  method,
4504
4618
  url,
4505
- path: path5,
4619
+ path: path6,
4506
4620
  headers,
4507
4621
  query,
4508
4622
  body: parsedBody,
@@ -4560,7 +4674,7 @@ function isMethodAllowed(webhookRegistry, handle, method) {
4560
4674
 
4561
4675
  // src/server/route-handlers/handlers.ts
4562
4676
  function getConfigFilePath() {
4563
- return process.env.LAMBDA_TASK_ROOT ? path.join(process.env.LAMBDA_TASK_ROOT, ".skedyul", "config.json") : ".skedyul/config.json";
4677
+ return process.env.LAMBDA_TASK_ROOT ? path2.join(process.env.LAMBDA_TASK_ROOT, ".skedyul", "config.json") : ".skedyul/config.json";
4564
4678
  }
4565
4679
  function findToolInRegistry(registry, toolName) {
4566
4680
  for (const [key, t] of Object.entries(registry)) {
@@ -4599,8 +4713,8 @@ function handleHealthRoute(ctx) {
4599
4713
  function handleConfigRoute(ctx) {
4600
4714
  const configFilePath = getConfigFilePath();
4601
4715
  try {
4602
- if (fs.existsSync(configFilePath)) {
4603
- const fileConfig = JSON.parse(fs.readFileSync(configFilePath, "utf-8"));
4716
+ if (fs2.existsSync(configFilePath)) {
4717
+ const fileConfig = JSON.parse(fs2.readFileSync(configFilePath, "utf-8"));
4604
4718
  return { status: 200, body: fileConfig };
4605
4719
  }
4606
4720
  } catch (err) {
@@ -5285,6 +5399,7 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
5285
5399
  installContextLogger();
5286
5400
  function createSkedyulServer(config) {
5287
5401
  mergeRuntimeEnv();
5402
+ registerQueueConfig(config);
5288
5403
  const registry = config.tools;
5289
5404
  const webhookRegistry = config.webhooks;
5290
5405
  if (config.coreApi?.service) {
@@ -5553,8 +5668,8 @@ function defineNavigation(navigation) {
5553
5668
  }
5554
5669
 
5555
5670
  // src/config/loader.ts
5556
- var fs2 = __toESM(require("fs"));
5557
- var path2 = __toESM(require("path"));
5671
+ var fs3 = __toESM(require("fs"));
5672
+ var path3 = __toESM(require("path"));
5558
5673
  var os = __toESM(require("os"));
5559
5674
  var CONFIG_FILE_NAMES = [
5560
5675
  "skedyul.config.ts",
@@ -5563,13 +5678,13 @@ var CONFIG_FILE_NAMES = [
5563
5678
  "skedyul.config.cjs"
5564
5679
  ];
5565
5680
  async function transpileTypeScript(filePath) {
5566
- const content = fs2.readFileSync(filePath, "utf-8");
5567
- const configDir = path2.dirname(path2.resolve(filePath));
5681
+ const content = fs3.readFileSync(filePath, "utf-8");
5682
+ const configDir = path3.dirname(path3.resolve(filePath));
5568
5683
  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*$/, "}");
5569
5684
  transpiled = transpiled.replace(
5570
5685
  /import\s+(\w+)\s+from\s+['"](\.[^'"]+)['"]\s*(?:with\s*\{[^}]*\})?/g,
5571
5686
  (match, varName, relativePath) => {
5572
- const absolutePath = path2.resolve(configDir, relativePath);
5687
+ const absolutePath = path3.resolve(configDir, relativePath);
5573
5688
  return `const ${varName} = require('${absolutePath.replace(/\\/g, "/")}')`;
5574
5689
  }
5575
5690
  );
@@ -5577,8 +5692,8 @@ async function transpileTypeScript(filePath) {
5577
5692
  return transpiled;
5578
5693
  }
5579
5694
  async function loadConfig(configPath) {
5580
- const absolutePath = path2.resolve(configPath);
5581
- if (!fs2.existsSync(absolutePath)) {
5695
+ const absolutePath = path3.resolve(configPath);
5696
+ if (!fs3.existsSync(absolutePath)) {
5582
5697
  throw new Error(`Config file not found: ${absolutePath}`);
5583
5698
  }
5584
5699
  const isTypeScript = absolutePath.endsWith(".ts");
@@ -5587,8 +5702,8 @@ async function loadConfig(configPath) {
5587
5702
  if (isTypeScript) {
5588
5703
  const transpiled = await transpileTypeScript(absolutePath);
5589
5704
  const tempDir = os.tmpdir();
5590
- const tempFile = path2.join(tempDir, `skedyul-config-${Date.now()}.cjs`);
5591
- fs2.writeFileSync(tempFile, transpiled);
5705
+ const tempFile = path3.join(tempDir, `skedyul-config-${Date.now()}.cjs`);
5706
+ fs3.writeFileSync(tempFile, transpiled);
5592
5707
  moduleToLoad = tempFile;
5593
5708
  try {
5594
5709
  const module3 = require(moduleToLoad);
@@ -5602,7 +5717,7 @@ async function loadConfig(configPath) {
5602
5717
  return config2;
5603
5718
  } finally {
5604
5719
  try {
5605
- fs2.unlinkSync(tempFile);
5720
+ fs3.unlinkSync(tempFile);
5606
5721
  } catch {
5607
5722
  }
5608
5723
  }
@@ -5637,13 +5752,13 @@ function validateConfig(config) {
5637
5752
  }
5638
5753
 
5639
5754
  // src/config/schema-loader.ts
5640
- var fs3 = __toESM(require("fs"));
5641
- var path3 = __toESM(require("path"));
5755
+ var fs4 = __toESM(require("fs"));
5756
+ var path4 = __toESM(require("path"));
5642
5757
  var os2 = __toESM(require("os"));
5643
5758
 
5644
5759
  // src/config/resolver.ts
5645
- var fs4 = __toESM(require("fs"));
5646
- var path4 = __toESM(require("path"));
5760
+ var fs5 = __toESM(require("fs"));
5761
+ var path5 = __toESM(require("path"));
5647
5762
 
5648
5763
  // src/config/utils.ts
5649
5764
  function getAllEnvKeys(config) {
@@ -5660,6 +5775,553 @@ function getRequiredInstallEnvKeys(config) {
5660
5775
  return Object.entries(provision.env).filter(([, def]) => def.required).map(([key]) => key);
5661
5776
  }
5662
5777
 
5778
+ // src/ratelimit/errors.ts
5779
+ var QueueContextError = class extends Error {
5780
+ constructor(message) {
5781
+ super(message);
5782
+ this.code = "QUEUE_CONTEXT_ERROR";
5783
+ this.name = "QueueContextError";
5784
+ }
5785
+ };
5786
+ var QueueNotFoundError = class extends Error {
5787
+ constructor(queueName) {
5788
+ super(`Queue "${queueName}" is not defined in skedyul.config queues`);
5789
+ this.code = "QUEUE_NOT_FOUND";
5790
+ this.name = "QueueNotFoundError";
5791
+ }
5792
+ };
5793
+ var QueuedFetchExhaustedError = class extends Error {
5794
+ constructor(attempts, maxRetries, causeError) {
5795
+ super(
5796
+ `queuedFetch exhausted retries after ${attempts} attempts (maxRetries=${maxRetries})`
5797
+ );
5798
+ this.code = "QUEUED_FETCH_EXHAUSTED";
5799
+ this.name = "QueuedFetchExhaustedError";
5800
+ this.attempts = attempts;
5801
+ this.maxRetries = maxRetries;
5802
+ this.causeError = causeError;
5803
+ }
5804
+ };
5805
+ var RequeueOutsideContextError = class extends Error {
5806
+ constructor() {
5807
+ super("requeue() can only be called inside an active queuedFetch operation");
5808
+ this.code = "REQUEUE_OUTSIDE_CONTEXT";
5809
+ this.name = "RequeueOutsideContextError";
5810
+ }
5811
+ };
5812
+ var RateLimitBackendError = class extends Error {
5813
+ constructor(message, statusCode) {
5814
+ super(message);
5815
+ this.code = "RATE_LIMIT_BACKEND_ERROR";
5816
+ this.name = "RateLimitBackendError";
5817
+ this.statusCode = statusCode;
5818
+ }
5819
+ };
5820
+
5821
+ // src/ratelimit/resolve-queue-key.ts
5822
+ function resolveEndpointHandle(config, invocation) {
5823
+ if (config.endpoint) {
5824
+ return config.endpoint;
5825
+ }
5826
+ if (invocation?.toolHandle) {
5827
+ return invocation.toolHandle;
5828
+ }
5829
+ if (invocation?.serverHookHandle) {
5830
+ return invocation.serverHookHandle;
5831
+ }
5832
+ throw new QueueContextError(
5833
+ `Cannot resolve endpoint handle for queue scope "${config.scope}". Set endpoint in queue config or ensure invocation.toolHandle/serverHookHandle is available.`
5834
+ );
5835
+ }
5836
+ function appendSubKey(base, subKey) {
5837
+ return subKey ? `${base}:${subKey}` : base;
5838
+ }
5839
+ function resolveQueueKey(queueName, config, ctx, subKey) {
5840
+ const { app, appInstallationId, invocation, isProvisionContext: isProvisionContext2 } = ctx;
5841
+ switch (config.scope) {
5842
+ case "provision": {
5843
+ const base = `rl:pv:${app.versionId}:${queueName}`;
5844
+ return appendSubKey(base, subKey);
5845
+ }
5846
+ case "install": {
5847
+ if (!appInstallationId) {
5848
+ throw new QueueContextError(
5849
+ `Queue "${queueName}" with scope "install" requires appInstallationId in context`
5850
+ );
5851
+ }
5852
+ const base = `rl:in:${appInstallationId}:${queueName}`;
5853
+ return appendSubKey(base, subKey);
5854
+ }
5855
+ case "provision_endpoint": {
5856
+ if (!isProvisionContext2 && appInstallationId) {
5857
+ throw new QueueContextError(
5858
+ `Queue "${queueName}" with scope "provision_endpoint" requires provision context (no install)`
5859
+ );
5860
+ }
5861
+ const endpointHandle = resolveEndpointHandle(config, invocation);
5862
+ const base = `rl:pep:${app.versionId}:${endpointHandle}:${queueName}`;
5863
+ return appendSubKey(base, subKey);
5864
+ }
5865
+ case "install_endpoint": {
5866
+ if (!appInstallationId) {
5867
+ throw new QueueContextError(
5868
+ `Queue "${queueName}" with scope "install_endpoint" requires appInstallationId in context`
5869
+ );
5870
+ }
5871
+ const endpointHandle = resolveEndpointHandle(config, invocation);
5872
+ const base = `rl:iep:${appInstallationId}:${endpointHandle}:${queueName}`;
5873
+ return appendSubKey(base, subKey);
5874
+ }
5875
+ case "global": {
5876
+ const base = `rl:gl:${app.versionId}:${queueName}`;
5877
+ return appendSubKey(base, subKey);
5878
+ }
5879
+ default: {
5880
+ const _exhaustive = config.scope;
5881
+ throw new QueueContextError(`Unknown queue scope: ${String(_exhaustive)}`);
5882
+ }
5883
+ }
5884
+ }
5885
+ function toQueueLimits(config) {
5886
+ return {
5887
+ maxConcurrent: config.maxConcurrent,
5888
+ minTime: config.minTime,
5889
+ reservoir: config.reservoir,
5890
+ reservoirRefreshAmount: config.reservoirRefreshAmount,
5891
+ reservoirRefreshInterval: config.reservoirRefreshInterval
5892
+ };
5893
+ }
5894
+
5895
+ // src/ratelimit/backends/platform.ts
5896
+ function getApiBaseUrl() {
5897
+ const config = getConfig();
5898
+ if (config.baseUrl) {
5899
+ return config.baseUrl.replace(/\/+$/, "");
5900
+ }
5901
+ return (process.env.SKEDYUL_API_URL ?? process.env.SKEDYUL_NODE_URL ?? "").replace(/\/+$/, "");
5902
+ }
5903
+ function getApiToken() {
5904
+ const config = getConfig();
5905
+ return config.apiToken || process.env.SKEDYUL_API_TOKEN || "";
5906
+ }
5907
+ async function callRateLimitApi(path6, body) {
5908
+ const baseUrl = getApiBaseUrl();
5909
+ const token2 = getApiToken();
5910
+ if (!baseUrl || !token2) {
5911
+ throw new RateLimitBackendError(
5912
+ "SKEDYUL_API_URL and SKEDYUL_API_TOKEN are required for platform rate limiting"
5913
+ );
5914
+ }
5915
+ const response = await fetch(`${baseUrl}/api/internal/ratelimit/${path6}`, {
5916
+ method: "POST",
5917
+ headers: {
5918
+ Authorization: `Bearer ${token2}`,
5919
+ "Content-Type": "application/json"
5920
+ },
5921
+ body: JSON.stringify(body)
5922
+ });
5923
+ const payload = await response.json().catch(() => null);
5924
+ if (!response.ok || payload?.success === false) {
5925
+ const message = payload?.errors?.[0]?.message ?? `Rate limit API ${path6} failed with status ${response.status}`;
5926
+ throw new RateLimitBackendError(message, response.status);
5927
+ }
5928
+ if (!payload?.data) {
5929
+ throw new RateLimitBackendError(`Rate limit API ${path6} returned empty data`);
5930
+ }
5931
+ return payload.data;
5932
+ }
5933
+ var PlatformRateLimitBackend = class {
5934
+ async acquire(queueKey, limits, timeoutMs) {
5935
+ const data = await callRateLimitApi("acquire", {
5936
+ queueKey,
5937
+ limits,
5938
+ timeoutMs
5939
+ });
5940
+ return {
5941
+ leaseId: data.leaseId,
5942
+ acquiredAt: data.acquiredAt,
5943
+ queueKey: data.queueKey
5944
+ };
5945
+ }
5946
+ async release(lease) {
5947
+ await callRateLimitApi("release", {
5948
+ leaseId: lease.leaseId
5949
+ });
5950
+ }
5951
+ };
5952
+ var platformRateLimitBackend = new PlatformRateLimitBackend();
5953
+
5954
+ // src/ratelimit/backends/memory.ts
5955
+ var DEFAULT_MAX_CONCURRENT = 10;
5956
+ function getMaxConcurrent(limits) {
5957
+ return limits.maxConcurrent ?? DEFAULT_MAX_CONCURRENT;
5958
+ }
5959
+ function getMinTime(limits) {
5960
+ return limits.minTime ?? 0;
5961
+ }
5962
+ function generateLeaseId() {
5963
+ return `mem_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
5964
+ }
5965
+ function refillTokens(state, limits) {
5966
+ if (limits.reservoir === void 0) {
5967
+ return;
5968
+ }
5969
+ const capacity = limits.reservoir;
5970
+ const refreshAmount = limits.reservoirRefreshAmount ?? limits.reservoir;
5971
+ const refreshInterval = limits.reservoirRefreshInterval ?? 6e4;
5972
+ const now = Date.now();
5973
+ const elapsed = now - state.lastRefillAt;
5974
+ if (elapsed >= refreshInterval) {
5975
+ const intervals = Math.floor(elapsed / refreshInterval);
5976
+ state.tokens = Math.min(capacity, state.tokens + intervals * refreshAmount);
5977
+ state.lastRefillAt = now;
5978
+ }
5979
+ }
5980
+ function canAcquire(state, limits) {
5981
+ if (state.running >= getMaxConcurrent(limits)) {
5982
+ return false;
5983
+ }
5984
+ refillTokens(state, limits);
5985
+ if (limits.reservoir !== void 0 && state.tokens <= 0) {
5986
+ return false;
5987
+ }
5988
+ const minTime = getMinTime(limits);
5989
+ if (minTime > 0 && state.lastStartAt > 0) {
5990
+ if (Date.now() - state.lastStartAt < minTime) {
5991
+ return false;
5992
+ }
5993
+ }
5994
+ return true;
5995
+ }
5996
+ var MemoryRateLimitBackend = class {
5997
+ constructor() {
5998
+ this.queues = /* @__PURE__ */ new Map();
5999
+ this.leases = /* @__PURE__ */ new Map();
6000
+ }
6001
+ getState(queueKey) {
6002
+ let state = this.queues.get(queueKey);
6003
+ if (!state) {
6004
+ state = {
6005
+ running: 0,
6006
+ waiters: [],
6007
+ lastStartAt: 0,
6008
+ tokens: Number.POSITIVE_INFINITY,
6009
+ lastRefillAt: Date.now()
6010
+ };
6011
+ if (queueKey.includes(":")) {
6012
+ }
6013
+ this.queues.set(queueKey, state);
6014
+ }
6015
+ return state;
6016
+ }
6017
+ grant(state, queueKey, limits) {
6018
+ if (limits.reservoir !== void 0) {
6019
+ state.tokens -= 1;
6020
+ }
6021
+ state.running += 1;
6022
+ state.lastStartAt = Date.now();
6023
+ const leaseId = generateLeaseId();
6024
+ this.leases.set(leaseId, { queueKey, limits });
6025
+ return { leaseId, acquiredAt: Date.now(), queueKey };
6026
+ }
6027
+ drainWaiters(queueKey, state) {
6028
+ let progressed = true;
6029
+ while (progressed && state.waiters.length > 0) {
6030
+ progressed = false;
6031
+ for (let i = 0; i < state.waiters.length; i += 1) {
6032
+ const waiter = state.waiters[i];
6033
+ if (!waiter || !canAcquire(state, waiter.limits)) {
6034
+ continue;
6035
+ }
6036
+ state.waiters.splice(i, 1);
6037
+ if (waiter.timer) {
6038
+ clearTimeout(waiter.timer);
6039
+ }
6040
+ waiter.resolve(this.grant(state, queueKey, waiter.limits));
6041
+ progressed = true;
6042
+ break;
6043
+ }
6044
+ }
6045
+ }
6046
+ async acquire(queueKey, limits, timeoutMs = 12e4) {
6047
+ const state = this.getState(queueKey);
6048
+ if (limits.reservoir !== void 0 && state.tokens === Number.POSITIVE_INFINITY) {
6049
+ state.tokens = limits.reservoir;
6050
+ }
6051
+ if (canAcquire(state, limits)) {
6052
+ return this.grant(state, queueKey, limits);
6053
+ }
6054
+ return new Promise((resolve4, reject) => {
6055
+ const waiter = {
6056
+ resolve: resolve4,
6057
+ reject,
6058
+ timer: null,
6059
+ limits
6060
+ };
6061
+ if (timeoutMs > 0) {
6062
+ waiter.timer = setTimeout(() => {
6063
+ const idx = state.waiters.indexOf(waiter);
6064
+ if (idx >= 0) {
6065
+ state.waiters.splice(idx, 1);
6066
+ }
6067
+ reject(
6068
+ new Error(`Rate limit acquire timed out after ${timeoutMs}ms for ${queueKey}`)
6069
+ );
6070
+ }, timeoutMs);
6071
+ }
6072
+ state.waiters.push(waiter);
6073
+ const retry = () => {
6074
+ if (!state.waiters.includes(waiter)) {
6075
+ return;
6076
+ }
6077
+ if (canAcquire(state, limits)) {
6078
+ const idx = state.waiters.indexOf(waiter);
6079
+ if (idx >= 0) {
6080
+ state.waiters.splice(idx, 1);
6081
+ }
6082
+ if (waiter.timer) {
6083
+ clearTimeout(waiter.timer);
6084
+ }
6085
+ resolve4(this.grant(state, queueKey, limits));
6086
+ return;
6087
+ }
6088
+ setTimeout(retry, Math.max(getMinTime(limits), 10));
6089
+ };
6090
+ setTimeout(retry, Math.max(getMinTime(limits), 10));
6091
+ });
6092
+ }
6093
+ async release(lease) {
6094
+ const tracked = this.leases.get(lease.leaseId);
6095
+ this.leases.delete(lease.leaseId);
6096
+ const queueKey = tracked?.queueKey ?? lease.queueKey;
6097
+ const state = this.queues.get(queueKey);
6098
+ if (!state) {
6099
+ return;
6100
+ }
6101
+ state.running = Math.max(0, state.running - 1);
6102
+ this.drainWaiters(queueKey, state);
6103
+ }
6104
+ };
6105
+ var memoryRateLimitBackend = new MemoryRateLimitBackend();
6106
+
6107
+ // src/ratelimit/backends/index.ts
6108
+ var cachedBackend = null;
6109
+ function shouldForceMemoryBackend() {
6110
+ const flag = process.env.SKEDYUL_RATE_LIMIT_MEMORY;
6111
+ return flag === "1" || flag === "true";
6112
+ }
6113
+ function getRateLimitBackend() {
6114
+ if (cachedBackend) {
6115
+ return cachedBackend;
6116
+ }
6117
+ if (shouldForceMemoryBackend()) {
6118
+ cachedBackend = memoryRateLimitBackend;
6119
+ return cachedBackend;
6120
+ }
6121
+ cachedBackend = createResilientBackend();
6122
+ return cachedBackend;
6123
+ }
6124
+ function createResilientBackend() {
6125
+ return {
6126
+ async acquire(queueKey, limits, timeoutMs) {
6127
+ try {
6128
+ return await platformRateLimitBackend.acquire(queueKey, limits, timeoutMs);
6129
+ } catch (error) {
6130
+ if (shouldFallbackToMemory(error)) {
6131
+ return memoryRateLimitBackend.acquire(queueKey, limits, timeoutMs);
6132
+ }
6133
+ throw error;
6134
+ }
6135
+ },
6136
+ async release(lease) {
6137
+ if (lease.leaseId.startsWith("mem_")) {
6138
+ return memoryRateLimitBackend.release(lease);
6139
+ }
6140
+ try {
6141
+ await platformRateLimitBackend.release(lease);
6142
+ } catch (error) {
6143
+ if (shouldFallbackToMemory(error)) {
6144
+ return memoryRateLimitBackend.release(lease);
6145
+ }
6146
+ throw error;
6147
+ }
6148
+ }
6149
+ };
6150
+ }
6151
+ function shouldFallbackToMemory(error) {
6152
+ if (shouldForceMemoryBackend()) {
6153
+ return true;
6154
+ }
6155
+ if (error instanceof RateLimitBackendError) {
6156
+ if (error.statusCode === 404 || error.statusCode === 503) {
6157
+ return true;
6158
+ }
6159
+ }
6160
+ if (error instanceof TypeError) {
6161
+ return true;
6162
+ }
6163
+ const nodeEnv = process.env.NODE_ENV;
6164
+ return nodeEnv === "development" || nodeEnv === "test";
6165
+ }
6166
+
6167
+ // src/ratelimit/should-retry.ts
6168
+ var DEFAULT_RETRY_STATUS_CODES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
6169
+ function getErrorStatusCode(error) {
6170
+ if (typeof error !== "object" || error === null) {
6171
+ return void 0;
6172
+ }
6173
+ const record2 = error;
6174
+ if (typeof record2.statusCode === "number") {
6175
+ return record2.statusCode;
6176
+ }
6177
+ if (typeof record2.status === "number") {
6178
+ return record2.status;
6179
+ }
6180
+ return void 0;
6181
+ }
6182
+ function getErrorCode(error) {
6183
+ if (typeof error !== "object" || error === null) {
6184
+ return void 0;
6185
+ }
6186
+ const record2 = error;
6187
+ return typeof record2.code === "string" ? record2.code : void 0;
6188
+ }
6189
+ function defaultShouldRetry(error, _attempt) {
6190
+ const code = getErrorCode(error);
6191
+ if (code === "RATE_LIMITED" || code === "RATE_LIMIT_BACKEND_ERROR") {
6192
+ return true;
6193
+ }
6194
+ const status = getErrorStatusCode(error);
6195
+ if (status !== void 0 && DEFAULT_RETRY_STATUS_CODES.has(status)) {
6196
+ return true;
6197
+ }
6198
+ const message = error instanceof Error ? error.message : typeof error === "string" ? error : "";
6199
+ const lower = message.toLowerCase();
6200
+ if (lower.includes("rate limit") || lower.includes("too many requests") || lower.includes("econnreset") || lower.includes("etimedout")) {
6201
+ return true;
6202
+ }
6203
+ return false;
6204
+ }
6205
+ function sleep(ms) {
6206
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
6207
+ }
6208
+
6209
+ // src/ratelimit/queued-fetch.ts
6210
+ function normalizeQueueInput(input) {
6211
+ if (typeof input === "string") {
6212
+ return { name: input };
6213
+ }
6214
+ return { name: input.queue, subKey: input.key };
6215
+ }
6216
+ function resolveQueue(queueInput, ctxOverride) {
6217
+ const ctx = ctxOverride ?? getRateLimitExecutionContext();
6218
+ if (!ctx?.app?.versionId) {
6219
+ throw new QueueContextError(
6220
+ "Cannot resolve queue without app context. Ensure queuedFetch runs inside a tool/hook handler."
6221
+ );
6222
+ }
6223
+ const { name, subKey } = normalizeQueueInput(queueInput);
6224
+ const config = getQueueConfigWithRetry(name);
6225
+ if (!config) {
6226
+ throw new QueueNotFoundError(name);
6227
+ }
6228
+ const queueKey = resolveQueueKey(name, config, ctx, subKey);
6229
+ return {
6230
+ name,
6231
+ queueKey,
6232
+ config,
6233
+ limits: toQueueLimits(config)
6234
+ };
6235
+ }
6236
+ function createQueueHandle(queueInput) {
6237
+ return {
6238
+ run(fn) {
6239
+ return queuedFetch(queueInput, fn);
6240
+ }
6241
+ };
6242
+ }
6243
+ async function queuedFetch(queueInput, fnOrPromise) {
6244
+ const fn = typeof fnOrPromise === "function" ? fnOrPromise : () => fnOrPromise;
6245
+ const resolved = resolveQueue(queueInput);
6246
+ const maxRetries = resolved.config.maxRetries ?? 0;
6247
+ const operation = {
6248
+ queueInput,
6249
+ fn,
6250
+ attempt: 0,
6251
+ resolved,
6252
+ lease: null
6253
+ };
6254
+ return runWithActiveQueuedOperation(
6255
+ operation,
6256
+ () => executeWithRetries(operation, maxRetries)
6257
+ );
6258
+ }
6259
+ async function executeWithRetries(operation, maxRetries) {
6260
+ const backend = getRateLimitBackend();
6261
+ const timeoutMs = operation.resolved.config.timeout ?? 12e4;
6262
+ const retryDelayMs = operation.resolved.config.retryDelayMs ?? 1e3;
6263
+ const shouldRetryFn = getQueueConfigWithRetry(operation.resolved.name)?.shouldRetry ?? defaultShouldRetry;
6264
+ const lease = await backend.acquire(
6265
+ operation.resolved.queueKey,
6266
+ operation.resolved.limits,
6267
+ timeoutMs
6268
+ );
6269
+ operation.lease = lease;
6270
+ setActiveQueuedOperationLease(lease);
6271
+ try {
6272
+ return await operation.fn();
6273
+ } catch (error) {
6274
+ await backend.release(lease);
6275
+ operation.lease = null;
6276
+ setActiveQueuedOperationLease(null);
6277
+ if (shouldRetryFn(error, operation.attempt) && operation.attempt < maxRetries) {
6278
+ await sleep(retryDelayMs);
6279
+ operation.attempt += 1;
6280
+ updateActiveQueuedOperationAttempt(operation.attempt);
6281
+ return executeWithRetries(operation, maxRetries);
6282
+ }
6283
+ if (shouldRetryFn(error, operation.attempt) && operation.attempt >= maxRetries) {
6284
+ throw new QueuedFetchExhaustedError(
6285
+ operation.attempt + 1,
6286
+ maxRetries,
6287
+ error
6288
+ );
6289
+ }
6290
+ throw error;
6291
+ } finally {
6292
+ if (operation.lease) {
6293
+ await backend.release(operation.lease);
6294
+ operation.lease = null;
6295
+ setActiveQueuedOperationLease(null);
6296
+ }
6297
+ }
6298
+ }
6299
+ async function requeue() {
6300
+ const operation = getActiveQueuedOperation();
6301
+ if (!operation) {
6302
+ throw new RequeueOutsideContextError();
6303
+ }
6304
+ const maxRetries = operation.resolved.config.maxRetries ?? 0;
6305
+ const nextAttempt = operation.attempt + 1;
6306
+ if (nextAttempt > maxRetries) {
6307
+ throw new QueuedFetchExhaustedError(nextAttempt, maxRetries, void 0);
6308
+ }
6309
+ if (operation.lease) {
6310
+ const backend = getRateLimitBackend();
6311
+ await backend.release(operation.lease);
6312
+ operation.lease = null;
6313
+ setActiveQueuedOperationLease(null);
6314
+ }
6315
+ operation.attempt = nextAttempt;
6316
+ updateActiveQueuedOperationAttempt(nextAttempt);
6317
+ const retryDelayMs = operation.resolved.config.retryDelayMs ?? 1e3;
6318
+ await sleep(retryDelayMs);
6319
+ return executeWithRetries(operation, maxRetries);
6320
+ }
6321
+ async function queuedFetchResponse(queueInput, url, init) {
6322
+ return queuedFetch(queueInput, () => fetch(url, init));
6323
+ }
6324
+
5663
6325
  // src/triggers/types.ts
5664
6326
  var import_v49 = require("zod/v4");
5665
6327
  var CRMDataSchema = import_v49.z.object({
@@ -5764,16 +6426,16 @@ function evaluateTemplate(template, context) {
5764
6426
  const templateRegex = /\{\{\s*([^}]+)\s*\}\}/g;
5765
6427
  const singleMatch = template.match(/^\{\{\s*([^}]+)\s*\}\}$/);
5766
6428
  if (singleMatch) {
5767
- const path5 = singleMatch[1].trim();
5768
- return resolvePath(context, path5);
6429
+ const path6 = singleMatch[1].trim();
6430
+ return resolvePath(context, path6);
5769
6431
  }
5770
- return template.replace(templateRegex, (_, path5) => {
5771
- const value = resolvePath(context, path5.trim());
6432
+ return template.replace(templateRegex, (_, path6) => {
6433
+ const value = resolvePath(context, path6.trim());
5772
6434
  return value === void 0 || value === null ? "" : String(value);
5773
6435
  });
5774
6436
  }
5775
- function resolvePath(obj, path5) {
5776
- const parts = path5.split(".");
6437
+ function resolvePath(obj, path6) {
6438
+ const parts = path6.split(".");
5777
6439
  let current = obj;
5778
6440
  for (const part of parts) {
5779
6441
  if (current === null || current === void 0) {
@@ -5794,14 +6456,14 @@ function evaluateCondition(condition, context) {
5794
6456
  const expression = match[1].trim();
5795
6457
  const eqMatch = expression.match(/^(.+?)\s*==\s*['"](.+)['"]$/);
5796
6458
  if (eqMatch) {
5797
- const [, path5, expectedValue] = eqMatch;
5798
- const actualValue = resolvePath(context, path5.trim());
6459
+ const [, path6, expectedValue] = eqMatch;
6460
+ const actualValue = resolvePath(context, path6.trim());
5799
6461
  return actualValue === expectedValue;
5800
6462
  }
5801
6463
  const neqMatch = expression.match(/^(.+?)\s*!=\s*['"](.+)['"]$/);
5802
6464
  if (neqMatch) {
5803
- const [, path5, expectedValue] = neqMatch;
5804
- const actualValue = resolvePath(context, path5.trim());
6465
+ const [, path6, expectedValue] = neqMatch;
6466
+ const actualValue = resolvePath(context, path6.trim());
5805
6467
  return actualValue !== expectedValue;
5806
6468
  }
5807
6469
  const value = resolvePath(context, expression);
@@ -7166,10 +7828,15 @@ var index_default = { z: import_v413.z };
7166
7828
  ParticipantEventPayloadSchema,
7167
7829
  ParticipantKindSchema,
7168
7830
  ProvisionConfigSchema,
7831
+ QueueContextError,
7832
+ QueueNotFoundError,
7833
+ QueuedFetchExhaustedError,
7834
+ RateLimitBackendError,
7169
7835
  RelationshipCardinalitySchema,
7170
7836
  RelationshipDefinitionSchema,
7171
7837
  RelationshipExtensionSchema,
7172
7838
  RelationshipLinkSchema,
7839
+ RequeueOutsideContextError,
7173
7840
  ResolvedSkillSchema,
7174
7841
  ResolvedTriggerSchema,
7175
7842
  ResourceDependencySchema,
@@ -7247,6 +7914,7 @@ var index_default = { z: import_v413.z };
7247
7914
  createListResponse,
7248
7915
  createNotFoundError,
7249
7916
  createPermissionError,
7917
+ createQueueHandle,
7250
7918
  createRateLimitError,
7251
7919
  createServerHookContext,
7252
7920
  createSuccessResponse,
@@ -7256,6 +7924,7 @@ var index_default = { z: import_v413.z };
7256
7924
  createWebhookContext,
7257
7925
  createWorkflowStepContext,
7258
7926
  cron,
7927
+ defaultShouldRetry,
7259
7928
  defineAgent,
7260
7929
  defineChannel,
7261
7930
  defineConfig,
@@ -7278,6 +7947,7 @@ var index_default = { z: import_v413.z };
7278
7947
  getConfig,
7279
7948
  getContextByHandle,
7280
7949
  getContextByModel,
7950
+ getRateLimitExecutionContext,
7281
7951
  getRequiredInstallEnvKeys,
7282
7952
  getRetryDelay,
7283
7953
  instance,
@@ -7297,10 +7967,16 @@ var index_default = { z: import_v413.z };
7297
7967
  loadConfig,
7298
7968
  matchesTrigger,
7299
7969
  parseCRMSchema,
7970
+ queuedFetch,
7971
+ queuedFetchResponse,
7972
+ registerQueueConfig,
7300
7973
  report,
7974
+ requeue,
7301
7975
  resolveInputMappings,
7976
+ resolveQueue,
7302
7977
  resource,
7303
7978
  runWithConfig,
7979
+ runWithRateLimitExecutionContext,
7304
7980
  safeParseCRMSchema,
7305
7981
  safeParseConfig,
7306
7982
  server,