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.
@@ -0,0 +1,10 @@
1
+ import { platformRateLimitBackend } from './platform';
2
+ import { memoryRateLimitBackend } from './memory';
3
+ import type { RateLimitBackend } from '../types';
4
+ /**
5
+ * Resolve the active rate limit backend.
6
+ * Uses platform proxy by default; falls back to in-memory when configured or on local errors.
7
+ */
8
+ export declare function getRateLimitBackend(): RateLimitBackend;
9
+ export declare function resetRateLimitBackendForTests(): void;
10
+ export { platformRateLimitBackend, memoryRateLimitBackend };
@@ -0,0 +1,14 @@
1
+ import type { RateLimitBackend, Lease, QueueLimits } from '../types';
2
+ /**
3
+ * In-process rate limit backend for local development.
4
+ */
5
+ export declare class MemoryRateLimitBackend implements RateLimitBackend {
6
+ private readonly queues;
7
+ private readonly leases;
8
+ private getState;
9
+ private grant;
10
+ private drainWaiters;
11
+ acquire(queueKey: string, limits: QueueLimits, timeoutMs?: number): Promise<Lease>;
12
+ release(lease: Lease): Promise<void>;
13
+ }
14
+ export declare const memoryRateLimitBackend: MemoryRateLimitBackend;
@@ -0,0 +1,9 @@
1
+ import type { RateLimitBackend, Lease, QueueLimits } from '../types';
2
+ /**
3
+ * HTTP backend that delegates acquire/release to the Skedyul platform proxy.
4
+ */
5
+ export declare class PlatformRateLimitBackend implements RateLimitBackend {
6
+ acquire(queueKey: string, limits: QueueLimits, timeoutMs?: number): Promise<Lease>;
7
+ release(lease: Lease): Promise<void>;
8
+ }
9
+ export declare const platformRateLimitBackend: PlatformRateLimitBackend;
@@ -0,0 +1,10 @@
1
+ import type { SkedyulConfig } from '../config/app-config';
2
+ import type { QueueConfig, SerializableQueueConfig } from '../config/queue-config';
3
+ /**
4
+ * Register in-process server config for queue resolution at runtime.
5
+ */
6
+ export declare function registerQueueConfig(config: SkedyulConfig): void;
7
+ export declare function clearRegisteredQueueConfig(): void;
8
+ export declare function getQueueDefinitions(): Record<string, SerializableQueueConfig>;
9
+ export declare function getQueueConfig(queueName: string): SerializableQueueConfig | undefined;
10
+ export declare function getQueueConfigWithRetry(queueName: string): QueueConfig | undefined;
@@ -0,0 +1,7 @@
1
+ import type { RateLimitExecutionContext, ActiveQueuedOperation } from './types';
2
+ export declare function runWithRateLimitExecutionContext<T>(ctx: RateLimitExecutionContext, fn: () => T | Promise<T>): T | Promise<T>;
3
+ export declare function getRateLimitExecutionContext(): RateLimitExecutionContext | undefined;
4
+ export declare function runWithActiveQueuedOperation<T>(operation: ActiveQueuedOperation<T>, fn: () => Promise<T>): Promise<T>;
5
+ export declare function getActiveQueuedOperation<T>(): ActiveQueuedOperation<T> | undefined;
6
+ export declare function setActiveQueuedOperationLease(lease: ActiveQueuedOperation<unknown>['lease']): void;
7
+ export declare function updateActiveQueuedOperationAttempt(attempt: number): void;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Rate limit errors for queuedFetch.
3
+ */
4
+ export declare class QueueContextError extends Error {
5
+ readonly code = "QUEUE_CONTEXT_ERROR";
6
+ constructor(message: string);
7
+ }
8
+ export declare class QueueNotFoundError extends Error {
9
+ readonly code = "QUEUE_NOT_FOUND";
10
+ constructor(queueName: string);
11
+ }
12
+ export declare class QueuedFetchExhaustedError extends Error {
13
+ readonly code = "QUEUED_FETCH_EXHAUSTED";
14
+ readonly attempts: number;
15
+ readonly maxRetries: number;
16
+ readonly causeError: unknown;
17
+ constructor(attempts: number, maxRetries: number, causeError: unknown);
18
+ }
19
+ export declare class RequeueOutsideContextError extends Error {
20
+ readonly code = "REQUEUE_OUTSIDE_CONTEXT";
21
+ constructor();
22
+ }
23
+ export declare class RateLimitBackendError extends Error {
24
+ readonly code = "RATE_LIMIT_BACKEND_ERROR";
25
+ readonly statusCode?: number;
26
+ constructor(message: string, statusCode?: number);
27
+ }
@@ -0,0 +1,9 @@
1
+ export { queuedFetch, requeue, resolveQueue, createQueueHandle, queuedFetchResponse, } from './queued-fetch';
2
+ export type { QueueInput, QueueSelector, Lease, QueueLimits, ResolvedQueue, RateLimitExecutionContext, RateLimitBackend, } from './types';
3
+ export { QueueContextError, QueueNotFoundError, QueuedFetchExhaustedError, RequeueOutsideContextError, RateLimitBackendError, } from './errors';
4
+ export { registerQueueConfig, clearRegisteredQueueConfig, getQueueDefinitions, getQueueConfig, } from './config-loader';
5
+ export { runWithRateLimitExecutionContext, getRateLimitExecutionContext, } from './context';
6
+ export { resolveQueueKey, toQueueLimits } from './resolve-queue-key';
7
+ export { defaultShouldRetry } from './should-retry';
8
+ export { getRateLimitBackend, resetRateLimitBackendForTests, memoryRateLimitBackend, platformRateLimitBackend, } from './backends';
9
+ export type { QueueScope, QueueConfig, SerializableQueueConfig, QueueRegistry, } from '../config/queue-config';
@@ -0,0 +1,11 @@
1
+ import type { QueueInput, ResolvedQueue } from './types';
2
+ import type { RateLimitExecutionContext } from './types';
3
+ export declare function resolveQueue(queueInput: QueueInput, ctxOverride?: RateLimitExecutionContext): ResolvedQueue;
4
+ export declare function createQueueHandle(queueInput: QueueInput): {
5
+ run<T>(fn: () => Promise<T>): Promise<T>;
6
+ };
7
+ export declare function queuedFetch<T>(queueInput: QueueInput, fnOrPromise: (() => Promise<T>) | Promise<T>): Promise<T>;
8
+ export declare function requeue<T = unknown>(): Promise<T>;
9
+ export declare function queuedFetchResponse(queueInput: QueueInput, url: string, init?: RequestInit): Promise<Response>;
10
+ /** @deprecated Use createQueueHandle */
11
+ export declare const resolveQueueHandle: typeof createQueueHandle;
@@ -0,0 +1,14 @@
1
+ import type { SerializableQueueConfig } from '../config/queue-config';
2
+ import type { RateLimitExecutionContext } from './types';
3
+ /**
4
+ * Build the Redis queue key for a named queue definition.
5
+ * Must stay in sync with skedyul-core rate limit validation.
6
+ */
7
+ export declare function resolveQueueKey(queueName: string, config: SerializableQueueConfig, ctx: RateLimitExecutionContext, subKey?: string): string;
8
+ export declare function toQueueLimits(config: SerializableQueueConfig): {
9
+ maxConcurrent: number | undefined;
10
+ minTime: number | undefined;
11
+ reservoir: number | undefined;
12
+ reservoirRefreshAmount: number | undefined;
13
+ reservoirRefreshInterval: number | undefined;
14
+ };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Default retry predicate for queuedFetch — rate limits and transient 5xx.
3
+ */
4
+ export declare function defaultShouldRetry(error: unknown, _attempt: number): boolean;
5
+ export declare function sleep(ms: number): Promise<void>;
@@ -0,0 +1,43 @@
1
+ import type { SerializableQueueConfig } from '../config/queue-config';
2
+ import type { AppInfo } from '../types/shared';
3
+ import type { InvocationContext } from '../types/invocation';
4
+ export interface RateLimitExecutionContext {
5
+ app: AppInfo;
6
+ appInstallationId?: string;
7
+ invocation?: InvocationContext;
8
+ isProvisionContext?: boolean;
9
+ }
10
+ export interface QueueSelector {
11
+ queue: string;
12
+ key?: string;
13
+ }
14
+ export type QueueInput = string | QueueSelector;
15
+ export interface Lease {
16
+ leaseId: string;
17
+ acquiredAt: number;
18
+ queueKey: string;
19
+ }
20
+ export interface QueueLimits {
21
+ maxConcurrent?: number;
22
+ minTime?: number;
23
+ reservoir?: number;
24
+ reservoirRefreshAmount?: number;
25
+ reservoirRefreshInterval?: number;
26
+ }
27
+ export interface ResolvedQueue {
28
+ name: string;
29
+ queueKey: string;
30
+ config: SerializableQueueConfig;
31
+ limits: QueueLimits;
32
+ }
33
+ export interface RateLimitBackend {
34
+ acquire(queueKey: string, limits: QueueLimits, timeoutMs?: number): Promise<Lease>;
35
+ release(lease: Lease): Promise<void>;
36
+ }
37
+ export interface ActiveQueuedOperation<T> {
38
+ queueInput: QueueInput;
39
+ fn: () => Promise<T>;
40
+ attempt: number;
41
+ resolved: ResolvedQueue;
42
+ lease: Lease | null;
43
+ }
@@ -114,6 +114,9 @@ function isTimeInWindowSlot(date, window) {
114
114
  const tzInfo = getTimezoneInfo(date, tz);
115
115
  return allowedDays.includes(tzInfo.day) && tzInfo.totalMinutes >= windowStartMinutes && tzInfo.totalMinutes < windowEndMinutes;
116
116
  }
117
+ function getLocalDateKey(date, timezone) {
118
+ return date.toLocaleDateString("en-CA", { timeZone: timezone });
119
+ }
117
120
  function calculateWaitTime(step, now) {
118
121
  const nowTime = now.getTime();
119
122
  switch (step.mode) {
@@ -182,7 +185,7 @@ function calculateWaitTime(step, now) {
182
185
  }
183
186
  }
184
187
  let earliestScheduledTime = null;
185
- let earliestWaitTime = Infinity;
188
+ let earliestWaitFromNow = Infinity;
186
189
  for (const window of step.windows) {
187
190
  if (!window || window.startTime === void 0 || window.endTime === void 0) {
188
191
  continue;
@@ -200,12 +203,10 @@ function calculateWaitTime(step, now) {
200
203
  const currentHour = targetTzInfo.hour;
201
204
  const currentMinute = targetTzInfo.minute;
202
205
  const currentTotalMinutes = targetTzInfo.totalMinutes;
203
- let windowWaitTime;
204
- let windowScheduledTime;
206
+ let msUntilWindowStart;
205
207
  if (allowedDays.includes(currentDay) && currentTotalMinutes < windowStartMinutes) {
206
208
  const minutesUntilWindow = windowStartMinutes - currentTotalMinutes;
207
- windowWaitTime = minutesUntilWindow * 60 * 1e3;
208
- windowScheduledTime = new Date(targetDate.getTime() + windowWaitTime);
209
+ msUntilWindowStart = minutesUntilWindow * 60 * 1e3;
209
210
  } else {
210
211
  let daysToAdd = 7;
211
212
  for (let i = 1; i <= 7; i++) {
@@ -221,17 +222,20 @@ function calculateWaitTime(step, now) {
221
222
  const daysMs = daysToAdd * millisecondsPerDay;
222
223
  const hoursAdjustment = (windowStartHour - currentHour) * millisecondsPerHour;
223
224
  const minutesAdjustment = (windowStartMinute - currentMinute) * millisecondsPerMinute;
224
- windowWaitTime = daysMs + hoursAdjustment + minutesAdjustment;
225
- windowScheduledTime = new Date(targetDate.getTime() + windowWaitTime);
225
+ msUntilWindowStart = daysMs + hoursAdjustment + minutesAdjustment;
226
226
  }
227
- if (windowWaitTime < earliestWaitTime) {
228
- earliestWaitTime = windowWaitTime;
227
+ const windowStartTimeMs = targetDate.getTime() + msUntilWindowStart;
228
+ const sameLocalDayAsNow = getLocalDateKey(now, tz) === getLocalDateKey(targetDate, tz);
229
+ const windowScheduledTime = sameLocalDayAsNow ? new Date(windowStartTimeMs + relativeDelay) : new Date(windowStartTimeMs);
230
+ const waitFromNow = windowScheduledTime.getTime() - nowTime;
231
+ if (waitFromNow < earliestWaitFromNow) {
232
+ earliestWaitFromNow = waitFromNow;
229
233
  earliestScheduledTime = windowScheduledTime;
230
234
  }
231
235
  }
232
236
  if (earliestScheduledTime) {
233
237
  return {
234
- waitTime: relativeDelay + earliestWaitTime,
238
+ waitTime: Math.max(0, earliestScheduledTime.getTime() - nowTime),
235
239
  scheduledAt: earliestScheduledTime
236
240
  };
237
241
  }
@@ -86,6 +86,9 @@ function isTimeInWindowSlot(date, window) {
86
86
  const tzInfo = getTimezoneInfo(date, tz);
87
87
  return allowedDays.includes(tzInfo.day) && tzInfo.totalMinutes >= windowStartMinutes && tzInfo.totalMinutes < windowEndMinutes;
88
88
  }
89
+ function getLocalDateKey(date, timezone) {
90
+ return date.toLocaleDateString("en-CA", { timeZone: timezone });
91
+ }
89
92
  function calculateWaitTime(step, now) {
90
93
  const nowTime = now.getTime();
91
94
  switch (step.mode) {
@@ -154,7 +157,7 @@ function calculateWaitTime(step, now) {
154
157
  }
155
158
  }
156
159
  let earliestScheduledTime = null;
157
- let earliestWaitTime = Infinity;
160
+ let earliestWaitFromNow = Infinity;
158
161
  for (const window of step.windows) {
159
162
  if (!window || window.startTime === void 0 || window.endTime === void 0) {
160
163
  continue;
@@ -172,12 +175,10 @@ function calculateWaitTime(step, now) {
172
175
  const currentHour = targetTzInfo.hour;
173
176
  const currentMinute = targetTzInfo.minute;
174
177
  const currentTotalMinutes = targetTzInfo.totalMinutes;
175
- let windowWaitTime;
176
- let windowScheduledTime;
178
+ let msUntilWindowStart;
177
179
  if (allowedDays.includes(currentDay) && currentTotalMinutes < windowStartMinutes) {
178
180
  const minutesUntilWindow = windowStartMinutes - currentTotalMinutes;
179
- windowWaitTime = minutesUntilWindow * 60 * 1e3;
180
- windowScheduledTime = new Date(targetDate.getTime() + windowWaitTime);
181
+ msUntilWindowStart = minutesUntilWindow * 60 * 1e3;
181
182
  } else {
182
183
  let daysToAdd = 7;
183
184
  for (let i = 1; i <= 7; i++) {
@@ -193,17 +194,20 @@ function calculateWaitTime(step, now) {
193
194
  const daysMs = daysToAdd * millisecondsPerDay;
194
195
  const hoursAdjustment = (windowStartHour - currentHour) * millisecondsPerHour;
195
196
  const minutesAdjustment = (windowStartMinute - currentMinute) * millisecondsPerMinute;
196
- windowWaitTime = daysMs + hoursAdjustment + minutesAdjustment;
197
- windowScheduledTime = new Date(targetDate.getTime() + windowWaitTime);
197
+ msUntilWindowStart = daysMs + hoursAdjustment + minutesAdjustment;
198
198
  }
199
- if (windowWaitTime < earliestWaitTime) {
200
- earliestWaitTime = windowWaitTime;
199
+ const windowStartTimeMs = targetDate.getTime() + msUntilWindowStart;
200
+ const sameLocalDayAsNow = getLocalDateKey(now, tz) === getLocalDateKey(targetDate, tz);
201
+ const windowScheduledTime = sameLocalDayAsNow ? new Date(windowStartTimeMs + relativeDelay) : new Date(windowStartTimeMs);
202
+ const waitFromNow = windowScheduledTime.getTime() - nowTime;
203
+ if (waitFromNow < earliestWaitFromNow) {
204
+ earliestWaitFromNow = waitFromNow;
201
205
  earliestScheduledTime = windowScheduledTime;
202
206
  }
203
207
  }
204
208
  if (earliestScheduledTime) {
205
209
  return {
206
- waitTime: relativeDelay + earliestWaitTime,
210
+ waitTime: Math.max(0, earliestScheduledTime.getTime() - nowTime),
207
211
  scheduledAt: earliestScheduledTime
208
212
  };
209
213
  }
package/dist/server.js CHANGED
@@ -87,6 +87,16 @@ var CoreApiService = class {
87
87
  };
88
88
  var coreApiService = new CoreApiService();
89
89
 
90
+ // src/ratelimit/config-loader.ts
91
+ var fs = __toESM(require("fs"));
92
+ var path = __toESM(require("path"));
93
+ var cachedRuntimeQueues = null;
94
+ var registeredServerConfig = null;
95
+ function registerQueueConfig(config) {
96
+ registeredServerConfig = config;
97
+ cachedRuntimeQueues = config.queues ?? null;
98
+ }
99
+
90
100
  // src/server/utils/schema.ts
91
101
  var z = __toESM(require("zod"));
92
102
  function normalizeBilling(billing) {
@@ -265,6 +275,14 @@ function runWithConfig(config, fn) {
265
275
  return requestConfigStorage.run(config, fn);
266
276
  }
267
277
 
278
+ // src/ratelimit/context.ts
279
+ var import_async_hooks2 = require("async_hooks");
280
+ var executionContextStorage = new import_async_hooks2.AsyncLocalStorage();
281
+ var activeOperationStorage = new import_async_hooks2.AsyncLocalStorage();
282
+ function runWithRateLimitExecutionContext(ctx, fn) {
283
+ return executionContextStorage.run(ctx, fn);
284
+ }
285
+
268
286
  // src/errors.ts
269
287
  var InstallError = class extends Error {
270
288
  // Optional: which field caused the error
@@ -284,8 +302,8 @@ var AppAuthInvalidError = class extends Error {
284
302
  };
285
303
 
286
304
  // src/server/context-logger.ts
287
- var import_async_hooks2 = require("async_hooks");
288
- var logContextStorage = new import_async_hooks2.AsyncLocalStorage();
305
+ var import_async_hooks3 = require("async_hooks");
306
+ var logContextStorage = new import_async_hooks3.AsyncLocalStorage();
289
307
  function runWithLogContext(context, fn) {
290
308
  return logContextStorage.run(context, fn);
291
309
  }
@@ -498,9 +516,17 @@ function createCallToolHandler(registry, state, onMaxRequests) {
498
516
  baseUrl: toolEnv.SKEDYUL_API_URL ?? "",
499
517
  apiToken: toolEnv.SKEDYUL_API_TOKEN ?? ""
500
518
  };
519
+ const rateLimitContext = {
520
+ app,
521
+ appInstallationId: trigger === "provision" ? void 0 : rawContext.appInstallationId,
522
+ invocation,
523
+ isProvisionContext: trigger === "provision"
524
+ };
501
525
  const functionResult = await runWithConfig(requestConfig, async () => {
502
- return await runWithLogContext({ invocation }, async () => {
503
- return await fn(inputs, executionContext);
526
+ return await runWithRateLimitExecutionContext(rateLimitContext, async () => {
527
+ return await runWithLogContext({ invocation }, async () => {
528
+ return await fn(inputs, executionContext);
529
+ });
504
530
  });
505
531
  });
506
532
  const billing = normalizeBilling(functionResult.billing);
@@ -544,6 +570,7 @@ function createCallToolHandler(registry, state, onMaxRequests) {
544
570
  };
545
571
  }
546
572
  const errorMessage = error instanceof Error ? error.message : String(error ?? "");
573
+ const errorCode = error && typeof error === "object" && "code" in error && typeof error.code === "string" ? error.code : "TOOL_EXECUTION_ERROR";
547
574
  return {
548
575
  output: null,
549
576
  billing: { credits: 0 },
@@ -553,7 +580,7 @@ function createCallToolHandler(registry, state, onMaxRequests) {
553
580
  toolName
554
581
  },
555
582
  error: {
556
- code: "TOOL_EXECUTION_ERROR",
583
+ code: errorCode,
557
584
  message: errorMessage
558
585
  }
559
586
  };
@@ -637,7 +664,7 @@ function printStartupLog(config, tools, port) {
637
664
 
638
665
  // src/server/route-handlers/adapters.ts
639
666
  function fromLambdaEvent(event) {
640
- const path2 = event.path || event.rawPath || "/";
667
+ const path3 = event.path || event.rawPath || "/";
641
668
  const method = event.httpMethod || event.requestContext?.http?.method || "POST";
642
669
  const forwardedProto = event.headers?.["x-forwarded-proto"] ?? event.headers?.["X-Forwarded-Proto"];
643
670
  const protocol = forwardedProto ?? "https";
@@ -645,9 +672,9 @@ function fromLambdaEvent(event) {
645
672
  const queryString = event.queryStringParameters ? "?" + new URLSearchParams(
646
673
  event.queryStringParameters
647
674
  ).toString() : "";
648
- const url = `${protocol}://${host}${path2}${queryString}`;
675
+ const url = `${protocol}://${host}${path3}${queryString}`;
649
676
  return {
650
- path: path2,
677
+ path: path3,
651
678
  method,
652
679
  headers: event.headers,
653
680
  query: event.queryStringParameters ?? {},
@@ -729,8 +756,8 @@ function parseBodyByContentType(req) {
729
756
  }
730
757
 
731
758
  // src/server/route-handlers/handlers.ts
732
- var fs = __toESM(require("fs"));
733
- var path = __toESM(require("path"));
759
+ var fs2 = __toESM(require("fs"));
760
+ var path2 = __toESM(require("path"));
734
761
 
735
762
  // src/server/core-api-handler.ts
736
763
  async function handleCoreMethod(method, params) {
@@ -898,7 +925,8 @@ function serializeConfig(config) {
898
925
  type: w.type ?? "WEBHOOK"
899
926
  })) : [],
900
927
  provision: isProvisionConfig(config.provision) ? config.provision : void 0,
901
- agents: config.agents
928
+ agents: config.agents,
929
+ queues: config.queues
902
930
  };
903
931
  }
904
932
  function isProvisionConfig(value) {
@@ -1194,7 +1222,7 @@ async function handleOAuthCallback(parsedBody, hooks) {
1194
1222
  }
1195
1223
 
1196
1224
  // src/server/handlers/webhook-handler.ts
1197
- function parseWebhookRequest(parsedBody, method, url, path2, headers, query, rawBody, appIdHeader, appVersionIdHeader) {
1225
+ function parseWebhookRequest(parsedBody, method, url, path3, headers, query, rawBody, appIdHeader, appVersionIdHeader) {
1198
1226
  const isEnvelope = typeof parsedBody === "object" && parsedBody !== null && "env" in parsedBody && "request" in parsedBody && "context" in parsedBody;
1199
1227
  if (isEnvelope) {
1200
1228
  const envelope = parsedBody;
@@ -1248,7 +1276,7 @@ function parseWebhookRequest(parsedBody, method, url, path2, headers, query, raw
1248
1276
  const webhookRequest = {
1249
1277
  method,
1250
1278
  url,
1251
- path: path2,
1279
+ path: path3,
1252
1280
  headers,
1253
1281
  query,
1254
1282
  body: parsedBody,
@@ -1306,7 +1334,7 @@ function isMethodAllowed(webhookRegistry, handle, method) {
1306
1334
 
1307
1335
  // src/server/route-handlers/handlers.ts
1308
1336
  function getConfigFilePath() {
1309
- return process.env.LAMBDA_TASK_ROOT ? path.join(process.env.LAMBDA_TASK_ROOT, ".skedyul", "config.json") : ".skedyul/config.json";
1337
+ return process.env.LAMBDA_TASK_ROOT ? path2.join(process.env.LAMBDA_TASK_ROOT, ".skedyul", "config.json") : ".skedyul/config.json";
1310
1338
  }
1311
1339
  function findToolInRegistry(registry, toolName) {
1312
1340
  for (const [key, t] of Object.entries(registry)) {
@@ -1345,8 +1373,8 @@ function handleHealthRoute(ctx) {
1345
1373
  function handleConfigRoute(ctx) {
1346
1374
  const configFilePath = getConfigFilePath();
1347
1375
  try {
1348
- if (fs.existsSync(configFilePath)) {
1349
- const fileConfig = JSON.parse(fs.readFileSync(configFilePath, "utf-8"));
1376
+ if (fs2.existsSync(configFilePath)) {
1377
+ const fileConfig = JSON.parse(fs2.readFileSync(configFilePath, "utf-8"));
1350
1378
  return { status: 200, body: fileConfig };
1351
1379
  }
1352
1380
  } catch (err) {
@@ -2031,6 +2059,7 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
2031
2059
  installContextLogger();
2032
2060
  function createSkedyulServer(config) {
2033
2061
  mergeRuntimeEnv();
2062
+ registerQueueConfig(config);
2034
2063
  const registry = config.tools;
2035
2064
  const webhookRegistry = config.webhooks;
2036
2065
  if (config.coreApi?.service) {
@@ -48,6 +48,16 @@ var CoreApiService = class {
48
48
  };
49
49
  var coreApiService = new CoreApiService();
50
50
 
51
+ // src/ratelimit/config-loader.ts
52
+ import * as fs from "fs";
53
+ import * as path from "path";
54
+ var cachedRuntimeQueues = null;
55
+ var registeredServerConfig = null;
56
+ function registerQueueConfig(config) {
57
+ registeredServerConfig = config;
58
+ cachedRuntimeQueues = config.queues ?? null;
59
+ }
60
+
51
61
  // src/server/utils/schema.ts
52
62
  import * as z from "zod";
53
63
  function normalizeBilling(billing) {
@@ -226,6 +236,14 @@ function runWithConfig(config, fn) {
226
236
  return requestConfigStorage.run(config, fn);
227
237
  }
228
238
 
239
+ // src/ratelimit/context.ts
240
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
241
+ var executionContextStorage = new AsyncLocalStorage2();
242
+ var activeOperationStorage = new AsyncLocalStorage2();
243
+ function runWithRateLimitExecutionContext(ctx, fn) {
244
+ return executionContextStorage.run(ctx, fn);
245
+ }
246
+
229
247
  // src/errors.ts
230
248
  var InstallError = class extends Error {
231
249
  // Optional: which field caused the error
@@ -245,8 +263,8 @@ var AppAuthInvalidError = class extends Error {
245
263
  };
246
264
 
247
265
  // src/server/context-logger.ts
248
- import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
249
- var logContextStorage = new AsyncLocalStorage2();
266
+ import { AsyncLocalStorage as AsyncLocalStorage3 } from "async_hooks";
267
+ var logContextStorage = new AsyncLocalStorage3();
250
268
  function runWithLogContext(context, fn) {
251
269
  return logContextStorage.run(context, fn);
252
270
  }
@@ -459,9 +477,17 @@ function createCallToolHandler(registry, state, onMaxRequests) {
459
477
  baseUrl: toolEnv.SKEDYUL_API_URL ?? "",
460
478
  apiToken: toolEnv.SKEDYUL_API_TOKEN ?? ""
461
479
  };
480
+ const rateLimitContext = {
481
+ app,
482
+ appInstallationId: trigger === "provision" ? void 0 : rawContext.appInstallationId,
483
+ invocation,
484
+ isProvisionContext: trigger === "provision"
485
+ };
462
486
  const functionResult = await runWithConfig(requestConfig, async () => {
463
- return await runWithLogContext({ invocation }, async () => {
464
- return await fn(inputs, executionContext);
487
+ return await runWithRateLimitExecutionContext(rateLimitContext, async () => {
488
+ return await runWithLogContext({ invocation }, async () => {
489
+ return await fn(inputs, executionContext);
490
+ });
465
491
  });
466
492
  });
467
493
  const billing = normalizeBilling(functionResult.billing);
@@ -505,6 +531,7 @@ function createCallToolHandler(registry, state, onMaxRequests) {
505
531
  };
506
532
  }
507
533
  const errorMessage = error instanceof Error ? error.message : String(error ?? "");
534
+ const errorCode = error && typeof error === "object" && "code" in error && typeof error.code === "string" ? error.code : "TOOL_EXECUTION_ERROR";
508
535
  return {
509
536
  output: null,
510
537
  billing: { credits: 0 },
@@ -514,7 +541,7 @@ function createCallToolHandler(registry, state, onMaxRequests) {
514
541
  toolName
515
542
  },
516
543
  error: {
517
- code: "TOOL_EXECUTION_ERROR",
544
+ code: errorCode,
518
545
  message: errorMessage
519
546
  }
520
547
  };
@@ -598,7 +625,7 @@ function printStartupLog(config, tools, port) {
598
625
 
599
626
  // src/server/route-handlers/adapters.ts
600
627
  function fromLambdaEvent(event) {
601
- const path2 = event.path || event.rawPath || "/";
628
+ const path3 = event.path || event.rawPath || "/";
602
629
  const method = event.httpMethod || event.requestContext?.http?.method || "POST";
603
630
  const forwardedProto = event.headers?.["x-forwarded-proto"] ?? event.headers?.["X-Forwarded-Proto"];
604
631
  const protocol = forwardedProto ?? "https";
@@ -606,9 +633,9 @@ function fromLambdaEvent(event) {
606
633
  const queryString = event.queryStringParameters ? "?" + new URLSearchParams(
607
634
  event.queryStringParameters
608
635
  ).toString() : "";
609
- const url = `${protocol}://${host}${path2}${queryString}`;
636
+ const url = `${protocol}://${host}${path3}${queryString}`;
610
637
  return {
611
- path: path2,
638
+ path: path3,
612
639
  method,
613
640
  headers: event.headers,
614
641
  query: event.queryStringParameters ?? {},
@@ -690,8 +717,8 @@ function parseBodyByContentType(req) {
690
717
  }
691
718
 
692
719
  // src/server/route-handlers/handlers.ts
693
- import * as fs from "fs";
694
- import * as path from "path";
720
+ import * as fs2 from "fs";
721
+ import * as path2 from "path";
695
722
 
696
723
  // src/server/core-api-handler.ts
697
724
  async function handleCoreMethod(method, params) {
@@ -859,7 +886,8 @@ function serializeConfig(config) {
859
886
  type: w.type ?? "WEBHOOK"
860
887
  })) : [],
861
888
  provision: isProvisionConfig(config.provision) ? config.provision : void 0,
862
- agents: config.agents
889
+ agents: config.agents,
890
+ queues: config.queues
863
891
  };
864
892
  }
865
893
  function isProvisionConfig(value) {
@@ -1155,7 +1183,7 @@ async function handleOAuthCallback(parsedBody, hooks) {
1155
1183
  }
1156
1184
 
1157
1185
  // src/server/handlers/webhook-handler.ts
1158
- function parseWebhookRequest(parsedBody, method, url, path2, headers, query, rawBody, appIdHeader, appVersionIdHeader) {
1186
+ function parseWebhookRequest(parsedBody, method, url, path3, headers, query, rawBody, appIdHeader, appVersionIdHeader) {
1159
1187
  const isEnvelope = typeof parsedBody === "object" && parsedBody !== null && "env" in parsedBody && "request" in parsedBody && "context" in parsedBody;
1160
1188
  if (isEnvelope) {
1161
1189
  const envelope = parsedBody;
@@ -1209,7 +1237,7 @@ function parseWebhookRequest(parsedBody, method, url, path2, headers, query, raw
1209
1237
  const webhookRequest = {
1210
1238
  method,
1211
1239
  url,
1212
- path: path2,
1240
+ path: path3,
1213
1241
  headers,
1214
1242
  query,
1215
1243
  body: parsedBody,
@@ -1267,7 +1295,7 @@ function isMethodAllowed(webhookRegistry, handle, method) {
1267
1295
 
1268
1296
  // src/server/route-handlers/handlers.ts
1269
1297
  function getConfigFilePath() {
1270
- return process.env.LAMBDA_TASK_ROOT ? path.join(process.env.LAMBDA_TASK_ROOT, ".skedyul", "config.json") : ".skedyul/config.json";
1298
+ return process.env.LAMBDA_TASK_ROOT ? path2.join(process.env.LAMBDA_TASK_ROOT, ".skedyul", "config.json") : ".skedyul/config.json";
1271
1299
  }
1272
1300
  function findToolInRegistry(registry, toolName) {
1273
1301
  for (const [key, t] of Object.entries(registry)) {
@@ -1306,8 +1334,8 @@ function handleHealthRoute(ctx) {
1306
1334
  function handleConfigRoute(ctx) {
1307
1335
  const configFilePath = getConfigFilePath();
1308
1336
  try {
1309
- if (fs.existsSync(configFilePath)) {
1310
- const fileConfig = JSON.parse(fs.readFileSync(configFilePath, "utf-8"));
1337
+ if (fs2.existsSync(configFilePath)) {
1338
+ const fileConfig = JSON.parse(fs2.readFileSync(configFilePath, "utf-8"));
1311
1339
  return { status: 200, body: fileConfig };
1312
1340
  }
1313
1341
  } catch (err) {
@@ -1992,6 +2020,7 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
1992
2020
  installContextLogger();
1993
2021
  function createSkedyulServer(config) {
1994
2022
  mergeRuntimeEnv();
2023
+ registerQueueConfig(config);
1995
2024
  const registry = config.tools;
1996
2025
  const webhookRegistry = config.webhooks;
1997
2026
  if (config.coreApi?.service) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skedyul",
3
- "version": "1.2.51",
3
+ "version": "1.3.1",
4
4
  "description": "The Skedyul SDK for Node.js",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -64,7 +64,7 @@
64
64
  "build": "rm -rf dist tsconfig.tsbuildinfo && tsc --emitDeclarationOnly && tsup",
65
65
  "db:generate": "echo 'No database generation needed for this package'",
66
66
  "dev": "npx nodemon --watch src --ext js,ts --exec \"pnpm run build\"",
67
- "test": "tsc --project tsconfig.tests.json && node --test dist-tests/tests/server.test.js dist-tests/tests/cli.test.js dist-tests/tests/agent-schema-v3.test.js"
67
+ "test": "tsc --project tsconfig.tests.json && node --test dist-tests/tests/server.test.js dist-tests/tests/cli.test.js dist-tests/tests/agent-schema-v3.test.js dist-tests/tests/ratelimit.test.js"
68
68
  },
69
69
  "keywords": [
70
70
  "mcp",
@@ -90,6 +90,6 @@
90
90
  "@types/node": "^24.10.1",
91
91
  "open": "^10.1.0",
92
92
  "tsup": "^8.0.0",
93
- "typescript": "^5.5.0"
93
+ "typescript": "^6.0.3"
94
94
  }
95
95
  }